New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@pusher/push-notifications-web

Package Overview
Dependencies
Maintainers
7
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pusher/push-notifications-web - npm Package Compare versions

Comparing version 0.12.0 to 1.0.0

CHANGELOG.md

88

end-to-end-tests/sdk-set-user-id.test.js
import { ErrolTestClient } from './errol-client';
import { launchServer, createChromeWebDriver } from './test-utils';
import { launchServer, createChromeWebDriver, NOTIFICATIONS_GRANTED } from './test-utils';

@@ -12,3 +12,3 @@ let killServer = null;

})
.then(() => createChromeWebDriver())
.then(() => createChromeWebDriver(NOTIFICATIONS_GRANTED))
.then(driver => {

@@ -54,7 +54,9 @@ chromeDriver = driver;

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
});
return beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -88,6 +90,8 @@ });

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(beamsClient => beamsClient.setUserId('cucas', tokenProvider))
});
return beamsClient
.start()
.then(() => beamsClient.setUserId('cucas', tokenProvider))
.then(() => asyncScriptReturnCallback(''))

@@ -117,7 +121,9 @@ .catch(e => asyncScriptReturnCallback(e.message));

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
});
return beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -138,8 +144,7 @@ });

let beamsClient;
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(c => (beamsClient = c))
.then(() => beamsClient.setUserId('cucas', tokenProvider))
});
beamsClient
.setUserId('cucas', tokenProvider)
.then(() => beamsClient.setUserId('ronaldinho', tokenProvider))

@@ -169,8 +174,7 @@ .then(() => asyncScriptReturnCallback(''))

let beamsClient;
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(c => (beamsClient = c))
.then(() => beamsClient.setUserId('cucas', tokenProvider))
});
beamsClient
.setUserId('cucas', tokenProvider)
.then(() => asyncScriptReturnCallback(''))

@@ -192,7 +196,9 @@ .catch(e => asyncScriptReturnCallback(e.message));

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
});
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -213,8 +219,7 @@ });

let beamsClient;
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(c => (beamsClient = c))
.then(() => beamsClient.setUserId('', tokenProvider))
});
beamsClient
.setUserId('', tokenProvider)
.then(() => asyncScriptReturnCallback(''))

@@ -236,7 +241,9 @@ .catch(e => asyncScriptReturnCallback(e.message));

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
});
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -257,8 +264,7 @@ });

let beamsClient;
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId: '1b880590-6301-4bb5-b34f-45db1c5f5644',
})
.then(c => (beamsClient = c))
.then(() => beamsClient.setUserId(undefined, tokenProvider))
});
beamsClient
.setUserId(undefined, tokenProvider)
.then(() => asyncScriptReturnCallback(''))

@@ -265,0 +271,0 @@ .catch(e => asyncScriptReturnCallback(e.message));

@@ -1,2 +0,2 @@

import { launchServer, createChromeWebDriver } from './test-utils';
import { launchServer, createChromeWebDriver, NOTIFICATIONS_GRANTED } from './test-utils';

@@ -11,3 +11,3 @@ let killServer = null;

})
.then(() => createChromeWebDriver())
.then(() => createChromeWebDriver(NOTIFICATIONS_GRANTED))
.then(driver => {

@@ -28,5 +28,7 @@ chromeDriver = driver;

const instanceId = 'deadc0de-2ce6-46e3-ad9a-5c02d0ab119b';
return PusherPushNotifications.init({ instanceId })
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -48,5 +50,7 @@ });

const instanceId = 'deadc0de-2ce6-46e3-ad9a-5c02d0ab119b';
return PusherPushNotifications.init({ instanceId })
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -64,5 +68,7 @@ });

const instanceId = 'deadc0de-2ce6-46e3-ad9a-5c02d0ab119b';
return PusherPushNotifications.init({ instanceId })
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -108,7 +114,8 @@ });

const instanceId = 'deadc0de-2ce6-46e3-ad9a-5c02d0ab119b';
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback('start succeeded'))
});
beamsClient
.start()
.then(() => asyncScriptReturnCallback('start succeeded'))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -147,3 +154,3 @@ });

const instanceId = 'deadc0de-2ce6-46e3-ad9a-5c02d0ab119b';
return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
serviceWorkerRegistration: await window.navigator.serviceWorker.register(

@@ -153,5 +160,7 @@ '/service-worker.js'

instanceId,
})
.then(beamsClient => beamsClient.start())
.then(beamsClient => asyncScriptReturnCallback(beamsClient.deviceId))
});
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -175,8 +184,9 @@ });

.register('/not-the-root/service-worker.js')
.then(registration =>
PusherPushNotifications.init({
.then(registration => {
const beamsClient = new PusherPushNotifications.Client({
instanceId,
serviceWorkerRegistration: registration,
})
)
});
return beamsClient.start();
})
.catch(e => asyncScriptReturnCallback(e.message));

@@ -183,0 +193,0 @@ });

import { ErrolTestClient } from './errol-client';
import { launchServer, createChromeWebDriver } from './test-utils';
import { launchServer, createChromeWebDriver, NOTIFICATIONS_GRANTED } from './test-utils';

@@ -12,3 +12,3 @@ let killServer = null;

})
.then(() => createChromeWebDriver())
.then(() => createChromeWebDriver(NOTIFICATIONS_GRANTED))
.then(driver => {

@@ -55,6 +55,7 @@ chromeDriver = driver;

return PusherPushNotifications.init({ instanceId })
.then(c => (window.beamsClient = c))
.then(() => window.beamsClient.start())
.then(() => asyncScriptReturnCallback(window.beamsClient.deviceId))
window.beamsClient = new PusherPushNotifications.Client({ instanceId });
window.beamsClient
.start()
.then(() => window.beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -88,5 +89,6 @@ });

return PusherPushNotifications.init({ instanceId })
.then(c => (window.beamsClient = c))
.then(() => asyncScriptReturnCallback(window.beamsClient.deviceId))
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.getDeviceId()
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -120,4 +122,5 @@ });

return PusherPushNotifications.init({ instanceId })
.then(beamsClient => beamsClient.stop())
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.stop()
.then(() => asyncScriptReturnCallback(''))

@@ -146,6 +149,7 @@ .catch(e => asyncScriptReturnCallback(e.message));

return PusherPushNotifications.init({ instanceId })
.then(c => (window.beamsClient = c))
.then(() => window.beamsClient.start())
.then(() => asyncScriptReturnCallback(window.beamsClient.deviceId))
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.start()
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -161,5 +165,9 @@ });

