<commit_before>describe('ionNavBackButton directive', function() {
  beforeEach(module('ionic'));

  function setup(attr, content) {
    var el;
    inject(function($compile, $rootScope) {
      el = angular.element('<ion-nav-back-button '+(attr||'')+'>'+(content||'')+'</ion-nav-back-button>');
      el.data('$ionNavBarController', {
        back: jasmine.createSpy('back'),
      });
      el = $compile(el)($rootScope.$new());
      $rootScope.$apply();
    });
    return el;
  }

  it('should error without a parent ionNavBar', inject(function($compile, $rootScope) {
    expect(function() {
      $compile('<ion-nav-back-button>')($rootScope);
    }).toThrow();
  }));

  it('should should have class', function() {
    var el = setup();
    expect(el.hasClass('button back-button')).toBe(true);
  });

  it('should hide based on historyChange', inject(function($rootScope) {
    ionic.animationFrameThrottle = function(cb) { return cb; };
    var el = setup();
    el.scope().backButtonShown = true;
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply();
    expect(el.hasClass('ng-hide')).toBe(false);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:false});
    el.scope().$apply();
    expect(el.hasClass('ng-hide')).toBe(true);
  }));

  it('should hide based on backButtonShown', inject(function($rootScope) {
    ionic.animationFrameThrottle = function(cb) { return cb; };
    var el = setup();
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(false);
    el.scope().$apply('backButtonShown = false');
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:false});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(false);
  }));

  it('should transclude content', function() {
    var el = setup('', 'content {{1+2}}');
    expect(el.text().trim()).toBe('content 3');
    expect(el.children().eq(0)[0].tagName.toLowerCase()).toBe('b');
  });

  it('should go back on click by default', function() {
    var el = setup();
    expect(el.controller('ionNavBar').back).not.toHaveBeenCalled();
    el.triggerHandler('click');
    expect(el.controller('ionNavBar').back).toHaveBeenCalled();
  });

  it('should do ngClick expression if defined', function() {
    var el = setup('ng-click="doSomething()"');
    el.scope().$navBack = jasmine.createSpy('$navBack');
    el.scope().doSomething = jasmine.createSpy('doSomething');
    el.triggerHandler('click');
    expect(el.scope().$navBack).not.toHaveBeenCalled();
    expect(el.scope().doSomething).toHaveBeenCalled();
  });

  describe('platforms', function() {
    describe('iOS', function() {
      beforeEach(function($provide) {
        TestUtil.setPlatform('ios');
      });

      it('should not set default back button icon if icon classname exists', function() {
        var el = setup('class="ion-navicon"');
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(false);
      });

      it('should not set default back button icon if icon child exists', function() {
        var el = setup('', '');
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(false);
      });

      it('Should set default back button icon from ionicNavBarConfig ', inject(function($ionicNavBarConfig) {
        var el = setup();
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(true);
      }));
    });

    // Android defaults disabled for now
    // describe('android', function() {
    //   beforeEach(function($provide) {
    //     TestUtil.setPlatform('android');
    //   });

    //   it('Should set default back button icon from ionicNavBarConfig ', inject(function($ionicNavBarConfig) {
    //     var el = setup();
    //     expect(el.hasClass('ion-android-arrow-back')).toBe(true);
    //   }));
    // });
  });
});
<commit_msg>test(navBackButton): remove redundant `should` word from test case description<commit_after>describe('ionNavBackButton directive', function() {
  beforeEach(module('ionic'));

  function setup(attr, content) {
    var el;
    inject(function($compile, $rootScope) {
      el = angular.element('<ion-nav-back-button '+(attr||'')+'>'+(content||'')+'</ion-nav-back-button>');
      el.data('$ionNavBarController', {
        back: jasmine.createSpy('back'),
      });
      el = $compile(el)($rootScope.$new());
      $rootScope.$apply();
    });
    return el;
  }

  it('should error without a parent ionNavBar', inject(function($compile, $rootScope) {
    expect(function() {
      $compile('<ion-nav-back-button>')($rootScope);
    }).toThrow();
  }));

  it('should have class', function() {
    var el = setup();
    expect(el.hasClass('button back-button')).toBe(true);
  });

  it('should hide based on historyChange', inject(function($rootScope) {
    ionic.animationFrameThrottle = function(cb) { return cb; };
    var el = setup();
    el.scope().backButtonShown = true;
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply();
    expect(el.hasClass('ng-hide')).toBe(false);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:false});
    el.scope().$apply();
    expect(el.hasClass('ng-hide')).toBe(true);
  }));

  it('should hide based on backButtonShown', inject(function($rootScope) {
    ionic.animationFrameThrottle = function(cb) { return cb; };
    var el = setup();
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(false);
    el.scope().$apply('backButtonShown = false');
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:false});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(true);
    $rootScope.$broadcast('$viewHistory.historyChange', {showBack:true});
    el.scope().$apply('backButtonShown = true');
    expect(el.hasClass('ng-hide')).toBe(false);
  }));

  it('should transclude content', function() {
    var el = setup('', 'content {{1+2}}');
    expect(el.text().trim()).toBe('content 3');
    expect(el.children().eq(0)[0].tagName.toLowerCase()).toBe('b');
  });

  it('should go back on click by default', function() {
    var el = setup();
    expect(el.controller('ionNavBar').back).not.toHaveBeenCalled();
    el.triggerHandler('click');
    expect(el.controller('ionNavBar').back).toHaveBeenCalled();
  });

  it('should do ngClick expression if defined', function() {
    var el = setup('ng-click="doSomething()"');
    el.scope().$navBack = jasmine.createSpy('$navBack');
    el.scope().doSomething = jasmine.createSpy('doSomething');
    el.triggerHandler('click');
    expect(el.scope().$navBack).not.toHaveBeenCalled();
    expect(el.scope().doSomething).toHaveBeenCalled();
  });

  describe('platforms', function() {
    describe('iOS', function() {
      beforeEach(function($provide) {
        TestUtil.setPlatform('ios');
      });

      it('should not set default back button icon if icon classname exists', function() {
        var el = setup('class="ion-navicon"');
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(false);
      });

      it('should not set default back button icon if icon child exists', function() {
        var el = setup('', '');
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(false);
      });

      it('Should set default back button icon from ionicNavBarConfig ', inject(function($ionicNavBarConfig) {
        var el = setup();
        expect(el.hasClass('ion-ios7-arrow-back')).toBe(true);
      }));
    });

    // Android defaults disabled for now
    // describe('android', function() {
    //   beforeEach(function($provide) {
    //     TestUtil.setPlatform('android');
    //   });

    //   it('Should set default back button icon from ionicNavBarConfig ', inject(function($ionicNavBarConfig) {
    //     var el = setup();
    //     expect(el.hasClass('ion-android-arrow-back')).toBe(true);
    //   }));
    // });
  });
});
<|endoftext|>


<commit_before>'use strict';

var format = require('util').format;

var platform = require('thali/NextGeneration/utils/platform');
var logger = require('../lib/testLogger')('thaliMobileNativeTestUtils');
var randomString = require('randomstring');
var Promise = require('lie');
var makeIntoCloseAllServer =
  require('thali/NextGeneration/makeIntoCloseAllServer');
var Promise = require('bluebird');
var net = require('net');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var testUtils = require('../lib/testUtils.js');

var ThaliMobileNativeWrapper =
  require('thali/NextGeneration/thaliMobileNativeWrapper.js');

/**
 * @readonly
 * @type {{NOT_CONNECTED: string, CONNECTING: string, CONNECTED: string}}
 */
var connectStatus = {
  NOT_CONNECTED : 'notConnected',
  CONNECTING : 'connecting',
  CONNECTED : 'connected'
};

function startAndListen(t, server, peerAvailabilityChangedHandler) {
  server.listen(0, function () {

    var applicationPort = server.address().port;

    Mobile('peerAvailabilityChanged')
      .registerToNative(peerAvailabilityChangedHandler);

    Mobile('startUpdateAdvertisingAndListening').callNative(applicationPort,
      function (err) {
        t.notOk(err,
          'Can call startUpdateAdvertisingAndListening without error');
        Mobile('startListeningForAdvertisements').callNative(function (err) {
          t.notOk(err,
            'Can call startListeningForAdvertisements without error');
        });
      });
  });
}

module.exports.startAndListen = startAndListen;

function getMessageByLength(socket, lengthOfMessage) {
  return new Promise(function (resolve, reject) {
    var readData = new Buffer(0);
    var dataHandlerFunc = function (data) {
      readData = Buffer.concat([readData, data]);
      if (readData.length >= lengthOfMessage) {
        socket.removeListener('data', dataHandlerFunc);
        if (readData.length === lengthOfMessage) {
          resolve(readData);
        } else {
          reject(new Error('data is too long - ' + readData.length + ', ' +
            'expected - ' + lengthOfMessage));
        }
      }
    };
    socket.on('data', dataHandlerFunc);
  });
}

module.exports.getMessageByLength = getMessageByLength;

var RETRY_COUNT   = 10;
var RETRY_TIMEOUT = 3000;

