@superhuman/push-receiver
Advanced tools
| const { register, listen } = require('../src'); | ||
| const senderId = require('yargs').argv.senderId; | ||
| if (!senderId) { | ||
| console.error('Missing senderId'); | ||
| return; | ||
| } | ||
| (async () => { | ||
| // First time | ||
| // Register to GCM and FCM | ||
| const credentials = await register(senderId); // You should call register only once and then store the credentials somewhere | ||
| const fcmToken = credentials.fcm.token; // Token to use to send notifications | ||
| console.log('Use this following token to send a notification', fcmToken); | ||
| // persistentIds is the list of notification ids received to avoid receiving all already received notifications on start. | ||
| const persistentIds = []; // get all previous persistentIds from somewhere (file, db, etc...) | ||
| await listen({ ...credentials, persistentIds }, onNotification); | ||
| })(); | ||
| // Called on new notification | ||
| function onNotification({ notification }) { | ||
| // Do someting with the notification | ||
| console.log('Notification received'); | ||
| console.log(notification); | ||
| } |
| const request = require('request-promise'); | ||
| const { SENDER_ID, SERVER_KEY } = require('./keys'); | ||
| const { register, listen } = require('../src/index'); | ||
| const NOTIFICATIONS = { | ||
| SIMPLE : { title : 'Hello world ', body : 'Test' }, | ||
| LARGE : { title : 'Hello world ', body : require('./4kb') }, | ||
| }; | ||
| let credentials; | ||
| let client; | ||
| describe('Parser', function() { | ||
| beforeEach(async function() { | ||
| credentials = await register(SENDER_ID); | ||
| }); | ||
| afterEach(async function() { | ||
| client.destroy(); | ||
| credentials = null; | ||
| }); | ||
| it('should receive a simple notification', async function() { | ||
| await send(NOTIFICATIONS.SIMPLE); | ||
| const notifications = await receive(1); | ||
| expect(notifications.length).toEqual(1); | ||
| expect(notifications[0].notification.notification).toEqual( | ||
| NOTIFICATIONS.SIMPLE | ||
| ); | ||
| }); | ||
| it('should receive a large notification', async function() { | ||
| await send(NOTIFICATIONS.LARGE); | ||
| const notifications = await receive(1); | ||
| expect(notifications.length).toEqual(1); | ||
| expect(notifications[0].notification.notification).toEqual( | ||
| NOTIFICATIONS.LARGE | ||
| ); | ||
| }); | ||
| it('should receive multiple notifications', async function() { | ||
| await send(NOTIFICATIONS.SIMPLE); | ||
| await send(NOTIFICATIONS.LARGE); | ||
| await send(NOTIFICATIONS.SIMPLE); | ||
| const notifications = await receive(3); | ||
| expect(notifications.length).toEqual(3); | ||
| expect(notifications[0].notification.notification).toEqual( | ||
| NOTIFICATIONS.SIMPLE | ||
| ); | ||
| expect(notifications[1].notification.notification).toEqual( | ||
| NOTIFICATIONS.LARGE | ||
| ); | ||
| expect(notifications[2].notification.notification).toEqual( | ||
| NOTIFICATIONS.SIMPLE | ||
| ); | ||
| }); | ||
| }); | ||
| async function send(notification) { | ||
| const response = await request({ | ||
| method : 'POST', | ||
| url : 'https://fcm.googleapis.com/fcm/send', | ||
| json : true, | ||
| body : { | ||
| to : credentials.fcm.token, | ||
| notification : notification, | ||
| }, | ||
| headers : { Authorization : `key=${SERVER_KEY}` }, | ||
| }); | ||
| try { | ||
| expect(response.success).toEqual(1); | ||
| } catch (e) { | ||
| throw new Error( | ||
| `sending of notification failed: ${JSON.stringify(response)}` | ||
| ); | ||
| } | ||
| return response; | ||
| } | ||
| async function receive(n) { | ||
| const received = []; | ||
| return new Promise(async resolve => { | ||
| const onNotification = notification => { | ||
| received.push(notification); | ||
| if (received.length === n) { | ||
| resolve(received); | ||
| } | ||
| }; | ||
| credentials.persistentIds = []; | ||
| client = await listen(credentials, onNotification); | ||
| }); | ||
| } |
+22
-9
| { | ||
| "name": "@superhuman/push-receiver", | ||
| "version": "1.2.0", | ||
| "description": "A module to subscribe to GCM/FCM and receive notifications within a node process.", | ||
| "version": "2.1.2", | ||
| "description": | ||
| "A module to subscribe to GCM/FCM and receive notifications within a node process.", | ||
| "main": "src/index.js", | ||
@@ -10,8 +11,18 @@ "scripts": { | ||
| "send": "node scripts/send", | ||
| "pretty": "prettier-eslint --single-quote --trailing-comma es5 --write \"**/*.js\" \"**/*.json\"", | ||
| "pretty:check": "prettier-eslint --single-quote --trailing-comma es5 --list-different --log-level silent \"**/*.js\" \"**/*.json\"", | ||
| "pretty": | ||
| "prettier-eslint --single-quote --trailing-comma es5 --write \"**/*.js\" \"**/*.json\"", | ||
| "pretty:check": | ||
| "prettier-eslint --single-quote --trailing-comma es5 --list-different --log-level silent \"**/*.js\" \"**/*.json\"", | ||
| "lint": "eslint 'src/**/*.js'", | ||
| "lint:fix": "eslint 'src/**/*.js' --fix", | ||
| "test": "mocha test/*_test.js" | ||
| "test": "jest", | ||
| "precommit": "lint-staged" | ||
| }, | ||
| "lint-staged": { | ||
| "*.js": [ | ||
| "eslint --fix", | ||
| "prettier-eslint --single-quote --trailing-comma es5 --write" | ||
| ], | ||
| "*.json": ["prettier-eslint --single-quote --trailing-comma es5 --write"] | ||
| }, | ||
| "repository": { | ||
@@ -42,3 +53,5 @@ "type": "git", | ||
| "http-proxy": "^1.16.2", | ||
| "mocha": "^5.0.0", | ||
| "husky": "^0.14.3", | ||
| "jest": "^22.2.2", | ||
| "lint-staged": "^6.1.0", | ||
| "prettier": "^1.6.1", | ||
@@ -52,7 +65,7 @@ "prettier-eslint": "^7.0.0", | ||
| "long": "^3.2.0", | ||
| "protobufjs": "^6.8.6", | ||
| "request": "^2.88.0", | ||
| "request-promise": "^4.2.4", | ||
| "protobufjs": "^6.8.0", | ||
| "request": "^2.81.0", | ||
| "request-promise": "^4.2.1", | ||
| "uuid": "^3.1.0" | ||
| } | ||
| } |
+71
-45
@@ -7,5 +7,10 @@ const EventEmitter = require('events'); | ||
| const tls = require('tls'); | ||
| const {checkIn} = require('./gcm'); | ||
| const {kMCSVersion, kLoginRequestTag, kDataMessageStanzaTag, kLoginResponseTag} = require('./constants'); | ||
| const {load} = require('protobufjs'); | ||
| const { checkIn } = require('./gcm'); | ||
| const { | ||
| kMCSVersion, | ||
| kLoginRequestTag, | ||
| kDataMessageStanzaTag, | ||
| kLoginResponseTag, | ||
| } = require('./constants'); | ||
| const { load } = require('protobufjs'); | ||
@@ -19,3 +24,3 @@ const HOST = 'mtalk.google.com'; | ||
| module.exports = class Client extends EventEmitter { | ||
| static async init () { | ||
| static async init() { | ||
| if (proto) { | ||
@@ -27,6 +32,7 @@ return; | ||
| constructor(credentials, persistentIds) { | ||
| constructor(credentials, { persistentIds, socketTimeout } = {}) { | ||
| super(); | ||
| this._credentials = credentials; | ||
| this._persistentIds = persistentIds || []; | ||
| this._socketTimeout = socketTimeout; | ||
| this._retryCount = 0; | ||
@@ -38,2 +44,3 @@ this._onSocketConnect = this._onSocketConnect.bind(this); | ||
| this._onParserError = this._onParserError.bind(this); | ||
| this._onSocketTimeout = this._onSocketTimeout.bind(this); | ||
| } | ||
@@ -46,6 +53,10 @@ | ||
| // can happen if the socket immediately closes after being created | ||
| if (!this._socket) { return } | ||
| if (!this._socket) { | ||
| return; | ||
| } | ||
| await Parser.init(); | ||
| // can happen if the socket immediately closes after being created | ||
| if (!this._socket) { return } | ||
| if (!this._socket) { | ||
| return; | ||
| } | ||
| this._parser = new Parser(this._socket); | ||
@@ -60,8 +71,7 @@ this._parser.on('message', this._onMessage); | ||
| getPersistentIds() { | ||
| return this._persistentIds | ||
| } | ||
| async _checkIn() { | ||
| return checkIn(this._credentials.gcm.androidId, this._credentials.gcm.securityToken); | ||
| return checkIn( | ||
| this._credentials.gcm.androidId, | ||
| this._credentials.gcm.securityToken | ||
| ); | ||
| } | ||
@@ -71,7 +81,13 @@ | ||
| this._socket = new tls.TLSSocket(); | ||
| if (this._socketTimeout) { | ||
| this._socket.setTimeout(this._socketTimeout); | ||
| } | ||
| this._socket.setKeepAlive(true); | ||
| this._socket.on('connect', this._onSocketConnect); | ||
| this._socket.on('error', this._onSocketError); | ||
| this._socket.on('timeout', this._onSocketTimeout); | ||
| this._socket.on('close', this._onSocketClose); | ||
| this._socket.on('error', this._onSocketError); | ||
| this._socket.connect({host : HOST, port : PORT}); | ||
| this._socket.connect({ host : HOST, port : PORT }); | ||
| this._socket.write(this._loginBuffer()); | ||
@@ -99,3 +115,5 @@ } | ||
| const LoginRequestType = proto.lookupType('mcs_proto.LoginRequest'); | ||
| const hexAndroidId = Long.fromString(this._credentials.gcm.androidId).toString(16); | ||
| const hexAndroidId = Long.fromString( | ||
| this._credentials.gcm.androidId | ||
| ).toString(16); | ||
| const loginRequest = { | ||
@@ -112,3 +130,3 @@ adaptiveHeartbeat : false, | ||
| useRmq2 : true, | ||
| setting : [{name : 'new_vc', value : '1'}], | ||
| setting : [{ name : 'new_vc', value : '1' }], | ||
| // Id of the last notification received | ||
@@ -123,7 +141,9 @@ clientEvent : [], | ||
| } | ||
| const message = LoginRequestType.create(loginRequest); | ||
| const buffer = LoginRequestType.encodeDelimited(message).finish(); | ||
| // FIXME Can change depending on persistentIds | ||
| return Buffer.concat([Buffer.from([kMCSVersion, kLoginRequestTag]), buffer]); | ||
| const buffer = LoginRequestType.encodeDelimited(loginRequest).finish(); | ||
| return Buffer.concat([ | ||
| Buffer.from([kMCSVersion, kLoginRequestTag]), | ||
| buffer, | ||
| ]); | ||
| } | ||
@@ -137,14 +157,17 @@ | ||
| _onSocketClose() { | ||
| this.emit('disconnect'); | ||
| this._retry(); | ||
| } | ||
| _onSocketError(error) { | ||
| _onSocketError() { | ||
| // ignore, the close handler takes care of retry | ||
| console.error(error); | ||
| } | ||
| _onParserError(error) { | ||
| this.emit('parserError', error) | ||
| _onSocketTimeout() { | ||
| // Socket will be reopened by the _onSocketClose handler | ||
| this._socket.destroy(); | ||
| } | ||
| _onParserError() { | ||
| this._retry(); | ||
| console.error(error); | ||
| } | ||
@@ -158,9 +181,9 @@ | ||
| _onMessage({tag, object}) { | ||
| _onMessage({ tag, object }) { | ||
| if (tag === kLoginResponseTag) { | ||
| // clear persistent ids, as we just sent them to the server while logging | ||
| // in | ||
| this._persistentIds = [] | ||
| this._persistentIds = []; | ||
| } else if (tag === kDataMessageStanzaTag) { | ||
| this._onDataMessage(object) | ||
| this._onDataMessage(object); | ||
| } | ||
@@ -174,21 +197,23 @@ } | ||
| let message | ||
| let message; | ||
| try { | ||
| message = decrypt(object, this._credentials.keys); | ||
| } catch (error) { | ||
| if (error.message.includes('Unsupported state or unable to authenticate data')) { | ||
| // NOTE(ibash) Periodically we're unable to decrypt notifications. In | ||
| // all cases we've been able to receive future notifications using the | ||
| // same keys. So, we silently drop this notification. | ||
| this._persistentIds.push(object.persistentId); | ||
| // NOTE(ibash) for superhuman we want to keep an eye on these errors - | ||
| // so throw them in their own call stack so that they get caught by | ||
| // bugsnag. | ||
| setTimeout(() => { | ||
| throw error | ||
| }, 0) | ||
| return | ||
| } else { | ||
| throw error | ||
| switch (true) { | ||
| case error.message.includes( | ||
| 'Unsupported state or unable to authenticate data' | ||
| ): | ||
| case error.message.includes('crypto-key is missing'): | ||
| case error.message.includes('salt is missing'): | ||
| // NOTE(ibash) Periodically we're unable to decrypt notifications. In | ||
| // all cases we've been able to receive future notifications using the | ||
| // same keys. So, we silently drop this notification. | ||
| console.warn( | ||
| 'Message dropped as it could not be decrypted: ' + error.message | ||
| ); | ||
| this._persistentIds.push(object.persistentId); | ||
| return; | ||
| default: { | ||
| throw error; | ||
| } | ||
| } | ||
@@ -201,6 +226,7 @@ } | ||
| this.emit('ON_NOTIFICATION_RECEIVED', { | ||
| client: this, | ||
| notification : message, | ||
| // Needs to be saved by the client | ||
| persistentId : object.persistentId, | ||
| }); | ||
| } | ||
| }; |
+10
-4
@@ -21,4 +21,10 @@ const crypto = require('crypto'); | ||
| endpoint : `${FCM_ENDPOINT}/${token}`, | ||
| encryption_key : keys.publicKey, | ||
| encryption_key : keys.publicKey | ||
| .replace(/=/g, '') | ||
| .replace(/\+/g, '-') | ||
| .replace(/\//g, '_'), | ||
| encryption_auth : keys.authSecret | ||
| .replace(/=/g, '') | ||
| .replace(/\+/g, '-') | ||
| .replace(/\//g, '_'), | ||
| }, | ||
@@ -41,5 +47,5 @@ }); | ||
| return resolve({ | ||
| privateKey : dh.getPrivateKey('base64'), | ||
| publicKey : dh.getPublicKey('base64'), | ||
| authSecret : buf.toString('base64'), | ||
| privateKey : escape(dh.getPrivateKey('base64')), | ||
| publicKey : escape(dh.getPublicKey('base64')), | ||
| authSecret : escape(buf.toString('base64')), | ||
| }); | ||
@@ -46,0 +52,0 @@ }); |
+5
-0
@@ -9,2 +9,7 @@ const path = require('path'); | ||
| // Hack to fix PHONE_REGISTRATION_ERROR #17 when bundled with webpack | ||
| // https://github.com/dcodeIO/protobuf.js#browserify-integration | ||
| protobuf.util.Long = Long | ||
| protobuf.configure() | ||
| const serverKey = toBase64(Buffer.from(fcmKey)); | ||
@@ -11,0 +16,0 @@ |
+7
-2
@@ -9,3 +9,5 @@ const register = require('./register'); | ||
| async function listen(credentials, notificationCallback) { | ||
| async function listen(credentials, notificationCallback, options = {}) { | ||
| const { socketTimeout } = options; | ||
| if (!credentials) { | ||
@@ -33,3 +35,6 @@ throw new Error('Missing credentials'); | ||
| const client = new Client(credentials, credentials.persistentIds); | ||
| const client = new Client(credentials, { | ||
| persistentIds : credentials.persistentIds, | ||
| socketTimeout, | ||
| }); | ||
| client.on('ON_NOTIFICATION_RECEIVED', notificationCallback); | ||
@@ -36,0 +41,0 @@ client.connect(); |
+18
-11
| const EventEmitter = require('events'); | ||
| const path = require('path'); | ||
| const {load, BufferReader} = require('protobufjs'); | ||
| const { load, BufferReader } = require('protobufjs'); | ||
| const { | ||
@@ -42,3 +42,3 @@ MCS_VERSION_TAG_AND_SIZE, | ||
| module.exports = class Parser extends EventEmitter { | ||
| static async init () { | ||
| static async init() { | ||
| if (proto) { | ||
@@ -74,3 +74,3 @@ return; | ||
| _onData (buffer) { | ||
| _onData(buffer) { | ||
| DEBUG(`Got data: ${buffer.length}`); | ||
@@ -89,3 +89,3 @@ this._data = Buffer.concat([this._data, buffer]); | ||
| switch(this._state) { | ||
| switch (this._state) { | ||
| case MCS_VERSION_TAG_AND_SIZE: | ||
@@ -110,3 +110,6 @@ minBytesNeeded = kVersionPacketLen + kTagPacketLen + kSizePacketLenMin; | ||
| // TODO(ibash) set a timeout and check for socket disconnect | ||
| DEBUG(`Socket read finished prematurely. Waiting for ${minBytesNeeded - this._data.length} more bytes`); | ||
| DEBUG( | ||
| `Socket read finished prematurely. Waiting for ${minBytesNeeded - | ||
| this._data.length} more bytes` | ||
| ); | ||
| this._isWaitingForData = true; | ||
@@ -118,3 +121,3 @@ return; | ||
| switch(this._state) { | ||
| switch (this._state) { | ||
| case MCS_VERSION_TAG_AND_SIZE: | ||
@@ -171,3 +174,3 @@ this._onGotVersion(); | ||
| this._emitError(error); | ||
| return | ||
| return; | ||
| } | ||
@@ -210,3 +213,3 @@ } | ||
| if (this._messageSize === 0) { | ||
| this.emit('message', {tag: this._messageTag, object: {}}); | ||
| this.emit('message', { tag : this._messageTag, object : {} }); | ||
| this._getNextMessage(); | ||
@@ -218,3 +221,7 @@ return; | ||
| // Continue reading data. | ||
| DEBUG(`Continuing data read. Buffer size is ${this._data.length}, expecting ${this._messageSize}`); | ||
| DEBUG( | ||
| `Continuing data read. Buffer size is ${this._data.length}, expecting ${ | ||
| this._messageSize | ||
| }` | ||
| ); | ||
| this._state = MCS_PROTO_BYTES; | ||
@@ -234,3 +241,3 @@ this._waitForData(); | ||
| this.emit('message', {tag: this._messageTag, object: object}); | ||
| this.emit('message', { tag : this._messageTag, object : object }); | ||
@@ -257,3 +264,3 @@ if (this._messageTag === kLoginResponseTag) { | ||
| _buildProtobufFromTag(tag) { | ||
| switch(tag) { | ||
| switch (tag) { | ||
| case kHeartbeatPingTag: | ||
@@ -260,0 +267,0 @@ return proto.lookupType('mcs_proto.HeartbeatPing'); |
@@ -8,8 +8,6 @@ const crypto = require('crypto'); | ||
| function decrypt(object, keys) { | ||
| const cryptoKey = object.appData | ||
| .find(item => item.key === 'crypto-key') | ||
| .value.slice(3); | ||
| const salt = object.appData | ||
| .find(item => item.key === 'encryption') | ||
| .value.slice(5); | ||
| const cryptoKey = object.appData.find(item => item.key === 'crypto-key'); | ||
| if (!cryptoKey) throw new Error('crypto-key is missing'); | ||
| const salt = object.appData.find(item => item.key === 'encryption'); | ||
| if (!salt) throw new Error('salt is missing'); | ||
| const dh = crypto.createECDH('prime256v1'); | ||
@@ -20,5 +18,5 @@ dh.setPrivateKey(keys.privateKey, 'base64'); | ||
| authSecret : keys.authSecret, | ||
| dh : cryptoKey, | ||
| dh : cryptoKey.value.slice(3), | ||
| privateKey : dh, | ||
| salt, | ||
| salt : salt.value.slice(5), | ||
| }; | ||
@@ -25,0 +23,0 @@ const decrypted = ece.decrypt(object.rawData, params); |
+2
-1
@@ -1,1 +0,2 @@ | ||
| module.exports = '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' | ||
| module.exports = | ||
| 'iVBORw0KGgoAAAANSUhEUgAAAAwAAAACCAYAAABsfz2XAAAMFGlDQ1BJQ0MgUHJvZmlsZQAASImVVwdUk8kWnr+kEBJaINIJvQnSq0DoIAhIBxshCRBKgISgYkcXFVy7WFBUdAVE0bUAshZE7CyCvT8QUVlZFws2VN6kgK6vnXfPmX++3Ln3zncnd+bMAKBsx87Ly0ZVAMgRFAijg/2YiUnJTFIPwIAe0AA6QIvNEeX5RkWFAyij/d/l3S2ASPrrNpJY/zr+X0WVyxNxAECiIE7lijg5EB8FANfk5AkLACC0Q73xrII8CR6EWF0ICQJAxCU4XYY1JThVhsdLbWKj/SFmAUCmstnCdACUJLyZhZx0GEdJwtFOwOULIN4GsTcng82F+AHE43NyciFWJkNskfpdnPS/xUwdi8lmp49hWS5SIQfwRXnZ7Dn/53L8b8nJFo/OYQQbNUMYEi3JGa5bTVZumARTIT4hSI2IhFgN4ot8rtRegu9liEPi5PYDHJE/XDPAAAAFXHZAGMS6EDPEWXG+cuzAFkp9oT0awS8IjZXjVGFutDw+WijIjgiXx1mewQsdxZU8UWDMqE0aPygUYlhp6NGijNgEGU+0rZAfHwGxEsSdoqyYMLnvo6IM/4hRG6E4WsLZBOK3acKgaJkNppkjGs0Ls+WwpXPBWsBYBRmxITJfLJEnSgwf5cDlBQTKOGBcniBOzg2D1eUXLfctycuOkttjlbzs4GjZOmOHRIUxo77XCmCBydYBe5zJnhQln+tdXkFUrIwbjoJw4A8CABOIYUsFuSAT8DsGGgfgL9lIEGADIUgHPGAj14x6JEhHBPAbA4rAnxDxgGjMz086ygOFUP9lTCv72oA06Wih1CMLPIU4B9fGvXFPPBx+WbA54G64+6gfU3l0VmIgMYAYQgwiWo7x4EDW2bAJAf/f6MJgz4PZSbgIRnP4Fo/wlNBFeEy4Segm3AXx4Ik0itxqJr9Y+ANzJpgMumG0IHl2qd9nh5tB1s64H+4F+UPuOAPXBja4E8zEF/eBuTlD7fcMxWPcvq3lj/NJWH+fj1yvZKXkLGeROvbP+I9Z/RjF/7s14sI+7EdLbDl2BLuAncEuYSewRsDETmNNWDt2UoLHKuGJtBJGZ4uWcsuCcfijNnZ1dv12n3+Ymy2fX7JeogLe7ALJZvDPzZsj5KdnFDB94WnMY4YKOLbjmQ529q4ASM522dHxhiE9sxHG5W+6/BYA3EuhMv2bjm0MwPGnANDffdMZv4blvgaAk50csbBQppMcx4AAKEAZ7gotoA+MgQXMxwG4AE/AAoFgEogEsSAJzIArngFyIOdZYB5YDEpAGVgDNoKtYAfYDWrAAXAYNIIT4Aw4D66ATnAT3Id10QdegEHwDgwjCEJCaAgd0UIMEFPEGnFA3BBvJBAJR6KRJCQFSUcEiBiZhyxBypB1yFZkF1KL/IocR84gl5Au5C7Sg/Qjr5FPKIZSUXVUDzVDJ6BuqC8ahsai09F0NB8tQpeiq9DNaBW6H21Az6BX0JtoN/oCHcIApogxMEPMBnPD/LFILBlLw4TYAqwUK8eqsHqsGf7P17FubAD7iBNxOs7EbWBthuBxOAfPxxfgK/GteA3egLfh1/EefBD/SqARdAnWBA9CKCGRkE6YRSghlBP2Eo4RzsF900d4RyQSGURzoivcl0nETOJc4kriduJBYguxi9hLHCKRSFoka5IXKZLEJhWQSkhbSPtJp0nXSH2kD2RFsgHZgRxETiYLyMXkcvI+8inyNfIz8rCCioKpgodCpAJXYY7CaoU9Cs0KVxX6FIYpqhRzihcllpJJWUzZTKmnnKM8oLxRVFQ0UnRXnKLIV1ykuFnxkOJFxR7Fj1Q1qhXVnzqNKqauolZTW6h3qW9oNJoZjUVLphXQVtFqaWdpj2gflOhKtkqhSlylhUoVSg1K15ReKisomyr7Ks9QLlIuVz6ifFV5QEVBxUzFX4WtskClQuW4ym2VIVW6qr1qpGqO6krVfaqXVJ+rkdTM1ALVuGpL1XarnVXrpWN0Y7o/nUNfQt9DP0fvUyeqm6uHqmeql6kfUO9QH9RQ03DSiNeYrVGhcVKjm4ExzBihjGzGasZhxi3Gp3F643zH8catGFc/7tq495o6mixNnmap5kHNm5qftJhagVpZWmu1GrUeauPaVtpTtGdpV2qf0x7QUdfx1OHolOoc1rmni+pa6UbrztXdrduuO6Snrxesl6e3Re+s3oA+Q5+ln6m/Qf+Ufr8B3cDbgG+wweC0wR9MDaYvM5u5mdnGHDTUNQwxFBvuMuwwHDYyN4ozKjY6aPTQmGLsZpxmvMG41XjQxMBkssk8kzqTe6YKpm6mGaabTC+YvjczN0swW2bWaPbcXNM81LzIvM78gQXNwsci36LK4oYl0dLNMstyu2WnFWrlbJVhVWF11Rq1drHmW2+37hpPGO8+XjC+avxtG6qNr02hTZ1Njy3DNty22LbR9uUEkwnJE9ZOuDDhq52zXbbdHrv79mr2k+yL7ZvtXztYOXAcKhxuONIcgxwXOjY5vnKyduI5VTrdcaY7T3Ze5tzq/MXF1UXoUu/S72rimuK6zfW2m7pblNtKt4vuBHc/94XuJ9w/erh4FHgc9vjL08Yzy3Of5/OJ5hN5E/dM7PUy8mJ77fLq9mZ6p3jv9O72MfRh+1T5PGYZs7isvaxnvpa+mb77fV/62fkJ/Y75vff38J/v3xKABQQHlAZ0BKoFxgVuDXwUZBSUHlQXNBjsHDw3uCWEEBIWsjbkdqheKCe0NnRwkuuk+ZPawqhhMWFbwx6HW4ULw5sno5MnTV4/+UGEaYQgojESRIZGro98GGUelR/12xTilKgpFVOeRttHz4u+EEOPmRmzL+ZdrF/s6tj7cRZx4rjWeOX4afG18e8TAhLWJXQnTkicn3glSTuJn9SUTEqOT96bPDQ1cOrGqX3TnKeVTLs13Xz67OmXZmjPyJ5xcqbyTPbMIymElISUfSmf2ZHsKvZQamjqttRBjj9nE+cFl8XdwO3nefHW8Z6leaWtS3ue7pW+Pr0/wyejPGOA78/fyn+VGZK5I/N9VmRWddZIdkL2wRxyTkrOcYGaIEvQlqufOzu3K886rySvO98jf2P+oDBMuFeEiKaLmgrU4TWnXWwh/kncU+hdWFH4YVb8rCOzVWcLZrfPsZqzYs6zoqCiX+biczlzW+cZzls8r2e+7/xdC5AFqQtaFxovXLqwb1HwoprFlMVZi38vtiteV/x2ScKS5qV6Sxct7f0p+Ke6EqUSYcntZZ7LdizHl/OXd6xwXLFlxddSbunlMruy8rLPKzkrL/9s//Pmn0dWpa3qWO2yunINcY1gza21Pmtr1qmuK1rXu37y+oYNzA2lG95unLnxUrlT+Y5NlE3iTd2bwzc3bTHZsmbL560ZW29W+FUc3Ka7bcW299u5269Vsirrd+jtKNvxaSd/551dwbsaqsyqyncTdxfufronfs+FX9x+qd2rvbds75dqQXV3TXRNW61rbe0+3X2r69A6cV3//mn7Ow8EHGiqt6nfdZBxsOwQOCQ+9MevKb/eOhx2uPWI25H6o6ZHtx2jHyttQBrmNAw2ZjR2NyU1dR2fdLy12bP52G+2v1WfMDxRcVLj5OpTlFNLT42cLjo91JLXMnAm/Uxv68zW+2cTz95om9LWcS7s3MXzQefPXvC9cPqi18UTlzwuHb/sdrnxisuVhnbn9mO/O/9+rMOlo+Gq69WmTvfO5q6JXaeu+Vw7cz3g+vkboTeu3Iy42XUr7tad29Nud9/h3nl+N/vuq3uF94bvL3pAeFD6UOVh+SPdR1X/sPzHwW6X7pM9AT3tj2Me3+/l9L54InryuW/pU9rT8mcGz2qfOzw/0R/U3/nH1D/6XuS9GB4o+'; |
| // Set a sender id and server key and save this under test/keys.js | ||
| module.exports = { | ||
| SENDER_ID: 'Your sender id here', | ||
| SERVER_KEY: 'Your server key here' | ||
| } | ||
| SENDER_ID : 'Your sender id here', | ||
| SERVER_KEY : 'Your server key here', | ||
| }; |
| const assert = require('assert') | ||
| const request = require('request-promise') | ||
| const {SENDER_ID, SERVER_KEY} = require('./keys') | ||
| const {register, listen} = require('../src/index') | ||
| const NOTIFICATIONS = { | ||
| SIMPLE: {title: 'Hello world ', body: 'Test'}, | ||
| LARGE: {title: 'Hello world ', body: require('./4kb')} | ||
| } | ||
| describe('Parser', function() { | ||
| beforeEach(async function() { | ||
| let credentials = await register(SENDER_ID) | ||
| this.send = async (notification) => { | ||
| let response = await request({ | ||
| method: 'POST', | ||
| url: 'https://fcm.googleapis.com/fcm/send', | ||
| json: true, | ||
| body: { | ||
| to: credentials.fcm.token, | ||
| notification: notification, | ||
| }, | ||
| headers: {Authorization : `key=${SERVER_KEY}`}, | ||
| }) | ||
| assert.equal(response.success, 1, `sending of notification failed: ${JSON.stringify(response)}`) | ||
| return response | ||
| } | ||
| this.receive = async (n) => { | ||
| let received = [] | ||
| return new Promise(async (resolve) => { | ||
| let onNotification = (notification) => { | ||
| received.push(notification) | ||
| if (received.length == n) { | ||
| resolve(received) | ||
| } | ||
| } | ||
| credentials.persistentIds = [] | ||
| this.client = await listen(credentials, onNotification) | ||
| }) | ||
| } | ||
| }) | ||
| afterEach(async function() { | ||
| this.client.destroy() | ||
| }) | ||
| it('should receive a simple notification', async function() { | ||
| await this.send(NOTIFICATIONS.SIMPLE) | ||
| let notifications = await this.receive(1) | ||
| assert.equal(notifications.length, 1) | ||
| assert.deepEqual(notifications[0].notification.notification, NOTIFICATIONS.SIMPLE) | ||
| }) | ||
| it('should receive a large notification', async function() { | ||
| await this.send(NOTIFICATIONS.LARGE) | ||
| let notifications = await this.receive(1) | ||
| assert.equal(notifications.length, 1) | ||
| assert.deepEqual(notifications[0].notification.notification, NOTIFICATIONS.LARGE) | ||
| }) | ||
| it('should multiple notifications', async function() { | ||
| await this.send(NOTIFICATIONS.SIMPLE) | ||
| await this.send(NOTIFICATIONS.LARGE) | ||
| await this.send(NOTIFICATIONS.SIMPLE) | ||
| let notifications = await this.receive(3) | ||
| assert.equal(notifications.length, 3) | ||
| assert.deepEqual(notifications[0].notification.notification, NOTIFICATIONS.SIMPLE) | ||
| assert.deepEqual(notifications[1].notification.notification, NOTIFICATIONS.LARGE) | ||
| assert.deepEqual(notifications[2].notification.notification, NOTIFICATIONS.SIMPLE) | ||
| }) | ||
| }) |
Sorry, the diff of this file is too big to display
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
959
9.6%56007
-58.77%11
22.22%Updated
Updated
Updated