Skip to content

Commit

Permalink
Merge pull request #1072 from Abby-Wheelis/startprefs-rewrite
Browse files Browse the repository at this point in the history
Rewrite StartPrefs
  • Loading branch information
shankari authored Nov 2, 2023
2 parents 1dca6c8 + 3c23808 commit e8075b0
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 251 deletions.
31 changes: 31 additions & 0 deletions www/__mocks__/cordovaMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export const mockFile = () => {
"applicationStorageDirectory" : "../path/to/app/storage/directory"};
}

//for consent document
const _storage = {};

export const mockBEMUserCache = () => {
const _cache = {};
const messages = [];
Expand Down Expand Up @@ -92,9 +95,37 @@ export const mockBEMUserCache = () => {
rs(messages.filter(m => m.key == key).map(m => m.value));
}, 100)
);
},
getDocument: (key: string, withMetadata?: boolean) => {
return new Promise<any[]>((rs, rj) =>
setTimeout(() => {
rs(_storage[key]);
}, 100)
);
},
isEmptyDoc: (doc) => {
if (doc == undefined) { return true }
let string = doc.toString();
if (string.length == 0) {
return true;
} else {
return false;
}
}
}
window['cordova'] ||= {};
window['cordova'].plugins ||= {};
window['cordova'].plugins.BEMUserCache = mockBEMUserCache;
}

export const mockBEMDataCollection = () => {
const mockBEMDataCollection = {
markConsented: (consentDoc) => {
setTimeout(() => {
_storage['config/consent'] = consentDoc;
}, 100)
}
}
window['cordova'] ||= {};
window['cordova'].plugins.BEMDataCollection = mockBEMDataCollection;
}
24 changes: 24 additions & 0 deletions www/__tests__/startprefs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { markConsented, isConsented, readConsentState, getConsentDocument } from '../js/splash/startprefs';

import { mockBEMUserCache, mockBEMDataCollection } from "../__mocks__/cordovaMocks";
import { mockLogger } from "../__mocks__/globalMocks";