function connectToPeer(peer, quitSignal) {
  var connectMethod;
  if (platform.isAndroid) {
    connectMethod = androidConnectToPeer;
  } else if (platform.isIOS) {
    connectMethod = iOSConnectToPeer;
  } else {
    return Promise.reject(new Error('unknown platform'));
  }

  return new Promise(function (resolve, reject) {
    var retryIndex = 0;

    function retry () {
      if (quitSignal && quitSignal.raised) {
        // 'connection' will be null.
        return resolve(null);
      }

      // It will return 'connection'.
      connectMethod(peer, quitSignal)
        .then(resolve)
        .catch(function (error) {
          if (error.isFatal) {
            logger.error('Fatal connect error: \'%s\'', error.message);
            return reject(error);
          }
          logger.warn('Connect error: \'%s\'', error.message);

          if (retryIndex >= RETRY_COUNT) {
            error = 'Too many connect retries';
            logger.error(error);
            return reject(new Error(error));
          }

          retryIndex ++;
          logger.info('New connect retry, number: \'%d\'', retryIndex);
          setTimeout(retry, RETRY_TIMEOUT);
        });
    }
    retry();
  });
}
module.exports.connectToPeer = connectToPeer;

function androidConnectToPeer(peer, quitSignal) {
  return new Promise(function (resolve, reject) {
    Mobile('connect')
      .callNative(peer.peerIdentifier, function (error, connection) {
        if (quitSignal && quitSignal.raised) {
          return resolve(null);
        }

        if (error) {
          error = new Error(error);
          if (error.message === 'Already connect(ing/ed)') {
            error.isFatal = true;
          }
          return reject(error);
        }

        if (!connection) {
          return reject(new Error('connect returned no connection'));
        }
        connection = JSON.parse(connection);
        resolve(connection);
      });
  });
}

function createMultiConnectEmitter() {
  var emitter = new EventEmitter();
  ThaliMobileNativeWrapper.emitter.on('_multiConnectResolved',
    function (syncValue, error, listeningPort) {
      emitter.emit('multiConnectResolved', syncValue, error, listeningPort);
    }
  );
  ThaliMobileNativeWrapper.emitter.on('_multiConnectConnectionFailure',
    function (peerIdentifier, error) {
      emitter.emit('multiConnectConnectionFailure', peerIdentifier, error);
    }
  );
  return emitter;
}

var multiConnectEmitter = createMultiConnectEmitter();
module.exports.multiConnectEmitter = multiConnectEmitter;

function iOSConnectToPeer(peer, quitSignal) {
  var originalSyncValue = randomString.generate();
  var multiConnectHandler;

  return new Promise(function (resolve, reject) {
    multiConnectHandler = function (syncValue, error, listeningPort) {
      logger.debug(
        'Got multiConnectResolved -' +
        'syncValue: \'%s\', error: \'%s\', listeningPort: \'%s\'',
        syncValue, error, listeningPort
      );

      if (quitSignal && quitSignal.raised) {
        return resolve(null);
      }

      if (error) {
        // Connection to a peer failed.
        // Return a fatal error to avoid retrying connecting to a peer that is not available anymore
        error = new Error(error);
        error.isFatal = true;
        return reject(error);
      }
      if (syncValue !== originalSyncValue) {
        logger.warn(
          'multiConnectResolved received invalid ' +
          'syncValue: \'%s\', originalSyncValue: \'%s\'',
          syncValue, originalSyncValue
        );
        return;
      }

      var port = parseInt(listeningPort, 10);
      if (isNaN(port) || port != listeningPort) {
        return reject(new Error(format(
          'listeningPort is not a valid number: \'%s\'', listeningPort
        )));
      }
      resolve({
        listeningPort: port
      });
    };

    multiConnectEmitter.on('multiConnectResolved', multiConnectHandler);
    logger.debug(format(
      'Issuing multiConnect for %s (syncValue: %s)',
      peer.peerIdentifier, originalSyncValue
    ));
    Mobile('multiConnect')
      .callNative(peer.peerIdentifier, originalSyncValue, function (error) {
        logger.debug('Got \'multiConnect\' callback');

        if (error) {
          error = new Error(format(
            'We got an error synchronously from multiConnect, ' +
            'that really shouldn\'t happen: \'%s\'', error
          ));
          error.isFatal = true;
          return reject(error);
        }
      });
  })
    .finally(function () {
      multiConnectEmitter.removeListener('multiConnectResolved', multiConnectHandler);
    });
}

/**
 * This function will grab the first peer it can via nonTCPAvailableHandler and
 * will try to issue the GET request. If it fails then it will continue to
 * listen to nonTCPAvailableHandler until it sees a port for the same PeerID
 * and will try the GET again. It will repeat this process if there are
 * failures until the timer runs out.
 *
 * @param {string} path
 * @param {string} pskIdentity
 * @param {Buffer} pskKey
 * @param {?string} [selectedPeerId] This is only used for a single test that
 * needs to reconnect to a known peer, otherwise it is null.
 * @returns {Promise<Error | peerAndBody>}
 */
function getSamePeerWithRetry(path, pskIdentity, pskKey,
                                                selectedPeerId) {
  // We don't load thaliMobileNativeWrapper until after the tests have started
  // running so we pick up the right version of mobile
  var thaliMobileNativeWrapper =
    require('thali/NextGeneration/thaliMobileNativeWrapper');
  return new Promise(function (resolve, reject) {
    var retryCount = 0;
    var MAX_TIME_TO_WAIT_IN_MILLISECONDS = 1000 * 60;
    var exitCalled = false;
    var peerID = selectedPeerId;
    var getRequestPromise = null;
    var cancelGetPortTimeout = null;
    var status = connectStatus.NOT_CONNECTED;
    var availablePeers = [];

    var timeoutId = setTimeout(function () {
      exitCall(null, new Error('Timer expired'));
    }, MAX_TIME_TO_WAIT_IN_MILLISECONDS);

    function removeFromAvailablePeers(peer) {
      var i;

      for (i = availablePeers.length - 1; i >= 0; i--) {
        if (availablePeers[i].peerIdentifier === peer.peerIdentifier) {
          availablePeers.splice(i, 1);
        }
      }
    }

    function exitCall(success, failure) {
      if (exitCalled) {
        return;
      }
      exitCalled = true;
      clearTimeout(timeoutId);
      clearTimeout(cancelGetPortTimeout);
      thaliMobileNativeWrapper.emitter
        .removeListener('nonTCPPeerAvailabilityChangedEvent',
          nonTCPAvailableHandler);
      return failure ? reject(failure) : resolve(
        {
          httpResponseBody: success,
          peerId: peerID
        });
    }

    function tryAgain(portNumber) {
      ++retryCount;
      logger.warn('Retry count for getSamePeerWithRetry is ' + retryCount);
      getRequestPromise = testUtils.get('127.0.0.1',
        portNumber, path, pskIdentity, pskKey);
      getRequestPromise
        .then(function (result) {
          exitCall(result);
        })
        .catch(function (err) {
          logger.debug('getSamePeerWithRetry got an error it will retry - ' +
            err);
        });
    }

    function callTryAgain(portNumber) {
      // We have a predefined peerID
      if (!getRequestPromise) {
        return tryAgain(portNumber);
      }

      getRequestPromise
        .then(function () {
          // In theory this could maybe happen if a connection somehow got
          // cut before we were notified of a successful result thus causing
          // the system to automatically issue a new port, but that is
          // unlikely
        })
        .catch(function (err) {
          return tryAgain(portNumber, err);
        });
    }

    // In this case this method is only used when we run on iOS.
    function tryToConnect() {
      availablePeers.forEach(function (peer) {
        if (peer.peerAvailable && status === connectStatus.NOT_CONNECTED) {
          status = connectStatus.CONNECTING;

          connectToPeer(peer)
            .then(function (connection) {
              // When we establish multi connect connections, run test.
              peerID = peer.peerIdentifier;
              status = connectStatus.CONNECTED;
              callTryAgain(connection.listeningPort);
            })
            .catch(function (error) {
              status = connectStatus.NOT_CONNECTED;

              removeFromAvailablePeers(peer);
              tryToConnect();
            });
        }
      });
    }

    function nonTCPAvailableHandler(peer) {
      if (!peer.peerAvailable) {
        removeFromAvailablePeers(peer);
        return;
      }

      availablePeers.push(peer);

      logger.debug('We got a peer ' + JSON.stringify(peer));

      if (status === connectStatus.NOT_CONNECTED && availablePeers.length > 0) {
        if (!platform.isAndroid) {
          tryToConnect();
        } else {
          callTryAgain(peer.portNumber);
        }
      }
    }

    thaliMobileNativeWrapper.emitter.on('nonTCPPeerAvailabilityChangedEvent', nonTCPAvailableHandler);
  });
}

module.exports.getSamePeerWithRetry = getSamePeerWithRetry;

/**
 * This function is responsible for execution of test function requiring connection to the other peer.
 * It assures that the connection is not established with the zombie advertiser. In case of the connection error
 * it checks if there are other peers available beside the one already tried.
 * TODO: We could collect available peers and after that fire test function for every one to avoid
 * not coordinated behaviour like one peer will finish before another
 * @param {object} t test object.
 * @param {net.Server} server Server object
 * @param {function} testFunction
 */
