diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index 62aa9be1a..92ac23a6b 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -65,6 +65,7 @@ export const mockBEMUserCache = () => { return new Promise((rs, rj) => setTimeout(() => { for (let p in _cache) delete _cache[p]; + for (let doc in _storage) delete _storage[doc]; rs(); }, 100), ); @@ -129,7 +130,59 @@ export const mockBEMDataCollection = () => { _storage['config/consent'] = consentDoc; }, 100); }, + getConfig: () => { + return new Promise((rs, rj) => { + setTimeout(() => { + rs({ ios_use_remote_push_for_sync: true }); + }, 100); + }); + }, + handleSilentPush: () => { + return new Promise((rs, rj) => + setTimeout(() => { + rs(); + }, 100), + ); + }, }; window['cordova'] ||= {}; window['cordova'].plugins.BEMDataCollection = mockBEMDataCollection; }; + +export const mockBEMServerCom = () => { + const mockBEMServerCom = { + postUserPersonalData: (actionString, typeString, updateDoc, rs, rj) => { + setTimeout(() => { + console.log('set in mock', updateDoc); + _storage['user_data'] = updateDoc; + rs(); + }, 100); + }, + + getUserPersonalData: (actionString, rs, rj) => { + setTimeout(() => { + rs(_storage['user_data']); + }, 100); + }, + }; + window['cordova'].plugins.BEMServerComm = mockBEMServerCom; +}; + +let _url_stash = ''; + +export const mockInAppBrowser = () => { + const mockInAppBrowser = { + open: (url: string, mode: string, options: {}) => { + _url_stash = url; + }, + }; + window['cordova'].InAppBrowser = mockInAppBrowser; +}; + +export const getURL = () => { + return _url_stash; +}; + +export const clearURL = () => { + _url_stash = ''; +}; diff --git a/www/__mocks__/globalMocks.ts b/www/__mocks__/globalMocks.ts index f13cb274b..62ea1b935 100644 --- a/www/__mocks__/globalMocks.ts +++ b/www/__mocks__/globalMocks.ts @@ -1,3 +1,19 @@ export const mockLogger = () => { window['Logger'] = { log: console.log }; }; + +let alerts = []; + +export const mockAlert = () => { + window['alert'] = (message) => { + alerts.push(message); + }; +}; + +export const clearAlerts = () => { + alerts = []; +}; + +export const getAlerts = () => { + return alerts; +}; diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts new file mode 100644 index 000000000..53a81e16c --- /dev/null +++ b/www/__mocks__/pushNotificationMocks.ts @@ -0,0 +1,33 @@ +let notifSettings; +let onList: any = {}; +let called = null; + +export const mockPushNotification = () => { + window['PushNotification'] = { + init: (settings: Object) => { + notifSettings = settings; + return { + on: (event: string, callback: Function) => { + onList[event] = callback; + }, + finish: (content: any, errorFcn: Function, notID: any) => { + called = notID; + }, + }; + }, + }; +}; + +export const clearNotifMock = function () { + notifSettings = {}; + onList = {}; + called = null; +}; + +export const getOnList = function () { + return onList; +}; + +export const getCalled = function () { + return called; +}; diff --git a/www/__tests__/customEventHandler.test.ts b/www/__tests__/customEventHandler.test.ts new file mode 100644 index 000000000..7a5c09539 --- /dev/null +++ b/www/__tests__/customEventHandler.test.ts @@ -0,0 +1,24 @@ +import { publish, subscribe, unsubscribe } from '../js/customEventHandler'; +import { mockLogger } from '../__mocks__/globalMocks'; + +mockLogger(); + +it('subscribes and publishes to an event', () => { + const listener = jest.fn(); + subscribe('test', listener); + publish('test', 'test data'); + expect(listener).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'test', + detail: 'test data', + }), + ); +}); + +it('can unsubscribe', () => { + const listener = jest.fn(); + subscribe('test', listener); + unsubscribe('test', listener); + publish('test', 'test data'); + expect(listener).not.toHaveBeenCalled(); +}); diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts new file mode 100644 index 000000000..d452aa819 --- /dev/null +++ b/www/__tests__/pushNotifySettings.test.ts @@ -0,0 +1,119 @@ +import { DateTime } from 'luxon'; +import { EVENTS, publish } from '../js/customEventHandler'; +import { INTRO_DONE_KEY, readIntroDone } from '../js/onboarding/onboardingHelper'; +import { storageSet } from '../js/plugin/storage'; +import { initPushNotify } from '../js/splash/pushNotifySettings'; +import { mockCordova, mockBEMUserCache, mockBEMDataCollection } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import { + clearNotifMock, + getOnList, + mockPushNotification, + getCalled, +} from '../__mocks__/pushNotificationMocks'; + +mockCordova(); +mockLogger(); +mockPushNotification(); +mockBEMUserCache(); +mockBEMDataCollection(); + +global.fetch = (url: string) => + new Promise((rs, rj) => { + setTimeout(() => + rs({ + json: () => + new Promise((rs, rj) => { + let myJSON = { + emSensorDataCollectionProtocol: { + protocol_id: '2014-04-6267', + approval_date: '2016-07-14', + }, + }; + setTimeout(() => rs(myJSON), 100); + }), + }), + ); + }) as any; + +afterEach(() => { + clearNotifMock(); +}); + +it('intro done does nothing if not registered', () => { + expect(getOnList()).toStrictEqual({}); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); + expect(getOnList()).toStrictEqual({}); +}); + +it('intro done initializes the push notifications', () => { + expect(getOnList()).toStrictEqual({}); + + initPushNotify(); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); + expect(getOnList()).toStrictEqual( + expect.objectContaining({ + notification: expect.any(Function), + error: expect.any(Function), + registration: expect.any(Function), + }), + ); +}); + +it('cloud event does nothing if not registered', () => { + expect(window['cordova'].platformId).toEqual('ios'); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { + additionalData: { 'content-available': 1, payload: { notId: 3 } }, + }); + expect(getCalled()).toBeNull(); +}); + +it('cloud event handles notification if registered', async () => { + expect(window['cordova'].platformId).toEqual('ios'); + initPushNotify(); + publish(EVENTS.INTRO_DONE_EVENT, 'intro done'); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { + additionalData: { 'content-available': 1, payload: { notId: 3 } }, + }); + await new Promise((r) => setTimeout(r, 1000)); + expect(getCalled()).toEqual(3); +}); + +it('consent event does nothing if not registered', () => { + expect(getOnList()).toStrictEqual({}); + publish(EVENTS.CONSENTED_EVENT, 'test data'); + expect(getOnList()).toStrictEqual({}); +}); + +it('consent event registers if intro done', async () => { + //make sure the mock is clear + expect(getOnList()).toStrictEqual({}); + + //initialize the pushNotify, to subscribe to events + initPushNotify(); + + //mark the intro as done + const currDateTime = DateTime.now().toISO(); + let marked = await storageSet(INTRO_DONE_KEY, currDateTime); + let introDone = await readIntroDone(); + expect(introDone).toBeTruthy(); + + //publish consent event and check results + publish(EVENTS.CONSENTED_EVENT, 'test data'); + //have to wait a beat since event response is async + await new Promise((r) => setTimeout(r, 1000)); + expect(getOnList()).toStrictEqual( + expect.objectContaining({ + notification: expect.any(Function), + error: expect.any(Function), + registration: expect.any(Function), + }), + ); +}); + +it('consent event does not register if intro not done', () => { + expect(getOnList()).toStrictEqual({}); + initPushNotify(); + publish(EVENTS.CONSENTED_EVENT, 'test data'); + expect(getOnList()).toStrictEqual({}); //nothing, intro not done +}); diff --git a/www/__tests__/remoteNotifyHandler.test.ts b/www/__tests__/remoteNotifyHandler.test.ts new file mode 100644 index 000000000..320877c6b --- /dev/null +++ b/www/__tests__/remoteNotifyHandler.test.ts @@ -0,0 +1,76 @@ +import { EVENTS, publish } from '../js/customEventHandler'; +import { initRemoteNotifyHandler } from '../js/splash/remoteNotifyHandler'; +import { + clearURL, + getURL, + mockBEMUserCache, + mockDevice, + mockGetAppVersion, + mockInAppBrowser, +} from '../__mocks__/cordovaMocks'; +import { clearAlerts, getAlerts, mockAlert, mockLogger } from '../__mocks__/globalMocks'; + +mockLogger(); +mockDevice(); +mockBEMUserCache(); +mockGetAppVersion(); +mockInAppBrowser(); +mockAlert(); + +const db = window['cordova']?.plugins?.BEMUserCache; + +beforeEach(() => { + clearURL(); + clearAlerts(); +}); + +it('does not adds a statEvent if not subscribed', async () => { + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, 'test data'); + const storedMessages = await db.getAllMessages('stats/client_nav_event', false); + expect(storedMessages).toEqual([]); +}); + +it('adds a statEvent if subscribed', async () => { + initRemoteNotifyHandler(); + await new Promise((r) => setTimeout(r, 500)); //wait for subscription + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //wait for event handling + const storedMessages = await db.getAllMessages('stats/client_nav_event', false); + expect(storedMessages).toContainEqual({ + name: 'notification_open', + ts: expect.any(Number), + reading: null, + client_app_version: '1.2.3', + client_os_version: '14.0.0', + }); +}); + +it('handles the url if subscribed', () => { + initRemoteNotifyHandler(); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { + additionalData: { + payload: { alert_type: 'website', spec: { url: 'https://this_is_a_test.com' } }, + }, + }); + expect(getURL()).toBe('https://this_is_a_test.com'); +}); + +it('handles the popup if subscribed', () => { + initRemoteNotifyHandler(); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { + additionalData: { + payload: { + alert_type: 'popup', + spec: { title: 'Hello', text: 'World' }, + }, + }, + }); + expect(getAlerts()).toEqual(expect.arrayContaining(['Hello World'])); +}); + +it('does nothing if subscribed and no data', () => { + initRemoteNotifyHandler(); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, {}); + expect(getURL()).toEqual(''); + expect(getAlerts()).toEqual([]); +}); diff --git a/www/__tests__/storeDeviceSettings.test.ts b/www/__tests__/storeDeviceSettings.test.ts new file mode 100644 index 000000000..3cd6f8319 --- /dev/null +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -0,0 +1,120 @@ +import { readConsentState, markConsented } from '../js/splash/startprefs'; +import { storageClear } from '../js/plugin/storage'; +import { getUser } from '../js/commHelper'; +import { initStoreDeviceSettings, teardownDeviceSettings } from '../js/splash/storeDeviceSettings'; +import { + mockBEMDataCollection, + mockBEMServerCom, + mockBEMUserCache, + mockCordova, + mockDevice, + mockGetAppVersion, +} from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import { EVENTS, publish } from '../js/customEventHandler'; +import { markIntroDone } from '../js/onboarding/onboardingHelper'; + +mockBEMUserCache(); +mockDevice(); +mockCordova(); +mockLogger(); +mockGetAppVersion(); +mockBEMServerCom(); +mockBEMDataCollection(); + +global.fetch = (url: string) => + new Promise((rs, rj) => { + setTimeout(() => + rs({ + json: () => + new Promise((rs, rj) => { + let myJSON = { + emSensorDataCollectionProtocol: { + protocol_id: '2014-04-6267', + approval_date: '2016-07-14', + }, + }; + setTimeout(() => rs(myJSON), 100); + }), + }), + ); + }) as any; + +beforeEach(async () => { + teardownDeviceSettings(); + await storageClear({ local: true, native: true }); + let user = await getUser(); + expect(user).toBeUndefined(); +}); + +it('stores device settings when intialized after consent', async () => { + await readConsentState(); + let marked = await markConsented(); + await new Promise((r) => setTimeout(r, 500)); + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); + let user = await getUser(); + expect(user).toMatchObject({ + client_os_version: '14.0.0', + client_app_version: '1.2.3', + }); +}); + +it('does not stores device settings when intialized before consent', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); + let user = await getUser(); + expect(user).toBeUndefined(); +}); + +it('verifies my subscrition clearing', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); + teardownDeviceSettings(); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); + let user = await getUser(); + expect(user).toBeUndefined(); +}); + +it('does not store if not subscribed', async () => { + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); + publish(EVENTS.CONSENTED_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //time to carry out event handling + let user = await getUser(); + expect(user).toBeUndefined(); +}); + +it('stores device settings after intro done', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); //time to check consent and subscribe + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //time to carry out event handling + let user = await getUser(); + expect(user).toMatchObject({ + client_os_version: '14.0.0', + client_app_version: '1.2.3', + }); +}); + +it('stores device settings after consent if intro done', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); //time to check consent and subscribe + markIntroDone(); + await new Promise((r) => setTimeout(r, 500)); + publish(EVENTS.CONSENTED_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //time to carry out event handling + let user = await getUser(); + expect(user).toMatchObject({ + client_os_version: '14.0.0', + client_app_version: '1.2.3', + }); +}); + +it('does not store device settings after consent if intro not done', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); //time to check consent and subscribe + publish(EVENTS.CONSENTED_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //time to carry out event handling + let user = await getUser(); + expect(user).toBeUndefined(); +}); diff --git a/www/index.js b/www/index.js index e5a69ca58..83d73adbb 100644 --- a/www/index.js +++ b/www/index.js @@ -5,10 +5,7 @@ import 'leaflet/dist/leaflet.css'; import './js/ngApp.js'; import './js/splash/referral.js'; -import './js/splash/pushnotify.js'; -import './js/splash/storedevicesettings.js'; import './js/splash/localnotify.js'; -import './js/splash/remotenotify.js'; import './js/splash/notifScheduler.js'; import './js/controllers.js'; import './js/services.js'; diff --git a/www/js/App.tsx b/www/js/App.tsx index 54b677add..a955b032d 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -15,6 +15,9 @@ import { import { setServerConnSettings } from './config/serverConn'; import AppStatusModal from './control/AppStatusModal'; import usePermissionStatus from './usePermissionStatus'; +import { initPushNotify } from './splash/pushNotifySettings'; +import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; +import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; import { withErrorBoundary } from './plugin/ErrorBoundary'; const defaultRoutes = (t) => [ @@ -71,6 +74,9 @@ const App = () => { setServerConnSettings(appConfig).then(() => { refreshOnboardingState(); }); + initPushNotify(); + initStoreDeviceSettings(); + initRemoteNotifyHandler(); }, [appConfig]); const appContextValue = { diff --git a/www/js/controllers.js b/www/js/controllers.js index abf5916c5..16f962c8d 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -5,89 +5,72 @@ import { addStatError, addStatReading, statKeys } from './plugin/clientStats'; import { getPendingOnboardingState } from './onboarding/onboardingHelper'; angular - .module('emission.controllers', [ - 'emission.splash.pushnotify', - 'emission.splash.storedevicesettings', - 'emission.splash.localnotify', - 'emission.splash.remotenotify', - ]) + .module('emission.controllers', ['emission.splash.localnotify']) .controller('RootCtrl', function ($scope) {}) .controller('DashCtrl', function ($scope) {}) - .controller( - 'SplashCtrl', - function ( - $scope, - $state, - $interval, - $rootScope, - PushNotify, - StoreDeviceSettings, - LocalNotify, - RemoteNotify, - ) { - console.log('SplashCtrl invoked'); - // alert("attach debugger!"); - // PushNotify.startupInit(); + // alert("attach debugger!"); + // PushNotify.startupInit(); + .controller('SplashCtrl', function ($scope, $state, $interval, $rootScope, LocalNotify) { + console.log('SplashCtrl invoked'); - $rootScope.$on( - '$stateChangeSuccess', - function (event, toState, toParams, fromState, fromParams) { - console.log( - 'Finished changing state from ' + - JSON.stringify(fromState) + - ' to ' + - JSON.stringify(toState), - ); - addStatReading(statKeys.STATE_CHANGED, fromState.name + '-2-' + toState.name); - }, - ); - $rootScope.$on( - '$stateChangeError', - function (event, toState, toParams, fromState, fromParams, error) { - console.log( - 'Error ' + - error + - ' while changing state from ' + - JSON.stringify(fromState) + - ' to ' + - JSON.stringify(toState), - ); - addStatError(statKeys.STATE_CHANGED, fromState.name + '-2-' + toState.name + '_' + error); - }, - ); - $rootScope.$on('$stateNotFound', function (event, unfoundState, fromState, fromParams) { - console.log('unfoundState.to = ' + unfoundState.to); // "lazy.state" - console.log('unfoundState.toParams = ' + unfoundState.toParams); // {a:1, b:2} - console.log('unfoundState.options = ' + unfoundState.options); // {inherit:false} + default options - addStatError(statKeys.STATE_CHANGED, fromState.name + '-2-' + unfoundState.name); - }); + $rootScope.$on( + '$stateChangeSuccess', + function (event, toState, toParams, fromState, fromParams) { + console.log( + 'Finished changing state from ' + + JSON.stringify(fromState) + + ' to ' + + JSON.stringify(toState), + ); + addStatReading(statKeys.STATE_CHANGED, fromState.name + '-2-' + toState.name); + }, + ); + $rootScope.$on( + '$stateChangeError', + function (event, toState, toParams, fromState, fromParams, error) { + console.log( + 'Error ' + + error + + ' while changing state from ' + + JSON.stringify(fromState) + + ' to ' + + JSON.stringify(toState), + ); + addStatError(statKeys.STATE_CHANGED, fromState.name + '-2-' + toState.name + '_' + error); + }, + ); + $rootScope.$on('$stateNotFound', function (event, unfoundState, fromState, fromParams) { + console.log('unfoundState.to = ' + unfoundState.to); // "lazy.state" + console.log('unfoundState.toParams = ' + unfoundState.toParams); // {a:1, b:2} + console.log('unfoundState.options = ' + unfoundState.options); // {inherit:false} + default options + addStatError(statKeys.STATE_CHANGED, fromState.name + '-2-' + unfoundState.name); + }); - var isInList = function (element, list) { - return list.indexOf(element) != -1; - }; + var isInList = function (element, list) { + return list.indexOf(element) != -1; + }; - $rootScope.$on( - '$stateChangeStart', - function (event, toState, toParams, fromState, fromParams, options) { - var personalTabs = ['root.main.common.map', 'root.main.control', 'root.main.metrics']; - if (isInList(toState.name, personalTabs)) { - // toState is in the personalTabs list - getPendingOnboardingState().then(function (result) { - if (result != null) { - event.preventDefault(); - $state.go(result); - } - // else, will do default behavior, which is to go to the tab - }); - } - }, - ); - console.log('SplashCtrl invoke finished'); - }, - ) + $rootScope.$on( + '$stateChangeStart', + function (event, toState, toParams, fromState, fromParams, options) { + var personalTabs = ['root.main.common.map', 'root.main.control', 'root.main.metrics']; + if (isInList(toState.name, personalTabs)) { + // toState is in the personalTabs list + getPendingOnboardingState().then(function (result) { + if (result != null) { + event.preventDefault(); + $state.go(result); + } + // else, will do default behavior, which is to go to the tab + }); + } + }, + ); + console.log('SplashCtrl invoke finished'); + }) .controller('ChatsCtrl', function ($scope, Chats) { // With the new view caching in Ionic, Controllers are only called diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts new file mode 100644 index 000000000..a30a41349 --- /dev/null +++ b/www/js/customEventHandler.ts @@ -0,0 +1,55 @@ +/** + * since react doesn't quite support custom events, writing our own handler + * having the ability to broadcast and emit events prevents files from being tightly coupled + * if we want something else to happen when an event is emitted, we can just listen for it + * instead of having to change the code at the point the event is emitted + * + * looser coupling = point of broadcast doesn't 'know' what is triggered by that event + * leads to more extensible code + * consistent event names help us know what happens when + * + * code based on: https://blog.logrocket.com/using-custom-events-react/ + */ + +import { logDebug } from './plugin/logger'; + +/** + * central source for event names + */ +export const EVENTS = { + CLOUD_NOTIFICATION_EVENT: 'cloud:push:notification', + CONSENTED_EVENT: 'data_collection_consented', + INTRO_DONE_EVENT: 'intro_done', +}; + +/** + * @function starts listening to an event + * @param eventName {string} the name of the event + * @param listener event listener, function to execute on event + */ +export function subscribe(eventName: string, listener) { + logDebug('adding ' + eventName + ' listener'); + document.addEventListener(eventName, listener); +} + +/** + * @function stops listening to an event + * @param eventName {string} the name of the event + * @param listener event listener, function to execute on event + */ +export function unsubscribe(eventName: string, listener) { + logDebug('removing ' + eventName + ' listener'); + document.removeEventListener(eventName, listener); +} + +/** + * @function broadcasts an event + * the data is stored in the "detail" of the event + * @param eventName {string} the name of the event + * @param data any additional data to be added to event + */ +export function publish(eventName: string, data) { + logDebug('publishing ' + eventName + ' with data ' + JSON.stringify(data)); + const event = new CustomEvent(eventName, { detail: data }); + document.dispatchEvent(event); +} diff --git a/www/js/onboarding/ProtocolPage.tsx b/www/js/onboarding/ProtocolPage.tsx index 1f096ecc9..237488ebb 100644 --- a/www/js/onboarding/ProtocolPage.tsx +++ b/www/js/onboarding/ProtocolPage.tsx @@ -6,7 +6,6 @@ import { resetDataAndRefresh } from '../config/dynamicConfig'; import { AppContext } from '../App'; import PrivacyPolicy from './PrivacyPolicy'; import { onboardingStyles } from './OnboardingStack'; -import { markConsented } from '../splash/startprefs'; import { setProtocolDone } from './onboardingHelper'; const ProtocolPage = () => { diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index b776d65bd..d9292547e 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -2,8 +2,8 @@ import { DateTime } from 'luxon'; import { getConfig, resetDataAndRefresh } from '../config/dynamicConfig'; import { storageGet, storageSet } from '../plugin/storage'; import { logDebug } from '../plugin/logger'; +import { EVENTS, publish } from '../customEventHandler'; import { readConsentState, isConsented } from '../splash/startprefs'; -import { getAngularService } from '../angular-react-helper'; export const INTRO_DONE_KEY = 'intro_done'; @@ -90,10 +90,7 @@ export async function markIntroDone() { const currDateTime = DateTime.now().toISO(); return storageSet(INTRO_DONE_KEY, currDateTime).then(() => { //handle "on intro" events - logDebug('intro done, calling registerPush and storeDeviceSettings'); - const PushNotify = getAngularService('PushNotify'); - const StoreSeviceSettings = getAngularService('StoreDeviceSettings'); - PushNotify.registerPush(); - StoreSeviceSettings.storeDeviceSettings(); + logDebug('intro done, publishing event'); + publish(EVENTS.INTRO_DONE_EVENT, currDateTime); }); } diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts new file mode 100644 index 000000000..f3eb2c029 --- /dev/null +++ b/www/js/splash/pushNotifySettings.ts @@ -0,0 +1,239 @@ +/* + * This module deals with the interaction with the push plugin, the redirection + * of silent push notifications and the re-parsing of iOS pushes. It then + * re-emits a CLOUD_NOTIFICATION_EVENT that other modules can listen to. + * + * Other modules, such as the survey code, and the remotenotify module, listen + * to these CLOUD_NOTIFICATION_EVENTs and handle them through launching + * surveys, displaying popups, etc. + * + * This allows us to decouple the push handling logic from push notification + * interface. Note that the local notification is not currently decoupled since + * it only supports redirection to a specific app page. If the local + * notification handling gets more complex, we should consider decoupling it as well. + */ + +import { updateUser } from '../commHelper'; +import { logDebug, displayError } from '../plugin/logger'; +import { publish, subscribe, EVENTS } from '../customEventHandler'; +import { isConsented, readConsentState } from './startprefs'; +import { readIntroDone } from '../onboarding/onboardingHelper'; + +let push = null; + +/** + * @function initializes the PushNotification in window, + * assigns on 'notification' functionality + */ +const startupInit = function () { + push = window['PushNotification'].init({ + ios: { + badge: true, + sound: true, + vibration: true, + clearBadge: true, + }, + android: { + iconColor: '#008acf', + icon: 'ic_mood_question', + clearNotifications: true, + }, + }); + push.on('notification', function (data) { + if (window['cordova'].platformId == 'ios') { + // Parse the iOS values that are returned as strings + if (data && data.additionalData) { + if (data.additionalData.payload) { + data.additionalData.payload = JSON.parse(data.additionalData.payload); + } + if (data.additionalData.data && typeof data.additionalData.data == 'string') { + data.additionalData.data = JSON.parse(data.additionalData.data); + } else { + console.log('additionalData is already an object, no need to parse it'); + } + } else { + logDebug('No additional data defined, nothing to parse'); + } + } + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, data); + }); +}; + +/** + * @function registers notifications and handles result + * @returns Promise for initialization logic, + * resolves on registration with token + * rejects on error with error + */ +const registerPromise = function () { + return new Promise(function (resolve, reject) { + startupInit(); + push.on('registration', function (data) { + console.log('Got registration ' + data); + resolve({ + token: data.registrationId, + type: data.registrationType, + }); + }); + push.on('error', function (error) { + console.log('Got push error ' + error); + reject(error); + }); + console.log('push notify = ' + push); + }); +}; + +/** + * @function registers for notifications and updates user + * currently called on reconsent and on intro done + */ +const registerPush = function () { + registerPromise() + .then(function (t) { + logDebug('Token = ' + JSON.stringify(t)); + return window['cordova'].plugins.BEMServerSync.getConfig() + .then( + function (config) { + return config.sync_interval; + }, + function (error) { + console.log('Got error ' + error + ' while reading config, returning default = 3600'); + return 3600; + }, + ) + .then(function (sync_interval) { + updateUser({ + device_token: t['token'], + curr_platform: window['cordova'].platformId, + curr_sync_interval: sync_interval, + }); + return t; + }); + }) + .then(function (t) { + logDebug('Finished saving token = ' + JSON.stringify(t.token)); + }) + .catch(function (error) { + displayError(error, 'Error in registering push notifications'); + }); +}; + +/** + * @function handles silent push notifications + * works with BEMDataCollection plugin + * @param data from the notification + * @returns early if platform is not ios + */ +const redirectSilentPush = function (event, data) { + logDebug('Found silent push notification, for platform ' + window['cordova'].platformId); + if (window['cordova'].platformId != 'ios') { + logDebug('Platform is not ios, handleSilentPush is not implemented or needed'); + // doesn't matter if we finish or not because platforms other than ios don't care + return; + } + logDebug('Platform is ios, calling handleSilentPush on DataCollection'); + var notId = data.additionalData.payload.notId; + var finishErrFn = function (error) { + logDebug('in push.finish, error = ' + error); + }; + + window['cordova'].plugins.BEMDataCollection.getConfig() + .then(function (config) { + if (config.ios_use_remote_push_for_sync) { + window['cordova'].plugins.BEMDataCollection.handleSilentPush().then(function () { + logDebug('silent push finished successfully, calling push.finish'); + showDebugLocalNotification('silent push finished, calling push.finish'); + push.finish(function () {}, finishErrFn, notId); + }); + } else { + logDebug('Using background fetch for sync, no need to redirect push'); + push.finish(function () {}, finishErrFn, notId); + } + }) + .catch(function (error) { + push.finish(function () {}, finishErrFn, notId); + displayError(error, 'Error while redirecting silent push'); + }); +}; + +/** + * @function shows debug notifications if simulating user interaction + * @param message string to display in the degug notif + */ +const showDebugLocalNotification = function (message) { + window['cordova'].plugins.BEMDataCollection.getConfig().then(function (config) { + if (config.simulate_user_interaction) { + window['cordova'].plugins.notification.local.schedule({ + id: 1, + title: 'Debug javascript notification', + text: message, + actions: [], + category: 'SIGN_IN_TO_CLASS', + }); + } + }); +}; + +/** + * @function handles pushNotification intitially + * @param event that called this function + * @param data from the notification + */ +const onCloudEvent = function (event, data) { + logDebug('data = ' + JSON.stringify(data)); + if (data.additionalData['content-available'] == 1) { + redirectSilentPush(event, data); + } // else no need to call finish +}; + +/** + * @function registers push on reconsent + * @param event that called this function + * @param data data from the conesnt event + */ +const onConsentEvent = function (event, data) { + console.log( + 'got consented event ' + JSON.stringify(event['name']) + ' with data ' + JSON.stringify(data), + ); + readIntroDone().then((isIntroDone) => { + if (isIntroDone) { + console.log('intro is done -> reconsent situation, we already have a token -> register'); + registerPush(); + } + }); +}; + +/** + * @function registers push after intro received + * @param event that called this function + * @param data from the event + */ +const onIntroEvent = function (event, data) { + console.log( + 'intro is done -> original consent situation, we should have a token by now -> register', + ); + registerPush(); +}; + +/** + * startup code - + * @function registers push if consented, subscribes event listeners for local handline + */ +export const initPushNotify = function () { + readConsentState() + .then(isConsented) + .then(function (consentState) { + if (consentState == true) { + logDebug('already consented, signing up for remote push'); + registerPush(); + } else { + logDebug('no consent yet, waiting to sign up for remote push'); + } + }); + + subscribe(EVENTS.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event.detail)); + subscribe(EVENTS.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); + subscribe(EVENTS.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); + + logDebug('pushnotify startup done'); +}; diff --git a/www/js/splash/pushnotify.js b/www/js/splash/pushnotify.js deleted file mode 100644 index 28e37aaa1..000000000 --- a/www/js/splash/pushnotify.js +++ /dev/null @@ -1,193 +0,0 @@ -//naming of this file can be a little confusing - "pushnotifysettings" for rewritten file -//https://github.com/e-mission/e-mission-phone/pull/1072#discussion_r1375360832 - -/* - * This module deals with the interaction with the push plugin, the redirection - * of silent push notifications and the re-parsing of iOS pushes. It then - * re-emits a CLOUD_NOTIFICATION_EVENT that other modules can listen to. - * - * Other modules, such as the survey code, and the remotenotify module, listen - * to these CLOUD_NOTIFICATION_EVENTs and handle them through launching - * surveys, displaying popups, etc. - * - * This allows us to decouple the push handling logic from push notification - * interface. Note that the local notification is not currently decoupled since - * it only supports redirection to a specific app page. If the local - * notification handling gets more complex, we should consider decoupling it as well. - */ - -import angular from 'angular'; -import { updateUser } from '../commHelper'; -import { readConsentState, isConsented } from './startprefs'; - -angular - .module('emission.splash.pushnotify', ['emission.plugin.logger', 'emission.services']) - .factory( - 'PushNotify', - function ($window, $state, $rootScope, $ionicPlatform, $ionicPopup, Logger) { - var pushnotify = {}; - var push = null; - pushnotify.CLOUD_NOTIFICATION_EVENT = 'cloud:push:notification'; - - pushnotify.startupInit = function () { - push = $window.PushNotification.init({ - ios: { - badge: true, - sound: true, - vibration: true, - clearBadge: true, - }, - android: { - iconColor: '#008acf', - icon: 'ic_mood_question', - clearNotifications: true, - }, - }); - push.on('notification', function (data) { - if ($ionicPlatform.is('ios')) { - // Parse the iOS values that are returned as strings - if (angular.isDefined(data) && angular.isDefined(data.additionalData)) { - if (angular.isDefined(data.additionalData.payload)) { - data.additionalData.payload = JSON.parse(data.additionalData.payload); - } - if ( - angular.isDefined(data.additionalData.data) && - typeof data.additionalData.data == 'string' - ) { - data.additionalData.data = JSON.parse(data.additionalData.data); - } else { - console.log('additionalData is already an object, no need to parse it'); - } - } else { - Logger.log('No additional data defined, nothing to parse'); - } - } - $rootScope.$emit(pushnotify.CLOUD_NOTIFICATION_EVENT, data); - }); - }; - - pushnotify.registerPromise = function () { - return new Promise(function (resolve, reject) { - pushnotify.startupInit(); - push.on('registration', function (data) { - console.log('Got registration ' + data); - resolve({ token: data.registrationId, type: data.registrationType }); - }); - push.on('error', function (error) { - console.log('Got push error ' + error); - reject(error); - }); - console.log('push notify = ' + push); - }); - }; - - pushnotify.registerPush = function () { - pushnotify - .registerPromise() - .then(function (t) { - // alert("Token = "+JSON.stringify(t)); - Logger.log('Token = ' + JSON.stringify(t)); - return $window.cordova.plugins.BEMServerSync.getConfig() - .then( - function (config) { - return config.sync_interval; - }, - function (error) { - console.log( - 'Got error ' + error + ' while reading config, returning default = 3600', - ); - return 3600; - }, - ) - .then(function (sync_interval) { - updateUser({ - device_token: t.token, - curr_platform: ionic.Platform.platform(), - curr_sync_interval: sync_interval, - }); - return t; - }); - }) - .then(function (t) { - // alert("Finished saving token = "+JSON.stringify(t.token)); - Logger.log('Finished saving token = ' + JSON.stringify(t.token)); - }) - .catch(function (error) { - Logger.displayError('Error in registering push notifications', error); - }); - }; - - var redirectSilentPush = function (event, data) { - Logger.log('Found silent push notification, for platform ' + ionic.Platform.platform()); - if (!$ionicPlatform.is('ios')) { - Logger.log('Platform is not ios, handleSilentPush is not implemented or needed'); - // doesn't matter if we finish or not because platforms other than ios don't care - return; - } - Logger.log('Platform is ios, calling handleSilentPush on DataCollection'); - var notId = data.additionalData.payload.notId; - var finishErrFn = function (error) { - Logger.log('in push.finish, error = ' + error); - }; - - pushnotify.datacollect - .getConfig() - .then(function (config) { - if (config.ios_use_remote_push_for_sync) { - pushnotify.datacollect.handleSilentPush().then(function () { - Logger.log('silent push finished successfully, calling push.finish'); - showDebugLocalNotification('silent push finished, calling push.finish'); - push.finish(function () {}, finishErrFn, notId); - }); - } else { - Logger.log('Using background fetch for sync, no need to redirect push'); - push.finish(function () {}, finishErrFn, notId); - } - }) - .catch(function (error) { - push.finish(function () {}, finishErrFn, notId); - Logger.displayError('Error while redirecting silent push', error); - }); - }; - - var showDebugLocalNotification = function (message) { - pushnotify.datacollect.getConfig().then(function (config) { - if (config.simulate_user_interaction) { - cordova.plugins.notification.local.schedule({ - id: 1, - title: 'Debug javascript notification', - text: message, - actions: [], - category: 'SIGN_IN_TO_CLASS', - }); - } - }); - }; - - pushnotify.registerNotificationHandler = function () { - $rootScope.$on(pushnotify.CLOUD_NOTIFICATION_EVENT, function (event, data) { - Logger.log('data = ' + JSON.stringify(data)); - if (data.additionalData['content-available'] == 1) { - redirectSilentPush(event, data); - } // else no need to call finish - }); - }; - - $ionicPlatform.ready().then(function () { - pushnotify.datacollect = $window.cordova.plugins.BEMDataCollection; - readConsentState() - .then(isConsented) - .then(function (consentState) { - if (consentState == true) { - pushnotify.registerPush(); - } else { - Logger.log('no consent yet, waiting to sign up for remote push'); - } - }); - pushnotify.registerNotificationHandler(); - Logger.log('pushnotify startup done'); - }); - - return pushnotify; - }, - ); diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts new file mode 100644 index 000000000..cbc920ced --- /dev/null +++ b/www/js/splash/remoteNotifyHandler.ts @@ -0,0 +1,75 @@ +/* + * This module deals with handling specific push messages that open web pages + * or popups. It does not interface with the push plugin directly. Instead, it + * assumes that another module (currently `pushnotify`) deals with the plugin + * interface and emits a CLOUD_NOTIFICATION_EVENT when a push notification is + * received. + * + * This allows us to decouple the push handling logic from push notification + * interface. Note that the local notification is not currently decoupled since + * it only supports redirection to a specific app page. If the local + * notification handling gets more complex, we should consider decoupling it as well. + */ +import { EVENTS, subscribe } from '../customEventHandler'; +import { addStatEvent, statKeys } from '../plugin/clientStats'; +import { displayErrorMsg, logDebug } from '../plugin/logger'; + +const options = 'location=yes,clearcache=no,toolbar=yes,hideurlbar=yes'; + +/* +TODO: Potentially unify with the survey URL loading +*/ +/** + * @function launches a webpage + * @param url to open in the browser + */ +const launchWebpage = function (url) { + // THIS LINE FOR inAppBrowser + let iab = window['cordova'].InAppBrowser.open(url, '_blank', options); +}; + +/** + * @callback for cloud notification event + * @param event that triggered this call + */ +const onCloudNotifEvent = (event) => { + const data = event.detail; + addStatEvent(statKeys.NOTIFICATION_OPEN).then(() => { + console.log('Added ' + statKeys.NOTIFICATION_OPEN + ' event. Data = ' + JSON.stringify(data)); + }); + logDebug('data = ' + JSON.stringify(data)); + if ( + data.additionalData && + data.additionalData.payload && + data.additionalData.payload.alert_type + ) { + if (data.additionalData.payload.alert_type == 'website') { + var webpage_spec = data.additionalData.payload.spec; + if (webpage_spec && webpage_spec.url && webpage_spec.url.startsWith('https://')) { + launchWebpage(webpage_spec.url); + } else { + displayErrorMsg( + JSON.stringify(webpage_spec), + 'webpage was not specified correctly. spec is ', + ); + } + } + if (data.additionalData.payload.alert_type == 'popup') { + var popup_spec = data.additionalData.payload.spec; + if (popup_spec && popup_spec.title && popup_spec.text) { + /* TODO: replace popup with something with better UI */ + window.alert(popup_spec.title + ' ' + popup_spec.text); + } else { + displayErrorMsg(JSON.stringify(popup_spec), 'popup was not specified correctly. spec is '); + } + } + } +}; + +/** + * @function initializes the remote notification handling + * subscribes to cloud notification event + */ +export const initRemoteNotifyHandler = function () { + subscribe(EVENTS.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); +}; diff --git a/www/js/splash/remotenotify.js b/www/js/splash/remotenotify.js deleted file mode 100644 index a59fdf376..000000000 --- a/www/js/splash/remotenotify.js +++ /dev/null @@ -1,91 +0,0 @@ -//naming of this module can be confusing "remotenotifyhandler" for rewritten file -//https://github.com/e-mission/e-mission-phone/pull/1072#discussion_r1375360832 - -/* - * This module deals with handling specific push messages that open web pages - * or popups. It does not interface with the push plugin directly. Instead, it - * assumes that another module (currently `pushnotify`) deals with the plugin - * interface and emits a CLOUD_NOTIFICATION_EVENT when a push notification is - * received. - * - * This allows us to decouple the push handling logic from push notification - * interface. Note that the local notification is not currently decoupled since - * it only supports redirection to a specific app page. If the local - * notification handling gets more complex, we should consider decoupling it as well. - */ -'use strict'; - -import angular from 'angular'; -import { addStatEvent, statKeys } from '../plugin/clientStats'; - -angular - .module('emission.splash.remotenotify', ['emission.plugin.logger']) - - .factory('RemoteNotify', function ($http, $window, $ionicPopup, $rootScope, Logger) { - var remoteNotify = {}; - remoteNotify.options = 'location=yes,clearcache=no,toolbar=yes,hideurlbar=yes'; - - /* - TODO: Potentially unify with the survey URL loading - */ - remoteNotify.launchWebpage = function (url) { - // THIS LINE FOR inAppBrowser - let iab = $window.cordova.InAppBrowser.open(url, '_blank', remoteNotify.options); - }; - - remoteNotify.launchPopup = function (title, text) { - // THIS LINE FOR inAppBrowser - let alertPopup = $ionicPopup.alert({ - title: title, - template: text, - }); - }; - - remoteNotify.init = function () { - $rootScope.$on('cloud:push:notification', function (event, data) { - addStatEvent(statKeys.NOTIFICATION_OPEN).then(() => { - console.log( - 'Added ' + statKeys.NOTIFICATION_OPEN + ' event. Data = ' + JSON.stringify(data), - ); - }); - Logger.log('data = ' + JSON.stringify(data)); - if ( - angular.isDefined(data.additionalData) && - angular.isDefined(data.additionalData.payload) && - angular.isDefined(data.additionalData.payload.alert_type) - ) { - if (data.additionalData.payload.alert_type == 'website') { - var webpage_spec = data.additionalData.payload.spec; - if ( - angular.isDefined(webpage_spec) && - angular.isDefined(webpage_spec.url) && - webpage_spec.url.startsWith('https://') - ) { - remoteNotify.launchWebpage(webpage_spec.url); - } else { - $ionicPopup.alert( - 'webpage was not specified correctly. spec is ' + JSON.stringify(webpage_spec), - ); - } - } - if (data.additionalData.payload.alert_type == 'popup') { - var popup_spec = data.additionalData.payload.spec; - if ( - angular.isDefined(popup_spec) && - angular.isDefined(popup_spec.title) && - angular.isDefined(popup_spec.text) - ) { - remoteNotify.launchPopup(popup_spec.title, popup_spec.text); - } else { - $ionicPopup.alert( - 'webpage was not specified correctly. spec is ' + JSON.stringify(popup_spec), - ); - } - } - } - }); - }; - - remoteNotify.init(); - return remoteNotify; - }); diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 75282bfd3..6d5761aae 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -1,7 +1,6 @@ -import { getAngularService } from '../angular-react-helper'; import { storageGet, storageSet } from '../plugin/storage'; import { logInfo, logDebug, displayErrorMsg } from '../plugin/logger'; -import { readIntroDone } from '../onboarding/onboardingHelper'; +import { EVENTS, publish } from '../customEventHandler'; // data collection consented protocol: string, represents the date on // which the consented protocol was approved by the IRB @@ -28,32 +27,19 @@ function writeConsentToNative() { export function markConsented() { logInfo('changing consent from ' + _curr_consented + ' -> ' + JSON.stringify(_req_consent)); // mark in native storage - return ( - readConsentState() - .then(writeConsentToNative) - .then(function (response) { - // mark in local storage - storageSet(DATA_COLLECTION_CONSENTED_PROTOCOL, _req_consent); - // mark in local variable as well - _curr_consented = { ..._req_consent }; - }) - //check for reconsent - .then(readIntroDone) - .then((isIntroDone) => { - if (isIntroDone) { - logDebug( - 'reconsent scenario - marked consent after intro done - registering pushnoify and storing device settings', - ); - const PushNotify = getAngularService('PushNotify'); - const StoreSeviceSettings = getAngularService('StoreDeviceSettings'); - PushNotify.registerPush(); - StoreSeviceSettings.storeDeviceSettings(); - } - }) - .catch((error) => { - displayErrorMsg(error, 'Error while while wrting consent to storage'); - }) - ); + return readConsentState() + .then(writeConsentToNative) + .then(function (response) { + // mark in local storage + storageSet(DATA_COLLECTION_CONSENTED_PROTOCOL, _req_consent); + // mark in local variable as well + _curr_consented = { ..._req_consent }; + // publish event + publish(EVENTS.CONSENTED_EVENT, _req_consent); + }) + .catch((error) => { + displayErrorMsg(error, 'Error while while wrting consent to storage'); + }); } /** diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts new file mode 100644 index 000000000..79a34f930 --- /dev/null +++ b/www/js/splash/storeDeviceSettings.ts @@ -0,0 +1,92 @@ +import { updateUser } from '../commHelper'; +import { isConsented, readConsentState } from './startprefs'; +import i18next from 'i18next'; +import { displayError, logDebug } from '../plugin/logger'; +import { readIntroDone } from '../onboarding/onboardingHelper'; +import { subscribe, EVENTS, unsubscribe } from '../customEventHandler'; + +/** + * @function Gathers information about the user's device and stores it + * @returns promise to updateUser in comm settings with device info + */ +const storeDeviceSettings = function () { + var lang = i18next.resolvedLanguage; + var manufacturer = window['device'].manufacturer; + var osver = window['device'].version; + return window['cordova'].getAppVersion + .getVersionNumber() + .then(function (appver) { + var updateJSON = { + phone_lang: lang, + curr_platform: window['cordova'].platformId, + manufacturer: manufacturer, + client_os_version: osver, + client_app_version: appver, + }; + logDebug('About to update profile with settings = ' + JSON.stringify(updateJSON)); + return updateUser(updateJSON); + }) + .then(function (updateJSON) { + // alert("Finished saving token = "+JSON.stringify(t.token)); + }) + .catch(function (error) { + displayError(error, 'Error in updating profile to store device settings'); + }); +}; + +/** + * @function stores device settings on reconsent + * @param event that called this function + */ +const onConsentEvent = (event) => { + console.log( + 'got consented event ' + + JSON.stringify(event['name']) + + ' with data ' + + JSON.stringify(event.detail), + ); + readIntroDone().then(async (isIntroDone) => { + if (isIntroDone) { + logDebug( + 'intro is done -> reconsent situation, we already have a token -> store device settings', + ); + await storeDeviceSettings(); + } + }); +}; + +/** + * @function stores device settings after intro received + * @param event that called this function + */ +const onIntroEvent = async (event) => { + logDebug( + 'intro is done -> original consent situation, we should have a token by now -> store device settings', + ); + await storeDeviceSettings(); +}; + +/** + * @function initializes store device: subscribes to events + * stores settings if already consented + */ +export const initStoreDeviceSettings = function () { + readConsentState() + .then(isConsented) + .then(async function (consentState) { + console.log('found consent', consentState); + if (consentState == true) { + await storeDeviceSettings(); + } else { + logDebug('no consent yet, waiting to store device settings in profile'); + } + subscribe(EVENTS.CONSENTED_EVENT, onConsentEvent); + subscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); + }); + logDebug('storedevicesettings startup done'); +}; + +export const teardownDeviceSettings = function () { + unsubscribe(EVENTS.CONSENTED_EVENT, onConsentEvent); + unsubscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); +}; diff --git a/www/js/splash/storedevicesettings.js b/www/js/splash/storedevicesettings.js deleted file mode 100644 index ab28cde2c..000000000 --- a/www/js/splash/storedevicesettings.js +++ /dev/null @@ -1,53 +0,0 @@ -import angular from 'angular'; -import { updateUser } from '../commHelper'; -import { isConsented, readConsentState } from './startprefs'; - -angular - .module('emission.splash.storedevicesettings', ['emission.plugin.logger', 'emission.services']) - .factory( - 'StoreDeviceSettings', - function ($window, $state, $rootScope, $ionicPlatform, $ionicPopup, Logger) { - var storedevicesettings = {}; - - storedevicesettings.storeDeviceSettings = function () { - var lang = i18next.resolvedLanguage; - var manufacturer = $window.device.manufacturer; - var osver = $window.device.version; - return $window.cordova.getAppVersion - .getVersionNumber() - .then(function (appver) { - var updateJSON = { - phone_lang: lang, - curr_platform: ionic.Platform.platform(), - manufacturer: manufacturer, - client_os_version: osver, - client_app_version: appver, - }; - Logger.log('About to update profile with settings = ' + JSON.stringify(updateJSON)); - return updateUser(updateJSON); - }) - .then(function (updateJSON) { - // alert("Finished saving token = "+JSON.stringify(t.token)); - }) - .catch(function (error) { - Logger.displayError('Error in updating profile to store device settings', error); - }); - }; - - $ionicPlatform.ready().then(function () { - storedevicesettings.datacollect = $window.cordova.plugins.BEMDataCollection; - readConsentState() - .then(isConsented) - .then(function (consentState) { - if (consentState == true) { - storedevicesettings.storeDeviceSettings(); - } else { - Logger.log('no consent yet, waiting to store device settings in profile'); - } - }); - Logger.log('storedevicesettings startup done'); - }); - - return storedevicesettings; - }, - );