mockBEMUserCache();
mockBEMDataCollection();
mockLogger();

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('checks state of consent before and after marking consent', async () => {
expect(await readConsentState().then(isConsented)).toBeFalsy();
let marked = await markConsented();
expect(await readConsentState().then(isConsented)).toBeTruthy();
expect(await getConsentDocument()).toEqual({"approval_date": "2016-07-14", "protocol_id": "2014-04-6267"});
});
3 changes: 2 additions & 1 deletion www/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@
"confirm": "Confirm",
"user-data-erased": "User data erased.",
"consent-not-found": "Consent for data collection not found, consent now?",
"no-consent-logout": "Consent for data collection not found, please save your opcode, log out, and log back in with the same opcode. Note that you won't get any personalized stats until you do!",
"no-consent-message": "OK! Note that you won't get any personalized stats until you do!",
"consent-found": "Consent found!",
"consented-to": "Consented to protocol {{protocol_id}}, {{approval_date}}",
"consented-to": "Consented to protocol last updated on {{approval_date}}",
"consented-ok": "OK",
"qrcode": "My OPcode",
"qrcode-share-title": "You can save your OPcode to login easily in the future!"
Expand Down
1 change: 0 additions & 1 deletion www/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,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';
Expand Down
2 changes: 0 additions & 2 deletions www/js/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ const App = () => {
const { colors } = useTheme();
const { t } = useTranslation();

const StartPrefs = getAngularService('StartPrefs');

const routes = useMemo(() => {
const showMetrics = appConfig?.survey_info?.['trip-labels'] == 'MULTILABEL';
return showMetrics ? defaultRoutes(t) : defaultRoutes(t).filter(r => r.key != 'metrics');
Expand Down
22 changes: 10 additions & 12 deletions www/js/control/ProfileSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { AppContext } from "../App";
import { shareQR } from "../components/QrCode";
import { storageClear } from "../plugin/storage";
import { getAppVersion } from "../plugin/clientStats";
import { getConsentDocument } from "../splash/startprefs";
import { logDebug } from "../plugin/logger";

//any pure functions can go outside
const ProfileSettings = () => {
Expand All @@ -39,7 +41,6 @@ const ProfileSettings = () => {
const EmailHelper = getAngularService('EmailHelper');
const NotificationScheduler = getAngularService('NotificationScheduler');
const ControlHelper = getAngularService('ControlHelper');
const StartPrefs = getAngularService('StartPrefs');

//functions that come directly from an Angular service
const editCollectionConfig = () => setEditCollectionVis(true);
Expand Down Expand Up @@ -314,8 +315,9 @@ const ProfileSettings = () => {

//in ProfileSettings in DevZone (above two functions are helpers)
async function checkConsent() {
StartPrefs.getConsentDocument().then(function(resultDoc){
getConsentDocument().then(function(resultDoc){
setConsentDoc(resultDoc);
logDebug("In profile settings, consent doc found", resultDoc);
if (resultDoc == null) {
setNoConsentVis(true);
} else {
Expand Down Expand Up @@ -482,17 +484,13 @@ const ProfileSettings = () => {
onDismiss={()=>setNoConsentVis(false)}
style={settingStyles.dialog(colors.elevation.level3)}>
<Dialog.Title>{t('general-settings.consent-not-found')}</Dialog.Title>
<Dialog.Content>
<Text variant="">{t('general-settings.no-consent-logout')}</Text>
</Dialog.Content>
<Dialog.Actions>
<Button onPress={()=>{
setNoConsentVis(false);
setNoConsentMessageVis(true)}}>
{t('general-settings.cancel')}
</Button>
<Button onPress={()=>{
setNoConsentVis(false);
// $state.go("root.reconsent"); //don't know how to do this yet
}}>
{t('general-settings.confirm')}
setNoConsentVis(false); }}>
{t('general-settings.consented-ok')}
</Button>
</Dialog.Actions>
</Dialog>
Expand All @@ -503,7 +501,7 @@ const ProfileSettings = () => {
<Dialog visible={consentVis}
onDismiss={()=>setConsentVis(false)}
style={settingStyles.dialog(colors.elevation.level3)}>
<Dialog.Title>{t('general-settings.consented-to', {protocol_id: consentDoc.protocol_id, approval_date: consentDoc.approval_date})}</Dialog.Title>
<Dialog.Title>{t('general-settings.consented-to', {approval_date: consentDoc.approval_date})}</Dialog.Title>
<Dialog.Actions>
<Button onPress={()=>{
setConsentDoc({});
Expand Down
8 changes: 4 additions & 4 deletions www/js/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import angular from 'angular';
import { addStatError, addStatReading, statKeys } from './plugin/clientStats';
import { getPendingOnboardingState } from './onboarding/onboardingHelper';

angular.module('emission.controllers', ['emission.splash.startprefs',
'emission.splash.pushnotify',
angular.module('emission.controllers', ['emission.splash.pushnotify',
'emission.splash.storedevicesettings',
'emission.splash.localnotify',
'emission.splash.remotenotify'])
Expand All @@ -14,7 +14,7 @@ angular.module('emission.controllers', ['emission.splash.startprefs',
.controller('DashCtrl', function($scope) {})

.controller('SplashCtrl', function($scope, $state, $interval, $rootScope,
StartPrefs, PushNotify, StoreDeviceSettings,
PushNotify, StoreDeviceSettings,
LocalNotify, RemoteNotify) {
console.log('SplashCtrl invoked');
// alert("attach debugger!");
Expand Down Expand Up @@ -49,7 +49,7 @@ angular.module('emission.controllers', ['emission.splash.startprefs',
'root.main.metrics']
if (isInList(toState.name, personalTabs)) {
// toState is in the personalTabs list
StartPrefs.getPendingOnboardingState().then(function(result) {
getPendingOnboardingState().then(function(result) {
if (result != null) {
event.preventDefault();
$state.go(result);
Expand Down
2 changes: 1 addition & 1 deletion www/js/onboarding/ProtocolPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { View, ScrollView } from 'react-native';
import { Button, Surface } from 'react-native-paper';
import { resetDataAndRefresh } from '../config/dynamicConfig';
import { AppContext } from '../App';
import { getAngularService } from '../angular-react-helper';
import PrivacyPolicy from './PrivacyPolicy';
import { onboardingStyles } from './OnboardingStack';
import { markConsented } from '../splash/startprefs';
import { setProtocolDone } from './onboardingHelper';

const ProtocolPage = () => {
Expand Down
21 changes: 11 additions & 10 deletions www/js/onboarding/SaveQrPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { preloadDemoSurveyResponse } from "./SurveyPage";
import { storageSet } from "../plugin/storage";
import { registerUser } from "../commHelper";
import { resetDataAndRefresh } from "../config/dynamicConfig";
import { markConsented } from "../splash/startprefs";
import i18next from "i18next";

const SaveQrPage = ({ }) => {
Expand All @@ -22,16 +23,16 @@ const SaveQrPage = ({ }) => {

useEffect(() => {
if (overallStatus == true && !registerUserDone) {
const StartPrefs = getAngularService('StartPrefs');
StartPrefs.markConsented().then((response) => {
logDebug('permissions done, going to log in');
login(onboardingState.opcode).then((response) => {
logDebug('login done, refreshing onboarding state');
setRegisterUserDone(true);
preloadDemoSurveyResponse();
refreshOnboardingState();
});
});
logDebug('permissions done, going to log in');
markConsented()
.then(login(onboardingState.opcode)
.then((response) => {
logDebug('login done, refreshing onboarding state');
setRegisterUserDone(true);
preloadDemoSurveyResponse();
refreshOnboardingState();
})
);
} else {
logDebug('permissions not done, waiting');
}
Expand Down
18 changes: 13 additions & 5 deletions www/js/onboarding/onboardingHelper.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { DateTime } from "luxon";
import { getAngularService } from "../angular-react-helper";
import { getConfig, resetDataAndRefresh } from "../config/dynamicConfig";
import { storageGet, storageSet } from "../plugin/storage";
import { logDebug } from "../plugin/logger";
import { readConsentState, isConsented } from "../splash/startprefs";
import { getAngularService } from "../angular-react-helper";

export const INTRO_DONE_KEY = 'intro_done';

Expand Down Expand Up @@ -61,15 +62,22 @@ export function getPendingOnboardingState(): Promise<OnboardingState> {
};

async function readConsented() {
const StartPrefs = getAngularService('StartPrefs');
return StartPrefs.readConsentState().then(StartPrefs.isConsented) as Promise<boolean>;
return readConsentState().then(isConsented) as Promise<boolean>;
}

async function readIntroDone() {
export async function readIntroDone() {
return storageGet(INTRO_DONE_KEY).then((read_val) => !!read_val) as Promise<boolean>;
}

export async function markIntroDone() {
const currDateTime = DateTime.now().toISO();
return storageSet(INTRO_DONE_KEY, currDateTime);
return storageSet(INTRO_DONE_KEY, currDateTime)
.then(() => {
//handle "on intro" events
logDebug("intro done, calling registerPush and storeDeviceSettings");
const PushNotify = getAngularService("PushNotify");
const StoreSeviceSettings = getAngularService("StoreDeviceSettings");
PushNotify.registerPush();
StoreSeviceSettings.storeDeviceSettings();
});
}
7 changes: 6 additions & 1 deletion www/js/plugin/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ const unmungeValue = (key, retData) => {
}

const localStorageSet = (key: string, value: {[k: string]: any}) => {
localStorage.setItem(key, JSON.stringify(value));
//checking for a value to prevent storing undefined
//case where local was null and native was undefined stored "undefined"
//see discussion: https://github.com/e-mission/e-mission-phone/pull/1072#discussion_r1373753945
if (value) {
localStorage.setItem(key, JSON.stringify(value));
}
}

const localStorageGet = (key: string) => {
Expand Down
1 change: 0 additions & 1 deletion www/js/splash/localnotify.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import angular from 'angular';

angular.module('emission.splash.localnotify', ['emission.plugin.logger',
'emission.splash.startprefs',
'ionic-toast'])
.factory('LocalNotify', function($window, $ionicPlatform, $ionicPopup,
$state, $rootScope, ionicToast, Logger) {
Expand Down
28 changes: 9 additions & 19 deletions www/js/splash/pushnotify.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
//naming of this file can be a little confusing - "pushnotifysettings" for rewritten file
//https://github.com/e-mission/e-mission-phone/pull/1072#discussion_r1375360832


/*
* This module deals with the interaction with the push plugin, the redirection
* of silent push notifications and the re-parsing of iOS pushes. It then
Expand All @@ -15,12 +19,12 @@

import angular from 'angular';
import { updateUser } from '../commHelper';
import { readConsentState, isConsented } from './startprefs';

angular.module('emission.splash.pushnotify', ['emission.plugin.logger',
'emission.services',
'emission.splash.startprefs'])
'emission.services'])
.factory('PushNotify', function($window, $state, $rootScope, $ionicPlatform,
$ionicPopup, Logger, StartPrefs) {
$ionicPopup, Logger) {

var pushnotify = {};
var push = null;
Expand Down Expand Up @@ -159,8 +163,8 @@ angular.module('emission.splash.pushnotify', ['emission.plugin.logger',

$ionicPlatform.ready().then(function() {
pushnotify.datacollect = $window.cordova.plugins.BEMDataCollection;
StartPrefs.readConsentState()
.then(StartPrefs.isConsented)
readConsentState()
.then(isConsented)
.then(function(consentState) {
if (consentState == true) {
pushnotify.registerPush();
Expand All @@ -172,19 +176,5 @@ angular.module('emission.splash.pushnotify', ['emission.plugin.logger',
Logger.log("pushnotify startup done");
});

$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();
}
});

$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();
});

return pushnotify;
});
6 changes: 4 additions & 2 deletions www/js/splash/remotenotify.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//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
Expand All @@ -15,8 +18,7 @@
import angular from 'angular';
import { addStatEvent, statKeys } from '../plugin/clientStats';

angular.module('emission.splash.remotenotify', ['emission.plugin.logger',
'emission.splash.startprefs'])
angular.module('emission.splash.remotenotify', ['emission.plugin.logger'])

.factory('RemoteNotify', function($http, $window, $ionicPopup, $rootScope, Logger) {

Expand Down
Loading

0 comments on commit e8075b0

Please sign in to comment.