return window.beamsClient
const instanceId = '1b880590-6301-4bb5-b34f-45db1c5f5644';
const beamsClient = new PusherPushNotifications.Client({ instanceId });
beamsClient
.clearAllState()
.then(() => asyncScriptReturnCallback(window.beamsClient.deviceId))
.then(() => beamsClient.getDeviceId())
.then(deviceId => asyncScriptReturnCallback(deviceId))
.catch(e => asyncScriptReturnCallback(e.message));

@@ -166,0 +174,0 @@ });

@@ -22,2 +22,6 @@ const { spawn } = require('child_process');

export const NOTIFICATIONS_DEFAULT = 0
export const NOTIFICATIONS_GRANTED = 1
export const NOTIFICATIONS_DENIED = 2
beforeAll(() => {

@@ -92,5 +96,6 @@ jest.setTimeout(SCRIPT_TIMEOUT_MS);

* @async
* @param {number} notificationPermission - The notification permission for the test server
* @return {ChromeDriver} - Selenium webdriver instance (Chrome)
*/
export async function createChromeWebDriver() {
export async function createChromeWebDriver(notificationPermission = NOTIFICATIONS_DEFAULT) {
// This is tricky for a few reasons:

@@ -124,3 +129,3 @@ // 1. Selenium cannot accept the Web Push permission, so we have to

const prefsFileName = `${prefsDir}/Preferences`;
const configFileObject = createTempBrowserPreferences(TEST_SERVER_URL);
const configFileObject = createTempBrowserPreferences(TEST_SERVER_URL, notificationPermission);
const configFileString = JSON.stringify(configFileObject);

@@ -151,4 +156,12 @@

function createTempBrowserPreferences(testSiteURL) {
const testSiteKey = `${testSiteURL},*`;
function createTempBrowserPreferences(testSiteURL, notificationPermission) {
let notifications = {}
if (notificationPermission !== NOTIFICATIONS_DEFAULT) {
const testSiteKey = `${testSiteURL},*`;
notifications = {
[testSiteKey]: {
setting: notificationPermission,
}
}
}
return {

@@ -158,7 +171,3 @@ profile: {

exceptions: {
notifications: {
[testSiteKey]: {
setting: 1,
},
},
notifications
},

@@ -165,0 +174,0 @@ },

@@ -20,2 +20,9 @@ declare module '@pusher/push-notifications-web' {

enum RegistrationState {
PERMISSION_GRANTED_REGISTERED_WITH_BEAMS = 'PERMISSION_GRANTED_REGISTERED_WITH_BEAMS',
PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS = 'PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS',
PERMISSION_PROMPT_REQUIRED = 'PERMISSION_PROMPT_REQUIRED',
PERMISSION_DENIED = 'PERMISSION_DENIED',
}
class PushNotificationsInstance {

@@ -38,2 +45,3 @@ instanceId: string;

clearAllState(): Promise<undefined>;
getRegistrationState(): Promise<RegistrationState>;
}

@@ -40,0 +48,0 @@

{
"name": "@pusher/push-notifications-web",
"version": "0.12.0",
"version": "1.0.0",
"description": "",

@@ -10,2 +10,3 @@ "main": "dist/push-notifications-esm.js",

"build:cdn": "rollup -c ./rollup/cdn.js",
"build:sw": "rollup -c ./rollup/service-worker.js",
"format": "prettier ./src/**/*.js --write",

@@ -12,0 +13,0 @@ "lint": "eslint ./src/**/*.js && prettier ./src/**/*.js -l",

@@ -1,7 +0,5 @@

# Push Notifications Web (BETA)
# Push Notifications Web
[![Build Status](https://travis-ci.org/pusher/push-notifications-web.svg?branch=master)](https://travis-ci.org/pusher/push-notifications-web)
[![Twitter](https://img.shields.io/badge/twitter-@Pusher-blue.svg?style=flat)](http://twitter.com/Pusher)
**Warning: This SDK is in Beta therefore there may be breaking changes in the future. In its current form, the SDK has limitations, which are explained in the [getting started guide](https://pusher.com/docs/beams/getting-started/web/sdk-integration).**
This is the web SDK for the [Pusher Beams](https://pusher.com/beams) service.

@@ -8,0 +6,0 @@

@@ -12,124 +12,137 @@ import doRequest from './do-request';

export async function init(config) {
if (!config) {
throw new Error('Config object required');
}
const {
instanceId,
endpointOverride = null,
serviceWorkerRegistration = null,
} = config;
export const RegistrationState = Object.freeze({
PERMISSION_PROMPT_REQUIRED: 'PERMISSION_PROMPT_REQUIRED',
PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS:
'PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS',
PERMISSION_GRANTED_REGISTERED_WITH_BEAMS:
'PERMISSION_GRANTED_REGISTERED_WITH_BEAMS',
PERMISSION_DENIED: 'PERMISSION_DENIED',
});
if (instanceId === undefined) {
throw new Error('Instance ID is required');
}
if (typeof instanceId !== 'string') {
throw new Error('Instance ID must be a string');
}
if (instanceId.length === 0) {
throw new Error('Instance ID cannot be empty');
}
export class Client {
constructor(config) {
if (!config) {
throw new Error('Config object required');
}
const {
instanceId,
endpointOverride = null,
serviceWorkerRegistration = null,
} = config;
if (!('indexedDB' in window)) {
throw new Error(
'Pusher Beams does not support this browser version (IndexedDB not supported)'
);
}
if (instanceId === undefined) {
throw new Error('Instance ID is required');
}
if (typeof instanceId !== 'string') {
throw new Error('Instance ID must be a string');
}
if (instanceId.length === 0) {
throw new Error('Instance ID cannot be empty');
}
if (!window.isSecureContext) {
throw new Error(
'Pusher Beams relies on Service Workers, which only work in secure contexts. Check that your page is being served from localhost/over HTTPS'
);
}
if (!('indexedDB' in window)) {
throw new Error(
'Pusher Beams does not support this browser version (IndexedDB not supported)'
);
}
if (!('serviceWorker' in navigator)) {
throw new Error(
'Pusher Beams does not support this browser version (Service Workers not supported)'
);
}
if (!window.isSecureContext) {
throw new Error(
'Pusher Beams relies on Service Workers, which only work in secure contexts. Check that your page is being served from localhost/over HTTPS'
);
}
if (!('PushManager' in window)) {
throw new Error(
'Pusher Beams does not support this browser version (Web Push not supported)'
);
}
if (!('serviceWorker' in navigator)) {
throw new Error(
'Pusher Beams does not support this browser version (Service Workers not supported)'
);
}
let swReg;
if (serviceWorkerRegistration) {
const serviceWorkerScope = serviceWorkerRegistration.scope;
const currentURL = window.location.href;
const scopeMatchesCurrentPage = currentURL.startsWith(serviceWorkerScope);
if (!scopeMatchesCurrentPage) {
if (!('PushManager' in window)) {
throw new Error(
`Could not initialize Pusher web push: current page not in serviceWorkerRegistration scope (${serviceWorkerScope})`
'Pusher Beams does not support this browser version (Web Push not supported)'
);
}
swReg = serviceWorkerRegistration;
} else {
swReg = await getServiceWorkerRegistration();
if (serviceWorkerRegistration) {
const serviceWorkerScope = serviceWorkerRegistration.scope;
const currentURL = window.location.href;
const scopeMatchesCurrentPage = currentURL.startsWith(serviceWorkerScope);
if (!scopeMatchesCurrentPage) {
throw new Error(
`Could not initialize Pusher web push: current page not in serviceWorkerRegistration scope (${serviceWorkerScope})`
);
}
}
this.instanceId = instanceId;
this._deviceId = null;
this._token = null;
this._userId = null;
this._serviceWorkerRegistration = serviceWorkerRegistration;
this._deviceStateStore = new DeviceStateStore(instanceId);
this._endpoint = endpointOverride; // Internal only
this._ready = this._init();
}
const deviceStateStore = new DeviceStateStore(instanceId);
await deviceStateStore.connect();
async _init() {
if (this._deviceId !== null) {
return;
}
const storedToken = await deviceStateStore.getToken();
const actualToken = await getWebPushToken(swReg);
await this._deviceStateStore.connect();
const pushTokenHasChanged = storedToken !== actualToken;
if (!this._serviceWorkerRegistration) {
this._serviceWorkerRegistration = await getServiceWorkerRegistration();
}
if (pushTokenHasChanged) {
// The web push subscription has changed out from underneath us.
// This can happen when the user disables the web push permission
// (potentially also renabling it, thereby changing the token)
//
// This means the SDK has effectively been stopped, so we should update
// the SDK state to reflect that.
await deviceStateStore.clear();
await this._detectSubscriptionChange();
this._deviceId = await this._deviceStateStore.getDeviceId();
this._token = await this._deviceStateStore.getToken();
this._userId = await this._deviceStateStore.getUserId();
}
const deviceId = await deviceStateStore.getDeviceId();
const token = await deviceStateStore.getToken();
const userId = await deviceStateStore.getUserId();
// Ensure SDK is loaded and is consistent
async _resolveSDKState() {
await this._ready;
await this._detectSubscriptionChange();
}
const instance = new PushNotificationsInstance({
instanceId,
deviceId,
token,
userId,
serviceWorkerRegistration: swReg,
deviceStateStore,
endpointOverride,
});
async _detectSubscriptionChange() {
const storedToken = await this._deviceStateStore.getToken();
const actualToken = await getWebPushToken(this._serviceWorkerRegistration);
const deviceExists = deviceId !== null;
if (deviceExists) {
try {
await instance._updateDeviceMetadata();
} catch (_) {
// Best effort, do nothing if this fails.
const pushTokenHasChanged = storedToken !== actualToken;
if (pushTokenHasChanged) {
// The web push subscription has changed out from underneath us.
// This can happen when the user disables the web push permission
// (potentially also renabling it, thereby changing the token)
//
// This means the SDK has effectively been stopped, so we should update
// the SDK state to reflect that.
await this._deviceStateStore.clear();
this._deviceId = null;
this._token = null;
this._userId = null;
}
}
return instance;
}
async getDeviceId() {
await this._resolveSDKState();
return this._ready.then(() => this._deviceId);
}
class PushNotificationsInstance {
constructor({
instanceId,
deviceId,
token,
userId,
serviceWorkerRegistration,
deviceStateStore,
endpointOverride = null,
}) {
this.instanceId = instanceId;
this.deviceId = deviceId;
this.token = token;
this.userId = userId;
this._serviceWorkerRegistration = serviceWorkerRegistration;
this._deviceStateStore = deviceStateStore;
this._endpoint = endpointOverride; // Internal only
async getToken() {
await this._resolveSDKState();
return this._ready.then(() => this._token);
}
async getUserId() {
await this._resolveSDKState();
return this._ready.then(() => this._userId);
}
get _baseURL() {

@@ -143,3 +156,3 @@ if (this._endpoint !== null) {

_throwIfNotStarted(message) {
if (!this.deviceId) {
if (!this._deviceId) {
throw new Error(

@@ -152,2 +165,4 @@ `${message}. SDK not registered with Beams. Did you call .start?`

async start() {
await this._resolveSDKState();
if (!isSupportedBrowser()) {

@@ -157,3 +172,3 @@ return this;

if (this.deviceId !== null) {
if (this._deviceId !== null) {
return this;

@@ -177,9 +192,29 @@ }

this.token = token;
this.deviceId = deviceId;
this._token = token;
this._deviceId = deviceId;
return this;
}
async getRegistrationState() {
await this._resolveSDKState();
if (Notification.permission === 'denied') {
return RegistrationState.PERMISSION_DENIED;
}
if (Notification.permission === 'granted' && this._deviceId !== null) {
return RegistrationState.PERMISSION_GRANTED_REGISTERED_WITH_BEAMS;
}
if (Notification.permission === 'granted' && this._deviceId === null) {
return RegistrationState.PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS;
}
return RegistrationState.PERMISSION_PROMPT_REQUIRED;
}
async addDeviceInterest(interest) {
await this._resolveSDKState();
this._throwIfNotStarted('Could not add Device Interest');
validateInterestName(interest);

@@ -189,3 +224,3 @@

this.instanceId
)}/devices/web/${this.deviceId}/interests/${encodeURIComponent(interest)}`;
)}/devices/web/${this._deviceId}/interests/${encodeURIComponent(interest)}`;
const options = {

@@ -199,3 +234,5 @@ method: 'POST',

async removeDeviceInterest(interest) {
await this._resolveSDKState();
this._throwIfNotStarted('Could not remove Device Interest');
validateInterestName(interest);

@@ -205,3 +242,3 @@

this.instanceId
)}/devices/web/${this.deviceId}/interests/${encodeURIComponent(interest)}`;
)}/devices/web/${this._deviceId}/interests/${encodeURIComponent(interest)}`;
const options = {

@@ -215,2 +252,3 @@ method: 'DELETE',

async getDeviceInterests() {
await this._resolveSDKState();
this._throwIfNotStarted('Could not get Device Interests');

@@ -220,3 +258,3 @@

this.instanceId
)}/devices/web/${this.deviceId}/interests`;
)}/devices/web/${this._deviceId}/interests`;
const options = {

@@ -230,2 +268,3 @@ method: 'GET',

async setDeviceInterests(interests) {
await this._resolveSDKState();
this._throwIfNotStarted('Could not set Device Interests');

@@ -253,3 +292,3 @@

this.instanceId
)}/devices/web/${this.deviceId}/interests`;
)}/devices/web/${this._deviceId}/interests`;
const options = {

@@ -266,3 +305,5 @@ method: 'PUT',

async clearDeviceInterests() {
await this._resolveSDKState();
this._throwIfNotStarted('Could not clear Device Interests');
await this.setDeviceInterests([]);

@@ -272,2 +313,4 @@ }

async setUserId(userId, tokenProvider) {
await this._resolveSDKState();
if (!isSupportedBrowser()) {

@@ -277,3 +320,3 @@ return;

if (this.deviceId === null) {
if (this._deviceId === null) {
const error = new Error('.start must be called before .setUserId');

@@ -288,3 +331,3 @@ return Promise.reject(error);

}
if (this.userId !== null && this.userId !== userId) {
if (this._userId !== null && this._userId !== userId) {
throw new Error('Changing the `userId` is not allowed.');

@@ -295,3 +338,3 @@ }

this.instanceId
)}/devices/web/${this.deviceId}/user`;
)}/devices/web/${this._deviceId}/user`;

@@ -308,3 +351,3 @@ const { token: beamsAuthToken } = await tokenProvider.fetchToken(userId);

this.userId = userId;
this._userId = userId;
return this._deviceStateStore.setUserId(userId);

@@ -314,2 +357,4 @@ }

async stop() {
await this._resolveSDKState();
if (!isSupportedBrowser()) {

@@ -319,3 +364,3 @@ return;

if (this.deviceId === null) {
if (this._deviceId === null) {
return;

@@ -328,5 +373,5 @@ }

this.deviceId = null;
this.token = null;
this.userId = null;
this._deviceId = null;
this._token = null;
this._userId = null;
}

@@ -395,3 +440,3 @@

this.instanceId
)}/devices/web/${encodeURIComponent(this.deviceId)}`;
)}/devices/web/${encodeURIComponent(this._deviceId)}`;

@@ -417,3 +462,3 @@ const options = { method: 'DELETE', path };

this.instanceId
)}/devices/web/${this.deviceId}/metadata`;
)}/devices/web/${this._deviceId}/metadata`;

@@ -508,3 +553,3 @@ const metadata = {

console.warn(
'Whilst in Beta, Pusher Web Push Notifications supports Chrome, Firefox, Edge and Opera.'
'Pusher Web Push Notifications supports Chrome, Firefox, Edge and Opera.'
);

@@ -511,0 +556,0 @@ }

import * as PusherPushNotifications from './push-notifications';
import { makeDeviceStateStore } from '../test-utils/fake-device-state-store';

@@ -12,8 +13,4 @@ const DUMMY_PUSH_SUBSCRIPTION = { foo: 'bar' };

test('should be a function', () => {
expect(typeof PusherPushNotifications.init).toBe('function');
});
test('will throw if there is no config object given', () => {
return expect(PusherPushNotifications.init()).rejects.toThrow(
return expect(() => new PusherPushNotifications.Client()).toThrow(
'Config object required'

@@ -24,3 +21,3 @@ );

test('will throw if there is no instance ID specified', () => {
return expect(PusherPushNotifications.init({})).rejects.toThrow(
return expect(() => new PusherPushNotifications.Client({})).toThrow(
'Instance ID is required'

@@ -32,5 +29,5 @@ );

const instanceId = null;
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
'Instance ID must be a string'
);
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow('Instance ID must be a string');
});

@@ -40,5 +37,5 @@

const instanceId = '';
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
'Instance ID cannot be empty'
);
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow('Instance ID cannot be empty');
});

@@ -49,5 +46,5 @@

const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
'IndexedDB not supported'
);
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow('IndexedDB not supported');
});

@@ -58,3 +55,5 @@

const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow(
'Pusher Beams relies on Service Workers, which only work in secure contexts'

@@ -67,5 +66,5 @@ );

const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
'Service Workers not supported'
);
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow('Service Workers not supported');
});

@@ -76,5 +75,5 @@

const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
return expect(PusherPushNotifications.init({ instanceId })).rejects.toThrow(
'Web Push not supported'
);
return expect(
() => new PusherPushNotifications.Client({ instanceId })
).toThrow('Web Push not supported');
});

@@ -96,8 +95,11 @@

return PusherPushNotifications.init({ instanceId }).then(beamsClient => {
expect(beamsClient.deviceId).toEqual(
'web-1db66b8a-f51f-49de-b225-72591535c855'
);
expect(beamsClient.token).toEqual(ENCODED_DUMMY_PUSH_SUBSCRIPTION);
expect(beamsClient.userId).toEqual('alice');
const beamsClient = new PusherPushNotifications.Client({ instanceId });
return Promise.all([
beamsClient.getDeviceId(),
beamsClient.getToken(),
beamsClient.getUserId(),
]).then(([deviceId, token, userId]) => {
expect(deviceId).toEqual('web-1db66b8a-f51f-49de-b225-72591535c855');
expect(token).toEqual(ENCODED_DUMMY_PUSH_SUBSCRIPTION);
expect(userId).toEqual('alice');
});

@@ -138,19 +140,18 @@ });

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.addDeviceInterest(interest))
.then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'POST',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests/donuts',
].join(''),
});
});
return beamsClient.addDeviceInterest(interest).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'POST',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests/donuts',
].join(''),
});
});
});

@@ -161,5 +162,5 @@

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.addDeviceInterest())
}).addDeviceInterest()
).rejects.toThrow('Interest name is required');

@@ -172,5 +173,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.addDeviceInterest(interest))
}).addDeviceInterest(interest)
).rejects.toThrow('Interest false is not a string');

@@ -186,5 +187,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.addDeviceInterest(interest))
}).addDeviceInterest(interest)
).rejects.toThrow('Interest is longer than the maximum of 164 chars');

