<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* global chrome */

var _ = require('lodash');
var async = require('async');
var getIn = require('./getIn');

var serialDevice = require('../serialDevice');
var hidDevice = require('../hidDevice');
var driverManager = require('../driverManager');
var builder = require('../objectBuilder')();

var dexcomDriver = require('../drivers/dexcomDriver');
var oneTouchMiniDriver = require('../drivers/oneTouchMiniDriver');
var abbottPrecisionXtra = require('../drivers/abbottPrecisionXtra');
var insuletOmniPod = require('../drivers/insuletDriver');
var oneTouchUltra2 = require('../drivers/oneTouchUltra2');
var abbottFreeStyleLite = require('../drivers/abbottFreeStyleLite');
var bayerContourNext = require('../drivers/bayerContourNext');
var bayerContour = require('../drivers/bayerContour');

var device = {
  log: require('../bows')('Device')
};

device._deviceDrivers = {
  'DexcomG4': dexcomDriver,
  'OneTouchMini': oneTouchMiniDriver,
  'AbbottPrecisionXtra': abbottPrecisionXtra,
  'InsuletOmniPod': insuletOmniPod,
  'OneTouchUltra2': oneTouchUltra2,
  'AbbottFreeStyleLite': abbottFreeStyleLite,
  'AbbottFreeStyleFreedomLite': abbottFreeStyleLite,
  'BayerContourNext': bayerContourNext,
  'BayerContourNextUsb': bayerContourNext,
  'BayerContourUsb': bayerContourNext,
  'BayerContourNextLink': bayerContourNext,
  'BayerContour': bayerContour
};

device._deviceComms = {
  'DexcomG4': serialDevice,
  'OneTouchMini': serialDevice,
  'AbbottPrecisionXtra': serialDevice,
  'OneTouchUltra2': serialDevice,
  'AbbottFreeStyleLite': serialDevice,
  'AbbottFreeStyleFreedomLite': serialDevice,
  'BayerContourNext': hidDevice,
<<<<<<< HEAD
  'BayerContourNextUsb': hidDevice,
  'BayerContourUsb': hidDevice,
  'BayerContourNextLink': hidDevice
=======
  'BayerContour': serialDevice,
  'BayerContourNextUsb': hidDevice
>>>>>>> 563574ddf59cc0407649cad2be37307dea91d547
};

device._silentComms = {};
_.forEach(_.keys(device._deviceComms), function(driverId) {

  var comm = device._deviceComms[driverId];
  device._silentComms[driverId] = comm({silent: true});
  device._deviceComms[driverId] = comm();
});

// this is a cache for device information
// we need it so that what we learn in detect()
// can be used by process().
device._deviceInfoCache = {};

device.init = function(options, cb) {
  var self=this;
  self._defaultTimezone = options.defaultTimezone;
  self._driverManifests = self._getAllDriverManifests();
  self._api = options.api;
  self._version = options.version;
  self._groupId = options.targetId;

  chrome.runtime.getPlatformInfo(function (platformInfo) {
    self._os = platformInfo.os;
    if (self._os == 'win') {
      self._portpattern = 'COM[0-9]+';
    } else {
      self._portpattern = '/dev/cu\\.usb.+';
    }
    cb();
  });

};

device._getAllDriverManifests = function() {
  var manifest = chrome.runtime.getManifest();

  var usbDevicesPermission = _.find(manifest.permissions, function(permission) {
    return permission.usbDevices;
  });
  return _.reduce(usbDevicesPermission.usbDevices, function(acc, usbDevice) {
    acc[usbDevice.driverId] = usbDevice;
    return acc;
  }, {});
};

device.getDriverManifests = function() {
  return _.cloneDeep(this._driverManifests);
};

device.getDriverIds = function() {
  return _.keys(this._driverManifests);
};

device.getDriverManifest = function(driverId) {
  var driverManifest = this._driverManifests[driverId];
  if (!driverManifest) {
    throw new Error('Could not find driver manifest for "' + driverId + '"');
  }
  return driverManifest;
};

device.detectHelper = function(driverId, options, cb) {
  // Detect can run on a loop, so don't pollute the console with logging
  options.silent = true;
  var dm = this._createDriverManager(driverId, options);
  dm.detect(driverId, cb);
};

device._createDriverManager = function(driverId, options) {
  var drivers = {};
  drivers[driverId] = this._deviceDrivers[driverId];
  var configs = {};
  configs[driverId] = this._createDriverConfig(driverId, options);

  return driverManager(drivers, configs);
};

device._createDriverConfig = function(driverId, options) {
  options = options || {};
  var timezone = options.timezone || this._defaultTimezone;
  var comms = options.silent ? this._silentComms : this._deviceComms;
  var theVersion = options.version || this._version;
  var uploadGroup = options.targetId || this._groupId;

  // handle config for block-mode devices, which includes the file name and data
  if (options.filename != null) {
    return {
      filename: options.filename,
      filedata: options.filedata,
      deviceInfo: this._deviceInfoCache[driverId],
      timezone: timezone,
      groupId: uploadGroup,
      api: this._api,
      version: options.version,
      builder: builder,
      progress: options.progress,
      silent: Boolean(options.silent)
    };
  }

  return {
    deviceInfo: this._deviceInfoCache[driverId],
    deviceComms: comms[driverId],
    timezone: timezone,
    groupId: uploadGroup,
    api: this._api,
    version: options.version,
    builder: builder,
    progress: options.progress,
    silent: Boolean(options.silent)
  };
};

device.detectHid = function(driverId, cb) {
  var self = this;
  var driverManifest = this.getDriverManifest(driverId);
  var identification = {
    vendorId: driverManifest.vendorId,
    productId: driverManifest.productId
  };
  chrome.hid.getDevices(identification, function(results) {
    var devices = _.map(results, function(result) {
      var retval = {
        driverId: driverId,
        deviceId: result.deviceId,
        vendorId: result.vendorId,
        productId: result.productId
      };

      if (self._os == 'win') {
        if (driverManifest.winPortPattern) {
          retval.portPattern = driverManifest.winPortPattern;
        }
      } else {
        if (driverManifest.macPortPattern) {
          retval.portPattern = driverManifest.macPortPattern;
        }
      }

      return retval;
    });

    var devdata = _.first(devices);

    if (devices.length > 1) {
      self.log('WARNING: More than one device found for "' + driverId + '"');
      device.othersConnected = devices.length - 1;
    }

    return cb(null, devdata);
  });
};

device.detectUsb = function(driverId, cb) {
  var self = this;
  var driverManifest = this.getDriverManifest(driverId);
  var identification = {
    vendorId: driverManifest.vendorId,
    productId: driverManifest.productId
  };
  chrome.serial.getDevices(function(results) {
    self.log('Connected device(s): ');
    for(var i in results) {
        self.log(results[i].path);
    }
    var devices = _.map(results, function(result) {
      var retval = {
        driverId: driverId,
        vendorId: driverManifest.vendorId,
        productId: driverManifest.productId,
        portPattern: self._portpattern,
        usbDevice: result.device
      };
      if (self._os == 'win') {
        if (driverManifest.winPortPattern) {
          retval.portPattern = driverManifest.winPortPattern;
        }
      } else {
        if (driverManifest.macPortPattern) {
          retval.portPattern = driverManifest.macPortPattern;
        }
      }
      if (!!driverManifest.bitrate) {
        retval.bitrate = driverManifest.bitrate;
      }
      return retval;
    });

    var devdata = _.first(devices);

    if (devices.length > 1) {
      self.log('WARNING: More than one device found for "' + driverId + '"');
      device.othersConnected = devices.length - 1;
    }
    return cb(null, devdata);
  });
};

device.detect = function(driverId, options, cb) {
  var self = this;
  if (_.isFunction(options)) {
    cb = options;
    options = { version: self._version };
  }
  var driverManifest = this.getDriverManifest(driverId);

  if(driverManifest.mode === 'HID'){
      this.detectHid(driverId, function(err, devdata) {

        if (err) {
          return cb(err);
        }

        if (!devdata) {
          return cb();
        }

        self._deviceInfoCache[driverId] = _.cloneDeep(devdata);
        self.detectHelper(driverId, options, function(err, ftdiDevice) {

          if (err) {
            return cb(err);
          }
          device = _.assign(devdata, ftdiDevice);
          return cb(null, devdata);
        });
      });

  }else{

    this.detectUsb(driverId, function(err, devdata) {

      if (err) {
        return cb(err);
      }

      if (!devdata) {
        return cb();
      }

      self._deviceInfoCache[driverId] = _.cloneDeep(devdata);
      self.detectHelper(driverId, options, function(err, ftdiDevice) {
        if (err) {
          return cb(err);
        }
        device = _.assign(devdata, ftdiDevice);
        return cb(null, devdata);
      });
    });
  }
};

device.detectAll = function(cb) {
  async.map(this.getDriverIds(), this.detect.bind(this), function(err, results) {
    if (err) {
      return cb(err);
    }
    // Filter out any nulls
    results = _.filter(results);
    cb(null, results);
  });
};

device.upload = function(driverId, options, cb) {
  var dm = this._createDriverManager(driverId, options);
  dm.process(driverId, function(err, result) {
    if (err) {
      return cb(err);
    }
    return cb(null, result.post_records);
  });
};

module.exports = device;
<commit_msg>Fixed merging error<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* global chrome */

var _ = require('lodash');
var async = require('async');
var getIn = require('./getIn');

var serialDevice = require('../serialDevice');
var hidDevice = require('../hidDevice');
var driverManager = require('../driverManager');
var builder = require('../objectBuilder')();

var dexcomDriver = require('../drivers/dexcomDriver');
var oneTouchMiniDriver = require('../drivers/oneTouchMiniDriver');
var abbottPrecisionXtra = require('../drivers/abbottPrecisionXtra');
var insuletOmniPod = require('../drivers/insuletDriver');
var oneTouchUltra2 = require('../drivers/oneTouchUltra2');
var abbottFreeStyleLite = require('../drivers/abbottFreeStyleLite');
var bayerContourNext = require('../drivers/bayerContourNext');
var bayerContour = require('../drivers/bayerContour');

