'use strict'; /** * Fireproofs an existing Firebase reference, giving it magic promise powers. * @name Fireproof * @constructor * @param {Firebase} firebaseRef A Firebase reference object. * @property then A promise shortcut for .once('value'), * except for references created by .push(), where it resolves on success * and rejects on failure of the property object. * @example * var fp = new Fireproof(new Firebase('https://test.firebaseio.com/something')); * fp.then(function(snap) { console.log(snap.val()); }); */ function Fireproof(firebaseRef, promise) { this._ref = firebaseRef; if (promise && promise.then) { this.then = promise.then.bind(promise); } else { this.then = function(ok, fail) { return this.once('value', function() {}) .then(ok || null, fail || null); }; } } var Q; Fireproof._checkQ = function() { if (Q === undefined) { throw new Error('You must supply a Defer-style promise library to Fireproof!'); } return Q; }; Fireproof._nextTick = function(fn) { if (process && process.nextTick) { process.nextTick(fn); } else { setTimeout(fn, 0); } }; Fireproof._handleError = function(onComplete) { var deferred = Fireproof._checkQ().defer(); var rv = function(err, val) { if (onComplete && typeof onComplete === 'function') { Fireproof._nextTick(function() { onComplete(err); }); } if (err) { deferred.reject(err); } else { deferred.resolve(val); } }; rv.promise = deferred.promise; return rv; }; /** * Tell Fireproof to use a given promise library from now on. * @param {Q} Q a Q-style promise constructor with at least defer(). * @throws if you don't provide a valid Deferred-style promise library. */ Fireproof.bless = function(newQ) { if (newQ === undefined || newQ === null || typeof(newQ.defer) !== 'function') { throw new Error('You tried to give Fireproof an invalid Q library!'); } var deferred = newQ.defer(); if (deferred === undefined || deferred === null || deferred.promise === undefined || deferred.promise === null || typeof(deferred.reject) !== 'function' || typeof(deferred.resolve) !== 'function' || typeof(deferred.promise.then) !== 'function') { throw new Error('You tried to give Fireproof an invalid Q library!'); } Q = newQ; }; /* FIXME(goldibex): Find out the reason for this demonry. * For reasons completely incomprehensible to me, some type of race condition * is possible if multiple Fireproof references attempt authentication at the * same time, the result of which is one or more of the promises will never * resolve. * Accordingly, it is necessary that we wrap authentication actions in a * global lock. This is accomplished using setInterval. No, I don't like it * any more than you do. */ var authing = false; /** * Delegates Firebase#auth. * @method Fireproof#auth * @param {string} authToken Firebase authentication token. * @param {function=} onComplete Callback on initial completion. * @param {function=} onCancel Callback if we ever get disconnected. * @returns {Promise} Resolves on success, rejects on failure. */ Fireproof.prototype.auth = function(authToken, onComplete, onCancel) { var deferred = Fireproof._checkQ().defer(), self = this; var authIntervalId = setInterval(function() { if (!authing) { authing = true; self._ref.auth(authToken, function(err, info) { authing = false; clearInterval(authIntervalId); if (err !== null) { deferred.reject(err); } else { deferred.resolve(info); } if (typeof onComplete === 'function') { Fireproof._nextTick(function() { onComplete(err, info); }); } }, onCancel); } }, 1); return deferred.promise; }; /** * Delegates Firebase#child, wrapping the child in fireproofing. * @method Fireproof#child * @param {string} childPath The subpath to refer to. * @returns {Fireproof} A reference to the child path. */ Fireproof.prototype.child = function(childPath) { return new Fireproof(this._ref.child(childPath)); }; /** * Delegates Firebase#parent. * @method Fireproof#parent * @returns {Fireproof} A ref to the parent path, or null if there is none. */ Fireproof.prototype.parent = function() { if (this._ref.parent() === null) { return null; } else { return new Fireproof(this._ref.parent()); } }; /** * Delegates Firebase#root. * @method Fireproof#root * @returns {Fireproof} A ref to the root. */ Fireproof.prototype.root = function() { return new Fireproof(this._ref.root()); }; /** * Hands back the original Firebase reference. * @method Fireproof#toFirebase * @returns {Firebase} The proxied Firebase reference. */ Fireproof.prototype.toFirebase = function() { return this._ref; }; /** * Delegates Firebase#name. * @method Fireproof#name * @returns {string} The last component of this reference object's path. */ Fireproof.prototype.name = function() { return this._ref.name(); }; /** * Delegates Firebase#toString. * @method Fireproof#toString * @returns {string} The full URL of this reference object. */ Fireproof.prototype.toString = function() { return this._ref.toString(); }; module.exports = Fireproof;