function executeZombieProofTest (t, server, testFunction) {
  var availablePeers = [];
  var status = connectStatus.NOT_CONNECTED;

  function removeFromAvailablePeers(peer) {
    var i;

    for (i = availablePeers.length - 1; i >= 0; i--) {
      if (availablePeers[i].peerIdentifier === peer.peerIdentifier) {
        availablePeers.splice(i, 1);
      }
    }
  }

  function tryToConnect() {
    availablePeers.forEach(function (peer) {
      if (peer.peerAvailable && status === connectStatus.NOT_CONNECTED) {
        status = connectStatus.CONNECTING;

        connectToPeer(peer)
          .then(function (connection) {
            status = connectStatus.CONNECTED;
            testFunction(connection, peer);
          })
          .catch(function (error) {
            status = connectStatus.NOT_CONNECTED;
            removeFromAvailablePeers(peer);
            tryToConnect();
          });
      }
    });
  }

  // The peer we got here is in fact an one element array, so we
  // have to treat it like array.
  function peerAvailabilityChangedHandler(peerAsArray) {
    var peer = peerAsArray[0];

    if (!peer.peerAvailable) {
      removeFromAvailablePeers(peer);
      return;
    }

    availablePeers.push(peer);
    logger.debug('We got a peer ' + JSON.stringify(peer));

    tryToConnect();
  }

  startAndListen(t, server, peerAvailabilityChangedHandler);
}

module.exports.executeZombieProofTest = executeZombieProofTest;

function getConnectionToOnePeerAndTest(t, connectTest) {
  var echoServer = net.createServer(function (socket) {
    socket.pipe(socket);
  });
  echoServer = makeIntoCloseAllServer(echoServer);
  var runningTest = false;
  var currentTestPeer = null;
  var failedPeers = 0;
  var maxFailedPeers = 5;

  function tryToConnect() {
    runningTest = true;
    connectToPeer(currentTestPeer)
      .then(function (connectionCallback) {
        connectTest(connectionCallback.listeningPort, currentTestPeer);
      })
      .catch(function () {
        ++failedPeers;
        if (failedPeers >= maxFailedPeers) {
          t.fail('Could not get connection to anyone');
          t.end();
        } else {
          runningTest = false;
        }
      });
  }

  startAndListen(t, echoServer, function (peers) {
    if (runningTest) {
      return;
    }
    for (var i = 0; i < peers.length; ++i) {
      currentTestPeer = peers[i];
      if (!currentTestPeer.peerAvailable) {
        continue;
      }
      tryToConnect();
      break;
    }
  });

  return echoServer;
}

module.exports.getConnectionToOnePeerAndTest = getConnectionToOnePeerAndTest;
<commit_msg>Fix dekstop tests<commit_after>'use strict';

var format = require('util').format;

var platform = require('thali/NextGeneration/utils/platform');
var logger = require('../lib/testLogger')('thaliMobileNativeTestUtils');
var randomString = require('randomstring');
var Promise = require('lie');
var makeIntoCloseAllServer =
  require('thali/NextGeneration/makeIntoCloseAllServer');
var Promise = require('bluebird');
var net = require('net');
var inherits = require('util').inherits;
var EventEmitter = require('events').EventEmitter;
var testUtils = require('../lib/testUtils.js');

var ThaliMobileNativeWrapper =
  require('thali/NextGeneration/thaliMobileNativeWrapper.js');

/**
 * @readonly
 * @type {{NOT_CONNECTED: string, CONNECTING: string, CONNECTED: string}}
 */
var connectStatus = {
  NOT_CONNECTED : 'notConnected',
  CONNECTING : 'connecting',
  CONNECTED : 'connected'
};

function startAndListen(t, server, peerAvailabilityChangedHandler) {
  server.listen(0, function () {

    var applicationPort = server.address().port;

    Mobile('peerAvailabilityChanged')
      .registerToNative(peerAvailabilityChangedHandler);

    Mobile('startUpdateAdvertisingAndListening').callNative(applicationPort,
      function (err) {
        t.notOk(err,
          'Can call startUpdateAdvertisingAndListening without error');
        Mobile('startListeningForAdvertisements').callNative(function (err) {
          t.notOk(err,
            'Can call startListeningForAdvertisements without error');
        });
      });
  });
}

module.exports.startAndListen = startAndListen;

function getMessageByLength(socket, lengthOfMessage) {
  return new Promise(function (resolve, reject) {
    var readData = new Buffer(0);
    var dataHandlerFunc = function (data) {
      readData = Buffer.concat([readData, data]);
      if (readData.length >= lengthOfMessage) {
        socket.removeListener('data', dataHandlerFunc);
        if (readData.length === lengthOfMessage) {
          resolve(readData);
        } else {
          reject(new Error('data is too long - ' + readData.length + ', ' +
            'expected - ' + lengthOfMessage));
        }
      }
    };
    socket.on('data', dataHandlerFunc);
  });
}

module.exports.getMessageByLength = getMessageByLength;

var RETRY_COUNT   = 10;
var RETRY_TIMEOUT = 3000;

function connectToPeer(peer, quitSignal) {
  var connectMethod;
  if (platform.isAndroid) {
    connectMethod = androidConnectToPeer;
  } else if (platform.isIOS) {
    connectMethod = iOSConnectToPeer;
  } else {
    return Promise.reject(new Error('unknown platform'));
  }

  return new Promise(function (resolve, reject) {
    var retryIndex = 0;

    function retry () {
      if (quitSignal && quitSignal.raised) {
        // 'connection' will be null.
        return resolve(null);
      }

      // It will return 'connection'.
      connectMethod(peer, quitSignal)
        .then(resolve)
        .catch(function (error) {
          if (error.isFatal) {
            logger.error('Fatal connect error: \'%s\'', error.message);
            return reject(error);
          }
          logger.warn('Connect error: \'%s\'', error.message);

          if (retryIndex >= RETRY_COUNT) {
            error = 'Too many connect retries';
            logger.error(error);
            return reject(new Error(error));
          }

          retryIndex ++;
          logger.info('New connect retry, number: \'%d\'', retryIndex);
          setTimeout(retry, RETRY_TIMEOUT);
        });
    }
    retry();
  });
}
module.exports.connectToPeer = connectToPeer;

function androidConnectToPeer(peer, quitSignal) {
  return new Promise(function (resolve, reject) {
    Mobile('connect')
      .callNative(peer.peerIdentifier, function (error, connection) {
        if (quitSignal && quitSignal.raised) {
          return resolve(null);
        }

        if (error) {
          error = new Error(error);
          if (error.message === 'Already connect(ing/ed)') {
            error.isFatal = true;
          }
          return reject(error);
        }

        if (!connection) {
          return reject(new Error('connect returned no connection'));
        }
        connection = JSON.parse(connection);
        resolve(connection);
      });
  });
}

function createMultiConnectEmitter() {
  var emitter = new EventEmitter();
  ThaliMobileNativeWrapper.emitter.on('_multiConnectResolved',
    function (syncValue, error, listeningPort) {
      emitter.emit('multiConnectResolved', syncValue, error, listeningPort);
    }
  );
  ThaliMobileNativeWrapper.emitter.on('_multiConnectConnectionFailure',
    function (peerIdentifier, error) {
      emitter.emit('multiConnectConnectionFailure', peerIdentifier, error);
    }
  );
  return emitter;
}

var multiConnectEmitter = createMultiConnectEmitter();
module.exports.multiConnectEmitter = multiConnectEmitter;

function iOSConnectToPeer(peer, quitSignal) {
  var originalSyncValue = randomString.generate();
  var multiConnectHandler;

  return new Promise(function (resolve, reject) {
    multiConnectHandler = function (syncValue, error, listeningPort) {
      logger.debug(
        'Got multiConnectResolved -' +
        'syncValue: \'%s\', error: \'%s\', listeningPort: \'%s\'',
        syncValue, error, listeningPort
      );

      if (quitSignal && quitSignal.raised) {
        return resolve(null);
      }

      if (error) {
        // Connection to a peer failed.
        // Return a fatal error to avoid retrying connecting to a peer that is not available anymore
        error = new Error(error);
        error.isFatal = true;
        return reject(error);
      }
      if (syncValue !== originalSyncValue) {
        logger.warn(
          'multiConnectResolved received invalid ' +
          'syncValue: \'%s\', originalSyncValue: \'%s\'',
          syncValue, originalSyncValue
        );
        return;
      }

      var port = parseInt(listeningPort, 10);
      if (isNaN(port) || port != listeningPort) {
        return reject(new Error(format(
          'listeningPort is not a valid number: \'%s\'', listeningPort
        )));
      }
      resolve({
        listeningPort: port
      });
    };

    multiConnectEmitter.on('multiConnectResolved', multiConnectHandler);
    logger.debug(format(
      'Issuing multiConnect for %s (syncValue: %s)',
      peer.peerIdentifier, originalSyncValue
    ));
    Mobile('multiConnect')
      .callNative(peer.peerIdentifier, originalSyncValue, function (error) {
        logger.debug('Got \'multiConnect\' callback');

        if (error) {
          error = new Error(format(
            'We got an error synchronously from multiConnect, ' +
            'that really shouldn\'t happen: \'%s\'', error
          ));
          error.isFatal = true;
          return reject(error);
        }
      });
  })
    .finally(function () {
      multiConnectEmitter.removeListener('multiConnectResolved', multiConnectHandler);
    });
}