var device = {
  log: require('../bows')('Device')
};

device._deviceDrivers = {
  'DexcomG4': dexcomDriver,
  'OneTouchMini': oneTouchMiniDriver,
  'AbbottPrecisionXtra': abbottPrecisionXtra,
  'InsuletOmniPod': insuletOmniPod,
  'OneTouchUltra2': oneTouchUltra2,
  'AbbottFreeStyleLite': abbottFreeStyleLite,
  'AbbottFreeStyleFreedomLite': abbottFreeStyleLite,
  'BayerContourNext': bayerContourNext,
  'BayerContourNextUsb': bayerContourNext,
  'BayerContourUsb': bayerContourNext,
  'BayerContourNextLink': bayerContourNext,
  'BayerContour': bayerContour
};

device._deviceComms = {
  'DexcomG4': serialDevice,
  'OneTouchMini': serialDevice,
  'AbbottPrecisionXtra': serialDevice,
  'OneTouchUltra2': serialDevice,
  'AbbottFreeStyleLite': serialDevice,
  'AbbottFreeStyleFreedomLite': serialDevice,
  'BayerContourNext': hidDevice,
  'BayerContourNextUsb': hidDevice,
  'BayerContourUsb': hidDevice,
  'BayerContourNextLink': hidDevice,
  'BayerContour': serialDevice
};

device._silentComms = {};
_.forEach(_.keys(device._deviceComms), function(driverId) {

  var comm = device._deviceComms[driverId];
  device._silentComms[driverId] = comm({silent: true});
  device._deviceComms[driverId] = comm();
});

// this is a cache for device information
// we need it so that what we learn in detect()
// can be used by process().
device._deviceInfoCache = {};

device.init = function(options, cb) {
  var self=this;
  self._defaultTimezone = options.defaultTimezone;
  self._driverManifests = self._getAllDriverManifests();
  self._api = options.api;
  self._version = options.version;
  self._groupId = options.targetId;

  chrome.runtime.getPlatformInfo(function (platformInfo) {
    self._os = platformInfo.os;
    if (self._os == 'win') {
      self._portpattern = 'COM[0-9]+';
    } else {
      self._portpattern = '/dev/cu\\.usb.+';
    }
    cb();
  });

};

device._getAllDriverManifests = function() {
  var manifest = chrome.runtime.getManifest();

  var usbDevicesPermission = _.find(manifest.permissions, function(permission) {
    return permission.usbDevices;
  });
  return _.reduce(usbDevicesPermission.usbDevices, function(acc, usbDevice) {
    acc[usbDevice.driverId] = usbDevice;
    return acc;
  }, {});
};

device.getDriverManifests = function() {
  return _.cloneDeep(this._driverManifests);
};

device.getDriverIds = function() {
  return _.keys(this._driverManifests);
};

device.getDriverManifest = function(driverId) {
  var driverManifest = this._driverManifests[driverId];
  if (!driverManifest) {
    throw new Error('Could not find driver manifest for "' + driverId + '"');
  }
  return driverManifest;
};

device.detectHelper = function(driverId, options, cb) {
  // Detect can run on a loop, so don't pollute the console with logging
  options.silent = true;
  var dm = this._createDriverManager(driverId, options);
  dm.detect(driverId, cb);
};

device._createDriverManager = function(driverId, options) {
  var drivers = {};
  drivers[driverId] = this._deviceDrivers[driverId];
  var configs = {};
  configs[driverId] = this._createDriverConfig(driverId, options);

  return driverManager(drivers, configs);
};

device._createDriverConfig = function(driverId, options) {
  options = options || {};
  var timezone = options.timezone || this._defaultTimezone;
  var comms = options.silent ? this._silentComms : this._deviceComms;
  var theVersion = options.version || this._version;
  var uploadGroup = options.targetId || this._groupId;

  // handle config for block-mode devices, which includes the file name and data
  if (options.filename != null) {
    return {
      filename: options.filename,
      filedata: options.filedata,
      deviceInfo: this._deviceInfoCache[driverId],
      timezone: timezone,
      groupId: uploadGroup,
      api: this._api,
      version: options.version,
      builder: builder,
      progress: options.progress,
      silent: Boolean(options.silent)
    };
  }

  return {
    deviceInfo: this._deviceInfoCache[driverId],
    deviceComms: comms[driverId],
    timezone: timezone,
    groupId: uploadGroup,
    api: this._api,
    version: options.version,
    builder: builder,
    progress: options.progress,
    silent: Boolean(options.silent)
  };
};

device.detectHid = function(driverId, cb) {
  var self = this;
  var driverManifest = this.getDriverManifest(driverId);
  var identification = {
    vendorId: driverManifest.vendorId,
    productId: driverManifest.productId
  };
  chrome.hid.getDevices(identification, function(results) {
    var devices = _.map(results, function(result) {
      var retval = {
        driverId: driverId,
        deviceId: result.deviceId,
        vendorId: result.vendorId,
        productId: result.productId
      };

      if (self._os == 'win') {
        if (driverManifest.winPortPattern) {
          retval.portPattern = driverManifest.winPortPattern;
        }
      } else {
        if (driverManifest.macPortPattern) {
          retval.portPattern = driverManifest.macPortPattern;
        }
      }

      return retval;
    });

    var devdata = _.first(devices);

    if (devices.length > 1) {
      self.log('WARNING: More than one device found for "' + driverId + '"');
      device.othersConnected = devices.length - 1;
    }

    return cb(null, devdata);
  });
};

device.detectUsb = function(driverId, cb) {
  var self = this;
  var driverManifest = this.getDriverManifest(driverId);
  var identification = {
    vendorId: driverManifest.vendorId,
    productId: driverManifest.productId
  };
  chrome.serial.getDevices(function(results) {
    self.log('Connected device(s): ');
    for(var i in results) {
        self.log(results[i].path);
    }
    var devices = _.map(results, function(result) {
      var retval = {
        driverId: driverId,
        vendorId: driverManifest.vendorId,
        productId: driverManifest.productId,
        portPattern: self._portpattern,
        usbDevice: result.device
      };
      if (self._os == 'win') {
        if (driverManifest.winPortPattern) {
          retval.portPattern = driverManifest.winPortPattern;
        }
      } else {
        if (driverManifest.macPortPattern) {
          retval.portPattern = driverManifest.macPortPattern;
        }
      }
      if (!!driverManifest.bitrate) {
        retval.bitrate = driverManifest.bitrate;
      }
      return retval;
    });

    var devdata = _.first(devices);

    if (devices.length > 1) {
      self.log('WARNING: More than one device found for "' + driverId + '"');
      device.othersConnected = devices.length - 1;
    }
    return cb(null, devdata);
  });
};

device.detect = function(driverId, options, cb) {
  var self = this;
  if (_.isFunction(options)) {
    cb = options;
    options = { version: self._version };
  }
  var driverManifest = this.getDriverManifest(driverId);

  if(driverManifest.mode === 'HID'){
      this.detectHid(driverId, function(err, devdata) {

        if (err) {
          return cb(err);
        }

        if (!devdata) {
          return cb();
        }

        self._deviceInfoCache[driverId] = _.cloneDeep(devdata);
        self.detectHelper(driverId, options, function(err, ftdiDevice) {

          if (err) {
            return cb(err);
          }
          device = _.assign(devdata, ftdiDevice);
          return cb(null, devdata);
        });
      });

  }else{

    this.detectUsb(driverId, function(err, devdata) {

      if (err) {
        return cb(err);
      }

      if (!devdata) {
        return cb();
      }

      self._deviceInfoCache[driverId] = _.cloneDeep(devdata);
      self.detectHelper(driverId, options, function(err, ftdiDevice) {
        if (err) {
          return cb(err);
        }
        device = _.assign(devdata, ftdiDevice);
        return cb(null, devdata);
      });
    });
  }
};

device.detectAll = function(cb) {
  async.map(this.getDriverIds(), this.detect.bind(this), function(err, results) {
    if (err) {
      return cb(err);
    }
    // Filter out any nulls
    results = _.filter(results);
    cb(null, results);
  });
};

device.upload = function(driverId, options, cb) {
  var dm = this._createDriverManager(driverId, options);
  dm.process(driverId, function(err, result) {
    if (err) {
      return cb(err);
    }
    return cb(null, result.post_records);
  });
};

module.exports = device;
<|endoftext|>


<commit_before>var opbeat = require('../')
  , fs = require('fs')
  , nock = require('nock')
  , common = require('common')
  , mockudp = require('mock-udp');

var options = {
    organization_id: 'some-org-id',
    app_id: 'some-app-id',
    secret_token: 'secret',
    handleExceptions: false
};

var disableUncaughtExceptionHandler = {
    handleExceptions: false
};

var _oldConsoleWarn = console.warn;
var mockConsoleWarn = function () {
    console.warn = function () {
        console.warn._called = true;
    };
    console.warn._called = false;
}
var restoreConsoleWarn = function () {
    console.warn = _oldConsoleWarn;
}

describe('opbeat.version', function () {
    it('should be valid', function () {
        opbeat.version.should.match(/^\d+\.\d+\.\d+(-\w+)?$/);
    });

    it('should match package.json', function () {
        var version = require('../package.json').version;
        opbeat.version.should.equal(version);
    });
});

