(https://www.firebase.com/)"
],
diff --git a/dist/angularfire.js b/dist/angularfire.js
new file mode 100644
index 00000000..3f416358
--- /dev/null
+++ b/dist/angularfire.js
@@ -0,0 +1,2173 @@
+/*!
+ * AngularFire is the officially supported AngularJS binding for Firebase. Firebase
+ * is a full backend so you don't need servers to build your Angular app. AngularFire
+ * provides you with the $firebase service which allows you to easily keep your $scope
+ * variables in sync with your Firebase backend.
+ *
+ * AngularFire 0.9.0
+ * https://github.com/firebase/angularfire/
+ * Date: 11/18/2014
+ * License: MIT
+ */
+(function(exports) {
+ "use strict";
+
+// Define the `firebase` module under which all AngularFire
+// services will live.
+ angular.module("firebase", [])
+ //todo use $window
+ .value("Firebase", exports.Firebase)
+
+ // used in conjunction with firebaseUtils.debounce function, this is the
+ // amount of time we will wait for additional records before triggering
+ // Angular's digest scope to dirty check and re-render DOM elements. A
+ // larger number here significantly improves performance when working with
+ // big data sets that are frequently changing in the DOM, but delays the
+ // speed at which each record is rendered in real-time. A number less than
+ // 100ms will usually be optimal.
+ .value('firebaseBatchDelay', 50 /* milliseconds */);
+
+})(window);
+(function() {
+ 'use strict';
+ /**
+ * Creates and maintains a synchronized list of data. This constructor should not be
+ * manually invoked. Instead, one should create a $firebase object and call $asArray
+ * on it: $firebase( firebaseRef ).$asArray()
;
+ *
+ * Internally, the $firebase object depends on this class to provide 5 $$ methods, which it invokes
+ * to notify the array whenever a change has been made at the server:
+ * $$added - called whenever a child_added event occurs
+ * $$updated - called whenever a child_changed event occurs
+ * $$moved - called whenever a child_moved event occurs
+ * $$removed - called whenever a child_removed event occurs
+ * $$error - called when listeners are canceled due to a security error
+ * $$process - called immediately after $$added/$$updated/$$moved/$$removed
+ * to splice/manipulate the array and invokes $$notify
+ *
+ * Additionally, these methods may be of interest to devs extending this class:
+ * $$notify - triggers notifications to any $watch listeners, called by $$process
+ * $$getKey - determines how to look up a record's key (returns $id by default)
+ *
+ * Instead of directly modifying this class, one should generally use the $extendFactory
+ * method to add or change how methods behave. $extendFactory modifies the prototype of
+ * the array class by returning a clone of $FirebaseArray.
+ *
+ *
+ * var NewFactory = $FirebaseArray.$extendFactory({
+ * // add a new method to the prototype
+ * foo: function() { return 'bar'; },
+ *
+ * // change how records are created
+ * $$added: function(snap, prevChild) {
+ * return new Widget(snap, prevChild);
+ * },
+ *
+ * // change how records are updated
+ * $$updated: function(snap) {
+ * return this.$getRecord(snap.key()).update(snap);
+ * }
+ * });
+ *
+ *
+ * And then the new factory can be passed as an argument:
+ * $firebase( firebaseRef, {arrayFactory: NewFactory}).$asArray();
+ */
+ angular.module('firebase').factory('$FirebaseArray', ["$log", "$firebaseUtils",
+ function($log, $firebaseUtils) {
+ /**
+ * This constructor should probably never be called manually. It is used internally by
+ * $firebase.$asArray()
.
+ *
+ * @param $firebase
+ * @param {Function} destroyFn invoking this will cancel all event listeners and stop
+ * notifications from being delivered to $$added, $$updated, $$moved, and $$removed
+ * @param readyPromise resolved when the initial data downloaded from Firebase
+ * @returns {Array}
+ * @constructor
+ */
+ function FirebaseArray($firebase, destroyFn, readyPromise) {
+ var self = this;
+ this._observers = [];
+ this.$list = [];
+ this._inst = $firebase;
+ this._promise = readyPromise;
+ this._destroyFn = destroyFn;
+
+ // indexCache is a weak hashmap (a lazy list) of keys to array indices,
+ // items are not guaranteed to stay up to date in this list (since the data
+ // array can be manually edited without calling the $ methods) and it should
+ // always be used with skepticism regarding whether it is accurate
+ // (see $indexFor() below for proper usage)
+ this._indexCache = {};
+
+ // Array.isArray will not work on objects which extend the Array class.
+ // So instead of extending the Array class, we just return an actual array.
+ // However, it's still possible to extend FirebaseArray and have the public methods
+ // appear on the array object. We do this by iterating the prototype and binding
+ // any method that is not prefixed with an underscore onto the final array.
+ $firebaseUtils.getPublicMethods(self, function(fn, key) {
+ self.$list[key] = fn.bind(self);
+ });
+
+ return this.$list;
+ }
+
+ FirebaseArray.prototype = {
+ /**
+ * Create a new record with a unique ID and add it to the end of the array.
+ * This should be used instead of Array.prototype.push, since those changes will not be
+ * synchronized with the server.
+ *
+ * Any value, including a primitive, can be added in this way. Note that when the record
+ * is created, the primitive value would be stored in $value (records are always objects
+ * by default).
+ *
+ * Returns a future which is resolved when the data has successfully saved to the server.
+ * The resolve callback will be passed a Firebase ref representing the new data element.
+ *
+ * @param data
+ * @returns a promise resolved after data is added
+ */
+ $add: function(data) {
+ this._assertNotDestroyed('$add');
+ return this.$inst().$push($firebaseUtils.toJSON(data));
+ },
+
+ /**
+ * Pass either an item in the array or the index of an item and it will be saved back
+ * to Firebase. While the array is read-only and its structure should not be changed,
+ * it is okay to modify properties on the objects it contains and then save those back
+ * individually.
+ *
+ * Returns a future which is resolved when the data has successfully saved to the server.
+ * The resolve callback will be passed a Firebase ref representing the saved element.
+ * If passed an invalid index or an object which is not a record in this array,
+ * the promise will be rejected.
+ *
+ * @param {int|object} indexOrItem
+ * @returns a promise resolved after data is saved
+ */
+ $save: function(indexOrItem) {
+ this._assertNotDestroyed('$save');
+ var self = this;
+ var item = self._resolveItem(indexOrItem);
+ var key = self.$keyAt(item);
+ if( key !== null ) {
+ return self.$inst().$set(key, $firebaseUtils.toJSON(item))
+ .then(function(ref) {
+ self.$$notify('child_changed', key);
+ return ref;
+ });
+ }
+ else {
+ return $firebaseUtils.reject('Invalid record; could determine its key: '+indexOrItem);
+ }
+ },
+
+ /**
+ * Pass either an existing item in this array or the index of that item and it will
+ * be removed both locally and in Firebase. This should be used in place of
+ * Array.prototype.splice for removing items out of the array, as calling splice
+ * will not update the value on the server.
+ *
+ * Returns a future which is resolved when the data has successfully removed from the
+ * server. The resolve callback will be passed a Firebase ref representing the deleted
+ * element. If passed an invalid index or an object which is not a record in this array,
+ * the promise will be rejected.
+ *
+ * @param {int|object} indexOrItem
+ * @returns a promise which resolves after data is removed
+ */
+ $remove: function(indexOrItem) {
+ this._assertNotDestroyed('$remove');
+ var key = this.$keyAt(indexOrItem);
+ if( key !== null ) {
+ return this.$inst().$remove(key);
+ }
+ else {
+ return $firebaseUtils.reject('Invalid record; could not find key: '+indexOrItem);
+ }
+ },
+
+ /**
+ * Given an item in this array or the index of an item in the array, this returns the
+ * Firebase key (record.$id) for that record. If passed an invalid key or an item which
+ * does not exist in this array, it will return null.
+ *
+ * @param {int|object} indexOrItem
+ * @returns {null|string}
+ */
+ $keyAt: function(indexOrItem) {
+ var item = this._resolveItem(indexOrItem);
+ return this.$$getKey(item);
+ },
+
+ /**
+ * The inverse of $keyAt, this method takes a Firebase key (record.$id) and returns the
+ * index in the array where that record is stored. If the record is not in the array,
+ * this method returns -1.
+ *
+ * @param {String} key
+ * @returns {int} -1 if not found
+ */
+ $indexFor: function(key) {
+ var self = this;
+ var cache = self._indexCache;
+ // evaluate whether our key is cached and, if so, whether it is up to date
+ if( !cache.hasOwnProperty(key) || self.$keyAt(cache[key]) !== key ) {
+ // update the hashmap
+ var pos = self.$list.findIndex(function(rec) { return self.$$getKey(rec) === key; });
+ if( pos !== -1 ) {
+ cache[key] = pos;
+ }
+ }
+ return cache.hasOwnProperty(key)? cache[key] : -1;
+ },
+
+ /**
+ * The loaded method is invoked after the initial batch of data arrives from the server.
+ * When this resolves, all data which existed prior to calling $asArray() is now cached
+ * locally in the array.
+ *
+ * As a shortcut is also possible to pass resolve/reject methods directly into this
+ * method just as they would be passed to .then()
+ *
+ * @param {Function} [resolve]
+ * @param {Function} [reject]
+ * @returns a promise
+ */
+ $loaded: function(resolve, reject) {
+ var promise = this._promise;
+ if( arguments.length ) {
+ promise = promise.then.call(promise, resolve, reject);
+ }
+ return promise;
+ },
+
+ /**
+ * @returns the original $firebase object used to create this object.
+ */
+ $inst: function() { return this._inst; },
+
+ /**
+ * Listeners passed into this method are notified whenever a new change (add, updated,
+ * move, remove) is received from the server. Each invocation is sent an object
+ * containing { type: 'added|updated|moved|removed', key: 'key_of_item_affected'}
+ *
+ * Additionally, added and moved events receive a prevChild parameter, containing the
+ * key of the item before this one in the array.
+ *
+ * This method returns a function which can be invoked to stop observing events.
+ *
+ * @param {Function} cb
+ * @param {Object} [context]
+ * @returns {Function} used to stop observing
+ */
+ $watch: function(cb, context) {
+ var list = this._observers;
+ list.push([cb, context]);
+ // an off function for cancelling the listener
+ return function() {
+ var i = list.findIndex(function(parts) {
+ return parts[0] === cb && parts[1] === context;
+ });
+ if( i > -1 ) {
+ list.splice(i, 1);
+ }
+ };
+ },
+
+ /**
+ * Informs $firebase to stop sending events and clears memory being used
+ * by this array (delete's its local content).
+ */
+ $destroy: function(err) {
+ if( !this._isDestroyed ) {
+ this._isDestroyed = true;
+ this.$list.length = 0;
+ $log.debug('destroy called for FirebaseArray: '+this.$inst().$ref().toString());
+ this._destroyFn(err);
+ }
+ },
+
+ /**
+ * Returns the record for a given Firebase key (record.$id). If the record is not found
+ * then returns null.
+ *
+ * @param {string} key
+ * @returns {Object|null} a record in this array
+ */
+ $getRecord: function(key) {
+ var i = this.$indexFor(key);
+ return i > -1? this.$list[i] : null;
+ },
+
+ /**
+ * Called by $firebase to inform the array when a new item has been added at the server.
+ * This method must exist on any array factory used by $firebase.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @param {string} prevChild
+ * @return {object} the record to be inserted into the array
+ */
+ $$added: function(snap/*, prevChild*/) {
+ // check to make sure record does not exist
+ var i = this.$indexFor($firebaseUtils.getKey(snap));
+ if( i === -1 ) {
+ // parse data and create record
+ var rec = snap.val();
+ if( !angular.isObject(rec) ) {
+ rec = { $value: rec };
+ }
+ rec.$id = $firebaseUtils.getKey(snap);
+ rec.$priority = snap.getPriority();
+ $firebaseUtils.applyDefaults(rec, this.$$defaults);
+
+ return rec;
+ }
+ return false;
+ },
+
+ /**
+ * Called by $firebase whenever an item is removed at the server.
+ * This method does not physically remove the objects, but instead
+ * returns a boolean indicating whether it should be removed (and
+ * taking any other desired actions before the remove completes).
+ *
+ * @param {object} snap a Firebase snapshot
+ * @return {boolean} true if item should be removed
+ */
+ $$removed: function(snap) {
+ return this.$indexFor($firebaseUtils.getKey(snap)) > -1;
+ },
+
+ /**
+ * Called by $firebase whenever an item is changed at the server.
+ * This method should apply the changes, including changes to data
+ * and to $priority, and then return true if any changes were made.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @return {boolean} true if any data changed
+ */
+ $$updated: function(snap) {
+ var changed = false;
+ var rec = this.$getRecord($firebaseUtils.getKey(snap));
+ if( angular.isObject(rec) ) {
+ // apply changes to the record
+ changed = $firebaseUtils.updateRec(rec, snap);
+ $firebaseUtils.applyDefaults(rec, this.$$defaults);
+ }
+ return changed;
+ },
+
+ /**
+ * Called by $firebase whenever an item changes order (moves) on the server.
+ * This method should set $priority to the updated value and return true if
+ * the record should actually be moved. It should not actually apply the move
+ * operation.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @param {string} prevChild
+ */
+ $$moved: function(snap/*, prevChild*/) {
+ var rec = this.$getRecord($firebaseUtils.getKey(snap));
+ if( angular.isObject(rec) ) {
+ rec.$priority = snap.getPriority();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Called whenever a security error or other problem causes the listeners to become
+ * invalid. This is generally an unrecoverable error.
+ * @param {Object} err which will have a `code` property and possibly a `message`
+ */
+ $$error: function(err) {
+ $log.error(err);
+ this.$destroy(err);
+ },
+
+ /**
+ * Returns ID for a given record
+ * @param {object} rec
+ * @returns {string||null}
+ * @private
+ */
+ $$getKey: function(rec) {
+ return angular.isObject(rec)? rec.$id : null;
+ },
+
+ /**
+ * Handles placement of recs in the array, sending notifications,
+ * and other internals. Called by the $firebase synchronization process
+ * after $$added, $$updated, $$moved, and $$removed.
+ *
+ * @param {string} event one of child_added, child_removed, child_moved, or child_changed
+ * @param {object} rec
+ * @param {string} [prevChild]
+ * @private
+ */
+ $$process: function(event, rec, prevChild) {
+ var key = this.$$getKey(rec);
+ var changed = false;
+ var curPos;
+ switch(event) {
+ case 'child_added':
+ curPos = this.$indexFor(key);
+ break;
+ case 'child_moved':
+ curPos = this.$indexFor(key);
+ this._spliceOut(key);
+ break;
+ case 'child_removed':
+ // remove record from the array
+ changed = this._spliceOut(key) !== null;
+ break;
+ case 'child_changed':
+ changed = true;
+ break;
+ default:
+ throw new Error('Invalid event type: ' + event);
+ }
+ if( angular.isDefined(curPos) ) {
+ // add it to the array
+ changed = this._addAfter(rec, prevChild) !== curPos;
+ }
+ if( changed ) {
+ // send notifications to anybody monitoring $watch
+ this.$$notify(event, key, prevChild);
+ }
+ return changed;
+ },
+
+ /**
+ * Used to trigger notifications for listeners registered using $watch
+ *
+ * @param {string} event
+ * @param {string} key
+ * @param {string} [prevChild]
+ * @private
+ */
+ $$notify: function(event, key, prevChild) {
+ var eventData = {event: event, key: key};
+ if( angular.isDefined(prevChild) ) {
+ eventData.prevChild = prevChild;
+ }
+ angular.forEach(this._observers, function(parts) {
+ parts[0].call(parts[1], eventData);
+ });
+ },
+
+ /**
+ * Used to insert a new record into the array at a specific position. If prevChild is
+ * null, is inserted first, if prevChild is not found, it is inserted last, otherwise,
+ * it goes immediately after prevChild.
+ *
+ * @param {object} rec
+ * @param {string|null} prevChild
+ * @private
+ */
+ _addAfter: function(rec, prevChild) {
+ var i;
+ if( prevChild === null ) {
+ i = 0;
+ }
+ else {
+ i = this.$indexFor(prevChild)+1;
+ if( i === 0 ) { i = this.$list.length; }
+ }
+ this.$list.splice(i, 0, rec);
+ this._indexCache[this.$$getKey(rec)] = i;
+ return i;
+ },
+
+ /**
+ * Removes a record from the array by calling splice. If the item is found
+ * this method returns it. Otherwise, this method returns null.
+ *
+ * @param {string} key
+ * @returns {object|null}
+ * @private
+ */
+ _spliceOut: function(key) {
+ var i = this.$indexFor(key);
+ if( i > -1 ) {
+ delete this._indexCache[key];
+ return this.$list.splice(i, 1)[0];
+ }
+ return null;
+ },
+
+ /**
+ * Resolves a variable which may contain an integer or an item that exists in this array.
+ * Returns the item or null if it does not exist.
+ *
+ * @param indexOrItem
+ * @returns {*}
+ * @private
+ */
+ _resolveItem: function(indexOrItem) {
+ var list = this.$list;
+ if( angular.isNumber(indexOrItem) && indexOrItem >= 0 && list.length >= indexOrItem ) {
+ return list[indexOrItem];
+ }
+ else if( angular.isObject(indexOrItem) ) {
+ // it must be an item in this array; it's not sufficient for it just to have
+ // a $id or even a $id that is in the array, it must be an actual record
+ // the fastest way to determine this is to use $getRecord (to avoid iterating all recs)
+ // and compare the two
+ var key = this.$$getKey(indexOrItem);
+ var rec = this.$getRecord(key);
+ return rec === indexOrItem? rec : null;
+ }
+ return null;
+ },
+
+ /**
+ * Throws an error if $destroy has been called. Should be used for any function
+ * which tries to write data back to $firebase.
+ * @param {string} method
+ * @private
+ */
+ _assertNotDestroyed: function(method) {
+ if( this._isDestroyed ) {
+ throw new Error('Cannot call ' + method + ' method on a destroyed $FirebaseArray object');
+ }
+ }
+ };
+
+ /**
+ * This method allows FirebaseArray to be copied into a new factory. Methods passed into this
+ * function will be added onto the array's prototype. They can override existing methods as
+ * well.
+ *
+ * In addition to passing additional methods, it is also possible to pass in a class function.
+ * The prototype on that class function will be preserved, and it will inherit from
+ * FirebaseArray. It's also possible to do both, passing a class to inherit and additional
+ * methods to add onto the prototype.
+ *
+ * Once a factory is obtained by this method, it can be passed into $firebase as the
+ * `arrayFactory` parameter:
+ *
+ * var MyFactory = $FirebaseArray.$extendFactory({
+ * // add a method onto the prototype that sums all items in the array
+ * getSum: function() {
+ * var ct = 0;
+ * angular.forEach(this.$list, function(rec) { ct += rec.x; });
+ * return ct;
+ * }
+ * });
+ *
+ * // use our new factory in place of $FirebaseArray
+ * var list = $firebase(ref, {arrayFactory: MyFactory}).$asArray();
+ *
+ *
+ * @param {Function} [ChildClass] a child class which should inherit FirebaseArray
+ * @param {Object} [methods] a list of functions to add onto the prototype
+ * @returns {Function} a new factory suitable for use with $firebase
+ */
+ FirebaseArray.$extendFactory = function(ChildClass, methods) {
+ if( arguments.length === 1 && angular.isObject(ChildClass) ) {
+ methods = ChildClass;
+ ChildClass = function() { return FirebaseArray.apply(this, arguments); };
+ }
+ return $firebaseUtils.inherit(ChildClass, FirebaseArray, methods);
+ };
+
+ return FirebaseArray;
+ }
+ ]);
+})();
+
+/* istanbul ignore next */
+(function() {
+ 'use strict';
+ var FirebaseAuth;
+
+ // Define a service which provides user authentication and management.
+ angular.module('firebase').factory('$firebaseAuth', [
+ '$q', function($q) {
+ // This factory returns an object containing the current authentication state of the client.
+ // This service takes one argument:
+ //
+ // * `ref`: A Firebase reference.
+ //
+ // The returned object contains methods for authenticating clients, retrieving authentication
+ // state, and managing users.
+ return function(ref) {
+ var auth = new FirebaseAuth($q, ref);
+ return auth.construct();
+ };
+ }
+ ]);
+
+ FirebaseAuth = function($q, ref) {
+ this._q = $q;
+
+ if (typeof ref === 'string') {
+ throw new Error('Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.');
+ }
+ this._ref = ref;
+ };
+
+ FirebaseAuth.prototype = {
+ construct: function() {
+ this._object = {
+ // Authentication methods
+ $authWithCustomToken: this.authWithCustomToken.bind(this),
+ $authAnonymously: this.authAnonymously.bind(this),
+ $authWithPassword: this.authWithPassword.bind(this),
+ $authWithOAuthPopup: this.authWithOAuthPopup.bind(this),
+ $authWithOAuthRedirect: this.authWithOAuthRedirect.bind(this),
+ $authWithOAuthToken: this.authWithOAuthToken.bind(this),
+ $unauth: this.unauth.bind(this),
+
+ // Authentication state methods
+ $onAuth: this.onAuth.bind(this),
+ $getAuth: this.getAuth.bind(this),
+ $requireAuth: this.requireAuth.bind(this),
+ $waitForAuth: this.waitForAuth.bind(this),
+
+ // User management methods
+ $createUser: this.createUser.bind(this),
+ $changePassword: this.changePassword.bind(this),
+ $removeUser: this.removeUser.bind(this),
+ $sendPasswordResetEmail: this.sendPasswordResetEmail.bind(this)
+ };
+
+ return this._object;
+ },
+
+
+ /********************/
+ /* Authentication */
+ /********************/
+ // Common login completion handler for all authentication methods.
+ _onLoginHandler: function(deferred, error, authData) {
+ if (error !== null) {
+ deferred.reject(error);
+ } else {
+ deferred.resolve(authData);
+ }
+ },
+
+ // Authenticates the Firebase reference with a custom authentication token.
+ authWithCustomToken: function(authToken, options) {
+ var deferred = this._q.defer();
+
+ this._ref.authWithCustomToken(authToken, this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Authenticates the Firebase reference anonymously.
+ authAnonymously: function(options) {
+ var deferred = this._q.defer();
+
+ this._ref.authAnonymously(this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Authenticates the Firebase reference with an email/password user.
+ authWithPassword: function(credentials, options) {
+ var deferred = this._q.defer();
+
+ this._ref.authWithPassword(credentials, this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Authenticates the Firebase reference with the OAuth popup flow.
+ authWithOAuthPopup: function(provider, options) {
+ var deferred = this._q.defer();
+
+ this._ref.authWithOAuthPopup(provider, this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Authenticates the Firebase reference with the OAuth redirect flow.
+ authWithOAuthRedirect: function(provider, options) {
+ var deferred = this._q.defer();
+
+ this._ref.authWithOAuthRedirect(provider, this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Authenticates the Firebase reference with an OAuth token.
+ authWithOAuthToken: function(provider, credentials, options) {
+ var deferred = this._q.defer();
+
+ this._ref.authWithOAuthToken(provider, credentials, this._onLoginHandler.bind(this, deferred), options);
+
+ return deferred.promise;
+ },
+
+ // Unauthenticates the Firebase reference.
+ unauth: function() {
+ if (this.getAuth() !== null) {
+ this._ref.unauth();
+ }
+ },
+
+
+ /**************************/
+ /* Authentication State */
+ /**************************/
+ // Asynchronously fires the provided callback with the current authentication data every time
+ // the authentication data changes. It also fires as soon as the authentication data is
+ // retrieved from the server.
+ onAuth: function(callback, context) {
+ var self = this;
+
+ this._ref.onAuth(callback, context);
+
+ // Return a method to detach the `onAuth()` callback.
+ return function() {
+ self._ref.offAuth(callback, context);
+ };
+ },
+
+ // Synchronously retrieves the current authentication data.
+ getAuth: function() {
+ return this._ref.getAuth();
+ },
+
+ // Helper onAuth() callback method for the two router-related methods.
+ _routerMethodOnAuthCallback: function(deferred, rejectIfAuthDataIsNull, authData) {
+ if (authData !== null) {
+ deferred.resolve(authData);
+ } else if (rejectIfAuthDataIsNull) {
+ deferred.reject("AUTH_REQUIRED");
+ } else {
+ deferred.resolve(null);
+ }
+
+ // Turn off this onAuth() callback since we just needed to get the authentication data once.
+ this._ref.offAuth(this._routerMethodOnAuthCallback);
+ },
+
+ // Returns a promise which is resolved if the client is authenticated and rejects otherwise.
+ // This can be used to require that a route has a logged in user.
+ requireAuth: function() {
+ var deferred = this._q.defer();
+
+ this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this, deferred, /* rejectIfAuthDataIsNull */ true));
+
+ return deferred.promise;
+ },
+
+ // Returns a promise which is resolved with the client's current authenticated data. This can
+ // be used in a route's resolve() method to grab the current authentication data.
+ waitForAuth: function() {
+ var deferred = this._q.defer();
+
+ this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this, deferred, /* rejectIfAuthDataIsNull */ false));
+
+ return deferred.promise;
+ },
+
+
+ /*********************/
+ /* User Management */
+ /*********************/
+ // Creates a new email/password user. Note that this function only creates the user, if you
+ // wish to log in as the newly created user, call $authWithPassword() after the promise for
+ // this method has been resolved.
+ createUser: function(email, password) {
+ var deferred = this._q.defer();
+
+ this._ref.createUser({
+ email: email,
+ password: password
+ }, function(error) {
+ if (error !== null) {
+ deferred.reject(error);
+ } else {
+ deferred.resolve();
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ // Changes the password for an email/password user.
+ changePassword: function(email, oldPassword, newPassword) {
+ var deferred = this._q.defer();
+
+ this._ref.changePassword({
+ email: email,
+ oldPassword: oldPassword,
+ newPassword: newPassword
+ }, function(error) {
+ if (error !== null) {
+ deferred.reject(error);
+ } else {
+ deferred.resolve();
+ }
+ }
+ );
+
+ return deferred.promise;
+ },
+
+ // Removes an email/password user.
+ removeUser: function(email, password) {
+ var deferred = this._q.defer();
+
+ this._ref.removeUser({
+ email: email,
+ password: password
+ }, function(error) {
+ if (error !== null) {
+ deferred.reject(error);
+ } else {
+ deferred.resolve();
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ // Sends a password reset email to an email/password user.
+ sendPasswordResetEmail: function(email) {
+ var deferred = this._q.defer();
+
+ this._ref.resetPassword({
+ email: email
+ }, function(error) {
+ if (error !== null) {
+ deferred.reject(error);
+ } else {
+ deferred.resolve();
+ }
+ });
+
+ return deferred.promise;
+ }
+ };
+})();
+
+(function() {
+ 'use strict';
+ /**
+ * Creates and maintains a synchronized boject. This constructor should not be
+ * manually invoked. Instead, one should create a $firebase object and call $asObject
+ * on it: $firebase( firebaseRef ).$asObject()
;
+ *
+ * Internally, the $firebase object depends on this class to provide 2 methods, which it invokes
+ * to notify the object whenever a change has been made at the server:
+ * $$updated - called whenever a change occurs (a value event from Firebase)
+ * $$error - called when listeners are canceled due to a security error
+ *
+ * Instead of directly modifying this class, one should generally use the $extendFactory
+ * method to add or change how methods behave:
+ *
+ *
+ * var NewFactory = $FirebaseObject.$extendFactory({
+ * // add a new method to the prototype
+ * foo: function() { return 'bar'; },
+ * });
+ *
+ *
+ * And then the new factory can be used by passing it as an argument:
+ * $firebase( firebaseRef, {objectFactory: NewFactory}).$asObject();
+ */
+ angular.module('firebase').factory('$FirebaseObject', [
+ '$parse', '$firebaseUtils', '$log', '$interval',
+ function($parse, $firebaseUtils, $log, $interval) {
+ /**
+ * This constructor should probably never be called manually. It is used internally by
+ * $firebase.$asObject()
.
+ *
+ * @param $firebase
+ * @param {Function} destroyFn invoking this will cancel all event listeners and stop
+ * notifications from being delivered to $$updated and $$error
+ * @param readyPromise resolved when the initial data downloaded from Firebase
+ * @returns {FirebaseObject}
+ * @constructor
+ */
+ function FirebaseObject($firebase, destroyFn, readyPromise) {
+ // These are private config props and functions used internally
+ // they are collected here to reduce clutter in console.log and forEach
+ this.$$conf = {
+ promise: readyPromise,
+ inst: $firebase,
+ binding: new ThreeWayBinding(this),
+ destroyFn: destroyFn,
+ listeners: []
+ };
+
+ // this bit of magic makes $$conf non-enumerable and non-configurable
+ // and non-writable (its properties are still writable but the ref cannot be replaced)
+ // we declare it above so the IDE can relax
+ Object.defineProperty(this, '$$conf', {
+ value: this.$$conf
+ });
+
+ this.$id = $firebaseUtils.getKey($firebase.$ref().ref());
+ this.$priority = null;
+
+ $firebaseUtils.applyDefaults(this, this.$$defaults);
+ }
+
+ FirebaseObject.prototype = {
+ /**
+ * Saves all data on the FirebaseObject back to Firebase.
+ * @returns a promise which will resolve after the save is completed.
+ */
+ $save: function () {
+ var self = this;
+ return self.$inst().$set($firebaseUtils.toJSON(self))
+ .then(function(ref) {
+ self.$$notify();
+ return ref;
+ });
+ },
+
+ /**
+ * Removes all keys from the FirebaseObject and also removes
+ * the remote data from the server.
+ *
+ * @returns a promise which will resolve after the op completes
+ */
+ $remove: function() {
+ var self = this;
+ $firebaseUtils.trimKeys(this, {});
+ this.$value = null;
+ return self.$inst().$remove(self.$id).then(function(ref) {
+ self.$$notify();
+ return ref;
+ });
+ },
+
+ /**
+ * The loaded method is invoked after the initial batch of data arrives from the server.
+ * When this resolves, all data which existed prior to calling $asObject() is now cached
+ * locally in the object.
+ *
+ * As a shortcut is also possible to pass resolve/reject methods directly into this
+ * method just as they would be passed to .then()
+ *
+ * @param {Function} resolve
+ * @param {Function} reject
+ * @returns a promise which resolves after initial data is downloaded from Firebase
+ */
+ $loaded: function(resolve, reject) {
+ var promise = this.$$conf.promise;
+ if (arguments.length) {
+ // allow this method to be called just like .then
+ // by passing any arguments on to .then
+ promise = promise.then.call(promise, resolve, reject);
+ }
+ return promise;
+ },
+
+ /**
+ * @returns the original $firebase object used to create this object.
+ */
+ $inst: function () {
+ return this.$$conf.inst;
+ },
+
+ /**
+ * Creates a 3-way data sync between this object, the Firebase server, and a
+ * scope variable. This means that any changes made to the scope variable are
+ * pushed to Firebase, and vice versa.
+ *
+ * If scope emits a $destroy event, the binding is automatically severed. Otherwise,
+ * it is possible to unbind the scope variable by using the `unbind` function
+ * passed into the resolve method.
+ *
+ * Can only be bound to one scope variable at a time. If a second is attempted,
+ * the promise will be rejected with an error.
+ *
+ * @param {object} scope
+ * @param {string} varName
+ * @returns a promise which resolves to an unbind method after data is set in scope
+ */
+ $bindTo: function (scope, varName) {
+ var self = this;
+ return self.$loaded().then(function () {
+ return self.$$conf.binding.bindTo(scope, varName);
+ });
+ },
+
+ /**
+ * Listeners passed into this method are notified whenever a new change is received
+ * from the server. Each invocation is sent an object containing
+ * { type: 'updated', key: 'my_firebase_id' }
+ *
+ * This method returns an unbind function that can be used to detach the listener.
+ *
+ * @param {Function} cb
+ * @param {Object} [context]
+ * @returns {Function} invoke to stop observing events
+ */
+ $watch: function (cb, context) {
+ var list = this.$$conf.listeners;
+ list.push([cb, context]);
+ // an off function for cancelling the listener
+ return function () {
+ var i = list.findIndex(function (parts) {
+ return parts[0] === cb && parts[1] === context;
+ });
+ if (i > -1) {
+ list.splice(i, 1);
+ }
+ };
+ },
+
+ /**
+ * Informs $firebase to stop sending events and clears memory being used
+ * by this object (delete's its local content).
+ */
+ $destroy: function (err) {
+ var self = this;
+ if (!self.$isDestroyed) {
+ self.$isDestroyed = true;
+ self.$$conf.binding.destroy();
+ $firebaseUtils.each(self, function (v, k) {
+ delete self[k];
+ });
+ self.$$conf.destroyFn(err);
+ }
+ },
+
+ /**
+ * Called by $firebase whenever an item is changed at the server.
+ * This method must exist on any objectFactory passed into $firebase.
+ *
+ * It should return true if any changes were made, otherwise `$$notify` will
+ * not be invoked.
+ *
+ * @param {object} snap a Firebase snapshot
+ * @return {boolean} true if any changes were made.
+ */
+ $$updated: function (snap) {
+ // applies new data to this object
+ var changed = $firebaseUtils.updateRec(this, snap);
+ // applies any defaults set using $$defaults
+ $firebaseUtils.applyDefaults(this, this.$$defaults);
+ // returning true here causes $$notify to be triggered
+ return changed;
+ },
+
+ /**
+ * Called whenever a security error or other problem causes the listeners to become
+ * invalid. This is generally an unrecoverable error.
+ * @param {Object} err which will have a `code` property and possibly a `message`
+ */
+ $$error: function (err) {
+ // prints an error to the console (via Angular's logger)
+ $log.error(err);
+ // frees memory and cancels any remaining listeners
+ this.$destroy(err);
+ },
+
+ /**
+ * Called internally by $bindTo when data is changed in $scope.
+ * Should apply updates to this record but should not call
+ * notify().
+ */
+ $$scopeUpdated: function(newData) {
+ // we use a one-directional loop to avoid feedback with 3-way bindings
+ // since set() is applied locally anyway, this is still performant
+ return this.$inst().$set($firebaseUtils.toJSON(newData));
+ },
+
+ /**
+ * Updates any bound scope variables and
+ * notifies listeners registered with $watch
+ */
+ $$notify: function() {
+ var self = this, list = this.$$conf.listeners.slice();
+ // be sure to do this after setting up data and init state
+ angular.forEach(list, function (parts) {
+ parts[0].call(parts[1], {event: 'value', key: self.$id});
+ });
+ },
+
+ /**
+ * Overrides how Angular.forEach iterates records on this object so that only
+ * fields stored in Firebase are part of the iteration. To include meta fields like
+ * $id and $priority in the iteration, utilize for(key in obj) instead.
+ */
+ forEach: function(iterator, context) {
+ return $firebaseUtils.each(this, iterator, context);
+ }
+ };
+
+ /**
+ * This method allows FirebaseObject to be copied into a new factory. Methods passed into this
+ * function will be added onto the object's prototype. They can override existing methods as
+ * well.
+ *
+ * In addition to passing additional methods, it is also possible to pass in a class function.
+ * The prototype on that class function will be preserved, and it will inherit from
+ * FirebaseObject. It's also possible to do both, passing a class to inherit and additional
+ * methods to add onto the prototype.
+ *
+ * Once a factory is obtained by this method, it can be passed into $firebase as the
+ * `objectFactory` parameter:
+ *
+ *
+ * var MyFactory = $FirebaseObject.$extendFactory({
+ * // add a method onto the prototype that prints a greeting
+ * getGreeting: function() {
+ * return 'Hello ' + this.first_name + ' ' + this.last_name + '!';
+ * }
+ * });
+ *
+ * // use our new factory in place of $FirebaseObject
+ * var obj = $firebase(ref, {objectFactory: MyFactory}).$asObject();
+ *
+ *
+ * @param {Function} [ChildClass] a child class which should inherit FirebaseObject
+ * @param {Object} [methods] a list of functions to add onto the prototype
+ * @returns {Function} a new factory suitable for use with $firebase
+ */
+ FirebaseObject.$extendFactory = function(ChildClass, methods) {
+ if( arguments.length === 1 && angular.isObject(ChildClass) ) {
+ methods = ChildClass;
+ ChildClass = function() { FirebaseObject.apply(this, arguments); };
+ }
+ return $firebaseUtils.inherit(ChildClass, FirebaseObject, methods);
+ };
+
+ /**
+ * Creates a three-way data binding on a scope variable.
+ *
+ * @param {FirebaseObject} rec
+ * @returns {*}
+ * @constructor
+ */
+ function ThreeWayBinding(rec) {
+ this.subs = [];
+ this.scope = null;
+ this.key = null;
+ this.rec = rec;
+ }
+
+ ThreeWayBinding.prototype = {
+ assertNotBound: function(varName) {
+ if( this.scope ) {
+ var msg = 'Cannot bind to ' + varName + ' because this instance is already bound to ' +
+ this.key + '; one binding per instance ' +
+ '(call unbind method or create another $firebase instance)';
+ $log.error(msg);
+ return $firebaseUtils.reject(msg);
+ }
+ },
+
+ bindTo: function(scope, varName) {
+ function _bind(self) {
+ var sending = false;
+ var parsed = $parse(varName);
+ var rec = self.rec;
+ self.scope = scope;
+ self.varName = varName;
+
+ function equals(rec) {
+ var parsed = getScope();
+ var newData = $firebaseUtils.scopeData(rec);
+ return angular.equals(parsed, newData) &&
+ parsed.$priority === rec.$priority &&
+ parsed.$value === rec.$value;
+ }
+
+ function getScope() {
+ return $firebaseUtils.scopeData(parsed(scope));
+ }
+
+ function setScope(rec) {
+ parsed.assign(scope, $firebaseUtils.scopeData(rec));
+ }
+
+ var scopeUpdated = function() {
+ var send = $firebaseUtils.debounce(function() {
+ rec.$$scopeUpdated(getScope())
+ ['finally'](function() { sending = false; });
+ }, 50, 500);
+ if( !equals(rec) ) {
+ sending = true;
+ send();
+ }
+ };
+
+ var recUpdated = function() {
+ if( !sending && !equals(rec) ) {
+ setScope(rec);
+ }
+ };
+
+ // $watch will not check any vars prefixed with $, so we
+ // manually check $priority and $value using this method
+ function checkMetaVars() {
+ var dat = parsed(scope);
+ if( dat.$value !== rec.$value || dat.$priority !== rec.$priority ) {
+ scopeUpdated();
+ }
+ }
+
+ // Okay, so this magic hack is um... magic. It increments a
+ // variable every 50 seconds (counterKey) so that whenever $digest
+ // is run, the variable will be dirty. This allows us to determine
+ // when $digest is invoked, manually check the meta vars, and
+ // manually invoke our watcher if the $ prefixed data has changed
+ (function() {
+ // create a counter and store it in scope
+ var counterKey = '_firebaseCounterForVar'+varName;
+ scope[counterKey] = 0;
+ // update the counter every 51ms
+ // why 51? because it must be greater than scopeUpdated's debounce
+ // or protractor has a conniption
+ var to = $interval(function() {
+ scope[counterKey]++;
+ }, 51, 0, false);
+ // watch the counter for changes (which means $digest ran)
+ self.subs.push(scope.$watch(counterKey, checkMetaVars));
+ // cancel our interval and clear var from scope if unbound
+ self.subs.push(function() {
+ $interval.cancel(to);
+ delete scope[counterKey];
+ });
+ })();
+
+ setScope(rec);
+ self.subs.push(scope.$on('$destroy', self.unbind.bind(self)));
+
+ // monitor scope for any changes
+ self.subs.push(scope.$watch(varName, scopeUpdated, true));
+
+ // monitor the object for changes
+ self.subs.push(rec.$watch(recUpdated));
+
+ return self.unbind.bind(self);
+ }
+
+ return this.assertNotBound(varName) || _bind(this);
+ },
+
+ unbind: function() {
+ if( this.scope ) {
+ angular.forEach(this.subs, function(unbind) {
+ unbind();
+ });
+ this.subs = [];
+ this.scope = null;
+ this.key = null;
+ }
+ },
+
+ destroy: function() {
+ this.unbind();
+ this.rec = null;
+ }
+ };
+
+ return FirebaseObject;
+ }
+ ]);
+})();
+
+(function() {
+ 'use strict';
+
+ angular.module("firebase")
+
+ // The factory returns an object containing the value of the data at
+ // the Firebase location provided, as well as several methods. It
+ // takes one or two arguments:
+ //
+ // * `ref`: A Firebase reference. Queries or limits may be applied.
+ // * `config`: An object containing any of the advanced config options explained in API docs
+ .factory("$firebase", [ "$firebaseUtils", "$firebaseConfig",
+ function ($firebaseUtils, $firebaseConfig) {
+ function AngularFire(ref, config) {
+ // make the new keyword optional
+ if (!(this instanceof AngularFire)) {
+ return new AngularFire(ref, config);
+ }
+ this._config = $firebaseConfig(config);
+ this._ref = ref;
+ this._arraySync = null;
+ this._objectSync = null;
+ this._assertValidConfig(ref, this._config);
+ }
+
+ AngularFire.prototype = {
+ $ref: function () {
+ return this._ref;
+ },
+
+ $push: function (data) {
+ var def = $firebaseUtils.defer();
+ var ref = this._ref.ref().push();
+ var done = this._handle(def, ref);
+ if (arguments.length > 0) {
+ ref.set(data, done);
+ }
+ else {
+ done();
+ }
+ return def.promise;
+ },
+
+ $set: function (key, data) {
+ var ref = this._ref;
+ var def = $firebaseUtils.defer();
+ if (arguments.length > 1) {
+ ref = ref.ref().child(key);
+ }
+ else {
+ data = key;
+ }
+ if( angular.isFunction(ref.set) || !angular.isObject(data) ) {
+ // this is not a query, just do a flat set
+ ref.ref().set(data, this._handle(def, ref));
+ }
+ else {
+ var dataCopy = angular.extend({}, data);
+ // this is a query, so we will replace all the elements
+ // of this query with the value provided, but not blow away
+ // the entire Firebase path
+ ref.once('value', function(snap) {
+ snap.forEach(function(ss) {
+ if( !dataCopy.hasOwnProperty($firebaseUtils.getKey(ss)) ) {
+ dataCopy[$firebaseUtils.getKey(ss)] = null;
+ }
+ });
+ ref.ref().update(dataCopy, this._handle(def, ref));
+ }, this);
+ }
+ return def.promise;
+ },
+
+ $remove: function (key) {
+ var ref = this._ref, self = this, promise;
+ var def = $firebaseUtils.defer();
+ if (arguments.length > 0) {
+ ref = ref.ref().child(key);
+ }
+ if( angular.isFunction(ref.remove) ) {
+ // self is not a query, just do a flat remove
+ ref.remove(self._handle(def, ref));
+ promise = def.promise;
+ }
+ else {
+ var promises = [];
+ // self is a query so let's only remove the
+ // items in the query and not the entire path
+ ref.once('value', function(snap) {
+ snap.forEach(function(ss) {
+ var d = $firebaseUtils.defer();
+ promises.push(d);
+ ss.ref().remove(self._handle(d, ss.ref()));
+ }, self);
+ });
+ promise = $firebaseUtils.allPromises(promises)
+ .then(function() {
+ return ref;
+ });
+ }
+ return promise;
+ },
+
+ $update: function (key, data) {
+ var ref = this._ref.ref();
+ var def = $firebaseUtils.defer();
+ if (arguments.length > 1) {
+ ref = ref.child(key);
+ }
+ else {
+ data = key;
+ }
+ ref.update(data, this._handle(def, ref));
+ return def.promise;
+ },
+
+ $transaction: function (key, valueFn, applyLocally) {
+ var ref = this._ref.ref();
+ if( angular.isFunction(key) ) {
+ applyLocally = valueFn;
+ valueFn = key;
+ }
+ else {
+ ref = ref.child(key);
+ }
+ applyLocally = !!applyLocally;
+
+ var def = $firebaseUtils.defer();
+ ref.transaction(valueFn, function(err, committed, snap) {
+ if( err ) {
+ def.reject(err);
+ }
+ else {
+ def.resolve(committed? snap : null);
+ }
+ }, applyLocally);
+ return def.promise;
+ },
+
+ $asObject: function () {
+ if (!this._objectSync || this._objectSync.isDestroyed) {
+ this._objectSync = new SyncObject(this, this._config.objectFactory);
+ }
+ return this._objectSync.getObject();
+ },
+
+ $asArray: function () {
+ if (!this._arraySync || this._arraySync.isDestroyed) {
+ this._arraySync = new SyncArray(this, this._config.arrayFactory);
+ }
+ return this._arraySync.getArray();
+ },
+
+ _handle: function (def) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return function (err) {
+ if (err) {
+ def.reject(err);
+ }
+ else {
+ def.resolve.apply(def, args);
+ }
+ };
+ },
+
+ _assertValidConfig: function (ref, cnf) {
+ $firebaseUtils.assertValidRef(ref, 'Must pass a valid Firebase reference ' +
+ 'to $firebase (not a string or URL)');
+ if (!angular.isFunction(cnf.arrayFactory)) {
+ throw new Error('config.arrayFactory must be a valid function');
+ }
+ if (!angular.isFunction(cnf.objectFactory)) {
+ throw new Error('config.objectFactory must be a valid function');
+ }
+ }
+ };
+
+ function SyncArray($inst, ArrayFactory) {
+ function destroy(err) {
+ self.isDestroyed = true;
+ var ref = $inst.$ref();
+ ref.off('child_added', created);
+ ref.off('child_moved', moved);
+ ref.off('child_changed', updated);
+ ref.off('child_removed', removed);
+ array = null;
+ resolve(err||'destroyed');
+ }
+
+ function init() {
+ var ref = $inst.$ref();
+
+ // listen for changes at the Firebase instance
+ ref.on('child_added', created, error);
+ ref.on('child_moved', moved, error);
+ ref.on('child_changed', updated, error);
+ ref.on('child_removed', removed, error);
+
+ // determine when initial load is completed
+ ref.once('value', function() { resolve(null); }, resolve);
+ }
+
+ // call resolve(), do not call this directly
+ function _resolveFn(err) {
+ if( def ) {
+ if( err ) { def.reject(err); }
+ else { def.resolve(array); }
+ def = null;
+ }
+ }
+
+ function assertArray(arr) {
+ if( !angular.isArray(arr) ) {
+ var type = Object.prototype.toString.call(arr);
+ throw new Error('arrayFactory must return a valid array that passes ' +
+ 'angular.isArray and Array.isArray, but received "' + type + '"');
+ }
+ }
+
+ var def = $firebaseUtils.defer();
+ var array = new ArrayFactory($inst, destroy, def.promise);
+ var batch = $firebaseUtils.batch();
+ var created = batch(function(snap, prevChild) {
+ var rec = array.$$added(snap, prevChild);
+ if( rec ) {
+ array.$$process('child_added', rec, prevChild);
+ }
+ });
+ var updated = batch(function(snap) {
+ var rec = array.$getRecord($firebaseUtils.getKey(snap));
+ if( rec ) {
+ var changed = array.$$updated(snap);
+ if( changed ) {
+ array.$$process('child_changed', rec);
+ }
+ }
+ });
+ var moved = batch(function(snap, prevChild) {
+ var rec = array.$getRecord($firebaseUtils.getKey(snap));
+ if( rec ) {
+ var confirmed = array.$$moved(snap, prevChild);
+ if( confirmed ) {
+ array.$$process('child_moved', rec, prevChild);
+ }
+ }
+ });
+ var removed = batch(function(snap) {
+ var rec = array.$getRecord($firebaseUtils.getKey(snap));
+ if( rec ) {
+ var confirmed = array.$$removed(snap);
+ if( confirmed ) {
+ array.$$process('child_removed', rec);
+ }
+ }
+ });
+ var error = batch(array.$$error, array);
+ var resolve = batch(_resolveFn);
+
+ var self = this;
+ self.isDestroyed = false;
+ self.getArray = function() { return array; };
+
+ assertArray(array);
+ init();
+ }
+
+ function SyncObject($inst, ObjectFactory) {
+ function destroy(err) {
+ self.isDestroyed = true;
+ ref.off('value', applyUpdate);
+ obj = null;
+ resolve(err||'destroyed');
+ }
+
+ function init() {
+ ref.on('value', applyUpdate, error);
+ ref.once('value', function() { resolve(null); }, resolve);
+ }
+
+ // call resolve(); do not call this directly
+ function _resolveFn(err) {
+ if( def ) {
+ if( err ) { def.reject(err); }
+ else { def.resolve(obj); }
+ def = null;
+ }
+ }
+
+ var def = $firebaseUtils.defer();
+ var obj = new ObjectFactory($inst, destroy, def.promise);
+ var ref = $inst.$ref();
+ var batch = $firebaseUtils.batch();
+ var applyUpdate = batch(function(snap) {
+ var changed = obj.$$updated(snap);
+ if( changed ) {
+ // notifies $watch listeners and
+ // updates $scope if bound to a variable
+ obj.$$notify();
+ }
+ });
+ var error = batch(obj.$$error, obj);
+ var resolve = batch(_resolveFn);
+
+ var self = this;
+ self.isDestroyed = false;
+ self.getObject = function() { return obj; };
+ init();
+ }
+
+ return AngularFire;
+ }
+ ]);
+})();
+
+'use strict';
+
+// Shim Array.indexOf for IE compatibility.
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function (searchElement, fromIndex) {
+ if (this === undefined || this === null) {
+ throw new TypeError("'this' is null or not defined");
+ }
+ // Hack to convert object.length to a UInt32
+ // jshint -W016
+ var length = this.length >>> 0;
+ fromIndex = +fromIndex || 0;
+ // jshint +W016
+
+ if (Math.abs(fromIndex) === Infinity) {
+ fromIndex = 0;
+ }
+
+ if (fromIndex < 0) {
+ fromIndex += length;
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+ }
+
+ for (;fromIndex < length; fromIndex++) {
+ if (this[fromIndex] === searchElement) {
+ return fromIndex;
+ }
+ }
+
+ return -1;
+ };
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function (oThis) {
+ if (typeof this !== "function") {
+ // closest thing possible to the ECMAScript 5
+ // internal IsCallable function
+ throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP && oThis
+ ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+
+ return fBound;
+ };
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
+if (!Array.prototype.findIndex) {
+ Object.defineProperty(Array.prototype, 'findIndex', {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function(predicate) {
+ if (this == null) {
+ throw new TypeError('Array.prototype.find called on null or undefined');
+ }
+ if (typeof predicate !== 'function') {
+ throw new TypeError('predicate must be a function');
+ }
+ var list = Object(this);
+ var length = list.length >>> 0;
+ var thisArg = arguments[1];
+ var value;
+
+ for (var i = 0; i < length; i++) {
+ if (i in list) {
+ value = list[i];
+ if (predicate.call(thisArg, value, i, list)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+ });
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
+if (typeof Object.create != 'function') {
+ (function () {
+ var F = function () {};
+ Object.create = function (o) {
+ if (arguments.length > 1) {
+ throw new Error('Second argument not supported');
+ }
+ if (o === null) {
+ throw new Error('Cannot set a null [[Prototype]]');
+ }
+ if (typeof o != 'object') {
+ throw new TypeError('Argument must be an object');
+ }
+ F.prototype = o;
+ return new F();
+ };
+ })();
+}
+
+// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
+if (!Object.keys) {
+ Object.keys = (function () {
+ 'use strict';
+ var hasOwnProperty = Object.prototype.hasOwnProperty,
+ hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
+ dontEnums = [
+ 'toString',
+ 'toLocaleString',
+ 'valueOf',
+ 'hasOwnProperty',
+ 'isPrototypeOf',
+ 'propertyIsEnumerable',
+ 'constructor'
+ ],
+ dontEnumsLength = dontEnums.length;
+
+ return function (obj) {
+ if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
+ throw new TypeError('Object.keys called on non-object');
+ }
+
+ var result = [], prop, i;
+
+ for (prop in obj) {
+ if (hasOwnProperty.call(obj, prop)) {
+ result.push(prop);
+ }
+ }
+
+ if (hasDontEnumBug) {
+ for (i = 0; i < dontEnumsLength; i++) {
+ if (hasOwnProperty.call(obj, dontEnums[i])) {
+ result.push(dontEnums[i]);
+ }
+ }
+ }
+ return result;
+ };
+ }());
+}
+
+// http://ejohn.org/blog/objectgetprototypeof/
+if ( typeof Object.getPrototypeOf !== "function" ) {
+ if ( typeof "test".__proto__ === "object" ) {
+ Object.getPrototypeOf = function(object){
+ return object.__proto__;
+ };
+ } else {
+ Object.getPrototypeOf = function(object){
+ // May break if the constructor has been tampered with
+ return object.constructor.prototype;
+ };
+ }
+}
+
+(function() {
+ 'use strict';
+
+ angular.module('firebase')
+ .factory('$firebaseConfig', ["$FirebaseArray", "$FirebaseObject", "$injector",
+ function($FirebaseArray, $FirebaseObject, $injector) {
+ return function(configOpts) {
+ // make a copy we can modify
+ var opts = angular.extend({}, configOpts);
+ // look up factories if passed as string names
+ if( typeof opts.objectFactory === 'string' ) {
+ opts.objectFactory = $injector.get(opts.objectFactory);
+ }
+ if( typeof opts.arrayFactory === 'string' ) {
+ opts.arrayFactory = $injector.get(opts.arrayFactory);
+ }
+ // extend defaults and return
+ return angular.extend({
+ arrayFactory: $FirebaseArray,
+ objectFactory: $FirebaseObject
+ }, opts);
+ };
+ }
+ ])
+
+ .factory('$firebaseUtils', ["$q", "$timeout", "firebaseBatchDelay",
+ function($q, $timeout, firebaseBatchDelay) {
+ var utils = {
+ /**
+ * Returns a function which, each time it is invoked, will pause for `wait`
+ * milliseconds before invoking the original `fn` instance. If another
+ * request is received in that time, it resets `wait` up until `maxWait` is
+ * reached.
+ *
+ * Unlike a debounce function, once wait is received, all items that have been
+ * queued will be invoked (not just once per execution). It is acceptable to use 0,
+ * which means to batch all synchronously queued items.
+ *
+ * The batch function actually returns a wrap function that should be called on each
+ * method that is to be batched.
+ *
+ *
+ * var total = 0;
+ * var batchWrapper = batch(10, 100);
+ * var fn1 = batchWrapper(function(x) { return total += x; });
+ * var fn2 = batchWrapper(function() { console.log(total); });
+ * fn1(10);
+ * fn2();
+ * fn1(10);
+ * fn2();
+ * console.log(total); // 0 (nothing invoked yet)
+ * // after 10ms will log "10" and then "20"
+ *
+ *
+ * @param {int} wait number of milliseconds to pause before sending out after each invocation
+ * @param {int} maxWait max milliseconds to wait before sending out, defaults to wait * 10 or 100
+ * @returns {Function}
+ */
+ batch: function(wait, maxWait) {
+ wait = typeof('wait') === 'number'? wait : firebaseBatchDelay;
+ if( !maxWait ) { maxWait = wait*10 || 100; }
+ var queue = [];
+ var start;
+ var cancelTimer;
+
+ // returns `fn` wrapped in a function that queues up each call event to be
+ // invoked later inside fo runNow()
+ function createBatchFn(fn, context) {
+ if( typeof(fn) !== 'function' ) {
+ throw new Error('Must provide a function to be batched. Got '+fn);
+ }
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ queue.push([fn, context, args]);
+ resetTimer();
+ };
+ }
+
+ // clears the current wait timer and creates a new one
+ // however, if maxWait is exceeded, calles runNow() immediately
+ function resetTimer() {
+ if( cancelTimer ) {
+ cancelTimer();
+ cancelTimer = null;
+ }
+ if( start && Date.now() - start > maxWait ) {
+ utils.compile(runNow);
+ }
+ else {
+ if( !start ) { start = Date.now(); }
+ cancelTimer = utils.wait(runNow, wait);
+ }
+ }
+
+ // Clears the queue and invokes all of the functions awaiting notification
+ function runNow() {
+ cancelTimer = null;
+ start = null;
+ var copyList = queue.slice(0);
+ queue = [];
+ angular.forEach(copyList, function(parts) {
+ parts[0].apply(parts[1], parts[2]);
+ });
+ }
+
+ return createBatchFn;
+ },
+
+ /**
+ * A rudimentary debounce method
+ * @param {function} fn the function to debounce
+ * @param {object} [ctx] the `this` context to set in fn
+ * @param {int} wait number of milliseconds to pause before sending out after each invocation
+ * @param {int} [maxWait] max milliseconds to wait before sending out, defaults to wait * 10 or 100
+ */
+ debounce: function(fn, ctx, wait, maxWait) {
+ var start, cancelTimer, args;
+ if( typeof(ctx) === 'number' ) {
+ maxWait = wait;
+ wait = ctx;
+ ctx = null;
+ }
+
+ if( typeof wait !== 'number' ) {
+ throw new Error('Must provide a valid integer for wait. Try 0 for a default');
+ }
+ if( typeof(fn) !== 'function' ) {
+ throw new Error('Must provide a valid function to debounce');
+ }
+ if( !maxWait ) { maxWait = wait*10 || 100; }
+
+ // clears the current wait timer and creates a new one
+ // however, if maxWait is exceeded, calles runNow() immediately
+ function resetTimer() {
+ if( cancelTimer ) {
+ cancelTimer();
+ cancelTimer = null;
+ }
+ if( start && Date.now() - start > maxWait ) {
+ utils.compile(runNow);
+ }
+ else {
+ if( !start ) { start = Date.now(); }
+ cancelTimer = utils.wait(runNow, wait);
+ }
+ }
+
+ // Clears the queue and invokes all of the functions awaiting notification
+ function runNow() {
+ cancelTimer = null;
+ start = null;
+ fn.apply(ctx, args);
+ }
+
+ function debounced() {
+ args = Array.prototype.slice.call(arguments, 0);
+ resetTimer();
+ }
+ debounced.running = function() {
+ return start > 0;
+ };
+
+ return debounced;
+ },
+
+ assertValidRef: function(ref, msg) {
+ if( !angular.isObject(ref) ||
+ typeof(ref.ref) !== 'function' ||
+ typeof(ref.ref().transaction) !== 'function' ) {
+ throw new Error(msg || 'Invalid Firebase reference');
+ }
+ },
+
+ // http://stackoverflow.com/questions/7509831/alternative-for-the-deprecated-proto
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
+ inherit: function(ChildClass, ParentClass, methods) {
+ var childMethods = ChildClass.prototype;
+ ChildClass.prototype = Object.create(ParentClass.prototype);
+ ChildClass.prototype.constructor = ChildClass; // restoring proper constructor for child class
+ angular.forEach(Object.keys(childMethods), function(k) {
+ ChildClass.prototype[k] = childMethods[k];
+ });
+ if( angular.isObject(methods) ) {
+ angular.extend(ChildClass.prototype, methods);
+ }
+ return ChildClass;
+ },
+
+ getPrototypeMethods: function(inst, iterator, context) {
+ var methods = {};
+ var objProto = Object.getPrototypeOf({});
+ var proto = angular.isFunction(inst) && angular.isObject(inst.prototype)?
+ inst.prototype : Object.getPrototypeOf(inst);
+ while(proto && proto !== objProto) {
+ for (var key in proto) {
+ // we only invoke each key once; if a super is overridden it's skipped here
+ if (proto.hasOwnProperty(key) && !methods.hasOwnProperty(key)) {
+ methods[key] = true;
+ iterator.call(context, proto[key], key, proto);
+ }
+ }
+ proto = Object.getPrototypeOf(proto);
+ }
+ },
+
+ getPublicMethods: function(inst, iterator, context) {
+ utils.getPrototypeMethods(inst, function(m, k) {
+ if( typeof(m) === 'function' && k.charAt(0) !== '_' ) {
+ iterator.call(context, m, k);
+ }
+ });
+ },
+
+ defer: function() {
+ return $q.defer();
+ },
+
+ reject: function(msg) {
+ var def = utils.defer();
+ def.reject(msg);
+ return def.promise;
+ },
+
+ resolve: function() {
+ var def = utils.defer();
+ def.resolve.apply(def, arguments);
+ return def.promise;
+ },
+
+ wait: function(fn, wait) {
+ var to = $timeout(fn, wait||0);
+ return function() {
+ if( to ) {
+ $timeout.cancel(to);
+ to = null;
+ }
+ };
+ },
+
+ compile: function(fn) {
+ return $timeout(fn||function() {});
+ },
+
+ deepCopy: function(obj) {
+ if( !angular.isObject(obj) ) { return obj; }
+ var newCopy = angular.isArray(obj) ? obj.slice() : angular.extend({}, obj);
+ for (var key in newCopy) {
+ if (newCopy.hasOwnProperty(key)) {
+ if (angular.isObject(newCopy[key])) {
+ newCopy[key] = utils.deepCopy(newCopy[key]);
+ }
+ }
+ }
+ return newCopy;
+ },
+
+ trimKeys: function(dest, source) {
+ utils.each(dest, function(v,k) {
+ if( !source.hasOwnProperty(k) ) {
+ delete dest[k];
+ }
+ });
+ },
+
+ extendData: function(dest, source) {
+ utils.each(source, function(v,k) {
+ dest[k] = utils.deepCopy(v);
+ });
+ return dest;
+ },
+
+ scopeData: function(dataOrRec) {
+ var data = {
+ $id: dataOrRec.$id,
+ $priority: dataOrRec.$priority
+ };
+ if( dataOrRec.hasOwnProperty('$value') ) {
+ data.$value = dataOrRec.$value;
+ }
+ return utils.extendData(data, dataOrRec);
+ },
+
+ updateRec: function(rec, snap) {
+ var data = snap.val();
+ var oldData = angular.extend({}, rec);
+
+ // deal with primitives
+ if( !angular.isObject(data) ) {
+ rec.$value = data;
+ data = {};
+ }
+ else {
+ delete rec.$value;
+ }
+
+ // apply changes: remove old keys, insert new data, set priority
+ utils.trimKeys(rec, data);
+ angular.extend(rec, data);
+ rec.$priority = snap.getPriority();
+
+ return !angular.equals(oldData, rec) ||
+ oldData.$value !== rec.$value ||
+ oldData.$priority !== rec.$priority;
+ },
+
+ applyDefaults: function(rec, defaults) {
+ if( angular.isObject(defaults) ) {
+ angular.forEach(defaults, function(v,k) {
+ if( !rec.hasOwnProperty(k) ) {
+ rec[k] = v;
+ }
+ });
+ }
+ return rec;
+ },
+
+ dataKeys: function(obj) {
+ var out = [];
+ utils.each(obj, function(v,k) {
+ out.push(k);
+ });
+ return out;
+ },
+
+ each: function(obj, iterator, context) {
+ if(angular.isObject(obj)) {
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) {
+ var c = k.charAt(0);
+ if( c !== '_' && c !== '$' && c !== '.' ) {
+ iterator.call(context, obj[k], k, obj);
+ }
+ }
+ }
+ }
+ else if(angular.isArray(obj)) {
+ for(var i = 0, len = obj.length; i < len; i++) {
+ iterator.call(context, obj[i], i, obj);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * A utility for retrieving a Firebase reference or DataSnapshot's
+ * key name. This is backwards-compatible with `name()` from Firebase
+ * 1.x.x and `key()` from Firebase 2.0.0+. Once support for Firebase
+ * 1.x.x is dropped in AngularFire, this helper can be removed.
+ */
+ getKey: function(refOrSnapshot) {
+ return (typeof refOrSnapshot.key === 'function') ? refOrSnapshot.key() : refOrSnapshot.name();
+ },
+
+ /**
+ * A utility for converting records to JSON objects
+ * which we can save into Firebase. It asserts valid
+ * keys and strips off any items prefixed with $.
+ *
+ * If the rec passed into this method has a toJSON()
+ * method, that will be used in place of the custom
+ * functionality here.
+ *
+ * @param rec
+ * @returns {*}
+ */
+ toJSON: function(rec) {
+ var dat;
+ if( !angular.isObject(rec) ) {
+ rec = {$value: rec};
+ }
+ if (angular.isFunction(rec.toJSON)) {
+ dat = rec.toJSON();
+ }
+ else {
+ dat = {};
+ utils.each(rec, function (v, k) {
+ dat[k] = stripDollarPrefixedKeys(v);
+ });
+ }
+ if( angular.isDefined(rec.$value) && Object.keys(dat).length === 0 && rec.$value !== null ) {
+ dat['.value'] = rec.$value;
+ }
+ if( angular.isDefined(rec.$priority) && Object.keys(dat).length > 0 && rec.$priority !== null ) {
+ dat['.priority'] = rec.$priority;
+ }
+ angular.forEach(dat, function(v,k) {
+ if (k.match(/[.$\[\]#\/]/) && k !== '.value' && k !== '.priority' ) {
+ throw new Error('Invalid key ' + k + ' (cannot contain .$[]#)');
+ }
+ else if( angular.isUndefined(v) ) {
+ throw new Error('Key '+k+' was undefined. Cannot pass undefined in JSON. Use null instead.');
+ }
+ });
+ return dat;
+ },
+ batchDelay: firebaseBatchDelay,
+ allPromises: $q.all.bind($q)
+ };
+
+ return utils;
+ }
+ ]);
+
+ function stripDollarPrefixedKeys(data) {
+ if( !angular.isObject(data) ) { return data; }
+ var out = angular.isArray(data)? [] : {};
+ angular.forEach(data, function(v,k) {
+ if(typeof k !== 'string' || k.charAt(0) !== '$') {
+ out[k] = stripDollarPrefixedKeys(v);
+ }
+ });
+ return out;
+ }
+})();
diff --git a/dist/angularfire.min.js b/dist/angularfire.min.js
new file mode 100644
index 00000000..0c594be8
--- /dev/null
+++ b/dist/angularfire.min.js
@@ -0,0 +1,12 @@
+/*!
+ * AngularFire is the officially supported AngularJS binding for Firebase. Firebase
+ * is a full backend so you don't need servers to build your Angular app. AngularFire
+ * provides you with the $firebase service which allows you to easily keep your $scope
+ * variables in sync with your Firebase backend.
+ *
+ * AngularFire 0.9.0
+ * https://github.com/firebase/angularfire/
+ * Date: 11/18/2014
+ * License: MIT
+ */
+!function(a){"use strict";angular.module("firebase",[]).value("Firebase",a.Firebase).value("firebaseBatchDelay",50)}(window),function(){"use strict";angular.module("firebase").factory("$FirebaseArray",["$log","$firebaseUtils",function(a,b){function c(a,c,d){var e=this;return this._observers=[],this.$list=[],this._inst=a,this._promise=d,this._destroyFn=c,this._indexCache={},b.getPublicMethods(e,function(a,b){e.$list[b]=a.bind(e)}),this.$list}return c.prototype={$add:function(a){return this._assertNotDestroyed("$add"),this.$inst().$push(b.toJSON(a))},$save:function(a){this._assertNotDestroyed("$save");var c=this,d=c._resolveItem(a),e=c.$keyAt(d);return null!==e?c.$inst().$set(e,b.toJSON(d)).then(function(a){return c.$$notify("child_changed",e),a}):b.reject("Invalid record; could determine its key: "+a)},$remove:function(a){this._assertNotDestroyed("$remove");var c=this.$keyAt(a);return null!==c?this.$inst().$remove(c):b.reject("Invalid record; could not find key: "+a)},$keyAt:function(a){var b=this._resolveItem(a);return this.$$getKey(b)},$indexFor:function(a){var b=this,c=b._indexCache;if(!c.hasOwnProperty(a)||b.$keyAt(c[a])!==a){var d=b.$list.findIndex(function(c){return b.$$getKey(c)===a});-1!==d&&(c[a]=d)}return c.hasOwnProperty(a)?c[a]:-1},$loaded:function(a,b){var c=this._promise;return arguments.length&&(c=c.then.call(c,a,b)),c},$inst:function(){return this._inst},$watch:function(a,b){var c=this._observers;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(b){this._isDestroyed||(this._isDestroyed=!0,this.$list.length=0,a.debug("destroy called for FirebaseArray: "+this.$inst().$ref().toString()),this._destroyFn(b))},$getRecord:function(a){var b=this.$indexFor(a);return b>-1?this.$list[b]:null},$$added:function(a){var c=this.$indexFor(b.getKey(a));if(-1===c){var d=a.val();return angular.isObject(d)||(d={$value:d}),d.$id=b.getKey(a),d.$priority=a.getPriority(),b.applyDefaults(d,this.$$defaults),d}return!1},$$removed:function(a){return this.$indexFor(b.getKey(a))>-1},$$updated:function(a){var c=!1,d=this.$getRecord(b.getKey(a));return angular.isObject(d)&&(c=b.updateRec(d,a),b.applyDefaults(d,this.$$defaults)),c},$$moved:function(a){var c=this.$getRecord(b.getKey(a));return angular.isObject(c)?(c.$priority=a.getPriority(),!0):!1},$$error:function(b){a.error(b),this.$destroy(b)},$$getKey:function(a){return angular.isObject(a)?a.$id:null},$$process:function(a,b,c){var d,e=this.$$getKey(b),f=!1;switch(a){case"child_added":d=this.$indexFor(e);break;case"child_moved":d=this.$indexFor(e),this._spliceOut(e);break;case"child_removed":f=null!==this._spliceOut(e);break;case"child_changed":f=!0;break;default:throw new Error("Invalid event type: "+a)}return angular.isDefined(d)&&(f=this._addAfter(b,c)!==d),f&&this.$$notify(a,e,c),f},$$notify:function(a,b,c){var d={event:a,key:b};angular.isDefined(c)&&(d.prevChild=c),angular.forEach(this._observers,function(a){a[0].call(a[1],d)})},_addAfter:function(a,b){var c;return null===b?c=0:(c=this.$indexFor(b)+1,0===c&&(c=this.$list.length)),this.$list.splice(c,0,a),this._indexCache[this.$$getKey(a)]=c,c},_spliceOut:function(a){var b=this.$indexFor(a);return b>-1?(delete this._indexCache[a],this.$list.splice(b,1)[0]):null},_resolveItem:function(a){var b=this.$list;if(angular.isNumber(a)&&a>=0&&b.length>=a)return b[a];if(angular.isObject(a)){var c=this.$$getKey(a),d=this.$getRecord(c);return d===a?d:null}return null},_assertNotDestroyed:function(a){if(this._isDestroyed)throw new Error("Cannot call "+a+" method on a destroyed $FirebaseArray object")}},c.$extendFactory=function(a,d){return 1===arguments.length&&angular.isObject(a)&&(d=a,a=function(){return c.apply(this,arguments)}),b.inherit(a,c,d)},c}])}(),function(){"use strict";var a;angular.module("firebase").factory("$firebaseAuth",["$q",function(b){return function(c){var d=new a(b,c);return d.construct()}}]),a=function(a,b){if(this._q=a,"string"==typeof b)throw new Error("Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.");this._ref=b},a.prototype={construct:function(){return this._object={$authWithCustomToken:this.authWithCustomToken.bind(this),$authAnonymously:this.authAnonymously.bind(this),$authWithPassword:this.authWithPassword.bind(this),$authWithOAuthPopup:this.authWithOAuthPopup.bind(this),$authWithOAuthRedirect:this.authWithOAuthRedirect.bind(this),$authWithOAuthToken:this.authWithOAuthToken.bind(this),$unauth:this.unauth.bind(this),$onAuth:this.onAuth.bind(this),$getAuth:this.getAuth.bind(this),$requireAuth:this.requireAuth.bind(this),$waitForAuth:this.waitForAuth.bind(this),$createUser:this.createUser.bind(this),$changePassword:this.changePassword.bind(this),$removeUser:this.removeUser.bind(this),$sendPasswordResetEmail:this.sendPasswordResetEmail.bind(this)},this._object},_onLoginHandler:function(a,b,c){null!==b?a.reject(b):a.resolve(c)},authWithCustomToken:function(a,b){var c=this._q.defer();return this._ref.authWithCustomToken(a,this._onLoginHandler.bind(this,c),b),c.promise},authAnonymously:function(a){var b=this._q.defer();return this._ref.authAnonymously(this._onLoginHandler.bind(this,b),a),b.promise},authWithPassword:function(a,b){var c=this._q.defer();return this._ref.authWithPassword(a,this._onLoginHandler.bind(this,c),b),c.promise},authWithOAuthPopup:function(a,b){var c=this._q.defer();return this._ref.authWithOAuthPopup(a,this._onLoginHandler.bind(this,c),b),c.promise},authWithOAuthRedirect:function(a,b){var c=this._q.defer();return this._ref.authWithOAuthRedirect(a,this._onLoginHandler.bind(this,c),b),c.promise},authWithOAuthToken:function(a,b,c){var d=this._q.defer();return this._ref.authWithOAuthToken(a,b,this._onLoginHandler.bind(this,d),c),d.promise},unauth:function(){null!==this.getAuth()&&this._ref.unauth()},onAuth:function(a,b){var c=this;return this._ref.onAuth(a,b),function(){c._ref.offAuth(a,b)}},getAuth:function(){return this._ref.getAuth()},_routerMethodOnAuthCallback:function(a,b,c){null!==c?a.resolve(c):b?a.reject("AUTH_REQUIRED"):a.resolve(null),this._ref.offAuth(this._routerMethodOnAuthCallback)},requireAuth:function(){var a=this._q.defer();return this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this,a,!0)),a.promise},waitForAuth:function(){var a=this._q.defer();return this._ref.onAuth(this._routerMethodOnAuthCallback.bind(this,a,!1)),a.promise},createUser:function(a,b){var c=this._q.defer();return this._ref.createUser({email:a,password:b},function(a){null!==a?c.reject(a):c.resolve()}),c.promise},changePassword:function(a,b,c){var d=this._q.defer();return this._ref.changePassword({email:a,oldPassword:b,newPassword:c},function(a){null!==a?d.reject(a):d.resolve()}),d.promise},removeUser:function(a,b){var c=this._q.defer();return this._ref.removeUser({email:a,password:b},function(a){null!==a?c.reject(a):c.resolve()}),c.promise},sendPasswordResetEmail:function(a){var b=this._q.defer();return this._ref.resetPassword({email:a},function(a){null!==a?b.reject(a):b.resolve()}),b.promise}}}(),function(){"use strict";angular.module("firebase").factory("$FirebaseObject",["$parse","$firebaseUtils","$log","$interval",function(a,b,c,d){function e(a,c,d){this.$$conf={promise:d,inst:a,binding:new f(this),destroyFn:c,listeners:[]},Object.defineProperty(this,"$$conf",{value:this.$$conf}),this.$id=b.getKey(a.$ref().ref()),this.$priority=null,b.applyDefaults(this,this.$$defaults)}function f(a){this.subs=[],this.scope=null,this.key=null,this.rec=a}return e.prototype={$save:function(){var a=this;return a.$inst().$set(b.toJSON(a)).then(function(b){return a.$$notify(),b})},$remove:function(){var a=this;return b.trimKeys(this,{}),this.$value=null,a.$inst().$remove(a.$id).then(function(b){return a.$$notify(),b})},$loaded:function(a,b){var c=this.$$conf.promise;return arguments.length&&(c=c.then.call(c,a,b)),c},$inst:function(){return this.$$conf.inst},$bindTo:function(a,b){var c=this;return c.$loaded().then(function(){return c.$$conf.binding.bindTo(a,b)})},$watch:function(a,b){var c=this.$$conf.listeners;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(a){var c=this;c.$isDestroyed||(c.$isDestroyed=!0,c.$$conf.binding.destroy(),b.each(c,function(a,b){delete c[b]}),c.$$conf.destroyFn(a))},$$updated:function(a){var c=b.updateRec(this,a);return b.applyDefaults(this,this.$$defaults),c},$$error:function(a){c.error(a),this.$destroy(a)},$$scopeUpdated:function(a){return this.$inst().$set(b.toJSON(a))},$$notify:function(){var a=this,b=this.$$conf.listeners.slice();angular.forEach(b,function(b){b[0].call(b[1],{event:"value",key:a.$id})})},forEach:function(a,c){return b.each(this,a,c)}},e.$extendFactory=function(a,c){return 1===arguments.length&&angular.isObject(a)&&(c=a,a=function(){e.apply(this,arguments)}),b.inherit(a,e,c)},f.prototype={assertNotBound:function(a){if(this.scope){var d="Cannot bind to "+a+" because this instance is already bound to "+this.key+"; one binding per instance (call unbind method or create another $firebase instance)";return c.error(d),b.reject(d)}},bindTo:function(c,e){function f(f){function g(a){var c=h(),d=b.scopeData(a);return angular.equals(c,d)&&c.$priority===a.$priority&&c.$value===a.$value}function h(){return b.scopeData(l(c))}function i(a){l.assign(c,b.scopeData(a))}function j(){var a=l(c);(a.$value!==m.$value||a.$priority!==m.$priority)&&n()}var k=!1,l=a(e),m=f.rec;f.scope=c,f.varName=e;var n=function(){var a=b.debounce(function(){m.$$scopeUpdated(h())["finally"](function(){k=!1})},50,500);g(m)||(k=!0,a())},o=function(){k||g(m)||i(m)};return function(){var a="_firebaseCounterForVar"+e;c[a]=0;var b=d(function(){c[a]++},51,0,!1);f.subs.push(c.$watch(a,j)),f.subs.push(function(){d.cancel(b),delete c[a]})}(),i(m),f.subs.push(c.$on("$destroy",f.unbind.bind(f))),f.subs.push(c.$watch(e,n,!0)),f.subs.push(m.$watch(o)),f.unbind.bind(f)}return this.assertNotBound(e)||f(this)},unbind:function(){this.scope&&(angular.forEach(this.subs,function(a){a()}),this.subs=[],this.scope=null,this.key=null)},destroy:function(){this.unbind(),this.rec=null}},e}])}(),function(){"use strict";angular.module("firebase").factory("$firebase",["$firebaseUtils","$firebaseConfig",function(a,b){function c(a,d){return this instanceof c?(this._config=b(d),this._ref=a,this._arraySync=null,this._objectSync=null,void this._assertValidConfig(a,this._config)):new c(a,d)}function d(b,c){function d(a){q.isDestroyed=!0;var c=b.$ref();c.off("child_added",k),c.off("child_moved",m),c.off("child_changed",l),c.off("child_removed",n),i=null,p(a||"destroyed")}function e(){var a=b.$ref();a.on("child_added",k,o),a.on("child_moved",m,o),a.on("child_changed",l,o),a.on("child_removed",n,o),a.once("value",function(){p(null)},p)}function f(a){h&&(a?h.reject(a):h.resolve(i),h=null)}function g(a){if(!angular.isArray(a)){var b=Object.prototype.toString.call(a);throw new Error('arrayFactory must return a valid array that passes angular.isArray and Array.isArray, but received "'+b+'"')}}var h=a.defer(),i=new c(b,d,h.promise),j=a.batch(),k=j(function(a,b){var c=i.$$added(a,b);c&&i.$$process("child_added",c,b)}),l=j(function(b){var c=i.$getRecord(a.getKey(b));if(c){var d=i.$$updated(b);d&&i.$$process("child_changed",c)}}),m=j(function(b,c){var d=i.$getRecord(a.getKey(b));if(d){var e=i.$$moved(b,c);e&&i.$$process("child_moved",d,c)}}),n=j(function(b){var c=i.$getRecord(a.getKey(b));if(c){var d=i.$$removed(b);d&&i.$$process("child_removed",c)}}),o=j(i.$$error,i),p=j(f),q=this;q.isDestroyed=!1,q.getArray=function(){return i},g(i),e()}function e(b,c){function d(a){n.isDestroyed=!0,i.off("value",k),h=null,m(a||"destroyed")}function e(){i.on("value",k,l),i.once("value",function(){m(null)},m)}function f(a){g&&(a?g.reject(a):g.resolve(h),g=null)}var g=a.defer(),h=new c(b,d,g.promise),i=b.$ref(),j=a.batch(),k=j(function(a){var b=h.$$updated(a);b&&h.$$notify()}),l=j(h.$$error,h),m=j(f),n=this;n.isDestroyed=!1,n.getObject=function(){return h},e()}return c.prototype={$ref:function(){return this._ref},$push:function(b){var c=a.defer(),d=this._ref.ref().push(),e=this._handle(c,d);return arguments.length>0?d.set(b,e):e(),c.promise},$set:function(b,c){var d=this._ref,e=a.defer();if(arguments.length>1?d=d.ref().child(b):c=b,angular.isFunction(d.set)||!angular.isObject(c))d.ref().set(c,this._handle(e,d));else{var f=angular.extend({},c);d.once("value",function(b){b.forEach(function(b){f.hasOwnProperty(a.getKey(b))||(f[a.getKey(b)]=null)}),d.ref().update(f,this._handle(e,d))},this)}return e.promise},$remove:function(b){var c,d=this._ref,e=this,f=a.defer();if(arguments.length>0&&(d=d.ref().child(b)),angular.isFunction(d.remove))d.remove(e._handle(f,d)),c=f.promise;else{var g=[];d.once("value",function(b){b.forEach(function(b){var c=a.defer();g.push(c),b.ref().remove(e._handle(c,b.ref()))},e)}),c=a.allPromises(g).then(function(){return d})}return c},$update:function(b,c){var d=this._ref.ref(),e=a.defer();return arguments.length>1?d=d.child(b):c=b,d.update(c,this._handle(e,d)),e.promise},$transaction:function(b,c,d){var e=this._ref.ref();angular.isFunction(b)?(d=c,c=b):e=e.child(b),d=!!d;var f=a.defer();return e.transaction(c,function(a,b,c){a?f.reject(a):f.resolve(b?c:null)},d),f.promise},$asObject:function(){return(!this._objectSync||this._objectSync.isDestroyed)&&(this._objectSync=new e(this,this._config.objectFactory)),this._objectSync.getObject()},$asArray:function(){return(!this._arraySync||this._arraySync.isDestroyed)&&(this._arraySync=new d(this,this._config.arrayFactory)),this._arraySync.getArray()},_handle:function(a){var b=Array.prototype.slice.call(arguments,1);return function(c){c?a.reject(c):a.resolve.apply(a,b)}},_assertValidConfig:function(b,c){if(a.assertValidRef(b,"Must pass a valid Firebase reference to $firebase (not a string or URL)"),!angular.isFunction(c.arrayFactory))throw new Error("config.arrayFactory must be a valid function");if(!angular.isFunction(c.objectFactory))throw new Error("config.objectFactory must be a valid function")}},c}])}(),Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){if(void 0===this||null===this)throw new TypeError("'this' is null or not defined");var c=this.length>>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),Array.prototype.findIndex||Object.defineProperty(Array.prototype,"findIndex",{enumerable:!1,configurable:!0,writable:!0,value:function(a){if(null==this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof a)throw new TypeError("predicate must be a function");for(var b,c=Object(this),d=c.length>>>0,e=arguments[1],f=0;d>f;f++)if(f in c&&(b=c[f],a.call(e,b,f,c)))return f;return-1}}),"function"!=typeof Object.create&&!function(){var a=function(){};Object.create=function(b){if(arguments.length>1)throw new Error("Second argument not supported");if(null===b)throw new Error("Cannot set a null [[Prototype]]");if("object"!=typeof b)throw new TypeError("Argument must be an object");return a.prototype=b,new a}}(),Object.keys||(Object.keys=function(){"use strict";var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&("function"!=typeof e||null===e))throw new TypeError("Object.keys called on non-object");var f,g,h=[];for(f in e)a.call(e,f)&&h.push(f);if(b)for(g=0;d>g;g++)a.call(e,c[g])&&h.push(c[g]);return h}}()),"function"!=typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype}),function(){"use strict";function a(b){if(!angular.isObject(b))return b;var c=angular.isArray(b)?[]:{};return angular.forEach(b,function(b,d){("string"!=typeof d||"$"!==d.charAt(0))&&(c[d]=a(b))}),c}angular.module("firebase").factory("$firebaseConfig",["$FirebaseArray","$FirebaseObject","$injector",function(a,b,c){return function(d){var e=angular.extend({},d);return"string"==typeof e.objectFactory&&(e.objectFactory=c.get(e.objectFactory)),"string"==typeof e.arrayFactory&&(e.arrayFactory=c.get(e.arrayFactory)),angular.extend({arrayFactory:a,objectFactory:b},e)}}]).factory("$firebaseUtils",["$q","$timeout","firebaseBatchDelay",function(b,c,d){var e={batch:function(a,b){function c(a,b){if("function"!=typeof a)throw new Error("Must provide a function to be batched. Got "+a);return function(){var c=Array.prototype.slice.call(arguments,0);j.push([a,b,c]),f()}}function f(){i&&(i(),i=null),h&&Date.now()-h>b?e.compile(g):(h||(h=Date.now()),i=e.wait(g,a))}function g(){i=null,h=null;var a=j.slice(0);j=[],angular.forEach(a,function(a){a[0].apply(a[1],a[2])})}a=d,b||(b=10*a||100);var h,i,j=[];return c},debounce:function(a,b,c,d){function f(){j&&(j(),j=null),i&&Date.now()-i>d?e.compile(g):(i||(i=Date.now()),j=e.wait(g,c))}function g(){j=null,i=null,a.apply(b,k)}function h(){k=Array.prototype.slice.call(arguments,0),f()}var i,j,k;if("number"==typeof b&&(d=c,c=b,b=null),"number"!=typeof c)throw new Error("Must provide a valid integer for wait. Try 0 for a default");if("function"!=typeof a)throw new Error("Must provide a valid function to debounce");return d||(d=10*c||100),h.running=function(){return i>0},h},assertValidRef:function(a,b){if(!angular.isObject(a)||"function"!=typeof a.ref||"function"!=typeof a.ref().transaction)throw new Error(b||"Invalid Firebase reference")},inherit:function(a,b,c){var d=a.prototype;return a.prototype=Object.create(b.prototype),a.prototype.constructor=a,angular.forEach(Object.keys(d),function(b){a.prototype[b]=d[b]}),angular.isObject(c)&&angular.extend(a.prototype,c),a},getPrototypeMethods:function(a,b,c){for(var d={},e=Object.getPrototypeOf({}),f=angular.isFunction(a)&&angular.isObject(a.prototype)?a.prototype:Object.getPrototypeOf(a);f&&f!==e;){for(var g in f)f.hasOwnProperty(g)&&!d.hasOwnProperty(g)&&(d[g]=!0,b.call(c,f[g],g,f));f=Object.getPrototypeOf(f)}},getPublicMethods:function(a,b,c){e.getPrototypeMethods(a,function(a,d){"function"==typeof a&&"_"!==d.charAt(0)&&b.call(c,a,d)})},defer:function(){return b.defer()},reject:function(a){var b=e.defer();return b.reject(a),b.promise},resolve:function(){var a=e.defer();return a.resolve.apply(a,arguments),a.promise},wait:function(a,b){var d=c(a,b||0);return function(){d&&(c.cancel(d),d=null)}},compile:function(a){return c(a||function(){})},deepCopy:function(a){if(!angular.isObject(a))return a;var b=angular.isArray(a)?a.slice():angular.extend({},a);for(var c in b)b.hasOwnProperty(c)&&angular.isObject(b[c])&&(b[c]=e.deepCopy(b[c]));return b},trimKeys:function(a,b){e.each(a,function(c,d){b.hasOwnProperty(d)||delete a[d]})},extendData:function(a,b){return e.each(b,function(b,c){a[c]=e.deepCopy(b)}),a},scopeData:function(a){var b={$id:a.$id,$priority:a.$priority};return a.hasOwnProperty("$value")&&(b.$value=a.$value),e.extendData(b,a)},updateRec:function(a,b){var c=b.val(),d=angular.extend({},a);return angular.isObject(c)?delete a.$value:(a.$value=c,c={}),e.trimKeys(a,c),angular.extend(a,c),a.$priority=b.getPriority(),!angular.equals(d,a)||d.$value!==a.$value||d.$priority!==a.$priority},applyDefaults:function(a,b){return angular.isObject(b)&&angular.forEach(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)}),a},dataKeys:function(a){var b=[];return e.each(a,function(a,c){b.push(c)}),b},each:function(a,b,c){if(angular.isObject(a)){for(var d in a)if(a.hasOwnProperty(d)){var e=d.charAt(0);"_"!==e&&"$"!==e&&"."!==e&&b.call(c,a[d],d,a)}}else if(angular.isArray(a))for(var f=0,g=a.length;g>f;f++)b.call(c,a[f],f,a);return a},getKey:function(a){return"function"==typeof a.key?a.key():a.name()},toJSON:function(b){var c;return angular.isObject(b)||(b={$value:b}),angular.isFunction(b.toJSON)?c=b.toJSON():(c={},e.each(b,function(b,d){c[d]=a(b)})),angular.isDefined(b.$value)&&0===Object.keys(c).length&&null!==b.$value&&(c[".value"]=b.$value),angular.isDefined(b.$priority)&&Object.keys(c).length>0&&null!==b.$priority&&(c[".priority"]=b.$priority),angular.forEach(c,function(a,b){if(b.match(/[.$\[\]#\/]/)&&".value"!==b&&".priority"!==b)throw new Error("Invalid key "+b+" (cannot contain .$[]#)");if(angular.isUndefined(a))throw new Error("Key "+b+" was undefined. Cannot pass undefined in JSON. Use null instead.")}),c},batchDelay:d,allPromises:b.all.bind(b)};return e}])}();
\ No newline at end of file
diff --git a/package.json b/package.json
index 927ff196..905d9a3b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "angularfire",
"description": "The officially supported AngularJS binding for Firebase",
- "version": "0.0.0",
+ "version": "0.9.0",
"author": "Firebase (https://www.firebase.com/)",
"homepage": "https://github.com/firebase/angularfire",
"repository": {