@@ -197,5 +198,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.addDeviceInterest(interest))
}).addDeviceInterest(interest)
).rejects.toThrow('contains a forbidden character');

@@ -215,5 +216,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.addDeviceInterest(interest))
}).addDeviceInterest(interest)
).rejects.toThrow('SDK not registered with Beams. Did you call .start?');

@@ -233,19 +234,16 @@ });

return PusherPushNotifications.init({
instanceId,
})
.then(beamsClient => beamsClient.removeDeviceInterest(interest))
.then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'DELETE',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests/donuts',
].join(''),
});
const beamsClient = new PusherPushNotifications.Client({ instanceId });
return beamsClient.removeDeviceInterest(interest).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'DELETE',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests/donuts',
].join(''),
});
});
});

@@ -256,5 +254,5 @@

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.removeDeviceInterest())
}).removeDeviceInterest()
).rejects.toThrow('Interest name is required');

@@ -267,5 +265,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.removeDeviceInterest(interest))
}).removeDeviceInterest(interest)
).rejects.toThrow('Interest false is not a string');

@@ -281,5 +279,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.removeDeviceInterest(interest))
}).removeDeviceInterest(interest)
).rejects.toThrow('Interest is longer than the maximum of 164 chars');

@@ -292,5 +290,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.removeDeviceInterest(interest))
}).removeDeviceInterest(interest)
).rejects.toThrow('contains a forbidden character');