describe('opbeat.createClient', function () {
    var client;
    var skipBody = function (path) { return '*'; };
    beforeEach(function () {
        mockConsoleWarn();
        process.env.NODE_ENV='production';
    });
    afterEach(function () {
        restoreConsoleWarn();
    });

    it('should initialize the client property', function () {
        opbeat.should.not.have.ownProperty('client');
        var client = opbeat.createClient(options);
        opbeat.should.have.ownProperty('client');
        opbeat.client.should.have.ownProperty('dsn');
    });

    it('should parse the DSN with options', function () {
        var expected = {
            protocol: 'https',
            host: 'opbeat.com',
            path: '/api/v1/organizations/some-org-id/apps/some-app-id/errors/'
        };
        client = opbeat.createClient(common.join(options, { hostname: 'my-hostname' }));
        client.dsn.should.eql(expected);
        client.hostname.should.equal('my-hostname');
    });

    it('should pull OPBEAT_ORGANIZATION_ID from environment', function () {
        process.env.OPBEAT_ORGANIZATION_ID='another-org-id';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.organization_id.should.eql('another-org-id');
        delete process.env.OPBEAT_ORGANIZATION_ID; // gotta clean up so it doesn't leak into other tests
    });

    it('should pull OPBEAT_ORGANIZATION_ID from environment when passing options', function () {
        var expected = {
            protocol: 'https',
            host: 'opbeat.com',
            path: '/api/v1/organizations/another-org-id/apps/some-app-id/errors/'
        };
        process.env.OPBEAT_ORGANIZATION_ID='another-org-id';
        client = opbeat.createClient({
            app_id: 'some-app-id',
            secret_token: 'secret',
            handleExceptions: false
        });
        client.dsn.should.eql(expected);
        client.organization_id.should.equal('another-org-id');
        client.app_id.should.equal('some-app-id');
        client.secret_token.should.equal('secret');
        delete process.env.OPBEAT_ORGANIZATION_ID; // gotta clean up so it doesn't leak into other tests
    });

    it('should be disabled when no options have been specified', function () {
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client._enabled.should.eql(false);
        console.warn._called.should.eql(true);
    });

    it('should pull OPBEAT_APP_ID from environment', function () {
        process.env.OPBEAT_APP_ID='another-app-id';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.app_id.should.eql('another-app-id');
        delete process.env.OPBEAT_APP_ID;
    });

    it('should pull OPBEAT_SECRET_TOKEN from environment', function () {
        process.env.OPBEAT_SECRET_TOKEN='pazz';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.secret_token.should.eql('pazz');
        delete process.env.OPBEAT_SECRET_TOKEN;
    });

    it('should be disabled and warn when NODE_ENV=test', function () {
        process.env.NODE_ENV = 'test';
        client = opbeat.createClient(options);
        client._enabled.should.eql(false);
        console.warn._called.should.eql(true);
    });

    describe('#captureMessage()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should send a plain text message to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .defaultReplyHeaders({'Location': 'foo'})
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            client.on('logged', function (result) {
                result.should.eql('foo');
                scope.done();
                done();
            });
            client.captureMessage('Hey!');
        });

        it('should emit error when request returns non 200', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.on('error', function () {
                scope.done();
                done();
            });
            client.captureMessage('Hey!');
        });

        it('shouldn\'t shit it\'s pants when error is emitted without a listener', function () {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.captureMessage('Hey!');
        });

        it('should attach an Error object when emitting error', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.on('error', function (err) {
                err.message.should.eql('Opbeat error (500): Oops!');
                scope.done();
                done();
            });

            client.captureMessage('Hey!');
        });

        it('should use `param_message` instead of `message` if given an object as 1st argument', function (done) {
            var oldProcess = client.process;
            client.process = function (kwargs, cb) {
                kwargs.should.not.have.ownProperty('message');
                kwargs.should.have.ownProperty('param_message');
                kwargs.param_message.message.should.eql('Hello %s');
                kwargs.param_message.params.should.be.instanceOf(Array);
                kwargs.param_message.params[0].should.eql('World');
                done();
            };
            client.captureMessage({ message: 'Hello %s', params: ['World'] });
            client.process = oldProcess;
        });
    });

    describe('#captureError()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should send an Error to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .defaultReplyHeaders({'Location': 'foo'})
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            client.on('logged', function (result) {
                result.should.eql('foo');
                scope.done();
                done();
            });
            client.captureError(new Error('wtf?'));
        });

        it('should send a plain text "error" as a Message instead', function (done) {
            // See: https://github.com/mattrobenolt/raven-node/issues/18
            var old = client.captureMessage;
            client.captureMessage = function (message) {
                // I'm also appending "Error: " to the beginning to help hint
                message.should.equal('Error: wtf?');
                done();
                client.captureMessage = old;
            };
            client.captureError('wtf?');
        });
    });

    describe('#handleUncaughtExceptions()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should add itself to the uncaughtException event list', function () {
            var before = process._events.uncaughtException.length;
            client.handleUncaughtExceptions();
            process._events.uncaughtException.length.should.equal(before+1);
            process._events.uncaughtException.pop(); // patch it back to what it was
        });

        it('should send an uncaughtException to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            // remove existing uncaughtException handlers
            var before = process._events.uncaughtException;
            process.removeAllListeners('uncaughtException');

            client.handleUncaughtExceptions(function (err) {
                // restore things to how they were
                process._events.uncaughtException = before;
                scope.done();
                done();
            });

            process.emit('uncaughtException', new Error('derp'));
        });
    });
});
<commit_msg>Fix test after change in error output<commit_after>var opbeat = require('../')
  , fs = require('fs')
  , nock = require('nock')
  , common = require('common')
  , mockudp = require('mock-udp');

var options = {
    organization_id: 'some-org-id',
    app_id: 'some-app-id',
    secret_token: 'secret',
    handleExceptions: false
};

var disableUncaughtExceptionHandler = {
    handleExceptions: false
};

var _oldConsoleWarn = console.warn;
var mockConsoleWarn = function () {
    console.warn = function () {
        console.warn._called = true;
    };
    console.warn._called = false;
}
var restoreConsoleWarn = function () {
    console.warn = _oldConsoleWarn;
}

describe('opbeat.version', function () {
    it('should be valid', function () {
        opbeat.version.should.match(/^\d+\.\d+\.\d+(-\w+)?$/);
    });

    it('should match package.json', function () {
        var version = require('../package.json').version;
        opbeat.version.should.equal(version);
    });
});

describe('opbeat.createClient', function () {
    var client;
    var skipBody = function (path) { return '*'; };
    beforeEach(function () {
        mockConsoleWarn();
        process.env.NODE_ENV='production';
    });
    afterEach(function () {
        restoreConsoleWarn();
    });

    it('should initialize the client property', function () {
        opbeat.should.not.have.ownProperty('client');
        var client = opbeat.createClient(options);
        opbeat.should.have.ownProperty('client');
        opbeat.client.should.have.ownProperty('dsn');
    });

    it('should parse the DSN with options', function () {
        var expected = {
            protocol: 'https',
            host: 'opbeat.com',
            path: '/api/v1/organizations/some-org-id/apps/some-app-id/errors/'
        };
        client = opbeat.createClient(common.join(options, { hostname: 'my-hostname' }));
        client.dsn.should.eql(expected);
        client.hostname.should.equal('my-hostname');
    });

    it('should pull OPBEAT_ORGANIZATION_ID from environment', function () {
        process.env.OPBEAT_ORGANIZATION_ID='another-org-id';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.organization_id.should.eql('another-org-id');
        delete process.env.OPBEAT_ORGANIZATION_ID; // gotta clean up so it doesn't leak into other tests
    });

    it('should pull OPBEAT_ORGANIZATION_ID from environment when passing options', function () {
        var expected = {
            protocol: 'https',
            host: 'opbeat.com',
            path: '/api/v1/organizations/another-org-id/apps/some-app-id/errors/'
        };
        process.env.OPBEAT_ORGANIZATION_ID='another-org-id';
        client = opbeat.createClient({
            app_id: 'some-app-id',
            secret_token: 'secret',
            handleExceptions: false
        });
        client.dsn.should.eql(expected);
        client.organization_id.should.equal('another-org-id');
        client.app_id.should.equal('some-app-id');
        client.secret_token.should.equal('secret');
        delete process.env.OPBEAT_ORGANIZATION_ID; // gotta clean up so it doesn't leak into other tests
    });

    it('should be disabled when no options have been specified', function () {
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client._enabled.should.eql(false);
        console.warn._called.should.eql(true);
    });

    it('should pull OPBEAT_APP_ID from environment', function () {
        process.env.OPBEAT_APP_ID='another-app-id';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.app_id.should.eql('another-app-id');
        delete process.env.OPBEAT_APP_ID;
    });

    it('should pull OPBEAT_SECRET_TOKEN from environment', function () {
        process.env.OPBEAT_SECRET_TOKEN='pazz';
        client = opbeat.createClient(disableUncaughtExceptionHandler);
        client.secret_token.should.eql('pazz');
        delete process.env.OPBEAT_SECRET_TOKEN;
    });

    it('should be disabled and warn when NODE_ENV=test', function () {
        process.env.NODE_ENV = 'test';
        client = opbeat.createClient(options);
        client._enabled.should.eql(false);
        console.warn._called.should.eql(true);
    });

    describe('#captureMessage()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should send a plain text message to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .defaultReplyHeaders({'Location': 'foo'})
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            client.on('logged', function (result) {
                result.should.eql('foo');
                scope.done();
                done();
            });
            client.captureMessage('Hey!');
        });

        it('should emit error when request returns non 200', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.on('error', function () {
                scope.done();
                done();
            });
            client.captureMessage('Hey!');
        });

        it('shouldn\'t shit it\'s pants when error is emitted without a listener', function () {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.captureMessage('Hey!');
        });

        it('should attach an Error object when emitting error', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(500, { error: 'Oops!' });

            client.on('error', function (err) {
                err.message.should.eql('Opbeat error (500): {"error":"Oops!"}');
                scope.done();
                done();
            });

            client.captureMessage('Hey!');
        });

        it('should use `param_message` instead of `message` if given an object as 1st argument', function (done) {
            var oldProcess = client.process;
            client.process = function (kwargs, cb) {
                kwargs.should.not.have.ownProperty('message');
                kwargs.should.have.ownProperty('param_message');
                kwargs.param_message.message.should.eql('Hello %s');
                kwargs.param_message.params.should.be.instanceOf(Array);
                kwargs.param_message.params[0].should.eql('World');
                done();
            };
            client.captureMessage({ message: 'Hello %s', params: ['World'] });
            client.process = oldProcess;
        });
    });

    describe('#captureError()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should send an Error to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .defaultReplyHeaders({'Location': 'foo'})
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            client.on('logged', function (result) {
                result.should.eql('foo');
                scope.done();
                done();
            });
            client.captureError(new Error('wtf?'));
        });

        it('should send a plain text "error" as a Message instead', function (done) {
            // See: https://github.com/mattrobenolt/raven-node/issues/18
            var old = client.captureMessage;
            client.captureMessage = function (message) {
                // I'm also appending "Error: " to the beginning to help hint
                message.should.equal('Error: wtf?');
                done();
                client.captureMessage = old;
            };
            client.captureError('wtf?');
        });
    });

    describe('#handleUncaughtExceptions()', function () {
        beforeEach(function () {
            mockConsoleWarn();
            client = opbeat.createClient(options);
        });
        afterEach(function () {
            restoreConsoleWarn();
        });

        it('should add itself to the uncaughtException event list', function () {
            var before = process._events.uncaughtException.length;
            client.handleUncaughtExceptions();
            process._events.uncaughtException.length.should.equal(before+1);
            process._events.uncaughtException.pop(); // patch it back to what it was
        });

        it('should send an uncaughtException to Opbeat server', function (done) {
            var scope = nock('https://opbeat.com')
                .filteringRequestBody(skipBody)
                .post('/api/v1/organizations/some-org-id/apps/some-app-id/errors/', '*')
                .reply(200);

            // remove existing uncaughtException handlers
            var before = process._events.uncaughtException;
            process.removeAllListeners('uncaughtException');

            client.handleUncaughtExceptions(function (err) {
                // restore things to how they were
                process._events.uncaughtException = before;
                scope.done();
                done();
            });

            process.emit('uncaughtException', new Error('derp'));
        });
    });
});
<|endoftext|>


