From 4272591c88c9dea9399bb977a9fa271ae74a8d60 Mon Sep 17 00:00:00 2001 From: Jason Pang Date: Thu, 9 Jun 2016 20:17:38 -0700 Subject: [PATCH 1/2] WIP: Tests run in HTTP/HTTPS mode using same code Add test events to main SDK --- src/OneSignal.js | 249 ++++++++++++---------- src/utils.js | 45 ++++ test/entry.js | 8 +- test/extension.js | 25 ++- test/{httpsTests.js => tests.js} | 353 ++++++++++++++----------------- test/unsubscribedTests.js | 116 ---------- test/utils.js | 89 +++++++- test/vars.js | 3 +- 8 files changed, 455 insertions(+), 433 deletions(-) rename test/{httpsTests.js => tests.js} (50%) delete mode 100644 test/unsubscribedTests.js diff --git a/src/OneSignal.js b/src/OneSignal.js index 9d81bb69f..742e7ecf3 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'; @@ -488,127 +489,144 @@ 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}) + .then(() => OneSignal.setDefaultTitle("OneSignal Test HTTP Site"))); + } + + preinitializePromise.then(() => { + OneSignal._thisIsThePopup = options.isPopup; + if (Environment.isPopup() || OneSignal._thisIsThePopup) { + OneSignal.popupPostmam = new Postmam(this.opener, sendToOrigin, receiveFromOrigin, handshakeNonce); + } - OneSignal._thisIsThePopup = options.isPopup; - if (Environment.isPopup() || OneSignal._thisIsThePopup) { - OneSignal.popupPostmam = new Postmam(this.opener, sendToOrigin, receiveFromOrigin, handshakeNonce); - } - - OneSignal._thisIsTheModal = options.isModal; - if (OneSignal._thisIsTheModal) { - OneSignal.modalPostmam = new Postmam(this.parent, sendToOrigin, receiveFromOrigin, handshakeNonce); - } + OneSignal._thisIsTheModal = options.isModal; + if (OneSignal._thisIsTheModal) { + OneSignal.modalPostmam = new Postmam(this.parent, sendToOrigin, receiveFromOrigin, handshakeNonce); + } - OneSignal.iframePostmam = new Postmam(this.window, sendToOrigin, receiveFromOrigin, handshakeNonce); - OneSignal.iframePostmam.listen(); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.CONNECTED, e => { - log.debug(`(${Environment.getEnv()}) Fired Postmam connect event!`); - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_NOTIFICATION_PERMISSION, message => { - OneSignal.getNotificationPermission() - .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; - if (!table || !key) { - log.error('Missing table or key for remote database get.', 'table:', table, 'key:', key); + OneSignal.iframePostmam = new Postmam(this.window, sendToOrigin, receiveFromOrigin, handshakeNonce); + OneSignal.iframePostmam.listen(); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.CONNECTED, e => { + log.debug(`(${Environment.getEnv()}) Fired Postmam connect event!`); + }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_NOTIFICATION_PERMISSION, message => { + OneSignal.getNotificationPermission() + .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; + if (!table || !key) { + log.error('Missing table or key for remote database get.', 'table:', table, 'key:', key); + } + retrievalOpPromises.push(Database.get(table, key)); } - retrievalOpPromises.push(Database.get(table, key)); - } - Promise.all(retrievalOpPromises) - .then(results => message.reply(results)); - return false; - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_PUT, message => { - // insertions is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] - // It's formatted that way because our IndexedDB database is formatted that way - let insertions = message.data; - let insertionOpPromises = []; - for (let insertion of insertions) { - let { table, keypath } = insertion; - insertionOpPromises.push(Database.put(table, keypath)); - } - Promise.all(insertionOpPromises) - .then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE)); - return false; - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_REMOVE, message => { - // removals is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] - // It's formatted that way because our IndexedDB database is formatted that way - let removals = message.data; - let removalOpPromises = []; - for (let removal of removals) { - let { table, keypath } = removal; - removalOpPromises.push(Database.remove(table, keypath)); - } - Promise.all(removalOpPromises) - .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 + Promise.all(retrievalOpPromises) + .then(results => message.reply(results)); + return false; }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_PUT, message => { + // insertions is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] + // It's formatted that way because our IndexedDB database is formatted that way + let insertions = message.data; + let insertionOpPromises = []; + for (let insertion of insertions) { + let {table, keypath} = insertion; + insertionOpPromises.push(Database.put(table, keypath)); + } + Promise.all(insertionOpPromises) + .then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE)); + return false; + }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_REMOVE, message => { + // removals is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] + // It's formatted that way because our IndexedDB database is formatted that way + let removals = message.data; + let removalOpPromises = []; + for (let removal of removals) { + let {table, keypath} = removal; + removalOpPromises.push(Database.remove(table, keypath)); + } + Promise.all(removalOpPromises) + .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(); + 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}); + let opPromises = []; + if (options.continuePressed) { + opPromises.push(OneSignal.setSubscription(true)); } - })); - - 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); + // 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); + } + })); - 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); - }); - } + 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); + }); + }); + }) + .then(() => { + Event.trigger('httpInitialize'); }); } @@ -761,7 +779,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 +801,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 +881,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); @@ -1895,6 +1923,7 @@ objectAssign(OneSignal, { log: log, swivel: swivel, api: OneSignalApi, + indexedDb: IndexedDb, _sessionNonce: null, iframePostmam: null, popupPostmam: null, @@ -1914,8 +1943,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/utils.js b/src/utils.js index 9253c5d69..d29ec273d 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,47 @@ 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 (!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..3bdccb2c2 100644 --- a/test/httpsTests.js +++ b/test/tests.js @@ -5,8 +5,10 @@ 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'; chai.config.includeStack = false; @@ -18,31 +20,15 @@ describe('HTTPS Tests', 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 +36,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()); }); @@ -132,31 +96,11 @@ describe('HTTPS Tests', function() { tagsToCheckDeepEqual = Object.keys(sentTags).filter(x => expectedTagsUnsent.concat(['string', 'false']).indexOf(x) < 0); }); - it('should send, receive, and delete tags successfully', function () { + it.only('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 +125,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 +172,7 @@ describe('HTTPS Tests', function() { } OneSignal.sendTags(sentTags, onSendTagsComplete); - }) + }); }); }); @@ -256,29 +180,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 +196,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 +212,26 @@ 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()); + OneSignal.database.printIds(); + return OneSignal.getTags(); }) .then(tags => { expect(tags).to.be.null; @@ -374,7 +248,7 @@ describe('HTTPS Tests', function() { .then(tags => { expect(tags).to.not.be.null; expect(tags).to.have.property('key', tagValue); - }) + }); }); }); }); @@ -382,43 +256,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 - } - }]); + let tagValue = guid(); - 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(); + 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 +293,108 @@ 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('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/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 */ From 35808ed9980be4965ff1b424b6ad17411ca77738 Mon Sep 17 00:00:00 2001 From: Jason Pang Date: Fri, 10 Jun 2016 16:09:57 -0700 Subject: [PATCH 2/2] Added extra tests and all tests now pass on HTTP/HTTPS --- src/OneSignal.js | 251 +++++++++++++++++++++++++---------------------- src/events.js | 5 +- src/utils.js | 3 + test/tests.js | 202 +++++++++++++++++++++----------------- 4 files changed, 247 insertions(+), 214 deletions(-) diff --git a/src/OneSignal.js b/src/OneSignal.js index 742e7ecf3..9a5f0d9e1 100644 --- a/src/OneSignal.js +++ b/src/OneSignal.js @@ -98,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(); } } @@ -497,137 +503,136 @@ export default class OneSignal { // 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}) - .then(() => OneSignal.setDefaultTitle("OneSignal Test HTTP Site"))); + .then(() => wipeServiceWorkerAndUnsubscribe()) + .then(() => IndexedDb.put('Ids', {type: 'appId', id: options.appId})); } - preinitializePromise.then(() => { - OneSignal._thisIsThePopup = options.isPopup; - if (Environment.isPopup() || OneSignal._thisIsThePopup) { - OneSignal.popupPostmam = new Postmam(this.opener, sendToOrigin, receiveFromOrigin, handshakeNonce); - } + OneSignal._thisIsThePopup = options.isPopup; + if (Environment.isPopup() || OneSignal._thisIsThePopup) { + OneSignal.popupPostmam = new Postmam(this.opener, sendToOrigin, receiveFromOrigin, handshakeNonce); + } - OneSignal._thisIsTheModal = options.isModal; - if (OneSignal._thisIsTheModal) { - OneSignal.modalPostmam = new Postmam(this.parent, sendToOrigin, receiveFromOrigin, handshakeNonce); - } + OneSignal._thisIsTheModal = options.isModal; + if (OneSignal._thisIsTheModal) { + OneSignal.modalPostmam = new Postmam(this.parent, sendToOrigin, receiveFromOrigin, handshakeNonce); + } - OneSignal.iframePostmam = new Postmam(this.window, sendToOrigin, receiveFromOrigin, handshakeNonce); - OneSignal.iframePostmam.listen(); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.CONNECTED, e => { - log.debug(`(${Environment.getEnv()}) Fired Postmam connect event!`); - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_NOTIFICATION_PERMISSION, message => { - OneSignal.getNotificationPermission() - .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; - 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)); - return false; - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_PUT, message => { - // insertions is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] - // It's formatted that way because our IndexedDB database is formatted that way - let insertions = message.data; - let insertionOpPromises = []; - for (let insertion of insertions) { - let {table, keypath} = insertion; - insertionOpPromises.push(Database.put(table, keypath)); - } - Promise.all(insertionOpPromises) - .then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE)); - return false; - }); - OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_REMOVE, message => { - // removals is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] - // It's formatted that way because our IndexedDB database is formatted that way - let removals = message.data; - let removalOpPromises = []; - for (let removal of removals) { - let {table, keypath} = removal; - removalOpPromises.push(Database.remove(table, keypath)); + OneSignal.iframePostmam = new Postmam(this.window, sendToOrigin, receiveFromOrigin, handshakeNonce); + OneSignal.iframePostmam.listen(); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.CONNECTED, e => { + log.debug(`(${Environment.getEnv()}) Fired Postmam connect event!`); + }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_NOTIFICATION_PERMISSION, message => { + OneSignal.getNotificationPermission() + .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; + if (!table || !key) { + log.error('Missing table or key for remote database get.', 'table:', table, 'key:', key); } - Promise.all(removalOpPromises) - .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 - }); + retrievalOpPromises.push(Database.get(table, key)); + } + Promise.all(retrievalOpPromises) + .then(results => message.reply(results)); + return false; + }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_PUT, message => { + // insertions is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] + // It's formatted that way because our IndexedDB database is formatted that way + let insertions = message.data; + let insertionOpPromises = []; + for (let insertion of insertions) { + let {table, keypath} = insertion; + insertionOpPromises.push(Database.put(table, keypath)); + } + Promise.all(insertionOpPromises) + .then(results => message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE)); + return false; + }); + OneSignal.iframePostmam.on(OneSignal.POSTMAM_COMMANDS.REMOTE_DATABASE_REMOVE, message => { + // removals is an array of key-value pairs e.g. [table: {'Options': keypath: {key: persistNotification, value: '...'}}, {table: 'Ids', keypath: {type: 'userId', id: '...'}] + // It's formatted that way because our IndexedDB database is formatted that way + let removals = message.data; + let removalOpPromises = []; + for (let removal of removals) { + let {table, keypath} = removal; + removalOpPromises.push(Database.remove(table, keypath)); + } + Promise.all(removalOpPromises) + .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._installNativePromptPermissionChangedHook(); + preinitializePromise.then(() => { + OneSignal.config = objectAssign(message.data.hostInitOptions, options, { + pageUrl: message.data.pageUrl, + pageTitle: message.data.pageTitle + }); - 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}); - } - })); + OneSignal._installNativePromptPermissionChangedHook(); - 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); + 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); + } + })); + + + 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); + }); + } - 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); - }); - }); - }) - .then(() => { - Event.trigger('httpInitialize'); + message.reply(OneSignal.POSTMAM_COMMANDS.REMOTE_OPERATION_COMPLETE); + }); + }) + .catch(e => console.error(e)); }); + Event.trigger('httpInitialize'); } static _initPopup() { @@ -681,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`; } @@ -1261,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) { @@ -1270,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) { 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 d29ec273d..58c7f3fab 100644 --- a/src/utils.js +++ b/src/utils.js @@ -386,6 +386,9 @@ export function wipeIndexedDb() { */ 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(); diff --git a/test/tests.js b/test/tests.js index 3bdccb2c2..f8b5ce74a 100644 --- a/test/tests.js +++ b/test/tests.js @@ -9,6 +9,7 @@ import { executeAndTimeoutPromiseAfter, guid, isPushNotificationsSupported, isPu 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; @@ -17,7 +18,7 @@ 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 Utils.initialize({ @@ -96,7 +97,7 @@ describe('HTTPS Tests', function() { tagsToCheckDeepEqual = Object.keys(sentTags).filter(x => expectedTagsUnsent.concat(['string', 'false']).indexOf(x) < 0); }); - it.only('should send, receive, and delete tags successfully', function () { + it('should send, receive, and delete tags successfully', function () { return new SoloTest(this.test, {}, () => { return Utils.initialize({ welcomeNotification: false, @@ -213,10 +214,10 @@ describe('HTTPS Tests', function() { let deleteTagReturnValue = OneSignal.deleteTag(''); let deleteTagsReturnValue = OneSignal.deleteTags(['']); [getTagsReturnValue, - sendTagReturnValue, - sendTagsReturnValue, - deleteTagReturnValue, - deleteTagsReturnValue].forEach(x => expect(x.constructor.name).to.equal('Promise')); + sendTagReturnValue, + sendTagsReturnValue, + deleteTagReturnValue, + deleteTagsReturnValue].forEach(x => expect(x.constructor.name).to.equal('Promise')); }); }); }); @@ -230,7 +231,6 @@ describe('HTTPS Tests', function() { autoRegister: false }) .then(() => { - OneSignal.database.printIds(); return OneSignal.getTags(); }) .then(tags => { @@ -239,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(() => { @@ -304,97 +308,111 @@ describe('HTTPS Tests', function() { }); }) - 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('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; - }); + 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 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 * 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; - }); + 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)); - 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