user browser polyfill with Promise for options access

This commit is contained in:
Dima Gerasimov 2022-12-27 23:26:10 +00:00 committed by karlicoss
parent 8dc554fd61
commit f906728bf8
11 changed files with 93 additions and 62 deletions

View file

@ -5,6 +5,7 @@
[libs] [libs]
node_modules/flow-interfaces-chrome/interfaces/ node_modules/flow-interfaces-chrome/interfaces/
src/js/browser_types.js
[lints] [lints]

View file

@ -46,6 +46,7 @@
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"web-ext": "^7.4.0", "web-ext": "^7.4.0",
"web-ext-submit": "^7.4.0", "web-ext-submit": "^7.4.0",
"webextension-polyfill": "^0.10.0",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1", "webpack-dev-server": "^4.11.1",

View file

@ -1,8 +1,9 @@
/* @flow */ /* @flow */
// TODO do I really need to annotate all files with @flow?? // TODO do I really need to annotate all files with @flow??
import {COMMAND_CAPTURE_SIMPLE, METHOD_CAPTURE_WITH_EXTRAS, showNotification} from './common' import {COMMAND_CAPTURE_SIMPLE, METHOD_CAPTURE_WITH_EXTRAS, showNotification} from './common'
import {get_options} from './options' import {getOptions} from './options'
import type {Options} from './options' import type {Options} from './options'
@ -87,7 +88,7 @@ function capture(comment: ?string = null, tag_str: ?string = null) {
// TODO await properly and handle errors // TODO await properly and handle errors
const has_scripting = 'scripting' in chrome const has_scripting = 'scripting' in chrome
get_options(opts => { getOptions().then(opts => {
if (has_scripting) { if (has_scripting) {
// $FlowFixMe // $FlowFixMe
chrome.scripting.executeScript({ chrome.scripting.executeScript({

View file

@ -0,0 +1,25 @@
/* @flow */
/*
* Based on https://github.com/Shraymonks/flow-interfaces-chrome
* not sure if browser polyfill has flow types??
* https://github.com/mozilla/webextension-polyfill/issues/421
*/
type browser$StorageArea = {
get: (keys: string | Array<string> | Object | null) => Promise<Object>,
set: (items: Object) => Promise<void>,
}
type browser$storage = {
local: browser$StorageArea,
managed: browser$StorageArea,
sync: browser$StorageArea,
}
declare var browser: {
storage: browser$storage,
}

View file

@ -6,31 +6,23 @@ export type Options = {
notification: boolean; notification: boolean;
} }
function default_options(): Options {
function defaultOptions(): Options {
return { return {
endpoint: "http://localhost:12212/capture", endpoint: "http://localhost:12212/capture",
default_tags: "grasp", default_tags: "grasp",
notification: true, notification: true,
};
} }
export function get_options(cb: (Options) => void) {
chrome.storage.local.get(null, res => {
res = {...default_options(), ...res};
cb(res);
});
}
export function set_options(opts: Options, cb: () => void) {
console.log('Saving %s', JSON.stringify(opts));
chrome.storage.local.set(opts, cb);
} }
export async function getOptions(): Promise<Options> {
export function getOptions(): Promise<Options> { const res = await browser.storage.local.get(null)
return new Promise((resolve) => { return {...defaultOptions(), ...res}
// TODO FIXME reject? }
get_options(resolve);
});
export async function setOptions(opts: Options): Promise<void> {
console.log('Saving %o', opts)
await browser.storage.local.set(opts)
} }

View file

@ -1,15 +1,15 @@
/* @flow */ /* @flow */
import {get_options, set_options} from './options'; import {getOptions, setOptions} from './options'
import {ensurePermissions, hasPermissions} from './permissions'; import {ensurePermissions, hasPermissions} from './permissions'
const ENDPOINT_ID = 'endpoint_id'; const ENDPOINT_ID = 'endpoint_id';
const HAS_PERMISSION_ID = 'has_permission_id'; const HAS_PERMISSION_ID = 'has_permission_id';
const NOTIFICATION_ID = 'notification_id'; const NOTIFICATION_ID = 'notification_id';
const DEFAULT_TAGS_ID = 'default_tags_id'; const DEFAULT_TAGS_ID = 'default_tags_id';
// TODO specify capture path here?
const SAVE_ID = 'save_id'; const SAVE_ID = 'save_id';
function getEndpoint(): HTMLInputElement { function getEndpoint(): HTMLInputElement {
return ((document.getElementById(ENDPOINT_ID): any): HTMLInputElement); return ((document.getElementById(ENDPOINT_ID): any): HTMLInputElement);
} }
@ -31,47 +31,50 @@ function getHasPermission(): HTMLElement {
return ((document.getElementById(HAS_PERMISSION_ID): any): HTMLElement); return ((document.getElementById(HAS_PERMISSION_ID): any): HTMLElement);
} }
// ugh. needs a better name.. // ugh. needs a better name..
function refreshPermissionValidation(endpoint: string) { async function refreshPermissionValidation(endpoint: string) {
hasPermissions(endpoint).then(has => { const has = await hasPermissions(endpoint)
const pstyle = getHasPermission().style; const pstyle = getHasPermission().style
if (has) { if (has) {
console.debug('Got permisssions, nothing to worry about.'); console.debug('Got permisssions, nothing to worry about.')
pstyle.display = 'none'; pstyle.display = 'none'
return; return
} }
console.debug('Whoops, no permissions to access %s', endpoint); console.debug('Whoops, no permissions to access %s', endpoint)
// TODO maybe just show button? but then it would need to be reactive.. // TODO maybe just show button? but then it would need to be reactive..
pstyle.display = 'block'; pstyle.display = 'block'
});
} }
function restoreOptions() {
getSaveButton().addEventListener('click', saveOptions); async function restoreOptions() {
get_options(opts => { getSaveButton().addEventListener('click', saveOptions)
const ep = opts.endpoint;
getEndpoint().value = ep; const opts = await getOptions()
getDefaultTags().value = opts.default_tags; getEndpoint().value = opts.endpoint
getEnableNotification().checked = opts.notification; getDefaultTags().value = opts.default_tags
refreshPermissionValidation(ep); getEnableNotification().checked = opts.notification
}); await refreshPermissionValidation(opts.endpoint)
} }
function saveOptions() {
async function saveOptions() {
// TODO could also check for permissions and display message? // TODO could also check for permissions and display message?
const endpoint = getEndpoint().value; const endpoint = getEndpoint().value
ensurePermissions(endpoint).finally(() => { await ensurePermissions(endpoint)
refreshPermissionValidation(endpoint); refreshPermissionValidation(endpoint)
});
const opts = { const opts = {
endpoint: endpoint, endpoint: endpoint,
default_tags: getDefaultTags().value, default_tags: getDefaultTags().value,
notification: getEnableNotification().checked, notification: getEnableNotification().checked,
}; }
set_options(opts, () => { alert('Saved!'); }); await setOptions(opts)
// hmm seems that regular alert() doesn't work in chrome anymore
chrome.extension.getBackgroundPage().alert('Saved!')
} }
document.addEventListener('DOMContentLoaded', restoreOptions); document.addEventListener('DOMContentLoaded', restoreOptions)

View file

@ -1,7 +1,8 @@
/* @flow */ /* @flow */
import {METHOD_CAPTURE_WITH_EXTRAS} from './common';
import {get_options} from './options'; import {METHOD_CAPTURE_WITH_EXTRAS} from './common'
import {getOptions} from './options'
// TODO template it in html too? // TODO template it in html too?
const BUTTON_ID = 'button_id'; const BUTTON_ID = 'button_id';
@ -73,14 +74,15 @@ function getUiState(): State {
} }
function restoreState(state: ?State) { async function restoreState(state: ?State) {
window.justSubmitted = false window.justSubmitted = false
if (state == null) { if (state == null) {
// comment just relies on default // comment just relies on default
get_options(opts => {getTags().value = opts.default_tags;}); const opts = await getOptions()
getTags().value = opts.default_tags
} else { } else {
getComment().value = state.comment; getComment().value = state.comment
getTags().value = state.tag_str; getTags().value = state.tag_str
} }
} }

View file

@ -2,7 +2,10 @@
"name": "<webpack managed>", "name": "<webpack managed>",
"version": "<webpack managed>", "version": "<webpack managed>",
"background": { "background": {
"scripts": ["background.js"], "scripts": [
"browser-polyfill.js",
"background.js"
],
"persistent": false "persistent": false
}, },
"options_ui": { "options_ui": {

View file

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<script type="text/javascript" src="browser-polyfill.js"/></script>
<script type="text/javascript" src="options_page.js"/></script> <script type="text/javascript" src="options_page.js"/></script>
<style> <style>
input:invalid { input:invalid {

View file

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<script type="text/javascript" src="browser-polyfill.js"></script>
<script type="text/javascript" src="popup.js"></script> <script type="text/javascript" src="popup.js"></script>
</head> </head>
<body> <body>

View file

@ -166,6 +166,7 @@ const options = {
patterns: [ patterns: [
{ context: 'src', from: '**/*.html' }, { context: 'src', from: '**/*.html' },
{ context: 'src', from: '**/*.png' }, { context: 'src', from: '**/*.png' },
{ from: 'node_modules/webextension-polyfill/dist/browser-polyfill.js'},
] ]
}), }),
new WebpackExtensionManifestPlugin({ new WebpackExtensionManifestPlugin({