<commit_before>(function() {
    'use strict';

    angular.module('angular-throttle', [])
        .factory('throttle', [
            function() {
                var last = +new Date();

                return function(fn, delay) {
                    var now = +new Date();

                    if (now - last >= delay) {
                        last = now;
                        fn();
                    }
                };
            }
        ]);

    angular.module('angular-scrollify', ['angular-throttle']).directive('ngScrollify', ['$log', '$window', '$document', '$timeout', 'throttle',
        function($log, $window, $document, $timeout, throttle) {
            return {
                restrict: 'A',
                transclude: true,
                template: '

',
                compile: function(_element, _attr, linker) {
                    return function link(scope, element, attr) {

                        var expression = attr.ngScrollify;
                        var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*$/);
                        var valueIdentifier, listIdentifier;

                        if (!match) {
                            $log.error('Expected ngScrollify in form of "_item_ in _array_" but got "' + expression + '".');
                        }

                        valueIdentifier = match[1];
                        listIdentifier = match[2];

                        var defaults = {
                            container: 'window', // window/element - defines what to use for height measurements and scrolling
                            id: +new Date(), // `id` if using multiple instances
                            scrollSpeed: 200, // transition time to next pane (ms)
                            speedMod: 3, // factor to divide `scrollSpeed` by when moving more than 1 pane
                            scrollBarMod: 100, // length of container as a percentage of "real" length (prevent tiny handle on long pages)
                            wheelThrottle: 300, // throttle wheel/trackpad event
                            scrollMaxRate: 50, // debounce scroll event
                            // startIdx: 5 // optional start offset
                        };

                        if (attr.ngScrollifyOptions !== undefined) {
                            angular.extend(defaults, scope.$eval(attr.ngScrollifyOptions));
                        }

                        var dummy = angular.element(element.children()[0]);
                        var container = angular.element(element.children()[1]);
                        var wrapper = container.children();

                        var templatePane = wrapper.children();
                        wrapper.children().remove();
                        wrapper.append('');

                        var _linker = function(pane) {
                            linker(pane.scope, function(clone) {
                                var paneClone = templatePane.clone();
                                paneClone.children().replaceWith(clone);
                                wrapper.append(paneClone);
                                pane.element = paneClone;
                            });
                        };

                        var panes = [],
                            currentPane,
                            prevPane = null,
                            preventScroll = false;

                        var init = function() {
                            for (var i = 0; i < list.length; i++) {
                                var pane = {};
                                pane.scope = scope.$new();
                                pane.scope.$index = i;
                                panes.push(pane);

                                _linker(pane);

                                angular.element(pane.element).attr('data-index', i);
                            }

                            for (i = 0; i < list.length; i++) {
                                panes[i].scope[valueIdentifier] = list[i];

                                if (!panes[i].scope.$$phase) {
                                    panes[i].scope.$apply();
                                }
                            }

                            setContainerHeight();

                            $timeout(function() {
                                currentPane = defaults.startIdx || getCurrentPane();

                                scope.$broadcast('scrollify:init', defaults.id, currentPane);

                                moveWrapper(0);
                            });
                        };

                        var list = [];

                        scope.$watch(listIdentifier, function(n) {
                            if (n !== undefined) {
                                list = n;

                                init();
                            }
                        });

                        var wheelTimeout,
                            deltaCount = 0,
                            jumpCount = 0;

                        var hamster = new Hamster(element[0]).wheel(function(e, delta, deltaX, deltaY) {
                            e = e.originalEvent || e;

                            var normalisedDelta = normaliseDelta(e.detail, deltaY);

                            if (deltaY !== 0) {
                                throttle(function() {
                                    deltaCount += (normalisedDelta * 120);

                                    if (Math.abs(0 - deltaCount) >= 1) {
                                        deltaCount = 0;

                                        jumpCount -= (deltaY > 0 ? 1 : -1);

                                        prevPane = currentPane;

                                        var cp = currentPane + jumpCount;

                                        setCurrentPane(cp < 0 ? 0 : cp > list.length - 1 ? list.length - 1 : cp);

                                        jumpCount = 0;

                                        scrollToCurrent();
                                    }
                                }, defaults.wheelThrottle);
                            }

                            e.preventDefault();
                        });

                        // http://stackoverflow.com/a/13650579/1050862
                        var normaliseDelta = function(detail, wheelDelta) {
                            var d = detail,
                                w = wheelDelta,
                                n = 225,
                                n1 = n - 1;

                            // Normalize delta
                            d = d ? w && (f = w / d) ? d / f : -d / 1.35 : w / 120;

                            // Quadratic scale if |d| > 1
                            d = d < 1 ? d < -1 ? (-Math.pow(d, 2) - n1) / n : d : (Math.pow(d, 2) + n1) / n;

                            // Delta *should* not be greater than 2...
                            return (Math.min(Math.max(d / 2, -1), 1)) * 2;
                        };

                        var setCurrentPane = function(i) {
                            var changeEvent = scope.$broadcast('scrollify:change', defaults.id, i);

                            if (changeEvent.defaultPrevented) {
                                return false;

                            } else {
                                currentPane = i;

                                return true;
                            }
                        };

                        var getCurrentPane = function() {
                            if (list.length === 1) {
                                return 0;
                            } else if (defaults.container === 'window') {
                                return Math.round((list.length - 1) * ($window.scrollY / (dummy[0].scrollHeight - $window.innerHeight)));
                            } else {
                                return Math.round((list.length - 1) * (element[0].scrollTop / (dummy[0].scrollHeight - element[0].clientHeight)));
                            }
                        };

                        var scrollToCurrent = function(instant) {
                            var speed = instant ? 0 : Math.max(1, Math.abs(prevPane - currentPane) / defaults.speedMod) * defaults.scrollSpeed;

                            if (defaults.container === 'window') {
                                $window.scrollTo(0, ((dummy[0].scrollHeight - $window.innerHeight) / (list.length - 1)) * currentPane);
                            } else {
                                element[0].scrollTop = ((dummy[0].scrollHeight - element[0].clientHeight) / (list.length - 1)) * currentPane;
                            }

                            moveWrapper(speed);
                        };

                        var setContainerHeight = function() {
                            dummy.css('height', (list.length * defaults.scrollBarMod) + '%');
                        };

                        var moveEndTimeout;

                        var moveWrapper = function(transDuration) {
                            transDuration = transDuration || 0;
                            var wrapperY = -(currentPane * container[0].clientHeight);
                            wrapper[0].style[Modernizr.prefixed('transform')] = 'translate(0, ' + wrapperY + 'px)';
                            wrapper[0].style[Modernizr.prefixed('transitionDuration')] = transDuration + 'ms';

                            $timeout.cancel(moveEndTimeout);

                            moveEndTimeout = $timeout(function(){
                                scope.$broadcast('scrollify:transitionEnd', defaults.id, currentPane);
                            }, transDuration);
                        };

                        var scrollTimeout;

                        var scroll = function(e) {
                            $timeout.cancel(scrollTimeout);

                            if (!preventScroll) {
                                scrollTimeout = $timeout(function() {
                                    if (prevPane === null) {
                                        prevPane = currentPane;
                                    }

                                    setCurrentPane(getCurrentPane());

                                    moveWrapper(Math.max(1, Math.abs(prevPane - currentPane) / defaults.speedMod) * defaults.scrollSpeed);

                                    prevPane = null;
                                }, defaults.scrollMaxRate);
                            }
                        };

                        var goTo = function(i, instant) {
                            if (setCurrentPane(i)) {
                                prevPane = currentPane;

                                scrollToCurrent(instant);
                            }
                        };

                        var next = function() {
                            goTo(currentPane < list.length - 1 ? currentPane + 1 : list.length - 1);
                        };

                        var prev = function() {
                            goTo(currentPane > 0 ? currentPane - 1 : currentPane);
                        };

                        scope.$on('scrollify:goTo', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            goTo(obj.pane, obj.instant);
                        });

                        scope.$on('scrollify:next', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            next();
                        });

                        scope.$on('scrollify:prev', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            prev();
                        });

                        var keyDown = function(e) {
                            switch (e.keyCode) {
                                case 40:
                                    next();
                                    e.preventDefault();
                                    break;
                                case 38:
                                    prev();
                                    e.preventDefault();
                                    break;
                            }
                        };

                        var resetTimeout;

                        var resize = function(e) {
                            preventScroll = true;

                            setContainerHeight();

                            scrollToCurrent(true);

                            $timeout.cancel(resetTimeout);
                            resetTimeout = $timeout(function() {
                                preventScroll = false;
                            }, defaults.scrollSpeed);
                        };

                        var resizeEvent = 'onorientationchange' in $window ? 'orientationchange' : 'resize';

                        angular.element($window).on(resizeEvent, resize);

                        if (defaults.container === 'window') {
                            angular.element($window).on('scroll', scroll);
                        } else {
                            element.on('scroll', scroll);
                        }

                        $document.on('keydown', keyDown);

                        scope.$on('$destroy', function() {
                            angular.element($window).off(resizeEvent, resize);

                            if (defaults.container === 'window') {
                                angular.element($window).off('scroll', scroll);
                            } else {
                                element.off('scroll', scroll);
                            }

                            $document.off('keydown', keyDown);
                        });

                    };
                }
            };
        }
    ]);

})();
<commit_msg>Broadcast args as object<commit_after>(function() {
    'use strict';

    angular.module('angular-throttle', [])
        .factory('throttle', [
            function() {
                var last = +new Date();

                return function(fn, delay) {
                    var now = +new Date();

                    if (now - last >= delay) {
                        last = now;
                        fn();
                    }
                };
            }
        ]);

    angular.module('angular-scrollify', ['angular-throttle']).directive('ngScrollify', ['$log', '$window', '$document', '$timeout', 'throttle',
        function($log, $window, $document, $timeout, throttle) {
            return {
                restrict: 'A',
                transclude: true,
                template: '

',
                compile: function(_element, _attr, linker) {
                    return function link(scope, element, attr) {

                        var expression = attr.ngScrollify;
                        var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*$/);
                        var valueIdentifier, listIdentifier;

                        if (!match) {
                            $log.error('Expected ngScrollify in form of "_item_ in _array_" but got "' + expression + '".');
                        }

                        valueIdentifier = match[1];
                        listIdentifier = match[2];

                        var defaults = {
                            container: 'window', // window/element - defines what to use for height measurements and scrolling
                            id: +new Date(), // `id` if using multiple instances
                            scrollSpeed: 200, // transition time to next pane (ms)
                            speedMod: 3, // factor to divide `scrollSpeed` by when moving more than 1 pane
                            scrollBarMod: 100, // length of container as a percentage of "real" length (prevent tiny handle on long pages)
                            wheelThrottle: 300, // throttle wheel/trackpad event
                            scrollMaxRate: 50, // debounce scroll event
                            // startIdx: 5 // optional start offset
                        };

                        if (attr.ngScrollifyOptions !== undefined) {
                            angular.extend(defaults, scope.$eval(attr.ngScrollifyOptions));
                        }

                        var dummy = angular.element(element.children()[0]);
                        var container = angular.element(element.children()[1]);
                        var wrapper = container.children();

                        var templatePane = wrapper.children();
                        wrapper.children().remove();
                        wrapper.append('');

                        var _linker = function(pane) {
                            linker(pane.scope, function(clone) {
                                var paneClone = templatePane.clone();
                                paneClone.children().replaceWith(clone);
                                wrapper.append(paneClone);
                                pane.element = paneClone;
                            });
                        };

                        var panes = [],
                            currentPane,
                            prevPane = null,
                            preventScroll = false;

                        var init = function() {
                            for (var i = 0; i < list.length; i++) {
                                var pane = {};
                                pane.scope = scope.$new();
                                pane.scope.$index = i;
                                panes.push(pane);

                                _linker(pane);

                                angular.element(pane.element).attr('data-index', i);
                            }

                            for (i = 0; i < list.length; i++) {
                                panes[i].scope[valueIdentifier] = list[i];

                                if (!panes[i].scope.$$phase) {
                                    panes[i].scope.$apply();
                                }
                            }

                            setContainerHeight();

                            $timeout(function() {
                                currentPane = defaults.startIdx || getCurrentPane();

                                scope.$broadcast('scrollify:init', { id: defaults.id, currentPane: currentPane });

                                moveWrapper(0);
                            });
                        };

                        var list = [];

                        scope.$watch(listIdentifier, function(n) {
                            if (n !== undefined) {
                                list = n;

                                init();
                            }
                        });

                        var wheelTimeout,
                            deltaCount = 0,
                            jumpCount = 0;

                        var hamster = new Hamster(element[0]).wheel(function(e, delta, deltaX, deltaY) {
                            e = e.originalEvent || e;

                            var normalisedDelta = normaliseDelta(e.detail, deltaY);

                            if (deltaY !== 0) {
                                throttle(function() {
                                    deltaCount += (normalisedDelta * 120);

                                    if (Math.abs(0 - deltaCount) >= 1) {
                                        deltaCount = 0;

                                        jumpCount -= (deltaY > 0 ? 1 : -1);

                                        prevPane = currentPane;

                                        var cp = currentPane + jumpCount;

                                        setCurrentPane(cp < 0 ? 0 : cp > list.length - 1 ? list.length - 1 : cp);

                                        jumpCount = 0;

                                        scrollToCurrent();
                                    }
                                }, defaults.wheelThrottle);
                            }

                            e.preventDefault();
                        });

                        // http://stackoverflow.com/a/13650579/1050862
                        var normaliseDelta = function(detail, wheelDelta) {
                            var d = detail,
                                w = wheelDelta,
                                n = 225,
                                n1 = n - 1;

                            // Normalize delta
                            d = d ? w && (f = w / d) ? d / f : -d / 1.35 : w / 120;

                            // Quadratic scale if |d| > 1
                            d = d < 1 ? d < -1 ? (-Math.pow(d, 2) - n1) / n : d : (Math.pow(d, 2) + n1) / n;

                            // Delta *should* not be greater than 2...
                            return (Math.min(Math.max(d / 2, -1), 1)) * 2;
                        };

                        var setCurrentPane = function(i) {
                            var changeEvent = scope.$broadcast('scrollify:change', { id: defaults.id, i: i });

                            if (changeEvent.defaultPrevented) {
                                return false;

                            } else {
                                currentPane = i;

                                return true;
                            }
                        };

                        var getCurrentPane = function() {
                            if (list.length === 1) {
                                return 0;
                            } else if (defaults.container === 'window') {
                                return Math.round((list.length - 1) * ($window.scrollY / (dummy[0].scrollHeight - $window.innerHeight)));
                            } else {
                                return Math.round((list.length - 1) * (element[0].scrollTop / (dummy[0].scrollHeight - element[0].clientHeight)));
                            }
                        };

                        var scrollToCurrent = function(instant) {
                            var speed = instant ? 0 : Math.max(1, Math.abs(prevPane - currentPane) / defaults.speedMod) * defaults.scrollSpeed;

                            if (defaults.container === 'window') {
                                $window.scrollTo(0, ((dummy[0].scrollHeight - $window.innerHeight) / (list.length - 1)) * currentPane);
                            } else {
                                element[0].scrollTop = ((dummy[0].scrollHeight - element[0].clientHeight) / (list.length - 1)) * currentPane;
                            }

                            moveWrapper(speed);
                        };

                        var setContainerHeight = function() {
                            dummy.css('height', (list.length * defaults.scrollBarMod) + '%');
                        };

                        var moveEndTimeout;

                        var moveWrapper = function(transDuration) {
                            transDuration = transDuration || 0;
                            var wrapperY = -(currentPane * container[0].clientHeight);
                            wrapper[0].style[Modernizr.prefixed('transform')] = 'translate(0, ' + wrapperY + 'px)';
                            wrapper[0].style[Modernizr.prefixed('transitionDuration')] = transDuration + 'ms';

                            $timeout.cancel(moveEndTimeout);

                            moveEndTimeout = $timeout(function(){
                                scope.$broadcast('scrollify:transitionEnd', { id: defaults.id, currentPane: currentPane });
                            }, transDuration);
                        };

                        var scrollTimeout;

                        var scroll = function(e) {
                            $timeout.cancel(scrollTimeout);

                            if (!preventScroll) {
                                scrollTimeout = $timeout(function() {
                                    if (prevPane === null) {
                                        prevPane = currentPane;
                                    }

                                    setCurrentPane(getCurrentPane());

                                    moveWrapper(Math.max(1, Math.abs(prevPane - currentPane) / defaults.speedMod) * defaults.scrollSpeed);

                                    prevPane = null;
                                }, defaults.scrollMaxRate);
                            }
                        };

                        var goTo = function(i, instant) {
                            if (setCurrentPane(i)) {
                                prevPane = currentPane;

                                scrollToCurrent(instant);
                            }
                        };

                        var next = function() {
                            goTo(currentPane < list.length - 1 ? currentPane + 1 : list.length - 1);
                        };

                        var prev = function() {
                            goTo(currentPane > 0 ? currentPane - 1 : currentPane);
                        };

                        scope.$on('scrollify:goTo', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            goTo(obj.pane, obj.instant);
                        });

                        scope.$on('scrollify:next', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            next();
                        });

                        scope.$on('scrollify:prev', function(e, obj) {
                            if (obj.id && defaults.id !== obj.id) {
                                return false;
                            }

                            prev();
                        });

                        var keyDown = function(e) {
                            switch (e.keyCode) {
                                case 40:
                                    next();
                                    e.preventDefault();
                                    break;
                                case 38:
                                    prev();
                                    e.preventDefault();
                                    break;
                            }
                        };

                        var resetTimeout;

                        var resize = function(e) {
                            preventScroll = true;

                            setContainerHeight();

                            scrollToCurrent(true);

                            $timeout.cancel(resetTimeout);
                            resetTimeout = $timeout(function() {
                                preventScroll = false;
                            }, defaults.scrollSpeed);
                        };

                        var resizeEvent = 'onorientationchange' in $window ? 'orientationchange' : 'resize';

                        angular.element($window).on(resizeEvent, resize);

                        if (defaults.container === 'window') {
                            angular.element($window).on('scroll', scroll);
                        } else {
                            element.on('scroll', scroll);
                        }

                        $document.on('keydown', keyDown);

                        scope.$on('$destroy', function() {
                            angular.element($window).off(resizeEvent, resize);

                            if (defaults.container === 'window') {
                                angular.element($window).off('scroll', scroll);
                            } else {
                                element.off('scroll', scroll);
                            }

                            $document.off('keydown', keyDown);
                        });

                    };
                }
            };
        }
    ]);

})();
<|endoftext|>


