From a69e452ecf5cf24d6611d94e8edca1a850ed0a13 Mon Sep 17 00:00:00 2001 From: Simon Ho Date: Wed, 12 Oct 2016 19:33:20 -0700 Subject: [PATCH] Use eslint in favour of JSHint (#129) (#130) * Add ESLint and remove JSHint/JSCS * Add linting fixes via ESLint's fix command * Use ESLint in favour of JSHint Backport of #129 --- .eslintrc | 3 + .jscsrc | 13 -- .jshintignore | 3 - .jshintrc | 11 -- Gruntfile.js | 14 +- index.js | 6 +- lib/payload.js | 216 ++++++++++++++------------- lib/providers/apns.js | 9 +- lib/providers/gcm.js | 18 ++- lib/providers/index.js | 2 + lib/push-connector.js | 30 ++-- lib/push-manager.js | 147 +++++++++--------- models/index.js | 2 + models/installation.js | 27 ++-- models/notification.js | 3 +- package.json | 9 +- test/apns.provider.test.js | 65 ++++---- test/common.js | 4 +- test/device-registration.test.js | 90 +++++------ test/gcm.provider.test.js | 67 +++++---- test/helpers/mockery/apns.mockery.js | 3 +- test/helpers/mockery/gcm.mockery.js | 4 +- test/helpers/mockery/index.js | 2 + test/helpers/mockery/stub.mockery.js | 3 +- test/helpers/object-mother.js | 4 +- test/helpers/test-data-builder.js | 3 +- test/push-manager.test.js | 127 ++++++++-------- test/push-notification.test.js | 120 ++++++++------- 28 files changed, 520 insertions(+), 485 deletions(-) create mode 100644 .eslintrc delete mode 100644 .jscsrc delete mode 100644 .jshintignore delete mode 100644 .jshintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..70bcff8 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "loopback" +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index dd20d5f..0000000 --- a/.jscsrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "excludeFiles": ["example", "node_modules", "doc"], - "maximumLineLength": { - "value": 120, - "allowComments": true, - "allowRegex": true - }, - "validateJSDoc": { - "checkParamNames": true, - "checkRedundantParams": true, - "requireParamTypes": true - } -} diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 7ad1e0b..0000000 --- a/.jshintignore +++ /dev/null @@ -1,3 +0,0 @@ -/example/ -/node_modules/ -/doc/ diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 2721fe0..0000000 --- a/.jshintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ -"node": true, -"eqnull" : true, -"indent": 2, -"undef": true, -"newcap": true, -"nonew": true, -"sub": true, -"laxcomma": true, -"laxbreak": true -} diff --git a/Gruntfile.js b/Gruntfile.js index b24daa3..6f91bd1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,20 +3,22 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + module.exports = function(grunt) { grunt.initConfig({ - jsdoc : { - dist : { + jsdoc: { + dist: { src: ['lib/*.js', 'models/installation.js', 'models/notification.js'], options: { - destination: 'doc' - } - } + destination: 'doc', + }, + }, }, watch: { files: ['lib/*.js', 'models/*.js'], tasks: ['jsdoc'], - } + }, }); grunt.loadNpmTasks('grunt-jsdoc'); diff --git a/index.js b/index.js index 2babbda..94befd9 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var SG = require('strong-globalize'); SG.SetRootDir(__dirname); @@ -19,7 +21,7 @@ exports = module.exports = PushConnector; exports.Installation = require('./models').Installation; exports.Notification = require('./models').Notification; -exports.createPushModel = function (options) { +exports.createPushModel = function(options) { options = options || {}; var pushDataSource = loopback.createDataSource({ @@ -28,7 +30,7 @@ exports.createPushModel = function (options) { application: options.application, notification: options.notification, ttlInSeconds: options.ttlInSeconds, - checkPeriodInSeconds: options.checkPeriodInSeconds + checkPeriodInSeconds: options.checkPeriodInSeconds, }); var PushModel = pushDataSource.createModel(options.name || 'Push', {}, diff --git a/lib/payload.js b/lib/payload.js index 641dabe..05e9024 100644 --- a/lib/payload.js +++ b/lib/payload.js @@ -3,136 +3,140 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 -var g = require('strong-globalize')(); +'use strict'; -// this file has no tests so avoid refactor and ignore jshint for now -/*jshint ignore: start */ -var serial,__hasProp = {}.hasOwnProperty; +var g = require('strong-globalize')(); -serial = 0; +var serial = 0; +var __hasProp = {}.hasOwnProperty; +// eslint-disable-next-line camelcase Payload.prototype.locale_format = /^[a-z]{2}_[A-Z]{2}$/; function Payload(data) { - var key, prefix, subkey, sum, type, value, _i, _len, _ref, _ref1; - if (typeof data !== 'object') { - throw new Error(g.f('Invalid payload')); + var key, prefix, subkey, sum, type, value, _i, _len, _ref, _ref1; + if (typeof data !== 'object') { + throw new Error(g.f('Invalid payload')); + } + this.id = serial++; + this.compiled = false; + this.title = {}; + this.msg = {}; + this.data = {}; + this['var'] = {}; + for (key in data) { + if (!__hasProp.call(data, key)) continue; + value = data[key]; + if (typeof key !== 'string' || key.length === 0) { + throw new Error(g.f('Invalid field (empty)')); + } + if (typeof value !== 'string') { + throw new Error(g.f('Invalid value for `%s`', key)); } - this.id = serial++; - this.compiled = false; - this.title = {}; - this.msg = {}; - this.data = {}; - this["var"] = {}; - for (key in data) { - if (!__hasProp.call(data, key)) continue; - value = data[key]; - if (typeof key !== 'string' || key.length === 0) { - throw new Error(g.f("Invalid field (empty)")); - } - if (typeof value !== 'string') { - throw new Error(g.f("Invalid value for `%s`", key)); - } - switch (key) { - case 'title': - this.title["default"] = value; - break; - case 'msg': - this.msg["default"] = value; - break; - case 'sound': - this.sound = value; - break; - default: - if ((_ref = key.split('.', 2), prefix = _ref[0], subkey = _ref[1], _ref).length === 2) { - this[prefix][subkey] = value; - } else { - throw new Error(g.f("Invalid field: %s", key)); - } + switch (key) { + case 'title': + this.title['default'] = value; + break; + case 'msg': + this.msg['default'] = value; + break; + case 'sound': + this.sound = value; + break; + default: + if ( + (_ref = key.split('.', 2), prefix = _ref[0], subkey = _ref[1], _ref) + .length === 2) { + this[prefix][subkey] = value; + } else { + throw new Error(g.f('Invalid field: %s', key)); } } - sum = 0; - _ref1 = ['title', 'msg', 'data']; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - type = _ref1[_i]; - sum += ((function () { - var _ref2, _results; - _ref2 = this[type]; - _results = []; - for (key in _ref2) { - if (!__hasProp.call(_ref2, key)) continue; - _results.push(key); - } - return _results; - }).call(this)).length; - } - if (sum === 0) { - throw new Error(g.f('Empty payload')); - } + } + sum = 0; + _ref1 = ['title', 'msg', 'data']; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + type = _ref1[_i]; + sum += ((function() { + var _ref2, _results; + _ref2 = this[type]; + _results = []; + for (key in _ref2) { + if (!__hasProp.call(_ref2, key)) continue; + _results.push(key); + } + return _results; + }).call(this)).length; + } + if (sum === 0) { + throw new Error(g.f('Empty payload')); + } } -Payload.prototype.localizedTitle = function (lang) { - return this.localized('title', lang); +Payload.prototype.localizedTitle = function(lang) { + return this.localized('title', lang); }; -Payload.prototype.localizedMessage = function (lang) { - return this.localized('msg', lang); +Payload.prototype.localizedMessage = function(lang) { + return this.localized('msg', lang); }; -Payload.prototype.localized = function (type, lang) { - if (!this.compiled) { - this.compile(); - } - if (this[type][lang] != null) { - return this[type][lang]; - } else if (Payload.prototype.locale_format.test(lang) && (this[type][lang.slice(0, 2)] != null)) { - return this[type][lang.slice(0, 2)]; - } else if (this[type]["default"]) { - return this[type]["default"]; - } +Payload.prototype.localized = function(type, lang) { + if (!this.compiled) { + this.compile(); + } + if (this[type][lang] != null) { + return this[type][lang]; + } else if (Payload.prototype.locale_format.test(lang) && + (this[type][lang.slice(0, 2)] != null)) { + return this[type][lang.slice(0, 2)]; + } else if (this[type]['default']) { + return this[type]['default']; + } }; -Payload.prototype.compile = function () { - var lang, msg, type, _i, _len, _ref, _ref1; - _ref = ['title', 'msg']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - _ref1 = this[type]; - for (lang in _ref1) { - if (!__hasProp.call(_ref1, lang)) continue; - msg = _ref1[lang]; - this[type][lang] = this.compileTemplate(msg); - } +Payload.prototype.compile = function() { + var lang, msg, type, _i, _len, _ref, _ref1; + _ref = ['title', 'msg']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + _ref1 = this[type]; + for (lang in _ref1) { + if (!__hasProp.call(_ref1, lang)) continue; + msg = _ref1[lang]; + this[type][lang] = this.compileTemplate(msg); } - return this.compiled = true; + } + return this.compiled = true; }; -Payload.prototype.compileTemplate = function (tmpl) { - var _this = this; - return tmpl.replace(/\$\{(.*?)\}/g, function (match, keyPath) { - return _this.variable(keyPath); - }); +Payload.prototype.compileTemplate = function(tmpl) { + var _this = this; + return tmpl.replace(/\$\{(.*?)\}/g, function(match, keyPath) { + return _this.variable(keyPath); + }); }; -Payload.prototype.variable = function (keyPath) { - var key, prefix, _ref, _ref1, _ref2; - if (keyPath === 'event.name') { - if ((_ref = this.event) != null ? _ref.name : undefined) { - return (_ref1 = this.event) != null ? _ref1.name : undefined; - } else { - throw new Error(g.f("The ${%s} does not exist", keyPath)); - } - } - _ref2 = keyPath.split('.', 2), prefix = _ref2[0], key = _ref2[1]; - if (prefix !== 'var' && prefix !== 'data') { - throw new Error(g.f("Invalid variable type for ${%s}", keyPath)); +Payload.prototype.variable = function(keyPath) { + var key, prefix, _ref, _ref1, _ref2; + if (keyPath === 'event.name') { + if ((_ref = this.event) != null ? _ref.name : undefined) { + return (_ref1 = this.event) != null ? _ref1.name : undefined; + } else { + throw new Error(g.f('The ${%s} does not exist', keyPath)); } - if (this[prefix][key] == null) { - throw new Error(g.f("The ${%s} does not exist", keyPath)); - } - return this[prefix][key]; + } + _ref2 = keyPath.split('.', 2); + prefix = _ref2[0]; + key = _ref2[1]; + if (prefix !== 'var' && prefix !== 'data') { + throw new Error(g.f('Invalid variable type for ${%s}', keyPath)); + } + if (this[prefix][key] == null) { + throw new Error(g.f('The ${%s} does not exist', keyPath)); + } + return this[prefix][key]; }; - exports.Payload = Payload; diff --git a/lib/providers/apns.js b/lib/providers/apns.js index 643de22..eab19d5 100644 --- a/lib/providers/apns.js +++ b/lib/providers/apns.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var g = require('strong-globalize')(); var inherits = require('util').inherits; @@ -40,14 +42,15 @@ function ApnsProvider(pushSettings) { if (feedbackOptions.port !== undefined) { feedbackOptions.port = 2196; } - } else { pushOptions.production = false; feedbackOptions.production = false; // Honor the gateway settings for testing - pushOptions.gateway = pushOptions.gateway || 'gateway.sandbox.push.apple.com'; - feedbackOptions.gateway = feedbackOptions.gateway || 'feedback.sandbox.push.apple.com'; + pushOptions.gateway = pushOptions.gateway || + 'gateway.sandbox.push.apple.com'; + feedbackOptions.gateway = feedbackOptions.gateway || + 'feedback.sandbox.push.apple.com'; } // Keep the options for testing verification diff --git a/lib/providers/gcm.js b/lib/providers/gcm.js index 5e7a630..fa93062 100644 --- a/lib/providers/gcm.js +++ b/lib/providers/gcm.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var g = require('strong-globalize')(); var inherits = require('util').inherits; @@ -28,13 +30,16 @@ GcmProvider.prototype._setupPushConnection = function(options) { GcmProvider.prototype.pushNotification = function(notification, deviceToken) { var self = this; - var registrationIds = (typeof deviceToken == 'string') ? [deviceToken] : deviceToken; + var registrationIds = (typeof deviceToken == 'string') ? + [deviceToken] : deviceToken; var message = this._createMessage(notification); debug('Sending message to %j: %j', registrationIds, message); - this._connection.send(message, registrationIds, 3, function (err, result) { + this._connection.send(message, registrationIds, 3, function(err, result) { if (!err && result && result.failure) { - var devicesGoneRegistrationIds = [], errors = [], code; + var devicesGoneRegistrationIds = []; + var errors = []; + var code; result.results.forEach(function(value, index) { code = value && value.error; if (code === 'NotRegistered' || code === 'InvalidRegistration') { @@ -71,15 +76,16 @@ GcmProvider.prototype._createMessage = function(notification) { var message = new gcm.Message({ timeToLive: notification.getTimeToLiveInSecondsFromNow(), collapseKey: notification.collapseKey, - delayWhileIdle: notification.delayWhileIdle + delayWhileIdle: notification.delayWhileIdle, }); var propNames = Object.keys(notification); // GCM does not have reserved message parameters for alert or badge, adding them as data. propNames.push('alert', 'badge'); - propNames.forEach(function (key) { - if (notification[key] !== null && typeof notification[key] !== 'undefined') { + propNames.forEach(function(key) { + if (notification[key] !== null && + typeof notification[key] !== 'undefined') { message.addData(key, notification[key]); } }); diff --git a/lib/providers/index.js b/lib/providers/index.js index 74c4971..4406d33 100644 --- a/lib/providers/index.js +++ b/lib/providers/index.js @@ -3,5 +3,7 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + exports.ApnsProvider = require('./apns'); exports.GcmProvider = require('./gcm'); diff --git a/lib/push-connector.js b/lib/push-connector.js index 798e262..a7b5e78 100644 --- a/lib/push-connector.js +++ b/lib/push-connector.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var loopback = require('loopback'); var PushManager = require('./push-manager'); /** @@ -10,22 +12,22 @@ var PushManager = require('./push-manager'); * @param {Object} dataSource Loopback dataSource (Memory, etc). * @param {Function} callback (unused) */ -exports.initialize = function (dataSource, callback) { - var settings = dataSource.settings || {}; +exports.initialize = function(dataSource, callback) { + var settings = dataSource.settings || {}; // Create an instance of the APNSManager - var connector = new PushManager(settings); - dataSource.connector = connector; - dataSource.connector.dataSource = dataSource; + var connector = new PushManager(settings); + dataSource.connector = connector; + dataSource.connector.dataSource = dataSource; - connector.DataAccessObject = function() {}; - for (var m in PushManager.prototype) { - var method = PushManager.prototype[m]; - if ('function' === typeof method) { - connector.DataAccessObject[m] = method.bind(connector); - for(var k in method) { - connector.DataAccessObject[m][k] = method[k]; - } - } + connector.DataAccessObject = function() {}; + for (var m in PushManager.prototype) { + var method = PushManager.prototype[m]; + if ('function' === typeof method) { + connector.DataAccessObject[m] = method.bind(connector); + for (var k in method) { + connector.DataAccessObject[m][k] = method[k]; + } } + } }; diff --git a/lib/push-manager.js b/lib/push-manager.js index 7aa3e4e..c585990 100644 --- a/lib/push-manager.js +++ b/lib/push-manager.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var g = require('strong-globalize')(); var assert = require('assert'); @@ -37,17 +39,20 @@ module.exports = PushManager; * @header PushManager(settings) */ function PushManager(settings) { - if (!(this instanceof PushManager)) { - return new PushManager(settings); - } + if (!(this instanceof PushManager)) { + return new PushManager(settings); + } - settings = settings || {}; + settings = settings || {}; - this.settings = settings; - this._defineDependencies(); - this.ttlInSeconds = this.settings.ttlInSeconds || 0; - this.checkPeriodInSeconds = this.settings.checkPeriodInSeconds || 0; - this.applicationsCache = new NodeCache({ stdTTL: this.ttlInSeconds, checkperiod: this.checkPeriodInSeconds }); + this.settings = settings; + this._defineDependencies(); + this.ttlInSeconds = this.settings.ttlInSeconds || 0; + this.checkPeriodInSeconds = this.settings.checkPeriodInSeconds || 0; + this.applicationsCache = new NodeCache({ + stdTTL: this.ttlInSeconds, + checkperiod: this.checkPeriodInSeconds, + }); } inherits(PushManager, EventEmitter); @@ -59,46 +64,47 @@ inherits(PushManager, EventEmitter); * * @private */ -PushManager.prototype._defineDependencies = function () { - +PushManager.prototype._defineDependencies = function() { Object.defineProperties(this, { Installation: { - get: function () { + get: function() { if (!this._Installation) { - this._Installation = loopback.findModel(this.settings.installation) || + this._Installation = + loopback.findModel(this.settings.installation) || loopback.getModelByType(Installation); } return this._Installation; }, - set: function (installation) { + set: function(installation) { this._Installation = installation; - } + }, }, Notification: { - get: function () { + get: function() { if (!this._Notification) { - this._Notification = loopback.findModel(this.settings.notification) || + this._Notification = + loopback.findModel(this.settings.notification) || loopback.getModelByType(Notification); } return this._Notification; }, - set: function (notification) { + set: function(notification) { this._Notification = notification; - } + }, }, Application: { - get: function () { + get: function() { if (!this._Application) { this._Application = loopback.findModel(this.settings.application) || loopback.getModelByType(loopback.Application); } return this._Application; }, - set: function (application) { + set: function(application) { this._Application = application; - } - } + }, + }, }); }; @@ -110,7 +116,7 @@ PushManager.prototype._defineDependencies = function () { */ PushManager.providers = { ios: providers.ApnsProvider, - android: providers.GcmProvider + android: providers.GcmProvider, }; /** @@ -121,7 +127,7 @@ PushManager.providers = { * @returns {Provider|null} A provider from PushManager.providers (GcmProvider, ApnsProvider) * matching the deviceType (android, ios) */ -PushManager.prototype.configureProvider = function (deviceType, pushSettings) { +PushManager.prototype.configureProvider = function(deviceType, pushSettings) { var Provider = PushManager.providers[deviceType]; if (!Provider) { return null; @@ -131,7 +137,7 @@ PushManager.prototype.configureProvider = function (deviceType, pushSettings) { provider.on('devicesGone', function(deviceTokens) { this.Installation.destroyAll({ deviceType: deviceType, - deviceToken: { inq: deviceTokens } + deviceToken: {inq: deviceTokens}, }); }.bind(this)); @@ -148,7 +154,7 @@ PushManager.prototype.configureProvider = function (deviceType, pushSettings) { * @param {String} deviceType The type of device (android, ios) * @param {function(Error=,Application)} cb Callback function called with `cb(err, obj)` signature. */ -PushManager.prototype.configureApplication = function (appId, deviceType, cb) { +PushManager.prototype.configureApplication = function(appId, deviceType, cb) { assert.ok(cb, 'callback should be defined'); var self = this; var msg; @@ -156,13 +162,13 @@ PushManager.prototype.configureApplication = function (appId, deviceType, cb) { // Check the cache first var cacheApp = self.applicationsCache.get(appId); if (cacheApp && cacheApp[deviceType]) { - return process.nextTick(function () { + return process.nextTick(function() { cb(null, cacheApp[deviceType]); }); } // Look up the application object by id - self.Application.findById(appId, function (err, application) { + self.Application.findById(appId, function(err, application) { if (err) { debug('Cannot find application with id %j: %s', appId, err.stack || err.message); @@ -176,7 +182,7 @@ PushManager.prototype.configureApplication = function (appId, deviceType, cb) { debug('Error: %s', msg); err = new Error(msg); - err.details = { appId: appId }; + err.details = {appId: appId}; return cb(err); } @@ -191,8 +197,8 @@ PushManager.prototype.configureApplication = function (appId, deviceType, cb) { err.details = { application: { id: application.id, - name: application.name - } + name: application.name, + }, }; return cb(err); } @@ -210,7 +216,7 @@ PushManager.prototype.configureApplication = function (appId, deviceType, cb) { debug('Error: %s', msg); err = new Error(msg); err.details = { - deviceType: deviceType + deviceType: deviceType, }; return cb(err); } @@ -222,23 +228,22 @@ PushManager.prototype.configureApplication = function (appId, deviceType, cb) { }); }; - /** * Push a notification to the device with the given registration id. * @param {Object} installationId Registration id created by call to Installation.create(). * @param {Notification} notification The notification to send. * @param {function(Error=)} cb */ -PushManager.prototype.notifyById = function (installationId, notification, cb) { +PushManager.prototype.notifyById = function(installationId, notification, cb) { assert.ok(cb, 'callback should be defined'); var self = this; - self.Installation.findById(installationId, function (err, installation) { + self.Installation.findById(installationId, function(err, installation) { if (err) return cb(err); if (!installation) { var msg = 'Installation id ' + installationId + ' not found'; debug('notifyById failed: ' + msg); err = new Error(msg); - err.details = { installationId: installationId }; + err.details = {installationId: installationId}; return cb(err); } self.notify(installation, notification, cb); @@ -252,10 +257,11 @@ PushManager.prototype.notifyById = function (installationId, notification, cb) { * @param {Notification} notification The notification to send. * @param {function(Error=)} cb */ -PushManager.prototype.notifyByQuery = function(installationQuery, notification, cb) { +PushManager.prototype.notifyByQuery = function(installationQuery, notification, +cb) { assert.ok(cb, 'callback should be defined'); var self = this; - var filter = { where: installationQuery }; + var filter = {where: installationQuery}; self.Installation.find(filter, function(err, installationList) { if (err) return cb(err); async.each( @@ -278,7 +284,7 @@ PushManager.prototype.notifyByQuery = function(installationQuery, notification, PushManager.prototype.notify = function(installation, notification, cb) { assert(cb, 'callback should be defined'); - if(!(typeof notification === 'object' && notification)) { + if (!(typeof notification === 'object' && notification)) { return cb(new Error(g.f('notification must be an object'))); } @@ -288,7 +294,7 @@ PushManager.prototype.notify = function(installation, notification, cb) { // Normalize the notification from a plain object // for remote calls - if(!(notification instanceof this.Notification)) { + if (!(notification instanceof this.Notification)) { notification = new this.Notification(notification); } @@ -323,36 +329,37 @@ PushManager.prototype.notify = function(installation, notification, cb) { * @param {Notification} notification The Notification object to send. * @param {function(Error=)} cb */ -PushManager.prototype.notifyMany = function(appId, deviceType, deviceTokens, notification, cb) { - assert(appId, 'appId should be defined'); - assert(deviceType, 'deviceType should be defined'); - assert(cb, 'callback should be defined'); +PushManager.prototype.notifyMany = function(appId, deviceType, deviceTokens, +notification, cb) { + assert(appId, 'appId should be defined'); + assert(deviceType, 'deviceType should be defined'); + assert(cb, 'callback should be defined'); - if(!(typeof notification === 'object' && notification)) { - return cb(new Error(g.f('notification must be an object'))); - } + if (!(typeof notification === 'object' && notification)) { + return cb(new Error(g.f('notification must be an object'))); + } - if(!(Array.isArray(deviceTokens) && deviceTokens.length > 0)) { - return cb(new Error(g.f('deviceTokens must be an array'))); - } + if (!(Array.isArray(deviceTokens) && deviceTokens.length > 0)) { + return cb(new Error(g.f('deviceTokens must be an array'))); + } // Normalize the notification from a plain object // for remote calls - if(!(notification instanceof this.Notification)) { - notification = new this.Notification(notification); - if (!notification.isValid()) { - return cb(new loopback.ValidationError(notification)); - } + if (!(notification instanceof this.Notification)) { + notification = new this.Notification(notification); + if (!notification.isValid()) { + return cb(new loopback.ValidationError(notification)); } + } - this.configureApplication( + this.configureApplication( appId, deviceType, function(err, provider) { - if (err) { return cb(err); } + if (err) { return cb(err); } - provider.pushNotification(notification, deviceTokens); - cb(); + provider.pushNotification(notification, deviceTokens); + cb(); } ); }; @@ -364,21 +371,23 @@ PushManager.prototype.notifyMany = function(appId, deviceType, deviceTokens, not * @private */ function setRemoting(fn, options) { - options = options || {}; - for (var opt in options) { - if (options.hasOwnProperty(opt)) { - fn[opt] = options[opt]; - } + options = options || {}; + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + fn[opt] = options[opt]; } - fn.shared = true; + } + fn.shared = true; } setRemoting(PushManager.prototype.notifyByQuery, { description: 'Send a push notification by installation query', accepts: [ - {arg: 'deviceQuery', type: 'object', description: 'Installation query', http: {source: 'query'}}, - {arg: 'notification', type: 'object', description: 'Notification', http: {source: 'body'}} + {arg: 'deviceQuery', type: 'object', description: 'Installation query', + http: {source: 'query'}}, + {arg: 'notification', type: 'object', description: 'Notification', + http: {source: 'body'}}, ], returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'post', path: '/'} // The url will be POST /push + http: {verb: 'post', path: '/'}, // The url will be POST /push }); diff --git a/models/index.js b/models/index.js index 698b3f4..64073f0 100644 --- a/models/index.js +++ b/models/index.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // mostly borrowed from // https://github.com/strongloop/loopback-component-passport/blob/master/lib/index.js var loopback = require('loopback'); diff --git a/models/installation.js b/models/installation.js index 61b92f1..3fb80e2 100644 --- a/models/installation.js +++ b/models/installation.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var _ = require('lodash'); /** @@ -28,7 +30,6 @@ var _ = require('lodash'); * @header Installation */ module.exports = function(Installation) { - Installation.observe('before save', function trip(ctx, next) { var install = ctx.instance || ctx.data; install.modified = new Date(); @@ -50,7 +51,7 @@ module.exports = function(Installation) { var filter = {where: { appId: appId, appVersion: appVersion, - deviceType: deviceType} + deviceType: deviceType}, }; this.find(filter, cb); }; @@ -77,9 +78,9 @@ module.exports = function(Installation) { subscriptions = subscriptions.split(/[\s,]+/); } var filter = {where: { - subscriptions: {inq: subscriptions}, - deviceType: deviceType - } + subscriptions: {inq: subscriptions}, + deviceType: deviceType, + }, }; this.find(filter, cb); }; @@ -103,32 +104,32 @@ module.exports = function(Installation) { setRemoting(Installation.findByApp, { description: 'Find installations by application id', accepts: [ - _.extend({arg: 'deviceType', description: 'Device type'}, aDefs), - _.extend({arg: 'appId', description: 'Application id'}, aDefs), - _.extend({arg: 'appVersion', description: 'Application version'}, aDefs) + _.extend({arg: 'deviceType', description: 'Device type'}, aDefs), + _.extend({arg: 'appId', description: 'Application id'}, aDefs), + _.extend({arg: 'appVersion', description: 'Application version'}, aDefs), ], returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'get', path: '/byApp'} + http: {verb: 'get', path: '/byApp'}, }); setRemoting(Installation.findByUser, { description: 'Find installations by user id', accepts: [ _.extend({arg: 'deviceType', description: 'Device type'}, aDefs), - _.extend({arg: 'userId', description: 'User id'}, aDefs) + _.extend({arg: 'userId', description: 'User id'}, aDefs), ], returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'get', path: '/byUser'} + http: {verb: 'get', path: '/byUser'}, }); setRemoting(Installation.findBySubscriptions, { description: 'Find installations by subscriptions', accepts: [ _.extend({arg: 'deviceType', description: 'Device type'}, aDefs), - _.extend({arg: 'subscriptions', description: 'Subscriptions'}, aDefs) + _.extend({arg: 'subscriptions', description: 'Subscriptions'}, aDefs), ], returns: {arg: 'data', type: 'object', root: true}, - http: {verb: 'get', path: '/bySubscriptions'} + http: {verb: 'get', path: '/bySubscriptions'}, }); return Installation; diff --git a/models/notification.js b/models/notification.js index 714f352..16b7dda 100644 --- a/models/notification.js +++ b/models/notification.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + /** * Notification Model * @@ -39,7 +41,6 @@ * @header Notification */ module.exports = function(Notification) { - // Avoid exposure of internal properties such __data Notification.hideInternalProperties = true; diff --git a/package.json b/package.json index dcec4b2..f4bb408 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,9 @@ ], "main": "index.js", "scripts": { - "pretest": "jscs . && jshint .", - "test": "mocha" + "lint": "eslint .", + "test": "mocha", + "posttest": "npm run lint" }, "dependencies": { "apn": "^1.7.5", @@ -27,10 +28,10 @@ "devDependencies": { "chai": "^2.3.0", "deep-extend": "^0.4.0", + "eslint": "^2.13.1", + "eslint-config-loopback": "^4.0.0", "grunt-contrib-watch": "^0.6.1", "grunt-jsdoc": "^0.6.6", - "jscs": "^1.13.1", - "jshint": "^2.8.0", "loopback": "^2.18.0", "loopback-connector-mongodb": "^1.8.0", "loopback-datasource-juggler": "^2.29.0", diff --git a/test/apns.provider.test.js b/test/apns.provider.test.js index 5df63a9..b535c6b 100644 --- a/test/apns.provider.test.js +++ b/test/apns.provider.test.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var fs = require('fs'); var path = require('path'); var ApnsProvider = require('../lib/providers/apns'); @@ -25,7 +27,7 @@ describe('APNS provider', function() { givenProviderWithConfig(); var notification = aNotification({ - aKey: 'a-value' + aKey: 'a-value', }); provider.pushNotification(notification, aDeviceToken); @@ -37,7 +39,7 @@ describe('APNS provider', function() { expect(note.badge, 'badge').to.equal(undefined); expect(note.sound, 'sound').to.equal(undefined); expect(note.payload, 'payload').to.deep.equal({ - aKey: 'a-value' + aKey: 'a-value', }); expect(apnArgs[1]).to.equal(aDeviceToken); @@ -51,7 +53,7 @@ describe('APNS provider', function() { contentAvailable: true, category: 'my-category', urlArgs: ['foo', 'bar'], - arbitrary: 'baz' + arbitrary: 'baz', }); provider.pushNotification(notification, aDeviceToken); @@ -59,7 +61,8 @@ describe('APNS provider', function() { var note = apnArgs[0]; var payload = note.toJSON(); - expect(payload.aps['content-available'], 'aps.content-available').to.equal(1); + expect(payload.aps['content-available'], 'aps.content-available').to + .equal(1); expect(payload.aps.category, 'aps.category').to.equal('my-category'); expect(payload.aps['url-args'], 'aps.url-args').to.have.length(2); expect(payload.arbitrary, 'arbitrary').to.equal('baz'); @@ -70,8 +73,8 @@ describe('APNS provider', function() { it('raises "devicesGone" event when feedback arrives', function(done) { givenProviderWithConfig({ apns: { - feedbackOptions: {} - } + feedbackOptions: {}, + }, }); var eventSpy = sinon.spy(); provider.on('devicesGone', eventSpy); @@ -87,7 +90,7 @@ describe('APNS provider', function() { givenProviderWithConfig(); var notification = aNotification({ - expirationInterval: 1 /* second */ + expirationInterval: 1, /* second */ }); provider.pushNotification(notification, aDeviceToken); @@ -99,7 +102,7 @@ describe('APNS provider', function() { givenProviderWithConfig(); var notification = aNotification({ - expirationTime: new Date(this.clock.now + 1000 /* 1 second */ ) + expirationTime: new Date(this.clock.now + 1000 /* 1 second */), }); provider.pushNotification(notification, aDeviceToken); @@ -110,13 +113,13 @@ describe('APNS provider', function() { it('ignores Notification properties not applicable', function() { givenProviderWithConfig(); - var notification = aNotification(objectMother.allNotificationProperties()); + var notification = aNotification( + objectMother.allNotificationProperties()); provider.pushNotification(notification, aDeviceToken); var note = mockery.firstPushNotificationArgs()[0]; expect(note.payload).to.eql({}); }); - }); describe('in dev env', function() { @@ -132,8 +135,8 @@ describe('APNS provider', function() { certData: 'invalid-data', pushOptions: { gateway: '127.0.0.1', - } - } + }, + }, }); var eventSpy = sinon.spy(); @@ -157,8 +160,8 @@ describe('APNS provider', function() { keyData: objectMother.apnsDevKey(), pushOptions: { gateway: '127.0.0.1', - } - } + }, + }, }); var eventSpy = sinon.spy(); @@ -185,9 +188,9 @@ describe('APNS provider', function() { certData: objectMother.apnsDevCert(), keyData: objectMother.apnsDevKey(), pushOptions: { - gateway: '127.0.0.1' - } - } + gateway: '127.0.0.1', + }, + }, }); expect(provider._pushOptions).to.deep.equal({ cert: objectMother.apnsDevCert(), @@ -208,13 +211,13 @@ describe('APNS provider', function() { apns: { pushOptions: { gateway: 'push.test.com', - port: 1111 + port: 1111, }, feedbackOptions: { gateway: 'feedback.test.com', - port: 1112 - } - } + port: 1112, + }, + }, }); expect(provider._pushOptions).to.deep.equal({ gateway: 'push.test.com', @@ -237,9 +240,9 @@ describe('APNS provider', function() { }, */ feedbackOptions: { - interval: 300 - } - } + interval: 300, + }, + }, }); expect(provider._pushOptions).to.deep.equal({ gateway: 'gateway.sandbox.push.apple.com', @@ -260,8 +263,8 @@ describe('APNS provider', function() { feedbackOptions: { interval: 300, production: false, - } - } + }, + }, }); expect(provider._pushOptions).to.deep.equal({ gateway: 'gateway.push.apple.com', @@ -280,14 +283,14 @@ describe('APNS provider', function() { production: true, pushOptions: { gateway: 'invalid', - port: 1111 + port: 1111, }, feedbackOptions: { gateway: 'invalid', port: 1112, - interval: 300 - } - } + interval: 300, + }, + }, }); expect(provider._pushOptions).to.deep.equal({ gateway: 'gateway.push.apple.com', @@ -319,4 +322,4 @@ describe('APNS provider', function() { function tearDownFakeTimers() { this.clock.restore(); } -}); \ No newline at end of file +}); diff --git a/test/common.js b/test/common.js index f8860f9..0ac57e6 100644 --- a/test/common.js +++ b/test/common.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + /* exported global */ global.chai = require('chai'); global.should = require('chai').should(); @@ -14,7 +16,7 @@ global.assert = require('assert'); global.sinon = require('sinon'); global.ds = global.loopback.createDataSource('db', { - connector: global.loopback.Memory + connector: global.loopback.Memory, }); global.Application = global.loopback.Application; diff --git a/test/device-registration.test.js b/test/device-registration.test.js index a2e3a8c..c74d1ba 100644 --- a/test/device-registration.test.js +++ b/test/device-registration.test.js @@ -3,51 +3,51 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 -describe('Installation', function () { - var registration = null; - - it('registers a new installation', function (done) { - var token = '75624450 3c9f95b4 9d7ff821 20dc193c a1e3a7cb 56f60c2e f2a19241 e8f33305'; - Installation.create({ - appId: 'MyLoopbackApp', - appVersion: '1', - userId: 'raymond', - deviceToken: token, - deviceType: 'ios', - created: new Date(), - modified: new Date(), - status: 'Active' - }, function (err, result) { - if (err) { - console.error(err); - done(err, result); - return; - } else { - var reg = result; - assert.equal(reg.appId, 'MyLoopbackApp'); - assert.equal(reg.userId, 'raymond'); - assert.equal(reg.deviceType, 'ios'); - assert.equal(reg.deviceToken, token); - - assert(reg.created); - assert(reg.modified); - - registration = reg; - - Installation.findByApp('ios', 'MyLoopbackApp', function (err, results) { - assert(!err); - assert.equal(results.length, 1); - var reg = results[0]; - assert.equal(reg.appId, 'MyLoopbackApp'); - assert.equal(reg.userId, 'raymond'); - assert.equal(reg.deviceType, 'ios'); - assert.equal(reg.deviceToken, token); - done(err, results); - - }); - } +'use strict'; + +describe('Installation', function() { + var registration = null; + + it('registers a new installation', function(done) { + var token = '75624450 3c9f95b4 9d7ff821 20dc193c a1e3a7cb 56f60c2e ' + + 'f2a19241 e8f33305'; + Installation.create({ + appId: 'MyLoopbackApp', + appVersion: '1', + userId: 'raymond', + deviceToken: token, + deviceType: 'ios', + created: new Date(), + modified: new Date(), + status: 'Active', + }, function(err, result) { + if (err) { + console.error(err); + done(err, result); + return; + } else { + var reg = result; + assert.equal(reg.appId, 'MyLoopbackApp'); + assert.equal(reg.userId, 'raymond'); + assert.equal(reg.deviceType, 'ios'); + assert.equal(reg.deviceToken, token); + + assert(reg.created); + assert(reg.modified); + + registration = reg; + + Installation.findByApp('ios', 'MyLoopbackApp', function(err, results) { + assert(!err); + assert.equal(results.length, 1); + var reg = results[0]; + assert.equal(reg.appId, 'MyLoopbackApp'); + assert.equal(reg.userId, 'raymond'); + assert.equal(reg.deviceType, 'ios'); + assert.equal(reg.deviceToken, token); + done(err, results); }); - + } }); - + }); }); diff --git a/test/gcm.provider.test.js b/test/gcm.provider.test.js index 7a3a5ce..5c56eb2 100644 --- a/test/gcm.provider.test.js +++ b/test/gcm.provider.test.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + var extend = require('util')._extend; var GcmProvider = require('../lib/providers/gcm'); var mockery = require('./helpers/mockery').gcm; @@ -14,7 +16,7 @@ var aDeviceTokenList = [ 'second-device-token', 'third-device-token', 'fourth-device-token', - 'fifth-device-token' + 'fifth-device-token', ]; describe('GCM provider', function() { @@ -27,10 +29,9 @@ describe('GCM provider', function() { afterEach(tearDownFakeTimers); afterEach(mockery.tearDown); - describe('for single device token', function() { it('sends Notification as a GCM message', function(done) { - var notification = aNotification({ aKey: 'a-value' }); + var notification = aNotification({aKey: 'a-value'}); notification.alert = 'alert message'; notification.badge = 1; provider.pushNotification(notification, aDeviceToken); @@ -38,10 +39,12 @@ describe('GCM provider', function() { var gcmArgs = mockery.firstPushNotificationArgs(); var msg = gcmArgs[0]; - expect(msg.params.collapseKey, 'collapseKey').to.equal(undefined); + expect(msg.params.collapseKey, 'collapseKey').to + .equal(undefined); expect(msg.params.delayWhileIdle, 'delayWhileIdle').to.equal(undefined); expect(msg.params.timeToLive, 'timeToLive').to.equal(undefined); - expect(msg.params.data, 'data').to.deep.equal({ aKey: 'a-value', alert: 'alert message', badge: 1 }); + expect(msg.params.data, 'data').to + .deep.equal({aKey: 'a-value', alert: 'alert message', badge: 1}); expect(gcmArgs[1]).to.deep.equal([aDeviceToken]); done(); @@ -55,13 +58,14 @@ describe('GCM provider', function() { provider.pushNotification(aNotification(), aDeviceToken); - expect(eventSpy.calledOnce, 'error should be emitted once').to.equal(true); + expect(eventSpy.calledOnce, 'error should be emitted once').to + .equal(true); expect(eventSpy.args[0]).to.deep.equal([anError]); }); it('emits "error" event when GCM returns error result', function() { // This is a real result returned by GCM - var errorResult = aGcmResult([{ 'error': 'MismatchSenderId' }]); + var errorResult = aGcmResult([{'error': 'MismatchSenderId'}]); mockery.pushNotificationCallbackArgs = [null, errorResult]; @@ -69,12 +73,13 @@ describe('GCM provider', function() { provider.pushNotification(aNotification(), aDeviceToken); - expect(eventSpy.calledOnce, 'error should be emitted once').to.equal(true); + expect(eventSpy.calledOnce, 'error should be emitted once').to + .equal(true); expect(eventSpy.firstCall.args[0].message).to.contain('MismatchSenderId'); }); it('emits "devicesGone" when GCM returns NotRegistered', function(done) { - var errorResult = aGcmResult([{ 'error': 'NotRegistered' }]); + var errorResult = aGcmResult([{'error': 'NotRegistered'}]); mockery.pushNotificationCallbackArgs = [null, errorResult]; @@ -92,7 +97,7 @@ describe('GCM provider', function() { describe('for multiple device tokens', function() { it('sends Notification as a GCM message', function(done) { - var notification = aNotification({ aKey: 'a-value' }); + var notification = aNotification({aKey: 'a-value'}); provider.pushNotification(notification, aDeviceTokenList); var gcmArgs = mockery.pushNotification.args[0]; @@ -101,22 +106,23 @@ describe('GCM provider', function() { expect(msg.params.collapseKey, 'collapseKey').to.equal(undefined); expect(msg.params.delayWhileIdle, 'delayWhileIdle').to.equal(undefined); expect(msg.params.timeToLive, 'timeToLive').to.equal(undefined); - expect(msg.params.data, 'data').to.deep.equal({ aKey: 'a-value' }); + expect(msg.params.data, 'data').to.deep.equal({aKey: 'a-value'}); expect(gcmArgs[1]).to.deep.equal(aDeviceTokenList); done(); }); it('handles GCM response for multiple device tokens', function(done) { - var gcmError = new Error('GCM error code: MismatchSenderId, deviceToken: third-device-token\n' + - 'GCM error code: MismatchSenderId, deviceToken: fifth-device-token'); + var gcmError = new Error('GCM error code: MismatchSenderId, ' + + 'deviceToken: third-device-token\nGCM error code: ' + + 'MismatchSenderId, deviceToken: fifth-device-token'); var gcmResult = aGcmResult([ - { 'error': 'InvalidRegistration' }, - { 'message_id': '1234567890' }, - { 'error': 'MismatchSenderId' }, - { 'error': 'NotRegistered' }, - { 'error': 'MismatchSenderId' }, + {'error': 'InvalidRegistration'}, + {'message_id': '1234567890'}, + {'error': 'MismatchSenderId'}, + {'error': 'NotRegistered'}, + {'error': 'MismatchSenderId'}, ]); mockery.pushNotificationCallbackArgs = [null, gcmResult]; @@ -130,14 +136,15 @@ describe('GCM provider', function() { provider.pushNotification(aNotification(), aDeviceTokenList); var expectedIds = [aDeviceTokenList[0], aDeviceTokenList[3]]; - expect(eventSpy.calledOnce, 'error should be emitted once').to.equal(true); + expect(eventSpy.calledOnce, 'error should be emitted once').to + .equal(true); expect(eventSpy.args[0][0]).to.deep.equal(expectedIds); done(); }); }); it('converts expirationInterval to GCM timeToLive', function() { - var notification = aNotification({ expirationInterval: 1 /* second */}); + var notification = aNotification({expirationInterval: 1}); provider.pushNotification(notification, aDeviceToken); var message = mockery.firstPushNotificationArgs()[0]; @@ -146,7 +153,7 @@ describe('GCM provider', function() { it('converts expirationTime to GCM timeToLive relative to now', function() { var notification = aNotification({ - expirationTime: new Date(this.clock.now + 1000 /* 1 second */) + expirationTime: new Date(this.clock.now + 1000 /* 1 second */), }); provider.pushNotification(notification, aDeviceToken); @@ -157,7 +164,7 @@ describe('GCM provider', function() { it('forwards android parameters', function() { var notification = aNotification({ collapseKey: 'a-collapse-key', - delayWhileIdle: true + delayWhileIdle: true, }); provider.pushNotification(notification, aDeviceToken); @@ -172,7 +179,8 @@ describe('GCM provider', function() { provider.pushNotification(notification, aDeviceToken); var message = mockery.firstPushNotificationArgs()[0]; - expect(message.params.data).to.deep.equal({ alert: 'an-alert', badge: 1230001 }); + expect(message.params.data).to + .deep.equal({alert: 'an-alert', badge: 1230001}); }); it('ignores Notification properties null or undefined', function() { @@ -180,19 +188,19 @@ describe('GCM provider', function() { aFalse: false, aTrue: true, aNull: null, - anUndefined: undefined + anUndefined: undefined, }); provider.pushNotification(notification, aDeviceToken); var message = mockery.firstPushNotificationArgs()[0]; - expect(message.params.data).to.deep.equal({ aFalse: false, aTrue: true }); + expect(message.params.data).to.deep.equal({aFalse: false, aTrue: true}); }); function givenProviderWithConfig(pushSettings) { pushSettings = extend({}, pushSettings); pushSettings.gcm = extend({}, pushSettings.gcm); pushSettings.gcm.pushOptions = extend( - { serverKey: 'a-test-server-key' }, + {serverKey: 'a-test-server-key'}, pushSettings.gcm.pushOptions); provider = new GcmProvider(pushSettings); @@ -203,12 +211,11 @@ describe('GCM provider', function() { } function aGcmResult(results) { - - var success = results.filter(function(item){ + var success = results.filter(function(item) { return item.message_id; }).length; - var failure = results.filter(function(item){ + var failure = results.filter(function(item) { return item.error; }).length; @@ -217,7 +224,7 @@ describe('GCM provider', function() { 'success': success, 'failure': failure, 'canonical_ids': 0, - 'results': results + 'results': results, }; } diff --git a/test/helpers/mockery/apns.mockery.js b/test/helpers/mockery/apns.mockery.js index db2e2bb..50aa80a 100644 --- a/test/helpers/mockery/apns.mockery.js +++ b/test/helpers/mockery/apns.mockery.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // This module provides a mocked environment where APN connections // are calling callbacks provided by tests instead of communicating with // the real service @@ -49,7 +51,6 @@ mockery.firstPushNotificationArgs = function() { return mockery.pushNotification.firstCall.args; }; - var apnsSnapshot = {}; var defaultExports = {}; diff --git a/test/helpers/mockery/gcm.mockery.js b/test/helpers/mockery/gcm.mockery.js index 87c57e6..adab9f8 100644 --- a/test/helpers/mockery/gcm.mockery.js +++ b/test/helpers/mockery/gcm.mockery.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // This module provides a mocked environment where GCM connections // are calling callbacks provided by tests instead of communicating with // the real service @@ -65,7 +67,7 @@ exports.setUp = function() { } mockery.pushNotification = sinon.spy(); - mockery.pushNotificationCallbackArgs = [null, { success: 1, failure: 0 }]; + mockery.pushNotificationCallbackArgs = [null, {success: 1, failure: 0}]; gcm.Sender = function(opts) { mockery.senderOptions = Array.prototype.slice.call(arguments); diff --git a/test/helpers/mockery/index.js b/test/helpers/mockery/index.js index f3a3784..6e891b8 100644 --- a/test/helpers/mockery/index.js +++ b/test/helpers/mockery/index.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // This module provides a mocked environment where APN/GCM/etc. connections // are calling callbacks provided by tests instead of communicating with // the real service diff --git a/test/helpers/mockery/stub.mockery.js b/test/helpers/mockery/stub.mockery.js index 6014192..36932b8 100644 --- a/test/helpers/mockery/stub.mockery.js +++ b/test/helpers/mockery/stub.mockery.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // This module provides a mocked environment with a special "stub" // provider that does not depend on any real implementation (GCM, APNS) @@ -77,7 +79,6 @@ StubProvider.prototype.emitDevicesGone = function(devices) { this.emit('devicesGone', devices); }; - var defaultExports = {}; /** diff --git a/test/helpers/object-mother.js b/test/helpers/object-mother.js index a4ec0c7..467cf16 100644 --- a/test/helpers/object-mother.js +++ b/test/helpers/object-mother.js @@ -3,6 +3,8 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; + // An object mother is a kind of class used in testing to help create // example objects that you use for testing. // See http://martinfowler.com/bliki/ObjectMother.html @@ -21,7 +23,7 @@ exports.allNotificationProperties = function() { scheduledTime: new Date(2222, 3, 3), expirationTime: new Date(2222, 4, 4), expirationInterval: 123002, - status: 'a-status' + status: 'a-status', }; }; diff --git a/test/helpers/test-data-builder.js b/test/helpers/test-data-builder.js index 17f24b0..e9a4e27 100644 --- a/test/helpers/test-data-builder.js +++ b/test/helpers/test-data-builder.js @@ -3,6 +3,7 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; var extend = require('util')._extend; var async = require('async'); @@ -55,7 +56,7 @@ TestDataBuilder.prototype.define = function(name, Model, properties) { this._definitions.push({ name: name, model: Model, - properties: properties + properties: properties, }); return this; }; diff --git a/test/push-manager.test.js b/test/push-manager.test.js index 9c3109b..e5de71f 100644 --- a/test/push-manager.test.js +++ b/test/push-manager.test.js @@ -3,6 +3,7 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; var async = require('async'); var PushManager = require('../lib/push-manager'); @@ -18,8 +19,7 @@ describe('PushManager', function() { beforeEach(Installation.deleteAll.bind(Installation)); afterEach(mockery.tearDown); - var pushManager; - var context; + var pushManager, context; beforeEach(function(done) { pushManager = new PushManager(); @@ -34,11 +34,11 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) .define('installation', Installation, { appId: ref('application.id'), - deviceType: mockery.deviceType + deviceType: mockery.deviceType, }) .buildTo(context, cb); }, @@ -65,17 +65,17 @@ describe('PushManager', function() { expect(result).to.have.length(0); cb(); }); - } + }, ], done); }); - describe('.notify', function () { - it('should set device type/token from installation', function (done) { + describe('.notify', function() { + it('should set device type/token from installation', function(done) { async.series([ function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) // Note: the order in which the installations are created // is important. @@ -86,12 +86,12 @@ describe('PushManager', function() { .define('anotherDevice', Installation, { appId: ref('application.id'), deviceToken: 'a-device-token', - deviceType: 'another-device-type' + deviceType: 'another-device-type', }) .define('installation', Installation, { appId: ref('application.id'), deviceToken: 'a-device-token', - deviceType: mockery.deviceType + deviceType: mockery.deviceType, }) .buildTo(context, cb); }, @@ -107,21 +107,21 @@ describe('PushManager', function() { function verify(cb) { // Wait with the check to give the push manager some time // to load all data and push the message - setTimeout(function () { + setTimeout(function() { expect(mockery.firstPushNotificationArgs()).to.deep.equal( [context.notification, context.installation.deviceToken] ); cb(); }, 50); - } + }, ], done); }); - it('reports error on invalid notification', function (done) { + it('reports error on invalid notification', function(done) { pushManager.notify( - { userId: 'unknown-user' }, - { invalid: true }, // invalid - function (err) { + {userId: 'unknown-user'}, + {invalid: true}, // invalid + function(err) { expect(err.name).to.equal('ValidationError'); done(); } @@ -135,7 +135,7 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) // Note: the order in which the installations are created // is important. @@ -146,12 +146,12 @@ describe('PushManager', function() { .define('anotherDevice', Installation, { appId: ref('application.id'), deviceToken: 'a-device-token', - deviceType: 'another-device-type' + deviceType: 'another-device-type', }) .define('installation', Installation, { appId: ref('application.id'), deviceToken: 'a-device-token', - deviceType: mockery.deviceType + deviceType: mockery.deviceType, }) .buildTo(context, cb); }, @@ -173,7 +173,7 @@ describe('PushManager', function() { ); cb(); }, 50); - } + }, ], done); }); @@ -192,7 +192,7 @@ describe('PushManager', function() { .to.have.property('installationId', 'unknown-installation-id'); cb(); } - } + }, ], done); }); @@ -200,7 +200,7 @@ describe('PushManager', function() { async.series([ function arrange(cb) { new TestDataBuilder() - .define('installation', Installation, { appId: 'unknown-app-id' }) + .define('installation', Installation, {appId: 'unknown-app-id'}) .buildTo(context, cb); }, @@ -217,7 +217,7 @@ describe('PushManager', function() { .to.have.property('appId', 'unknown-app-id'); cb(); } - } + }, ], done); }); @@ -225,10 +225,10 @@ describe('PushManager', function() { async.series([ function arrange(cb) { new TestDataBuilder() - .define('application', Application, { pushSettings: null }) + .define('application', Application, {pushSettings: null}) .define('installation', Installation, { appId: ref('application.id'), - deviceType: 'unknown-device-type' + deviceType: 'unknown-device-type', }) .buildTo(context, cb); }, @@ -245,19 +245,18 @@ describe('PushManager', function() { expect(err.details).to.have.property('application'); cb(); } - } + }, ], done); - }); it('reports error for unknown device type', function(done) { async.series([ function arrange(cb) { new TestDataBuilder() - .define('application', Application, { pushSettings: {}}) + .define('application', Application, {pushSettings: {}}) .define('installation', Installation, { appId: ref('application.id'), - deviceType: 'unknown-device-type' + deviceType: 'unknown-device-type', }) .buildTo(context, cb); }, @@ -273,9 +272,8 @@ describe('PushManager', function() { expect(err).to.be.instanceOf(Error); cb(); } - } + }, ], done); - }); it('emits error when push fails inside provider', function(done) { @@ -283,12 +281,12 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) .define('installation', Installation, { appId: ref('application.id'), deviceToken: 'a-device-token', - deviceType: mockery.deviceType + deviceType: mockery.deviceType, }) .buildTo(context, cb); }, @@ -311,7 +309,7 @@ describe('PushManager', function() { cb(); } ); - } + }, ], done); }); }); @@ -322,32 +320,32 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) .define('myPhone', Installation, { appId: ref('application.id'), deviceToken: 'my-phone-token', deviceType: mockery.deviceType, - userId: 'myself' + userId: 'myself', }) .define('myOtherPhone', Installation, { appId: ref('application.id'), deviceToken: 'my-other-phone-token', deviceType: mockery.deviceType, - userId: 'myself' + userId: 'myself', }) .define('friendsPhone', Installation, { appId: ref('application.id'), deviceToken: 'friends-phone-token', deviceType: mockery.deviceType, - userId: 'somebody else' + userId: 'somebody else', }) .buildTo(context, cb); }, function act(cb) { pushManager.notifyByQuery( - { userId: 'myself' }, + {userId: 'myself'}, context.notification, cb ); @@ -367,7 +365,7 @@ describe('PushManager', function() { ); cb(); }, 50); - } + }, ], done); }); @@ -376,24 +374,23 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('myPhone', Installation, { - userId: 'myself' + userId: 'myself', }) .buildTo(context, cb); }, function act(cb) { pushManager.notifyByQuery( - { userId: 'myself' }, - "invalid notification", // invalid + {userId: 'myself'}, + 'invalid notification', // invalid function(err) { expect(err.message).to.equal('notification must be an object'); cb(); } ); - } + }, ], done); }); - }); describe('.notifyMany', function() { @@ -402,25 +399,25 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) .define('firstPhone', Installation, { appId: ref('application.id'), deviceToken: 'first-phone-token', deviceType: mockery.deviceType, - userId: 'myself' + userId: 'myself', }) .define('secondPhone', Installation, { appId: ref('application.id'), deviceToken: 'second-phone-token', deviceType: mockery.deviceType, - userId: 'myself' + userId: 'myself', }) .define('thirdPhone', Installation, { appId: ref('application.id'), deviceToken: 'third-phone-token', deviceType: mockery.deviceType, - userId: 'somebody else' + userId: 'somebody else', }) .buildTo(context, cb); }, @@ -440,12 +437,13 @@ describe('PushManager', function() { var callsArgs = mockery.pushNotification.args; expect(callsArgs[0], 'number of arguments').to.have.length(2); - expect(callsArgs[0]).to.deep.equal( - [context.notification, [context.firstPhone.deviceToken, context.secondPhone.deviceToken]] - ); + expect(callsArgs[0]).to.deep.equal([ + context.notification, + [context.firstPhone.deviceToken, context.secondPhone.deviceToken], + ]); cb(); }, 50); - } + }, ], done); }); @@ -454,7 +452,7 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('myPhone', Installation, { - userId: 'myself' + userId: 'myself', }) .buildTo(context, cb); }, @@ -469,7 +467,7 @@ describe('PushManager', function() { cb(); } ); - } + }, ], done); }); @@ -486,19 +484,18 @@ describe('PushManager', function() { cb(); } ); - } + }, ], done); }); }); describe('PushManager applicationsCache', function() { - it('settings', function() { var ttlInSeconds, checkPeriodInSeconds; ttlInSeconds = checkPeriodInSeconds = 10; var pm = new PushManager({ ttlInSeconds: 10, - checkPeriodInSeconds: 10 + checkPeriodInSeconds: 10, }); expect(pm.ttlInSeconds).to.be.equal(ttlInSeconds); expect(pm.checkPeriodInSeconds).to.be.equal(checkPeriodInSeconds); @@ -521,11 +518,11 @@ describe('PushManager', function() { function arrange(cb) { new TestDataBuilder() .define('application', Application, { - pushSettings: { stub: { } } + pushSettings: {stub: { }}, }) .define('installation', Installation, { appId: ref('application.id'), - deviceType: mockery.deviceType + deviceType: mockery.deviceType, }) .buildTo(context, cb); }, @@ -538,9 +535,11 @@ describe('PushManager', function() { }, function verify(cb) { - var cacheApp = pushManager.applicationsCache.get(context.installation.appId); + var cacheApp = pushManager + .applicationsCache + .get(context.installation.appId); expect(cacheApp).to.have.property(context.installation.appId); - } + }, ]); }); }); @@ -576,7 +575,7 @@ describe('PushManager model dependencies', function() { it('honors settings', function() { var pm = new PushManager({ - installation: 'myInstallation' + installation: 'myInstallation', }); var myInstallation = Installation.extend('myInstallation', {}); var otherInstallation = Installation.extend('otherInstallation', {}); @@ -585,7 +584,7 @@ describe('PushManager model dependencies', function() { it('supports setters', function() { var pm = new PushManager({ - installation: 'myInstallation' + installation: 'myInstallation', }); var myInstallation = Installation.extend('myInstallation', {}); var otherInstallation = Installation.extend('otherInstallation', {}); diff --git a/test/push-notification.test.js b/test/push-notification.test.js index eb7d9bb..e51a5ea 100644 --- a/test/push-notification.test.js +++ b/test/push-notification.test.js @@ -3,78 +3,82 @@ // This file is licensed under the Artistic License 2.0. // License text available at https://opensource.org/licenses/Artistic-2.0 +'use strict'; var PushModel = PushConnector.createPushModel({dataSource: ds}); var objectMother = require('./helpers/object-mother'); -describe('PushNotification', function () { - it('registers a new installation', function (done) { +describe('PushNotification', function() { + it('registers a new installation', function(done) { // Sign up an application - Application.register('test-user', 'TestApp', - { - description: 'My test mobile application', - pushSettings: { - apns: { - certData: objectMother.apnsDevCert(), - keyData: objectMother.apnsDevKey(), - pushOptions: { - }, - feedbackOptions: { - batchFeedback: true, - interval: 300 - } - } - } - }, function (err, result) { - if (err) { - throw err; - } - var application = result; + Application.register('test-user', 'TestApp', + { + description: 'My test mobile application', + pushSettings: { + apns: { + certData: objectMother.apnsDevCert(), + keyData: objectMother.apnsDevKey(), + pushOptions: { + }, + feedbackOptions: { + batchFeedback: true, + interval: 300, + }, + }, + }, + }, function(err, result) { + if (err) { + throw err; + } + var application = result; - Installation.destroyAll(function (err, result) { + Installation.destroyAll(function(err, result) { // console.log('Adding a test record'); - Installation.create({ - appId: application.id, - userId: 'raymond', - deviceToken: '75624450 3c9f95b4 9d7ff821 20dc193c a1e3a7cb 56f60c2e f2a19241 e8f33305', - deviceType: 'ios', - created: new Date(), - modified: new Date(), - status: 'Active' - }, function (err, result) { - if (err) { - console.error(err); - } else { + Installation.create({ + appId: application.id, + userId: 'raymond', + deviceToken: '75624450 3c9f95b4 9d7ff821 20dc193c a1e3a7cb ' + + '56f60c2e f2a19241 e8f33305', + deviceType: 'ios', + created: new Date(), + modified: new Date(), + status: 'Active', + }, function(err, result) { + if (err) { + console.error(err); + } else { // console.log('Registration record is created: ', result); - } + } - PushModel.dataSource.connector.applicationsCache.set(application.id, {memory: { - pushNotification: function (notification, deviceToken) { - // console.log(notification, deviceToken); - assert.equal(deviceToken, result.deviceToken); - done(); - } - }}); + PushModel.dataSource.connector.applicationsCache.set( + application.id, + { + memory: { + pushNotification: function(notification, deviceToken) { + // console.log(notification, deviceToken); + assert.equal(deviceToken, result.deviceToken); + done(); + }, + }, + } + ); - var note = new Notification(); + var note = new Notification(); // Expires 1 hour from now. - note.expirationInterval = Math.floor(Date.now() / 1000) + 3600; - note.badge = 5; - note.sound = 'ping.aiff'; - note.alert = '\uD83D\uDCE7 \u2709 ' + 'Hello'; - note.messageFrom = 'Ray'; + note.expirationInterval = Math.floor(Date.now() / 1000) + 3600; + note.badge = 5; + note.sound = 'ping.aiff'; + note.alert = '\uD83D\uDCE7 \u2709 ' + 'Hello'; + note.messageFrom = 'Ray'; - PushModel.notifyById( + PushModel.notifyById( result.id, note, - function(err) { if(err) throw err; done();} + function(err) { if (err) throw err; done(); } ); - - }); - - }); - }); - }); - + }); + }); + }); + }); });