diff --git a/bower.json b/bower.json index 008f714..032b202 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "mockfirebase", - "version": "0.7.0", + "version": "0.8.0", "homepage": "https://github.com/katowulf/mockfirebase", "authors": [ "Kato" diff --git a/browser/mockfirebase.js b/browser/mockfirebase.js index 4953067..bc59854 100755 --- a/browser/mockfirebase.js +++ b/browser/mockfirebase.js @@ -1,4 +1,4 @@ -/** mockfirebase - v0.7.0 +/** mockfirebase - v0.8.0 https://github.com/katowulf/mockfirebase * Copyright (c) 2014 Kato * License: MIT */ @@ -6,6 +6,7 @@ https://github.com/katowulf/mockfirebase 'use strict'; exports.MockFirebase = require('./firebase'); +/** @deprecated */ exports.MockFirebaseSimpleLogin = require('./login'); },{"./firebase":15,"./login":16}],2:[function(require,module,exports){ @@ -1507,7 +1508,7 @@ Buffer.prototype.copy = function (target, target_start, start, end) { var len = end - start - if (len < 100 || !Buffer.TYPED_ARRAY_SUPPORT) { + if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { for (var i = 0; i < len; i++) { target[i + target_start] = this[i + start] } @@ -1576,6 +1577,7 @@ var BP = Buffer.prototype * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods */ Buffer._augment = function (arr) { + arr.constructor = Buffer arr._isBuffer = true // save reference to original Uint8Array get/set methods before overwriting @@ -9557,6 +9559,12 @@ function MockFirebase(currentPath, data, parent, name) { // stores the last auto id generated by push() for tests this._lastAutoId = null; + + this._authUserData = null; + this._authListeners = []; + this._authCompletionListeners = []; + this._users = {}; + this._uidCounter = 1; } MockFirebase.prototype = { @@ -9701,7 +9709,7 @@ MockFirebase.prototype = { * * @param {string} event * @param {string} key - * @param data + * @param [data] * @param {string} [prevChild] * @param [pri] * @returns {MockFirebase} @@ -9727,6 +9735,29 @@ MockFirebase.prototype = { return this; }, + /** + * Modifies authentication state and changes the current user credentials. This will trigger + * any onAuth() listeners in the next flush() operation. + * + * @param {Object|null} userData if an object, it should contain all field specified here: https://www.firebase.com/docs/web/api/firebase/onauth.html + */ + changeAuthState: function (userData) { + var self = this; + this._defer(function() { + if (!_.isEqual(self._authUserData, userData)) { + self._authUserData = _.isObject(userData) ? userData : null; + self._triggerAuthEvent(); + } + }); + }, + + /** + * Retrieve an email user that was added with createUser() + */ + getEmailUser: function(email) { + return this._users.hasOwnProperty(email) ? _.extend({}, this._users[email]) : null; + }, + /***************************************************** * Firebase API methods *****************************************************/ @@ -9943,26 +9974,150 @@ MockFirebase.prototype = { return [valueFn, finishedFn, applyLocally]; }, + /********************************************************** + * AUTHENTICATION METHODS + **********************************************************/ + /** * If token is valid and parses, returns the contents of token as exected. If not, the error is returned. * Does not change behavior in any way (since we don't really auth anywhere) * * @param {String} token * @param {Function} [callback] + * @deprecated */ auth: function(token, callback) { - //todo invoke callback with the parsed token contents - var err = this._nextErr('auth'); - if (callback) { + console.warn('FIREBASE WARNING: FirebaseRef.auth() being deprecated. Please use FirebaseRef.authWithCustomToken() instead.'); + this._authEvent('auth', callback); + }, + + /** + * If token is valid and parses, returns the contents of token as exected. If not, the error is returned. + * Does not change behavior in any way (since we don't really auth anywhere) + * + * @param {String} token + * @param {Function} [onComplete] + */ + authWithCustomToken: function(token, onComplete) { + this._authEvent('authWithCustomToken', onComplete); + }, + + authAnonymously: function(onComplete) { + this._authEvent('authAnonymously', onComplete); + }, + + authWithPassword: function(credentials, onComplete) { + this._authEvent('authWithPassword', onComplete); + }, + + authWithOAuthPopup: function(provider, onComplete) { + this._authEvent('authWithOAuthPopup', onComplete); + }, + + authWithOAuthRedirect: function(provider, onComplete) { + this._authEvent('authWithOAuthRedirect', onComplete); + }, + + authWithOAuthToken: function(provider, credentials, onComplete) { + this._authEvent('authWithOAuthToken', onComplete); + }, + + _authEvent: function(method, callback) { + var err = this._nextErr(method); + if (!callback) return; + if (err) { + // if an error occurs, we defer the error report until the next flush() + // event is triggered this._defer(function() { - var auth = { auth: { id: 'test', _token: token }, expires: (new Date()).to_i / 1000 }; - if( err === null ) { - callback(null, auth); - } else { - callback(err); - } + callback(err, null); }); } + else { + // if there is no error, then we just add our callback to the listener + // stack and wait for the next changeAuthState() call. + this._authCompletionListeners.push({fn: callback}); + } + }, + + getAuth: function() { + return this._authUserData; + }, + + onAuth: function(onComplete, context) { + this._authListeners.push({fn: onComplete, ctx: context}); + }, + + offAuth: function(onComplete, context) { + var index = _.findIndex(this._authListeners, function(v) { + return v.fn === onComplete && v.ctx === context; + }); + if (index > -1) { + this._authListeners.splice(index, 1); + } + }, + + unauth: function() { + if (this._authUserData !== null) { + this._authUserData = null; + this._triggerAuthEvent(); + } + }, + + createUser: function(credentials, onComplete) { + var err = this._nextErr('createUser'); + var users = this._users; + this._defer(_.bind(function() { + var user = null; + err = err || + this._validateCreds('createUser', credentials, ['email', 'password']) || + this._validateNewEmail(credentials); + if( !err ) { + var key = credentials.email; + users[key] = {uid: this._nextUid(), email: key, password: credentials.password}; + user = {uid: users[key].uid}; + } + onComplete(err, user); + }, this)); + }, + + changePassword: function(credentials, onComplete) { + var err = this._nextErr('changePassword'); + this._defer(_.bind(function() { + err = err || + this._validateCreds('changePassword', credentials, ['email', 'oldPassword', 'newPassword']) || + this._validateExistingEmail(credentials) || + this._validPass(credentials, 'oldPassword'); + if( !err ) { + var key = credentials.email; + var user = this._users[key]; + user.password = credentials.newPassword; + } + onComplete(err); + }, this)); + }, + + removeUser: function(credentials, onComplete) { + var err = this._nextErr('removeUser'); + this._defer(_.bind(function() { + err = err || + this._validateCreds('removeUser', credentials, ['email', 'password']) || + this._validateExistingEmail(credentials) || + this._validPass(credentials, 'password'); + if( !err ) { + delete this._users[credentials.email]; + } + onComplete(err); + }, this)); + }, + + resetPassword: function(credentials, onComplete) { + var err = this._nextErr('resetPassword'); + this._defer(_.bind(function() { + err = err || + this._validateCreds('resetPassword', credentials, ['email']) || + this._validateExistingEmail(credentials); + onComplete(err); + }, this)); }, /** @@ -9985,6 +10140,65 @@ MockFirebase.prototype = { * Private/internal methods *****************************************************/ + _nextUid: function() { + return 'simplelogin:'+(this._uidCounter++); + }, + + _validateNewEmail: function(creds) { + creds = _.assign({}, creds); + if( this._users.hasOwnProperty(creds.email) ) { + var err = new Error('The specified email address is already in use.'); + err.code = 'EMAIL_TAKEN'; + return err; + } + return null; + }, + + _validateExistingEmail: function(creds) { + creds = _.assign({}, creds); + if( !this._users.hasOwnProperty(creds.email) ) { + var err = new Error('The specified user does not exist.'); + err.code = 'INVALID_USER'; + return err; + } + return null; + }, + + _validateCreds: function(method, creds, fields) { + var err = this._validObj(creds, method, 'First'); + var i = 0; + while (err === null && i < fields.length) { + err = this._validArg(method, creds, 'First', fields[i], 'string'); + i++; + } + return err; + }, + + _validPass: function(obj, name) { + var err = null; + var key = obj.email; + if( obj[name] !== this._users[key].password ) { + err = new Error('The specified password is incorrect.'); + err.code = 'INVALID_PASSWORD'; + } + return err; + }, + + _validObj: function(obj, method, position) { + if( !_.isObject(obj) ) { + return new Error('Firebase.' + method + ' failed: ' + position + ' argument must be a valid object.'); + } + return null; + }, + + _validArg: function(method, obj, position, name, type) { + if( !obj.hasOwnProperty(name) || typeof(obj[name]) !== type ) { + return new Error('Firebase.' + method + ' failed: ' + position + + ' argument must contain the key "' + name + '" with type "' + type + '"'); + } + return null; + }, + _childChanged: function(ref) { var events = []; var childKey = ref.key(); @@ -10107,6 +10321,21 @@ MockFirebase.prototype = { } }, + _triggerAuthEvent: function() { + var list = this._authCompletionListeners; + // clear the completion list before triggering callbacks + this._authCompletionListeners = []; + var user = this._authUserData; + // trigger completion listeners first + _.forEach(list, function(parts) { + parts.fn.call(parts.ctx, null, _.cloneDeep(user)); + }); + // then trigger onAuth listeners + _.forEach(this._authListeners, function(parts) { + parts.fn.call(parts.ctx, _.cloneDeep(user)); + }); + }, + _updateOrAdd: function(key, data, events) { var exists = _.isObject(this.data) && this.data.hasOwnProperty(key); if( !exists ) { @@ -10217,6 +10446,7 @@ var md5 = require('MD5'); /******************************************************************************* * SIMPLE LOGIN + * @deprecated ******************************************************************************/ function MockFirebaseSimpleLogin (ref, callback, userData) { // allows test units to monitor the callback function to make sure diff --git a/package.json b/package.json index bb5bfbf..9319548 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mockfirebase", - "version": "0.7.0", + "version": "0.8.0", "description": "An experimental Firebase stub/spy library for writing unit tests (not supported by Firebase)", "main": "./src", "scripts": {