<commit_before><commit_msg>Add message<commit_after><|endoftext|>


<commit_before>
const getContent = require('../lib/spider');
const cheerio = require('cheerio');
const toMarkdown = require('to-markdown');
const fs = require('fs');
const path = require('path');

exports.getGitDaily = function* () {
  var Cookie = 'SUHB=0Ryr9-FWsUMJP2; _T_WM=9999d45b15f34759ddbf410f183a9f6d; gsid_CTandWM=4uJa57b01vfgNxMxTcf5no2xv89; H5_INDEX_TITLE=; SUB=_2A257KP4GDeTxGeNJ6VsX8ynJyzmIHXVY0oJOrDV6PUJbrdANLXHukW2LVdDUCw8IUSpLWLuyLtRBDvZaMg..';

  var url = 'http://weibo.cn/GitHubDaily';
  var content = yield getContent(url, Cookie);
  var dir = path.dirname(__dirname)
  var file = path.join(dir, 'log/weibo.txt');
  var $ = cheerio.load(content);
  var title = $('.c .ctt').first().html();
  var titleStr = title.substring(0, 10);
  var fileContent = fs.readFileSync(file, 'utf-8');
  if (titleStr !== fileContent) {
    fs.writeFileSync(file, titleStr);
    var html = $('.c').first().html();
    return {
      text: title,
      desp: toMarkdown(html)
    };
  }
  return '';
};
<commit_msg>update Cookie<commit_after>
const getContent = require('../lib/spider');
const cheerio = require('cheerio');
const toMarkdown = require('to-markdown');
const fs = require('fs');
const path = require('path');

