/lib/connection.js

https://github.com/justinleoye/node-apn · JavaScript · 780 lines · 548 code · 94 blank · 138 comment · 140 complexity · 671208172f1e369118a2d3d97c12db3d MD5 · raw file

  1. var Errors = require('./errors');
  2. var fs = require('fs');
  3. var q = require('q');
  4. var tls = require('tls');
  5. var net = require('net');
  6. var sysu = require('util');
  7. var util = require('./util');
  8. var Device = require('./device');
  9. var events = require('events');
  10. var debug = function() {};
  11. if(process.env.DEBUG) {
  12. try {
  13. debug = require('debug')('apn');
  14. }
  15. catch (e) {
  16. console.log("Notice: 'debug' module is not available. This should be installed with `npm install debug` to enable debug messages", e);
  17. debug = function() {};
  18. }
  19. }
  20. /**
  21. * Create a new connection to the APN service.
  22. * @constructor
  23. * @param {Object} [options]
  24. * @config {Buffer|String} [cert="cert.pem"] The filename of the connection certificate to load from disk, or a Buffer/String containing the certificate data.
  25. * @config {Buffer|String} [key="key.pem"] The filename of the connection key to load from disk, or a Buffer/String containing the key data.
  26. * @config {Buffer[]|String[]} [ca] An array of trusted certificates. Each element should contain either a filename to load, or a Buffer/String to be used directly. If this is omitted several well known "root" CAs will be used. - You may need to use this as some environments don't include the CA used by Apple (entrust_2048).
  27. * @config {Buffer|String} [pfx] File path for private key, certificate and CA certs in PFX or PKCS12 format, or a Buffer/String containing the PFX data. If supplied will be used instead of certificate and key above.
  28. * @config {String} [passphrase] The passphrase for the connection key, if required
  29. * @config {Boolean} [production=(NODE_ENV=='production')] Specifies which environment to connect to: Production (if true) or Sandbox. (Defaults to false, unless NODE_ENV == "production")
  30. * @config {Number} [port=2195] Gateway port
  31. * @config {Boolean} [rejectUnauthorized=true] Reject Unauthorized property to be passed through to tls.connect()
  32. * @config {Boolean} [enhanced=true] Whether to use the enhanced notification format (recommended)
  33. * @config {Function} [errorCallback] A callback which accepts 2 parameters (err, notification). Use `transmissionError` event instead.
  34. * @config {Number} [cacheLength=100] Number of notifications to cache for error purposes (See doc/apn.markdown)
  35. * @config {Boolean} [autoAdjustCache=false] Whether the cache should grow in response to messages being lost after errors. (Will still emit a 'cacheTooSmall' event)
  36. * @config {Number} [maxConnections=1] The maximum number of connections to create for sending messages.
  37. * @config {Number} [connectionTimeout=0] The duration the socket should stay alive with no activity in milliseconds. 0 = Disabled.
  38. * @config {Boolean} [buffersNotifications=true] Whether to buffer notifications and resend them after failure.
  39. * @config {Boolean} [fastMode=false] Whether to aggresively empty the notification buffer while connected.
  40. * @config {Boolean} [legacy=false] Whether to use the old (pre-iOS 7) protocol format.
  41. */
  42. function Connection (options) {
  43. if(false === (this instanceof Connection)) {
  44. return new Connection(options);
  45. }
  46. this.options = {
  47. cert: 'cert.pem',
  48. key: 'key.pem',
  49. ca: null,
  50. pfx: null,
  51. passphrase: null,
  52. production: (process.env.NODE_ENV === "production"),
  53. address: null,
  54. port: 2195,
  55. rejectUnauthorized: true,
  56. enhanced: true,
  57. cacheLength: 100,
  58. autoAdjustCache: true,
  59. maxConnections: 1,
  60. connectionTimeout: 0,
  61. buffersNotifications: true,
  62. fastMode: false,
  63. legacy: false,
  64. disableNagle: false
  65. };
  66. for (var key in options) {
  67. if (options[key] == null) {
  68. debug("Option [" + key + "] set to null. This may cause unexpected behaviour.");
  69. }
  70. }
  71. util.extend(this.options, options);
  72. if (this.options.gateway != null) {
  73. this.options.address = this.options.gateway;
  74. }
  75. if (this.options.address == null) {
  76. if (this.options.production) {
  77. this.options.address = "gateway.push.apple.com";
  78. }
  79. else {
  80. this.options.address = "gateway.sandbox.push.apple.com";
  81. }
  82. }
  83. if (this.options.pfx || this.options.pfxData) {
  84. if (!options.cert) {
  85. this.options.cert = null;
  86. }
  87. if (!options.key) {
  88. this.options.key = null;
  89. }
  90. }
  91. this.initializationPromise = null;
  92. this.deferredConnection = null;
  93. this.sockets = [];
  94. this.notificationBuffer = [];
  95. this.socketId = 0;
  96. this.failureCount = 0;
  97. // when true, we end all sockets after the pending notifications reach 0
  98. this.shutdownPending = false;
  99. events.EventEmitter.call(this);
  100. }
  101. sysu.inherits(Connection, events.EventEmitter);
  102. /**
  103. * You should never need to call this method, initialization and connection is handled by {@link Connection#sendNotification}
  104. * @private
  105. */
  106. Connection.prototype.initialize = function () {
  107. if (this.initializationPromise) {
  108. return this.initializationPromise;
  109. }
  110. debug("Initialising module");
  111. var readFile = q.nfbind(fs.readFile);
  112. // Prepare PKCS#12 data if available
  113. var pfxPromise = null;
  114. if(this.options.pfx != null || this.options.pfxData != null) {
  115. if(this.options.pfxData) {
  116. pfxPromise = this.options.pfxData;
  117. }
  118. else if(Buffer.isBuffer(this.options.pfx)) {
  119. pfxPromise = this.options.pfx;
  120. }
  121. else {
  122. pfxPromise = readFile(this.options.pfx);
  123. }
  124. }
  125. // Prepare Certificate data if available.
  126. var certPromise = null;
  127. if (this.options.certData) {
  128. certPromise = this.options.certData;
  129. }
  130. else if(Buffer.isBuffer(this.options.cert) || checkPEMType(this.options.cert, "CERTIFICATE")) {
  131. certPromise = this.options.cert;
  132. }
  133. else if(this.options.cert){
  134. // Nothing has matched so attempt to load from disk
  135. certPromise = readFile(this.options.cert);
  136. }
  137. // Prepare Key data if available
  138. var keyPromise = null;
  139. if (this.options.keyData) {
  140. keyPromise = this.options.keyData;
  141. }
  142. else if(Buffer.isBuffer(this.options.key) || checkPEMType(this.options.key, "PRIVATE KEY")) {
  143. keyPromise = this.options.key;
  144. }
  145. else if(this.options.key) {
  146. keyPromise = readFile(this.options.key);
  147. }
  148. // Prepare Certificate Authority data if available.
  149. var caPromises = [];
  150. if (this.options.ca != null && !sysu.isArray(this.options.ca)) {
  151. this.options.ca = [ this.options.ca ];
  152. }
  153. for(var i in this.options.ca) {
  154. var ca = this.options.ca[i];
  155. if(Buffer.isBuffer(ca) || checkPEMType(ca, "CERTIFICATE")) {
  156. caPromises.push(ca);
  157. }
  158. else if (ca){
  159. caPromises.push(readFile(ca));
  160. }
  161. }
  162. if (caPromises.length == 0) {
  163. caPromises = undefined;
  164. }
  165. else {
  166. caPromises = q.all(caPromises);
  167. }
  168. this.initializationPromise = q.all([pfxPromise, certPromise, keyPromise, caPromises]);
  169. return this.initializationPromise;
  170. };
  171. function checkPEMType(input, type) {
  172. if (input == null) {
  173. return;
  174. }
  175. var matches = input.match(/\-\-\-\-\-BEGIN ([A-Z\s*]+)\-\-\-\-\-/);
  176. if (matches != null) {
  177. return matches[1].indexOf(type) >= 0;
  178. }
  179. return false;
  180. }
  181. /**
  182. * You should never need to call this method, initialisation and connection is handled by {@link Connection#pushNotification}
  183. * @private
  184. */
  185. Connection.prototype.connect = function () {
  186. if (this.deferredConnection) {
  187. return this.deferredConnection.promise;
  188. }
  189. debug("Initialising connection");
  190. this.deferredConnection = q.defer();
  191. this.initialize().spread(function (pfxData, certData, keyData, caData) {
  192. var socketOptions = {};
  193. socketOptions.pfx = pfxData;
  194. socketOptions.cert = certData;
  195. socketOptions.key = keyData;
  196. socketOptions.ca = caData;
  197. socketOptions.passphrase = this.options.passphrase;
  198. socketOptions.rejectUnauthorized = this.options.rejectUnauthorized;
  199. // We pass in our own Stream to delay connection until we have attached the
  200. // event listeners below.
  201. socketOptions.socket = new net.Socket();
  202. this.socket = tls.connect(
  203. this.options['port'],
  204. this.options['address'],
  205. socketOptions,
  206. function () {
  207. debug("Connection established");
  208. this.emit('connected', this.sockets.length + 1);
  209. this.deferredConnection.resolve();
  210. }.bind(this));
  211. socketOptions.socket.setNoDelay(this.options.disableNagle);
  212. socketOptions.socket.setKeepAlive(true);
  213. if (this.options.connectionTimeout > 0) {
  214. socketOptions.socket.setTimeout(this.options.connectionTimeout);
  215. }
  216. this.socket.on("error", this.errorOccurred.bind(this, this.socket));
  217. this.socket.on("timeout", this.socketTimeout.bind(this, this.socket));
  218. this.socket.on("data", this.handleTransmissionError.bind(this, this.socket));
  219. this.socket.on("drain", this.socketDrained.bind(this, this.socket, true));
  220. this.socket.once("close", this.socketClosed.bind(this, this.socket));
  221. // The actual connection is delayed until after all the event listeners have
  222. // been attached.
  223. if ("function" == typeof this.socket.connect ) {
  224. this.socket.connect(this.options['port'], this.options['address']);
  225. }
  226. else {
  227. socketOptions.socket.connect(this.options['port'], this.options['address']);
  228. }
  229. }.bind(this)).fail(function (error) {
  230. debug("Module initialisation error:", error);
  231. // This is a pretty fatal scenario, we don't have key/certificate to connect to APNS, there's not much we can do, so raise errors and clear the queue.
  232. while(this.notificationBuffer.length > 0) {
  233. var notification = this.notificationBuffer.shift();
  234. this.raiseError(error, notification.notification, notification.recipient);
  235. this.emit('transmissionError', Errors['moduleInitialisationFailed'], notification.notification, notification.recipient);
  236. }
  237. this.deferredConnection.reject(error);
  238. this.deferredConnection = null;
  239. }.bind(this));
  240. return this.deferredConnection.promise;
  241. };
  242. /**
  243. * @private
  244. */
  245. Connection.prototype.createConnection = function() {
  246. this.connect().then(function () {
  247. this.failureCount = 0;
  248. this.socket.socketId = this.socketId++;
  249. this.socket.currentId = 0;
  250. this.socket.cachedNotifications = [];
  251. this.sockets.push(this.socket);
  252. }.bind(this)).fail(function (error) {
  253. // Exponential backoff when connections fail.
  254. var delay = Math.pow(2, this.failureCount++) * 1000;
  255. this.raiseError(error);
  256. this.emit('error', error);
  257. return q.delay(delay);
  258. }.bind(this)).finally(function () {
  259. this.deferredConnection = null;
  260. this.socket = undefined;
  261. this.serviceBuffer();
  262. }.bind(this));
  263. };
  264. /**
  265. * @private
  266. */
  267. Connection.prototype.initialisingConnection = function() {
  268. if(this.deferredConnection !== null) {
  269. return true;
  270. }
  271. return false;
  272. };
  273. /**
  274. * @private
  275. */
  276. Connection.prototype.serviceBuffer = function() {
  277. var socket = null;
  278. var repeat = false;
  279. if(this.options.fastMode) {
  280. repeat = true;
  281. }
  282. do {
  283. socket = null;
  284. if (this.notificationBuffer.length === 0) break;
  285. for (var i = this.sockets.length - 1; i >= 0; i--) {
  286. if(this.socketAvailable(this.sockets[i])) {
  287. socket = this.sockets[i];
  288. break;
  289. }
  290. }
  291. if (socket !== null) {
  292. debug("Transmitting notification from buffer");
  293. if(this.transmitNotification(socket, this.notificationBuffer.shift())) {
  294. this.socketDrained(socket, !repeat);
  295. }
  296. }
  297. else if (!this.initialisingConnection() && this.sockets.length < this.options.maxConnections) {
  298. this.createConnection();
  299. repeat = false;
  300. }
  301. else {
  302. repeat = false;
  303. }
  304. } while(repeat);
  305. debug("%d left to send", this.notificationBuffer.length);
  306. if (this.notificationBuffer.length === 0 && this.shutdownPending) {
  307. debug("closing connections");
  308. for (var i = this.sockets.length - 1; i >= 0; i--) {
  309. var socket = this.sockets[i];
  310. if (!socket.busy) {
  311. // We delay before closing connections to ensure we don't miss any error packets from the service.
  312. setTimeout(socket.end.bind(socket), 2500);
  313. }
  314. }
  315. if (this.sockets.length == 0) {
  316. this.shutdownPending = false;
  317. }
  318. }
  319. };
  320. /**
  321. * @private
  322. */
  323. Connection.prototype.errorOccurred = function(socket, err) {
  324. debug("Socket error occurred", socket.socketId, err);
  325. if(socket.transmissionErrorOccurred && err.code == 'EPIPE') {
  326. debug("EPIPE occurred after a transmission error which we can ignore");
  327. return;
  328. }
  329. this.emit('socketError', err);
  330. if(this.socket == socket && this.deferredConnection && this.deferredConnection.promise.isPending()) {
  331. this.deferredConnection.reject(err);
  332. }
  333. else {
  334. this.raiseError(err, null);
  335. }
  336. if(socket.busy && socket.cachedNotifications.length > 0) {
  337. // A notification was in flight. It should be buffered for resending.
  338. this.bufferNotification(socket.cachedNotifications[socket.cachedNotifications.length - 1]);
  339. }
  340. this.destroyConnection(socket);
  341. };
  342. /**
  343. * @private
  344. */
  345. Connection.prototype.socketAvailable = function(socket) {
  346. if (!socket || !socket.writable || socket.busy || socket.transmissionErrorOccurred) {
  347. return false;
  348. }
  349. return true;
  350. };
  351. /**
  352. * @private
  353. */
  354. Connection.prototype.socketDrained = function(socket, serviceBuffer) {
  355. debug("Socket drained", socket.socketId);
  356. socket.busy = false;
  357. if(this.options.enhanced) {
  358. var notification = socket.cachedNotifications[socket.cachedNotifications.length - 1];
  359. this.emit('transmitted', notification.notification, notification.recipient);
  360. }
  361. if(serviceBuffer === true && !this.runningOnNextTick) {
  362. // There is a possibility that this could add multiple invocations to the
  363. // call stack unnecessarily. It will be resolved within one event loop but
  364. // should be mitigated if possible, this.nextTick aims to solve this,
  365. // ensuring "serviceBuffer" is only called once per loop.
  366. var nextRun = function() { this.runningOnNextTick = false; this.serviceBuffer(); }.bind(this);
  367. if('function' === typeof setImmediate) {
  368. setImmediate(nextRun);
  369. }
  370. else {
  371. process.nextTick(nextRun);
  372. }
  373. this.runningOnNextTick = true;
  374. }
  375. };
  376. /**
  377. * @private
  378. */
  379. Connection.prototype.socketTimeout = function(socket) {
  380. debug("Socket timeout", socket.socketId);
  381. this.emit('timeout');
  382. this.destroyConnection(socket);
  383. };
  384. /**
  385. * @private
  386. */
  387. Connection.prototype.destroyConnection = function(socket) {
  388. debug("Destroying connection", socket.socketId);
  389. if (socket) {
  390. socket.destroy();
  391. }
  392. };
  393. /**
  394. * @private
  395. */
  396. Connection.prototype.socketClosed = function(socket) {
  397. debug("Socket closed", socket.socketId);
  398. if (socket === this.socket && this.deferredConnection.promise.isPending()) {
  399. debug("Connection error occurred before TLS Handshake");
  400. this.deferredConnection.reject(new Error("Unable to connect"));
  401. }
  402. else {
  403. var index = this.sockets.indexOf(socket);
  404. if (index > -1) {
  405. this.sockets.splice(index, 1);
  406. }
  407. this.emit('disconnected', this.sockets.length);
  408. }
  409. this.serviceBuffer();
  410. };
  411. /**
  412. * Use this method to modify the cache length after initialisation.
  413. */
  414. Connection.prototype.setCacheLength = function(newLength) {
  415. this.options.cacheLength = newLength;
  416. };
  417. /**
  418. * @private
  419. */
  420. Connection.prototype.bufferNotification = function (notification) {
  421. if (notification.retryLimit === 0) {
  422. this.raiseError(Errors['retryLimitExceeded'], notification);
  423. this.emit('transmissionError', Errors['retryLimitExceeded'], notification.notification, notification.recipient);
  424. return;
  425. }
  426. notification.retryLimit -= 1;
  427. this.notificationBuffer.push(notification);
  428. };
  429. /**
  430. * @private
  431. */
  432. Connection.prototype.prepareNotification = function (notification, device) {
  433. var recipient = device;
  434. // If a device token hasn't been given then we should raise an error.
  435. if (recipient === undefined) {
  436. process.nextTick(function () {
  437. this.raiseError(Errors['missingDeviceToken'], notification);
  438. this.emit('transmissionError', Errors['missingDeviceToken'], notification);
  439. }.bind(this));
  440. return;
  441. }
  442. // If we have been passed a token instead of a `Device` then we should convert.
  443. if (!(recipient instanceof Device)) {
  444. try {
  445. recipient = new Device(recipient);
  446. }
  447. catch (e) {
  448. // If an exception has been thrown it's down to an invalid token.
  449. process.nextTick(function () {
  450. this.raiseError(Errors['invalidToken'], notification, device);
  451. this.emit('transmissionError', Errors['invalidToken'], notification, device);
  452. }.bind(this));
  453. return;
  454. }
  455. }
  456. var retryLimit = (notification.retryLimit < 0) ? -1 : notification.retryLimit + 1;
  457. this.bufferNotification( { "notification": notification, "recipient": recipient, "retryLimit": retryLimit } );
  458. };
  459. /**
  460. * @private
  461. */
  462. Connection.prototype.cacheNotification = function (socket, notification) {
  463. socket.cachedNotifications.push(notification);
  464. if (socket.cachedNotifications.length > this.options.cacheLength) {
  465. debug("Clearing notification %d from the cache", socket.cachedNotifications[0]['_uid']);
  466. socket.cachedNotifications.splice(0, socket.cachedNotifications.length - this.options.cacheLength);
  467. }
  468. };
  469. /**
  470. * @private
  471. */
  472. Connection.prototype.handleTransmissionError = function (socket, data) {
  473. if (data[0] == 8) {
  474. socket.transmissionErrorOccurred = true;
  475. if (!this.options.enhanced) {
  476. return;
  477. }
  478. var errorCode = data[1];
  479. var identifier = data.readUInt32BE(2);
  480. var notification = null;
  481. var foundNotification = false;
  482. var temporaryCache = [];
  483. debug("Notification %d caused an error: %d", identifier, errorCode);
  484. while (socket.cachedNotifications.length) {
  485. notification = socket.cachedNotifications.shift();
  486. if (notification['_uid'] == identifier) {
  487. foundNotification = true;
  488. break;
  489. }
  490. temporaryCache.push(notification);
  491. }
  492. if (foundNotification) {
  493. while (temporaryCache.length) {
  494. temporaryCache.shift();
  495. }
  496. this.emit('transmissionError', errorCode, notification.notification, notification.recipient);
  497. this.raiseError(errorCode, notification.notification, notification.recipient);
  498. }
  499. else {
  500. socket.cachedNotifications = temporaryCache;
  501. if(socket.cachedNotifications.length > 0) {
  502. var differentialSize = socket.cachedNotifications[0]['_uid'] - identifier;
  503. this.emit('cacheTooSmall', differentialSize);
  504. if(this.options.autoAdjustCache) {
  505. this.options.cacheLength += differentialSize * 2;
  506. }
  507. }
  508. this.emit('transmissionError', errorCode, null);
  509. this.raiseError(errorCode, null);
  510. }
  511. var count = socket.cachedNotifications.length;
  512. if(this.options.buffersNotifications) {
  513. debug("Buffering %d notifications for resending", count);
  514. for (var i = 0; i < count; ++i) {
  515. notification = socket.cachedNotifications.shift();
  516. this.bufferNotification(notification);
  517. }
  518. }
  519. }
  520. else {
  521. debug("Unknown data received: ", data);
  522. }
  523. };
  524. /**
  525. * @private
  526. */
  527. Connection.prototype.raiseError = function(errorCode, notification, recipient) {
  528. debug("Raising error:", errorCode, notification, recipient);
  529. if(errorCode instanceof Error) {
  530. debug("Error occurred with trace:", errorCode.stack);
  531. }
  532. if (notification && typeof notification.errorCallback == 'function' ) {
  533. notification.errorCallback(errorCode, recipient);
  534. } else if (typeof this.options.errorCallback == 'function') {
  535. this.options.errorCallback(errorCode, notification, recipient);
  536. }
  537. };
  538. /**
  539. * @private
  540. * @return {Boolean} Write completed, returns true if socketDrained should be called by the caller of this method.
  541. */
  542. Connection.prototype.transmitNotification = function(socket, notification) {
  543. if (!this.socketAvailable(socket)) {
  544. this.bufferNotification(notification);
  545. return;
  546. }
  547. var token = notification.recipient.token;
  548. var encoding = notification.notification.encoding || 'utf8';
  549. var message = notification.notification.compile();
  550. var messageLength = Buffer.byteLength(message, encoding);
  551. var position = 0;
  552. var data;
  553. notification._uid = socket.currentId++;
  554. if (socket.currentId > 0xffffffff) {
  555. socket.currentId = 0;
  556. }
  557. if (this.options.legacy) {
  558. if (this.options.enhanced) {
  559. data = new Buffer(1 + 4 + 4 + 2 + token.length + 2 + messageLength);
  560. // Command
  561. data[position] = 1;
  562. position++;
  563. // Identifier
  564. data.writeUInt32BE(notification._uid, position);
  565. position += 4;
  566. // Expiry
  567. data.writeUInt32BE(notification.notification.expiry, position);
  568. position += 4;
  569. this.cacheNotification(socket, notification);
  570. }
  571. else {
  572. data = new Buffer(1 + 2 + token.length + 2 + messageLength);
  573. //Command
  574. data[position] = 0;
  575. position++;
  576. }
  577. // Token Length
  578. data.writeUInt16BE(token.length, position);
  579. position += 2;
  580. // Device Token
  581. position += token.copy(data, position, 0);
  582. // Payload Length
  583. data.writeUInt16BE(messageLength, position);
  584. position += 2;
  585. //Payload
  586. position += data.write(message, position, encoding);
  587. }
  588. else {
  589. // New Protocol uses framed notifications consisting of multiple items
  590. // 1: Device Token
  591. // 2: Payload
  592. // 3: Notification Identifier
  593. // 4: Expiration Date
  594. // 5: Priority
  595. // Each item has a 3 byte header: Type (1), Length (2) followed by data
  596. // The frame layout is hard coded for now as original dynamic system had a
  597. // significant performance penalty
  598. var frameLength = 3 + token.length + 3 + messageLength + 3 + 4;
  599. if(notification.notification.expiry > 0) {
  600. frameLength += 3 + 4;
  601. }
  602. if(notification.notification.priority != 10) {
  603. frameLength += 3 + 1;
  604. }
  605. // Frame has a 5 byte header: Type (1), Length (4) followed by items.
  606. data = new Buffer(5 + frameLength);
  607. data[position] = 2; position += 1;
  608. // Frame Length
  609. data.writeUInt32BE(frameLength, position); position += 4;
  610. // Token Item
  611. data[position] = 1; position += 1;
  612. data.writeUInt16BE(token.length, position); position += 2;
  613. position += token.copy(data, position, 0);
  614. // Payload Item
  615. data[position] = 2; position += 1;
  616. data.writeUInt16BE(messageLength, position); position += 2;
  617. position += data.write(message, position, encoding);
  618. // Identifier Item
  619. data[position] = 3; position += 1;
  620. data.writeUInt16BE(4, position); position += 2;
  621. data.writeUInt32BE(notification._uid, position); position += 4;
  622. if(notification.notification.expiry > 0) {
  623. // Expiry Item
  624. data[position] = 4; position += 1;
  625. data.writeUInt16BE(4, position); position += 2;
  626. data.writeUInt32BE(notification.notification.expiry, position); position += 4;
  627. }
  628. if(notification.notification.priority != 10) {
  629. // Priority Item
  630. data[position] = 5; position += 1;
  631. data.writeUInt16BE(1, position); position += 2;
  632. data[position] = notification.notification.priority; position += 1;
  633. }
  634. this.cacheNotification(socket, notification);
  635. }
  636. socket.busy = true;
  637. return socket.write(data);
  638. };
  639. Connection.prototype.validNotification = function (notification, recipient) {
  640. var messageLength = notification.length();
  641. if (messageLength > 256) {
  642. process.nextTick(function () {
  643. this.raiseError(Errors['invalidPayloadSize'], notification, recipient);
  644. this.emit('transmissionError', Errors['invalidPayloadSize'], notification, recipient);
  645. }.bind(this));
  646. return false;
  647. }
  648. notification.compile();
  649. return true;
  650. };
  651. /**
  652. * Queue a notification for delivery to recipients
  653. * @param {Notification} notification The Notification object to be sent
  654. * @param {Device|String|Buffer|Device[]|String[]|Buffer[]} recipient The token(s) for devices the notification should be delivered to.
  655. * @since v1.3.0
  656. */
  657. Connection.prototype.pushNotification = function (notification, recipient) {
  658. if (!this.validNotification(notification, recipient)) {
  659. return;
  660. }
  661. if (sysu.isArray(recipient)) {
  662. for (var i = recipient.length - 1; i >= 0; i--) {
  663. this.prepareNotification(notification, recipient[i]);
  664. }
  665. }
  666. else {
  667. this.prepareNotification(notification, recipient);
  668. }
  669. this.serviceBuffer();
  670. };
  671. /**
  672. * Send a notification to the APN service
  673. * @param {Notification} notification The notification object to be sent
  674. * @deprecated Since v1.3.0, use pushNotification instead
  675. */
  676. Connection.prototype.sendNotification = function (notification) {
  677. return this.pushNotification(notification, notification.device);
  678. };
  679. /**
  680. * End connections with APNS once we've finished sending all notifications
  681. */
  682. Connection.prototype.shutdown = function () {
  683. debug("Shutdown pending");
  684. this.shutdownPending = true;
  685. };
  686. module.exports = Connection;