Source: core.js

'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;