/**
 * This function will grab the first peer it can via nonTCPAvailableHandler and
 * will try to issue the GET request. If it fails then it will continue to
 * listen to nonTCPAvailableHandler until it sees a port for the same PeerID
 * and will try the GET again. It will repeat this process if there are
 * failures until the timer runs out.
 *
 * @param {string} path
 * @param {string} pskIdentity
 * @param {Buffer} pskKey
 * @param {?string} [selectedPeerId] This is only used for a single test that
 * needs to reconnect to a known peer, otherwise it is null.
 * @returns {Promise<Error | peerAndBody>}
 */
function getSamePeerWithRetry(path, pskIdentity, pskKey,
                                                selectedPeerId) {
  // We don't load thaliMobileNativeWrapper until after the tests have started
  // running so we pick up the right version of mobile
  var thaliMobileNativeWrapper =
    require('thali/NextGeneration/thaliMobileNativeWrapper');
  return new Promise(function (resolve, reject) {
    var retryCount = 0;
    var MAX_TIME_TO_WAIT_IN_MILLISECONDS = 1000 * 60;
    var exitCalled = false;
    var peerID = selectedPeerId;
    var getRequestPromise = null;
    var cancelGetPortTimeout = null;
    var status = connectStatus.NOT_CONNECTED;
    var availablePeers = [];

    var timeoutId = setTimeout(function () {
      exitCall(null, new Error('Timer expired'));
    }, MAX_TIME_TO_WAIT_IN_MILLISECONDS);

    function removeFromAvailablePeers(peer) {
      var i;

      for (i = availablePeers.length - 1; i >= 0; i--) {
        if (availablePeers[i].peerIdentifier === peer.peerIdentifier) {
          availablePeers.splice(i, 1);
        }
      }
    }

    function exitCall(success, failure) {
      if (exitCalled) {
        return;
      }
      exitCalled = true;
      clearTimeout(timeoutId);
      clearTimeout(cancelGetPortTimeout);
      thaliMobileNativeWrapper.emitter
        .removeListener('nonTCPPeerAvailabilityChangedEvent',
          nonTCPAvailableHandler);
      return failure ? reject(failure) : resolve(
        {
          httpResponseBody: success,
          peerId: peerID
        });
    }

    function tryAgain(portNumber) {
      ++retryCount;
      logger.warn('Retry count for getSamePeerWithRetry is ' + retryCount);
      getRequestPromise = testUtils.get('127.0.0.1',
        portNumber, path, pskIdentity, pskKey);
      getRequestPromise
        .then(function (result) {
          exitCall(result);
        })
        .catch(function (err) {
          logger.debug('getSamePeerWithRetry got an error it will retry - ' +
            err);
        });
    }

    function callTryAgain(portNumber) {
      // We have a predefined peerID
      if (!getRequestPromise) {
        return tryAgain(portNumber);
      }

      getRequestPromise
        .then(function () {
          // In theory this could maybe happen if a connection somehow got
          // cut before we were notified of a successful result thus causing
          // the system to automatically issue a new port, but that is
          // unlikely
        })
        .catch(function (err) {
          return tryAgain(portNumber, err);
        });
    }

    // In this case this method is only used when we run on iOS.
    function tryToConnect() {
      availablePeers.forEach(function (peer) {
        if (peer.peerAvailable && status === connectStatus.NOT_CONNECTED) {
          status = connectStatus.CONNECTING;

          connectToPeer(peer)
            .then(function (connection) {
              status = connectStatus.CONNECTED;
              // When running on iOS, change peerID to the one we connected with
              peerID = peer.peerIdentifier;
              // When we establish multi connect connections, run test.
              callTryAgain(connection.listeningPort);
            })
            .catch(function (error) {
              status = connectStatus.NOT_CONNECTED;

              removeFromAvailablePeers(peer);
              tryToConnect();
            });
        }
      });
    }

    function nonTCPAvailableHandler(peer) {
      if (!peer.peerAvailable) {
        removeFromAvailablePeers(peer);
        return;
      }

      availablePeers.push(peer);

      if (!peerID) {
        peerID = peer.peerIdentifier;
      }

      logger.debug('We got a peer ' + JSON.stringify(peer));

      if (status === connectStatus.NOT_CONNECTED && availablePeers.length > 0) {
        if (!platform.isAndroid) {
          tryToConnect();
        } else {
          callTryAgain(peer.portNumber);
        }
      }
    }

    thaliMobileNativeWrapper.emitter.on('nonTCPPeerAvailabilityChangedEvent', nonTCPAvailableHandler);
  });
}

module.exports.getSamePeerWithRetry = getSamePeerWithRetry;

/**
 * This function is responsible for execution of test function requiring connection to the other peer.
 * It assures that the connection is not established with the zombie advertiser. In case of the connection error
 * it checks if there are other peers available beside the one already tried.
 * TODO: We could collect available peers and after that fire test function for every one to avoid
 * not coordinated behaviour like one peer will finish before another
 * @param {object} t test object.
 * @param {net.Server} server Server object
 * @param {function} testFunction
 */
function executeZombieProofTest (t, server, testFunction) {
  var availablePeers = [];
  var status = connectStatus.NOT_CONNECTED;

  function removeFromAvailablePeers(peer) {
    var i;

    for (i = availablePeers.length - 1; i >= 0; i--) {
      if (availablePeers[i].peerIdentifier === peer.peerIdentifier) {
        availablePeers.splice(i, 1);
      }
    }
  }

  function tryToConnect() {
    availablePeers.forEach(function (peer) {
      if (peer.peerAvailable && status === connectStatus.NOT_CONNECTED) {
        status = connectStatus.CONNECTING;

        connectToPeer(peer)
          .then(function (connection) {
            status = connectStatus.CONNECTED;
            testFunction(connection, peer);
          })
          .catch(function (error) {
            status = connectStatus.NOT_CONNECTED;
            removeFromAvailablePeers(peer);
            tryToConnect();
          });
      }
    });
  }

  // The peer we got here is in fact an one element array, so we
  // have to treat it like array.
  function peerAvailabilityChangedHandler(peerAsArray) {
    var peer = peerAsArray[0];

    if (!peer.peerAvailable) {
      removeFromAvailablePeers(peer);
      return;
    }

    availablePeers.push(peer);
    logger.debug('We got a peer ' + JSON.stringify(peer));

    tryToConnect();
  }

  startAndListen(t, server, peerAvailabilityChangedHandler);
}

module.exports.executeZombieProofTest = executeZombieProofTest;

function getConnectionToOnePeerAndTest(t, connectTest) {
  var echoServer = net.createServer(function (socket) {
    socket.pipe(socket);
  });
  echoServer = makeIntoCloseAllServer(echoServer);
  var runningTest = false;
  var currentTestPeer = null;
  var failedPeers = 0;
  var maxFailedPeers = 5;

  function tryToConnect() {
    runningTest = true;
    connectToPeer(currentTestPeer)
      .then(function (connectionCallback) {
        connectTest(connectionCallback.listeningPort, currentTestPeer);
      })
      .catch(function () {
        ++failedPeers;
        if (failedPeers >= maxFailedPeers) {
          t.fail('Could not get connection to anyone');
          t.end();
        } else {
          runningTest = false;
        }
      });
  }

  startAndListen(t, echoServer, function (peers) {
    if (runningTest) {
      return;
    }
    for (var i = 0; i < peers.length; ++i) {
      currentTestPeer = peers[i];
      if (!currentTestPeer.peerAvailable) {
        continue;
      }
      tryToConnect();
      break;
    }
  });

  return echoServer;
}

module.exports.getConnectionToOnePeerAndTest = getConnectionToOnePeerAndTest;
<|endoftext|>


<commit_before>CppRandomNames =
{
    one: ["foo","bar","baz","fiddle","faddle","bim","bam","quux","snork","snap"],
    two: ["squish","squash","smoot","spiffle","splin","squal","spork","smop","smick","smock"],
    three: ["blarp","squeeble","blurgle","podiddle","tulopulop","porskidor","swamwam"],
    four: ["MOOP","MooP","mOOp","mooP","Moop","moop","minx","mox","mole","moof","moog"]
};
CppRandomTypes = [ "int", "double", "bool", "void" ];
CppBoolComparisons = [ "==", "!=", ">", "<", ">=", "<=" ];
RandomReturnTypes = ["int", "float", "double", "string"];

function getRandomReturnType(randomStream)
{
    return RandomReturnTypes[randomStream.nextIntRange(4)];
}

function cppGenerateRandomValue(randomStream, num)
{
    switch(num)
    {
        case 0:
            return randomStream.nextIntRange(100);
        case 1:
            return randomStream.nextIntRange(99) + "." + randomStream.nextIntRange(99);
        default:
            return ["true", "false"][randomStream.nextIntRange(2)];
    }
}

function cppGetRandomId(randomStream, num)
{
    var id;

    switch(num){
        case 0:
            id = CppRandomNames.one[randomStream.nextIntRange(CppRandomNames.one.length)];
            break;
        case 1:
            id = CppRandomNames.two[randomStream.nextIntRange(CppRandomNames.two.length)];
            break;
        case 2:
            id = CppRandomNames.three[randomStream.nextIntRange(CppRandomNames.three.length)];
            break;
        case 3:
            id = CppRandomNames.four[randomStream.nextIntRange(CppRandomNames.four.length)];
            break;
        default:
            break;
    }

    return id;
}
<commit_msg>Refactored cpp utilities random names to be mo betta.<commit_after>CppRandomNames =
[
    ["foo","bar","baz","fiddle","faddle","bim","bam","quux","snork","snap"],
    ["squish","squash","smoot","spiffle","splin","squal","spork","smop","smick","smock"],
    ["blarp","squeeble","blurgle","podiddle","tulopulop","porskidor","swamwam"],
    ["MOOP","MooP","mOOp","mooP","Moop","moop","minx","mox","mole","moof","moog"]
];
CppRandomTypes = [ "int", "double", "bool", "void" ];
CppBoolComparisons = [ "==", "!=", ">", "<", ">=", "<=" ];
RandomReturnTypes = ["int", "float", "double", "string"];