exports.getGitDaily = function* () {
  var Cookie = 'SUHB=0Ryr9-FWsUMJP2; _T_WM=9999d45b15f34759ddbf410f183a9f6d; SUB=_2A257MPX8DeTxGeNJ6VsX8ynJyzmIHXVY2pu0rDV6PUJbrdANLVbjkW2YdM5mLj4JVbZdnGJ-ayw9GDRZBw..; gsid_CTandWM=4urrd0fb1tseAAFLGnxU8o2xv89';

  var url = 'http://weibo.cn/GitHubDaily';
  var content = yield getContent(url, Cookie);
  var dir = path.dirname(__dirname)
  var file = path.join(dir, 'log/weibo.txt');
  var $ = cheerio.load(content);
  var title = $('.c .ctt').first().html();
  if (!title) {
    return {
      text: 'Cookie失效',
      desp: '请更新Cookie'
    };
  }
  var titleStr = title.substring(0, 10);
  var fileContent = fs.readFileSync(file, 'utf-8');
  if (titleStr !== fileContent) {
    fs.writeFileSync(file, titleStr);
    var html = $('.c').first().html();
    return {
      text: title,
      desp: toMarkdown(html)
    };
  }
  return '';
};
<|endoftext|>


<commit_before>'use strict';
/**
 * Created by Adrian on 03-Apr-16.
 * The Thorin Intent class is used to defined incoming events or actions.
 * Transports will then match intents based on their actions and
 * validate them.
 */
