Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better tests #81

Merged
merged 2 commits into from
Jun 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
166 changes: 104 additions & 62 deletions src/OneSignal.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { DEV_HOST, DEV_FRAME_HOST, PROD_HOST, API_URL } from './vars.js';
import Environment from './environment.js';
import './string.js';
import OneSignalApi from './oneSignalApi.js';
import IndexedDb from './indexedDb';
import log from 'loglevel';
import LimitStore from './limitStore.js';
import Event from "./events.js";
import Bell from "./bell/bell.js";
import Database from './database.js';
import * as Browser from 'bowser';
import { isPushNotificationsSupported, isPushNotificationsSupportedAndWarn, getConsoleStyle, once, guid, contains, normalizeSubdomain, decodeHtmlEntities, getUrlQueryParam, executeAndTimeoutPromiseAfter } from './utils.js';
import { isPushNotificationsSupported, isPushNotificationsSupportedAndWarn, getConsoleStyle, once, guid, contains, normalizeSubdomain, decodeHtmlEntities, getUrlQueryParam, executeAndTimeoutPromiseAfter, wipeIndexedDb, wipeServiceWorkerAndUnsubscribe } from './utils.js';
import objectAssign from 'object-assign';
import EventEmitter from 'wolfy87-eventemitter';
import heir from 'heir';
Expand Down Expand Up @@ -97,7 +98,13 @@ export default class OneSignal {
}