function getRandomReturnType(randomStream)
{
    return RandomReturnTypes[randomStream.nextIntRange(4)];
}

function cppGenerateRandomValue(randomStream, num)  // 0: integer, 1: double, 2: boolean
{
    switch(num)
    {
        case 0:
            return randomStream.nextIntRange(100);
        case 1:
            return randomStream.nextIntRange(99) + "." + randomStream.nextIntRange(99);
        default:
            return ["true", "false"][randomStream.nextIntRange(2)];
    }
}

function cppGetRandomId(randomStream, num)
{
/*    var id;

    switch(num){
        case 0:
            id = CppRandomNames.one[randomStream.nextIntRange(CppRandomNames.one.length)];
            break;
        case 1:
            id = CppRandomNames.two[randomStream.nextIntRange(CppRandomNames.two.length)];
            break;
        case 2:
            id = CppRandomNames.three[randomStream.nextIntRange(CppRandomNames.three.length)];
            break;
        case 3:
            id = CppRandomNames.four[randomStream.nextIntRange(CppRandomNames.four.length)];
            break;
        default:
            break;
    }
*/
    return CppRandomNames[num][randomStream.nextIntRange(CppRandomNames[num].length)];
}
<|endoftext|>


<commit_before>import React, {
  View,
  Platform,
  Navigator,
  BackAndroid,
  TouchableOpacity,
  Text,
} from 'react-native'

import Drawer from 'react-native-drawer'

import Settings from '../settings'

// Components
import Header from '../components/Header'
import Loading from '../components/Loading'

// Styles
import Styles from '../styles/Styles'

// Model
import Store from '../data/Store'

// Parse
import Parse from 'parse/react-native'
import {connectToParseServer} from '../api/ParseServer'
import {isAuthenticated, register, logout} from '../api/Account'

// Views
import LoginPage from '../views/LoginPage'
import SurveyListPage from '../views/SurveyListPage'
import TermsOfServicePage from '../views/TermsOfServicePage'
import SurveyDetailsPage from '../views/SurveyDetailsPage'
import RegistrationPages from '../views/RegistrationPages'
import FormPage from '../views/FormPage'
import ControlPanel from '../views/ControlPanel'
import ProfilePage from '../views/ProfilePage'

/* Configuration */
if (Platform.OS === 'ios') {
  Store.platform = 'ios'
} else {
  Store.platform = 'android'
}

let navigator;
// Binds the hardware "back button" from Android devices
if ( Platform.OS === 'android' ) {
  BackAndroid.addEventListener('hardwareBackPress', () => {
    if (navigator && navigator.getCurrentRoutes().length > 1) {
      navigator.pop();
      return true;
    }
    return false;
  });
}

const SharedNavigator = React.createClass ({
  getInitialState() {
    return {
      title: '',
      isLoading: true,
      isAuthenticated: false,
      drawerOpen: false,
    }
  },
  componentWillMount() {
    connectToParseServer(Settings.parse.serverUrl, Settings.parse.appId);
  },
  componentDidMount() {
    isAuthenticated((authenticated) => {
      this.setState({
        isAuthenticated: authenticated,
        isLoading: false,
      });
    });
  },

  /* Methods */
  setAuthenticated(authenticated) {
    this.setState({
      isAuthenticated: authenticated
    }, function() {
      navigator.resetTo({});
    });
  },

  logoutHandler() {
    logout();
    this.setState({
      isAuthenticated: false,
    }, function() {
      navigator.resetTo({path:'login',title:''});
    });
  },

  routeMapper(route, nav) {
    const sharedProps = {
      navigator: nav,
    };

    if (!this.state.isAuthenticated && !route.unsecured) {
      route.path = 'login'
      route.title = ''
    }

    switch (route.path) {
      case 'login': return <LoginPage {...sharedProps} setAuthenticated={this.setAuthenticated} />
      case 'surveylist': return <SurveyListPage {...sharedProps} />
      case 'terms': return <TermsOfServicePage {...sharedProps} />
      case 'registration': return <RegistrationPages {...sharedProps} index={route.index} />
      case 'profile': return <ProfilePage {...sharedProps} />
      case 'form': return <FormPage {...sharedProps} form={route.form} survey={route.survey} />
      case 'survey-details': return <SurveyDetailsPage {...sharedProps} survey={route.survey} />
      default: return <SurveyListPage {...sharedProps} />
    }
  },

  /* Render */
  render() {
    const initialRoute = { path:'surveylist', title: 'Surveys' }
    // show loading component without the navigationBar
    if (this.state.isLoading) {
      return (<Loading/>);
    }
    // show the navigator
    if (this.state.isAuthenticated) {
      return (
        <Drawer
          type="overlay"
          content={<ControlPanel
            navigator={navigator}
            logout={this.logoutHandler}
            closeDrawer={()=>this.setState({drawerOpen: false})} />}
          tapToClose={true}
          openDrawerOffset={0.2} // 20% gap on the right side of drawer
          panCloseMask={0.2}
          closedDrawerOffset={-3}
          styles={Styles.drawer}
          open={this.state.drawerOpen}
          onClose={()=>this.setState({drawerOpen: false})}
          tweenHandler={(ratio) => ({
            main: { opacity:(2-ratio)/2 }
          })}
          >
          <Navigator
            ref={(nav) => { navigator = nav }}
            initialRoute={initialRoute}
            renderScene={this.routeMapper}
            configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromRight}
            style={Styles.container.wrapper}
            navigationBar={
              <Header
                title={this.state.title}
                openDrawer={()=>this.setState({drawerOpen: true})} />
            }
          />
        </Drawer>
      );
    }

    return(
      <Navigator
        ref={(nav) => { navigator = nav }}
        initialRoute={initialRoute}
        renderScene={this.routeMapper}
        configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromRight}
        style={Styles.container.wrapper}
        navigationBar={
          <Header
            title={this.state.title} />
        }
      />
    );
  }
})

module.exports = SharedNavigator
<commit_msg>share logout amongst our views<commit_after>import React, {
  View,
  Platform,
  Navigator,
  BackAndroid,
  TouchableOpacity,
  Text,
} from 'react-native'

import Drawer from 'react-native-drawer'

import Settings from '../settings'

// Components
import Header from '../components/Header'
import Loading from '../components/Loading'

// Styles
import Styles from '../styles/Styles'

// Model
import Store from '../data/Store'

// Parse
import Parse from 'parse/react-native'
import {connectToParseServer} from '../api/ParseServer'
import {isAuthenticated, register, logout} from '../api/Account'

// Views
import LoginPage from '../views/LoginPage'
import SurveyListPage from '../views/SurveyListPage'
import TermsOfServicePage from '../views/TermsOfServicePage'
import SurveyDetailsPage from '../views/SurveyDetailsPage'
import RegistrationPages from '../views/RegistrationPages'
import FormPage from '../views/FormPage'
import ControlPanel from '../views/ControlPanel'
import ProfilePage from '../views/ProfilePage'

/* Configuration */
if (Platform.OS === 'ios') {
  Store.platform = 'ios'
} else {
  Store.platform = 'android'
}

let navigator;
// Binds the hardware "back button" from Android devices
if ( Platform.OS === 'android' ) {
  BackAndroid.addEventListener('hardwareBackPress', () => {
    if (navigator && navigator.getCurrentRoutes().length > 1) {
      navigator.pop();
      return true;
    }
    return false;
  });
}

