From ea3fb10d468689dddb6c89a72d947030b1668272 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 14:11:31 -0600 Subject: [PATCH 01/43] create event handler React doesn't quite support custom events, so we're writing our own event handling code this will apply to our custom events, rather than the built-in events like 'onClick' --- www/js/customEventHandler.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 www/js/customEventHandler.ts diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts new file mode 100644 index 000000000..4284501b2 --- /dev/null +++ b/www/js/customEventHandler.ts @@ -0,0 +1,30 @@ +/** + * 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'; + +export function subscribe(eventName: string, listener) { + logDebug("adding " + eventName + " listener"); + document.addEventListener(eventName, listener); +} + +export function unsubscribe(eventName: string, listener){ + logDebug("removing " + eventName + " listener"); + document.removeEventListener(eventName, listener); +} + +export function publish(eventName, data) { + logDebug("publishing " + eventName); + const event = new CustomEvent(eventName, { detail: data }); + document.dispatchEvent(event); +} \ No newline at end of file From d267fcfbef9b15b3ed8965184f1a5b2463a3a6ae Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 14:13:28 -0600 Subject: [PATCH 02/43] change file name in preparation for full de-angularization, renaming the file to preserve some of the blame history --- www/js/splash/{pushnotify.js => pushNotifySettings.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/js/splash/{pushnotify.js => pushNotifySettings.ts} (100%) diff --git a/www/js/splash/pushnotify.js b/www/js/splash/pushNotifySettings.ts similarity index 100% rename from www/js/splash/pushnotify.js rename to www/js/splash/pushNotifySettings.ts From 7d69847752ce448f94c7a70cfdcd7e31b6d77e60 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 14:57:45 -0600 Subject: [PATCH 03/43] rewrite pushNotify in react updating to React, including the event subscription model usage --- www/js/splash/pushNotifySettings.ts | 303 ++++++++++++++-------------- 1 file changed, 150 insertions(+), 153 deletions(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 40d859f09..c20db243b 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -15,176 +15,173 @@ import angular from 'angular'; import { updateUser } from '../commHelper'; +import { logDebug, displayError } from '../plugin/logger'; +import { publish, subscribe, EVENT_NAMES } from '../customEventHandler'; +import { getAngularService } from '../angular-react-helper'; -angular.module('emission.splash.pushnotify', ['emission.plugin.logger', - 'emission.services', - 'emission.splash.startprefs']) -.factory('PushNotify', function($window, $state, $rootScope, $ionicPlatform, - $ionicPopup, Logger, StartPrefs) { +let push = null; - 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 +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 (angular.isDefined(data) && + angular.isDefined(data.additionalData)) { + if (angular.isDefined(data.additionalData.payload)) { + data.additionalData.payload = JSON.parse(data.additionalData.payload); } - }); - 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"); - } + 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"); } - $rootScope.$emit(pushnotify.CLOUD_NOTIFICATION_EVENT, data); - }); + } else { + logDebug("No additional data defined, nothing to parse"); + } } + publish(EVENT_NAMES.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); - }); - } +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); + }); +} - 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); +const registerPush = function () { + registerPromise().then(function (t) { + // alert("Token = "+JSON.stringify(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) { + // alert("Finished saving token = "+JSON.stringify(t.token)); + logDebug("Finished saving token = " + JSON.stringify(t.token)); + }).catch(function (error) { + displayError(error, "Error in registering push notifications"); + }); +} - 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); - }; +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); + }; - 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); - }; + 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); }) - .catch(function(error) { - push.finish(function(){}, finishErrFn, notId); - Logger.displayError("Error while redirecting silent push", error); - }); - } + } 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"); + }); +} - 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' - }); - } - }); +var 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' + }); } + }); +} - 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 - }); - }; +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 +} - $ionicPlatform.ready().then(function() { - pushnotify.datacollect = $window.cordova.plugins.BEMDataCollection; - StartPrefs.readConsentState() - .then(StartPrefs.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"); - }); +const onConsentEvent = function (event, data) { + const StartPrefs = getAngularService('StartPrefs'); + console.log("got consented event " + JSON.stringify(event['name']) + + " with data " + JSON.stringify(data)); + if (StartPrefs.isIntroDone()) { + console.log("intro is done -> reconsent situation, we already have a token -> register"); + registerPush(); + } +} - $rootScope.$on(StartPrefs.CONSENTED_EVENT, function(event, data) { - console.log("got consented event "+JSON.stringify(event.name) - +" with data "+ JSON.stringify(data)); - if (StartPrefs.isIntroDone()) { - console.log("intro is done -> reconsent situation, we already have a token -> register"); - pushnotify.registerPush(); +const onIntroEvent = function (event, data) { + console.log("intro is done -> original consent situation, we should have a token by now -> register"); + registerPush(); +} + +const initPushNotify = function () { + const StartPrefs = getAngularService('StartPrefs'); + StartPrefs.readConsentState() + .then(StartPrefs.isConsented) + .then(function (consentState) { + if (consentState == true) { + registerPush(); + } else { + logDebug("no consent yet, waiting to sign up for remote push"); } }); - $rootScope.$on(StartPrefs.INTRO_DONE_EVENT, function(event, data) { - console.log("intro is done -> original consent situation, we should have a token by now -> register"); - pushnotify.registerPush(); - }); + subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event['detail'].data)); + subscribe(StartPrefs.CONSENTED_EVENT, (event) => onConsentEvent(event, event['detail'].data)); + subscribe(StartPrefs.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event['detail'].data)); - return pushnotify; -}); + logDebug("pushnotify startup done"); +} From 56156a0255076db3e00a84729332baa46c4a03ca Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 15:03:03 -0600 Subject: [PATCH 04/43] remove references to old file --- www/index.js | 1 - www/js/controllers.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/www/index.js b/www/index.js index 0a0c63708..429085657 100644 --- a/www/index.js +++ b/www/index.js @@ -6,7 +6,6 @@ import 'leaflet/dist/leaflet.css'; import './js/ngApp.js'; import './js/splash/referral.js'; import './js/splash/startprefs.js'; -import './js/splash/pushnotify.js'; import './js/splash/storedevicesettings.js'; import './js/splash/localnotify.js'; import './js/splash/remotenotify.js'; diff --git a/www/js/controllers.js b/www/js/controllers.js index 75124efce..323359ad0 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -4,7 +4,6 @@ import angular from 'angular'; import { addStatError, addStatReading, statKeys } from './plugin/clientStats'; angular.module('emission.controllers', ['emission.splash.startprefs', - 'emission.splash.pushnotify', 'emission.splash.storedevicesettings', 'emission.splash.localnotify', 'emission.splash.remotenotify']) @@ -14,7 +13,7 @@ angular.module('emission.controllers', ['emission.splash.startprefs', .controller('DashCtrl', function($scope) {}) .controller('SplashCtrl', function($scope, $state, $interval, $rootScope, - StartPrefs, PushNotify, StoreDeviceSettings, + StartPrefs, StoreDeviceSettings, LocalNotify, RemoteNotify) { console.log('SplashCtrl invoked'); // alert("attach debugger!"); From 332bfbe50444545148e928e0e8aec1eb21217817 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 15:03:14 -0600 Subject: [PATCH 05/43] centralize the event names --- www/js/customEventHandler.ts | 6 ++++++ www/js/splash/pushNotifySettings.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts index 4284501b2..9f24243ff 100644 --- a/www/js/customEventHandler.ts +++ b/www/js/customEventHandler.ts @@ -13,6 +13,12 @@ import { logDebug } from './plugin/logger'; +export const EVENT_NAMES = { + CLOUD_NOTIFICATION_EVENT: 'cloud:push:notification', + CONSENTED_EVENT: "data_collection_consented", + INTRO_DONE_EVENT: "intro_done", +} + export function subscribe(eventName: string, listener) { logDebug("adding " + eventName + " listener"); document.addEventListener(eventName, listener); diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index c20db243b..e7a142264 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -180,8 +180,8 @@ const initPushNotify = function () { }); subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event['detail'].data)); - subscribe(StartPrefs.CONSENTED_EVENT, (event) => onConsentEvent(event, event['detail'].data)); - subscribe(StartPrefs.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event['detail'].data)); + subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event['detail'].data)); + subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event['detail'].data)); logDebug("pushnotify startup done"); } From a9918140c7d848c0bc733a55294fb005141349a7 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 15:08:32 -0600 Subject: [PATCH 06/43] emit AND publish events for now, so they get picked up both ways --- www/js/splash/startprefs.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/js/splash/startprefs.js b/www/js/splash/startprefs.js index 92a07e624..96e049b35 100644 --- a/www/js/splash/startprefs.js +++ b/www/js/splash/startprefs.js @@ -1,5 +1,6 @@ import angular from 'angular'; import { getConfig } from '../config/dynamicConfig'; +import { EVENT_NAMES, publish } from '../customEventHandler'; import { storageGet, storageSet } from '../plugin/storage'; angular.module('emission.splash.startprefs', ['emission.plugin.logger', @@ -36,6 +37,7 @@ angular.module('emission.splash.startprefs', ['emission.plugin.logger', // mark in local variable as well $rootScope.curr_consented = angular.copy($rootScope.req_consent); $rootScope.$emit(startprefs.CONSENTED_EVENT, $rootScope.req_consent); + publish(EVENT_NAMES.CONSENTED_EVENT, $rootScope.req_consent); }); }; @@ -43,6 +45,7 @@ angular.module('emission.splash.startprefs', ['emission.plugin.logger', var currTime = moment().format(); storageSet(INTRO_DONE_KEY, currTime); $rootScope.$emit(startprefs.INTRO_DONE_EVENT, currTime); + publish(EVENT_NAMES.INTRO_DONE_EVENT, currTime); } // returns boolean From a90af2f6ecdd515ba6bd4496e70b41cf16614ad7 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 15:27:20 -0600 Subject: [PATCH 07/43] add function descriptions --- www/js/splash/pushNotifySettings.ts | 43 +++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index e7a142264..2a112364c 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -21,6 +21,10 @@ import { getAngularService } from '../angular-react-helper'; let push = null; +/** + * @function initializes the PushNotification in window, + * assigns on 'notification' functionality + */ const startupInit = function () { push = window['PushNotification'].init({ "ios": { @@ -56,6 +60,12 @@ const startupInit = function () { }); } +/** + * @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(); @@ -74,6 +84,10 @@ const registerPromise = function () { }); } +/** + * @function registers for notifications and updates user + * currently called on reconsent and on intro done + */ const registerPush = function () { registerPromise().then(function (t) { // alert("Token = "+JSON.stringify(t)); @@ -99,6 +113,12 @@ const registerPush = function () { }); } +/** + * @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') { @@ -131,6 +151,10 @@ const redirectSilentPush = function (event, data) { }); } +/** + * @function shows debug notifications if simulating user interaction + * @param message string to display in the degug notif + */ var showDebugLocalNotification = function (message) { window['cordova'].plugins.BEMDataCollection.getConfig().then(function (config) { if (config.simulate_user_interaction) { @@ -145,6 +169,11 @@ var showDebugLocalNotification = function (message) { }); } +/** + * @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) { @@ -152,6 +181,11 @@ const onCloudEvent = function (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) { const StartPrefs = getAngularService('StartPrefs'); console.log("got consented event " + JSON.stringify(event['name']) @@ -162,11 +196,20 @@ const onConsentEvent = function (event, data) { } } +/** + * @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 + */ const initPushNotify = function () { const StartPrefs = getAngularService('StartPrefs'); StartPrefs.readConsentState() From 09cbaa14459c4c1f5b36ab36742ce28deab4c531 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 16:11:53 -0600 Subject: [PATCH 08/43] ensure proper initialization --- www/js/App.tsx | 2 ++ www/js/onboarding/onboardingHelper.ts | 2 ++ www/js/splash/pushNotifySettings.ts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index 3c6c8bec9..72bdc4712 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -11,6 +11,7 @@ import { OnboardingRoute, OnboardingState, getPendingOnboardingState } from './o import { setServerConnSettings } from './config/serverConn'; import AppStatusModal from './control/AppStatusModal'; import usePermissionStatus from './usePermissionStatus'; +import { initPushNotify } from './splash/pushNotifySettings'; const defaultRoutes = (t) => [ { key: 'label', title: t('diary.label-tab'), focusedIcon: 'check-bold', unfocusedIcon: 'check-outline' }, @@ -52,6 +53,7 @@ const App = () => { setServerConnSettings(appConfig).then(() => { refreshOnboardingState(); }); + initPushNotify(); }, [appConfig]); const appContextValue = { diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index abd8e78aa..05ace6068 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -3,6 +3,7 @@ import { getAngularService } from "../angular-react-helper"; import { getConfig, resetDataAndRefresh } from "../config/dynamicConfig"; import { storageGet, storageSet } from "../plugin/storage"; import { logDebug } from "../plugin/logger"; +import { EVENT_NAMES, publish } from "../customEventHandler"; export const INTRO_DONE_KEY = 'intro_done'; @@ -71,5 +72,6 @@ async function readIntroDone() { export async function markIntroDone() { const currDateTime = DateTime.now().toISO(); + publish(EVENT_NAMES.INTRO_DONE_EVENT, currDateTime); return storageSet(INTRO_DONE_KEY, currDateTime); } diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 2a112364c..4e98d669f 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -210,7 +210,7 @@ const onIntroEvent = function (event, data) { * startup code - * @function registers push if consented, subscribes event listeners for local handline */ -const initPushNotify = function () { +export const initPushNotify = function () { const StartPrefs = getAngularService('StartPrefs'); StartPrefs.readConsentState() .then(StartPrefs.isConsented) From b1838aa43a3867543340032171279f8937ee56e1 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 16:38:53 -0600 Subject: [PATCH 09/43] add tests for customEventHandler tests gleaned from https://blog.logrocket.com/using-custom-events-react/ --- www/__tests__/customEventHandler.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 www/__tests__/customEventHandler.test.ts diff --git a/www/__tests__/customEventHandler.test.ts b/www/__tests__/customEventHandler.test.ts new file mode 100644 index 000000000..d45dba01b --- /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(); +}) \ No newline at end of file From 2dca41e2b424e72d5188bd5411baf0646e07aa2d Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Tue, 31 Oct 2023 16:55:45 -0600 Subject: [PATCH 10/43] finish resolving merge conflicts after merging, needed to fix the differences in the was startprefs is used --- www/js/splash/pushNotifySettings.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 4e98d669f..b6d4f8986 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -17,7 +17,8 @@ import angular from 'angular'; import { updateUser } from '../commHelper'; import { logDebug, displayError } from '../plugin/logger'; import { publish, subscribe, EVENT_NAMES } from '../customEventHandler'; -import { getAngularService } from '../angular-react-helper'; +import { isConsented, readConsentState } from './startprefs'; +import { readIntroDone } from '../onboarding/onboardingHelper'; let push = null; @@ -187,13 +188,14 @@ const onCloudEvent = function (event, data) { * @param data data from the conesnt event */ const onConsentEvent = function (event, data) { - const StartPrefs = getAngularService('StartPrefs'); console.log("got consented event " + JSON.stringify(event['name']) + " with data " + JSON.stringify(data)); - if (StartPrefs.isIntroDone()) { - console.log("intro is done -> reconsent situation, we already have a token -> register"); - registerPush(); - } + readIntroDone().then((isIntroDone) => { + if (isIntroDone) { + console.log("intro is done -> reconsent situation, we already have a token -> register"); + registerPush(); + } + }) } /** @@ -211,9 +213,8 @@ const onIntroEvent = function (event, data) { * @function registers push if consented, subscribes event listeners for local handline */ export const initPushNotify = function () { - const StartPrefs = getAngularService('StartPrefs'); - StartPrefs.readConsentState() - .then(StartPrefs.isConsented) + readConsentState() + .then(isConsented) .then(function (consentState) { if (consentState == true) { registerPush(); From bb8f1ec3e3777e511fa6bf63a998605189aad429 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 09:35:44 -0600 Subject: [PATCH 11/43] add rough tests, correct data access the way to access the data from the event is with event.detail, not event.detail.data I discovered and fixed this, as well as adding mocks, when I started a draft test currently runs the code, but does not check that it runs correctly - used to know mocks were sufficient --- www/__mocks__/pushNotificationMocks.ts | 29 ++++++++++++++++++++++++ www/__tests__/pushNotifySettings.test.ts | 27 ++++++++++++++++++++++ www/js/customEventHandler.ts | 2 +- www/js/splash/pushNotifySettings.ts | 6 ++--- 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 www/__mocks__/pushNotificationMocks.ts create mode 100644 www/__tests__/pushNotifySettings.test.ts diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts new file mode 100644 index 000000000..66bd550ca --- /dev/null +++ b/www/__mocks__/pushNotificationMocks.ts @@ -0,0 +1,29 @@ +let notifSettings; +let onList = {}; + +export const mockPushNotification = () => { + window['PushNotification'] = { + init: (settings: Object) => { + notifSettings = settings; + return { + on: (event: string, callback: Function) => { + onList[event] = callback; + } + }; + }, + }; +} + +export const clearNotifMock = function () { + notifSettings = {}; + onList = {}; +} + +export const getOnList = function () { + return onList; +} + +export const fakeEvent = function (eventName : string) { + //fake the event by executing whatever we have stored for it + onList[eventName](); +} \ No newline at end of file diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts new file mode 100644 index 000000000..06621087c --- /dev/null +++ b/www/__tests__/pushNotifySettings.test.ts @@ -0,0 +1,27 @@ +import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { initPushNotify } from '../js/splash/pushNotifySettings'; +import { mockCordova, mockBEMUserCache } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import { mockPushNotification } from '../__mocks__/pushNotificationMocks'; + +mockCordova(); +mockLogger(); +mockPushNotification(); +mockBEMUserCache(); + +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; + +it('initializes the push notifications', () => { + initPushNotify(); + + publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-avaliable': 1}}); +}) \ No newline at end of file diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts index 9f24243ff..e84fe8123 100644 --- a/www/js/customEventHandler.ts +++ b/www/js/customEventHandler.ts @@ -30,7 +30,7 @@ export function unsubscribe(eventName: string, listener){ } export function publish(eventName, data) { - logDebug("publishing " + eventName); + logDebug("publishing " + eventName + " with data " + JSON.stringify(data)); const event = new CustomEvent(eventName, { detail: data }); document.dispatchEvent(event); } \ No newline at end of file diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index b6d4f8986..29c036a85 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -223,9 +223,9 @@ export const initPushNotify = function () { } }); - subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event['detail'].data)); - subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event['detail'].data)); - subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event['detail'].data)); + subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event.detail)); + subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); + subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); logDebug("pushnotify startup done"); } From 5fbeac2954ba30f1a02fe2c3d2201c56ee4fddd5 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 11:17:54 -0600 Subject: [PATCH 12/43] increase tests now all but one feature has a passing test, working on testing the reconsent situation --- www/__mocks__/pushNotificationMocks.ts | 2 +- www/__tests__/pushNotifySettings.test.ts | 63 ++++++++++++++++++++++-- www/js/splash/pushNotifySettings.ts | 1 + 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts index 66bd550ca..c6f8548ed 100644 --- a/www/__mocks__/pushNotificationMocks.ts +++ b/www/__mocks__/pushNotificationMocks.ts @@ -1,5 +1,5 @@ let notifSettings; -let onList = {}; +let onList : any = {}; export const mockPushNotification = () => { window['PushNotification'] = { diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index 06621087c..7d35a6c11 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -1,8 +1,9 @@ import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { markIntroDone } from '../js/onboarding/onboardingHelper'; import { initPushNotify } from '../js/splash/pushNotifySettings'; import { mockCordova, mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; -import { mockPushNotification } from '../__mocks__/pushNotificationMocks'; +import { clearNotifMock, getOnList, mockPushNotification } from '../__mocks__/pushNotificationMocks'; mockCordova(); mockLogger(); @@ -18,10 +19,64 @@ global.fetch = (url: string) => new Promise((rs, rj) => { })); }) as any; -it('initializes the push notifications', () => { - initPushNotify(); +it('intro done does nothing if not registered', () => { + clearNotifMock(); + expect(getOnList()).toStrictEqual({}); + publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + expect(getOnList()).toStrictEqual({}); +}) - publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); +it('intro done initializes the push notifications', () => { + clearNotifMock(); + expect(getOnList()).toStrictEqual({}); + + initPushNotify(); publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + // setTimeout(() => {}, 100); + 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', () => { + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-avaliable': 1}}); + //how to test did nothing? +}) + +it('cloud event handles notification if registered', () => { + initPushNotify(); publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-avaliable': 1}}); + //how to test did something? +}) + +it('consent event does nothing if not registered', () => { + clearNotifMock(); + expect(getOnList()).toStrictEqual({}); + publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + expect(getOnList()).toStrictEqual({}); +}) + +// it('consent event registers if intro done', () => { +// clearNotifMock(); +// expect(getOnList()).toStrictEqual({}); +// initPushNotify(); +// markIntroDone(); +// // setTimeout(() => {}, 100); +// publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); +// setTimeout(() => {}, 200); +// 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', () => { + clearNotifMock(); + expect(getOnList()).toStrictEqual({}); + initPushNotify(); + publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + expect(getOnList()).toStrictEqual({}); //nothing, intro not done }) \ No newline at end of file diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 29c036a85..a7a4ec001 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -217,6 +217,7 @@ export const initPushNotify = function () { .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"); From c40698893d0d861c3b85b48ba2e6841611cd66cb Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 11:24:04 -0600 Subject: [PATCH 13/43] pushnotify relies on events now moving to event-driven paradigm, so we don't call the method from pushNotify directly. this will apply to storedevicesettings once it gets migrated --- www/js/onboarding/ProtocolPage.tsx | 1 - www/js/splash/startprefs.ts | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/www/js/onboarding/ProtocolPage.tsx b/www/js/onboarding/ProtocolPage.tsx index a047a0aae..1630748b4 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/splash/startprefs.ts b/www/js/splash/startprefs.ts index 43f29c692..3c4823af7 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -2,6 +2,7 @@ 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 { EVENT_NAMES, publish } from "../customEventHandler"; // data collection consented protocol: string, represents the date on // which the consented protocol was approved by the IRB @@ -37,15 +38,16 @@ export function markConsented() { _req_consent); // mark in local variable as well _curr_consented = { ..._req_consent }; + // publish event + publish(EVENT_NAMES.CONSENTED_EVENT, _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"); + logDebug("reconsent scenario - marked consent after intro done - registering pushnoify and storing device settings"); + //pushnotify uses events now const StoreSeviceSettings = getAngularService("StoreDeviceSettings"); - PushNotify.registerPush(); StoreSeviceSettings.storeDeviceSettings(); } }) From d27aeddf5bb00872a40e391967a995b9277ca486 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 12:01:42 -0600 Subject: [PATCH 14/43] update tests after working on the consent event and process for checking for reconsent, we now have a test for that module that passes --- www/__tests__/pushNotifySettings.test.ts | 45 ++++++++++++++++-------- www/js/splash/pushNotifySettings.ts | 13 +++---- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index 7d35a6c11..166b0ac7c 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -1,5 +1,7 @@ +import { DateTime } from 'luxon'; import { EVENT_NAMES, publish } from '../js/customEventHandler'; -import { markIntroDone } from '../js/onboarding/onboardingHelper'; +import { INTRO_DONE_KEY, markIntroDone, readIntroDone } from '../js/onboarding/onboardingHelper'; +import { storageSet } from '../js/plugin/storage'; import { initPushNotify } from '../js/splash/pushNotifySettings'; import { mockCordova, mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; @@ -58,20 +60,33 @@ it('consent event does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); }) -// it('consent event registers if intro done', () => { -// clearNotifMock(); -// expect(getOnList()).toStrictEqual({}); -// initPushNotify(); -// markIntroDone(); -// // setTimeout(() => {}, 100); -// publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); -// setTimeout(() => {}, 200); -// expect(getOnList()).toStrictEqual(expect.objectContaining({ -// notification: expect.any(Function), -// error: expect.any(Function), -// registration: expect.any(Function) -// })); -// }) +it('consent event registers if intro done', async () => { + //make sure the mock is clear + clearNotifMock(); + expect(getOnList()).toStrictEqual({}); + + //initialize the pushNotify, to subscribe to events + initPushNotify(); + console.log("initialized"); + + //mark the intro as done + const currDateTime = DateTime.now().toISO(); + let marked = await storageSet(INTRO_DONE_KEY, currDateTime); + console.log("marked intro"); + let introDone = await readIntroDone(); + expect(introDone).toBeTruthy(); + + //publish consent event and check results + publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + //have to wait a beat since event response is async + setTimeout(() => { + expect(getOnList()).toStrictEqual(expect.objectContaining({ + notification: expect.any(Function), + error: expect.any(Function), + registration: expect.any(Function) + })); + }, 100); +}) it('consent event does not register if intro not done', () => { clearNotifMock(); diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index a7a4ec001..b252508db 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -190,12 +190,13 @@ const onCloudEvent = function (event, data) { 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(); - } - }) + readIntroDone() + .then((isIntroDone) => { + if (isIntroDone) { + console.log("intro is done -> reconsent situation, we already have a token -> register"); + registerPush(); + } + }); } /** From 82e864bea38c071802810a3eed7a06ddf917df51 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 12:06:40 -0600 Subject: [PATCH 15/43] add docstrings to customEventHandler --- www/js/customEventHandler.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts index e84fe8123..f6606cf8f 100644 --- a/www/js/customEventHandler.ts +++ b/www/js/customEventHandler.ts @@ -13,23 +13,42 @@ import { logDebug } from './plugin/logger'; +/** + * central source for event names + */ export const EVENT_NAMES = { 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); } -export function publish(eventName, data) { +/** + * @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); From 0eb7b4bb2f42ed6da5f00559f4a5a65ff2e0e744 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 13:46:33 -0600 Subject: [PATCH 16/43] test cloud event added in test for the cloud event when subscribed and when not subscribed. additional mocking was required to handle the silent push functions --- www/__mocks__/cordovaMocks.ts | 12 ++++++++++++ www/__mocks__/pushNotificationMocks.ts | 9 +++++++++ www/__tests__/pushNotifySettings.test.ts | 23 +++++++++++++++-------- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index c00377120..2c45e2a20 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -124,6 +124,18 @@ export const mockBEMDataCollection = () => { setTimeout(() => { _storage['config/consent'] = consentDoc; }, 100) + }, + getConfig: () => { + return new Promise((rs, rj) => { + rs({ 'ios_use_remote_push_for_sync': true }); + }); + }, + handleSilentPush: () => { + return new Promise((rs, rj) => + setTimeout(() => { + rs(); + }, 100) + ); } } window['cordova'] ||= {}; diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts index c6f8548ed..47b7bd940 100644 --- a/www/__mocks__/pushNotificationMocks.ts +++ b/www/__mocks__/pushNotificationMocks.ts @@ -1,5 +1,6 @@ let notifSettings; let onList : any = {}; +let called = null; export const mockPushNotification = () => { window['PushNotification'] = { @@ -8,6 +9,9 @@ export const mockPushNotification = () => { return { on: (event: string, callback: Function) => { onList[event] = callback; + }, + finish: (content: any, errorFcn: Function, notID: any) => { + called = notID; } }; }, @@ -17,12 +21,17 @@ export const mockPushNotification = () => { export const clearNotifMock = function () { notifSettings = {}; onList = {}; + called = null; } export const getOnList = function () { return onList; } +export const getCalled = function() { + return called; +} + export const fakeEvent = function (eventName : string) { //fake the event by executing whatever we have stored for it onList[eventName](); diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index 166b0ac7c..22579f536 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -1,21 +1,22 @@ import { DateTime } from 'luxon'; import { EVENT_NAMES, publish } from '../js/customEventHandler'; -import { INTRO_DONE_KEY, markIntroDone, readIntroDone } from '../js/onboarding/onboardingHelper'; +import { INTRO_DONE_KEY, readIntroDone } from '../js/onboarding/onboardingHelper'; import { storageSet } from '../js/plugin/storage'; import { initPushNotify } from '../js/splash/pushNotifySettings'; -import { mockCordova, mockBEMUserCache } from '../__mocks__/cordovaMocks'; +import { mockCordova, mockBEMUserCache, mockBEMDataCollection } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; -import { clearNotifMock, getOnList, mockPushNotification } from '../__mocks__/pushNotificationMocks'; +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" } }; + let myJSON = { "emSensorDataCollectionProtocol": { "protocol_id": "2014-04-6267", "approval_date": "2016-07-14" }, }; setTimeout(() => rs(myJSON), 100); }) })); @@ -43,14 +44,20 @@ it('intro done initializes the push notifications', () => { }) it('cloud event does nothing if not registered', () => { - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-avaliable': 1}}); - //how to test did nothing? + expect(window['cordova'].platformId).toEqual('ios'); + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notID' : 3}}}); + expect(getCalled()).toBeNull(); }) it('cloud event handles notification if registered', () => { + clearNotifMock(); + expect(window['cordova'].platformId).toEqual('ios'); initPushNotify(); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-avaliable': 1}}); - //how to test did something? + publish(EVENT_NAMES.INTRO_DONE_EVENT, "intro done"); + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notID' : 3}}}); + setTimeout(() => { + expect(getCalled()).toEqual(3); + }, 300) }) it('consent event does nothing if not registered', () => { From 24784db6fae4c5f0425f3e77dcac7a3ad122ef2a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 13:48:55 -0600 Subject: [PATCH 17/43] add timeout to config mock I had originally omitted the timeout because it seemed to break the test, but upon having the tests fully working, I was able to restore it this change makes the test code a little more realistic --- www/__mocks__/cordovaMocks.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index 2c45e2a20..9b8980c37 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -127,7 +127,9 @@ export const mockBEMDataCollection = () => { }, getConfig: () => { return new Promise((rs, rj) => { - rs({ 'ios_use_remote_push_for_sync': true }); + setTimeout(() => { + rs({ 'ios_use_remote_push_for_sync': true }); + }, 100) }); }, handleSilentPush: () => { From 46d5ed8960d263b91ac42fdc536b2ac336d81188 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 14:33:50 -0600 Subject: [PATCH 18/43] update tests moved the clear plugin into an "afterEach" call to clean up the code corrected a key spelling that fixed one of the tests, and updated the way I wait for the event handling to happen https://stackoverflow.com/questions/45478730/jest-react-testing-check-state-after-delay --- www/__tests__/pushNotifySettings.test.ts | 26 +++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index 22579f536..abaaa0954 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -22,20 +22,21 @@ global.fetch = (url: string) => new Promise((rs, rj) => { })); }) as any; -it('intro done does nothing if not registered', () => { +afterEach(() => { clearNotifMock(); +}); + +it('intro done does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); expect(getOnList()).toStrictEqual({}); }) it('intro done initializes the push notifications', () => { - clearNotifMock(); expect(getOnList()).toStrictEqual({}); initPushNotify(); publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); - // setTimeout(() => {}, 100); expect(getOnList()).toStrictEqual(expect.objectContaining({ notification: expect.any(Function), error: expect.any(Function), @@ -45,23 +46,20 @@ it('intro done initializes the push notifications', () => { it('cloud event does nothing if not registered', () => { expect(window['cordova'].platformId).toEqual('ios'); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notID' : 3}}}); + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notId' : 3}}}); expect(getCalled()).toBeNull(); }) -it('cloud event handles notification if registered', () => { - clearNotifMock(); +it('cloud event handles notification if registered', async () => { expect(window['cordova'].platformId).toEqual('ios'); initPushNotify(); publish(EVENT_NAMES.INTRO_DONE_EVENT, "intro done"); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notID' : 3}}}); - setTimeout(() => { - expect(getCalled()).toEqual(3); - }, 300) -}) + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notId' : 3}}}); + await new Promise((r) => setTimeout(r, 1000)); + expect(getCalled()).toEqual(3); +}, 10000) it('consent event does nothing if not registered', () => { - clearNotifMock(); expect(getOnList()).toStrictEqual({}); publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); expect(getOnList()).toStrictEqual({}); @@ -69,17 +67,14 @@ it('consent event does nothing if not registered', () => { it('consent event registers if intro done', async () => { //make sure the mock is clear - clearNotifMock(); expect(getOnList()).toStrictEqual({}); //initialize the pushNotify, to subscribe to events initPushNotify(); - console.log("initialized"); //mark the intro as done const currDateTime = DateTime.now().toISO(); let marked = await storageSet(INTRO_DONE_KEY, currDateTime); - console.log("marked intro"); let introDone = await readIntroDone(); expect(introDone).toBeTruthy(); @@ -96,7 +91,6 @@ it('consent event registers if intro done', async () => { }) it('consent event does not register if intro not done', () => { - clearNotifMock(); expect(getOnList()).toStrictEqual({}); initPushNotify(); publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); From 4f5e85341db1d3de60e90faedbece48d672c021c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 1 Nov 2023 14:56:46 -0600 Subject: [PATCH 19/43] sync up test runs syncing up the ways that tests run, replacing the way timeouts are set, removing unneeded timeout extension --- www/__tests__/pushNotifySettings.test.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index abaaa0954..58c6bb48e 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -57,7 +57,7 @@ it('cloud event handles notification if registered', async () => { publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notId' : 3}}}); await new Promise((r) => setTimeout(r, 1000)); expect(getCalled()).toEqual(3); -}, 10000) +}) it('consent event does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); @@ -81,13 +81,12 @@ it('consent event registers if intro done', async () => { //publish consent event and check results publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); //have to wait a beat since event response is async - setTimeout(() => { - expect(getOnList()).toStrictEqual(expect.objectContaining({ - notification: expect.any(Function), - error: expect.any(Function), - registration: expect.any(Function) - })); - }, 100); + 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', () => { From e1999fd85f63fe1b6eab9685ac055323b7ffbc77 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 11:05:13 -0600 Subject: [PATCH 20/43] rename file --- www/js/splash/{storedevicesettings.js => storeDeviceSettings.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/js/splash/{storedevicesettings.js => storeDeviceSettings.ts} (100%) diff --git a/www/js/splash/storedevicesettings.js b/www/js/splash/storeDeviceSettings.ts similarity index 100% rename from www/js/splash/storedevicesettings.js rename to www/js/splash/storeDeviceSettings.ts From ede23473eed48016128db1f95fa6d4958ee234e9 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 11:16:17 -0600 Subject: [PATCH 21/43] rewrite service to typscript in this rewrite, also include the subscription to events so that events can be called on consent and on intro done --- www/js/splash/storeDeviceSettings.ts | 104 ++++++++++++++++----------- 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 31543bc6c..0077499e5 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -1,48 +1,70 @@ -import angular from 'angular'; + 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, EVENT_NAMES } from '../customEventHandler'; -angular.module('emission.splash.storedevicesettings', ['emission.plugin.logger', - 'emission.services']) -.factory('StoreDeviceSettings', function($window, $state, $rootScope, $ionicPlatform, - $ionicPopup, Logger ) { +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"); + }); +} - var storedevicesettings = {}; +/** + * @function stores device settings 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) { + logDebug("intro is done -> reconsent situation, we already have a token -> store device settings"); + 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); - }); - } +/** + * @function stores device settings after intro received + * @param event that called this function + * @param data from the event + */ +const onIntroEvent = function (event, data) { + logDebug("intro is done -> original consent situation, we should have a token by now -> store device settings"); + storeDeviceSettings(); +} - $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"); +const initStoreDeviceSettings = function () { + readConsentState() + .then(isConsented) + .then(function (consentState) { + if (consentState == true) { + storeDeviceSettings(); + } else { + logDebug("no consent yet, waiting to store device settings in profile"); + } + subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); + subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); }); - - return storedevicesettings; -}); + logDebug("storedevicesettings startup done"); +} From cd6dc05a14d8e031f0dd9de1fd56e0bc8610b55a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 11:16:53 -0600 Subject: [PATCH 22/43] remove / update references remove references to the angular service, events replace explicit calls on consent and on intro done --- www/index.js | 1 - www/js/controllers.js | 5 ++--- www/js/onboarding/onboardingHelper.ts | 5 +---- www/js/splash/startprefs.ts | 10 ---------- 4 files changed, 3 insertions(+), 18 deletions(-) diff --git a/www/index.js b/www/index.js index 8bcbe32a6..ca1bbcfbb 100644 --- a/www/index.js +++ b/www/index.js @@ -5,7 +5,6 @@ import 'leaflet/dist/leaflet.css'; import './js/ngApp.js'; import './js/splash/referral.js'; -import './js/splash/storedevicesettings.js'; import './js/splash/localnotify.js'; import './js/splash/remotenotify.js'; import './js/splash/notifScheduler.js'; diff --git a/www/js/controllers.js b/www/js/controllers.js index d645c01d7..4e6be3777 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -4,8 +4,7 @@ import angular from 'angular'; import { addStatError, addStatReading, statKeys } from './plugin/clientStats'; import { getPendingOnboardingState } from './onboarding/onboardingHelper'; -angular.module('emission.controllers', ['emission.splash.storedevicesettings', - 'emission.splash.localnotify', +angular.module('emission.controllers', ['emission.splash.localnotify', 'emission.splash.remotenotify']) .controller('RootCtrl', function($scope) {}) @@ -13,7 +12,7 @@ angular.module('emission.controllers', ['emission.splash.storedevicesettings', .controller('DashCtrl', function($scope) {}) .controller('SplashCtrl', function($scope, $state, $interval, $rootScope, - StoreDeviceSettings, LocalNotify, RemoteNotify) { + LocalNotify, RemoteNotify) { console.log('SplashCtrl invoked'); // alert("attach debugger!"); // PushNotify.startupInit(); diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index 88c5fd934..70ccb4bf2 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -4,7 +4,6 @@ import { storageGet, storageSet } from "../plugin/storage"; import { logDebug } from "../plugin/logger"; import { EVENT_NAMES, publish } from "../customEventHandler"; import { readConsentState, isConsented } from "../splash/startprefs"; -import { getAngularService } from "../angular-react-helper"; export const INTRO_DONE_KEY = 'intro_done'; @@ -75,9 +74,7 @@ export async function markIntroDone() { return storageSet(INTRO_DONE_KEY, currDateTime) .then(() => { //handle "on intro" events - logDebug("intro done, calling registerPush and storeDeviceSettings"); - const StoreSeviceSettings = getAngularService("StoreDeviceSettings"); + logDebug("intro done, publishing event"); publish(EVENT_NAMES.INTRO_DONE_EVENT, currDateTime); - StoreSeviceSettings.storeDeviceSettings(); }); } diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 3c4823af7..bb4054d65 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -41,16 +41,6 @@ export function markConsented() { // publish event publish(EVENT_NAMES.CONSENTED_EVENT, _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"); - //pushnotify uses events now - const StoreSeviceSettings = getAngularService("StoreDeviceSettings"); - StoreSeviceSettings.storeDeviceSettings(); - } - }) .catch((error) => { displayErrorMsg(error, "Error while while wrting consent to storage"); }); From 7b7efc870579e652177597c2b5e520b8e6f4e2fe Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 11:19:08 -0600 Subject: [PATCH 23/43] add initStoreDeviceSettings call this initializes the file, and conducts the subscription to onConsent and onIntroDone events --- www/js/App.tsx | 2 ++ www/js/splash/storeDeviceSettings.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index 15e530e9c..04e049938 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -12,6 +12,7 @@ import { setServerConnSettings } from './config/serverConn'; import AppStatusModal from './control/AppStatusModal'; import usePermissionStatus from './usePermissionStatus'; import { initPushNotify } from './splash/pushNotifySettings'; +import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; const defaultRoutes = (t) => [ { key: 'label', title: t('diary.label-tab'), focusedIcon: 'check-bold', unfocusedIcon: 'check-outline' }, @@ -52,6 +53,7 @@ const App = () => { refreshOnboardingState(); }); initPushNotify(); + initStoreDeviceSettings(); }, [appConfig]); const appContextValue = { diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 0077499e5..e91af6962 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -54,7 +54,7 @@ const onIntroEvent = function (event, data) { storeDeviceSettings(); } -const initStoreDeviceSettings = function () { +export const initStoreDeviceSettings = function () { readConsentState() .then(isConsented) .then(function (consentState) { From 78eb704621ad2096baa2d1643374db3a0a46ad98 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 13:35:02 -0600 Subject: [PATCH 24/43] add more docstrings --- www/js/splash/storeDeviceSettings.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index e91af6962..f4269355e 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -6,6 +6,10 @@ import { displayError, logDebug } from '../plugin/logger'; import { readIntroDone } from '../onboarding/onboardingHelper'; import { subscribe, EVENT_NAMES } 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; @@ -54,6 +58,10 @@ const onIntroEvent = function (event, data) { storeDeviceSettings(); } +/** + * @function initializes store device: subscribes to events + * stores settings if already consented + */ export const initStoreDeviceSettings = function () { readConsentState() .then(isConsented) From 72a76fa16a520664b9f3fc8a66f8c6c238cda660 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 14:24:25 -0600 Subject: [PATCH 25/43] add server comm mock this is needed for the updateUser/getUser in storeDeviceSettings and corresponding tests --- www/__mocks__/cordovaMocks.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index 9b8980c37..99b9cf3b4 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -143,3 +143,22 @@ export const mockBEMDataCollection = () => { 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; +} From 188d478915930d245d2654259a164668d6ba9321 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 14:25:03 -0600 Subject: [PATCH 26/43] start tests needed to await the calls to storeDeviceSettings since it stores a promise needed to clean out the doc in storage --- www/__mocks__/cordovaMocks.ts | 1 + www/__tests__/storeDeviceSettings.test.ts | 65 +++++++++++++++++++++++ www/js/splash/storeDeviceSettings.ts | 20 ++++--- 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 www/__tests__/storeDeviceSettings.test.ts diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index 99b9cf3b4..1c13cc144 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -63,6 +63,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) ); diff --git a/www/__tests__/storeDeviceSettings.test.ts b/www/__tests__/storeDeviceSettings.test.ts new file mode 100644 index 000000000..ceaf93bda --- /dev/null +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -0,0 +1,65 @@ + +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 { mockBEMServerCom, mockBEMUserCache, mockCordova, mockDevice, mockGetAppVersion } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import { EVENT_NAMES, publish, unsubscribe } from '../js/customEventHandler'; + +mockBEMUserCache(); +mockDevice(); +mockCordova(); +mockLogger(); +mockGetAppVersion(); +mockBEMServerCom(); + +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(async () => { + await storageClear({ local: true, native: true }); + teardownDeviceSettings(); +}); + +it('stores device settings when intialized after consent', async () => { + await storageClear({ local: true, native: true }); + await readConsentState(); + 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 store if not subscribed', async () => { + publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + publish(EVENT_NAMES.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(EVENT_NAMES.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' + }) +}); + diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index f4269355e..2c86ace49 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -4,7 +4,7 @@ import { isConsented, readConsentState } from "./startprefs"; import i18next from 'i18next'; import { displayError, logDebug } from '../plugin/logger'; import { readIntroDone } from '../onboarding/onboardingHelper'; -import { subscribe, EVENT_NAMES } from '../customEventHandler'; +import { subscribe, EVENT_NAMES, unsubscribe } from '../customEventHandler'; /** * @function Gathers information about the user's device and stores it @@ -40,10 +40,10 @@ const storeDeviceSettings = function () { console.log("got consented event " + JSON.stringify(event['name']) + " with data " + JSON.stringify(data)); readIntroDone() - .then((isIntroDone) => { + .then(async (isIntroDone) => { if (isIntroDone) { logDebug("intro is done -> reconsent situation, we already have a token -> store device settings"); - storeDeviceSettings(); + await storeDeviceSettings(); } }); } @@ -53,9 +53,9 @@ const storeDeviceSettings = function () { * @param event that called this function * @param data from the event */ -const onIntroEvent = function (event, data) { +const onIntroEvent = async function (event, data) { logDebug("intro is done -> original consent situation, we should have a token by now -> store device settings"); - storeDeviceSettings(); + await storeDeviceSettings(); } /** @@ -65,9 +65,10 @@ const onIntroEvent = function (event, data) { export const initStoreDeviceSettings = function () { readConsentState() .then(isConsented) - .then(function (consentState) { + .then(async function (consentState) { + console.log("found consent", consentState); if (consentState == true) { - storeDeviceSettings(); + await storeDeviceSettings(); } else { logDebug("no consent yet, waiting to store device settings in profile"); } @@ -76,3 +77,8 @@ export const initStoreDeviceSettings = function () { }); logDebug("storedevicesettings startup done"); } + +export const teardownDeviceSettings = function() { + unsubscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); + unsubscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); +} From 5e0453693de47d302ba94e223aa17d6ea589938b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 14:39:55 -0600 Subject: [PATCH 27/43] re-prettify merge conflicted files when I was resolving merge conflicts, keeping some of my changes meant that I kept the old formatting. I enabled prettier locally and re-saved those files, resolving this formatting --- www/__mocks__/cordovaMocks.ts | 30 +++++++++++++-------------- www/js/controllers.js | 14 ++----------- www/js/onboarding/onboardingHelper.ts | 14 ++++++------- www/js/splash/startprefs.ts | 27 ++++++++++++------------ 4 files changed, 37 insertions(+), 48 deletions(-) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index f27dc1c1b..a031c8444 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -128,42 +128,42 @@ export const mockBEMDataCollection = () => { markConsented: (consentDoc) => { setTimeout(() => { _storage['config/consent'] = consentDoc; - }, 100) + }, 100); }, getConfig: () => { return new Promise((rs, rj) => { setTimeout(() => { - rs({ 'ios_use_remote_push_for_sync': true }); - }, 100) + rs({ ios_use_remote_push_for_sync: true }); + }, 100); }); }, handleSilentPush: () => { return new Promise((rs, rj) => setTimeout(() => { rs(); - }, 100) + }, 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; + console.log('set in mock', updateDoc); + _storage['user_data'] = updateDoc; rs(); - }, 100) + }, 100); }, getUserPersonalData: (actionString, rs, rj) => { setTimeout(() => { - rs( _storage["user_data"] ); - }, 100) - } - } + rs(_storage['user_data']); + }, 100); + }, + }; window['cordova'].plugins.BEMServerComm = mockBEMServerCom; -} +}; diff --git a/www/js/controllers.js b/www/js/controllers.js index a973ad16d..e502dda2e 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -5,10 +5,7 @@ import { addStatError, addStatReading, statKeys } from './plugin/clientStats'; import { getPendingOnboardingState } from './onboarding/onboardingHelper'; angular - .module('emission.controllers', [ - 'emission.splash.localnotify', - 'emission.splash.remotenotify', - ]) + .module('emission.controllers', ['emission.splash.localnotify', 'emission.splash.remotenotify']) .controller('RootCtrl', function ($scope) {}) @@ -16,14 +13,7 @@ angular .controller( 'SplashCtrl', - function ( - $scope, - $state, - $interval, - $rootScope, - LocalNotify, - RemoteNotify, - ) { + function ($scope, $state, $interval, $rootScope, LocalNotify, RemoteNotify) { console.log('SplashCtrl invoked'); // alert("attach debugger!"); // PushNotify.startupInit(); diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index adaf58ad8..fb65c1648 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -1,9 +1,9 @@ -import { DateTime } from "luxon"; -import { getConfig, resetDataAndRefresh } from "../config/dynamicConfig"; -import { storageGet, storageSet } from "../plugin/storage"; -import { logDebug } from "../plugin/logger"; -import { EVENT_NAMES, publish } from "../customEventHandler"; -import { readConsentState, isConsented } from "../splash/startprefs"; +import { DateTime } from 'luxon'; +import { getConfig, resetDataAndRefresh } from '../config/dynamicConfig'; +import { storageGet, storageSet } from '../plugin/storage'; +import { logDebug } from '../plugin/logger'; +import { EVENT_NAMES, publish } from '../customEventHandler'; +import { readConsentState, isConsented } from '../splash/startprefs'; export const INTRO_DONE_KEY = 'intro_done'; @@ -90,7 +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, publishing event"); + logDebug('intro done, publishing event'); publish(EVENT_NAMES.INTRO_DONE_EVENT, currDateTime); }); } diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index c896a2b2e..22ff4acaa 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -1,6 +1,6 @@ import { storageGet, storageSet } from '../plugin/storage'; import { logInfo, logDebug, displayErrorMsg } from '../plugin/logger'; -import { EVENT_NAMES, publish } from "../customEventHandler"; +import { EVENT_NAMES, publish } from '../customEventHandler'; // data collection consented protocol: string, represents the date on // which the consented protocol was approved by the IRB @@ -31,17 +31,16 @@ export function markConsented() { .then(writeConsentToNative) .then(function (response) { // mark in local storage - storageSet(DATA_COLLECTION_CONSENTED_PROTOCOL, - _req_consent); + storageSet(DATA_COLLECTION_CONSENTED_PROTOCOL, _req_consent); // mark in local variable as well _curr_consented = { ..._req_consent }; // publish event publish(EVENT_NAMES.CONSENTED_EVENT, _req_consent); }) .catch((error) => { - displayErrorMsg(error, "Error while while wrting consent to storage"); + displayErrorMsg(error, 'Error while while wrting consent to storage'); }); -}; +} /** * @function checking for consent locally @@ -93,15 +92,15 @@ export function readConsentState() { */ //used in ProfileSettings export function getConsentDocument() { - return window['cordova'].plugins.BEMUserCache.getDocument('config/consent', false).then( - function (resultDoc) { - if (window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc)) { - return null; - } else { - return resultDoc; - } - }, - ); + return window['cordova'].plugins.BEMUserCache.getDocument('config/consent', false).then(function ( + resultDoc, + ) { + if (window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc)) { + return null; + } else { + return resultDoc; + } + }); } /** From 4393362ae2b1e2174c927ca27f13d740b092c24e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 2 Nov 2023 16:57:15 -0600 Subject: [PATCH 28/43] prettify recent changes a lot of the changes on my branch had not passed through the prettier workflow yet, this should hopefully be the last prettifying change on this branch, as I will keep up with the formatting as I edit from here forward --- www/__mocks__/pushNotificationMocks.ts | 18 +- www/__tests__/customEventHandler.test.ts | 24 +-- www/__tests__/pushNotifySettings.test.ts | 90 ++++++---- www/__tests__/storeDeviceSettings.test.ts | 54 +++--- www/js/customEventHandler.ts | 24 +-- www/js/splash/pushNotifySettings.ts | 194 ++++++++++++---------- www/js/splash/startprefs.ts | 18 +- www/js/splash/storeDeviceSettings.ts | 81 ++++----- 8 files changed, 278 insertions(+), 225 deletions(-) diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts index 47b7bd940..0e98fa064 100644 --- a/www/__mocks__/pushNotificationMocks.ts +++ b/www/__mocks__/pushNotificationMocks.ts @@ -1,5 +1,5 @@ let notifSettings; -let onList : any = {}; +let onList: any = {}; let called = null; export const mockPushNotification = () => { @@ -12,27 +12,27 @@ export const mockPushNotification = () => { }, 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() { +export const getCalled = function () { return called; -} +}; -export const fakeEvent = function (eventName : string) { +export const fakeEvent = function (eventName: string) { //fake the event by executing whatever we have stored for it onList[eventName](); -} \ No newline at end of file +}; diff --git a/www/__tests__/customEventHandler.test.ts b/www/__tests__/customEventHandler.test.ts index d45dba01b..7a5c09539 100644 --- a/www/__tests__/customEventHandler.test.ts +++ b/www/__tests__/customEventHandler.test.ts @@ -1,24 +1,24 @@ -import { publish, subscribe, unsubscribe } from "../js/customEventHandler"; -import { mockLogger } from "../__mocks__/globalMocks"; +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"); + subscribe('test', listener); + publish('test', 'test data'); expect(listener).toHaveBeenCalledWith( expect.objectContaining({ - type: "test", - detail: "test data", - }) + type: 'test', + detail: 'test data', + }), ); -}) +}); it('can unsubscribe', () => { const listener = jest.fn(); - subscribe("test", listener); - unsubscribe("test", listener); - publish("test", "test data"); + subscribe('test', listener); + unsubscribe('test', listener); + publish('test', 'test data'); expect(listener).not.toHaveBeenCalled(); -}) \ No newline at end of file +}); diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index 58c6bb48e..bf2ce4343 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -5,7 +5,12 @@ 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'; +import { + clearNotifMock, + getOnList, + mockPushNotification, + getCalled, +} from '../__mocks__/pushNotificationMocks'; mockCordova(); mockLogger(); @@ -13,14 +18,23 @@ 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; +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(); @@ -28,42 +42,48 @@ afterEach(() => { it('intro done does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); - publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); expect(getOnList()).toStrictEqual({}); -}) +}); it('intro done initializes the push notifications', () => { expect(getOnList()).toStrictEqual({}); initPushNotify(); - publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); - expect(getOnList()).toStrictEqual(expect.objectContaining({ - notification: expect.any(Function), - error: expect.any(Function), - registration: expect.any(Function) - })); -}) + publish(EVENT_NAMES.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(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notId' : 3}}}); + publish(EVENT_NAMES.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(EVENT_NAMES.INTRO_DONE_EVENT, "intro done"); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {additionalData: {'content-available': 1, 'payload' : {'notId' : 3}}}); + publish(EVENT_NAMES.INTRO_DONE_EVENT, 'intro done'); + publish(EVENT_NAMES.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(EVENT_NAMES.CONSENTED_EVENT, "test data"); + publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); expect(getOnList()).toStrictEqual({}); -}) +}); it('consent event registers if intro done', async () => { //make sure the mock is clear @@ -79,19 +99,21 @@ it('consent event registers if intro done', async () => { expect(introDone).toBeTruthy(); //publish consent event and check results - publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + publish(EVENT_NAMES.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) - })); -}) + 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(EVENT_NAMES.CONSENTED_EVENT, "test data"); + publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); expect(getOnList()).toStrictEqual({}); //nothing, intro not done -}) \ No newline at end of file +}); diff --git a/www/__tests__/storeDeviceSettings.test.ts b/www/__tests__/storeDeviceSettings.test.ts index ceaf93bda..9026e7621 100644 --- a/www/__tests__/storeDeviceSettings.test.ts +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -1,9 +1,14 @@ - 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 { mockBEMServerCom, mockBEMUserCache, mockCordova, mockDevice, mockGetAppVersion } from '../__mocks__/cordovaMocks'; +import { + mockBEMServerCom, + mockBEMUserCache, + mockCordova, + mockDevice, + mockGetAppVersion, +} from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import { EVENT_NAMES, publish, unsubscribe } from '../js/customEventHandler'; @@ -14,14 +19,23 @@ mockLogger(); mockGetAppVersion(); mockBEMServerCom(); -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; +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(async () => { await storageClear({ local: true, native: true }); @@ -37,29 +51,27 @@ it('stores device settings when intialized after consent', async () => { 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' - }) + client_os_version: '14.0.0', + client_app_version: '1.2.3', + }); }); it('does not store if not subscribed', async () => { - publish(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); - publish(EVENT_NAMES.CONSENTED_EVENT, "test data"); + publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + publish(EVENT_NAMES.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(EVENT_NAMES.INTRO_DONE_EVENT, "test data"); + publish(EVENT_NAMES.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' - }) + client_os_version: '14.0.0', + client_app_version: '1.2.3', + }); }); - diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts index f6606cf8f..600847223 100644 --- a/www/js/customEventHandler.ts +++ b/www/js/customEventHandler.ts @@ -3,11 +3,11 @@ * 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/ */ @@ -18,27 +18,27 @@ import { logDebug } from './plugin/logger'; */ export const EVENT_NAMES = { CLOUD_NOTIFICATION_EVENT: 'cloud:push:notification', - CONSENTED_EVENT: "data_collection_consented", - INTRO_DONE_EVENT: "intro_done", -} + 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 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"); + logDebug('adding ' + eventName + ' listener'); document.addEventListener(eventName, listener); } /** * @function stops listening to an event - * @param eventName {string} the name of the 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"); +export function unsubscribe(eventName: string, listener) { + logDebug('removing ' + eventName + ' listener'); document.removeEventListener(eventName, listener); } @@ -49,7 +49,7 @@ export function unsubscribe(eventName: string, listener){ * @param data any additional data to be added to event */ export function publish(eventName: string, data) { - logDebug("publishing " + eventName + " with data " + JSON.stringify(data)); + logDebug('publishing ' + eventName + ' with data ' + JSON.stringify(data)); const event = new CustomEvent(eventName, { detail: data }); document.dispatchEvent(event); -} \ No newline at end of file +} diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index b252508db..eefcbcdf8 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -28,91 +28,101 @@ let push = null; */ const startupInit = function () { push = window['PushNotification'].init({ - "ios": { - "badge": true, - "sound": true, - "vibration": true, - "clearBadge": true + ios: { + badge: true, + sound: true, + vibration: true, + clearBadge: true, + }, + android: { + iconColor: '#008acf', + icon: 'ic_mood_question', + clearNotifications: 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 (angular.isDefined(data) && - angular.isDefined(data.additionalData)) { + 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") { + 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"); + console.log('additionalData is already an object, no need to parse it'); } } else { - logDebug("No additional data defined, nothing to parse"); + logDebug('No additional data defined, nothing to parse'); } } publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, data); }); -} +}; /** * @function registers notifications and handles result - * @returns Promise for initialization logic, - * resolves on registration with token + * @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); + push.on('registration', function (data) { + console.log('Got registration ' + data); resolve({ token: data.registrationId, - type: data.registrationType + type: data.registrationType, }); }); - push.on("error", function (error) { - console.log("Got push error " + error); + push.on('error', function (error) { + console.log('Got push error ' + error); reject(error); }); - console.log("push notify = " + push); + 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) { - // alert("Token = "+JSON.stringify(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; + registerPromise() + .then(function (t) { + // alert("Token = "+JSON.stringify(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) { + // alert("Finished saving token = "+JSON.stringify(t.token)); + logDebug('Finished saving token = ' + JSON.stringify(t.token)); + }) + .catch(function (error) { + displayError(error, 'Error in registering push notifications'); }); - }).then(function (t) { - // alert("Finished saving token = "+JSON.stringify(t.token)); - logDebug("Finished saving token = " + JSON.stringify(t.token)); - }).catch(function (error) { - displayError(error, "Error in registering push notifications"); - }); -} +}; /** * @function handles silent push notifications @@ -121,36 +131,36 @@ const registerPush = function () { * @returns early if platform is not ios */ const redirectSilentPush = function (event, data) { - logDebug("Found silent push notification, for platform " + window['cordova'].platformId); + 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"); + 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"); + logDebug('Platform is ios, calling handleSilentPush on DataCollection'); var notId = data.additionalData.payload.notId; var finishErrFn = function (error) { - logDebug("in push.finish, error = " + 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); - }; - }) + 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"); + push.finish(function () {}, finishErrFn, notId); + displayError(error, 'Error while redirecting silent push'); }); -} +}; /** * @function shows debug notifications if simulating user interaction @@ -161,14 +171,14 @@ var showDebugLocalNotification = function (message) { if (config.simulate_user_interaction) { window['cordova'].plugins.notification.local.schedule({ id: 1, - title: "Debug javascript notification", + title: 'Debug javascript notification', text: message, actions: [], - category: 'SIGN_IN_TO_CLASS' + category: 'SIGN_IN_TO_CLASS', }); } }); -} +}; /** * @function handles pushNotification intitially @@ -176,11 +186,11 @@ var showDebugLocalNotification = function (message) { * @param data from the notification */ const onCloudEvent = function (event, data) { - logDebug("data = " + JSON.stringify(data)); - if (data.additionalData["content-available"] == 1) { + logDebug('data = ' + JSON.stringify(data)); + if (data.additionalData['content-available'] == 1) { redirectSilentPush(event, data); - }; // else no need to call finish -} + } // else no need to call finish +}; /** * @function registers push on reconsent @@ -188,16 +198,16 @@ const onCloudEvent = function (event, data) { * @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(); - } - }); -} + 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 @@ -205,12 +215,14 @@ const onConsentEvent = function (event, data) { * @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"); + console.log( + 'intro is done -> original consent situation, we should have a token by now -> register', + ); registerPush(); -} +}; /** - * startup code - + * startup code - * @function registers push if consented, subscribes event listeners for local handline */ export const initPushNotify = function () { @@ -218,10 +230,10 @@ export const initPushNotify = function () { .then(isConsented) .then(function (consentState) { if (consentState == true) { - logDebug("already consented, signing up for remote push"); + logDebug('already consented, signing up for remote push'); registerPush(); } else { - logDebug("no consent yet, waiting to sign up for remote push"); + logDebug('no consent yet, waiting to sign up for remote push'); } }); @@ -229,5 +241,5 @@ export const initPushNotify = function () { subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); - logDebug("pushnotify startup done"); -} + logDebug('pushnotify startup done'); +}; diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 22ff4acaa..7c75bf3b3 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -92,15 +92,15 @@ export function readConsentState() { */ //used in ProfileSettings export function getConsentDocument() { - return window['cordova'].plugins.BEMUserCache.getDocument('config/consent', false).then(function ( - resultDoc, - ) { - if (window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc)) { - return null; - } else { - return resultDoc; - } - }); + return window['cordova'].plugins.BEMUserCache.getDocument('config/consent', false).then( + function (resultDoc) { + if (window['cordova'].plugins.BEMUserCache.isEmptyDoc(resultDoc)) { + return null; + } else { + return resultDoc; + } + }, + ); } /** diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index 2c86ace49..f54ce8d53 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -1,6 +1,5 @@ - import { updateUser } from '../commHelper'; -import { isConsented, readConsentState } from "./startprefs"; +import { isConsented, readConsentState } from './startprefs'; import i18next from 'i18next'; import { displayError, logDebug } from '../plugin/logger'; import { readIntroDone } from '../onboarding/onboardingHelper'; @@ -14,39 +13,45 @@ 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"); - }); -} + 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 * @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(async (isIntroDone) => { - if (isIntroDone) { - logDebug("intro is done -> reconsent situation, we already have a token -> store device settings"); - await storeDeviceSettings(); - } - }); -} +const onConsentEvent = function (event, data) { + console.log( + 'got consented event ' + JSON.stringify(event['name']) + ' with data ' + JSON.stringify(data), + ); + 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 @@ -54,9 +59,11 @@ const storeDeviceSettings = function () { * @param data from the event */ const onIntroEvent = async function (event, data) { - logDebug("intro is done -> original consent situation, we should have a token by now -> store device settings"); + 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 @@ -66,19 +73,19 @@ export const initStoreDeviceSettings = function () { readConsentState() .then(isConsented) .then(async function (consentState) { - console.log("found consent", consentState); + console.log('found consent', consentState); if (consentState == true) { await storeDeviceSettings(); } else { - logDebug("no consent yet, waiting to store device settings in profile"); + logDebug('no consent yet, waiting to store device settings in profile'); } subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); }); - logDebug("storedevicesettings startup done"); -} + logDebug('storedevicesettings startup done'); +}; -export const teardownDeviceSettings = function() { +export const teardownDeviceSettings = function () { unsubscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); unsubscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); -} +}; From bc5c5896861dad660e2c70f2c3b43edc421ef56b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 10:58:47 -0600 Subject: [PATCH 29/43] update tests so they pass I found two problems to fix in my testing - first was that I was not properly removing the event listeners and learned more: https://macarthur.me/posts/options-for-removing-event-listeners Second was that I was missing a mock, which meant some of the storage operations were failing due to that function not exisiting --- www/__tests__/storeDeviceSettings.test.ts | 22 +++++++++++++++++----- www/js/splash/storeDeviceSettings.ts | 19 ++++++++++--------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/www/__tests__/storeDeviceSettings.test.ts b/www/__tests__/storeDeviceSettings.test.ts index 9026e7621..1676f6a86 100644 --- a/www/__tests__/storeDeviceSettings.test.ts +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -3,6 +3,7 @@ import { storageClear } from '../js/plugin/storage'; import { getUser } from '../js/commHelper'; import { initStoreDeviceSettings, teardownDeviceSettings } from '../js/splash/storeDeviceSettings'; import { + mockBEMDataCollection, mockBEMServerCom, mockBEMUserCache, mockCordova, @@ -10,7 +11,7 @@ import { mockGetAppVersion, } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; -import { EVENT_NAMES, publish, unsubscribe } from '../js/customEventHandler'; +import { EVENT_NAMES, publish } from '../js/customEventHandler'; mockBEMUserCache(); mockDevice(); @@ -18,6 +19,7 @@ mockCordova(); mockLogger(); mockGetAppVersion(); mockBEMServerCom(); +mockBEMDataCollection(); global.fetch = (url: string) => new Promise((rs, rj) => { @@ -37,15 +39,16 @@ global.fetch = (url: string) => ); }) as any; -afterEach(async () => { - await storageClear({ local: true, native: true }); +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 storageClear({ local: true, native: true }); await readConsentState(); - await markConsented(); + let marked = await markConsented(); await new Promise((r) => setTimeout(r, 500)); initStoreDeviceSettings(); await new Promise((r) => setTimeout(r, 500)); @@ -56,6 +59,15 @@ it('stores device settings when intialized after consent', async () => { }); }); +it('verifies my subscrition clearing', async () => { + initStoreDeviceSettings(); + await new Promise((r) => setTimeout(r, 500)); + teardownDeviceSettings(); + publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + let user = await getUser(); + expect(user).toBeUndefined(); +}); + it('does not store if not subscribed', async () => { publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); diff --git a/www/js/splash/storeDeviceSettings.ts b/www/js/splash/storeDeviceSettings.ts index f54ce8d53..d9604b3b5 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -37,11 +37,13 @@ const storeDeviceSettings = function () { /** * @function stores device settings on reconsent * @param event that called this function - * @param data data from the conesnt event */ -const onConsentEvent = function (event, data) { +const onConsentEvent = (event) => { console.log( - 'got consented event ' + JSON.stringify(event['name']) + ' with data ' + JSON.stringify(data), + 'got consented event ' + + JSON.stringify(event['name']) + + ' with data ' + + JSON.stringify(event.detail), ); readIntroDone().then(async (isIntroDone) => { if (isIntroDone) { @@ -56,9 +58,8 @@ const onConsentEvent = function (event, data) { /** * @function stores device settings after intro received * @param event that called this function - * @param data from the event */ -const onIntroEvent = async function (event, data) { +const onIntroEvent = async (event) => { logDebug( 'intro is done -> original consent situation, we should have a token by now -> store device settings', ); @@ -79,13 +80,13 @@ export const initStoreDeviceSettings = function () { } else { logDebug('no consent yet, waiting to store device settings in profile'); } - subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); - subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); + subscribe(EVENT_NAMES.CONSENTED_EVENT, onConsentEvent); + subscribe(EVENT_NAMES.INTRO_DONE_EVENT, onIntroEvent); }); logDebug('storedevicesettings startup done'); }; export const teardownDeviceSettings = function () { - unsubscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); - unsubscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); + unsubscribe(EVENT_NAMES.CONSENTED_EVENT, onConsentEvent); + unsubscribe(EVENT_NAMES.INTRO_DONE_EVENT, onIntroEvent); }; From 1aceb94ec79e6c0cd824a1fce83508f1496402b2 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 11:11:06 -0600 Subject: [PATCH 30/43] complete tests now testing for settings stored on initialization, on consent, and on intro done also testing for not done if not consented on initialization, and if intro not done on consent --- www/__tests__/storeDeviceSettings.test.ts | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/www/__tests__/storeDeviceSettings.test.ts b/www/__tests__/storeDeviceSettings.test.ts index 1676f6a86..ae046216a 100644 --- a/www/__tests__/storeDeviceSettings.test.ts +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -12,6 +12,7 @@ import { } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { markIntroDone } from '../js/onboarding/onboardingHelper'; mockBEMUserCache(); mockDevice(); @@ -59,6 +60,13 @@ it('stores device settings when intialized after consent', async () => { }); }); +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)); @@ -87,3 +95,26 @@ it('stores device settings after intro done', async () => { 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(EVENT_NAMES.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(EVENT_NAMES.CONSENTED_EVENT, 'test data'); + await new Promise((r) => setTimeout(r, 500)); //time to carry out event handling + let user = await getUser(); + expect(user).toBeUndefined(); +}); From 9581ca2e6abc71ac59c422c36ad5d33a8c0cef8e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 11:44:02 -0600 Subject: [PATCH 31/43] remove unused mock code this is leftover code and I never used it in testing, removing --- www/__mocks__/pushNotificationMocks.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/__mocks__/pushNotificationMocks.ts b/www/__mocks__/pushNotificationMocks.ts index 0e98fa064..53a81e16c 100644 --- a/www/__mocks__/pushNotificationMocks.ts +++ b/www/__mocks__/pushNotificationMocks.ts @@ -31,8 +31,3 @@ export const getOnList = function () { export const getCalled = function () { return called; }; - -export const fakeEvent = function (eventName: string) { - //fake the event by executing whatever we have stored for it - onList[eventName](); -}; From 9aa89d588ec6832369ff8d487dd5e95d53fd7b8a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 11:44:46 -0600 Subject: [PATCH 32/43] rename remotenotify file hoping to preserve some commit history here remotenotify uses the cloud notif event, so rewritting alongside the rest of the event-driven code --- www/js/splash/{remotenotify.js => remoteNotifyHandler.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/js/splash/{remotenotify.js => remoteNotifyHandler.ts} (100%) diff --git a/www/js/splash/remotenotify.js b/www/js/splash/remoteNotifyHandler.ts similarity index 100% rename from www/js/splash/remotenotify.js rename to www/js/splash/remoteNotifyHandler.ts From 16c87f0dd789e55e2d03f482cfefe1884effa2fe Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 12:00:29 -0600 Subject: [PATCH 33/43] typescript migration migrate the file into typescript, including adding the event handling remove references (all unused) add init call with others in App.tsx --- www/index.js | 1 - www/js/App.tsx | 2 + www/js/controllers.js | 8 +- www/js/splash/remoteNotifyHandler.ts | 120 ++++++++++++--------------- 4 files changed, 60 insertions(+), 71 deletions(-) diff --git a/www/index.js b/www/index.js index ca1bbcfbb..3aa5be4ed 100644 --- a/www/index.js +++ b/www/index.js @@ -6,7 +6,6 @@ import 'leaflet/dist/leaflet.css'; import './js/ngApp.js'; import './js/splash/referral.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 dbbef406c..e6b09b3ee 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -17,6 +17,7 @@ import AppStatusModal from './control/AppStatusModal'; import usePermissionStatus from './usePermissionStatus'; import { initPushNotify } from './splash/pushNotifySettings'; import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; +import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; const defaultRoutes = (t) => [ { @@ -74,6 +75,7 @@ const App = () => { }); initPushNotify(); initStoreDeviceSettings(); + initRemoteNotifyHandler(); }, [appConfig]); const appContextValue = { diff --git a/www/js/controllers.js b/www/js/controllers.js index e502dda2e..ee29af103 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -5,18 +5,16 @@ import { addStatError, addStatReading, statKeys } from './plugin/clientStats'; import { getPendingOnboardingState } from './onboarding/onboardingHelper'; angular - .module('emission.controllers', ['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, LocalNotify, RemoteNotify) { - console.log('SplashCtrl invoked'); // alert("attach debugger!"); // PushNotify.startupInit(); + .controller('SplashCtrl', function ($scope, $state, $interval, $rootScope, LocalNotify) { + console.log('SplashCtrl invoked'); $rootScope.$on( '$stateChangeSuccess', diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index a59fdf376..0be6ca201 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -16,76 +16,66 @@ 'use strict'; import angular from 'angular'; +import { EVENT_NAMES, subscribe } from '../customEventHandler'; import { addStatEvent, statKeys } from '../plugin/clientStats'; +import { displayErrorMsg, logDebug } from '../plugin/logger'; -angular - .module('emission.splash.remotenotify', ['emission.plugin.logger']) +const options = 'location=yes,clearcache=no,toolbar=yes,hideurlbar=yes'; - .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); - }; +const launchWebpage = function (url) { + // THIS LINE FOR inAppBrowser + let iab = window['cordova'].InAppBrowser.open(url, '_blank', options); +}; - remoteNotify.launchPopup = function (title, text) { - // THIS LINE FOR inAppBrowser - let alertPopup = $ionicPopup.alert({ - title: title, - template: text, - }); - }; +const launchPopup = function (title, text) { + // THIS LINE FOR inAppBrowser + displayErrorMsg(text, title); +}; - 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; +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 ( + 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://') + ) { + 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 ( + angular.isDefined(popup_spec) && + angular.isDefined(popup_spec.title) && + angular.isDefined(popup_spec.text) + ) { + launchPopup(popup_spec.title, popup_spec.text); + } else { + displayErrorMsg(JSON.stringify(popup_spec), 'popup was not specified correctly. spec is '); + } + } + } +}; + +export const initRemoteNotifyHandler = function () { + subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); +}; From 68e0701dad82dcc796cbadc7d7b5b0321f1a9843 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 12:03:38 -0600 Subject: [PATCH 34/43] formatting changes to controller.js prettier wanted to make a bunch of changes to code that I didn't edit, so putting it in a dedicated commit --- www/js/controllers.js | 111 +++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/www/js/controllers.js b/www/js/controllers.js index ee29af103..16f962c8d 100644 --- a/www/js/controllers.js +++ b/www/js/controllers.js @@ -11,67 +11,66 @@ angular .controller('DashCtrl', function ($scope) {}) - // 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 From 84064bb79d7830f870275d9a5668668f11006080 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 12:10:10 -0600 Subject: [PATCH 35/43] remove angular.isDefined calls since our eventual goal is to remove angular, removing these calls since it will evaluate false if undefined anyways --- www/js/splash/pushNotifySettings.ts | 10 +++------- www/js/splash/remoteNotifyHandler.ts | 19 +++++-------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index eefcbcdf8..38f3a4931 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -13,7 +13,6 @@ * notification handling gets more complex, we should consider decoupling it as well. */ -import angular from 'angular'; import { updateUser } from '../commHelper'; import { logDebug, displayError } from '../plugin/logger'; import { publish, subscribe, EVENT_NAMES } from '../customEventHandler'; @@ -43,14 +42,11 @@ const startupInit = function () { push.on('notification', function (data) { if (window['cordova'].platformId == 'ios') { // Parse the iOS values that are returned as strings - if (angular.isDefined(data) && angular.isDefined(data.additionalData)) { - if (angular.isDefined(data.additionalData.payload)) { + if (data && data.additionalData) { + if (data.additionalData.payload) { data.additionalData.payload = JSON.parse(data.additionalData.payload); } - if ( - angular.isDefined(data.additionalData.data) && - typeof data.additionalData.data == 'string' - ) { + 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'); diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 0be6ca201..8252d4169 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -15,7 +15,6 @@ */ 'use strict'; -import angular from 'angular'; import { EVENT_NAMES, subscribe } from '../customEventHandler'; import { addStatEvent, statKeys } from '../plugin/clientStats'; import { displayErrorMsg, logDebug } from '../plugin/logger'; @@ -42,17 +41,13 @@ const onCloudNotifEvent = (event) => { }); logDebug('data = ' + JSON.stringify(data)); if ( - angular.isDefined(data.additionalData) && - angular.isDefined(data.additionalData.payload) && - angular.isDefined(data.additionalData.payload.alert_type) + 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 ( - angular.isDefined(webpage_spec) && - angular.isDefined(webpage_spec.url) && - webpage_spec.url.startsWith('https://') - ) { + if (webpage_spec && webpage_spec.url && webpage_spec.url.startsWith('https://')) { launchWebpage(webpage_spec.url); } else { displayErrorMsg( @@ -63,11 +58,7 @@ const onCloudNotifEvent = (event) => { } 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) - ) { + if (popup_spec && popup_spec.title && popup_spec.text) { launchPopup(popup_spec.title, popup_spec.text); } else { displayErrorMsg(JSON.stringify(popup_spec), 'popup was not specified correctly. spec is '); From b58ee5ca1f394e5f278893a8e9e62ee06a9d5d69 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 13:26:36 -0600 Subject: [PATCH 36/43] clean up comments and docstrings --- www/js/splash/remoteNotifyHandler.ts | 30 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 8252d4169..54a6b32d8 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -1,6 +1,3 @@ -//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 @@ -13,8 +10,6 @@ * 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 { EVENT_NAMES, subscribe } from '../customEventHandler'; import { addStatEvent, statKeys } from '../plugin/clientStats'; import { displayErrorMsg, logDebug } from '../plugin/logger'; @@ -22,18 +17,35 @@ import { displayErrorMsg, logDebug } from '../plugin/logger'; const options = 'location=yes,clearcache=no,toolbar=yes,hideurlbar=yes'; /* - TODO: Potentially unify with the survey URL loading - */ +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); }; +/* +TODO: replace popup with something with better UI +*/ + +/** + * @function launches popup + * @param title string text for popup title + * @param text string text for popup bode + */ const launchPopup = function (title, text) { // THIS LINE FOR inAppBrowser displayErrorMsg(text, title); }; +/** + * @callback for cloud notification event + * @param event that triggered this call + */ const onCloudNotifEvent = (event) => { const data = event.detail; addStatEvent(statKeys.NOTIFICATION_OPEN).then(() => { @@ -67,6 +79,10 @@ const onCloudNotifEvent = (event) => { } }; +/** + * @function initializes the remote notification handling + * subscribes to cloud notification event + */ export const initRemoteNotifyHandler = function () { subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); }; From 87de6c208b39e8be27d6055de993d56870ff28d3 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 14:08:20 -0600 Subject: [PATCH 37/43] first two tests function checks for no action if not subscribed and action if subscribed --- www/__tests__/remoteNotifyHandler.test.ts | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 www/__tests__/remoteNotifyHandler.test.ts diff --git a/www/__tests__/remoteNotifyHandler.test.ts b/www/__tests__/remoteNotifyHandler.test.ts new file mode 100644 index 000000000..c6d7feab6 --- /dev/null +++ b/www/__tests__/remoteNotifyHandler.test.ts @@ -0,0 +1,33 @@ +import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { initRemoteNotifyHandler } from '../js/splash/remoteNotifyHandler'; +import { mockBEMUserCache, mockDevice, mockGetAppVersion } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; + +mockLogger(); +mockDevice(); +mockBEMUserCache(); +mockGetAppVersion(); + +const db = window['cordova']?.plugins?.BEMUserCache; + +it('does not adds a statEvent if not subscribed', async () => { + publish(EVENT_NAMES.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(EVENT_NAMES.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', + }); +}); + From dcf35facfbaf595867659336286fb44e4ff6e9c4 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 3 Nov 2023 15:31:15 -0600 Subject: [PATCH 38/43] complete writing testing adding more tests - leveraged mocks to ensure that calls to open url or create popup were happening at the appropriate times --- www/__mocks__/cordovaMocks.ts | 19 +++++++++ www/__mocks__/globalMocks.ts | 16 ++++++++ www/__tests__/remoteNotifyHandler.test.ts | 47 ++++++++++++++++++++++- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index a031c8444..92ac23a6b 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -167,3 +167,22 @@ export const mockBEMServerCom = () => { }; 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/__tests__/remoteNotifyHandler.test.ts b/www/__tests__/remoteNotifyHandler.test.ts index c6d7feab6..1d0220da5 100644 --- a/www/__tests__/remoteNotifyHandler.test.ts +++ b/www/__tests__/remoteNotifyHandler.test.ts @@ -1,15 +1,29 @@ import { EVENT_NAMES, publish } from '../js/customEventHandler'; import { initRemoteNotifyHandler } from '../js/splash/remoteNotifyHandler'; -import { mockBEMUserCache, mockDevice, mockGetAppVersion } from '../__mocks__/cordovaMocks'; -import { mockLogger } from '../__mocks__/globalMocks'; +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(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, 'test data'); const storedMessages = await db.getAllMessages('stats/client_nav_event', false); @@ -31,3 +45,32 @@ it('adds a statEvent if subscribed', async () => { }); }); +it('handles the url if subscribed', () => { + initRemoteNotifyHandler(); + publish(EVENT_NAMES.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(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, { + additionalData: { + payload: { + alert_type: 'popup', + spec: { title: 'Hello', text: 'World' }, + }, + }, + }); + expect(getAlerts()).toEqual(expect.arrayContaining(['━━━━\nHello\n━━━━\nWorld'])); +}); + +it('does nothing if subscribed and no data', () => { + initRemoteNotifyHandler(); + publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {}); + expect(getURL()).toEqual(''); + expect(getAlerts()).toEqual([]); +}); From 9b1665a972a8b1b595bf2c2804bfbef22545b402 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 6 Nov 2023 11:59:45 -0700 Subject: [PATCH 39/43] simplify event name storage --- www/__tests__/pushNotifySettings.test.ts | 18 +++++++++--------- www/__tests__/remoteNotifyHandler.test.ts | 12 ++++++------ www/__tests__/storeDeviceSettings.test.ts | 14 +++++++------- www/js/customEventHandler.ts | 2 +- www/js/onboarding/onboardingHelper.ts | 4 ++-- www/js/splash/pushNotifySettings.ts | 10 +++++----- www/js/splash/remoteNotifyHandler.ts | 4 ++-- www/js/splash/startprefs.ts | 4 ++-- www/js/splash/storeDeviceSettings.ts | 10 +++++----- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/www/__tests__/pushNotifySettings.test.ts b/www/__tests__/pushNotifySettings.test.ts index bf2ce4343..d452aa819 100644 --- a/www/__tests__/pushNotifySettings.test.ts +++ b/www/__tests__/pushNotifySettings.test.ts @@ -1,5 +1,5 @@ import { DateTime } from 'luxon'; -import { EVENT_NAMES, publish } from '../js/customEventHandler'; +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'; @@ -42,7 +42,7 @@ afterEach(() => { it('intro done does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); expect(getOnList()).toStrictEqual({}); }); @@ -50,7 +50,7 @@ it('intro done initializes the push notifications', () => { expect(getOnList()).toStrictEqual({}); initPushNotify(); - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); expect(getOnList()).toStrictEqual( expect.objectContaining({ notification: expect.any(Function), @@ -62,7 +62,7 @@ it('intro done initializes the push notifications', () => { it('cloud event does nothing if not registered', () => { expect(window['cordova'].platformId).toEqual('ios'); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, { + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { additionalData: { 'content-available': 1, payload: { notId: 3 } }, }); expect(getCalled()).toBeNull(); @@ -71,8 +71,8 @@ it('cloud event does nothing if not registered', () => { it('cloud event handles notification if registered', async () => { expect(window['cordova'].platformId).toEqual('ios'); initPushNotify(); - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'intro done'); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, { + 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)); @@ -81,7 +81,7 @@ it('cloud event handles notification if registered', async () => { it('consent event does nothing if not registered', () => { expect(getOnList()).toStrictEqual({}); - publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); + publish(EVENTS.CONSENTED_EVENT, 'test data'); expect(getOnList()).toStrictEqual({}); }); @@ -99,7 +99,7 @@ it('consent event registers if intro done', async () => { expect(introDone).toBeTruthy(); //publish consent event and check results - publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); + 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( @@ -114,6 +114,6 @@ it('consent event registers if intro done', async () => { it('consent event does not register if intro not done', () => { expect(getOnList()).toStrictEqual({}); initPushNotify(); - publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); + 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 index 1d0220da5..e1965b80b 100644 --- a/www/__tests__/remoteNotifyHandler.test.ts +++ b/www/__tests__/remoteNotifyHandler.test.ts @@ -1,4 +1,4 @@ -import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { EVENTS, publish } from '../js/customEventHandler'; import { initRemoteNotifyHandler } from '../js/splash/remoteNotifyHandler'; import { clearURL, @@ -25,7 +25,7 @@ beforeEach(() => { }); it('does not adds a statEvent if not subscribed', async () => { - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, 'test data'); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, 'test data'); const storedMessages = await db.getAllMessages('stats/client_nav_event', false); expect(storedMessages).toEqual([]); }); @@ -33,7 +33,7 @@ it('does not adds a statEvent if not subscribed', async () => { it('adds a statEvent if subscribed', async () => { initRemoteNotifyHandler(); await new Promise((r) => setTimeout(r, 500)); //wait for subscription - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, 'test data'); + 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({ @@ -47,7 +47,7 @@ it('adds a statEvent if subscribed', async () => { it('handles the url if subscribed', () => { initRemoteNotifyHandler(); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, { + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { additionalData: { payload: { alert_type: 'website', spec: { url: 'https://this_is_a_test.com' } }, }, @@ -57,7 +57,7 @@ it('handles the url if subscribed', () => { it('handles the popup if subscribed', () => { initRemoteNotifyHandler(); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, { + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, { additionalData: { payload: { alert_type: 'popup', @@ -70,7 +70,7 @@ it('handles the popup if subscribed', () => { it('does nothing if subscribed and no data', () => { initRemoteNotifyHandler(); - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, {}); + 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 index ae046216a..3cd6f8319 100644 --- a/www/__tests__/storeDeviceSettings.test.ts +++ b/www/__tests__/storeDeviceSettings.test.ts @@ -11,7 +11,7 @@ import { mockGetAppVersion, } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; -import { EVENT_NAMES, publish } from '../js/customEventHandler'; +import { EVENTS, publish } from '../js/customEventHandler'; import { markIntroDone } from '../js/onboarding/onboardingHelper'; mockBEMUserCache(); @@ -71,14 +71,14 @@ it('verifies my subscrition clearing', async () => { initStoreDeviceSettings(); await new Promise((r) => setTimeout(r, 500)); teardownDeviceSettings(); - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + publish(EVENTS.INTRO_DONE_EVENT, 'test data'); let user = await getUser(); expect(user).toBeUndefined(); }); it('does not store if not subscribed', async () => { - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); - publish(EVENT_NAMES.CONSENTED_EVENT, 'test data'); + 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(); @@ -87,7 +87,7 @@ it('does not store if not subscribed', async () => { it('stores device settings after intro done', async () => { initStoreDeviceSettings(); await new Promise((r) => setTimeout(r, 500)); //time to check consent and subscribe - publish(EVENT_NAMES.INTRO_DONE_EVENT, 'test data'); + 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({ @@ -101,7 +101,7 @@ it('stores device settings after consent if intro done', async () => { await new Promise((r) => setTimeout(r, 500)); //time to check consent and subscribe markIntroDone(); await new Promise((r) => setTimeout(r, 500)); - publish(EVENT_NAMES.CONSENTED_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).toMatchObject({ @@ -113,7 +113,7 @@ it('stores device settings after consent if intro done', async () => { 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(EVENT_NAMES.CONSENTED_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(); diff --git a/www/js/customEventHandler.ts b/www/js/customEventHandler.ts index 600847223..a30a41349 100644 --- a/www/js/customEventHandler.ts +++ b/www/js/customEventHandler.ts @@ -16,7 +16,7 @@ import { logDebug } from './plugin/logger'; /** * central source for event names */ -export const EVENT_NAMES = { +export const EVENTS = { CLOUD_NOTIFICATION_EVENT: 'cloud:push:notification', CONSENTED_EVENT: 'data_collection_consented', INTRO_DONE_EVENT: 'intro_done', diff --git a/www/js/onboarding/onboardingHelper.ts b/www/js/onboarding/onboardingHelper.ts index fb65c1648..d9292547e 100644 --- a/www/js/onboarding/onboardingHelper.ts +++ b/www/js/onboarding/onboardingHelper.ts @@ -2,7 +2,7 @@ import { DateTime } from 'luxon'; import { getConfig, resetDataAndRefresh } from '../config/dynamicConfig'; import { storageGet, storageSet } from '../plugin/storage'; import { logDebug } from '../plugin/logger'; -import { EVENT_NAMES, publish } from '../customEventHandler'; +import { EVENTS, publish } from '../customEventHandler'; import { readConsentState, isConsented } from '../splash/startprefs'; export const INTRO_DONE_KEY = 'intro_done'; @@ -91,6 +91,6 @@ export async function markIntroDone() { return storageSet(INTRO_DONE_KEY, currDateTime).then(() => { //handle "on intro" events logDebug('intro done, publishing event'); - publish(EVENT_NAMES.INTRO_DONE_EVENT, currDateTime); + publish(EVENTS.INTRO_DONE_EVENT, currDateTime); }); } diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index 38f3a4931..a755f7bfe 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -15,7 +15,7 @@ import { updateUser } from '../commHelper'; import { logDebug, displayError } from '../plugin/logger'; -import { publish, subscribe, EVENT_NAMES } from '../customEventHandler'; +import { publish, subscribe, EVENTS } from '../customEventHandler'; import { isConsented, readConsentState } from './startprefs'; import { readIntroDone } from '../onboarding/onboardingHelper'; @@ -55,7 +55,7 @@ const startupInit = function () { logDebug('No additional data defined, nothing to parse'); } } - publish(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, data); + publish(EVENTS.CLOUD_NOTIFICATION_EVENT, data); }); }; @@ -233,9 +233,9 @@ export const initPushNotify = function () { } }); - subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, (event) => onCloudEvent(event, event.detail)); - subscribe(EVENT_NAMES.CONSENTED_EVENT, (event) => onConsentEvent(event, event.detail)); - subscribe(EVENT_NAMES.INTRO_DONE_EVENT, (event) => onIntroEvent(event, event.detail)); + 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/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 54a6b32d8..3eec02900 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -10,7 +10,7 @@ * 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 { EVENT_NAMES, subscribe } from '../customEventHandler'; +import { EVENTS, subscribe } from '../customEventHandler'; import { addStatEvent, statKeys } from '../plugin/clientStats'; import { displayErrorMsg, logDebug } from '../plugin/logger'; @@ -84,5 +84,5 @@ const onCloudNotifEvent = (event) => { * subscribes to cloud notification event */ export const initRemoteNotifyHandler = function () { - subscribe(EVENT_NAMES.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); + subscribe(EVENTS.CLOUD_NOTIFICATION_EVENT, onCloudNotifEvent); }; diff --git a/www/js/splash/startprefs.ts b/www/js/splash/startprefs.ts index 7c75bf3b3..6d5761aae 100644 --- a/www/js/splash/startprefs.ts +++ b/www/js/splash/startprefs.ts @@ -1,6 +1,6 @@ import { storageGet, storageSet } from '../plugin/storage'; import { logInfo, logDebug, displayErrorMsg } from '../plugin/logger'; -import { EVENT_NAMES, publish } from '../customEventHandler'; +import { EVENTS, publish } from '../customEventHandler'; // data collection consented protocol: string, represents the date on // which the consented protocol was approved by the IRB @@ -35,7 +35,7 @@ export function markConsented() { // mark in local variable as well _curr_consented = { ..._req_consent }; // publish event - publish(EVENT_NAMES.CONSENTED_EVENT, _req_consent); + 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 index d9604b3b5..79a34f930 100644 --- a/www/js/splash/storeDeviceSettings.ts +++ b/www/js/splash/storeDeviceSettings.ts @@ -3,7 +3,7 @@ import { isConsented, readConsentState } from './startprefs'; import i18next from 'i18next'; import { displayError, logDebug } from '../plugin/logger'; import { readIntroDone } from '../onboarding/onboardingHelper'; -import { subscribe, EVENT_NAMES, unsubscribe } from '../customEventHandler'; +import { subscribe, EVENTS, unsubscribe } from '../customEventHandler'; /** * @function Gathers information about the user's device and stores it @@ -80,13 +80,13 @@ export const initStoreDeviceSettings = function () { } else { logDebug('no consent yet, waiting to store device settings in profile'); } - subscribe(EVENT_NAMES.CONSENTED_EVENT, onConsentEvent); - subscribe(EVENT_NAMES.INTRO_DONE_EVENT, onIntroEvent); + subscribe(EVENTS.CONSENTED_EVENT, onConsentEvent); + subscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); }); logDebug('storedevicesettings startup done'); }; export const teardownDeviceSettings = function () { - unsubscribe(EVENT_NAMES.CONSENTED_EVENT, onConsentEvent); - unsubscribe(EVENT_NAMES.INTRO_DONE_EVENT, onIntroEvent); + unsubscribe(EVENTS.CONSENTED_EVENT, onConsentEvent); + unsubscribe(EVENTS.INTRO_DONE_EVENT, onIntroEvent); }; From c25ebb6f71f20d24f14b135186da66013b6806f8 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 6 Nov 2023 12:07:10 -0700 Subject: [PATCH 40/43] directly use alert, for now https://github.com/e-mission/e-mission-phone/pull/1093#discussion_r1383603427 --- www/js/splash/remoteNotifyHandler.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 3eec02900..7725138d1 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -28,20 +28,6 @@ const launchWebpage = function (url) { let iab = window['cordova'].InAppBrowser.open(url, '_blank', options); }; -/* -TODO: replace popup with something with better UI -*/ - -/** - * @function launches popup - * @param title string text for popup title - * @param text string text for popup bode - */ -const launchPopup = function (title, text) { - // THIS LINE FOR inAppBrowser - displayErrorMsg(text, title); -}; - /** * @callback for cloud notification event * @param event that triggered this call @@ -71,7 +57,8 @@ const onCloudNotifEvent = (event) => { if (data.additionalData.payload.alert_type == 'popup') { var popup_spec = data.additionalData.payload.spec; if (popup_spec && popup_spec.title && popup_spec.text) { - launchPopup(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 '); } From ad68e1bb0e3a3387850ccc45912a3ce468184a37 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 6 Nov 2023 12:16:57 -0700 Subject: [PATCH 41/43] remove old alerts we still have the information through debug statements --- www/js/splash/pushNotifySettings.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index a755f7bfe..e39849f7a 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -90,7 +90,6 @@ const registerPromise = function () { const registerPush = function () { registerPromise() .then(function (t) { - // alert("Token = "+JSON.stringify(t)); logDebug('Token = ' + JSON.stringify(t)); return window['cordova'].plugins.BEMServerSync.getConfig() .then( @@ -112,7 +111,6 @@ const registerPush = function () { }); }) .then(function (t) { - // alert("Finished saving token = "+JSON.stringify(t.token)); logDebug('Finished saving token = ' + JSON.stringify(t.token)); }) .catch(function (error) { From 85d2884128aecc351ccaafd12a7ab553f8385e83 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 6 Nov 2023 12:17:45 -0700 Subject: [PATCH 42/43] swap var for const https://github.com/e-mission/e-mission-phone/pull/1093#discussion_r1383635228 kept to remain consistent within file --- www/js/splash/pushNotifySettings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/splash/pushNotifySettings.ts b/www/js/splash/pushNotifySettings.ts index e39849f7a..f3eb2c029 100644 --- a/www/js/splash/pushNotifySettings.ts +++ b/www/js/splash/pushNotifySettings.ts @@ -160,7 +160,7 @@ const redirectSilentPush = function (event, data) { * @function shows debug notifications if simulating user interaction * @param message string to display in the degug notif */ -var showDebugLocalNotification = function (message) { +const showDebugLocalNotification = function (message) { window['cordova'].plugins.BEMDataCollection.getConfig().then(function (config) { if (config.simulate_user_interaction) { window['cordova'].plugins.notification.local.schedule({ From 29f44e765e764d7a0a5d17b573048b5560146446 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Mon, 6 Nov 2023 12:29:28 -0700 Subject: [PATCH 43/43] update test when I moved from the formatted alert to a plain alert, the text that ends up in the alert changed, but I did not update my test also added a space between the title and the text --- www/__tests__/remoteNotifyHandler.test.ts | 2 +- www/js/splash/remoteNotifyHandler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/__tests__/remoteNotifyHandler.test.ts b/www/__tests__/remoteNotifyHandler.test.ts index e1965b80b..320877c6b 100644 --- a/www/__tests__/remoteNotifyHandler.test.ts +++ b/www/__tests__/remoteNotifyHandler.test.ts @@ -65,7 +65,7 @@ it('handles the popup if subscribed', () => { }, }, }); - expect(getAlerts()).toEqual(expect.arrayContaining(['━━━━\nHello\n━━━━\nWorld'])); + expect(getAlerts()).toEqual(expect.arrayContaining(['Hello World'])); }); it('does nothing if subscribed and no data', () => { diff --git a/www/js/splash/remoteNotifyHandler.ts b/www/js/splash/remoteNotifyHandler.ts index 7725138d1..cbc920ced 100644 --- a/www/js/splash/remoteNotifyHandler.ts +++ b/www/js/splash/remoteNotifyHandler.ts @@ -58,7 +58,7 @@ const onCloudNotifEvent = (event) => { 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); + window.alert(popup_spec.title + ' ' + popup_spec.text); } else { displayErrorMsg(JSON.stringify(popup_spec), 'popup was not specified correctly. spec is '); }