module.exports = function init(thorin) {
  const input = Symbol(),
    rawInput = Symbol(),
    data = Symbol(),
    client = Symbol(),
    error = Symbol(),
    alias = Symbol(),
    authorization = Symbol(),
    authorizationSource = Symbol(),
    onSend = Symbol(),
    metaData = Symbol(),
    rawResult = Symbol(),
    timeStart = Symbol(),
    timeEnd = Symbol(),
    events = Symbol(),
    eventsFired = Symbol(),
    resultHeaders = Symbol(),
    result = Symbol();

  const INTENT_EVENTS = {
    END: 'end'
  };

  class ThorinIntent {

    constructor(actionType, rawInputData, onIntentSend) {
      this.completed = false;
      // this.transport is set at the transport layer, the source transport
      this.proxied = false; // if the default onIntentSend callback was changed.
      this.action = actionType;
      this[rawInput] = rawInputData;
      this[rawResult] = false;  // raw results will not send an object, with {result:}, but the actual value of .result()
      this[events] = {};    // this is our mini event handler. with only .on('event', oneFunctionCall)
      this[eventsFired] = {}; // a hash of {eventName:true}
      this[timeStart] = Date.now();
      this[timeEnd] = null;
      this[data] = {};  // we can attach data to it.
      this[input] = {}; // input data
      this[result] = null;  // output data
      this[metaData] = null;  // pagination data
      this[error] = null;   // error info
      this[authorization] = null; // authorization data
      //this[authorizationSource] = null; // the authorization source. Where did it come from / what is it?
      this[client] = {};  // incoming client data.
      this[onSend] = onIntentSend;
      this[resultHeaders] = null; // a hash of headers to send back
    }

    /* Sets the authorization information for this intent.
     * The authorization information is set at the transport level, depending
     * on the source of it.
     * As an example, HTTP transports may place the Authorization: "Bearer {token}" header value
     * as the authorization data.
     * */
    _setAuthorization(authSource, authId) {
      if(typeof authSource !== 'string' || !authSource) {
        console.error('Thorin.Intent: _setAuthorization(source, id) must contain a valid authorization source');
        return this;
      }
      if(typeof authId === 'undefined') {
        console.error('Thorin.Intent: _setAuthorization(source, id) must have an authorization id.');
        return this;
      }
      this[authorization] = authId;
      this[authorizationSource] = authSource;
      return this;
    }

    get authorization() {
      return this[authorization];
    }
    set authorization(v) {
      console.error('Thorin.Intent: please use _setAuthorization(source, id) in stead of direct set.');
    }

    get alias() {
      return this[alias] || null;
    }
    set alias(v) {
      if(this[alias]) return;
      this[alias] = v;
    }

    get authorizationSource() {
      return this[authorizationSource] || null;
    }

    get rawInput() {
      return this[rawInput];
    }

    /*
     * An intent's send() function can be proxied, going through another callback
     * before actually calling the send() function. You can look at this like a
     * call that will be called before the send() function. This proxy function can alter
     * errors or results of the intent.
     * WARNING: When proxying an intent, you MUST call the send() of that intent, otherwise
     * it will not send the response to the client.
     * For example:
     *   intentObj.proxy(function(obj) {
     *     console.log("PROXY WITH", obj);
     *     this.send({something: "else"});
     *   });
     *
     *   /////
     *   intentObj.send({some: "object"})
     * */
    proxy(proxyFn) {
      if(this.proxied) {
        console.warn('Thorin.Intent: intent for action ' + this.action + ' is already proxied.');
        return this;
      }
      this.proxied = true;
      let oldSend = this[onSend];
      this[onSend] = function ProxySend() {
        this[onSend] = oldSend;
        try {
          proxyFn.apply(this, arguments);
        } catch(e) {
          console.error('Thorin.intent: proxy function on intent action ' + this.action + ' threw an error.');
          this.error(thorin.error(e));
          this.send();
        }
      }.bind(this);
      this.proxied = true;
      return this;
    }

    /*
     * When an intent is triggered by the dispatcher, this function gets called asynchronously,
     * in order to asynchronously initiate the intent.
     * A plugin or anybody can override this function to insert itself
     * into it, BEFORE it is actually run through its stack.
     * NOTE:
     *   when overriding it, the function MUST call the callback function with (err|null)
     * */
    runCreate(fn) {
      fn();
    }

    /*
     * This will set some specific headers to be sent to the transport layer.
     * */
    resultHeaders(name, _val) {
      if(typeof name === 'object' && name) {
        if(typeof this[resultHeaders] !== 'object' || this[resultHeaders] == null) this[resultHeaders] = {};
        Object.keys(name).forEach((key) => {
          this[resultHeaders][key] = name[key];
        });
        return this;
      }
      if (typeof name === 'undefined') return this[resultHeaders];
      if (typeof name === 'string' && typeof _val === 'undefined') {
        return this[resultHeaders][name] || null;
      }
      if (typeof name === 'string' && typeof _val !== 'undefined') {
        if(typeof this[resultHeaders] !== 'object' || !this[resultHeaders]) this[resultHeaders] = {};
        this[resultHeaders][name] = _val;
        return this;
      }
      return null;
    }

    /* Event handler ON: will call the function only once. */
    on(eventName, fn) {
      if (typeof fn !== 'function') {
        console.error('Thorin.intent: .on(eventName, fn), fn is not a function.');
        return this;
      }
      if (this[eventsFired][eventName]) {
        try {
          fn();
        } catch (e) {
          console.error('Thorin.Intent: triggered event ' + eventName + ' on callback function threw an exception.');
          console.error(e);
        }
        return this;
      }
      if (typeof this[events][eventName] === 'undefined') this[events][eventName] = [];
      this[events][eventName].push(fn);
      return this;
    }

    /*
     * Sets/gets specific client information.
     * */
    client(key, val) {
      if (typeof key === 'object' && key) {
        Object.keys(key).forEach((a) => {
          this.client(a, key[a]);
        });
        return this;
      }
      if (typeof key === 'undefined') return this[client];
      if (typeof key === 'string' && typeof val === 'undefined') return (typeof this[client][key] === 'undefined' ? null : this[client][key]);
      this[client][key] = val;
      return this;
    }

    /*
     * Get/Set additional data to it.
     * */
    data(name, _val) {
      if (typeof name !== 'string') return this[data];
      if (typeof name === 'string' && typeof _val === 'undefined') return (typeof this[data][name] === 'undefined' ? null : this[data][name]);
      this[data][name] = _val;
      return this;
    }

    /*
     * Getter/setter for input data.
     * */
    input(name, _val) {
      if (typeof name === 'undefined') return this[input];
      if (typeof name === 'string' && typeof _val === 'undefined') {
        return (typeof this[input][name] === 'undefine' ? null : this[input][name]);
      }
      if (typeof name === 'string' && typeof _val !== 'undefined') {
        this[input][name] = _val;
        try {
          this[rawInput][name] = _val;
        } catch(e) {}
        return this;
      }
      return null;
    }

    /* Checks if the intent is an error yet. */
    hasError() {
      return (this[error] != null);
    }

    /* Checks if we have any result */
    hasResult() {
      return (this[result] != null);
    }

    hasRawResult() {
      return (this[rawResult] === true);
    }

    /*
     * Sets the raw result of the intent. Raw results will send the result as is,
     * without wrapping it into an object.
     * */
    rawResult(val) {
      if(typeof val === 'undefined') return;
      this[rawResult] = true;
      this[result] = val;
      return this;
    }

    /*
     * Get/set result information
     * */
    result(name, val) {
      if(typeof name === 'string' && typeof val === 'undefined') {
        return this[result][name] || null;
      }
      if(name instanceof Array) {
        this[result] = name;
        return this;
      }
      if (typeof name === 'object' && name) {
        if(typeof name.getDataValue === 'function') { // ignore objects that have toJSON, as the transport will take care of it.
          this[result] = name;
          return this;
        }
        if(typeof name.toJSON === 'function') {
          this[result] = name.toJSON();
        } else {
          this[result] = {};
          Object.keys(name).forEach((key) => {
            this.result(key, name[key]);
          });
        }
        return this;
      }
      if (typeof name === 'string' && !(this[result] instanceof Array)) {
        if (typeof val === 'undefined') {
          return (typeof this[result][name] === 'undefined' ? null : this[result][name]);
        }
        if (this[result] == null) this[result] = {};
        this[result][name] = val;
        return this;
      }
      if(typeof name === 'undefined') {
        return this[result];
      }
      return this;
    }

    /*
     * Sets pagination data or other information that will be used in the root object.. The pagination data will be included in the base response,
     * right next to id and result.
     * TODO: this should work with a thorin.Pagination
     * */
    setMeta(data, val) {
      if (typeof data === 'object') {
        Object.keys(data).forEach((k) => {
          this.setMeta(k, data[k]);
        });
        return this;
      }
      if (typeof data === 'string') {
        if (typeof val === 'undefined') {
          return (typeof this[metaData][data] === 'undefined' ? null : this[metaData][data]);
        }
        if (this[metaData] == null) this[metaData] = {};
        this[metaData][data] = val;
        return this;
      }
      return this;
    };

    /*
     * Marks the intent as error. We can set the error only once.
     * */
    error(err) {
      if (typeof err === 'undefined') return this[error];
      if (this[error]) return this;
      if (err instanceof Error && err.name.indexOf('Thorin') !== 0) {
        err = thorin.error(err);
      }
      this[error] = err;
      return this;
    }

    /*
     * Returns the number of ms it took for the intent to end.
     * */
    get took() {
      if (!this[timeEnd]) return 0;
      return this[timeEnd] - this[timeStart];
    }

    /*
     * Ends the current intent, responding with the result back to the transport layer.
     * */
    send(obj) {
      if(this.proxied) {
        this.proxied = false;
        this[onSend].apply(this, arguments);  // proxy the send.
        return;
      }
      if (this.completed) {
        console.error('Thorin.intent: already completed for action ' + this.action);
        return this;
      }
      if (!this[rawResult] && typeof obj === 'object' && obj != null) {
        if (obj instanceof Error && !this.hasError()) {
          this.error(obj);
        } else if (!this.hasResult()) {
          this.result(obj);
        }
      }
      this[timeEnd] = Date.now();
      this.completed = true;
      const intentResult = {
        "type": this.action
      };
      if (this.hasError()) {
        intentResult.error = this.error();
        triggerEvent.call(this, INTENT_EVENTS.END);
        if (typeof this[onSend] === 'function') {
          this[onSend](true, intentResult, this);
          this.destroy();
        } else {
          this.destroy();
          return intentResult;
        }
      } else {
        // IF we have a raw result, we just send it.
        if(this[rawResult] === true) {
          triggerEvent.call(this, INTENT_EVENTS.END);
          if (typeof this[onSend] === 'function') {
            this[onSend](false, this[result], this);
            this.destroy();
          } else {
            this.destroy();
            return this[result];
          }
          return;
        }

        // set any pagination data.
        if (this[metaData] != null) {
          intentResult.meta = {};
          Object.keys(this[metaData]).forEach((k) => {
            if(k === 'result' || k === "action") return;
            if (typeof intentResult[k] !== 'undefined') return;
            intentResult.meta[k] = this[metaData][k];
          });
        }
        if (this.hasResult()) {
          intentResult.result = this.result();
        }
        triggerEvent.call(this, INTENT_EVENTS.END);
        if (typeof this[onSend] === 'function') {
          this[onSend](false, intentResult, this);
          this.destroy();
        } else {
          this.destroy();
          return intentResult;
        }
      }
    }

    /*
     * Intent destructor
     * */
    destroy() {
      this[data] = null;
      this[rawInput] = null;
      this[input] = null;
      this[result] = null;
      this[error] = null;
      this[authorization] = null;
      this[client] = null;
      this[metaData] = null;
      this[events] = {};
      this[eventsFired] = {};
      delete this[resultHeaders];
      delete this[onSend];
    }
  }
  ThorinIntent.EVENT = INTENT_EVENTS;

  /*
   * This is called by the intent to trigger an event
   * Event triggering is only for the intent to fire.
   * */
  function triggerEvent(eventName, _args) {
    if (typeof this[events][eventName] === 'undefined') return;
    for (let i = 0; i < this[events][eventName].length; i++) {
      try {
        this[events][eventName][i](_args);
      } catch (e) {
        console.error('Thorin.Intent: triggered event ' + eventName + ' on callback function threw an exception.');
        console.error(e);
      }
    }
    this[eventsFired][eventName] = true;
    delete this[events][eventName];
  }

  return ThorinIntent;
};<commit_msg>raw input setter is now a "private" variable and to be used only when ABSOULTELY required<commit_after>'use strict';
/**
 * Created by Adrian on 03-Apr-16.
 * The Thorin Intent class is used to defined incoming events or actions.
 * Transports will then match intents based on their actions and
 * validate them.
 */