const SharedNavigator = React.createClass ({
  getInitialState() {
    return {
      title: '',
      isLoading: true,
      isAuthenticated: false,
      drawerOpen: false,
    }
  },
  componentWillMount() {
    connectToParseServer(Settings.parse.serverUrl, Settings.parse.appId);
  },
  componentDidMount() {
    isAuthenticated((authenticated) => {
      this.setState({
        isAuthenticated: authenticated,
        isLoading: false,
      });
    });
  },

  /* Methods */
  setAuthenticated(authenticated) {
    this.setState({
      isAuthenticated: authenticated
    }, function() {
      navigator.resetTo({});
    });
  },

  logoutHandler() {
    logout();
    this.setState({
      isAuthenticated: false,
    }, function() {
      navigator.resetTo({path:'login',title:''});
    });
  },

  routeMapper(route, nav) {
    const sharedProps = {
      navigator: nav,
      logout: this.logoutHandler,
    };

    if (!this.state.isAuthenticated && !route.unsecured) {
      route.path = 'login'
      route.title = ''
    }

    switch (route.path) {
      case 'login': return <LoginPage {...sharedProps} setAuthenticated={this.setAuthenticated} />
      case 'surveylist': return <SurveyListPage {...sharedProps} />
      case 'terms': return <TermsOfServicePage {...sharedProps} />
      case 'registration': return <RegistrationPages {...sharedProps} index={route.index} />
      case 'profile': return <ProfilePage {...sharedProps} />
      case 'form': return <FormPage {...sharedProps} form={route.form} survey={route.survey} />
      case 'survey-details': return <SurveyDetailsPage {...sharedProps} survey={route.survey} />
      default: return <SurveyListPage {...sharedProps} />
    }
  },

  /* Render */
  render() {
    const initialRoute = { path:'surveylist', title: 'Surveys' }
    // show loading component without the navigationBar
    if (this.state.isLoading) {
      return (<Loading/>);
    }
    // show the navigator
    if (this.state.isAuthenticated) {
      return (
        <Drawer
          type="overlay"
          content={<ControlPanel
            navigator={navigator}
            logout={this.logoutHandler}
            closeDrawer={()=>this.setState({drawerOpen: false})} />}
          tapToClose={true}
          openDrawerOffset={0.2} // 20% gap on the right side of drawer
          panCloseMask={0.2}
          closedDrawerOffset={-3}
          styles={Styles.drawer}
          open={this.state.drawerOpen}
          onClose={()=>this.setState({drawerOpen: false})}
          tweenHandler={(ratio) => ({
            main: { opacity:(2-ratio)/2 }
          })}
          >
          <Navigator
            ref={(nav) => { navigator = nav }}
            initialRoute={initialRoute}
            renderScene={this.routeMapper}
            configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromRight}
            style={Styles.container.wrapper}
            navigationBar={
              <Header
                title={this.state.title}
                openDrawer={()=>this.setState({drawerOpen: true})} />
            }
          />
        </Drawer>
      );
    }

    return(
      <Navigator
        ref={(nav) => { navigator = nav }}
        initialRoute={initialRoute}
        renderScene={this.routeMapper}
        configureScene={(route, routeStack) => Navigator.SceneConfigs.FloatFromRight}
        style={Styles.container.wrapper}
        navigationBar={
          <Header
            title={this.state.title} />
        }
      />
    );
  }
})

module.exports = SharedNavigator
<|endoftext|>


<commit_before>'use strict';

angular.module('notifications', []).
  factory('$notification', ['$timeout',function($timeout){

    console.log('notification service online');
    var notifications = JSON.parse(localStorage.getItem('$notifications')) || [],
        queue = [];

    var settings = {
      info: { duration: 5000, enabled: true },
      warning: { duration: 5000, enabled: true },
      error: { duration: 5000, enabled: true },
      success: { duration: 5000, enabled: true },
      progress: { duration: 0, enabled: true },
      custom: { duration: 35000, enabled: true },
      details: true,
      localStorage: false,
      html5Mode: false,
      html5DefaultIcon: '../img/satoshi.gif'
    };

    function html5Notify(icon, title, content, ondisplay, onclose){
      if(window.webkitNotifications.checkPermission() === 0){
        if(!icon){
          icon = '../img/favicon.ico';
        }
        var noti = window.webkitNotifications.createNotification(icon, title, content);
        if(typeof ondisplay === 'function'){
          noti.ondisplay = ondisplay;
        }
        if(typeof onclose === 'function'){
          noti.onclose = onclose;
        }
        noti.show();
      }
      else {
        settings.html5Mode = false;
      }
    }

    return {

      /* ========== SETTINGS RELATED METHODS =============*/

      disableHtml5Mode: function(){
        settings.html5Mode = false;
      },

      disableType: function(notificationType){
        settings[notificationType].enabled = false;
      },

      enableHtml5Mode: function(){
        // settings.html5Mode = true;
        settings.html5Mode = this.requestHtml5ModePermissions();
      },

      enableType: function(notificationType){
        settings[notificationType].enabled = true;
      },

      getSettings: function(){
        return settings;
      },

      toggleType: function(notificationType){
        settings[notificationType].enabled = !settings[notificationType].enabled;
      },

      toggleHtml5Mode: function(){
        settings.html5Mode = !settings.html5Mode;
      },

      requestHtml5ModePermissions: function(){
        if (window.webkitNotifications){
          console.log('notifications are available');
          if (window.webkitNotifications.checkPermission() === 0) {
            return true;
          }
          else{
            window.webkitNotifications.requestPermission(function(){
              if(window.webkitNotifications.checkPermission() === 0){
                settings.html5Mode = true;
              }
              else{
                settings.html5Mode = false;
              }
            });
            return false;
          }
        }
        else{
          console.log('notifications are not supported');
          return false;
        }
      },

      /* ============ QUERYING RELATED METHODS ============*/

      getAll: function(){
        // Returns all notifications that are currently stored
        return notifications;
      },

      getQueue: function(){
        return queue;
      },

      /* ============== NOTIFICATION METHODS ==============*/

      info: function(title, content, userData){
        console.log(title, content);
        return this.awesomeNotify('info','info', title, content, userData);
      },

      error: function(title, content, userData){
        return this.awesomeNotify('error', 'remove', title, content, userData);
      },

      success: function(title, content, userData){
        return this.awesomeNotify('success', 'ok', title, content, userData);
      },

      warning: function(title, content, userData){
        return this.awesomeNotify('warning', 'exclamation', title, content, userData);
      },

      awesomeNotify: function(type, icon, title, content, userData){
        /**
         * Supposed to wrap the makeNotification method for drawing icons using font-awesome
         * rather than an image.
         *
         * Need to find out how I'm going to make the API take either an image
         * resource, or a font-awesome icon and then display either of them.
         * Also should probably provide some bits of color, could do the coloring
         * through classes.
         */
        // image = '';
        return this.makeNotification(type, false, icon, title, content, userData);
      },

      notify: function(image, title, content, userData){
        // Wraps the makeNotification method for displaying notifications with images
        // rather than icons
        return this.makeNotification('custom', image, true, title, content, userData);
      },

      makeNotification: function(type, image, icon, title, content, userData){
        var notification = {
          'type': type,
          'image': image,
          'icon': icon,
          'title': title,
          'content': content,
          'timestamp': +new Date(),
          'userData': userData
        };
        notifications.push(notification);

        if(settings.html5Mode){
          html5Notify(image, title, content, function(){
            console.log("inner on display function");
          }, function(){
            console.log("inner on close function");
          });
        }
        else{
          queue.push(notification);
          $timeout(function removeFromQueueTimeout(){
            queue.splice(queue.indexOf(notification), 1);
          }, settings[type].duration);

        }

        this.save();
        return notification;
      },

      /* ============ PERSISTENCE METHODS ============ */

      save: function(){
        // Save all the notifications into localStorage
        // console.log(JSON);
        if(settings.localStorage){
          localStorage.setItem('$notifications', JSON.stringify(notifications));
        }
        // console.log(localStorage.getItem('$notifications'));
      },

      restore: function(){
        // Load all notifications from localStorage
      },

      clear: function(){
        notifications = [];
        this.save();
      }

    };
  }]).
  directive('notifications', ['$notification', '$compile', function($notification, $compile){
    /**
     *
     * It should also parse the arguments passed to it that specify
     * its position on the screen like "bottom right" and apply those
     * positions as a class to the container element
     *
     * Finally, the directive should have its own controller for
     * handling all of the notifications from the notification service
     */
//    console.log('this is a new directive');
    var html =
      '
' +
        '
' +
          '' +
        '
' +
        '
' +
          '
' +
            '' +
            '' +
          '
' +
          '
' +
            '
{{noti.title}}
' +
            '
{{noti.content}}
' +
          '
' +
        '
' +
      '
';

    function link(scope, element, attrs){
      var position = attrs.notifications;
      position = position.split(' ');
      element.addClass('dr-notification-container');
      for(var i = 0; i < position.length ; i++){
        element.addClass(position[i]);
      }
    }

    return {
      restrict: 'A',
      scope: {},
      template: html,
      link: link,
      controller: ['$scope', function NotificationsCtrl( $scope ){
        $scope.queue = $notification.getQueue();

        $scope.removeNotification = function(noti){
          $scope.queue.splice($scope.queue.indexOf(noti), 1);
        };
      }
    ]

    };
  }]);
<commit_msg>copay icon in notifications<commit_after>'use strict';