static _onDbValueSet(info) {
if (info.type === 'userId') {
/*
For HTTPS sites, this is how the subscription change event gets fired.
For HTTP sites, leaving this enabled fires the subscription change event twice. The first event is from Postmam
remotely re-triggering the db.set event to notify the host page that the popup set the user ID in the db. The second
event is from Postmam remotely re-triggering the subscription.changed event which is also fired from the popup.
*/
if (info.type === 'userId' && !OneSignal.isUsingSubscriptionWorkaround()) {
OneSignalHelpers.checkAndTriggerSubscriptionChanged();
}
}
Expand Down Expand Up @@ -488,6 +495,17 @@ export default class OneSignal {
}
let receiveFromOrigin = options.origin;
let handshakeNonce = getUrlQueryParam('session');
let shouldWipeData = getUrlQueryParam('dangerouslyWipeData');

let preinitializePromise = Promise.resolve();
if (shouldWipeData) {
OneSignal.LOGGING = true;
// Wipe IndexedDB and unsubscribe from push/unregister the service worker for testing.
console.warn('Wiping away previous HTTP data.');
preinitializePromise = wipeIndexedDb()
.then(() => wipeServiceWorkerAndUnsubscribe())
.then(() => IndexedDb.put('Ids', {type: 'appId', id: options.appId}));
}

OneSignal._thisIsThePopup = options.isPopup;
if (Environment.isPopup() || OneSignal._thisIsThePopup) {
Expand All @@ -506,22 +524,22 @@ export default class OneSignal {
});
OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_NOTIFICATION_PERMISSION, message => {
OneSignal.getNotificationPermission()
.then(permission => message.reply(permission));
.then(permission => message.reply(permission));
return false;
});
OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_GET, message => {
// retrievals is an array of key-value pairs e.g. [{table: 'Ids', keys: 'userId'}, {table: 'Ids', keys: 'registrationId'}]
let retrievals = message.data;
let retrievalOpPromises = [];
for (let retrieval of retrievals) {
let { table, key } = retrieval;
let {table, key} = retrieval;
if (!table || !key) {
log.error('Missing table or key for remote database get.', 'table:', table, 'key:', key);
}
retrievalOpPromises.push(Database.get(table, key));
}
Promise.all(retrievalOpPromises)
.then(results => message.reply(results));
.then(results => message.reply(results));
return false;
});
OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_PUT, message => {
Expand All @@ -530,11 +548,11 @@ export default class OneSignal {
let insertions = message.data;
let insertionOpPromises = [];
for (let insertion of insertions) {
let { table, keypath } = insertion;
let {table, keypath} = insertion;
insertionOpPromises.push(Database.put(table, keypath));
}
Promise.all(insertionOpPromises)
.then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE));
.then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE));
return false;
});
OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_REMOVE, message => {
Expand All @@ -543,73 +561,78 @@ export default class OneSignal {
let removals = message.data;
let removalOpPromises = [];
for (let removal of removals) {
let { table, keypath } = removal;
let {table, keypath} = removal;
removalOpPromises.push(Database.remove(table, keypath));
}
Promise.all(removalOpPromises)
.then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE));
.then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE));
return false;
});
OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.IFRAME_POPUP_INITIALIZE, message => {
log.warn(`(${Environment.getEnv()}) The iFrame has just received initOptions from the host page!`);
OneSignal.config = objectAssign(message.data.hostInitOptions, options, {
pageUrl: message.data.pageUrl,
pageTitle: message.data.pageTitle
});

OneSignal._installNativePromptPermissionChangedHook();

let opPromises = [];
if (options.continuePressed) {
opPromises.push(OneSignal.setSubscription(true));
}
// 3/30/16: For HTTP sites, put the host page URL as default URL if one doesn't exist already
opPromises.push(Database.get('Options', 'defaultUrl').then(defaultUrl => {
if (!defaultUrl) {
return Database.put('Options', {key: 'defaultUrl', value: new URL(OneSignal.config.pageUrl).origin});
}
}));

opPromises.push(Database.get("NotificationOpened", OneSignal.config.pageUrl)
.then(notificationOpenedResult => {
if (notificationOpenedResult) {
Database.remove("NotificationOpened", OneSignal.config.pageUrl);
OneSignal.iframePostmam.message(OneSignal.POSTMAM_COMMANDS.NOTIFICATION_OPENED, notificationOpenedResult);
}
}));
preinitializePromise.then(() => {
OneSignal.config = objectAssign(message.data.hostInitOptions, options, {
pageUrl: message.data.pageUrl,
pageTitle: message.data.pageTitle
});

OneSignal._installNativePromptPermissionChangedHook();

opPromises.push(OneSignal._initSaveState());
opPromises.push(OneSignal._storeInitialValues());
opPromises.push(OneSignal._saveInitOptions());
Promise.all(opPromises)
.then(() => {
if (contains(location.search, "continuingSession=true"))
return;
let opPromises = [];
if (options.continuePressed) {
opPromises.push(OneSignal.setSubscription(true));
}
// 3/30/16: For HTTP sites, put the host page URL as default URL if one doesn't exist already
opPromises.push(Database.get('Options', 'defaultUrl').then(defaultUrl => {
if (!defaultUrl) {
return Database.put('Options', {key: 'defaultUrl', value: new URL(OneSignal.config.pageUrl).origin});
}
}));

/* 3/20/16: In the future, if navigator.serviceWorker.ready is unusable inside of an insecure iFrame host, adding a message event listener will still work. */
//if (navigator.serviceWorker) {
//log.warn('We have added an event listener for service worker messages.', Environment.getEnv());
//navigator.serviceWorker.addEventListener('message', function(event) {
// log.warn('Wow! We got a message!', event);
//});
//}

if (navigator.serviceWorker && window.location.protocol === 'https:') {
navigator.serviceWorker.ready
.then(registration => {
if (registration && registration.active) {
OneSignalHelpers.establishServiceWorkerChannel(registration);
}
})
.catch(e => {
log.error(`Error interacting with Service Worker inside an HTTP-hosted iFrame:`, e);
});
}
opPromises.push(Database.get("NotificationOpened", OneSignal.config.pageUrl)
.then(notificationOpenedResult => {
if (notificationOpenedResult) {
Database.remove("NotificationOpened", OneSignal.config.pageUrl);
OneSignal.iframePostmam.message(OneSignal.POSTMAM_COMMANDS.NOTIFICATION_OPENED, notificationOpenedResult);
}
}));


opPromises.push(OneSignal._initSaveState());
opPromises.push(OneSignal._storeInitialValues());
opPromises.push(OneSignal._saveInitOptions());
Promise.all(opPromises)
.then(() => {
if (contains(location.search, "continuingSession=true"))
return;

/* 3/20/16: In the future, if navigator.serviceWorker.ready is unusable inside of an insecure iFrame host, adding a message event listener will still work. */
//if (navigator.serviceWorker) {
//log.warn('We have added an event listener for service worker messages.', Environment.getEnv());
//navigator.serviceWorker.addEventListener('message', function(event) {
// log.warn('Wow! We got a message!', event);
//});
//}

if (navigator.serviceWorker && window.location.protocol === 'https:') {
navigator.serviceWorker.ready
.then(registration => {
if (registration && registration.active) {
OneSignalHelpers.establishServiceWorkerChannel(registration);
}
})
.catch(e => {
log.error(`Error interacting with Service Worker inside an HTTP-hosted iFrame:`, e);
});
}

message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE);
});
message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE);
});
})
.catch(e => console.error(e));
});
Event.trigger('httpInitialize');
}