@@ -310,5 +308,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.removeDeviceInterest(interest))
}).removeDeviceInterest(interest)
).rejects.toThrow('SDK not registered with Beams. Did you call .start?');

@@ -332,20 +330,19 @@ });

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.getDeviceInterests())
.then(interests => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'GET',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join(''),
});
expect(interests).toEqual(['donuts']);
});
return beamsClient.getDeviceInterests().then(interests => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'GET',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join(''),
});
expect(interests).toEqual(['donuts']);
});
});

@@ -363,5 +360,5 @@

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.getDeviceInterests())
}).getDeviceInterests()
).rejects.toThrow('SDK not registered with Beams. Did you call .start?');

@@ -381,22 +378,21 @@ });

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.setDeviceInterests(interests))
.then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0].method).toEqual('PUT');
expect(mockDoRequest.mock.calls[0][0].path).toEqual(
[
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join('')
);
expect(mockDoRequest.mock.calls[0][0].body.interests.sort()).toEqual(
[...interests].sort()
);
});
});
return beamsClient.setDeviceInterests(interests).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0].method).toEqual('PUT');
expect(mockDoRequest.mock.calls[0][0].path).toEqual(
[
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join('')
);
expect(mockDoRequest.mock.calls[0][0].body.interests.sort()).toEqual(
[...interests].sort()
);
});
});

