diff --git a/src/OneSignal.js b/src/OneSignal.js index 9d81bb69f..9a5f0d9e1 100644 --- a/src/OneSignal.js +++ b/src/OneSignal.js @@ -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'; @@ -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(); } } @@ -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) { @@ -506,7 +524,7 @@ 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 => { @@ -514,14 +532,14 @@ export default class OneSignal { 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 => { @@ -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 => { @@ -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() { @@ -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`; } @@ -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); @@ -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'); }); @@ -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); @@ -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) { @@ -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) { @@ -1895,6 +1934,7 @@ objectAssign(OneSignal, { log: log, swivel: swivel, api: OneSignalApi, + indexedDb: IndexedDb, _sessionNonce: null, iframePostmam: null, popupPostmam: null, @@ -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', diff --git a/src/events.js b/src/events.js index 797c38683..55a4d5472 100644 --- a/src/events.js +++ b/src/events.js @@ -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 { diff --git a/src/utils.js b/src/utils.js index 9253c5d69..58c7f3fab 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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]'; @@ -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; }); } @@ -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)); } \ No newline at end of file diff --git a/test/entry.js b/test/entry.js index 9edc4d466..66d536780 100644 --- a/test/entry.js +++ b/test/entry.js @@ -1,12 +1,8 @@ import Extension from './extension'; export default class OneSignalTests { - static runHttpsTests() { - require('./httpsTests.js'); - } - - static runUnsubscribedTests() { - require('./unsubscribedTests.js'); + static runTests() { + require('./tests.js'); } } diff --git a/test/extension.js b/test/extension.js index 2a85ba51f..7676261d3 100644 --- a/test/extension.js +++ b/test/extension.js @@ -5,9 +5,11 @@ export default class Extension { static get COMMANDS() { return { + SET_POPUP_PERMISSION: 'SET_POPUP_PERMISSION', SET_NOTIFICATION_PERMISSION: 'SET_NOTIFICATION_PERMISSION', CREATE_BROWSER_TAB: 'CREATE_BROWSER_TAB', - EXECUTE_SCRIPT: 'EXECUTE_SCRIPT' + EXECUTE_SCRIPT: 'EXECUTE_SCRIPT', + ACCEPT_HTTP_SUBSCRIPTION_POPUP: 'ACCEPT_HTTP_SUBSCRIPTION_POPUP' }; } @@ -24,6 +26,19 @@ export default class Extension { }); } + /** + * Sets the site's popup permission setting. + * @param siteUrl The match pattern URL for a website. + * @param permission One of 'allow', 'block', or 'clear'. + */ + static setPopupPermission(siteUrl, permission) { + return Extension.message({ + command: Extension.COMMANDS.SET_POPUP_PERMISSION, + siteUrl: siteUrl, + permission: permission + }); + } + /** * Creates a new Chrome browser tab. * @param url The URL the browser tab should initially navigate to. @@ -48,6 +63,14 @@ export default class Extension { }); } + /** + * Attempts to click the 'Continue' button on the HTTP subscription popup. + */ + static acceptHttpSubscriptionPopup() { + return Extension.message({ + command: Extension.COMMANDS.ACCEPT_HTTP_SUBSCRIPTION_POPUP + }); + } static message(data) { return new Promise((resolve, reject) => { diff --git a/test/httpsTests.js b/test/tests.js similarity index 50% rename from test/httpsTests.js rename to test/tests.js index 9a7d85754..f8b5ce74a 100644 --- a/test/httpsTests.js +++ b/test/tests.js @@ -5,8 +5,11 @@ import {APP_ID, PLAYER_ID} from './vars.js'; import SoloTest from './soloTest'; import PMPlus from './PMPlus'; import Utils from './utils'; -import { executeAndTimeoutPromiseAfter, guid } from '../src/utils'; +import { executeAndTimeoutPromiseAfter, guid, isPushNotificationsSupported, isPushNotificationsSupportedAndWarn } from '../src/utils'; import IndexedDb from '../src/indexedDb'; +import Environment from '../src/environment.js'; +import Postmam from '../src/postmam.js'; +import Database from '../src/database'; chai.config.includeStack = false; @@ -15,34 +18,18 @@ chai.config.truncateThreshold = 0; describe('HTTPS Tests', function() { - describe('Notifications', function() { + describe('Notifications', function () { it('should subscribe and receive a welcome notification successfully', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true - }]); - - OneSignal.on('notificationDisplay', resolve); - }); - }); + return Utils.initialize({ + welcomeNotification: true, + autoRegister: true }) + .then(() => Utils.expectEvent('notificationDisplay')) .then(notification => { expect(notification).to.not.be.null; expect(notification).to.have.property('message', 'Thanks for subscribing!'); - return new Promise(resolve => setTimeout(resolve, 250)); + return Utils.wait(150); }) .then(() => OneSignal.closeNotifications()); }); @@ -50,41 +37,19 @@ describe('HTTPS Tests', function() { it('should subscribe and receive a notification successfully', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) .then(() => { - return new Promise(resolve => { - OneSignal.on('notificationDisplay', resolve); - OneSignal.sendSelfNotification() - }); + OneSignal.sendSelfNotification(); + return Utils.expectEvent('notificationDisplay') }) .then(notification => { expect(notification).to.not.be.null; expect(notification).to.have.property('message', 'This is an example notification.'); expect(notification).to.have.property('title', 'OneSignal Test Message'); - return new Promise(resolve => setTimeout(resolve, 250)); + return Utils.wait(150); }) .then(() => OneSignal.closeNotifications()); }); @@ -134,29 +99,9 @@ describe('HTTPS Tests', function() { it('should send, receive, and delete tags successfully', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) .then(() => OneSignal.sendTags(sentTags)) .then(() => OneSignal.getTags()) @@ -181,35 +126,15 @@ describe('HTTPS Tests', function() { Object.keys(expectedTags).forEach(tag => { expect(receivedTags.hasOwnProperty(tag)).to.be.false; }); - }) + }); }); }); it('should successfully send, receive, and delete tags via callbacks', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) .then(() => { function getTagsCallback(receivedTags) { @@ -248,7 +173,7 @@ describe('HTTPS Tests', function() { } OneSignal.sendTags(sentTags, onSendTagsComplete); - }) + }); }); }); @@ -256,29 +181,10 @@ describe('HTTPS Tests', function() { return new SoloTest(this.test, {}, () => { let tagKey = 'string'; let tagValue = sentTags[tagKey]; - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); + + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) .then(() => OneSignal.sendTag(tagKey, tagValue)) .then(() => OneSignal.getTags()) @@ -291,35 +197,15 @@ describe('HTTPS Tests', function() { .then(receivedTags => { expect(receivedTags).to.not.be.undefined; expect(receivedTags[tagKey]).to.be.undefined; - }) + }); }); }); it('should return Promise objects', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) .then(() => { let getTagsReturnValue = OneSignal.getTags(); @@ -327,37 +213,25 @@ describe('HTTPS Tests', function() { let sendTagsReturnValue = OneSignal.sendTags(); let deleteTagReturnValue = OneSignal.deleteTag(''); let deleteTagsReturnValue = OneSignal.deleteTags(['']); - [getTagsReturnValue, sendTagReturnValue, sendTagsReturnValue, deleteTagReturnValue, deleteTagsReturnValue].forEach(x => expect(x.constructor.name).to.equal('Promise')); + [getTagsReturnValue, + sendTagReturnValue, + sendTagsReturnValue, + deleteTagReturnValue, + deleteTagsReturnValue].forEach(x => expect(x.constructor.name).to.equal('Promise')); }); }); }); it('should automatically be sent after subscribing if called before subscribing', function () { - let tagValue = guid(); return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) + let tagValue = guid(); + + return Utils.initialize({ + welcomeNotification: false, + autoRegister: false + }) .then(() => { - // Initialize OneSignal - return new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: false, - welcomeNotification: { - disable: true - } - }]); - OneSignal.on('initialize', resolve); - }); - }) - .then(() => OneSignal.getTags()); + return OneSignal.getTags(); }) .then(tags => { expect(tags).to.be.null; @@ -365,7 +239,11 @@ describe('HTTPS Tests', function() { return executeAndTimeoutPromiseAfter(new Promise(resolve => { OneSignal.sendTags({key: tagValue}).then(resolve); OneSignal.registerForPushNotifications(); - }).catch(e => console.error(e)), 5000, + if (location.protocol === 'http:') { + Utils.expectEvent('popupLoad') + .then(() => Extension.acceptHttpSubscriptionPopup()) + } + }).catch(e => console.error(e)), 7000, 'Expected tags to be sent after subscription but tags were not sent.'); }) .then(() => { @@ -374,7 +252,7 @@ describe('HTTPS Tests', function() { .then(tags => { expect(tags).to.not.be.null; expect(tags).to.have.property('key', tagValue); - }) + }); }); }); }); @@ -382,43 +260,20 @@ describe('HTTPS Tests', function() { describe('Server-Sided State Changes', function () { it('should remove client-sided data if user is deleted from OneSignal dashboard', function () { return new SoloTest(this.test, {}, () => { - return Promise.all([ - // Wipe database and force allow notifications permission - Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), - Utils.wipeIndexedDb(), - Utils.wipeServiceWorkerAndUnsubscribe() - ]) - .then(() => { - // Initialize OneSignal and subscribe - return executeAndTimeoutPromiseAfter(new Promise(resolve => { - window.OneSignal = OneSignal || []; - OneSignal.push(function () { - OneSignal.LOGGING = true; - OneSignal.push(["init", { - appId: APP_ID, - autoRegister: true, - welcomeNotification: { - disable: true - } - }]); - - OneSignal.database.printIds(); - OneSignal.on('subscriptionChange', resolve); - }); - }).catch(e => console.error(e)), 3000, 'No subscription change after given time.'); - }) - .then(() => { - // We're now subscribed - return OneSignal.getUserId(); + let tagValue = guid(); + + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true }) + .then(() => OneSignal.getUserId()) .then(id => { - console.log('User ID (initial subscription):', id); expect(id).to.not.be.null; // Set the user ID to something else; this has the same effect as deleting an ID on the dashboard let newId = guid(); return Promise.all([id, newId, - IndexedDb.put("Ids", {type: "userId", id: newId})]); + OneSignal.database.put("Ids", {type: "userId", id: newId})]); }) .then(([originalId, newId]) => { // Ids should be diff @@ -442,4 +297,122 @@ describe('HTTPS Tests', function() { }); }); }); + + describe('Environment', () => { + it('isPushNotificationsSupported() should return true', () => { + expect(isPushNotificationsSupported()).to.be.true; + }); + + it('isPushNotificationsSupportedAndWarn() should return true', () => { + expect(isPushNotificationsSupportedAndWarn()).to.be.true; + }); + }) + + describe('Subdomain', () => { + it('valid subdomains should have the proper subdomain extracted', () => { + let validSubdomains = [ + 'subdomain', + ' subdomain ', + 'https://subdomain.onesignal.com', + 'https://subdomain.onesignal.com/', + 'http://www.subdomain.onesignal.com', + 'https://www.subdomain.onesignal.com/', + 'http://subdomain.onesignal.com', + 'http://subdomain.onesignal.com/', + 'subdomain.onesignal.com', + ]; + let expectedNormalizedSubdomain = 'subdomain'; + for (let validSubdomain of validSubdomains) { + let actualNormalizedSubdomain = OneSignal.helpers.getNormalizedSubdomain(validSubdomain); + expect(actualNormalizedSubdomain).to.equal(expectedNormalizedSubdomain); + } + }); + }); + + describe('Postmam origin checking', () => { + it('isSafeOrigin for HTTP sites', () => { + let origin = 'http://site.com'; + let postmam = new Postmam(window, origin, origin, 'nonce'); + expect(postmam.isSafeOrigin('http://site.com')).to.be.true; + expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; + expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; + }); + it('isSafeOrigin for HTTP www. sites', () => { + let origin = 'http://www.site.com'; + let postmam = new Postmam(window, origin, origin, 'nonce'); + expect(postmam.isSafeOrigin('http://site.com')).to.be.true; + expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; + expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; + }); + + it('isSafeOrigin for HTTPS sites', () => { + let origin = 'https://site.com'; + let postmam = new Postmam(window, origin, origin, 'nonce'); + expect(postmam.isSafeOrigin('http://site.com')).to.be.false; + expect(postmam.isSafeOrigin('http://www.site.com')).to.be.false; + expect(postmam.isSafeOrigin('https://site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; + expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; + }); + + it('isSafeOrigin for * sites', () => { + let origin = '*'; + let postmam = new Postmam(window, origin, origin, 'nonce'); + expect(postmam.isSafeOrigin('http://site.com')).to.be.true; + expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; + expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.true; + expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.true; + expect(postmam.isSafeOrigin('abc')).to.be.true; + }); + + it('isSafeOrigin for invalid sites', () => { + let origin = '*.google.com'; + let postmam = new Postmam(window, origin, origin, 'nonce'); + expect(postmam.isSafeOrigin('http://site.com')).to.be.false; + expect(postmam.isSafeOrigin('http://www.site.com')).to.be.false; + expect(postmam.isSafeOrigin('https://site.com')).to.be.false; + expect(postmam.isSafeOrigin('https://www.site.com')).to.be.false; + expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; + expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; + expect(postmam.isSafeOrigin('abc')).to.be.false; + }); + }); + + describe('Navigator language checking', () => { + it('should detect navigator language correctly', () => { + expect(Environment.getLanguage('en-US')).to.equal('en'); + expect(Environment.getLanguage('english-US')).to.equal('en'); + expect(Environment.getLanguage('zh')).to.equal('zh-Hant'); + expect(Environment.getLanguage('zh-CN')).to.equal('zh-Hans'); + expect(Environment.getLanguage('zh-Hans')).to.equal('zh-Hans'); + expect(Environment.getLanguage('zh-TW')).to.equal('zh-Hant'); + expect(Environment.getLanguage('zh-Hant')).to.equal('zh-Hant'); + expect(Environment.getLanguage('de-Arabic')).to.equal('de'); + }); + }); + + describe('SDK Events', () => { + it('subscriptionChange event should fire at most once when subscribing', function () { + return new SoloTest(this.test, {}, () => { + let subscriptionChangeEventCount = 0; + OneSignal.on('subscriptionChange', () => subscriptionChangeEventCount++); + return Utils.initialize({ + welcomeNotification: false, + autoRegister: true + }) + .then(() => Utils.wait(1000)) + .then(() => expect(subscriptionChangeEventCount).to.equal(1)); + + }); + }); + }); }); \ No newline at end of file diff --git a/test/unsubscribedTests.js b/test/unsubscribedTests.js deleted file mode 100644 index 23a81de33..000000000 --- a/test/unsubscribedTests.js +++ /dev/null @@ -1,116 +0,0 @@ -import chai, { expect } from 'chai'; -import chaiAsPromised from 'chai-as-promised'; -import StackTrace from 'stacktrace-js'; -import log from 'loglevel'; -import { guid, delay, isPushNotificationsSupported, isPushNotificationsSupportedAndWarn, logError } from '../src/utils.js'; -import Postmam from '../src/postmam.js'; -import Environment from '../src/environment.js'; -import {APP_ID, PLAYER_ID} from './vars.js'; - -chai.use(chaiAsPromised); - -describe('sdk.js', function(done) { - describe('Environment', () => { - it('isPushNotificationsSupported() should return true', () => { - expect(isPushNotificationsSupported()).to.be.true; - }); - - it('isPushNotificationsSupportedAndWarn() should return true', () => { - expect(isPushNotificationsSupportedAndWarn()).to.be.true; - }); - }) - - describe('SDK Initialization', () => { - describe('Subdomain', () => { - it('valid subdomains should have the proper subdomain extracted', () => { - let validSubdomains = [ - 'subdomain', - ' subdomain ', - 'https://subdomain.onesignal.com', - 'https://subdomain.onesignal.com/', - 'http://www.subdomain.onesignal.com', - 'https://www.subdomain.onesignal.com/', - 'http://subdomain.onesignal.com', - 'http://subdomain.onesignal.com/', - 'subdomain.onesignal.com', - ]; - let expectedNormalizedSubdomain = 'subdomain'; - for (let validSubdomain of validSubdomains) { - let actualNormalizedSubdomain = OneSignal.helpers.getNormalizedSubdomain(validSubdomain); - expect(actualNormalizedSubdomain).to.equal(expectedNormalizedSubdomain); - } - }); - }); - - describe('Postmam origin checking', () => { - it('isSafeOrigin for HTTP sites', () => { - let origin = 'http://site.com'; - let postmam = new Postmam(window, origin, origin, 'nonce'); - expect(postmam.isSafeOrigin('http://site.com')).to.be.true; - expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; - expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; - }); - it('isSafeOrigin for HTTP www. sites', () => { - let origin = 'http://www.site.com'; - let postmam = new Postmam(window, origin, origin, 'nonce'); - expect(postmam.isSafeOrigin('http://site.com')).to.be.true; - expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; - expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; - }); - - it('isSafeOrigin for HTTPS sites', () => { - let origin = 'https://site.com'; - let postmam = new Postmam(window, origin, origin, 'nonce'); - expect(postmam.isSafeOrigin('http://site.com')).to.be.false; - expect(postmam.isSafeOrigin('http://www.site.com')).to.be.false; - expect(postmam.isSafeOrigin('https://site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; - expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; - }); - - it('isSafeOrigin for * sites', () => { - let origin = '*'; - let postmam = new Postmam(window, origin, origin, 'nonce'); - expect(postmam.isSafeOrigin('http://site.com')).to.be.true; - expect(postmam.isSafeOrigin('http://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com')).to.be.true; - expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.true; - expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.true; - expect(postmam.isSafeOrigin('abc')).to.be.true; - }); - - it('isSafeOrigin for invalid sites', () => { - let origin = '*.google.com'; - let postmam = new Postmam(window, origin, origin, 'nonce'); - expect(postmam.isSafeOrigin('http://site.com')).to.be.false; - expect(postmam.isSafeOrigin('http://www.site.com')).to.be.false; - expect(postmam.isSafeOrigin('https://site.com')).to.be.false; - expect(postmam.isSafeOrigin('https://www.site.com')).to.be.false; - expect(postmam.isSafeOrigin('https://www.site.com:123')).to.be.false; - expect(postmam.isSafeOrigin('https://ww.site.com')).to.be.false; - expect(postmam.isSafeOrigin('abc')).to.be.false; - }); - }); - - describe('Navigator language checking', () => { - it('is navigator language detected correctly', () => { - expect(Environment.getLanguage('en-US')).to.equal('en'); - expect(Environment.getLanguage('english-US')).to.equal('en'); - expect(Environment.getLanguage('zh')).to.equal('zh-Hant'); - expect(Environment.getLanguage('zh-CN')).to.equal('zh-Hans'); - expect(Environment.getLanguage('zh-Hans')).to.equal('zh-Hans'); - expect(Environment.getLanguage('zh-TW')).to.equal('zh-Hant'); - expect(Environment.getLanguage('zh-Hant')).to.equal('zh-Hant'); - expect(Environment.getLanguage('de-Arabic')).to.equal('de'); - }); - }); - }) -}); \ No newline at end of file diff --git a/test/utils.js b/test/utils.js index 6b67b2bb4..e8f543b65 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,6 +1,9 @@ -import StackTrace from 'stacktrace-js'; import StackTraceGPS from 'stacktrace-gps'; +import {APP_ID, PLAYER_ID, SUBDOMAIN} from './vars.js'; +import chai, { expect } from 'chai'; +import StackTrace from 'stacktrace-js'; import IndexedDb from '../src/indexedDb'; +import { executeAndTimeoutPromiseAfter } from '../src/utils'; // URLSearchParams.toString() does a second weird URL encoding so here we have to redo the URL encoding @@ -85,4 +88,88 @@ export default class Utils { unregisterWorkerPromise ]); } + + /** + * Gets the sequence of calls to initialize / subscribe for the HTTP / HTTPS test site. + * @param options Use 'autoRegister' or 'welcomeNotification'. + */ + static initialize(options) { + if (!options) { + options = {}; + } + return Promise.all([ + // Wipe database and force allow notifications permission for current site origin + Extension.setNotificationPermission(`${location.origin}/*`, 'allow'), + // Also allow popup permissions (only for HTTP, but doesn't hurt to enable for HTTPS) + Extension.setPopupPermission(`${location.origin}/*`, 'allow'), + // Only for HTTPS: Wipes the IndexedDB on the current site origin + Utils.wipeIndexedDb(), + Utils.wipeServiceWorkerAndUnsubscribe() + ]) + .then(() => { + // Initialize OneSignal and subscribe + return new Promise(resolve => { + window.OneSignal = OneSignal || []; + OneSignal.push(function () { + OneSignal.LOGGING = true; + let initOptions = { + appId: APP_ID, + autoRegister: options.autoRegister, + persistNotification: false, + dangerouslyWipeData: true && location.protocol === 'http:' // Wipes IndexedDB data on popup / iframe initialize for HTTP + }; + if (!options.welcomeNotification) { + initOptions.welcomeNotification = { + disable: true + } + } + if (location.protocol === 'http:') { + initOptions.subdomainName = SUBDOMAIN; + if (options.autoRegister) { + OneSignal.registerForPushNotifications(); + } + } + OneSignal.push(["init", initOptions]); + + if (options.autoRegister) { + if (location.protocol === 'http:') { + // Wait for the HTTP popup to appear and be interactable + OneSignal.on('popupLoad', resolve); + } else { + // Wait for the HTTPS subscription to finish + OneSignal.on('subscriptionChange', resolve); + } + } else { + // Don't subscribe, just wait for SDK to initialize + OneSignal.on('initialize', resolve); + } + }); + }); + }) + .then(() => { + if (location.protocol === 'http:' && options.autoRegister) { + return Extension.acceptHttpSubscriptionPopup(); + } + }) + .then(() => { + if (location.protocol === 'http:' && options.autoRegister) { + return new Promise(resolve => { + OneSignal.on('subscriptionChange', resolve); + }); + } + }) + } + + static expectEvent(eventName, timeout) { + if (!timeout) { + timeout = 5000; + } + return executeAndTimeoutPromiseAfter(new Promise(resolve => { + OneSignal.once(eventName, resolve); + }).catch(e => console.error(e)), timeout, `Event '${eventName}' did not fire after ${timeout} ms.`); + } + + static wait(milliseconds) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); + } } \ No newline at end of file diff --git a/test/vars.js b/test/vars.js index bba78afd7..f343ff745 100644 --- a/test/vars.js +++ b/test/vars.js @@ -1,8 +1,9 @@ import { DEV_HOST, PROD_HOST, API_URL } from '../src/vars.js'; import Environment from '../src/environment.js' -export var APP_ID = Environment.isDev() ? '2ed062be-0ea9-45db-ad01-f28a0820a7ea' : '7b6053e0-9911-4003-a0a4-a33e417ad663'; +export var APP_ID = Environment.isDev() ? (location.protocol === 'https:' ? '2ed062be-0ea9-45db-ad01-f28a0820a7ea' : '2b49220d-0933-4f86-b99b-cda87a1a7e2e') : '7b6053e0-9911-4003-a0a4-a33e417ad663'; export var PLAYER_ID = Environment.isDev() ? '6c71d09a-d825-421e-9cc4-52138b8e15ba' : '15b23511-e0cf-489a-8682-7cf129cb4585'; +export var SUBDOMAIN = 'washington'; /* Web SDK Test Chrome Extension Variables */