3 var apn = require('apn');
4 var debug = require('debug')('gitter:infra:ios-notification-gateway');
5 var env = require('gitter-web-env');
6 var Promise = require('bluebird');
7 var EventEmitter = require('events');
8 var path = require('path');
10 var iosNotificationGenerator = require('./ios-notification-generator');
11 var logger = env.logger.get('push-notifications');
12 var config = env.config;
13 var errorReporter = env.errorReporter;
15 var rootDirname = path.resolve(__dirname, '..', '..', '..', '..');
17 var ERROR_DESCRIPTIONS = {
18 0: 'No errors encountered',
19 1: 'Processing error',
20 2: 'Missing device token',
23 5: 'Invalid token size',
24 6: 'Invalid topic size',
25 7: 'Invalid payload size',
31 APPLE: createConnection('Prod', true),
32 'APPLE-DEV': createConnection('Dev')
35 function resolveCertConfig(key) {
36 var relative = config.get(key);
37 if (!relative) return;
39 return path.resolve(rootDirname, relative);
42 function sendNotificationToDevice(notificationType, notificationDetails, device) {
43 var appleNotification = iosNotificationGenerator(notificationType, notificationDetails, device);
44 if (!appleNotification) return false;
46 var deviceToken = new apn.Device(device.appleToken);
48 var connection = connections[device.deviceType];
50 if (connection === false) {
52 } else if (!connection) {
53 return Promise.reject(new Error('unknown device type: ' + device.deviceType));
56 connection.pushNotification(appleNotification, deviceToken);
58 // timout needed to ensure that the push notification packet is sent.
59 // if we dont, SIGINT will kill notifications before they have left.
60 // until apn uses proper callbacks, we have to guess that it takes a second.
61 return Promise.resolve(true).delay(1000);
64 function createConnection(suffix, isProduction) {
65 debug('ios push notification gateway (%s) starting', suffix);
67 var certConfigKey = 'apn:cert' + suffix;
68 var cert = resolveCertConfig(certConfigKey);
69 var keyConfigKey = 'apn:key' + suffix;
70 var key = resolveCertConfig(keyConfigKey);
74 'ios push notification gateway (' +
76 ') missing config, ' +
88 var connection = new apn.Connection({
91 production: isProduction,
92 connectionTimeout: 60000
95 connection.on('error', function(err) {
96 logger.error('ios push notification gateway (' + suffix + ') experienced an error', {
99 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
102 connection.on('socketError', function(err) {
103 logger.error('ios push notification gateway (' + suffix + ') experienced a socketError', {
106 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
109 connection.on('transmissionError', function(errCode, notification, device) {
110 var err = new Error('apn transmission error ' + errCode + ': ' + ERROR_DESCRIPTIONS[errCode]);
113 'ios push notification gateway (' +
115 ') invalid device token for "' +
117 '". Need to remove the following device sometime:',
121 logger.error('ios push notification gateway (' + suffix + ')', { error: err.message });
122 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
129 function sendBadgeUpdateToDevice(device, badge) {
130 if (!device || !device.appleToken) return false;
132 var deviceToken = new apn.Device(device.appleToken);
133 var connection = connections[device.deviceType];
135 if (connection === false) {
137 } else if (!connection) {
141 var note = new apn.Notification();
144 connection.pushNotification(note, deviceToken);
146 // timout needed to ensure that the push notification packet is sent.
147 // if we dont, SIGINT will kill notifications before they have left.
148 // until apn uses proper callbacks, we have to guess that it takes a second.
149 return Promise.resolve(true).delay(1000);
152 function createFeedbackEmitterForEnv(suffix, isProduction) {
153 var emitter = new EventEmitter();
156 debug('ios push notification feedback listener (%s) starting', suffix);
158 var certConfigKey = 'apn:cert' + suffix;
159 var cert = resolveCertConfig(certConfigKey);
160 var keyConfigKey = 'apn:key' + suffix;
161 var key = resolveCertConfig(keyConfigKey);
165 'ios push notification feedback (' +
167 ') missing config, ' +
179 var feedback = new apn.Feedback({
182 interval: config.get('apn:feedbackInterval'),
184 production: isProduction
187 feedback.on('feedback', function(item) {
188 var deviceTokens = item.map(function(item) {
189 return item.device.token;
192 emitter.emit('deregister', deviceTokens);
195 feedback.on('error', function(err) {
196 logger.error('ios push notification feedback (' + suffix + ') experienced an error', {
199 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
202 feedback.on('feedbackError', function(err) {
203 logger.error('ios push notification feedback (' + suffix + ') experienced a feedbackError', {
206 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
209 logger.error('Unable to start feedback service (' + suffix + ')', { exception: err });
210 errorReporter(err, { apnEnv: suffix }, { module: 'ios-notification-gateway' });
216 function createFeedbackEmitter() {
217 /* Only create the feedback listeners for the current environment */
218 switch (config.get('NODE_ENV') || 'dev') {
220 return createFeedbackEmitterForEnv('Prod', true);
224 return createFeedbackEmitterForEnv('Dev');
227 return new EventEmitter();
231 sendNotificationToDevice: Promise.method(sendNotificationToDevice),
232 sendBadgeUpdateToDevice: Promise.method(sendBadgeUpdateToDevice),
233 createFeedbackEmitter: createFeedbackEmitter