angular.module('notifications', []).
  factory('$notification', ['$timeout',function($timeout){

    console.log('notification service online');
    var notifications = JSON.parse(localStorage.getItem('$notifications')) || [],
        queue = [];

    var settings = {
      info: { duration: 5000, enabled: true },
      warning: { duration: 5000, enabled: true },
      error: { duration: 5000, enabled: true },
      success: { duration: 5000, enabled: true },
      progress: { duration: 0, enabled: true },
      custom: { duration: 35000, enabled: true },
      details: true,
      localStorage: false,
      html5Mode: false,
      html5DefaultIcon: 'img/favicon.ico'
    };

    function html5Notify(icon, title, content, ondisplay, onclose){
      if(window.webkitNotifications.checkPermission() === 0){
        if(!icon){
          icon = 'img/favicon.ico';
        }
        var noti = window.webkitNotifications.createNotification(icon, title, content);
        if(typeof ondisplay === 'function'){
          noti.ondisplay = ondisplay;
        }
        if(typeof onclose === 'function'){
          noti.onclose = onclose;
        }
        noti.show();
      }
      else {
        settings.html5Mode = false;
      }
    }

    return {

      /* ========== SETTINGS RELATED METHODS =============*/

      disableHtml5Mode: function(){
        settings.html5Mode = false;
      },

      disableType: function(notificationType){
        settings[notificationType].enabled = false;
      },

      enableHtml5Mode: function(){
        // settings.html5Mode = true;
        settings.html5Mode = this.requestHtml5ModePermissions();
      },

      enableType: function(notificationType){
        settings[notificationType].enabled = true;
      },

      getSettings: function(){
        return settings;
      },

      toggleType: function(notificationType){
        settings[notificationType].enabled = !settings[notificationType].enabled;
      },

      toggleHtml5Mode: function(){
        settings.html5Mode = !settings.html5Mode;
      },

      requestHtml5ModePermissions: function(){
        if (window.webkitNotifications){
          console.log('notifications are available');
          if (window.webkitNotifications.checkPermission() === 0) {
            return true;
          }
          else{
            window.webkitNotifications.requestPermission(function(){
              if(window.webkitNotifications.checkPermission() === 0){
                settings.html5Mode = true;
              }
              else{
                settings.html5Mode = false;
              }
            });
            return false;
          }
        }
        else{
          console.log('notifications are not supported');
          return false;
        }
      },

      /* ============ QUERYING RELATED METHODS ============*/

      getAll: function(){
        // Returns all notifications that are currently stored
        return notifications;
      },

      getQueue: function(){
        return queue;
      },

      /* ============== NOTIFICATION METHODS ==============*/

      info: function(title, content, userData){
        console.log(title, content);
        return this.awesomeNotify('info','loop', title, content, userData);
      },

      error: function(title, content, userData){
        return this.awesomeNotify('error', 'remove', title, content, userData);
      },

      success: function(title, content, userData){
        return this.awesomeNotify('success', 'ok', title, content, userData);
      },

      warning: function(title, content, userData){
        return this.awesomeNotify('warning', 'exclamation', title, content, userData);
      },

      awesomeNotify: function(type, icon, title, content, userData){
        /**
         * Supposed to wrap the makeNotification method for drawing icons using font-awesome
         * rather than an image.
         *
         * Need to find out how I'm going to make the API take either an image
         * resource, or a font-awesome icon and then display either of them.
         * Also should probably provide some bits of color, could do the coloring
         * through classes.
         */
        // image = '';
        return this.makeNotification(type, false, icon, title, content, userData);
      },

      notify: function(image, title, content, userData){
        // Wraps the makeNotification method for displaying notifications with images
        // rather than icons
        return this.makeNotification('custom', image, true, title, content, userData);
      },

      makeNotification: function(type, image, icon, title, content, userData){
        var notification = {
          'type': type,
          'image': image,
          'icon': icon,
          'title': title,
          'content': content,
          'timestamp': +new Date(),
          'userData': userData
        };
        notifications.push(notification);

        if(settings.html5Mode){
          html5Notify(image, title, content, function(){
            console.log("inner on display function");
          }, function(){
            console.log("inner on close function");
          });
        }
        else{
          queue.push(notification);
          $timeout(function removeFromQueueTimeout(){
            queue.splice(queue.indexOf(notification), 1);
          }, settings[type].duration);

        }

        this.save();
        return notification;
      },

      /* ============ PERSISTENCE METHODS ============ */

      save: function(){
        // Save all the notifications into localStorage
        // console.log(JSON);
        if(settings.localStorage){
          localStorage.setItem('$notifications', JSON.stringify(notifications));
        }
        // console.log(localStorage.getItem('$notifications'));
      },

      restore: function(){
        // Load all notifications from localStorage
      },

      clear: function(){
        notifications = [];
        this.save();
      }

    };
  }]).
  directive('notifications', ['$notification', '$compile', function($notification, $compile){
    /**
     *
     * It should also parse the arguments passed to it that specify
     * its position on the screen like "bottom right" and apply those
     * positions as a class to the container element
     *
     * Finally, the directive should have its own controller for
     * handling all of the notifications from the notification service
     */
//    console.log('this is a new directive');
    var html =
      '
' +
        '
' +
          '' +
        '
' +
        '
' +
          '
' +
            '' +
            '' +
          '
' +
          '
' +
            '
{{noti.title}}
' +
            '
{{noti.content}}
' +
          '
' +
        '
' +
      '
';

    function link(scope, element, attrs){
      var position = attrs.notifications;
      position = position.split(' ');
      element.addClass('dr-notification-container');
      for(var i = 0; i < position.length ; i++){
        element.addClass(position[i]);
      }
    }

    return {
      restrict: 'A',
      scope: {},
      template: html,
      link: link,
      controller: ['$scope', function NotificationsCtrl( $scope ){
        $scope.queue = $notification.getQueue();

        $scope.removeNotification = function(noti){
          $scope.queue.splice($scope.queue.indexOf(noti), 1);
        };
      }
    ]

    };
  }]);
<|endoftext|>


<commit_before>import $ from 'jquery';

export default class reloadView {
    constructor(element, options) {
        const $element = $(element);

        if(options.callback) {
            $.get(options.callback, (data) => {
                $element.replaceWith(data);
            });
        }
    }
}

reloadView.DEFAULTS = {
    callback: null,
};<commit_msg>Add js param change<commit_after>import $ from 'jquery';

export default class reloadView {
    constructor(element, options) {
        const $element = $(element);

        if(options.uri) {
            $.get(options.uri, options.uri_options, (data) => {
                $element.replaceWith(data);
            });
        }
    }
}

reloadView.DEFAULTS = {
    uri: null,
    uri_options: [],
};<|endoftext|>


<commit_before>var React = require('react/addons');
var cs = require('classnames');
var moment = require('moment-range');
var Cell = require('./Cell');
var ViewHeader = require('./ViewHeader');

module.exports = React.createClass({displayName: "exports",

    propTypes: {
        date: React.PropTypes.object.isRequired,
        minDate: React.PropTypes.any,
        maxDate: React.PropTypes.any,
        setDate: React.PropTypes.func,
        nextView: React.PropTypes.func
    },

    getDaysTitles: function () {
        return moment.weekdaysMin().map(function (item) {
            return {
                val: item,
                label: item
            };
        });
    },

    next: function() {
        var nextDate = this.props.date.clone().add(1, 'months');

        if (this.props.maxDate && nextDate.isAfter(this.props.maxDate)) {
            nextDate = this.props.maxDate;
        }

        this.props.setDate(nextDate);
    },

    prev: function() {
        var prevDate = this.props.date.clone().subtract(1, 'months');

        if (this.props.minDate && prevDate.isBefore(this.props.minDate)) {
            prevDate = this.props.minDate;
        }

        this.props.setDate(prevDate);
    },

    cellClick: function (e) {
        var cell = e.target,
            date = parseInt(cell.innerHTML, 10),
            newDate = this.props.date ? this.props.date.clone() : moment();

        if (isNaN(date)) {
            return;
        }

        if (cell.className.indexOf('prev') > -1 ) {
            newDate.subtract(1, 'months');
        } else if (cell.className.indexOf('next') > -1) {
            newDate.add(1, 'months');
        }

        newDate.date(date);
        this.props.setDate(newDate, true);
    },

    getDays: function () {
        var now = this.props.date ? this.props.date : moment(),
            start = now.clone().startOf('month').day(0),
            end = now.clone().endOf('month').day(6),
            minDate = this.props.minDate,
            maxDate = this.props.maxDate,
            month = now.month(),
            today = moment(),
            currDay = now.date(),
            year = now.year(),
            days = [];

        moment()
            .range(start, end)
            .by('days', function(day) {
                days.push({
                    label: day.format('D'),
                    prev: (day.month() < month && !(day.year() > year)) || day.year() < year ,
                    next: day.month() > month || day.year() > year,
                    disabled: day.isBefore(minDate) || day.isAfter(maxDate),
                    curr: day.date() === currDay && day.month() === month,
                    today: day.date() === today.date() && day.month() === today.month()
                });
            });

        return days;
    },

    render: function () {
        var titles = this.getDaysTitles().map(function (item, i) {
            return React.createElement(Cell, {value: item.label, classes: "day title", key: i})
        });

        var days = this.getDays().map(function (item, i) {
            var _class = cs({
                'day': true,
                'next': item.next,
                'prev': item.prev,
                'disabled': item.disabled,
                'current': item.curr,
                'today': item.today
            });
            return React.createElement(Cell, {value: item.label, classes: _class, key: i})
        });

        var currentDate = this.props.date ? this.props.date.format('MMMM') : moment().format('MMMM');

        return (
            React.createElement("div", {className: "view days-view", onKeyDown: this.keyDown},
                React.createElement(ViewHeader, {
                    prev: this.prev,
                    next: this.next,
                    data: currentDate,
                    titleAction: this.props.nextView}),

                React.createElement("div", {className: "days-title"}, titles),
                React.createElement("div", {className: "days", onClick: this.cellClick}, days)
            )
        );
    }

});
<commit_msg>german weekdays<commit_after>var React = require('react/addons');
var cs = require('classnames');
var moment = require('moment-range');
var Cell = require('./Cell');
var ViewHeader = require('./ViewHeader');