@@ -415,22 +411,21 @@

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.setDeviceInterests(interests))
.then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0].method).toEqual('PUT');
expect(mockDoRequest.mock.calls[0][0].path).toEqual(
[
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join('')
);
expect(mockDoRequest.mock.calls[0][0].body.interests.sort()).toEqual(
expectedInterests.sort()
);
});
});
return beamsClient.setDeviceInterests(interests).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0].method).toEqual('PUT');
expect(mockDoRequest.mock.calls[0][0].path).toEqual(
[
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join('')
);
expect(mockDoRequest.mock.calls[0][0].body.interests.sort()).toEqual(
expectedInterests.sort()
);
});
});

@@ -441,5 +436,5 @@

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests())
}).setDeviceInterests()
).rejects.toThrow('interests argument is required');

@@ -452,5 +447,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests(interests))
}).setDeviceInterests(interests)
).rejects.toThrow('interests argument must be an array');

@@ -468,5 +463,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests(interests))
}).setDeviceInterests(interests)
).rejects.toThrow(

@@ -483,5 +478,5 @@ `Number of interests (${maxInterests +

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests(interests))
}).setDeviceInterests(interests)
).rejects.toThrow('Interest false is not a string');

@@ -498,5 +493,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests(interests))
}).setDeviceInterests(interests)
).rejects.toThrow('longer than the maximum of 164 chars');

@@ -510,5 +505,5 @@ });

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests(interests))
}).setDeviceInterests(interests)
).rejects.toThrow(

@@ -529,5 +524,5 @@ 'interest "bad|interest" contains a forbidden character'

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.setDeviceInterests([]))
}).setDeviceInterests([])
).rejects.toThrow('SDK not registered with Beams. Did you call .start?');

@@ -546,20 +541,19 @@ });

return PusherPushNotifications.init({
const beamsClient = new PusherPushNotifications.Client({
instanceId,
})
.then(beamsClient => beamsClient.clearDeviceInterests())
.then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'PUT',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join(''),
body: { interests: [] },
});
});
return beamsClient.clearDeviceInterests().then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
expect(mockDoRequest.mock.calls[0][0]).toEqual({
method: 'PUT',
path: [
'https://df3c1965-e870-4bd6-8d75-fea56b26335f.pushnotifications.pusher.com',
'/device_api/v1/instances/df3c1965-e870-4bd6-8d75-fea56b26335f',
'/devices/web/web-1db66b8a-f51f-49de-b225-72591535c855',
'/interests',
].join(''),
body: { interests: [] },
});
});
});

@@ -577,5 +571,5 @@

return expect(
PusherPushNotifications.init({
new PusherPushNotifications.Client({
instanceId,
}).then(beamsClient => beamsClient.clearDeviceInterests())
}).clearDeviceInterests()
).rejects.toThrow('SDK not registered with Beams. Did you call .start?');

@@ -586,2 +580,143 @@ });

describe('.getRegistrationState', () => {
const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
describe('if SDK is started', () => {
let devicestatestore = require('./device-state-store');
beforeEach(() => {
devicestatestore.default = makeDeviceStateStore({
deviceId: 'web-1db66b8a-f51f-49de-b225-72591535c855',
token: ENCODED_DUMMY_PUSH_SUBSCRIPTION,
userId: null,
});
});
afterEach(() => {
jest.resetModules();
devicestatestore = require('./device-state-store');
});
test('should return PERMISSION_GRANTED_REGISTERED_WITH_BEAMS if browser permission is granted', () => {
setUpGlobals({ notificationPermission: 'granted' });
let beamsClient = new PusherPushNotifications.Client({
instanceId,
});
return beamsClient.getRegistrationState().then(state => {
expect(state).toEqual(
PusherPushNotifications.RegistrationState
.PERMISSION_GRANTED_REGISTERED_WITH_BEAMS
);
});
});
});
describe('if SDK is not started', () => {
let devicestatestore = require('./device-state-store');
beforeEach(() => {
devicestatestore.default = makeDeviceStateStore({
deviceId: null,
token: null,
userId: null,
});
});
afterEach(() => {
jest.resetModules();
devicestatestore = require('./device-state-store');
tearDownGlobals();
});
test('should return PERMISSION_DENIED if browser permission is denied', () => {
setUpGlobals({ notificationPermission: 'denied' });
let beamsClient = new PusherPushNotifications.Client({
instanceId,
});
return beamsClient.getRegistrationState().then(state => {
expect(state).toEqual(
PusherPushNotifications.RegistrationState.PERMISSION_DENIED
);
});
});
test('should return PERMISSION_PROMPT_REQUIRED if browser permission is default', () => {
setUpGlobals({ notificationPermission: 'default' });
let beamsClient = new PusherPushNotifications.Client({
instanceId,
});
return beamsClient.getRegistrationState().then(state => {
expect(state).toEqual(
PusherPushNotifications.RegistrationState.PERMISSION_PROMPT_REQUIRED
);
});
});
test('should return PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS if browser permission is granted', () => {
setUpGlobals({ notificationPermission: 'granted' });
let beamsClient = new PusherPushNotifications.Client({
instanceId,
});
return beamsClient.getRegistrationState().then(state => {
expect(state).toEqual(
PusherPushNotifications.RegistrationState
.PERMISSION_GRANTED_NOT_REGISTERED_WITH_BEAMS
);
});
});
});
});
describe('SDK state', () => {
afterEach(() => {
jest.resetModules();
tearDownGlobals();
});
test('should be reset if subscription changes', () => {
const PusherPushNotifications = require('./push-notifications');
const devicestatestore = require('./device-state-store');
let subscription = DUMMY_PUSH_SUBSCRIPTION;
setUpGlobals({
getSWSubscription: () => {
return Promise.resolve(subscription);
},
});
const instanceId = 'df3c1965-e870-4bd6-8d75-fea56b26335f';
let deviceId = 'web-1db66b8a-f51f-49de-b225-72591535c855';
let newSubscription = { another: 'subscription' };
expect(newSubscription).not.toEqual(DUMMY_PUSH_SUBSCRIPTION);
devicestatestore.default = makeDeviceStateStore({
deviceId,
token: ENCODED_DUMMY_PUSH_SUBSCRIPTION,
userId: 'alice',
});
let beamsClient = new PusherPushNotifications.Client({
instanceId,
});
return beamsClient
.getDeviceId()
.then(returnedDeviceId => {
// Device ID should have been set
return expect(returnedDeviceId).toEqual(deviceId);
})
.then(() => {
// Change subscription
subscription = newSubscription;
})
.then(() => beamsClient.getDeviceId())
.then(deviceId => {
// Device ID should have been cleared
return expect(deviceId).toBeNull();
});
});
});
const setUpGlobals = ({

@@ -592,2 +727,4 @@ indexedDBSupport = true,

isSecureContext = true,
notificationPermission = 'default',
getSWSubscription = () => Promise.resolve(DUMMY_PUSH_SUBSCRIPTION),
}) => {

@@ -602,3 +739,3 @@ if (indexedDBSupport) {

pushManager: {
getSubscription: () => Promise.resolve(DUMMY_PUSH_SUBSCRIPTION),
getSubscription: getSWSubscription,
},

@@ -611,2 +748,5 @@ });

global.window.isSecureContext = isSecureContext;
global.Notification = {};
global.Notification.permission = notificationPermission;
};

@@ -618,51 +758,4 @@

delete global.navigator.serviceWorker;
delete global.window.isSecureContext;
delete global.Notification;
};
const makeDeviceStateStore = ({ deviceId, token, userId }) => {
class FakeDeviceStateStore {
constructor(instanceId) {
this.instanceId = instanceId;
this._deviceId = null;
this._token = null;
this._userId = null;
}
async connect() {
this._deviceId = deviceId || null;
this._token = token || null;
this._userId = userId || null;
}
async clear() {
this._deviceId = null;
this._token = null;
this._userId = null;
}
async getDeviceId() {
return this._deviceId;
}
async setDeviceId(deviceId) {
this._deviceId = deviceId;
}
async getToken() {
return this._token;
}
async setToken(token) {
this._token = token;
}
async getUserId() {
return this._userId;
}
async setUserId(userId) {
this._userId = userId;
}
}
return FakeDeviceStateStore;
};
/* eslint-env serviceworker */
import doRequest from './do-request';
import DeviceStateStore from './device-state-store';
self.PusherPushNotifications = {
endpointOverride: null,
onNotificationReceived: null,
_endpoint: instanceId =>
self.PusherPushNotifications.endpointOverride
? self.PusherPushNotifications.endpointOverride
: `https://${instanceId}.pushnotifications.pusher.com`,
_getVisibleClient: () =>
self.clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then(clients => clients.find(c => c.visibilityState === 'visible')),
_hasVisibleClient: () =>
self.PusherPushNotifications._getVisibleClient().then(
client => client !== undefined
),
_getFocusedClient: () =>
self.clients
.matchAll({
type: 'window',
includeUncontrolled: true,
})
.then(clients => clients.find(c => c.focused === true)),
_hasFocusedClient: () =>
self.PusherPushNotifications._getFocusedClient().then(
client => client !== undefined
),
reportEvent: async ({ eventType, pusherMetadata }) => {
const {
instanceId,
publishId,
hasDisplayableContent,
hasData,
} = pusherMetadata;
if (!instanceId || !publishId) {
// Can't report this notification, fail silently.
return;
}
const deviceStateStore = new DeviceStateStore(instanceId);
await deviceStateStore.connect();
const deviceId = await deviceStateStore.getDeviceId();
const userId = (await deviceStateStore.getUserId()) || null;
const appInBackground = !(await self.PusherPushNotifications._hasVisibleClient());
const path = `${self.PusherPushNotifications._endpoint(
instanceId
)}/reporting_api/v2/instances/${instanceId}/events`;
const options = {
method: 'POST',
path,
body: {
publishId,
event: eventType,
deviceId,
userId,
timestampSecs: Date.now() / 1000,
appInBackground,
hasDisplayableContent,
hasData,
},
};
try {
await doRequest(options);
} catch (_) {
// Reporting is best effort, so we do nothing.
}
},
};