module.exports = function init(thorin) {
  const input = Symbol(),
    rawInput = Symbol(),
    data = Symbol(),
    client = Symbol(),
    error = Symbol(),
    alias = Symbol(),
    authorization = Symbol(),
    authorizationSource = Symbol(),
    onSend = Symbol(),
    metaData = Symbol(),
    rawResult = Symbol(),
    timeStart = Symbol(),
    timeEnd = Symbol(),
    events = Symbol(),
    eventsFired = Symbol(),
    resultHeaders = Symbol(),
    result = Symbol();

  const INTENT_EVENTS = {
    END: 'end'
  };

  class ThorinIntent {

    constructor(actionType, rawInputData, onIntentSend) {
      this.completed = false;
      // this.transport is set at the transport layer, the source transport
      this.proxied = false; // if the default onIntentSend callback was changed.
      this.action = actionType;
      this[rawInput] = rawInputData;
      this[rawResult] = false;  // raw results will not send an object, with {result:}, but the actual value of .result()
      this[events] = {};    // this is our mini event handler. with only .on('event', oneFunctionCall)
      this[eventsFired] = {}; // a hash of {eventName:true}
      this[timeStart] = Date.now();
      this[timeEnd] = null;
      this[data] = {};  // we can attach data to it.
      this[input] = {}; // input data
      this[result] = null;  // output data
      this[metaData] = null;  // pagination data
      this[error] = null;   // error info
      this[authorization] = null; // authorization data
      //this[authorizationSource] = null; // the authorization source. Where did it come from / what is it?
      this[client] = {};  // incoming client data.
      this[onSend] = onIntentSend;
      this[resultHeaders] = null; // a hash of headers to send back
    }

    /* Sets the authorization information for this intent.
     * The authorization information is set at the transport level, depending
     * on the source of it.
     * As an example, HTTP transports may place the Authorization: "Bearer {token}" header value
     * as the authorization data.
     * */
    _setAuthorization(authSource, authId) {
      if(typeof authSource !== 'string' || !authSource) {
        console.error('Thorin.Intent: _setAuthorization(source, id) must contain a valid authorization source');
        return this;
      }
      if(typeof authId === 'undefined') {
        console.error('Thorin.Intent: _setAuthorization(source, id) must have an authorization id.');
        return this;
      }
      this[authorization] = authId;
      this[authorizationSource] = authSource;
      return this;
    }

    get authorization() {
      return this[authorization];
    }
    set authorization(v) {
      console.error('Thorin.Intent: please use _setAuthorization(source, id) in stead of direct set.');
    }

    get alias() {
      return this[alias] || null;
    }
    set alias(v) {
      if(this[alias]) return;
      this[alias] = v;
    }

    get authorizationSource() {
      return this[authorizationSource] || null;
    }

    get rawInput() {
      return this[rawInput];
    }
    __setRawInput(v) {
      if(typeof v !== 'object' || !v) return this;
      this[rawInput] = v;
      return this;
    }

    /*
     * An intent's send() function can be proxied, going through another callback
     * before actually calling the send() function. You can look at this like a
     * call that will be called before the send() function. This proxy function can alter
     * errors or results of the intent.
     * WARNING: When proxying an intent, you MUST call the send() of that intent, otherwise
     * it will not send the response to the client.
     * For example:
     *   intentObj.proxy(function(obj) {
     *     console.log("PROXY WITH", obj);
     *     this.send({something: "else"});
     *   });
     *
     *   /////
     *   intentObj.send({some: "object"})
     * */
    proxy(proxyFn) {
      if(this.proxied) {
        console.warn('Thorin.Intent: intent for action ' + this.action + ' is already proxied.');
        return this;
      }
      this.proxied = true;
      let oldSend = this[onSend];
      this[onSend] = function ProxySend() {
        this[onSend] = oldSend;
        try {
          proxyFn.apply(this, arguments);
        } catch(e) {
          console.error('Thorin.intent: proxy function on intent action ' + this.action + ' threw an error.');
          this.error(thorin.error(e));
          this.send();
        }
      }.bind(this);
      this.proxied = true;
      return this;
    }

    /*
     * When an intent is triggered by the dispatcher, this function gets called asynchronously,
     * in order to asynchronously initiate the intent.
     * A plugin or anybody can override this function to insert itself
     * into it, BEFORE it is actually run through its stack.
     * NOTE:
     *   when overriding it, the function MUST call the callback function with (err|null)
     * */
    runCreate(fn) {
      fn();
    }

    /*
     * This will set some specific headers to be sent to the transport layer.
     * */
    resultHeaders(name, _val) {
      if(typeof name === 'object' && name) {
        if(typeof this[resultHeaders] !== 'object' || this[resultHeaders] == null) this[resultHeaders] = {};
        Object.keys(name).forEach((key) => {
          this[resultHeaders][key] = name[key];
        });
        return this;
      }
      if (typeof name === 'undefined') return this[resultHeaders];
      if (typeof name === 'string' && typeof _val === 'undefined') {
        return this[resultHeaders][name] || null;
      }
      if (typeof name === 'string' && typeof _val !== 'undefined') {
        if(typeof this[resultHeaders] !== 'object' || !this[resultHeaders]) this[resultHeaders] = {};
        this[resultHeaders][name] = _val;
        return this;
      }
      return null;
    }

    /* Event handler ON: will call the function only once. */
    on(eventName, fn) {
      if (typeof fn !== 'function') {
        console.error('Thorin.intent: .on(eventName, fn), fn is not a function.');
        return this;
      }
      if (this[eventsFired][eventName]) {
        try {
          fn();
        } catch (e) {
          console.error('Thorin.Intent: triggered event ' + eventName + ' on callback function threw an exception.');
          console.error(e);
        }
        return this;
      }
      if (typeof this[events][eventName] === 'undefined') this[events][eventName] = [];
      this[events][eventName].push(fn);
      return this;
    }

    /*
     * Sets/gets specific client information.
     * */
    client(key, val) {
      if (typeof key === 'object' && key) {
        Object.keys(key).forEach((a) => {
          this.client(a, key[a]);
        });
        return this;
      }
      if (typeof key === 'undefined') return this[client];
      if (typeof key === 'string' && typeof val === 'undefined') return (typeof this[client][key] === 'undefined' ? null : this[client][key]);
      this[client][key] = val;
      return this;
    }

    /*
     * Get/Set additional data to it.
     * */
    data(name, _val) {
      if (typeof name !== 'string') return this[data];
      if (typeof name === 'string' && typeof _val === 'undefined') return (typeof this[data][name] === 'undefined' ? null : this[data][name]);
      this[data][name] = _val;
      return this;
    }

    /*
     * Getter/setter for input data.
     * */
    input(name, _val) {
      if (typeof name === 'undefined') return this[input];
      if (typeof name === 'string' && typeof _val === 'undefined') {
        return (typeof this[input][name] === 'undefine' ? null : this[input][name]);
      }
      if (typeof name === 'string' && typeof _val !== 'undefined') {
        this[input][name] = _val;
        try {
          this[rawInput][name] = _val;
        } catch(e) {}
        return this;
      }
      return null;
    }

    /* Checks if the intent is an error yet. */
    hasError() {
      return (this[error] != null);
    }

    /* Checks if we have any result */
    hasResult() {
      return (this[result] != null);
    }

    hasRawResult() {
      return (this[rawResult] === true);
    }

    /*
     * Sets the raw result of the intent. Raw results will send the result as is,
     * without wrapping it into an object.
     * */
    rawResult(val) {
      if(typeof val === 'undefined') return;
      this[rawResult] = true;
      this[result] = val;
      return this;
    }

    /*
     * Get/set result information
     * */
    result(name, val) {
      if(typeof name === 'string' && typeof val === 'undefined') {
        return this[result][name] || null;
      }
      if(name instanceof Array) {
        this[result] = name;
        return this;
      }
      if (typeof name === 'object' && name) {
        if(typeof name.getDataValue === 'function') { // ignore objects that have toJSON, as the transport will take care of it.
          this[result] = name;
          return this;
        }
        if(typeof name.toJSON === 'function') {
          this[result] = name.toJSON();
        } else {
          this[result] = {};
          Object.keys(name).forEach((key) => {
            this.result(key, name[key]);
          });
        }
        return this;
      }
      if (typeof name === 'string' && !(this[result] instanceof Array)) {
        if (typeof val === 'undefined') {
          return (typeof this[result][name] === 'undefined' ? null : this[result][name]);
        }
        if (this[result] == null) this[result] = {};
        this[result][name] = val;
        return this;
      }
      if(typeof name === 'undefined') {
        return this[result];
      }
      return this;
    }

    /*
     * Sets pagination data or other information that will be used in the root object.. The pagination data will be included in the base response,
     * right next to id and result.
     * TODO: this should work with a thorin.Pagination
     * */
    setMeta(data, val) {
      if (typeof data === 'object') {
        Object.keys(data).forEach((k) => {
          this.setMeta(k, data[k]);
        });
        return this;
      }
      if (typeof data === 'string') {
        if (typeof val === 'undefined') {
          return (typeof this[metaData][data] === 'undefined' ? null : this[metaData][data]);
        }
        if (this[metaData] == null) this[metaData] = {};
        this[metaData][data] = val;
        return this;
      }
      return this;
    };

    /*
     * Marks the intent as error. We can set the error only once.
     * */
    error(err) {
      if (typeof err === 'undefined') return this[error];
      if (this[error]) return this;
      if (err instanceof Error && err.name.indexOf('Thorin') !== 0) {
        err = thorin.error(err);
      }
      this[error] = err;
      return this;
    }

    /*
     * Returns the number of ms it took for the intent to end.
     * */
    get took() {
      if (!this[timeEnd]) return 0;
      return this[timeEnd] - this[timeStart];
    }

    /*
     * Ends the current intent, responding with the result back to the transport layer.
     * */
    send(obj) {
      if(this.proxied) {
        this.proxied = false;
        this[onSend].apply(this, arguments);  // proxy the send.
        return;
      }
      if (this.completed) {
        console.error('Thorin.intent: already completed for action ' + this.action);
        return this;
      }
      if (!this[rawResult] && typeof obj === 'object' && obj != null) {
        if (obj instanceof Error && !this.hasError()) {
          this.error(obj);
        } else if (!this.hasResult()) {
          this.result(obj);
        }
      }
      this[timeEnd] = Date.now();
      this.completed = true;
      const intentResult = {
        "type": this.action
      };
      if (this.hasError()) {
        intentResult.error = this.error();
        triggerEvent.call(this, INTENT_EVENTS.END);
        if (typeof this[onSend] === 'function') {
          this[onSend](true, intentResult, this);
          this.destroy();
        } else {
          this.destroy();
          return intentResult;
        }
      } else {
        // IF we have a raw result, we just send it.
        if(this[rawResult] === true) {
          triggerEvent.call(this, INTENT_EVENTS.END);
          if (typeof this[onSend] === 'function') {
            this[onSend](false, this[result], this);
            this.destroy();
          } else {
            this.destroy();
            return this[result];
          }
          return;
        }

        // set any pagination data.
        if (this[metaData] != null) {
          intentResult.meta = {};
          Object.keys(this[metaData]).forEach((k) => {
            if(k === 'result' || k === "action") return;
            if (typeof intentResult[k] !== 'undefined') return;
            intentResult.meta[k] = this[metaData][k];
          });
        }
        if (this.hasResult()) {
          intentResult.result = this.result();
        }
        triggerEvent.call(this, INTENT_EVENTS.END);
        if (typeof this[onSend] === 'function') {
          this[onSend](false, intentResult, this);
          this.destroy();
        } else {
          this.destroy();
          return intentResult;
        }
      }
    }

    /*
     * Intent destructor
     * */
    destroy() {
      this[data] = null;
      this[rawInput] = null;
      this[input] = null;
      this[result] = null;
      this[error] = null;
      this[authorization] = null;
      this[client] = null;
      this[metaData] = null;
      this[events] = {};
      this[eventsFired] = {};
      delete this[resultHeaders];
      delete this[onSend];
    }
  }
  ThorinIntent.EVENT = INTENT_EVENTS;

  /*
   * This is called by the intent to trigger an event
   * Event triggering is only for the intent to fire.
   * */
  function triggerEvent(eventName, _args) {
    if (typeof this[events][eventName] === 'undefined') return;
    for (let i = 0; i < this[events][eventName].length; i++) {
      try {
        this[events][eventName][i](_args);
      } catch (e) {
        console.error('Thorin.Intent: triggered event ' + eventName + ' on callback function threw an exception.');
        console.error(e);
      }
    }
    this[eventsFired][eventName] = true;
    delete this[events][eventName];
  }

  return ThorinIntent;
};<|endoftext|>