module.exports = React.createClass({displayName: "exports",

    propTypes: {
        date: React.PropTypes.object.isRequired,
        minDate: React.PropTypes.any,
        maxDate: React.PropTypes.any,
        setDate: React.PropTypes.func,
        nextView: React.PropTypes.func
    },

    getDaysTitles: function () {

        if(moment.locale() === 'de') {
          return 'Mo_Di_Mi_Do_Fr_Sa_So'.split('_').map(function (item) {
              return {
                  val: item,
                  label: item
              };
          });
        }

        return moment.weekdaysMin().map(function (item) {
            return {
                val: item,
                label: item
            };
        });
    },

    next: function() {
        var nextDate = this.props.date.clone().add(1, 'months');

        if (this.props.maxDate && nextDate.isAfter(this.props.maxDate)) {
            nextDate = this.props.maxDate;
        }

        this.props.setDate(nextDate);
    },

    prev: function() {
        var prevDate = this.props.date.clone().subtract(1, 'months');

        if (this.props.minDate && prevDate.isBefore(this.props.minDate)) {
            prevDate = this.props.minDate;
        }

        this.props.setDate(prevDate);
    },

    cellClick: function (e) {
        var cell = e.target,
            date = parseInt(cell.innerHTML, 10),
            newDate = this.props.date ? this.props.date.clone() : moment();

        if (isNaN(date)) {
            return;
        }

        if (cell.className.indexOf('prev') > -1 ) {
            newDate.subtract(1, 'months');
        } else if (cell.className.indexOf('next') > -1) {
            newDate.add(1, 'months');
        }

        newDate.date(date);
        this.props.setDate(newDate, true);
    },

    getDays: function () {
        var now = this.props.date ? this.props.date : moment(),
            start = now.clone().startOf('month').weekday(0),
            end = now.clone().endOf('month').weekday(6),
            minDate = this.props.minDate,
            maxDate = this.props.maxDate,
            month = now.month(),
            today = moment(),
            currDay = now.date(),
            year = now.year(),
            days = [];

        moment()
            .range(start, end)
            .by('days', function(day) {
                days.push({
                    label: day.format('D'),
                    prev: (day.month() < month && !(day.year() > year)) || day.year() < year ,
                    next: day.month() > month || day.year() > year,
                    disabled: day.isBefore(minDate) || day.isAfter(maxDate),
                    curr: day.date() === currDay && day.month() === month,
                    today: day.date() === today.date() && day.month() === today.month()
                });
            });

        return days;
    },

    render: function () {
        var titles = this.getDaysTitles().map(function (item, i) {
            return React.createElement(Cell, {value: item.label, classes: "day title", key: i})
        });

        var days = this.getDays().map(function (item, i) {
            var _class = cs({
                'day': true,
                'next': item.next,
                'prev': item.prev,
                'disabled': item.disabled,
                'current': item.curr,
                'today': item.today
            });
            return React.createElement(Cell, {value: item.label, classes: _class, key: i})
        });

        var currentDate = this.props.date ? this.props.date.format('MMMM') : moment().format('MMMM');

        return (
            React.createElement("div", {className: "view days-view", onKeyDown: this.keyDown},
                React.createElement(ViewHeader, {
                    prev: this.prev,
                    next: this.next,
                    data: currentDate,
                    titleAction: this.props.nextView}),

                React.createElement("div", {className: "days-title"}, titles),
                React.createElement("div", {className: "days", onClick: this.cellClick}, days)
            )
        );
    }

});
<|endoftext|>


<commit_before>var FB = function (url, idGame) {
    this.firebase = new Firebase(url);
    this.idGame = idGame;
};

FB.prototype.ref = function () {
    return this.firebase.child('/' + this.idGame + '/');
};

FB.prototype.newIdGame = function (size) {
    var ref = this.firebase.root();
    var key = ref.push().key();
    var def = $.Deferred();
    ref.child(key).set({size: size}, function(error) {
        if (error) {
            def.reject(error);
        } else {
            def.resolve(key);
        }
    });
    return def.promise();
};

FB.prototype.setStone = function (x, y, color) {
    return this.ref().child('board/' + x + "-" + y).set(color);
};

FB.prototype.removeStone = function (x, y) {
    return this.ref().child('board/' + x + "-" + y).remove();
};

FB.prototype.resetBoard = function () {
    this.ref().child('board').remove();
};

FB.prototype.setToken = function (playerNum) {
    var numOpponent = Math.abs(playerNum - 1);
    // TODO Utilise la méthode waitToJoin afin de réduire les connexions
    var self = this;
    var player = 'player' + playerNum + '/token';
    var opponent = 'player' + numOpponent + '/token';
    this.ref().child(player).once('value', function (onlineSnap) {
        self.ref().child(opponent).once('value', function (onlineSnapOpponent) {
            if (_.isNull(onlineSnap.val()) && _.isNull(onlineSnapOpponent.val())) {
                self.ref().child(player).transaction(function () {
                    return true;
                });
            }
        });
    });
};

FB.prototype.switchToken = function (playerNum) {
    // TODO Utilise la méthode waitToJoin afin de réduire les connexions
    var numOpponent = Math.abs(playerNum - 1);
    var opponent = 'player' + numOpponent + '/token';
    var player = 'player' + playerNum + '/token';
    this.ref().child(player).set({});
    this.ref().child(opponent).transaction(function () {
        return true;
    });
};

/**
 * Listens for data changes at a particular location
 *
 *

 *     FB.on('board', 'child_added')
 *       .progress( childAdded )
 *       .fail( securityError )
 *       .done( listenerDisposed )
 *

 *
 * @param path
 * @param event
 * @returns {snapshot}
 */
FB.prototype.on = function (path, event) {
    var def = $.Deferred();

    this.ref().child(path).on(event, function(snap) {
        def.notify(snap);
    }, function (err) {
        console.error('Access denied attempting to read database ', err, path, event)
    });

    return def.promise();
};

FB.prototype.once = function(path, event) {
    var def = $.Deferred();

    this.ref().child(path).once(event, function(snap) {
        def.resolve(snap);
    }, function(err) {
        console.error('Access denied attempting to read database', err, path, event);
        def.reject(err);
    });

    return def.promise();
};

FB.prototype.childWithTransaction = function (path, input) {
    var def = $.Deferred();
    this.ref().child(path).transaction(function (value) {
        def.resolve(value);
        if (value === null) {
            return input;
        } else {
            return;
        }
    }, function (error, committed) {
        def.reject(error, committed);
    });
    return def.promise();
};<commit_msg>add documentation<commit_after>var FB = function (url, idGame) {
    this.firebase = new Firebase(url);
    this.idGame = idGame;
};

FB.prototype.ref = function () {
    return this.firebase.child('/' + this.idGame + '/');
};

FB.prototype.newIdGame = function (size) {
    var ref = this.firebase.root();
    var key = ref.push().key();
    var def = $.Deferred();
    ref.child(key).set({size: size}, function(error) {
        if (error) {
            def.reject(error);
        } else {
            def.resolve(key);
        }
    });
    return def.promise();
};

FB.prototype.setStone = function (x, y, color) {
    return this.ref().child('board/' + x + "-" + y).set(color);
};

FB.prototype.removeStone = function (x, y) {
    return this.ref().child('board/' + x + "-" + y).remove();
};

FB.prototype.resetBoard = function () {
    this.ref().child('board').remove();
};

FB.prototype.setToken = function (playerNum) {
    var numOpponent = Math.abs(playerNum - 1);
    // TODO Utilise la méthode waitToJoin afin de réduire les connexions
    var self = this;
    var player = 'player' + playerNum + '/token';
    var opponent = 'player' + numOpponent + '/token';
    this.ref().child(player).once('value', function (onlineSnap) {
        self.ref().child(opponent).once('value', function (onlineSnapOpponent) {
            if (_.isNull(onlineSnap.val()) && _.isNull(onlineSnapOpponent.val())) {
                self.ref().child(player).transaction(function () {
                    return true;
                });
            }
        });
    });
};

FB.prototype.switchToken = function (playerNum) {
    // TODO Utilise la méthode waitToJoin afin de réduire les connexions
    var numOpponent = Math.abs(playerNum - 1);
    var opponent = 'player' + numOpponent + '/token';
    var player = 'player' + playerNum + '/token';
    this.ref().child(player).set({});
    this.ref().child(opponent).transaction(function () {
        return true;
    });
};

/**
 * Calls Firebase.on(), Listens for data changes at a particular location
 *
 *

 *     FB.on('board', 'child_added')
 *       .progress( childAdded )
 *       .fail( securityError )
 *       .done( listenerDisposed )
 *

 *
 * @param path
 * @param event
 * @return {jQuery.Deferred}
 */
FB.prototype.on = function (path, event) {
    var def = $.Deferred();

    this.ref().child(path).on(event, function(snap) {
        def.notify(snap);
    }, function (err) {
        console.error('Access denied attempting to read database ', err, path, event)
    });

    return def.promise();
};

/**
 * Calls Firebase.once(), Listens for exactly one event of the specified event type, and then stops listening.
 *
 * @param path
 * @param event
 * @returns {jQuery.Deferred}
 */
FB.prototype.once = function(path, event) {
    var def = $.Deferred();

    this.ref().child(path).once(event, function(snap) {
        def.resolve(snap);
    }, function(err) {
        console.error('Access denied attempting to read database', err, path, event);
        def.reject(err);
    });

    return def.promise();
};

FB.prototype.childWithTransaction = function (path, input) {
    var def = $.Deferred();
    this.ref().child(path).transaction(function (value) {
        def.resolve(value);
        if (value === null) {
            return input;
        } else {
            return;
        }
    }, function (error, committed) {
        def.reject(error, committed);
    });
    return def.promise();
};<|endoftext|>