@@ -18,2 +99,8 @@

// Report analytics event, best effort
self.PusherPushNotifications.reportEvent({
eventType: 'delivery',
pusherMetadata: payload.data.pusher,
});
const customerPayload = { ...payload };

@@ -28,3 +115,12 @@ const customerData = {};

const handleNotification = payload => {
const handleNotification = async payload => {
const hideNotificationIfSiteHasFocus =
payload.notification.hide_notification_if_site_has_focus === true;
if (
hideNotificationIfSiteHasFocus &&
(await self.PusherPushNotifications._hasFocusedClient())
) {
return;
}
const title = payload.notification.title || '';

@@ -59,2 +155,8 @@ const body = payload.notification.body || '';

if (isPusherNotification) {
// Report analytics event, best effort
self.PusherPushNotifications.reportEvent({
eventType: 'open',
pusherMetadata: payload.data.pusher,
});
if (payload.notification.deep_link) {

@@ -61,0 +163,0 @@ e.waitUntil(clients.openWindow(payload.notification.deep_link));

@@ -0,4 +1,16 @@

import { makeDeviceStateStore } from '../test-utils/fake-device-state-store';
const ASYNC_TEST_WAIT_MS = 100;
const TEST_INSTANCE_ID = 'some-instance-id';
const TEST_PUBLISH_ID = 'some-publish-id';
const TEST_NOTIFICATION_TITLE = 'Hi!';
const TEST_NOTIFICATION_BODY = 'This is a test notification!';
const TEST_NOTIFICATION_ICON = 'an-icon.png';
let listeners = {};
let shownNotifications = [];
let openedWindows = [];
let clients = [];
let now = new Date('2000-01-01T00:00:00Z');

@@ -9,2 +21,3 @@ beforeEach(() => {

openedWindows = [];
clients = [];

@@ -20,7 +33,26 @@ global.addEventListener = (name, func) => {

openWindow: url => openedWindows.push(url),
matchAll: () => Promise.resolve(clients),
};
global.Date.now = () => now.getTime();
jest.resetModules();
// Mock out IO modules
const devicestatestore = require('./device-state-store');
devicestatestore.default = makeDeviceStateStore({
deviceId: 'web-1db66b8a-f51f-49de-b225-72591535c855',
token: 'some-token',
userId: 'alice',
});
const dorequest = require('./do-request');
dorequest.default = () => Promise.resolve('ok');
});
afterEach(() => {
// Wait for any async operations to complete
// This is horrible, but we we want to do open/delivery tracking without
// blocking the callbacks this will have to do.
return new Promise(resolve => setTimeout(resolve, ASYNC_TEST_WAIT_MS));
});
describe('SW should ignore notification when', () => {

@@ -60,16 +92,3 @@ test.each([

// Given a push event that comes from Pusher
const pushEvent = makePushEvent(`
{
"notification": {
"title": "Hi!",
"body": "This is a notification!",
"icon": "my-icon.png"
},
"data": {
"pusher": {
"publishId": "some-publish-id"
}
}
}
`);
const pushEvent = makeBeamsPushEvent({});

@@ -86,16 +105,19 @@ // When the push listener is called

expect(shownNotifications[0]).toEqual({
title: 'Hi!',
title: TEST_NOTIFICATION_TITLE,
options: {
icon: 'my-icon.png',
body: 'This is a notification!',
icon: TEST_NOTIFICATION_ICON,
body: TEST_NOTIFICATION_BODY,
data: {
pusherPayload: {
notification: {
title: 'Hi!',
body: 'This is a notification!',
icon: 'my-icon.png',
title: TEST_NOTIFICATION_TITLE,
body: TEST_NOTIFICATION_BODY,
icon: TEST_NOTIFICATION_ICON,
},
data: {
pusher: {
publishId: 'some-publish-id',
instanceId: TEST_INSTANCE_ID,
publishId: TEST_PUBLISH_ID,
hasDisplayableContent: true,
hasData: false,
},

@@ -114,16 +136,3 @@ },

// Given a push event that comes from Pusher
const pushEvent = makePushEvent(`
{
"notification": {
"title": "Hi!",
"body": "This is a notification!",
"icon": "my-icon.png"
},
"data": {
"pusher": {
"publishId": "some-publish-id"
}
}
}
`);
const pushEvent = makeBeamsPushEvent({});

@@ -155,16 +164,3 @@ // And an onNotificationReceived had been set

// Given a push event that comes from Pusher
const pushEvent = makePushEvent(`
{
"notification": {
"title": "Hi!",
"body": "This is a notification!",
"icon": "my-icon.png"
},
"data": {
"pusher": {
"publishId": "some-publish-id"
}
}
}
`);
const pushEvent = makeBeamsPushEvent({});

@@ -187,5 +183,5 @@ // And an onNotificationReceived had been set

notification: {
title: 'Hi!',
body: 'This is a notification!',
icon: 'my-icon.png',
title: TEST_NOTIFICATION_TITLE,
body: TEST_NOTIFICATION_BODY,
icon: TEST_NOTIFICATION_ICON,
},

@@ -205,16 +201,3 @@ data: {}, // Pusher namespace should be stripped

// Given a push event that comes from Pusher
const pushEvent = makePushEvent(`
{
"notification": {
"title": "Hi!",
"body": "This is a notification!",
"icon": "my-icon.png"
},
"data": {
"pusher": {
"publishId": "some-publish-id"
}
}
}
`);
const pushEvent = makeBeamsPushEvent({});

@@ -303,9 +286,299 @@ // And an onNotificationReceived had been set

const makePushEvent = payload => ({
waitUntil: () => {},
data: {
json: () => JSON.parse(payload),
},
test('SW should send delivery event when notification arrives', () => {
jest.resetModules();
const devicestatestore = require('./device-state-store');
devicestatestore.default = makeDeviceStateStore({
deviceId: 'web-1db66b8a-f51f-49de-b225-72591535c855',
token: 'some-token',
userId: 'alice',
});
const dorequest = require('./do-request');
const mockDoRequest = jest.fn();
mockDoRequest.mockReturnValueOnce(Promise.resolve('ok'));
dorequest.default = mockDoRequest;
require('./service-worker.js');
// Given a push event that comes from Pusher
const pushEvent = makeBeamsPushEvent({});
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
// Then the correct delivery event should be reported
return new Promise(resolve => setTimeout(resolve, 200)).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
const requestOptions = mockDoRequest.mock.calls[0][0];
expect(requestOptions.method).toBe('POST');
expect(requestOptions.path).toBe(
[
`https://${TEST_INSTANCE_ID}.pushnotifications.pusher.com`,
`/reporting_api/v2/instances/${TEST_INSTANCE_ID}/events`,
].join('')
);
expect(requestOptions.body.publishId).toBe(TEST_PUBLISH_ID);
expect(requestOptions.body.event).toBe('delivery');
expect(requestOptions.body.userId).toBe('alice');
expect(requestOptions.body.timestampSecs).toBe(946684800);
expect(requestOptions.body.appInBackground).toBe(true);
expect(requestOptions.body.hasDisplayableContent).toBe(true);
expect(requestOptions.body.hasData).toBe(false);
});
});
test('SW should send open event when notification clicked', () => {
jest.resetModules();
const devicestatestore = require('./device-state-store');
devicestatestore.default = makeDeviceStateStore({
deviceId: 'web-1db66b8a-f51f-49de-b225-72591535c855',
token: 'some-token',
userId: 'alice',
});
const dorequest = require('./do-request');
const mockDoRequest = jest.fn();
mockDoRequest.mockReturnValueOnce(Promise.resolve('ok'));
dorequest.default = mockDoRequest;
require('./service-worker.js');
// Given a notification click event with a deep link
const clickEvent = makeClickEvent({
data: {
pusherPayload: {
notification: {
title: 'Hi!',
body: 'This is a notification!',
deep_link: 'https://pusher.com',
},
data: {
pusher: {
instanceId: TEST_INSTANCE_ID,
publishId: TEST_PUBLISH_ID,
hasDisplayableContent: true,
hasData: false,
},
},
},
},
});
// When the notificationclick listener is called
const clickListener = listeners['notificationclick'];
if (!clickListener) {
throw new Error('No click listener has been set');
}
clickListener(clickEvent);
// Then an open event should be reported
return new Promise(resolve => setTimeout(resolve, 200)).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
const requestOptions = mockDoRequest.mock.calls[0][0];
expect(requestOptions.method).toBe('POST');
expect(requestOptions.path).toBe(
[
`https://${TEST_INSTANCE_ID}.pushnotifications.pusher.com`,
`/reporting_api/v2/instances/${TEST_INSTANCE_ID}/events`,
].join('')
);
expect(requestOptions.body.publishId).toBe(TEST_PUBLISH_ID);
expect(requestOptions.body.event).toBe('open');
expect(requestOptions.body.userId).toBe('alice');
expect(requestOptions.body.timestampSecs).toBe(946684800);
expect(requestOptions.body.appInBackground).toBe(true);
expect(requestOptions.body.hasDisplayableContent).toBe(true);
expect(requestOptions.body.hasData).toBe(false);
});
});
test('SW should send event with appInBackground false given a visible client', () => {
jest.resetModules();
const devicestatestore = require('./device-state-store');
devicestatestore.default = makeDeviceStateStore({
deviceId: 'web-1db66b8a-f51f-49de-b225-72591535c855',
token: 'some-token',
userId: 'alice',
});
const dorequest = require('./do-request');
const mockDoRequest = jest.fn();
mockDoRequest.mockReturnValueOnce(Promise.resolve('ok'));
dorequest.default = mockDoRequest;
require('./service-worker.js');
// Given a push event that comes from Pusher
const pushEvent = makeBeamsPushEvent({});
// and at least once visible client
registerVisibleClient();
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
// Then the correct delivery event should be reported
return new Promise(resolve => setTimeout(resolve, 200)).then(() => {
expect(mockDoRequest.mock.calls.length).toBe(1);
expect(mockDoRequest.mock.calls[0].length).toBe(1);
const requestOptions = mockDoRequest.mock.calls[0][0];
expect(requestOptions.body.appInBackground).toBe(false);
});
});
test('SW should show notification if site has focus but hide flag is not set', () => {
require('./service-worker.js');
// Given a push event that comes from Pusher without the flag set
const pushEvent = makeBeamsPushEvent({
hide_notification_if_site_has_focus: undefined,
});
// and at least once focused client
registerFocusedClient();
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
return pushEvent.getWaitUntilPromise().then(() => {
// Then a notification should be shown
expect(shownNotifications).toHaveLength(1);
});
});
test('SW should show notification if site has focus but hide flag is false', () => {
require('./service-worker.js');
// Given a push event that comes from Pusher with the flag set to false
const pushEvent = makeBeamsPushEvent({
hide_notification_if_site_has_focus: false,
});
// and at least once focused client
registerFocusedClient();
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
return pushEvent.getWaitUntilPromise().then(() => {
// Then a notification should be shown
expect(shownNotifications).toHaveLength(1);
});
});
test('SW should not show notification if site has focus and hide flag is true', () => {
require('./service-worker.js');
// Given a push event that comes from Pusher with the flag set
const pushEvent = makeBeamsPushEvent({
hide_notification_if_site_has_focus: true,
});
// and at least once focused client
registerFocusedClient();
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
return pushEvent.getWaitUntilPromise().then(() => {
// Then a notification should not be shown
expect(shownNotifications).toHaveLength(0);
});
});
test('SW should show notification if site does not have focus and hide flag is true', () => {
require('./service-worker.js');
// Given a push event that comes from Pusher with the flag set
const pushEvent = makeBeamsPushEvent({
hide_notification_if_site_has_focus: true,
});
// and no focused clients
expect(clients).toHaveLength(0);
// When the push listener is called
const pushListener = listeners['push'];
if (!pushListener) {
throw new Error('No push listener has been set');
}
pushListener(pushEvent);
return pushEvent.getWaitUntilPromise().then(() => {
// Then a notification should be shown
expect(shownNotifications).toHaveLength(1);
});
});
class FakePushEvent {
constructor(payload) {
this.data = {
json: () => JSON.parse(payload),
};
this.waitUntil = promise => {
this.waitUntilPromise = promise;
};
}
getWaitUntilPromise() {
expect(this.waitUntilPromise).not.toBeUndefined();
return this.waitUntilPromise;
}
}
const makePushEvent = payload => new FakePushEvent(payload);
const makeBeamsPushEvent = ({
instanceId = TEST_INSTANCE_ID,
publishId = TEST_PUBLISH_ID,
title = TEST_NOTIFICATION_TITLE,
body = TEST_NOTIFICATION_BODY,
icon = TEST_NOTIFICATION_ICON,
hide_notification_if_site_has_focus = undefined,
}) =>
makePushEvent(
JSON.stringify({
notification: { title, body, icon, hide_notification_if_site_has_focus },
data: {
pusher: {
instanceId,
publishId,
hasDisplayableContent: true,
hasData: false,
},
},
})
);
const makeClickEvent = ({ data }) => {

@@ -326,1 +599,6 @@ let isOpen = true;

};
const registerVisibleClient = () =>
clients.push({ visibilityState: 'visible' });
const registerFocusedClient = () => clients.push({ focused: true });

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc