From 7aa75971bafcd4b8b64876d835a07275df56d72f Mon Sep 17 00:00:00 2001 From: senbar Date: Thu, 4 Jan 2024 13:47:10 +0100 Subject: [PATCH] Changed from commonJS module to ESM. --- .eslintrc | 7 +- package.json | 3 +- src/cli.js | 68 +-- src/encryption-helper.js | 16 +- src/index.js | 37 +- src/urlsafe-base64-helper.js | 8 +- src/vapid-helper.js | 50 +-- src/web-push-constants.js | 4 +- src/web-push-error.js | 8 +- src/web-push-lib.js | 564 ++++++++++++------------- test/.eslintrc | 3 + test/data/demo/service-worker.js | 6 +- test/helpers/create-server.js | 22 +- test/helpers/download-test-browsers.js | 44 +- test/test-cli.js | 70 ++- test/test-encryption-helper.js | 42 +- test/test-generate-request-details.js | 41 +- test/test-set-vapid-details.js | 28 +- test/test-vapid-helper.js | 88 ++-- test/testSelenium.js | 338 ++++++++------- test/testSendNotification.js | 189 +++++---- test/testSetGCMAPIKey.js | 30 +- 22 files changed, 812 insertions(+), 854 deletions(-) diff --git a/.eslintrc b/.eslintrc index 81c2f577..a74e3613 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,6 +3,9 @@ "env": { "es6": true }, + "parserOptions": { + "sourceType": "module" + }, "rules": { "no-var": 2, "no-const-assign": 2, @@ -11,12 +14,12 @@ "vars-on-top": 0, "no-use-before-define": 0, "space-before-function-paren": 0, - "max-len": [1, 120, 2, {"ignoreComments": true, "ignoreUrls": true}], + "max-len": [1, 120, 2, { "ignoreComments": true, "ignoreUrls": true }], "no-param-reassign": 0, "quote-props": 0, "wrap-iife": [2, "inside"], "import/no-unresolved": 0, "indent": 0, - "no-buffer-constructor": 0, // We still support Node v4. + "no-buffer-constructor": 0 // We still support Node v4. } } diff --git a/package.json b/package.json index 2181cff4..76069102 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "web-push", "version": "3.6.6", "description": "Web Push library for Node.js", - "main": "src/index.js", + "type": "module", + "exports": "./src/index.js", "bin": { "web-push": "src/cli.js" }, diff --git a/src/cli.js b/src/cli.js index fc644b05..fb7392ad 100755 --- a/src/cli.js +++ b/src/cli.js @@ -1,9 +1,7 @@ #! /usr/bin/env node /* eslint consistent-return:0 */ - -'use strict'; - -const webPush = require('../src/index.js'); +import * as webPush from '../src/index.js'; +import minimist from 'minimist'; const printUsageDetails = () => { const actions = [ @@ -50,9 +48,9 @@ const generateVapidKeys = returnJson => { } else { const outputLine = '\n=======================================\n'; outputText = outputLine + '\n' - + 'Public Key:\n' + vapidKeys.publicKey + '\n\n' - + 'Private Key:\n' + vapidKeys.privateKey + '\n' - + outputLine; + + 'Public Key:\n' + vapidKeys.publicKey + '\n\n' + + 'Private Key:\n' + vapidKeys.privateKey + '\n' + + outputLine; } console.log(outputText); @@ -80,7 +78,7 @@ const sendNotification = args => { options.TTL = args.ttl; } - if (argv['vapid-subject'] || argv['vapid-pubkey'] || argv['vapid-pvtkey']) { + if (args['vapid-subject'] || args['vapid-pubkey'] || args['vapid-pvtkey']) { options.vapidDetails = { subject: args['vapid-subject'] || null, publicKey: args['vapid-pubkey'] || null, @@ -101,31 +99,35 @@ const sendNotification = args => { } webPush.sendNotification(subscription, payload, options) - .then(() => { - console.log('Push message sent.'); - }, err => { - console.log('Error sending push message: '); - console.log(err); - }) - .then(() => { - process.exit(0); - }); + .then(() => { + console.log('Push message sent.'); + }, err => { + console.log('Error sending push message: '); + console.log(err); + }) + .then(() => { + process.exit(0); + }); }; -const action = process.argv[2]; -const argv = require('minimist')(process.argv.slice(3)); -switch (action) { - case 'send-notification': - if (!argv.endpoint) { - return printUsageDetails(); - } +const executeCliAction = () => { + const action = process.argv[2]; + const argv = minimist(process.argv.slice(3)); + switch (action) { + case 'send-notification': + if (!argv.endpoint) { + return printUsageDetails(); + } + + sendNotification(argv); + break; + case 'generate-vapid-keys': + generateVapidKeys(argv.json || false); + break; + default: + printUsageDetails(); + break; + } +}; - sendNotification(argv); - break; - case 'generate-vapid-keys': - generateVapidKeys(argv.json || false); - break; - default: - printUsageDetails(); - break; -} +executeCliAction(); diff --git a/src/encryption-helper.js b/src/encryption-helper.js index 779e2ad0..f655824b 100644 --- a/src/encryption-helper.js +++ b/src/encryption-helper.js @@ -1,9 +1,7 @@ -'use strict'; +import crypto from 'crypto'; +import ece from 'http_ece'; -const crypto = require('crypto'); -const ece = require('http_ece'); - -const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) { +export function encrypt(userPublicKey, userAuth, payload, contentEncoding) { if (!userPublicKey) { throw new Error('No user public key provided for encryption.'); } @@ -26,7 +24,7 @@ const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) { if (Buffer.from(userAuth, 'base64url').length < 16) { throw new Error('The subscription auth key should be at least 16 ' - + 'bytes long'); + + 'bytes long'); } if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) { @@ -55,8 +53,4 @@ const encrypt = function(userPublicKey, userAuth, payload, contentEncoding) { salt: salt, cipherText: cipherText }; -}; - -module.exports = { - encrypt: encrypt -}; +} diff --git a/src/index.js b/src/index.js index f7b36741..aa20456e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,21 +1,24 @@ -'use strict'; - -const vapidHelper = require('./vapid-helper.js'); -const encryptionHelper = require('./encryption-helper.js'); -const WebPushLib = require('./web-push-lib.js'); -const WebPushError = require('./web-push-error.js'); -const WebPushConstants = require('./web-push-constants.js'); +import { getVapidHeaders, generateVAPIDKeys } from './vapid-helper.js'; +import { encrypt } from './encryption-helper.js'; +import { WebPushLib } from './web-push-lib.js'; +import WebPushError from './web-push-error.js'; +import WebPushConstants from './web-push-constants.js'; const webPush = new WebPushLib(); -module.exports = { - WebPushError: WebPushError, - supportedContentEncodings: WebPushConstants.supportedContentEncodings, - encrypt: encryptionHelper.encrypt, - getVapidHeaders: vapidHelper.getVapidHeaders, - generateVAPIDKeys: vapidHelper.generateVAPIDKeys, - setGCMAPIKey: webPush.setGCMAPIKey, - setVapidDetails: webPush.setVapidDetails, - generateRequestDetails: webPush.generateRequestDetails, - sendNotification: webPush.sendNotification.bind(webPush) +const { supportedContentEncodings } = WebPushConstants; +const { setGCMAPIKey, setVapidDetails, generateRequestDetails } = webPush; +const sendNotification = webPush.sendNotification.bind(webPush); + +// Exporting variables and functions +export { + WebPushError, + supportedContentEncodings, + encrypt, // renaming for clarity + getVapidHeaders, + generateVAPIDKeys, + setGCMAPIKey, + setVapidDetails, + generateRequestDetails, + sendNotification }; diff --git a/src/urlsafe-base64-helper.js b/src/urlsafe-base64-helper.js index 30650fa2..645b934a 100644 --- a/src/urlsafe-base64-helper.js +++ b/src/urlsafe-base64-helper.js @@ -1,13 +1,7 @@ -'use strict'; - /** * @param {string} base64 * @returns {boolean} */ -function validate(base64) { +export function validate(base64) { return /^[A-Za-z0-9\-_]+$/.test(base64); } - -module.exports = { - validate: validate -}; diff --git a/src/vapid-helper.js b/src/vapid-helper.js index c0305bdd..08789a6f 100644 --- a/src/vapid-helper.js +++ b/src/vapid-helper.js @@ -1,12 +1,10 @@ -'use strict'; +import crypto from 'crypto'; +import asn1 from 'asn1.js'; +import jws from 'jws'; +import { URL } from 'url'; -const crypto = require('crypto'); -const asn1 = require('asn1.js'); -const jws = require('jws'); -const { URL } = require('url'); - -const WebPushConstants = require('./web-push-constants.js'); -const urlBase64Helper = require('./urlsafe-base64-helper'); +import WebPushConstants from './web-push-constants.js'; +import * as urlBase64Helper from './urlsafe-base64-helper.js'; /** * DEFAULT_EXPIRATION is set to seconds in 12 hours @@ -16,7 +14,7 @@ const DEFAULT_EXPIRATION_SECONDS = 12 * 60 * 60; // Maximum expiration is 24 hours according. (See VAPID spec) const MAX_EXPIRATION_SECONDS = 24 * 60 * 60; -const ECPrivateKeyASN = asn1.define('ECPrivateKey', function() { +const ECPrivateKeyASN = asn1.define('ECPrivateKey', function () { this.seq().obj( this.key('version').int(), this.key('privateKey').octstr(), @@ -37,7 +35,7 @@ function toPEM(key) { }); } -function generateVAPIDKeys() { +export function generateVAPIDKeys() { const curve = crypto.createECDH('prime256v1'); curve.generateKeys(); @@ -65,14 +63,14 @@ function generateVAPIDKeys() { }; } -function validateSubject(subject) { +export function validateSubject(subject) { if (!subject) { throw new Error('No subject set in vapidDetails.subject.'); } if (typeof subject !== 'string' || subject.length === 0) { throw new Error('The subject value must be a string containing an https: URL or ' - + 'mailto: address. ' + subject); + + 'mailto: address. ' + subject); } let subjectParseResult = null; @@ -88,17 +86,17 @@ function validateSubject(subject) { console.warn('Vapid subject points to a localhost web URI, which is unsupported by ' + 'Apple\'s push notification server and will result in a BadJwtToken error when ' + 'sending notifications.'); - } + } } -function validatePublicKey(publicKey) { +export function validatePublicKey(publicKey) { if (!publicKey) { throw new Error('No key set vapidDetails.publicKey'); } if (typeof publicKey !== 'string') { throw new Error('Vapid public key is must be a URL safe Base 64 ' - + 'encoded string.'); + + 'encoded string.'); } if (!urlBase64Helper.validate(publicKey)) { @@ -112,14 +110,14 @@ function validatePublicKey(publicKey) { } } -function validatePrivateKey(privateKey) { +export function validatePrivateKey(privateKey) { if (!privateKey) { throw new Error('No key set in vapidDetails.privateKey'); } if (typeof privateKey !== 'string') { throw new Error('Vapid private key must be a URL safe Base 64 ' - + 'encoded string.'); + + 'encoded string.'); } if (!urlBase64Helper.validate(privateKey)) { @@ -141,7 +139,7 @@ function validatePrivateKey(privateKey) { * @param {Number} numSeconds Number of seconds to be added * @return {Number} Future expiration in seconds */ -function getFutureExpirationTimestamp(numSeconds) { +export function getFutureExpirationTimestamp(numSeconds) { const futureExp = new Date(); futureExp.setSeconds(futureExp.getSeconds() + numSeconds); return Math.floor(futureExp.getTime() / 1000); @@ -153,7 +151,7 @@ function getFutureExpirationTimestamp(numSeconds) { * * @param {Number} expiration Expiration seconds from Epoch to be validated */ -function validateExpiration(expiration) { +export function validateExpiration(expiration) { if (!Number.isInteger(expiration)) { throw new Error('`expiration` value must be a number'); } @@ -184,14 +182,14 @@ function validateExpiration(expiration) { * @return {Object} Returns an Object with the Authorization and * 'Crypto-Key' values to be used as headers. */ -function getVapidHeaders(audience, subject, publicKey, privateKey, contentEncoding, expiration) { +export function getVapidHeaders(audience, subject, publicKey, privateKey, contentEncoding, expiration) { if (!audience) { throw new Error('No audience could be generated for VAPID.'); } if (typeof audience !== 'string' || audience.length === 0) { throw new Error('The audience value must be a string containing the ' - + 'origin of a push service. ' + audience); + + 'origin of a push service. ' + audience); } try { @@ -243,13 +241,3 @@ function getVapidHeaders(audience, subject, publicKey, privateKey, contentEncodi throw new Error('Unsupported encoding type specified.'); } - -module.exports = { - generateVAPIDKeys: generateVAPIDKeys, - getFutureExpirationTimestamp: getFutureExpirationTimestamp, - getVapidHeaders: getVapidHeaders, - validateSubject: validateSubject, - validatePublicKey: validatePublicKey, - validatePrivateKey: validatePrivateKey, - validateExpiration: validateExpiration -}; diff --git a/src/web-push-constants.js b/src/web-push-constants.js index 0d0a8b74..a2d0f10d 100644 --- a/src/web-push-constants.js +++ b/src/web-push-constants.js @@ -1,5 +1,3 @@ -'use strict'; - const WebPushConstants = {}; WebPushConstants.supportedContentEncodings = { @@ -14,4 +12,4 @@ WebPushConstants.supportedUrgency = { HIGH: 'high' }; -module.exports = WebPushConstants; +export default WebPushConstants; diff --git a/src/web-push-error.js b/src/web-push-error.js index 94a1cda2..b0ff72eb 100644 --- a/src/web-push-error.js +++ b/src/web-push-error.js @@ -1,6 +1,6 @@ -'use strict'; +import util from 'util'; -function WebPushError(message, statusCode, headers, body, endpoint) { +export default function WebPushError(message, statusCode, headers, body, endpoint) { Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; @@ -11,6 +11,4 @@ function WebPushError(message, statusCode, headers, body, endpoint) { this.endpoint = endpoint; } -require('util').inherits(WebPushError, Error); - -module.exports = WebPushError; +util.inherits(WebPushError, Error); diff --git a/src/web-push-lib.js b/src/web-push-lib.js index a47df054..abe78417 100644 --- a/src/web-push-lib.js +++ b/src/web-push-lib.js @@ -1,13 +1,11 @@ -'use strict'; +import url from 'url'; +import https from 'https'; -const url = require('url'); -const https = require('https'); - -const WebPushError = require('./web-push-error.js'); -const vapidHelper = require('./vapid-helper.js'); -const encryptionHelper = require('./encryption-helper.js'); -const webPushConstants = require('./web-push-constants.js'); -const urlBase64Helper = require('./urlsafe-base64-helper'); +import WebPushError from './web-push-error.js'; +import * as vapidHelper from './vapid-helper.js'; +import * as encryptionHelper from './encryption-helper.js'; +import webPushConstants from './web-push-constants.js'; +import * as urlBase64Helper from './urlsafe-base64-helper.js'; // Default TTL is four weeks. const DEFAULT_TTL = 2419200; @@ -15,7 +13,7 @@ const DEFAULT_TTL = 2419200; let gcmAPIKey = ''; let vapidDetails; -function WebPushLib() { +export function WebPushLib() { } @@ -25,15 +23,15 @@ function WebPushLib() { * to sendNotification(). * @param {string} apiKey The API key to send with the GCM request. */ -WebPushLib.prototype.setGCMAPIKey = function(apiKey) { +WebPushLib.prototype.setGCMAPIKey = function (apiKey) { if (apiKey === null) { gcmAPIKey = null; return; } if (typeof apiKey === 'undefined' - || typeof apiKey !== 'string' - || apiKey.length === 0) { + || typeof apiKey !== 'string' + || apiKey.length === 0) { throw new Error('The GCM API Key should be a non-empty string or null.'); } @@ -50,276 +48,276 @@ WebPushLib.prototype.setGCMAPIKey = function(apiKey) { * @param {string} publicKey The public VAPID key, a URL safe, base64 encoded string * @param {string} privateKey The private VAPID key, a URL safe, base64 encoded string. */ -WebPushLib.prototype.setVapidDetails = function(subject, publicKey, privateKey) { - if (arguments.length === 1 && arguments[0] === null) { - vapidDetails = null; - return; - } +WebPushLib.prototype.setVapidDetails = function (subject, publicKey, privateKey) { + if (arguments.length === 1 && arguments[0] === null) { + vapidDetails = null; + return; + } - vapidHelper.validateSubject(subject); - vapidHelper.validatePublicKey(publicKey); - vapidHelper.validatePrivateKey(privateKey); + vapidHelper.validateSubject(subject); + vapidHelper.validatePublicKey(publicKey); + vapidHelper.validatePrivateKey(privateKey); - vapidDetails = { - subject: subject, - publicKey: publicKey, - privateKey: privateKey - }; + vapidDetails = { + subject: subject, + publicKey: publicKey, + privateKey: privateKey }; +}; - /** - * To get the details of a request to trigger a push message, without sending - * a push notification call this method. - * - * This method will throw an error if there is an issue with the input. - * @param {PushSubscription} subscription The PushSubscription you wish to - * send the notification to. - * @param {string|Buffer} [payload] The payload you wish to send to the - * the user. - * @param {Object} [options] Options for the GCM API key and - * vapid keys can be passed in if they are unique for each notification you - * wish to send. - * @return {Object} This method returns an Object which - * contains 'endpoint', 'method', 'headers' and 'payload'. - */ -WebPushLib.prototype.generateRequestDetails = function(subscription, payload, options) { - if (!subscription || !subscription.endpoint) { - throw new Error('You must pass in a subscription with at least ' +/** + * To get the details of a request to trigger a push message, without sending + * a push notification call this method. + * + * This method will throw an error if there is an issue with the input. + * @param {PushSubscription} subscription The PushSubscription you wish to + * send the notification to. + * @param {string|Buffer} [payload] The payload you wish to send to the + * the user. + * @param {Object} [options] Options for the GCM API key and + * vapid keys can be passed in if they are unique for each notification you + * wish to send. + * @return {Object} This method returns an Object which + * contains 'endpoint', 'method', 'headers' and 'payload'. + */ +WebPushLib.prototype.generateRequestDetails = function (subscription, payload, options) { + if (!subscription || !subscription.endpoint) { + throw new Error('You must pass in a subscription with at least ' + 'an endpoint.'); - } + } - if (typeof subscription.endpoint !== 'string' + if (typeof subscription.endpoint !== 'string' || subscription.endpoint.length === 0) { - throw new Error('The subscription endpoint must be a string with ' + throw new Error('The subscription endpoint must be a string with ' + 'a valid URL.'); - } + } - if (payload) { - // Validate the subscription keys - if (typeof subscription !== 'object' || !subscription.keys + if (payload) { + // Validate the subscription keys + if (typeof subscription !== 'object' || !subscription.keys || !subscription.keys.p256dh || !subscription.keys.auth) { - throw new Error('To send a message with a payload, the ' + throw new Error('To send a message with a payload, the ' + 'subscription must have \'auth\' and \'p256dh\' keys.'); - } } + } - let currentGCMAPIKey = gcmAPIKey; - let currentVapidDetails = vapidDetails; - let timeToLive = DEFAULT_TTL; - let extraHeaders = {}; - let contentEncoding = webPushConstants.supportedContentEncodings.AES_128_GCM; - let urgency = webPushConstants.supportedUrgency.NORMAL; - let topic; - let proxy; - let agent; - let timeout; - - if (options) { - const validOptionKeys = [ - 'headers', - 'gcmAPIKey', - 'vapidDetails', - 'TTL', - 'contentEncoding', - 'urgency', - 'topic', - 'proxy', - 'agent', - 'timeout' - ]; - const optionKeys = Object.keys(options); - for (let i = 0; i < optionKeys.length; i += 1) { - const optionKey = optionKeys[i]; - if (!validOptionKeys.includes(optionKey)) { - throw new Error('\'' + optionKey + '\' is an invalid option. ' + let currentGCMAPIKey = gcmAPIKey; + let currentVapidDetails = vapidDetails; + let timeToLive = DEFAULT_TTL; + let extraHeaders = {}; + let contentEncoding = webPushConstants.supportedContentEncodings.AES_128_GCM; + let urgency = webPushConstants.supportedUrgency.NORMAL; + let topic; + let proxy; + let agent; + let timeout; + + if (options) { + const validOptionKeys = [ + 'headers', + 'gcmAPIKey', + 'vapidDetails', + 'TTL', + 'contentEncoding', + 'urgency', + 'topic', + 'proxy', + 'agent', + 'timeout' + ]; + const optionKeys = Object.keys(options); + for (let i = 0; i < optionKeys.length; i += 1) { + const optionKey = optionKeys[i]; + if (!validOptionKeys.includes(optionKey)) { + throw new Error('\'' + optionKey + '\' is an invalid option. ' + 'The valid options are [\'' + validOptionKeys.join('\', \'') + '\'].'); - } } + } - if (options.headers) { - extraHeaders = options.headers; - let duplicates = Object.keys(extraHeaders) - .filter(function (header) { - return typeof options[header] !== 'undefined'; - }); + if (options.headers) { + extraHeaders = options.headers; + let duplicates = Object.keys(extraHeaders) + .filter(function (header) { + return typeof options[header] !== 'undefined'; + }); - if (duplicates.length > 0) { - throw new Error('Duplicated headers defined [' + if (duplicates.length > 0) { + throw new Error('Duplicated headers defined [' + duplicates.join(',') + ']. Please either define the header in the' + 'top level options OR in the \'headers\' key.'); - } } + } - if (options.gcmAPIKey) { - currentGCMAPIKey = options.gcmAPIKey; - } + if (options.gcmAPIKey) { + currentGCMAPIKey = options.gcmAPIKey; + } - // Falsy values are allowed here so one can skip Vapid `else if` below and use FCM - if (options.vapidDetails !== undefined) { - currentVapidDetails = options.vapidDetails; - } + // Falsy values are allowed here so one can skip Vapid `else if` below and use FCM + if (options.vapidDetails !== undefined) { + currentVapidDetails = options.vapidDetails; + } - if (options.TTL !== undefined) { - timeToLive = Number(options.TTL); - if (timeToLive < 0) { - throw new Error('TTL should be a number and should be at least 0'); - } + if (options.TTL !== undefined) { + timeToLive = Number(options.TTL); + if (timeToLive < 0) { + throw new Error('TTL should be a number and should be at least 0'); } + } - if (options.contentEncoding) { - if ((options.contentEncoding === webPushConstants.supportedContentEncodings.AES_128_GCM - || options.contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM)) { - contentEncoding = options.contentEncoding; - } else { - throw new Error('Unsupported content encoding specified.'); - } + if (options.contentEncoding) { + if ((options.contentEncoding === webPushConstants.supportedContentEncodings.AES_128_GCM + || options.contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM)) { + contentEncoding = options.contentEncoding; + } else { + throw new Error('Unsupported content encoding specified.'); } + } - if (options.urgency) { - if ((options.urgency === webPushConstants.supportedUrgency.VERY_LOW - || options.urgency === webPushConstants.supportedUrgency.LOW - || options.urgency === webPushConstants.supportedUrgency.NORMAL - || options.urgency === webPushConstants.supportedUrgency.HIGH)) { - urgency = options.urgency; - } else { - throw new Error('Unsupported urgency specified.'); - } + if (options.urgency) { + if ((options.urgency === webPushConstants.supportedUrgency.VERY_LOW + || options.urgency === webPushConstants.supportedUrgency.LOW + || options.urgency === webPushConstants.supportedUrgency.NORMAL + || options.urgency === webPushConstants.supportedUrgency.HIGH)) { + urgency = options.urgency; + } else { + throw new Error('Unsupported urgency specified.'); } + } - if (options.topic) { - if (!urlBase64Helper.validate(options.topic)) { - throw new Error('Unsupported characters set use the URL or filename-safe Base64 characters set'); - } - if (options.topic.length > 32) { - throw new Error('use maximum of 32 characters from the URL or filename-safe Base64 characters set'); - } - topic = options.topic; + if (options.topic) { + if (!urlBase64Helper.validate(options.topic)) { + throw new Error('Unsupported characters set use the URL or filename-safe Base64 characters set'); } - - if (options.proxy) { - if (typeof options.proxy === 'string' - || typeof options.proxy.host === 'string') { - proxy = options.proxy; - } else { - console.warn('Attempt to use proxy option, but invalid type it should be a string or proxy options object.'); - } + if (options.topic.length > 32) { + throw new Error('use maximum of 32 characters from the URL or filename-safe Base64 characters set'); } + topic = options.topic; + } - if (options.agent) { - if (options.agent instanceof https.Agent) { - if (proxy) { - console.warn('Agent option will be ignored because proxy option is defined.'); - } + if (options.proxy) { + if (typeof options.proxy === 'string' + || typeof options.proxy.host === 'string') { + proxy = options.proxy; + } else { + console.warn('Attempt to use proxy option, but invalid type it should be a string or proxy options object.'); + } + } - agent = options.agent; - } else { - console.warn('Wrong type for the agent option, it should be an instance of https.Agent.'); + if (options.agent) { + if (options.agent instanceof https.Agent) { + if (proxy) { + console.warn('Agent option will be ignored because proxy option is defined.'); } - } - if (typeof options.timeout === 'number') { - timeout = options.timeout; + agent = options.agent; + } else { + console.warn('Wrong type for the agent option, it should be an instance of https.Agent.'); } } - if (typeof timeToLive === 'undefined') { - timeToLive = DEFAULT_TTL; + if (typeof options.timeout === 'number') { + timeout = options.timeout; } + } - const requestDetails = { - method: 'POST', - headers: { - TTL: timeToLive - } - }; - Object.keys(extraHeaders).forEach(function (header) { - requestDetails.headers[header] = extraHeaders[header]; - }); - let requestPayload = null; - - if (payload) { - const encrypted = encryptionHelper - .encrypt(subscription.keys.p256dh, subscription.keys.auth, payload, contentEncoding); - - requestDetails.headers['Content-Length'] = encrypted.cipherText.length; - requestDetails.headers['Content-Type'] = 'application/octet-stream'; - - if (contentEncoding === webPushConstants.supportedContentEncodings.AES_128_GCM) { - requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_128_GCM; - } else if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) { - requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_GCM; - requestDetails.headers.Encryption = 'salt=' + encrypted.salt; - requestDetails.headers['Crypto-Key'] = 'dh=' + encrypted.localPublicKey.toString('base64url'); - } + if (typeof timeToLive === 'undefined') { + timeToLive = DEFAULT_TTL; + } - requestPayload = encrypted.cipherText; - } else { - requestDetails.headers['Content-Length'] = 0; + const requestDetails = { + method: 'POST', + headers: { + TTL: timeToLive + } + }; + Object.keys(extraHeaders).forEach(function (header) { + requestDetails.headers[header] = extraHeaders[header]; + }); + let requestPayload = null; + + if (payload) { + const encrypted = encryptionHelper + .encrypt(subscription.keys.p256dh, subscription.keys.auth, payload, contentEncoding); + + requestDetails.headers['Content-Length'] = encrypted.cipherText.length; + requestDetails.headers['Content-Type'] = 'application/octet-stream'; + + if (contentEncoding === webPushConstants.supportedContentEncodings.AES_128_GCM) { + requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_128_GCM; + } else if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) { + requestDetails.headers['Content-Encoding'] = webPushConstants.supportedContentEncodings.AES_GCM; + requestDetails.headers.Encryption = 'salt=' + encrypted.salt; + requestDetails.headers['Crypto-Key'] = 'dh=' + encrypted.localPublicKey.toString('base64url'); } - const isGCM = subscription.endpoint.startsWith('https://android.googleapis.com/gcm/send'); - const isFCM = subscription.endpoint.startsWith('https://fcm.googleapis.com/fcm/send'); - // VAPID isn't supported by GCM hence the if, else if. - if (isGCM) { - if (!currentGCMAPIKey) { - console.warn('Attempt to send push notification to GCM endpoint, ' + requestPayload = encrypted.cipherText; + } else { + requestDetails.headers['Content-Length'] = 0; + } + + const isGCM = subscription.endpoint.startsWith('https://android.googleapis.com/gcm/send'); + const isFCM = subscription.endpoint.startsWith('https://fcm.googleapis.com/fcm/send'); + // VAPID isn't supported by GCM hence the if, else if. + if (isGCM) { + if (!currentGCMAPIKey) { + console.warn('Attempt to send push notification to GCM endpoint, ' + 'but no GCM key is defined. Please use setGCMApiKey() or add ' + '\'gcmAPIKey\' as an option.'); - } else { - requestDetails.headers.Authorization = 'key=' + currentGCMAPIKey; - } - } else if (currentVapidDetails) { - const parsedUrl = url.parse(subscription.endpoint); - const audience = parsedUrl.protocol + '//' + } else { + requestDetails.headers.Authorization = 'key=' + currentGCMAPIKey; + } + } else if (currentVapidDetails) { + const parsedUrl = url.parse(subscription.endpoint); + const audience = parsedUrl.protocol + '//' + parsedUrl.host; - const vapidHeaders = vapidHelper.getVapidHeaders( - audience, - currentVapidDetails.subject, - currentVapidDetails.publicKey, - currentVapidDetails.privateKey, - contentEncoding - ); + const vapidHeaders = vapidHelper.getVapidHeaders( + audience, + currentVapidDetails.subject, + currentVapidDetails.publicKey, + currentVapidDetails.privateKey, + contentEncoding + ); - requestDetails.headers.Authorization = vapidHeaders.Authorization; + requestDetails.headers.Authorization = vapidHeaders.Authorization; - if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) { - if (requestDetails.headers['Crypto-Key']) { - requestDetails.headers['Crypto-Key'] += ';' + if (contentEncoding === webPushConstants.supportedContentEncodings.AES_GCM) { + if (requestDetails.headers['Crypto-Key']) { + requestDetails.headers['Crypto-Key'] += ';' + vapidHeaders['Crypto-Key']; - } else { - requestDetails.headers['Crypto-Key'] = vapidHeaders['Crypto-Key']; - } + } else { + requestDetails.headers['Crypto-Key'] = vapidHeaders['Crypto-Key']; } - } else if (isFCM && currentGCMAPIKey) { - requestDetails.headers.Authorization = 'key=' + currentGCMAPIKey; } + } else if (isFCM && currentGCMAPIKey) { + requestDetails.headers.Authorization = 'key=' + currentGCMAPIKey; + } - requestDetails.headers.Urgency = urgency; + requestDetails.headers.Urgency = urgency; - if (topic) { - requestDetails.headers.Topic = topic; - } + if (topic) { + requestDetails.headers.Topic = topic; + } - requestDetails.body = requestPayload; - requestDetails.endpoint = subscription.endpoint; + requestDetails.body = requestPayload; + requestDetails.endpoint = subscription.endpoint; - if (proxy) { - requestDetails.proxy = proxy; - } + if (proxy) { + requestDetails.proxy = proxy; + } - if (agent) { - requestDetails.agent = agent; - } + if (agent) { + requestDetails.agent = agent; + } - if (timeout) { - requestDetails.timeout = timeout; - } + if (timeout) { + requestDetails.timeout = timeout; + } - return requestDetails; - }; + return requestDetails; +}; /** * To send a push notification call this method with a subscription, optional @@ -335,79 +333,77 @@ WebPushLib.prototype.generateRequestDetails = function(subscription, payload, op * resolves if the sending of the notification was successful, otherwise it * rejects. */ -WebPushLib.prototype.sendNotification = function(subscription, payload, options) { - let requestDetails; - try { - requestDetails = this.generateRequestDetails(subscription, payload, options); - } catch (err) { - return Promise.reject(err); - } - - return new Promise(function(resolve, reject) { - const httpsOptions = {}; - const urlParts = url.parse(requestDetails.endpoint); - httpsOptions.hostname = urlParts.hostname; - httpsOptions.port = urlParts.port; - httpsOptions.path = urlParts.path; +WebPushLib.prototype.sendNotification = function (subscription, payload, options) { + let requestDetails; + try { + requestDetails = this.generateRequestDetails(subscription, payload, options); + } catch (err) { + return Promise.reject(err); + } - httpsOptions.headers = requestDetails.headers; - httpsOptions.method = requestDetails.method; + return new Promise(function (resolve, reject) { + const httpsOptions = {}; + const urlParts = url.parse(requestDetails.endpoint); + httpsOptions.hostname = urlParts.hostname; + httpsOptions.port = urlParts.port; + httpsOptions.path = urlParts.path; - if (requestDetails.timeout) { - httpsOptions.timeout = requestDetails.timeout; - } + httpsOptions.headers = requestDetails.headers; + httpsOptions.method = requestDetails.method; - if (requestDetails.agent) { - httpsOptions.agent = requestDetails.agent; - } + if (requestDetails.timeout) { + httpsOptions.timeout = requestDetails.timeout; + } - if (requestDetails.proxy) { - const { HttpsProxyAgent } = require('https-proxy-agent'); // eslint-disable-line global-require - httpsOptions.agent = new HttpsProxyAgent(requestDetails.proxy); - } + if (requestDetails.agent) { + httpsOptions.agent = requestDetails.agent; + } - const pushRequest = https.request(httpsOptions, function(pushResponse) { - let responseText = ''; + if (requestDetails.proxy) { + const { HttpsProxyAgent } = require('https-proxy-agent'); // eslint-disable-line global-require + httpsOptions.agent = new HttpsProxyAgent(requestDetails.proxy); + } - pushResponse.on('data', function(chunk) { - responseText += chunk; - }); + const pushRequest = https.request(httpsOptions, function (pushResponse) { + let responseText = ''; - pushResponse.on('end', function() { - if (pushResponse.statusCode < 200 || pushResponse.statusCode > 299) { - reject(new WebPushError( - 'Received unexpected response code', - pushResponse.statusCode, - pushResponse.headers, - responseText, - requestDetails.endpoint - )); - } else { - resolve({ - statusCode: pushResponse.statusCode, - body: responseText, - headers: pushResponse.headers - }); - } - }); + pushResponse.on('data', function (chunk) { + responseText += chunk; }); - if (requestDetails.timeout) { - pushRequest.on('timeout', function() { - pushRequest.destroy(new Error('Socket timeout')); - }); - } - - pushRequest.on('error', function(e) { - reject(e); + pushResponse.on('end', function () { + if (pushResponse.statusCode < 200 || pushResponse.statusCode > 299) { + reject(new WebPushError( + 'Received unexpected response code', + pushResponse.statusCode, + pushResponse.headers, + responseText, + requestDetails.endpoint + )); + } else { + resolve({ + statusCode: pushResponse.statusCode, + body: responseText, + headers: pushResponse.headers + }); + } }); + }); - if (requestDetails.body) { - pushRequest.write(requestDetails.body); - } + if (requestDetails.timeout) { + pushRequest.on('timeout', function () { + pushRequest.destroy(new Error('Socket timeout')); + }); + } - pushRequest.end(); + pushRequest.on('error', function (e) { + reject(e); }); - }; -module.exports = WebPushLib; + if (requestDetails.body) { + pushRequest.write(requestDetails.body); + } + + pushRequest.end(); + }); +}; diff --git a/test/.eslintrc b/test/.eslintrc index 48924543..51fb1f3e 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -5,5 +5,8 @@ "rules": { "max-len": 0, "global-require": 0 + }, + "parserOptions": { + "sourceType": "module" } } diff --git a/test/data/demo/service-worker.js b/test/data/demo/service-worker.js index 99702fa2..21f16e45 100644 --- a/test/data/demo/service-worker.js +++ b/test/data/demo/service-worker.js @@ -1,12 +1,10 @@ /* Needed because of https://github.com/airbnb/javascript/issues/1632 */ /* eslint no-restricted-globals: 0 */ -'use strict'; - let port; let pushMessage; -self.addEventListener('push', function(event) { +self.addEventListener('push', function (event) { pushMessage = event.data ? event.data.text() : 'no payload'; if (port) { @@ -19,7 +17,7 @@ self.addEventListener('push', function(event) { })); }); -self.onmessage = function(e) { +self.onmessage = function (e) { port = e.ports[0]; if (pushMessage) { diff --git a/test/helpers/create-server.js b/test/helpers/create-server.js index eded6891..fb39428d 100644 --- a/test/helpers/create-server.js +++ b/test/helpers/create-server.js @@ -1,14 +1,12 @@ -'use strict'; +import http from 'http'; +import portfinder from 'portfinder'; +import fs from 'fs'; +import path from 'path'; -const http = require('http'); -const portfinder = require('portfinder'); -const fs = require('fs'); -const path = require('path'); - -function createServer() { +export function createServer() { const demoPath = 'test/data/demo'; - const server = http.createServer(function(req, res) { + const server = http.createServer(function (req, res) { try { if (req.method === 'GET') { // Ignore query parameters which are used to inject application keys @@ -40,7 +38,7 @@ function createServer() { } }); - portfinder.getPort(function(err, port) { + portfinder.getPort(function (err, port) { if (err) { server.port = 50005; } else { @@ -49,11 +47,9 @@ function createServer() { server.listen(server.port); }); - return new Promise(function(resolve) { - server.on('listening', function() { + return new Promise(function (resolve) { + server.on('listening', function () { resolve(server); }); }); } - -module.exports = createServer; diff --git a/test/helpers/download-test-browsers.js b/test/helpers/download-test-browsers.js index f34e11d6..eca9cab6 100644 --- a/test/helpers/download-test-browsers.js +++ b/test/helpers/download-test-browsers.js @@ -1,7 +1,5 @@ -'use strict'; - -const os = require('os'); -const seleniumAssistant = require('selenium-assistant'); +import os from 'os'; +import seleniumAssistant from 'selenium-assistant'; const MAX_RETRIES = 3; let expiration; @@ -14,18 +12,18 @@ const downloadBrowser = (name, version, attempt) => { return new Promise((resolve, reject) => { seleniumAssistant.downloadLocalBrowser(name, version, expiration) - .catch((err) => { - if (attempt < MAX_RETRIES) { - console.log(`Attempt ${attempt + 1} of browser ${name} - ${version} failed.`); - return downloadBrowser(name, version, attempt + 1); - } - - return reject(err); - }) - .then(() => { - console.log(`Successfully downloaded ${name} - ${version}.`); - resolve(); - }); + .catch((err) => { + if (attempt < MAX_RETRIES) { + console.log(`Attempt ${attempt + 1} of browser ${name} - ${version} failed.`); + return downloadBrowser(name, version, attempt + 1); + } + + return reject(err); + }) + .then(() => { + console.log(`Successfully downloaded ${name} - ${version}.`); + resolve(); + }); }); }; @@ -48,10 +46,10 @@ if (os.platform() !== 'darwin') { } Promise.all(promises) -.then(function() { - console.log('Download complete.'); -}) -.catch(function(err) { - console.error('Unable to download browsers.', err); - process.exit(1); -}); + .then(function () { + console.log('Download complete.'); + }) + .catch(function (err) { + console.error('Unable to download browsers.', err); + process.exit(1); + }); diff --git a/test/test-cli.js b/test/test-cli.js index 471b13af..5136a69e 100644 --- a/test/test-cli.js +++ b/test/test-cli.js @@ -1,34 +1,32 @@ -'use strict'; +import assert from 'assert'; +import { spawn } from 'child_process'; -(function() { +(function () { const invalidNodeVersions = /0.(10|12).(\d+)/; if (process.versions.node.match(invalidNodeVersions)) { console.log('Skipping CLI tests as they can\'t run on node: ' + process.versions.node); return; } - const assert = require('assert'); - const spawn = require('child_process').spawn; - const cliPath = 'src/cli.js'; - suite('Test CLI', function() { - test('no args run', function() { - return new Promise(function(resolve) { + suite('Test CLI', function () { + test('no args run', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath ]); let errorData = ''; let consoleOutput = ''; - webpushCLISpawn.stdout.on('data', function(data) { + webpushCLISpawn.stdout.on('data', function (data) { consoleOutput += data; }); - webpushCLISpawn.stderr.on('data', function(data) { + webpushCLISpawn.stderr.on('data', function (data) { errorData += data; }); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { // No args should have code 1 assert(code, 1); @@ -40,14 +38,14 @@ }); }); - test('test send-notification no args', function() { - return new Promise(function(resolve) { + test('test send-notification no args', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath, 'send-notification' ]); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { // No args should have code 1 assert.equal(code, 1); resolve(); @@ -55,8 +53,8 @@ }); }); - test('test send-notification only endpoint', function() { - return new Promise(function(resolve) { + test('test send-notification only endpoint', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath, 'send-notification', @@ -65,15 +63,15 @@ let errorData = ''; let consoleOutput = ''; - webpushCLISpawn.stdout.on('data', function(data) { + webpushCLISpawn.stdout.on('data', function (data) { consoleOutput += data; }); - webpushCLISpawn.stderr.on('data', function(data) { + webpushCLISpawn.stderr.on('data', function (data) { errorData += data; }); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { assert.equal(code, 0); assert.equal(errorData, ''); assert.equal(consoleOutput.indexOf('Error sending push message: '), 0); @@ -82,8 +80,8 @@ }); }); - test('test send-notification all options', function() { - return new Promise(function(resolve) { + test('test send-notification all options', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath, 'send-notification', @@ -101,15 +99,15 @@ let errorData = ''; let consoleOutput = ''; - webpushCLISpawn.stdout.on('data', function(data) { + webpushCLISpawn.stdout.on('data', function (data) { consoleOutput += data; }); - webpushCLISpawn.stderr.on('data', function(data) { + webpushCLISpawn.stderr.on('data', function (data) { errorData += data; }); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { assert.equal(code, 0); assert.equal(errorData, ''); assert.equal(consoleOutput.indexOf('Error sending push message: '), 0); @@ -118,8 +116,8 @@ }); }); - test('test generate vapid keys', function() { - return new Promise(function(resolve) { + test('test generate vapid keys', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath, 'generate-vapid-keys' @@ -127,28 +125,28 @@ let errorData = ''; let consoleOutput = ''; - webpushCLISpawn.stdout.on('data', function(data) { + webpushCLISpawn.stdout.on('data', function (data) { consoleOutput += data; }); - webpushCLISpawn.stderr.on('data', function(data) { + webpushCLISpawn.stderr.on('data', function (data) { errorData += data; }); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { assert.equal(code, 0); assert.equal(errorData, ''); assert.notEqual(consoleOutput.indexOf('Public Key:'), -1); assert.notEqual(consoleOutput.indexOf('Private Key:'), -1); const lines = consoleOutput.split('\n'); - const publicKeyTitleIndex = lines.findIndex(function(line) { + const publicKeyTitleIndex = lines.findIndex(function (line) { return line.indexOf('Public Key:') !== -1; }); const publicKey = lines[publicKeyTitleIndex + 1].trim(); assert.equal(Buffer.from(publicKey, 'base64url').length, 65); - const privateKeyTitleIndex = lines.findIndex(function(line) { + const privateKeyTitleIndex = lines.findIndex(function (line) { return line.indexOf('Private Key:') !== -1; }); const privateKey = lines[privateKeyTitleIndex + 1].trim(); @@ -158,8 +156,8 @@ }); }); - test('test generate JSON vapid keys', function() { - return new Promise(function(resolve) { + test('test generate JSON vapid keys', function () { + return new Promise(function (resolve) { const webpushCLISpawn = spawn('node', [ cliPath, 'generate-vapid-keys', @@ -168,15 +166,15 @@ let errorData = ''; let consoleOutput = ''; - webpushCLISpawn.stdout.on('data', function(data) { + webpushCLISpawn.stdout.on('data', function (data) { consoleOutput += data; }); - webpushCLISpawn.stderr.on('data', function(data) { + webpushCLISpawn.stderr.on('data', function (data) { errorData += data; }); - webpushCLISpawn.on('close', function(code) { + webpushCLISpawn.on('close', function (code) { assert.equal(code, 0); assert.equal(errorData, ''); diff --git a/test/test-encryption-helper.js b/test/test-encryption-helper.js index 364d3b5f..a499b6b5 100644 --- a/test/test-encryption-helper.js +++ b/test/test-encryption-helper.js @@ -1,16 +1,14 @@ -'use strict'; - -const assert = require('assert'); -const crypto = require('crypto'); -const webPush = require('../src/index'); -const ece = require('http_ece'); +import assert from 'assert'; +import crypto from 'crypto'; +import * as webPush from '../src/index.js'; +import ece from 'http_ece'; const userCurve = crypto.createECDH('prime256v1'); const VALID_PUBLIC_KEY = userCurve.generateKeys().toString('base64url'); const VALID_AUTH = crypto.randomBytes(16).toString('base64url'); -suite('Test Encryption Helpers', function() { - test('is defined', function() { +suite('Test Encryption Helpers', function () { + test('is defined', function () { assert(webPush.encrypt); }); @@ -26,63 +24,63 @@ suite('Test Encryption Helpers', function() { }); } - test('encrypt/decrypt string (aesgcm)', function() { + test('encrypt/decrypt string (aesgcm)', function () { assert(encryptDecrypt('hello', webPush.supportedContentEncodings.AES_GCM).equals(Buffer.from('hello'))); }); - test('encrypt/decrypt string (aes128gcm)', function() { + test('encrypt/decrypt string (aes128gcm)', function () { assert(encryptDecrypt('hello', webPush.supportedContentEncodings.AES_128_GCM).equals(Buffer.from('hello'))); }); - test('encrypt/decrypt buffer (aesgcm)', function() { + test('encrypt/decrypt buffer (aesgcm)', function () { assert(encryptDecrypt(Buffer.from('hello'), webPush.supportedContentEncodings.AES_GCM).equals(Buffer.from('hello'))); }); - test('encrypt/decrypt buffer (aes128gcm)', function() { + test('encrypt/decrypt buffer (aes128gcm)', function () { assert(encryptDecrypt(Buffer.from('hello'), webPush.supportedContentEncodings.AES_128_GCM).equals(Buffer.from('hello'))); }); // userPublicKey, userAuth, payload const badInputs = [ - function(contentEncoding) { + function (contentEncoding) { webPush.encrypt(null, null, null, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // Invalid public key webPush.encrypt(null, VALID_AUTH, 'Example', contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // Invalid auth webPush.encrypt(VALID_PUBLIC_KEY, null, 'Example', contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // No payload webPush.encrypt(VALID_PUBLIC_KEY, VALID_AUTH, null, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // Invalid auth size webPush.encrypt(VALID_PUBLIC_KEY, 'Fake', 'Example', contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // Invalid auth size webPush.encrypt(VALID_PUBLIC_KEY, VALID_AUTH, [], contentEncoding); } ]; function testBadInput(contentEncoding) { - badInputs.forEach(function(badInput, index) { - assert.throws(function() { + badInputs.forEach(function (badInput, index) { + assert.throws(function () { badInput(contentEncoding); console.log('Encryption input failed to throw: ' + index); }); }); } - test('bad input to encrypt (aesgcm)', function() { + test('bad input to encrypt (aesgcm)', function () { testBadInput(webPush.supportedContentEncodings.AES_GCM); }); - test('bad input to encrypt (aes128gcm)', function() { + test('bad input to encrypt (aes128gcm)', function () { testBadInput(webPush.supportedContentEncodings.AES_128_GCM); }); }); diff --git a/test/test-generate-request-details.js b/test/test-generate-request-details.js index 007de665..cd8a5e26 100644 --- a/test/test-generate-request-details.js +++ b/test/test-generate-request-details.js @@ -1,21 +1,20 @@ -'use strict'; +import assert from 'assert'; +import { generateRequestDetails } from '../src/index.js'; +import crypto from 'crypto'; +import jws from 'jws'; +import { parse as urlParse } from 'url'; +import https from 'https'; +import { generateVAPIDKeys } from '../src/vapid-helper.js'; -const assert = require('assert'); -const { generateRequestDetails } = require('../src/index'); -const crypto = require('crypto'); -const jws = require('jws'); -const urlParse = require('url').parse; -const https = require('https'); - -suite('Test Generate Request Details', function() { - test('is defined', function() { +suite('Test Generate Request Details', function () { + test('is defined', function () { assert(generateRequestDetails); }); const userCurve = crypto.createECDH('prime256v1'); const userPublicKey = userCurve.generateKeys(); const userAuth = crypto.randomBytes(16); - const vapidKeys = require('../src/vapid-helper').generateVAPIDKeys(); + const vapidKeys = generateVAPIDKeys(); const VALID_KEYS = { p256dh: userPublicKey.toString('base64url'), @@ -229,18 +228,18 @@ suite('Test Generate Request Details', function() { } ]; - invalidRequests.forEach(function(invalidRequest) { - test(invalidRequest.testTitle, function() { + invalidRequests.forEach(function (invalidRequest) { + test(invalidRequest.testTitle, function () { if (invalidRequest.addEndpoint) { invalidRequest.requestOptions.subscription.endpoint = 'https://127.0.0.1:8080'; } if (invalidRequest.serverFlags) { invalidRequest.requestOptions.subscription.endpoint += '?' - + invalidRequest.serverFlags.join('&'); + + invalidRequest.serverFlags.join('&'); } - assert.throws(function() { + assert.throws(function () { return generateRequestDetails( invalidRequest.requestOptions.subscription, invalidRequest.requestOptions.message, @@ -250,7 +249,7 @@ suite('Test Generate Request Details', function() { }); }); - test('Extra headers', function() { + test('Extra headers', function () { let subscription = { endpoint: 'https://127.0.0.1:8080' }; let message; let extraOptions = { @@ -270,7 +269,7 @@ suite('Test Generate Request Details', function() { assert.equal(details.headers.Urgency, extraOptions.headers.Urgency); }); - test('Audience contains port with aes128gcm', function() { + test('Audience contains port with aes128gcm', function () { const subscription = { endpoint: 'http://example.com:4242/life-universe-and-everything' }; @@ -297,7 +296,7 @@ suite('Test Generate Request Details', function() { assert.equal(audience, 'http://example.com:4242', 'Audience contains expected value with port'); }); - test('Audience contains port with aesgcm', function() { + test('Audience contains port with aesgcm', function () { const subscription = { endpoint: 'http://example.com:4242/life-universe-and-everything' }; @@ -325,7 +324,7 @@ suite('Test Generate Request Details', function() { assert.equal(audience, 'http://example.com:4242', 'Audience contains expected value with port'); }); - test('Proxy option', function() { + test('Proxy option', function () { let subscription = { endpoint: 'https://127.0.0.1:8080' }; let message; let extraOptions = { @@ -339,7 +338,7 @@ suite('Test Generate Request Details', function() { assert.equal(details.proxy, extraOptions.proxy); }); - test('Proxy option as an object', function() { + test('Proxy option as an object', function () { let subscription = { endpoint: 'https://127.0.0.1:8080' }; @@ -355,7 +354,7 @@ suite('Test Generate Request Details', function() { assert.equal(details.proxy, extraOptions.proxy); }); - test('Agent option as an https.Agent instance', function() { + test('Agent option as an https.Agent instance', function () { let subscription = { endpoint: 'https://127.0.0.1:8080' }; diff --git a/test/test-set-vapid-details.js b/test/test-set-vapid-details.js index 657017d8..1358b1b7 100644 --- a/test/test-set-vapid-details.js +++ b/test/test-set-vapid-details.js @@ -1,32 +1,30 @@ -'use strict'; - -const assert = require('assert'); -const { setVapidDetails } = require('../src/index'); +import assert from 'assert'; +import { setVapidDetails } from '../src/index.js'; const VALID_SUBJECT_MAILTO = 'mailto: example@example.com'; const VALID_SUBJECT_URL = 'https://exampe.com/contact'; const VALID_PUBLIC_KEY = Buffer.alloc(65).toString('base64url'); const VALID_PRIVATE_KEY = Buffer.alloc(32).toString('base64url'); -suite('setVapidDetails()', function() { - test('is defined', function() { +suite('setVapidDetails()', function () { + test('is defined', function () { assert(setVapidDetails); }); - test('Valid URL input', function() { - assert.doesNotThrow(function() { + test('Valid URL input', function () { + assert.doesNotThrow(function () { setVapidDetails(VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }); }); - test('Valid mailto: input', function() { - assert.doesNotThrow(function() { + test('Valid mailto: input', function () { + assert.doesNotThrow(function () { setVapidDetails(VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }); }); - test('reset Vapid Details with null', function() { - assert.doesNotThrow(function() { + test('reset Vapid Details with null', function () { + assert.doesNotThrow(function () { setVapidDetails(null); }); }); @@ -104,9 +102,9 @@ suite('setVapidDetails()', function() { } ]; - test('Invalid input should throw an error', function() { - invalidInputs.forEach(function(invalidInput, index) { - assert.throws(function() { + test('Invalid input should throw an error', function () { + invalidInputs.forEach(function (invalidInput, index) { + assert.throws(function () { setVapidDetails( invalidInput.subject, invalidInput.publicKey, diff --git a/test/test-vapid-helper.js b/test/test-vapid-helper.js index 949a352d..77a4bb93 100644 --- a/test/test-vapid-helper.js +++ b/test/test-vapid-helper.js @@ -1,11 +1,9 @@ -'use strict'; - -const assert = require('assert'); -const sinon = require('sinon'); -const crypto = require('crypto'); -const mocha = require('mocha'); -const webPush = require('../src/index'); -const vapidHelper = require('../src/vapid-helper'); +import assert from 'assert'; +import sinon from 'sinon'; +import crypto from 'crypto'; +import mocha from 'mocha'; +import * as webPush from '../src/index.js'; +import * as vapidHelper from '../src/vapid-helper.js'; const VALID_AUDIENCE = 'https://example.com'; const VALID_SUBJECT_MAILTO = 'mailto:example@example.com'; @@ -21,23 +19,23 @@ const VALID_UNSAFE_BASE64_PRIVATE_KEY = Buffer.alloc(32).toString('base64'); const VALID_CONTENT_ENCODING = webPush.supportedContentEncodings.AES_GCM; const VALID_EXPIRATION = Math.floor(Date.now() / 1000) + (60 * 60 * 12); -suite('Test Vapid Helpers', function() { +suite('Test Vapid Helpers', function () { const sandbox = sinon.createSandbox(); - mocha.beforeEach(function() { + mocha.beforeEach(function () { sandbox.restore(); }); - mocha.after(function() { + mocha.after(function () { sandbox.restore(); }); - test('is defined', function() { + test('is defined', function () { assert(webPush.generateVAPIDKeys); assert(webPush.getVapidHeaders); }); - test('generate vapid keys', function() { + test('generate vapid keys', function () { const keys = webPush.generateVAPIDKeys(); assert(keys.privateKey); assert(keys.publicKey); @@ -49,10 +47,10 @@ suite('Test Vapid Helpers', function() { assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65); }); - test('generate vapid keys with padding', function() { + test('generate vapid keys with padding', function () { sandbox.stub(crypto, 'createECDH').callsFake(() => { return { - generateKeys: () => {}, + generateKeys: () => { }, getPublicKey: () => Buffer.alloc(60), getPrivateKey: () => Buffer.alloc(27) }; @@ -69,7 +67,7 @@ suite('Test Vapid Helpers', function() { assert.equal(Buffer.from(keys.publicKey, 'base64url').length, 65); }); - test('generate new vapid keys between calls', function() { + test('generate new vapid keys between calls', function () { const keys = webPush.generateVAPIDKeys(); assert(keys.privateKey); assert(keys.publicKey); @@ -79,61 +77,61 @@ suite('Test Vapid Helpers', function() { assert.notEqual(keys.publicKey, secondKeys.publicKey); }); - test('should throw errors on bad input', function() { + test('should throw errors on bad input', function () { const badInputs = [ - function() { + function () { // No args vapidHelper.getVapidHeaders(); }, - function() { + function () { // Missing subject, public key, private key vapidHelper.getVapidHeaders(VALID_AUDIENCE); }, - function() { + function () { // Missing public key, private key vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO); }, - function() { + function () { // Missing public key, private key vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_URL); }, - function() { + function () { // Missing private key vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders('Not a URL', VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { // http URL protocol vapidHelper.getVapidHeaders(VALID_AUDIENCE, INVALID_SUBJECT_URL_1, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { // ftp URL protocol vapidHelper.getVapidHeaders(VALID_AUDIENCE, INVALID_SUBJECT_URL_2, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, 'Some Random String', VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, 'Example key', VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, Buffer.alloc(5), VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, 'Example Key'); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, Buffer.alloc(5)); }, - function() { + function () { vapidHelper.getVapidHeaders({ something: 'else' }, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, { something: 'else' }, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY); }, - function() { + function () { vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, 'invalid encoding type'); }, function () { @@ -167,8 +165,8 @@ suite('Test Vapid Helpers', function() { } ]; - badInputs.forEach(function(badInput, index) { - assert.throws(function() { + badInputs.forEach(function (badInput, index) { + assert.throws(function () { badInput(); console.log('Bad Input Test Failed on test: ', index); }); @@ -176,24 +174,24 @@ suite('Test Vapid Helpers', function() { }); const validInputs = [ - function(contentEncoding) { + function (contentEncoding) { return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // localhost https: subject; should pass, since we don't throw an error for this, just warn to console return vapidHelper.getVapidHeaders(VALID_AUDIENCE, WARN_SUBJECT_LOCALHOST_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { // localhost mailto: subject return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_LOCALHOST_MAILTO, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding); }, - function(contentEncoding) { + function (contentEncoding) { return vapidHelper.getVapidHeaders(VALID_AUDIENCE, VALID_SUBJECT_URL, VALID_PUBLIC_KEY, VALID_PRIVATE_KEY, contentEncoding, VALID_EXPIRATION); }, - function(contentEncoding) { + function (contentEncoding) { // 0 is a valid value for `expiration` // since the the `expiration` value isn't checked for minimum const secondsFromEpoch = 0; @@ -208,7 +206,7 @@ suite('Test Vapid Helpers', function() { ]; function testValidInputs(contentEncoding) { - validInputs.forEach(function(validInput, index) { + validInputs.forEach(function (validInput, index) { try { const headers = validInput(contentEncoding); assert(headers.Authorization); @@ -218,17 +216,17 @@ suite('Test Vapid Helpers', function() { } } catch (err) { console.warn('Valid input call for getVapidHeaders() threw an ' - + 'error. [' + index + ']'); + + 'error. [' + index + ']'); throw err; } }); } - test('should get valid VAPID headers (aesgcm)', function() { + test('should get valid VAPID headers (aesgcm)', function () { testValidInputs(webPush.supportedContentEncodings.AES_GCM); }); - test('should get valid VAPID headers (aes128gcm)', function() { + test('should get valid VAPID headers (aes128gcm)', function () { testValidInputs(webPush.supportedContentEncodings.AES_128_GCM); }); }); diff --git a/test/testSelenium.js b/test/testSelenium.js index 17380edf..40111bdf 100644 --- a/test/testSelenium.js +++ b/test/testSelenium.js @@ -1,18 +1,16 @@ -'use strict'; - -const seleniumAssistant = require('selenium-assistant'); -const webdriver = require('selenium-webdriver'); -const seleniumFirefox = require('selenium-webdriver/firefox'); -const assert = require('assert'); -const mkdirp = require('mkdirp'); -const fs = require('fs'); -const del = require('del'); -const webPush = require('../src/index'); -const createServer = require('./helpers/create-server'); +import seleniumAssistant from 'selenium-assistant'; +import webdriver from 'selenium-webdriver'; +import seleniumFirefox from 'selenium-webdriver/firefox/index.js'; +import assert from 'assert'; +import * as mkdirp from 'mkdirp'; +import fs from 'fs'; +import del from 'del'; +import * as webPush from '../src/index.js'; +import { createServer } from './helpers/create-server.js'; // We need geckodriver on the path -require('geckodriver'); -require('chromedriver'); +import 'geckodriver'; +import 'chromedriver'; const vapidKeys = webPush.generateVAPIDKeys(); @@ -38,210 +36,210 @@ function runTest(browser, options) { } return createServer(options, webPush) - .then(function(server) { - globalServer = server; - testServerURL = 'http://127.0.0.1:' + server.port; - - if (browser.getId() === 'firefox') { - // This is based off of: https://bugzilla.mozilla.org/show_bug.cgi?id=1275521 - // Unfortunately it doesn't seem to work :( - const ffProfile = new seleniumFirefox.Profile(); - ffProfile.setPreference('dom.push.testing.ignorePermission', true); - ffProfile.setPreference('notification.prompt.testing', true); - ffProfile.setPreference('notification.prompt.testing.allow', true); - browser.getSeleniumOptions().setProfile(ffProfile); - } else if (browser.getId() === 'chrome') { - const chromeOperaPreferences = { - profile: { - content_settings: { - exceptions: { - notifications: {} + .then(function (server) { + globalServer = server; + testServerURL = 'http://127.0.0.1:' + server.port; + + if (browser.getId() === 'firefox') { + // This is based off of: https://bugzilla.mozilla.org/show_bug.cgi?id=1275521 + // Unfortunately it doesn't seem to work :( + const ffProfile = new seleniumFirefox.Profile(); + ffProfile.setPreference('dom.push.testing.ignorePermission', true); + ffProfile.setPreference('notification.prompt.testing', true); + ffProfile.setPreference('notification.prompt.testing.allow', true); + browser.getSeleniumOptions().setProfile(ffProfile); + } else if (browser.getId() === 'chrome') { + const chromeOperaPreferences = { + profile: { + content_settings: { + exceptions: { + notifications: {} + } } } - } - }; - chromeOperaPreferences.profile.content_settings.exceptions.notifications[testServerURL + ',*'] = { - setting: 1 - }; - /* eslint-enable camelcase */ - - // Write to a file - const tempPreferenceDir = './test/output/temp/chromeOperaPreferences'; - mkdirp.sync(tempPreferenceDir + '/Default'); - - // NOTE: The Default part of this path might be Chrome specific. - fs.writeFileSync(tempPreferenceDir + '/Default/Preferences', JSON.stringify(chromeOperaPreferences)); - - const seleniumOptions = browser.getSeleniumOptions(); - seleniumOptions.addArguments('user-data-dir=' + tempPreferenceDir + '/'); - } - - return browser.getSeleniumDriver(); - }) - .then(function(driver) { - globalDriver = driver; + }; + chromeOperaPreferences.profile.content_settings.exceptions.notifications[testServerURL + ',*'] = { + setting: 1 + }; + /* eslint-enable camelcase */ - if (options.vapid) { - testServerURL += '?vapid=' + options.vapid.publicKey; - } + // Write to a file + const tempPreferenceDir = './test/output/temp/chromeOperaPreferences'; + mkdirp.sync(tempPreferenceDir + '/Default'); - return globalDriver.get(testServerURL) - .then(function() { - return globalDriver.executeScript(function() { - return typeof navigator.serviceWorker !== 'undefined'; - }); - }) - .then(function(serviceWorkerSupported) { - assert(serviceWorkerSupported); - }) - .then(function() { - return globalDriver.wait(function() { - return globalDriver.executeScript(function() { - return typeof window.subscribeSuccess !== 'undefined'; - }); - }); - }) - .then(function() { - return globalDriver.executeScript(function() { - if (!window.subscribeSuccess) { - return window.subscribeError; - } + // NOTE: The Default part of this path might be Chrome specific. + fs.writeFileSync(tempPreferenceDir + '/Default/Preferences', JSON.stringify(chromeOperaPreferences)); - return null; - }); - }) - .then(function(subscribeError) { - if (subscribeError) { - console.log('subscribeError: ', subscribeError); - throw subscribeError; + const seleniumOptions = browser.getSeleniumOptions(); + seleniumOptions.addArguments('user-data-dir=' + tempPreferenceDir + '/'); } - return globalDriver.executeScript(function() { - return window.testSubscription; - }); + return browser.getSeleniumDriver(); }) - .then(function(subscription) { - if (!subscription) { - throw new Error('No subscription found.'); + .then(function (driver) { + globalDriver = driver; + + if (options.vapid) { + testServerURL += '?vapid=' + options.vapid.publicKey; } - subscription = JSON.parse(subscription); + return globalDriver.get(testServerURL) + .then(function () { + return globalDriver.executeScript(function () { + return typeof navigator.serviceWorker !== 'undefined'; + }); + }) + .then(function (serviceWorkerSupported) { + assert(serviceWorkerSupported); + }) + .then(function () { + return globalDriver.wait(function () { + return globalDriver.executeScript(function () { + return typeof window.subscribeSuccess !== 'undefined'; + }); + }); + }) + .then(function () { + return globalDriver.executeScript(function () { + if (!window.subscribeSuccess) { + return window.subscribeError; + } - let promise; - let pushPayload = null; - let vapid = null; - let contentEncoding = null; - if (options) { - pushPayload = options.payload; - vapid = options.vapid; - contentEncoding = options.contentEncoding; - } + return null; + }); + }) + .then(function (subscribeError) { + if (subscribeError) { + console.log('subscribeError: ', subscribeError); + throw subscribeError; + } - if (!pushPayload) { - promise = webPush.sendNotification(subscription, null, { - vapidDetails: vapid, - contentEncoding: contentEncoding - }); - } else { - if (!subscription.keys) { - throw new Error('Require subscription.keys not found.'); - } - - promise = webPush.sendNotification(subscription, pushPayload, { - vapidDetails: vapid, - contentEncoding: contentEncoding - }); - } + return globalDriver.executeScript(function () { + return window.testSubscription; + }); + }) + .then(function (subscription) { + if (!subscription) { + throw new Error('No subscription found.'); + } - return promise - .then(function(response) { - if (response.length > 0) { - const data = JSON.parse(response); - if (typeof data.failure !== 'undefined' && data.failure > 0) { - throw new Error('Bad GCM Response: ' + response); + subscription = JSON.parse(subscription); + + let promise; + let pushPayload = null; + let vapid = null; + let contentEncoding = null; + if (options) { + pushPayload = options.payload; + vapid = options.vapid; + contentEncoding = options.contentEncoding; } - } - }); - }) - .then(function() { - const expectedTitle = options.payload ? options.payload : 'no payload'; - return globalDriver.wait(function() { - return webdriver.until.titleIs(expectedTitle, 60000); - }); + + if (!pushPayload) { + promise = webPush.sendNotification(subscription, null, { + vapidDetails: vapid, + contentEncoding: contentEncoding + }); + } else { + if (!subscription.keys) { + throw new Error('Require subscription.keys not found.'); + } + + promise = webPush.sendNotification(subscription, pushPayload, { + vapidDetails: vapid, + contentEncoding: contentEncoding + }); + } + + return promise + .then(function (response) { + if (response.length > 0) { + const data = JSON.parse(response); + if (typeof data.failure !== 'undefined' && data.failure > 0) { + throw new Error('Bad GCM Response: ' + response); + } + } + }); + }) + .then(function () { + const expectedTitle = options.payload ? options.payload : 'no payload'; + return globalDriver.wait(function () { + return webdriver.until.titleIs(expectedTitle, 60000); + }); + }); }); - }); } seleniumAssistant.printAvailableBrowserInfo(); const availableBrowsers = seleniumAssistant.getLocalBrowsers(); -availableBrowsers.forEach(function(browser) { +availableBrowsers.forEach(function (browser) { if (browser.getId() !== 'chrome' && browser.getId() !== 'firefox') { return; } - suite('Selenium ' + browser.getPrettyName(), function() { + suite('Selenium ' + browser.getPrettyName(), function () { if (process.env.CI) { this.retries(3); } - setup(function() { + setup(function () { globalServer = null; return del(testDirectory); }); - teardown(function() { + teardown(function () { this.timeout(10000); return seleniumAssistant.killWebDriver(globalDriver) - .catch(function(err) { - console.log('Error killing web driver: ', err); - }) - .then(function() { - globalDriver = null; - - return del(testDirectory) - .catch(function() { - console.warn('Unable to delete test directory, going to wait 2 ' - + 'seconds and try again'); - // Add a timeout so that if the browser - // changes any files in the test directory - // it doesn't cause del to throw an error - // (i.e. del checks files in directory, deletes them - // while another process adds a file, then del fails - // to remove a non-empty directory). - return new Promise(function(resolve) { - setTimeout(resolve, 2000); - }); + .catch(function (err) { + console.log('Error killing web driver: ', err); + }) + .then(function () { + globalDriver = null; + + return del(testDirectory) + .catch(function () { + console.warn('Unable to delete test directory, going to wait 2 ' + + 'seconds and try again'); + // Add a timeout so that if the browser + // changes any files in the test directory + // it doesn't cause del to throw an error + // (i.e. del checks files in directory, deletes them + // while another process adds a file, then del fails + // to remove a non-empty directory). + return new Promise(function (resolve) { + setTimeout(resolve, 2000); + }); + }) + .then(function () { + return del(testDirectory); + }); }) - .then(function() { - return del(testDirectory); + .then(function () { + if (globalServer) { + globalServer.close(); + globalServer = null; + } }); - }) - .then(function() { - if (globalServer) { - globalServer.close(); - globalServer = null; - } - }); }); - test('send/receive notification without payload with ' + browser.getPrettyName() + ' (aesgcm)', function() { + test('send/receive notification without payload with ' + browser.getPrettyName() + ' (aesgcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { contentEncoding: webPush.supportedContentEncodings.AES_GCM }); }); - test('send/receive notification without payload with ' + browser.getPrettyName() + ' (aes128gcm)', function() { + test('send/receive notification without payload with ' + browser.getPrettyName() + ' (aes128gcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { contentEncoding: webPush.supportedContentEncodings.AES_128_GCM }); }); - test('send/receive notification with payload with ' + browser.getPrettyName() + ' (aesgcm)', function() { + test('send/receive notification with payload with ' + browser.getPrettyName() + ' (aesgcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { payload: 'marco', @@ -249,7 +247,7 @@ availableBrowsers.forEach(function(browser) { }); }); - test('send/receive notification with payload with ' + browser.getPrettyName() + ' (aes128gcm)', function() { + test('send/receive notification with payload with ' + browser.getPrettyName() + ' (aes128gcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { payload: 'marco', @@ -257,7 +255,7 @@ availableBrowsers.forEach(function(browser) { }); }); - test('send/receive notification with vapid with ' + browser.getPrettyName() + ' (aesgcm)', function() { + test('send/receive notification with vapid with ' + browser.getPrettyName() + ' (aesgcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { vapid: VAPID_PARAM, @@ -265,7 +263,7 @@ availableBrowsers.forEach(function(browser) { }); }); - test('send/receive notification with vapid with ' + browser.getPrettyName() + ' (aes128gcm)', function() { + test('send/receive notification with vapid with ' + browser.getPrettyName() + ' (aes128gcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { vapid: VAPID_PARAM, @@ -273,7 +271,7 @@ availableBrowsers.forEach(function(browser) { }); }); - test('send/receive notification with payload & vapid with ' + browser.getPrettyName() + ' (aesgcm)', function() { + test('send/receive notification with payload & vapid with ' + browser.getPrettyName() + ' (aesgcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { payload: 'marco', @@ -282,7 +280,7 @@ availableBrowsers.forEach(function(browser) { }); }); - test('send/receive notification with payload & vapid with ' + browser.getPrettyName() + ' (aes128gcm)', function() { + test('send/receive notification with payload & vapid with ' + browser.getPrettyName() + ' (aes128gcm)', function () { this.timeout(PUSH_TEST_TIMEOUT); return runTest(browser, { payload: 'marco', diff --git a/test/testSendNotification.js b/test/testSendNotification.js index 429235c2..162649a6 100644 --- a/test/testSendNotification.js +++ b/test/testSendNotification.js @@ -1,26 +1,25 @@ -'use strict'; - -const assert = require('assert'); -const crypto = require('crypto'); -const https = require('https'); -const fs = require('fs'); -const path = require('path'); -const ece = require('http_ece'); -const portfinder = require('portfinder'); -const jws = require('jws'); -const mocha = require('mocha'); -const WebPushConstants = require('../src/web-push-constants.js'); - -suite('sendNotification', function() { - let sendNotification; - let setGCMAPIKey; - let setVapidDetails; - +import assert from 'assert'; +import crypto from 'crypto'; +import https from 'https'; +import fs from 'fs'; +// import path from 'path'; +import ece from 'http_ece'; +import portfinder from 'portfinder'; +import jws from 'jws'; +import mocha from 'mocha'; +import WebPushConstants from '../src/web-push-constants.js'; +import { generateVAPIDKeys } from '../src/vapid-helper.js'; +import { + sendNotification, + setGCMAPIKey, + setVapidDetails +} from '../src/index.js'; + +suite('sendNotification', function () { mocha.beforeEach(function () { - ({ sendNotification, setGCMAPIKey, setVapidDetails } = require('../src/index')); }); - test('is defined', function() { + test('is defined', function () { assert(sendNotification); }); @@ -34,18 +33,18 @@ suite('sendNotification', function() { // https request mock to accept self-signed certificate. // Probably worth switching with proxyquire and sinon. - const certHTTPSRequest = function(options, listener) { + const certHTTPSRequest = function (options, listener) { options.rejectUnauthorized = false; return originalHTTPSRequest.call(https, options, listener); }; - mocha.beforeEach(function() { + mocha.beforeEach(function () { requestBody = null; requestDetails = null; // Delete caches of web push libs to start clean between test runs - delete require.cache[path.join(__dirname, '..', 'src', 'index.js')]; - delete require.cache[path.join(__dirname, '..', 'src', 'web-push-lib.js')]; + // delete require.cache[path.join(__dirname, '..', 'src', 'index.js')]; + // delete require.cache[path.join(__dirname, '..', 'src', 'web-push-lib.js')]; // Reset https request mock https.request = certHTTPSRequest; @@ -58,7 +57,7 @@ suite('sendNotification', function() { return returnPromise; }); - mocha.after(function() { + mocha.after(function () { return closeServer(); }); @@ -72,7 +71,7 @@ suite('sendNotification', function() { auth: userAuth.toString('base64url') }; - const vapidKeys = require('../src/vapid-helper').generateVAPIDKeys(); + const vapidKeys = generateVAPIDKeys(); function startServer() { const options = { @@ -80,14 +79,14 @@ suite('sendNotification', function() { cert: pem }; - server = https.createServer(options, function(req, res) { + server = https.createServer(options, function (req, res) { requestBody = Buffer.alloc(0); - req.on('data', function(chunk) { + req.on('data', function (chunk) { requestBody = Buffer.concat([requestBody, chunk]); }); - req.on('end', function() { + req.on('end', function () { requestDetails = req; if (req.url.indexOf('statusCode=404') !== -1) { @@ -100,7 +99,7 @@ suite('sendNotification', function() { }); }); - portfinder.getPort(function(err, port) { + portfinder.getPort(function (err, port) { if (err) { serverPort = 50005; } else { @@ -110,20 +109,20 @@ suite('sendNotification', function() { server.listen(serverPort); }); - return new Promise(function(resolve) { + return new Promise(function (resolve) { server.on('listening', resolve); }); } function closeServer() { serverPort = null; - return new Promise(function(resolve) { + return new Promise(function (resolve) { if (!server) { resolve(); return; } - server.on('close', function() { + server.on('close', function () { server = null; resolve(); }); @@ -141,12 +140,12 @@ suite('sendNotification', function() { const isGCM = options.subscription.endpoint .indexOf('https://android.googleapis.com/gcm') === 0; const isFCM = options.subscription.endpoint - .indexOf('https://fcm.googleapis.com/fcm') === 0; + .indexOf('https://fcm.googleapis.com/fcm') === 0; assert.equal(requestDetails.headers['content-length'], requestBody.length, 'Check Content-Length header'); if (typeof options.extraOptions !== 'undefined' - && typeof options.extraOptions.TTL !== 'undefined') { + && typeof options.extraOptions.TTL !== 'undefined') { assert.equal(requestDetails.headers.ttl, options.extraOptions.TTL, 'Check TTL header'); } else if (!isGCM) { assert.equal(requestDetails.headers.ttl, 2419200, 'Check default TTL header'); @@ -161,7 +160,7 @@ suite('sendNotification', function() { assert.equal(requestDetails.headers['content-type'], 'application/octet-stream', 'Check Content-Type header'); const keys = requestDetails.headers['crypto-key'].split(';'); - const appServerPublicKey = keys.find(function(key) { + const appServerPublicKey = keys.find(function (key) { return key.indexOf('dh=') === 0; }).substring('dh='.length); @@ -184,7 +183,7 @@ suite('sendNotification', function() { if (contentEncoding === WebPushConstants.supportedContentEncodings.AES_GCM) { const keys = requestDetails.headers['crypto-key'].split(';'); - const vapidKeyHeader = keys.find(function(key) { + const vapidKeyHeader = keys.find(function (key) { return key.indexOf('p256ecdsa=') === 0; }); @@ -203,17 +202,17 @@ suite('sendNotification', function() { assert.equal(vapidKey, vapidKeys.publicKey); // assert(jws.verify(jwt, 'ES256', appServerVapidPublicKey)), 'JWT valid'); - const decoded = jws.decode(jwt); - assert.equal(decoded.header.typ, 'JWT'); - assert.equal(decoded.header.alg, 'ES256'); - assert.equal(options.subscription.endpoint.startsWith(decoded.payload.aud), true); - assert(decoded.payload.exp > Date.now() / 1000); - assert.equal(decoded.payload.sub, 'mailto:mozilla@example.org'); + const decoded = jws.decode(jwt); + assert.equal(decoded.header.typ, 'JWT'); + assert.equal(decoded.header.alg, 'ES256'); + assert.equal(options.subscription.endpoint.startsWith(decoded.payload.aud), true); + assert(decoded.payload.exp > Date.now() / 1000); + assert.equal(decoded.payload.sub, 'mailto:mozilla@example.org'); } if (isGCM || (isFCM && !options.vapid)) { if (typeof options.extraOptions !== 'undefined' - && typeof options.extraOptions.gcmAPIKey !== 'undefined') { + && typeof options.extraOptions.gcmAPIKey !== 'undefined') { assert.equal(requestDetails.headers.authorization, 'key=' + options.extraOptions.gcmAPIKey, 'Check GCM Authorization header'); } else { assert.equal(requestDetails.headers.authorization, 'key=my_gcm_key', 'Check GCM Authorization header'); @@ -356,8 +355,8 @@ suite('sendNotification', function() { // TODO: Add test for VAPID override - validRequests.forEach(function(validRequest) { - test(validRequest.testTitle + ' (aesgcm)', function() { + validRequests.forEach(function (validRequest) { + test(validRequest.testTitle + ' (aesgcm)', function () { // Set the default endpoint if it's not already configured if (!validRequest.requestOptions.subscription.endpoint) { validRequest.requestOptions.subscription.endpoint = 'https://127.0.0.1:' + serverPort; @@ -365,7 +364,7 @@ suite('sendNotification', function() { if (validRequest.serverFlags) { validRequest.requestOptions.subscription.endpoint += '?' - + validRequest.serverFlags.join('&'); + + validRequest.serverFlags.join('&'); } validRequest.requestOptions.extraOptions = validRequest.requestOptions.extraOptions || {}; @@ -376,15 +375,15 @@ suite('sendNotification', function() { validRequest.requestOptions.message, validRequest.requestOptions.extraOptions ) - .then(function(response) { - assert.equal(response.body, 'ok'); - }) - .then(function() { - validateRequest(validRequest); - }); + .then(function (response) { + assert.equal(response.body, 'ok'); + }) + .then(function () { + validateRequest(validRequest); + }); }); - test(validRequest.testTitle + ' (aes128gcm)', function() { + test(validRequest.testTitle + ' (aes128gcm)', function () { // Set the default endpoint if it's not already configured if (!validRequest.requestOptions.subscription.endpoint) { validRequest.requestOptions.subscription.endpoint = 'https://127.0.0.1:' + serverPort; @@ -392,7 +391,7 @@ suite('sendNotification', function() { if (validRequest.serverFlags) { validRequest.requestOptions.subscription.endpoint += '?' - + validRequest.serverFlags.join('&'); + + validRequest.serverFlags.join('&'); } validRequest.requestOptions.extraOptions = validRequest.requestOptions.extraOptions || {}; @@ -403,12 +402,12 @@ suite('sendNotification', function() { validRequest.requestOptions.message, validRequest.requestOptions.extraOptions ) - .then(function(response) { - assert.equal(response.body, 'ok'); - }) - .then(function() { - validateRequest(validRequest); - }); + .then(function (response) { + assert.equal(response.body, 'ok'); + }) + .then(function () { + validateRequest(validRequest); + }); }); }); @@ -565,11 +564,11 @@ suite('sendNotification', function() { } ]; - validGCMRequests.forEach(function(validGCMRequest) { - test(validGCMRequest.testTitle, function() { + validGCMRequests.forEach(function (validGCMRequest) { + test(validGCMRequest.testTitle, function () { // This mocks out the httpsrequest used by web push. // Probably worth switching with proxyquire and sinon. - https.request = function(options, listener) { + https.request = function (options, listener) { options.hostname = '127.0.0.1'; options.port = serverPort; options.path = '/'; @@ -603,12 +602,12 @@ suite('sendNotification', function() { validGCMRequest.requestOptions.message, validGCMRequest.requestOptions.extraOptions ) - .then(function(response) { - assert.equal(response.body, 'ok'); - }) - .then(function() { - validateRequest(validGCMRequest); - }); + .then(function (response) { + assert.equal(response.body, 'ok'); + }) + .then(function () { + validateRequest(validGCMRequest); + }); }); }); @@ -789,15 +788,15 @@ suite('sendNotification', function() { } ]; - invalidRequests.forEach(function(invalidRequest) { - test(invalidRequest.testTitle + ' (aesgcm)', function() { + invalidRequests.forEach(function (invalidRequest) { + test(invalidRequest.testTitle + ' (aesgcm)', function () { if (invalidRequest.addEndpoint) { invalidRequest.requestOptions.subscription.endpoint = 'https://127.0.0.1:' + serverPort; } if (invalidRequest.serverFlags) { invalidRequest.requestOptions.subscription.endpoint += '?' - + invalidRequest.serverFlags.join('&'); + + invalidRequest.serverFlags.join('&'); } invalidRequest.requestOptions.extraOptions = invalidRequest.requestOptions.extraOptions || {}; @@ -808,21 +807,21 @@ suite('sendNotification', function() { invalidRequest.requestOptions.message, invalidRequest.requestOptions.extraOptions ) - .then(function() { - throw new Error('Expected promise to reject'); - }, function() { - // NOOP, this error is expected - }); + .then(function () { + throw new Error('Expected promise to reject'); + }, function () { + // NOOP, this error is expected + }); }); - test(invalidRequest.testTitle + ' (aes128gcm)', function() { + test(invalidRequest.testTitle + ' (aes128gcm)', function () { if (invalidRequest.addEndpoint) { invalidRequest.requestOptions.subscription.endpoint = 'https://127.0.0.1:' + serverPort; } if (invalidRequest.serverFlags) { invalidRequest.requestOptions.subscription.endpoint += '?' - + invalidRequest.serverFlags.join('&'); + + invalidRequest.serverFlags.join('&'); } invalidRequest.requestOptions.extraOptions = invalidRequest.requestOptions.extraOptions || {}; @@ -833,26 +832,26 @@ suite('sendNotification', function() { invalidRequest.requestOptions.message, invalidRequest.requestOptions.extraOptions ) - .then(function() { - throw new Error('Expected promise to reject'); - }, function() { - // NOOP, this error is expected - }); + .then(function () { + throw new Error('Expected promise to reject'); + }, function () { + // NOOP, this error is expected + }); }); }); - test('rejects when it can\'t connect to the server', function() { + test('rejects when it can\'t connect to the server', function () { const currentServerPort = serverPort; return closeServer() - .then(function() { - return sendNotification({ - endpoint: 'https://127.0.0.1:' + currentServerPort - }) - .then(function() { - throw new Error('sendNotification should have rejected due to server not running'); - }, function() { - // NOOP + .then(function () { + return sendNotification({ + endpoint: 'https://127.0.0.1:' + currentServerPort + }) + .then(function () { + throw new Error('sendNotification should have rejected due to server not running'); + }, function () { + // NOOP + }); }); - }); }); }); diff --git a/test/testSetGCMAPIKey.js b/test/testSetGCMAPIKey.js index 30fb1e52..d3eca4de 100644 --- a/test/testSetGCMAPIKey.js +++ b/test/testSetGCMAPIKey.js @@ -1,39 +1,37 @@ -'use strict'; +import assert from 'assert'; +import { setGCMAPIKey } from '../src/index.js'; -const assert = require('assert'); -const { setGCMAPIKey } = require('../src/index'); - -suite('setGCMAPIKey', function() { - test('is defined', function() { +suite('setGCMAPIKey', function () { + test('is defined', function () { assert(setGCMAPIKey); }); - test('non-empty string', function() { - assert.doesNotThrow(function() { + test('non-empty string', function () { + assert.doesNotThrow(function () { setGCMAPIKey('AIzaSyAwmdX6KKd4hPfIcGU2SOfj9vuRDW6u-wo'); }); }); - test('reset GCM API Key with null', function() { - assert.doesNotThrow(function() { + test('reset GCM API Key with null', function () { + assert.doesNotThrow(function () { setGCMAPIKey(null); }); }); - test('empty string', function() { - assert.throws(function() { + test('empty string', function () { + assert.throws(function () { setGCMAPIKey(''); }, Error); }); - test('non string', function() { - assert.throws(function() { + test('non string', function () { + assert.throws(function () { setGCMAPIKey(42); }, Error); }); - test('undefined value', function() { - assert.throws(function() { + test('undefined value', function () { + assert.throws(function () { setGCMAPIKey(); }, Error); });