From d707b91fe829428c8e0060c306673b1af56c8350 Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 11 Aug 2019 16:49:18 -0500 Subject: [PATCH] Keep track of permissions granted via background local storage Settings use cloud sync, so without tracking optional permissions, the user could reinstall the extension and get stuck in an unrecoverable error state on startup because the view mode is set to an option we haven't asked permissions for yet. The chrome.permissions* methods can only be accessed via user gestures, including chrome.permissions.contains from my testing. Work around this by tracking permissions granted, and resetting preferences accordingly when setPrefs is called. --- app/scripts/bg/bg.js | 2 + .../components/settings/preferences.js | 1 + app/scripts/components/stores/main.js | 15 ++++-- app/scripts/components/stores/prefs.js | 50 ++++++++++++++++++- 4 files changed, 61 insertions(+), 7 deletions(-) diff --git a/app/scripts/bg/bg.js b/app/scripts/bg/bg.js index 96c28d8..2e5cc0d 100755 --- a/app/scripts/bg/bg.js +++ b/app/scripts/bg/bg.js @@ -575,6 +575,8 @@ class Bg { this.undoAction(this.state.windows[refWindow].tabs, this.state.chromeVersion); } else if (msg.method === 'getActions') { sendResponse({actions: this.state.actions, windowId: sender.tab.windowId}); + } else if (msg.method === 'setPermissions') { + prefsStore.setPermissions(msg.permissions); } return true; }); diff --git a/app/scripts/components/settings/preferences.js b/app/scripts/components/settings/preferences.js index d009bbe..b582444 100644 --- a/app/scripts/components/settings/preferences.js +++ b/app/scripts/components/settings/preferences.js @@ -310,6 +310,7 @@ class Preferences extends React.Component { }, (granted) => { if (!granted) return; + msgStore.setPermissions({screenshot: true}); this.handleClick(opt); setTimeout(chrome.runtime.reload, 500); diff --git a/app/scripts/components/stores/main.js b/app/scripts/components/stores/main.js index 9f1d3b1..74cec96 100755 --- a/app/scripts/components/stores/main.js +++ b/app/scripts/components/stores/main.js @@ -78,9 +78,8 @@ export var utilityStore = { // Chrome only allows interacting with the chrome.permissions* API with a user gesture, // including the function that calls back with a boolean if we have a permission. // This makes it difficult to handle extension reloads, where TM5K likes to load - // a new tab page if one was already open, to restore it. For now we can assume - // the only way a user will get stuck in a mode requiring permissions without the permission, - // is if they are modifying state directly in devtools. + // a new tab page if one was already open, to restore it. For now TM5K tracks permissions + // granted manually, and resets prefs based on those values any time prefs are updated. if (userGesture) { chrome.permissions.request({ permissions: ['bookmarks'], @@ -88,6 +87,7 @@ export var utilityStore = { }, (granted) => { if (!granted) return; + msgStore.setPermissions({bookmarks: true}); msgStore.queryBookmarks(init); this._handleMode(mode, stateUpdate, init); }); @@ -105,6 +105,7 @@ export var utilityStore = { }, (granted) => { if (!granted) return; + msgStore.setPermissions({history: true}); msgStore.queryHistory(init); this._handleMode(mode, stateUpdate, init); }); @@ -123,6 +124,7 @@ export var utilityStore = { }, (granted) => { if (!granted) return; + msgStore.setPermissions({management: true}); msgStore.queryExtensions(init); this._handleMode(mode, stateUpdate, init); }); @@ -286,7 +288,7 @@ export var msgStore = { handleMessage(s, msg, sender, sendResponse); }); }, - setPrefs(obj){ + setPrefs(obj) { state.set({prefs: obj}, true); chrome.runtime.sendMessage(chrome.runtime.id, {method: 'setPrefs', obj: obj}, (response) => { if (response && response.prefs) { @@ -294,7 +296,7 @@ export var msgStore = { } }); }, - getPrefs(){ + getPrefs() { return new Promise((resolve) => { chrome.runtime.sendMessage(chrome.runtime.id, {method: 'prefs'}, (response) => { if (response && response.prefs) { @@ -303,6 +305,9 @@ export var msgStore = { }); }); }, + setPermissions(permissions) { + chrome.runtime.sendMessage(chrome.runtime.id, {method: 'setPermissions', permissions}); + }, getWindowId() { return new Promise((resolve) => { chrome.runtime.sendMessage(chrome.runtime.id, {method: 'getWindowId'}, (windowId) => { diff --git a/app/scripts/components/stores/prefs.js b/app/scripts/components/stores/prefs.js index e16775c..ef7a831 100755 --- a/app/scripts/components/stores/prefs.js +++ b/app/scripts/components/stores/prefs.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import initStore from '@jaszhix/state'; -import {each} from '@jaszhix/utils'; +import {each, tryFn} from '@jaszhix/utils'; let prefsStore = initStore({ prefs: {}, @@ -39,7 +39,13 @@ let prefsStore = initStore({ allTabs: false, resetSearchOnClick: true, tablePadding: 5, - errorTelemetry: false + errorTelemetry: false, + }, + permissions: { + screenshot: false, + bookmarks: false, + history: false, + management: false, }, init: function() { let getPrefs = new Promise((resolve, reject)=>{ @@ -52,6 +58,7 @@ let prefsStore = initStore({ reject(chrome.extension.lastError); } else { prefsStore.prefs = prefsStore.defaultPrefs; + prefsStore.syncPermissions(); prefsStore.setPrefs(prefsStore.prefs); console.log('init prefs: ', prefsStore.prefs); } @@ -69,14 +76,53 @@ let prefsStore = initStore({ } }); + prefsStore.syncPermissions(); + console.log('load prefs: ', prefs, prefsStore.prefs); prefsStore.set({prefs: prefsStore.prefs}, true); }).catch((err)=>{ console.log('chrome.extension.lastError: ', err); }); }, + syncPermissions() { + // With cloud syncing, the extension settings could be restored and the user might get + // stuck in a view mode we haven't asked permission for yet. Resolve this by tracking + // all permissions granted, and resetting prefs accordingly (handled in checkPermissions). + let permissions = localStorage.getItem('tm5kPermissionsTracking'); + + if (!permissions) { + localStorage.setItem('tm5kPermissionsTracking', JSON.stringify(prefsStore.permissions)); + } else { + permissions = tryFn( + () => JSON.parse(permissions), + () => prefsStore.permissions + ); + + prefsStore.permissions = permissions; + } + }, + checkPermissions(prefs) { + const {permissions} = prefsStore; + + if (!permissions.screenshot && prefs.screenshot) { + prefs.screenshot = false; + } + + if ((!permissions.bookmarks && prefs.mode === 'bookmarks') + || (!permissions.history && prefs.mode === 'history') + || (!permissions.management && (prefs.mode === 'apps' || prefs.mode === 'extensions'))) { + prefs.mode = 'tabs'; + } + }, + setPermissions(obj) { + _.merge(prefsStore.permissions, obj); + localStorage.setItem('tm5kPermissionsTracking', JSON.stringify(prefsStore.permissions)); + }, setPrefs(obj) { + prefsStore.checkPermissions(obj); + _.merge(prefsStore.prefs, obj); + prefsStore.set({prefs: prefsStore.prefs}, true); let themePrefs = { wallpaper: prefsStore.prefs.wallpaper,