summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2022-09-12 14:28:53 -0300
committerSebastian <sebasjm@gmail.com>2022-09-12 14:28:53 -0300
commitad63d4c0e1ab249362e1f684ac9dbe5fae2ef34e (patch)
treedfc38c13a23e45f4916071c76e26049a57ebd63b
parent27201416c7d234361507e6055ce7ed42c11c650e (diff)
downloadwallet-core-ad63d4c0e1ab249362e1f684ac9dbe5fae2ef34e.tar.gz
wallet-core-ad63d4c0e1ab249362e1f684ac9dbe5fae2ef34e.tar.bz2
wallet-core-ad63d4c0e1ab249362e1f684ac9dbe5fae2ef34e.zip
add clipboard perms
-rw-r--r--packages/taler-wallet-webextension/manifest-v2.json5
-rw-r--r--packages/taler-wallet-webextension/manifest-v3.json5
-rw-r--r--packages/taler-wallet-webextension/src/background.ts8
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts (renamed from packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts)10
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts73
-rw-r--r--packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts3
-rw-r--r--packages/taler-wallet-webextension/src/platform/api.ts4
-rw-r--r--packages/taler-wallet-webextension/src/platform/chrome.ts59
-rw-r--r--packages/taler-wallet-webextension/src/platform/dev.ts3
-rw-r--r--packages/taler-wallet-webextension/src/platform/firefox.ts18
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx12
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Settings.tsx47
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Welcome.tsx4
-rw-r--r--packages/taler-wallet-webextension/src/wxBackend.ts14
14 files changed, 217 insertions, 48 deletions
diff --git a/packages/taler-wallet-webextension/manifest-v2.json b/packages/taler-wallet-webextension/manifest-v2.json
index e2dd67623..74f103feb 100644
--- a/packages/taler-wallet-webextension/manifest-v2.json
+++ b/packages/taler-wallet-webextension/manifest-v2.json
@@ -24,7 +24,8 @@
"optional_permissions": [
"http://*/*",
"https://*/*",
- "webRequest"
+ "webRequest",
+ "clipboardRead"
],
"browser_action": {
"default_icon": {
@@ -45,4 +46,4 @@
"page": "static/background.html",
"persistent": true
}
-}
+} \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/manifest-v3.json b/packages/taler-wallet-webextension/manifest-v3.json
index 3ddaec513..78dcac623 100644
--- a/packages/taler-wallet-webextension/manifest-v3.json
+++ b/packages/taler-wallet-webextension/manifest-v3.json
@@ -27,7 +27,8 @@
}
},
"optional_permissions": [
- "webRequest"
+ "webRequest",
+ "clipboardRead"
],
"host_permissions": [
"http://*/*",
@@ -51,4 +52,4 @@
"background": {
"service_worker": "service_worker.js"
}
-}
+} \ No newline at end of file
diff --git a/packages/taler-wallet-webextension/src/background.ts b/packages/taler-wallet-webextension/src/background.ts
index 0085ee4fd..0e2ea3f3a 100644
--- a/packages/taler-wallet-webextension/src/background.ts
+++ b/packages/taler-wallet-webextension/src/background.ts
@@ -42,14 +42,6 @@ if (isFirefox) {
setupPlatform(chromeAPI);
}
-try {
- platform.registerOnInstalled(() => {
- platform.openWalletPage("/welcome");
- });
-} catch (e) {
- console.error(e);
-}
-
// setGlobalLogLevelFromString("trace")
platform.notifyWhenAppIsReady(() => {
wxMain();
diff --git a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts
index 06ae84593..727d653af 100644
--- a/packages/taler-wallet-webextension/src/hooks/useExtendedPermissions.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useAutoOpenPermissions.ts
@@ -20,21 +20,21 @@ import { platform } from "../platform/api.js";
import { ToggleHandler } from "../mui/handlers.js";
import { TalerError } from "@gnu-taler/taler-wallet-core";
-export function useExtendedPermissions(): ToggleHandler {
+export function useAutoOpenPermissions(): ToggleHandler {
const [enabled, setEnabled] = useState(false);
const [error, setError] = useState<TalerError | undefined>();
const toggle = async (): Promise<void> => {
- return handleExtendedPerm(enabled, setEnabled).catch((e) => {
+ return handleAutoOpenPerm(enabled, setEnabled).catch((e) => {
setError(TalerError.fromException(e));
});
};
useEffect(() => {
- async function getExtendedPermValue(): Promise<void> {
+ async function getValue(): Promise<void> {
const res = await wxApi.containsHeaderListener();
setEnabled(res.newValue);
}
- getExtendedPermValue();
+ getValue();
}, []);
return {
value: enabled,
@@ -45,7 +45,7 @@ export function useExtendedPermissions(): ToggleHandler {
};
}
-async function handleExtendedPerm(
+async function handleAutoOpenPerm(
isEnabled: boolean,
onChange: (value: boolean) => void,
): Promise<void> {
diff --git a/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts
new file mode 100644
index 000000000..c69b116b7
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/hooks/useClipboardPermissions.ts
@@ -0,0 +1,73 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import { useState, useEffect } from "preact/hooks";
+import * as wxApi from "../wxApi.js";
+import { platform } from "../platform/api.js";
+import { ToggleHandler } from "../mui/handlers.js";
+import { TalerError } from "@gnu-taler/taler-wallet-core";
+
+export function useClipboardPermissions(): ToggleHandler {
+ const [enabled, setEnabled] = useState(false);
+ const [error, setError] = useState<TalerError | undefined>();
+ const toggle = async (): Promise<void> => {
+ return handleClipboardPerm(enabled, setEnabled).catch((e) => {
+ setError(TalerError.fromException(e));
+ });
+ };
+
+ useEffect(() => {
+ async function getValue(): Promise<void> {
+ const res = await wxApi.containsHeaderListener();
+ setEnabled(res.newValue);
+ }
+ getValue();
+ }, []);
+
+ return {
+ value: enabled,
+ button: {
+ onClick: toggle,
+ error,
+ },
+ };
+}
+
+async function handleClipboardPerm(
+ isEnabled: boolean,
+ onChange: (value: boolean) => void,
+): Promise<void> {
+ if (!isEnabled) {
+ // We set permissions here, since apparently FF wants this to be done
+ // as the result of an input event ...
+ let granted: boolean;
+ try {
+ granted = await platform.getPermissionsApi().requestClipboardPermissions();
+ } catch (lastError) {
+ onChange(false);
+ throw lastError;
+ }
+ // const res = await wxApi.toggleHeaderListener(granted);
+ onChange(granted);
+ } else {
+ try {
+ await wxApi.toggleHeaderListener(false).then((r) => onChange(r.newValue));
+ } catch (e) {
+ console.log(e);
+ }
+ }
+ return;
+}
diff --git a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
index e1b08278b..75a92fd3c 100644
--- a/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
+++ b/packages/taler-wallet-webextension/src/hooks/useTalerActionURL.ts
@@ -31,7 +31,6 @@ export function useTalerActionURL(): [
);
const [dismissed, setDismissed] = useState(false);
const { findTalerUriInActiveTab, findTalerUriInClipboard } = useIocContext();
-
useEffect(() => {
async function check(): Promise<void> {
const clipUri = await findTalerUriInClipboard();
@@ -52,7 +51,7 @@ export function useTalerActionURL(): [
}
}
check();
- }, [setTalerActionUrl]);
+ }, []);
const url = dismissed ? undefined : talerActionUrl;
return [url, setDismissed];
diff --git a/packages/taler-wallet-webextension/src/platform/api.ts b/packages/taler-wallet-webextension/src/platform/api.ts
index 23fd80ed7..da257c25f 100644
--- a/packages/taler-wallet-webextension/src/platform/api.ts
+++ b/packages/taler-wallet-webextension/src/platform/api.ts
@@ -37,6 +37,10 @@ export interface CrossBrowserPermissionsApi {
requestHostPermissions(): Promise<boolean>;
removeHostPermissions(): Promise<boolean>;
+ containsClipboardPermissions(): Promise<boolean>;
+ requestClipboardPermissions(): Promise<boolean>;
+ removeClipboardPermissions(): Promise<boolean>;
+
addPermissionsListener(
callback: (p: Permissions, lastError?: string) => void,
): void;
diff --git a/packages/taler-wallet-webextension/src/platform/chrome.ts b/packages/taler-wallet-webextension/src/platform/chrome.ts
index 7311354c9..75900882f 100644
--- a/packages/taler-wallet-webextension/src/platform/chrome.ts
+++ b/packages/taler-wallet-webextension/src/platform/chrome.ts
@@ -77,6 +77,18 @@ const hostPermissions = {
origins: ["http://*/*", "https://*/*"],
};
+export function containsClipboardPermissions(): Promise<boolean> {
+ return new Promise((res, rej) => {
+ chrome.permissions.contains({ permissions: ["clipboardRead"] }, (resp) => {
+ const le = chrome.runtime.lastError?.message;
+ if (le) {
+ rej(le);
+ }
+ res(resp);
+ });
+ });
+}
+
export function containsHostPermissions(): Promise<boolean> {
return new Promise((res, rej) => {
chrome.permissions.contains(hostPermissions, (resp) => {
@@ -89,6 +101,18 @@ export function containsHostPermissions(): Promise<boolean> {
});
}
+export async function requestClipboardPermissions(): Promise<boolean> {
+ return new Promise((res, rej) => {
+ chrome.permissions.request({ permissions: ["clipboardRead"] }, (resp) => {
+ const le = chrome.runtime.lastError?.message;
+ if (le) {
+ rej(le);
+ }
+ res(resp);
+ })
+ });
+}
+
export async function requestHostPermissions(): Promise<boolean> {
return new Promise((res, rej) => {
chrome.permissions.request(hostPermissions, (resp) => {
@@ -155,6 +179,18 @@ export async function removeHostPermissions(): Promise<boolean> {
});
}
+export function removeClipboardPermissions(): Promise<boolean> {
+ return new Promise((res, rej) => {
+ chrome.permissions.remove({ permissions: ["clipboardRead"] }, (resp) => {
+ const le = chrome.runtime.lastError?.message;
+ if (le) {
+ rej(le);
+ }
+ res(resp);
+ });
+ });
+}
+
function addPermissionsListener(
callback: (p: Permissions, lastError?: string) => void,
): void {
@@ -170,6 +206,9 @@ function getPermissionsApi(): CrossBrowserPermissionsApi {
containsHostPermissions,
requestHostPermissions,
removeHostPermissions,
+ requestClipboardPermissions,
+ removeClipboardPermissions,
+ containsClipboardPermissions,
};
}
@@ -382,11 +421,9 @@ function registerTalerHeaderListener(
}
async function tabListener(tabId: number, info: chrome.tabs.TabChangeInfo): Promise<void> {
- console.log("tab update", tabId, info)
if (tabId < 0) return;
if (info.status !== "complete") return;
const uri = await findTalerUriInTab(tabId);
- console.log("uri", uri)
if (!uri) return;
logger.info(`Found a Taler URI in the tab ${tabId}`)
callback(tabId, uri)
@@ -585,7 +622,6 @@ async function registerIconChangeOnTalerContent(): Promise<void> {
chrome.tabs.onUpdated.addListener(
async (tabId, info: chrome.tabs.TabChangeInfo) => {
if (tabId < 0) return;
- logger.info("tab updated", tabId, info);
if (info.status !== "complete") return;
const uri = await findTalerUriInTab(tabId);
if (uri) {
@@ -690,9 +726,22 @@ async function findTalerUriInTab(tabId: number): Promise<string | undefined> {
}
}
+async function timeout(ms: number): Promise<void> {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
async function findTalerUriInClipboard(): Promise<string | undefined> {
- const textInClipboard = await window.navigator.clipboard.readText();
- return textInClipboard.startsWith("taler://") || textInClipboard.startsWith("taler+http://") ? textInClipboard : undefined
+ try {
+ //It looks like clipboard promise does not return, so we need a timeout
+ const textInClipboard = await Promise.any([
+ timeout(100),
+ window.navigator.clipboard.readText()
+ ])
+ if (!textInClipboard) return;
+ return textInClipboard.startsWith("taler://") || textInClipboard.startsWith("taler+http://") ? textInClipboard : undefined
+ } catch (e) {
+ logger.error("could not read clipboard", e)
+ return undefined
+ }
}
async function findTalerUriInActiveTab(): Promise<string | undefined> {
diff --git a/packages/taler-wallet-webextension/src/platform/dev.ts b/packages/taler-wallet-webextension/src/platform/dev.ts
index bb7e181c4..8a410b062 100644
--- a/packages/taler-wallet-webextension/src/platform/dev.ts
+++ b/packages/taler-wallet-webextension/src/platform/dev.ts
@@ -32,6 +32,9 @@ const api: PlatformAPI = {
containsHostPermissions: async () => true,
removeHostPermissions: async () => false,
requestHostPermissions: async () => false,
+ containsClipboardPermissions: async () => true,
+ removeClipboardPermissions: async () => false,
+ requestClipboardPermissions: async () => false,
}),
getWalletWebExVersion: () => ({
version: "none",
diff --git a/packages/taler-wallet-webextension/src/platform/firefox.ts b/packages/taler-wallet-webextension/src/platform/firefox.ts
index a56e21f24..943168956 100644
--- a/packages/taler-wallet-webextension/src/platform/firefox.ts
+++ b/packages/taler-wallet-webextension/src/platform/firefox.ts
@@ -16,9 +16,12 @@
import { CrossBrowserPermissionsApi, Permissions, PlatformAPI } from "./api.js";
import chromePlatform, {
- containsHostPermissions as chromeContains,
- removeHostPermissions as chromeRemove,
- requestHostPermissions as chromeRequest,
+ containsHostPermissions as chromeHostContains,
+ removeHostPermissions as chromeHostRemove,
+ requestHostPermissions as chromeHostRequest,
+ containsClipboardPermissions as chromeClipContains,
+ removeClipboardPermissions as chromeClipRemove,
+ requestClipboardPermissions as chromeClipRequest,
} from "./chrome.js";
const api: PlatformAPI = {
@@ -43,9 +46,12 @@ function addPermissionsListener(callback: (p: Permissions) => void): void {
function getPermissionsApi(): CrossBrowserPermissionsApi {
return {
addPermissionsListener,
- containsHostPermissions: chromeContains,
- requestHostPermissions: chromeRequest,
- removeHostPermissions: chromeRemove,
+ containsHostPermissions: chromeHostContains,
+ requestHostPermissions: chromeHostRequest,
+ removeHostPermissions: chromeHostRemove,
+ containsClipboardPermissions: chromeClipContains,
+ removeClipboardPermissions: chromeClipRemove,
+ requestClipboardPermissions: chromeClipRequest,
};
}
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
index 9e85a9bed..d0707952f 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.stories.tsx
@@ -46,21 +46,24 @@ const version = {
export const AllOff = createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
- permissionToggle: { value: false, button: {} },
+ autoOpenToggle: { value: false, button: {} },
+ clipboardToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(),
...version,
});
export const OneChecked = createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
- permissionToggle: { value: false, button: {} },
+ autoOpenToggle: { value: false, button: {} },
+ clipboardToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(),
...version,
});
export const WithOneExchange = createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
- permissionToggle: { value: false, button: {} },
+ autoOpenToggle: { value: false, button: {} },
+ clipboardToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(),
knownExchanges: [
{
@@ -80,7 +83,8 @@ export const WithOneExchange = createExample(TestedComponent, {
export const WithExchangeInDifferentState = createExample(TestedComponent, {
deviceName: "this-is-the-device-name",
- permissionToggle: { value: false, button: {} },
+ autoOpenToggle: { value: false, button: {} },
+ clipboardToggle: { value: false, button: {} },
setDeviceName: () => Promise.resolve(),
knownExchanges: [
{
diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
index 4a520c3bb..5219bbb38 100644
--- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx
@@ -33,17 +33,19 @@ import { useDevContext } from "../context/devContext.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useBackupDeviceName } from "../hooks/useBackupDeviceName.js";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions.js";
+import { useAutoOpenPermissions } from "../hooks/useAutoOpenPermissions.js";
import { ToggleHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
import { buildTermsOfServiceStatus } from "../utils/index.js";
import * as wxApi from "../wxApi.js";
import { platform } from "../platform/api.js";
+import { useClipboardPermissions } from "../hooks/useClipboardPermissions.js";
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
export function SettingsPage(): VNode {
- const permissionToggle = useExtendedPermissions();
+ const autoOpenToggle = useAutoOpenPermissions();
+ const clipboardToggle = useClipboardPermissions();
const { devMode, toggleDevMode } = useDevContext();
const { name, update } = useBackupDeviceName();
const webex = platform.getWalletWebExVersion();
@@ -63,7 +65,8 @@ export function SettingsPage(): VNode {
knownExchanges={exchanges}
deviceName={name}
setDeviceName={update}
- permissionToggle={permissionToggle}
+ autoOpenToggle={autoOpenToggle}
+ clipboardToggle={clipboardToggle}
developerMode={devMode}
toggleDeveloperMode={toggleDevMode}
webexVersion={{
@@ -78,7 +81,8 @@ export function SettingsPage(): VNode {
export interface ViewProps {
deviceName: string;
setDeviceName: (s: string) => Promise<void>;
- permissionToggle: ToggleHandler;
+ autoOpenToggle: ToggleHandler;
+ clipboardToggle: ToggleHandler;
developerMode: boolean;
toggleDeveloperMode: () => Promise<void>;
knownExchanges: Array<ExchangeListItem>;
@@ -91,7 +95,8 @@ export interface ViewProps {
export function SettingsView({
knownExchanges,
- permissionToggle,
+ autoOpenToggle,
+ clipboardToggle,
developerMode,
coreVersion,
webexVersion,
@@ -102,10 +107,16 @@ export function SettingsView({
return (
<Fragment>
<section>
- {permissionToggle.button.error && (
+ {autoOpenToggle.button.error && (
<ErrorTalerOperation
title={<i18n.Translate>Could not toggle auto-open</i18n.Translate>}
- error={permissionToggle.button.error.errorDetail}
+ error={autoOpenToggle.button.error.errorDetail}
+ />
+ )}
+ {clipboardToggle.button.error && (
+ <ErrorTalerOperation
+ title={<i18n.Translate>Could not toggle clipboard</i18n.Translate>}
+ error={clipboardToggle.button.error.errorDetail}
/>
)}
<SubTitle>
@@ -117,15 +128,31 @@ export function SettingsView({
Automatically open wallet based on page content
</i18n.Translate>
}
- name="perm"
+ name="autoOpen"
+ description={
+ <i18n.Translate>
+ Enabling this option below will make using the wallet faster, but
+ requires more permissions from your browser.
+ </i18n.Translate>
+ }
+ enabled={autoOpenToggle.value!}
+ onToggle={autoOpenToggle.button.onClick!}
+ />
+ <Checkbox
+ label={
+ <i18n.Translate>
+ Automatically check clipboard for Taler URI
+ </i18n.Translate>
+ }
+ name="clipboard"
description={
<i18n.Translate>
Enabling this option below will make using the wallet faster, but
requires more permissions from your browser.
</i18n.Translate>
}
- enabled={permissionToggle.value!}
- onToggle={permissionToggle.button.onClick!}
+ enabled={clipboardToggle.value!}
+ onToggle={clipboardToggle.button.onClick!}
/>
<SubTitle>
diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
index 0f327640e..659a6c2cf 100644
--- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx
@@ -26,12 +26,12 @@ import { Checkbox } from "../components/Checkbox.js";
import { SubTitle, Title } from "../components/styled/index.js";
import { useTranslationContext } from "../context/translation.js";
import { useDiagnostics } from "../hooks/useDiagnostics.js";
-import { useExtendedPermissions } from "../hooks/useExtendedPermissions.js";
+import { useAutoOpenPermissions } from "../hooks/useAutoOpenPermissions.js";
import { ToggleHandler } from "../mui/handlers.js";
import { platform } from "../platform/api.js";
export function WelcomePage(): VNode {
- const permissionToggle = useExtendedPermissions();
+ const permissionToggle = useAutoOpenPermissions();
const [diagnostics, timedOut] = useDiagnostics();
return (
<View
diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts
index 0835aae12..60b250453 100644
--- a/packages/taler-wallet-webextension/src/wxBackend.ts
+++ b/packages/taler-wallet-webextension/src/wxBackend.ts
@@ -330,11 +330,21 @@ export async function wxMain(): Promise<void> {
platform.registerAllIncomingConnections();
try {
- platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
+ platform.registerOnInstalled(() => {
+ platform.openWalletPage("/welcome");
+
+ //
+ try {
+ platform.registerTalerHeaderListener(parseTalerUriAndRedirect);
+ } catch (e) {
+ logger.error("could not register header listener", e);
+ }
+ });
} catch (e) {
- logger.error("could not register header listener", e);
+ console.error(e);
}
+
// On platforms that support it, also listen to external
// modification of permissions.
platform.getPermissionsApi().addPermissionsListener((perm, lastError) => {