static _initPopup() {
Expand Down Expand Up @@ -663,7 +686,12 @@ export default class OneSignal {
log.debug(`Called %cloadSubdomainIFrame()`, getConsoleStyle('code'));

// TODO: Previously, '?session=true' added to the iFrame's URL meant this was not a new tab (same page refresh) and that the HTTP iFrame should not re-register the service worker. Now that is gone, find an alternative way to do that.

let dangerouslyWipeData = OneSignal.config.dangerouslyWipeData;
let iframeUrl = `${OneSignal.iframePopupModalUrl}Iframe?session=${OneSignal._sessionNonce}`;
if (dangerouslyWipeData) {
iframeUrl += '&dangerouslyWipeData=true';
}
if (OneSignalHelpers.isContinuingBrowserSession()) {
iframeUrl += `&continuingSession=true`;
}
Expand Down Expand Up @@ -761,7 +789,11 @@ export default class OneSignal {
}
let receiveFromOrigin = sendToOrigin;
let handshakeNonce = OneSignal._sessionNonce;
let dangerouslyWipeData = OneSignal.config.dangerouslyWipeData;
let popupUrl = `${OneSignal.iframePopupModalUrl}?${OneSignalHelpers.getPromptOptionsQueryString()}&session=${handshakeNonce}&promptType=popup`;
if (dangerouslyWipeData) {
popupUrl += '&dangerouslyWipeData=true';
}
log.info('Opening popup window:', popupUrl);
var subdomainPopup = OneSignalHelpers.openSubdomainPopup(popupUrl);

Expand All @@ -779,6 +811,9 @@ export default class OneSignal {
return false;
});

OneSignal.popupPostmam.once(OneSignal.POSTMAM_COMMANDS.POPUP_LOADED, message => {
Event.trigger('popupLoad');
});
OneSignal.popupPostmam.once(OneSignal.POSTMAM_COMMANDS.POPUP_ACCEPTED, message => {
OneSignalHelpers.triggerCustomPromptClicked('granted');
});
Expand Down Expand Up @@ -856,6 +891,9 @@ export default class OneSignal {
OneSignal.modalPostmam.startPostMessageReceive();

return new Promise((resolve, reject) => {
OneSignal.modalPostmam.once(OneSignal.POSTMAM_COMMANDS.MODAL_LOADED, message => {
Event.trigger('modalLoaded');
});
OneSignal.modalPostmam.once(OneSignal.POSTMAM_COMMANDS.POPUP_ACCEPTED, message => {
let iframeModalDom = document.getElementById('OneSignal-iframe-modal');
iframeModalDom.parentNode.removeChild(iframeModalDom);
Expand Down Expand Up @@ -1233,6 +1271,7 @@ export default class OneSignal {
notificationPermissionBeforeRequest = permission;
})
.then(() => {
log.debug("Calling service worker's pushManager.subscribe()");
return serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true});
})
.then(function (subscription) {
Expand All @@ -1242,7 +1281,7 @@ export default class OneSignal {

OneSignal.getAppId()
.then(appId => {
log.debug("Called OneSignal._subscribeForPush() -> serviceWorkerRegistration.pushManager.subscribe().");
log.debug("Finished subscribing for push via pushManager.subscribe().");

var subscriptionInfo = {};
if (subscription) {
Expand Down Expand Up @@ -1895,6 +1934,7 @@ objectAssign(OneSignal, {
log: log,
swivel: swivel,
api: OneSignalApi,
indexedDb: IndexedDb,
_sessionNonce: null,
iframePostmam: null,
popupPostmam: null,
Expand All @@ -1914,8 +1954,10 @@ objectAssign(OneSignal, {
REMOTE_DATABASE_REMOVE: 'postmam.remoteDatabaseRemove',
REMOTE_OPERATION_COMPLETE: 'postman.operationComplete',
REMOTE_RETRIGGER_EVENT: 'postmam.remoteRetriggerEvent',
MODAL_LOADED: 'postmam.modalPrompt.loaded',
MODAL_PROMPT_ACCEPTED: 'postmam.modalPrompt.accepted',
MODAL_PROMPT_REJECTED: 'postmam.modalPrompt.canceled',
POPUP_LOADED: 'postmam.popup.loaded',
POPUP_ACCEPTED: 'postmam.popup.accepted',
POPUP_REJECTED: 'postmam.popup.canceled',
POPUP_CLOSING: 'postman.popup.closing',
Expand Down
5 changes: 3 additions & 2 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ const RETRIGGER_REMOTE_EVENTS = [
'subscriptionSet',
'sendWelcomeNotification',
'subscriptionChange',
'notificationPermissionChange'
'notificationPermissionChange',
'dbSet'
];

const LEGACY_EVENT_MAP = {
'notificationPermissionChange': 'onesignal.prompt.native.permissionchanged',
'subscriptionChange': 'onesignal.subscription.changed',
'customPromptClick': 'onesignal.prompt.custom.clicked',
}
};

export default class Event {

Expand Down
48 changes: 48 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import log from 'loglevel';
import * as Browser from 'bowser';
import Environment from './environment.js';
import IndexedDb from './indexedDb';

export function isArray(variable) {
return Object.prototype.toString.call( variable ) === '[object Array]';
Expand Down Expand Up @@ -284,6 +285,7 @@ export function executeAndTimeoutPromiseAfter(promise, milliseconds, displayErro
log.warn(displayError || `Promise ${promise} timed out after ${milliseconds} ms.`);
return Promise.reject(displayError || `Promise ${promise} timed out after ${milliseconds} ms.`);
}
else return value;
});
}

Expand Down Expand Up @@ -364,4 +366,50 @@ export function getUrlQueryParam(name) {
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}

/**
* Wipe OneSignal-related IndexedDB data.
*/
export function wipeIndexedDb() {
console.warn('OneSignal: Wiping IndexedDB data.');
return Promise.all([
IndexedDb.remove('Ids'),
IndexedDb.remove('NotificationOpened'),
IndexedDb.remove('Options')
]);
}


/**
* Unsubscribe from push notifications and remove any active service worker.
*/
export function wipeServiceWorkerAndUnsubscribe() {
console.warn('OneSignal: Unsubscribe from push and unregistering service worker.');
if (Environment.isIframe()) {
return;
}
if (!navigator.serviceWorker || !navigator.serviceWorker.controller)
return Promise.resolve();

let unsubscribePromise = navigator.serviceWorker.ready
.then(registration => registration.pushManager)
.then(pushManager => pushManager.getSubscription())
.then(subscription => {
if (subscription) {
return subscription.unsubscribe();
}
});

let unregisterWorkerPromise = navigator.serviceWorker.ready
.then(registration => registration.unregister());

return Promise.all([
unsubscribePromise,
unregisterWorkerPromise
]);
}

export function wait(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}