Transition from manifest v2 to v3 for chrome (#1367)
* Update manifest.json * replace `extension.getUrl` to `runtime.getUrl` * Update localStorage to async chrome.storage.local * Change action to browserAction * fix bug * revert async changes * revert test change * Fix localStorage breaking change * .extension -> .runtime * fix undefined behaviour * fixed tests * lint * Up some callbacks to async * Change where extractMetadata is called * fix in mock * Update background field to take in type * Change background.js to service_worker.js * Update id and request origin for service worker * updated chrome mock * 13 tests passing * Fix async issue * switch naming back to original * linting * Fix linting in base * remove timeOut * added permission to alarms in manifest * fixed alarms uncaught errors * Cleanup addr for tests --------- Co-authored-by: bee344 <alberto.penayo@parity.io>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
import '@polkadot/extension-mocks/chrome';
|
||||
|
||||
import type { AuthUrls, ResponseSigning } from '@polkadot/extension-base/background/types';
|
||||
import type { ResponseSigning } from '@polkadot/extension-base/background/types';
|
||||
import type { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import type { KeyringPair } from '@polkadot/keyring/types';
|
||||
import type { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types';
|
||||
@@ -29,24 +29,15 @@ describe('Extension', () => {
|
||||
let tabs: Tabs;
|
||||
const suri = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
const password = 'passw0rd';
|
||||
const address = '5FbSap4BsWfjyRhCchoVdZHkDnmDm3NEgLZ25mesq4aw2WvX';
|
||||
|
||||
async function createExtension (): Promise<Extension> {
|
||||
try {
|
||||
await cryptoWaitReady();
|
||||
|
||||
keyring.loadAll({ store: new AccountsStore() });
|
||||
const authUrls: AuthUrls = {};
|
||||
|
||||
authUrls['localhost:3000'] = {
|
||||
authorizedAccounts: [address],
|
||||
count: 0,
|
||||
id: '11',
|
||||
origin: 'example.com',
|
||||
url: 'http://localhost:3000'
|
||||
};
|
||||
localStorage.setItem('authUrls', JSON.stringify(authUrls));
|
||||
state = new State();
|
||||
await state.init();
|
||||
tabs = new Tabs(state);
|
||||
|
||||
return new Extension(state);
|
||||
@@ -313,7 +304,7 @@ describe('Extension', () => {
|
||||
userExtensions
|
||||
};
|
||||
|
||||
state.saveMetadata(meta);
|
||||
await state.saveMetadata(meta);
|
||||
|
||||
const payload: SignerPayloadJSON = {
|
||||
address,
|
||||
@@ -388,7 +379,7 @@ describe('Extension', () => {
|
||||
userExtensions
|
||||
};
|
||||
|
||||
state.saveMetadata(meta);
|
||||
await state.saveMetadata(meta);
|
||||
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
@@ -444,7 +435,7 @@ describe('Extension', () => {
|
||||
userExtensions
|
||||
};
|
||||
|
||||
state.saveMetadata(meta);
|
||||
await state.saveMetadata(meta);
|
||||
|
||||
const payload = {
|
||||
address,
|
||||
|
||||
@@ -116,7 +116,7 @@ export default class Extension {
|
||||
};
|
||||
}
|
||||
|
||||
private accountsForget ({ address }: RequestAccountForget): boolean {
|
||||
private async accountsForget ({ address }: RequestAccountForget): Promise<boolean> {
|
||||
const authorizedAccountsDiff: AuthorizedAccountsDiff = [];
|
||||
|
||||
// cycle through authUrls and prepare the array of diff
|
||||
@@ -128,12 +128,12 @@ export default class Extension {
|
||||
}
|
||||
});
|
||||
|
||||
this.#state.updateAuthorizedAccounts(authorizedAccountsDiff);
|
||||
await this.#state.updateAuthorizedAccounts(authorizedAccountsDiff);
|
||||
|
||||
// cycle through default account selection for auth and remove any occurrence of the account
|
||||
const newDefaultAuthAccounts = this.#state.defaultAuthAccountSelection.filter((defaultSelectionAddress) => defaultSelectionAddress !== address);
|
||||
|
||||
this.#state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
|
||||
await this.#state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
|
||||
|
||||
keyring.forgetAccount(address);
|
||||
|
||||
@@ -212,8 +212,8 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private authorizeUpdate ({ authorizedAccounts, url }: RequestUpdateAuthorizedAccounts): void {
|
||||
return this.#state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
|
||||
private async authorizeUpdate ({ authorizedAccounts, url }: RequestUpdateAuthorizedAccounts): Promise<void> {
|
||||
return await this.#state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
|
||||
}
|
||||
|
||||
private getAuthList (): ResponseAuthorizeList {
|
||||
@@ -235,14 +235,14 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private metadataApprove ({ id }: RequestMetadataApprove): boolean {
|
||||
private async metadataApprove ({ id }: RequestMetadataApprove): Promise<boolean> {
|
||||
const queued = this.#state.getMetaRequest(id);
|
||||
|
||||
assert(queued, 'Unable to find request');
|
||||
|
||||
const { request, resolve } = queued;
|
||||
|
||||
this.#state.saveMetadata(request);
|
||||
await this.#state.saveMetadata(request);
|
||||
|
||||
resolve(true);
|
||||
|
||||
@@ -467,7 +467,7 @@ export default class Extension {
|
||||
}
|
||||
|
||||
private windowOpen (path: AllowedPath): boolean {
|
||||
const url = `${chrome.extension.getURL('index.html')}#${path}`;
|
||||
const url = `${chrome.runtime.getURL('index.html')}#${path}`;
|
||||
|
||||
if (!ALLOWED_PATH.includes(path)) {
|
||||
console.error('Not allowed to open the url:', url);
|
||||
@@ -518,8 +518,10 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private removeAuthorization (url: string): ResponseAuthorizeList {
|
||||
return { list: this.#state.removeAuthorization(url) };
|
||||
private async removeAuthorization (url: string): Promise<ResponseAuthorizeList> {
|
||||
const remAuth = await this.#state.removeAuthorization(url);
|
||||
|
||||
return { list: remAuth };
|
||||
}
|
||||
|
||||
private deleteAuthRequest (requestId: string): void {
|
||||
@@ -593,7 +595,7 @@ export default class Extension {
|
||||
return this.accountsValidate(request as RequestAccountValidate);
|
||||
|
||||
case 'pri(metadata.approve)':
|
||||
return this.metadataApprove(request as RequestMetadataApprove);
|
||||
return await this.metadataApprove(request as RequestMetadataApprove);
|
||||
|
||||
case 'pri(metadata.get)':
|
||||
return this.metadataGet(request as string);
|
||||
|
||||
@@ -59,7 +59,7 @@ interface SignRequest extends Resolver<ResponseSigning> {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const NOTIFICATION_URL = chrome.extension.getURL('notification.html');
|
||||
const NOTIFICATION_URL = chrome.runtime.getURL('notification.html');
|
||||
|
||||
const POPUP_WINDOW_OPTS: chrome.windows.CreateData = {
|
||||
focused: true,
|
||||
@@ -86,8 +86,8 @@ export enum NotificationOptions {
|
||||
const AUTH_URLS_KEY = 'authUrls';
|
||||
const DEFAULT_AUTH_ACCOUNTS = 'defaultAuthAccounts';
|
||||
|
||||
function extractMetadata (store: MetadataStore): void {
|
||||
store.allMap((map): void => {
|
||||
async function extractMetadata (store: MetadataStore): Promise<void> {
|
||||
await store.allMap(async (map): Promise<void> => {
|
||||
const knownEntries = Object.entries(knownGenesis);
|
||||
const defs: Record<string, { def: MetadataDef, index: number, key: string }> = {};
|
||||
const removals: string[] = [];
|
||||
@@ -117,13 +117,16 @@ function extractMetadata (store: MetadataStore): void {
|
||||
}
|
||||
});
|
||||
|
||||
removals.forEach((key) => store.remove(key));
|
||||
for (const key of removals) {
|
||||
await store.remove(key);
|
||||
}
|
||||
|
||||
Object.values(defs).forEach(({ def }) => addMetadata(def));
|
||||
});
|
||||
}
|
||||
|
||||
export default class State {
|
||||
readonly #authUrls: AuthUrls = {};
|
||||
#authUrls: AuthUrls = {};
|
||||
|
||||
readonly #authRequests: Record<string, AuthRequest> = {};
|
||||
|
||||
@@ -155,17 +158,20 @@ export default class State {
|
||||
|
||||
constructor (providers: Providers = {}) {
|
||||
this.#providers = providers;
|
||||
}
|
||||
|
||||
extractMetadata(this.#metaStore);
|
||||
|
||||
public async init () {
|
||||
await extractMetadata(this.#metaStore);
|
||||
// retrieve previously set authorizations
|
||||
const authString = localStorage.getItem(AUTH_URLS_KEY) || '{}';
|
||||
const storageAuthUrls: Record<string, string> = await chrome.storage.local.get(AUTH_URLS_KEY);
|
||||
const authString = storageAuthUrls?.[AUTH_URLS_KEY] || '{}';
|
||||
const previousAuth = JSON.parse(authString) as AuthUrls;
|
||||
|
||||
this.#authUrls = previousAuth;
|
||||
|
||||
// retrieve previously set default auth accounts
|
||||
const defaultAuthString = localStorage.getItem(DEFAULT_AUTH_ACCOUNTS) || '[]';
|
||||
const storageDefaultAuthAccounts: Record<string, string> = await chrome.storage.local.get(DEFAULT_AUTH_ACCOUNTS);
|
||||
const defaultAuthString: string = storageDefaultAuthAccounts?.[DEFAULT_AUTH_ACCOUNTS] || '[]';
|
||||
const previousDefaultAuth = JSON.parse(defaultAuthString) as string[];
|
||||
|
||||
this.defaultAuthAccountSelection = previousDefaultAuth;
|
||||
@@ -209,6 +215,10 @@ export default class State {
|
||||
return this.#authUrls;
|
||||
}
|
||||
|
||||
private set authUrls (urls: AuthUrls) {
|
||||
this.#authUrls = urls;
|
||||
}
|
||||
|
||||
private popupClose (): void {
|
||||
this.#windows.forEach((id: number) =>
|
||||
withErrorLog(() => chrome.windows.remove(id))
|
||||
@@ -230,7 +240,7 @@ export default class State {
|
||||
}
|
||||
|
||||
private authComplete = (id: string, resolve: (resValue: AuthResponse) => void, reject: (error: Error) => void): Resolver<AuthResponse> => {
|
||||
const complete = (authorizedAccounts: string[] = []) => {
|
||||
const complete = async (authorizedAccounts: string[] = []) => {
|
||||
const { idStr, request: { origin }, url } = this.#authRequests[id];
|
||||
|
||||
this.#authUrls[this.stripUrl(url)] = {
|
||||
@@ -241,19 +251,21 @@ export default class State {
|
||||
url
|
||||
};
|
||||
|
||||
this.saveCurrentAuthList();
|
||||
this.updateDefaultAuthAccounts(authorizedAccounts);
|
||||
await this.saveCurrentAuthList();
|
||||
await this.updateDefaultAuthAccounts(authorizedAccounts);
|
||||
delete this.#authRequests[id];
|
||||
this.updateIconAuth(true);
|
||||
};
|
||||
|
||||
return {
|
||||
reject: (error: Error): void => {
|
||||
complete();
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
reject: async (error: Error): Promise<void> => {
|
||||
await complete();
|
||||
reject(error);
|
||||
},
|
||||
resolve: ({ authorizedAccounts, result }: AuthResponse): void => {
|
||||
complete(authorizedAccounts);
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
resolve: async ({ authorizedAccounts, result }: AuthResponse): Promise<void> => {
|
||||
await complete(authorizedAccounts);
|
||||
resolve({ authorizedAccounts, result });
|
||||
}
|
||||
};
|
||||
@@ -289,17 +301,17 @@ export default class State {
|
||||
this.updateIconAuth(true);
|
||||
}
|
||||
|
||||
private saveCurrentAuthList () {
|
||||
localStorage.setItem(AUTH_URLS_KEY, JSON.stringify(this.#authUrls));
|
||||
private async saveCurrentAuthList () {
|
||||
await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(this.#authUrls) });
|
||||
}
|
||||
|
||||
private saveDefaultAuthAccounts () {
|
||||
localStorage.setItem(DEFAULT_AUTH_ACCOUNTS, JSON.stringify(this.defaultAuthAccountSelection));
|
||||
private async saveDefaultAuthAccounts () {
|
||||
await chrome.storage.local.set({ [DEFAULT_AUTH_ACCOUNTS]: JSON.stringify(this.defaultAuthAccountSelection) });
|
||||
}
|
||||
|
||||
public updateDefaultAuthAccounts (newList: string[]) {
|
||||
public async updateDefaultAuthAccounts (newList: string[]) {
|
||||
this.defaultAuthAccountSelection = newList;
|
||||
this.saveDefaultAuthAccounts();
|
||||
await this.saveDefaultAuthAccounts();
|
||||
}
|
||||
|
||||
private metaComplete = (id: string, resolve: (result: boolean) => void, reject: (error: Error) => void): Resolver<boolean> => {
|
||||
@@ -358,20 +370,20 @@ export default class State {
|
||||
: (signCount ? `${signCount}` : '')
|
||||
);
|
||||
|
||||
withErrorLog(() => chrome.browserAction.setBadgeText({ text }));
|
||||
withErrorLog(() => chrome.action.setBadgeText({ text }));
|
||||
|
||||
if (shouldClose && text === '') {
|
||||
this.popupClose();
|
||||
}
|
||||
}
|
||||
|
||||
public removeAuthorization (url: string): AuthUrls {
|
||||
public async removeAuthorization (url: string): Promise<AuthUrls> {
|
||||
const entry = this.#authUrls[url];
|
||||
|
||||
assert(entry, `The source ${url} is not known`);
|
||||
|
||||
delete this.#authUrls[url];
|
||||
this.saveCurrentAuthList();
|
||||
await this.saveCurrentAuthList();
|
||||
|
||||
return this.#authUrls;
|
||||
}
|
||||
@@ -391,12 +403,12 @@ export default class State {
|
||||
this.updateIcon(shouldClose);
|
||||
}
|
||||
|
||||
public updateAuthorizedAccounts (authorizedAccountDiff: AuthorizedAccountsDiff): void {
|
||||
public async updateAuthorizedAccounts (authorizedAccountDiff: AuthorizedAccountsDiff): Promise<void> {
|
||||
authorizedAccountDiff.forEach(([url, authorizedAccountDiff]) => {
|
||||
this.#authUrls[url].authorizedAccounts = authorizedAccountDiff;
|
||||
});
|
||||
|
||||
this.saveCurrentAuthList();
|
||||
await this.saveCurrentAuthList();
|
||||
}
|
||||
|
||||
public async authorizeUrl (url: string, request: RequestAuthorizeTab): Promise<AuthResponse> {
|
||||
@@ -539,8 +551,8 @@ export default class State {
|
||||
return provider.unsubscribe(request.type, request.method, request.subscriptionId);
|
||||
}
|
||||
|
||||
public saveMetadata (meta: MetadataDef): void {
|
||||
this.#metaStore.set(meta.genesisHash, meta);
|
||||
public async saveMetadata (meta: MetadataDef): Promise<void> {
|
||||
await this.#metaStore.set(meta.genesisHash, meta);
|
||||
|
||||
addMetadata(meta);
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ export default class Tabs {
|
||||
private redirectPhishingLanding (phishingWebsite: string): void {
|
||||
const nonFragment = phishingWebsite.split('#')[0];
|
||||
const encodedWebsite = encodeURIComponent(nonFragment);
|
||||
const url = `${chrome.extension.getURL('index.html')}#${PHISHING_PAGE_REDIRECT}/${encodedWebsite}`;
|
||||
const url = `${chrome.runtime.getURL('index.html')}#${PHISHING_PAGE_REDIRECT}/${encodedWebsite}`;
|
||||
|
||||
chrome.tabs.query({ url: nonFragment }, (tabs) => {
|
||||
tabs
|
||||
|
||||
@@ -15,6 +15,8 @@ import Tabs from './Tabs.js';
|
||||
export { withErrorLog } from './helpers.js';
|
||||
|
||||
const state = new State();
|
||||
|
||||
await state.init();
|
||||
const extension = new Extension(state);
|
||||
const tabs = new Tabs(state);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class AccountsStore extends BaseStore<KeyringJson> implements Key
|
||||
);
|
||||
}
|
||||
|
||||
public override set (key: string, value: KeyringJson, update?: () => void): void {
|
||||
public override async set (key: string, value: KeyringJson, update?: () => void): Promise<void> {
|
||||
// shortcut, don't save testing accounts in extension storage
|
||||
if (key.startsWith('account:') && value.meta && value.meta.isTesting) {
|
||||
update && update();
|
||||
@@ -23,6 +23,6 @@ export default class AccountsStore extends BaseStore<KeyringJson> implements Key
|
||||
return;
|
||||
}
|
||||
|
||||
super.set(key, value, update);
|
||||
await super.set(key, value, update);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,19 @@ export default abstract class BaseStore <T> {
|
||||
this.#prefix = prefix ? `${prefix}:` : '';
|
||||
}
|
||||
|
||||
public all (update: (key: string, value: T) => void): void {
|
||||
this.allMap((map): void => {
|
||||
Object.entries(map).forEach(([key, value]): void => {
|
||||
update(key, value);
|
||||
});
|
||||
public async all (update: (key: string, value: T) => void): Promise<void> {
|
||||
await this.allMap(async (map): Promise<void> => {
|
||||
const entries = Object.entries(map);
|
||||
|
||||
for (const [key, value] of entries) {
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await update(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public allMap (update: (value: Record<string, T>) => void): void {
|
||||
chrome.storage.local.get(null, (result: StoreValue): void => {
|
||||
public async allMap (update: (value: Record<string, T>) => Promise<void>): Promise<void> {
|
||||
await chrome.storage.local.get(null).then(async (result: StoreValue) => {
|
||||
lastError('all');
|
||||
|
||||
const entries = Object.entries(result);
|
||||
@@ -43,37 +46,48 @@ export default abstract class BaseStore <T> {
|
||||
}
|
||||
}
|
||||
|
||||
update(map);
|
||||
await update(map);
|
||||
}).catch(({ message }: Error) => {
|
||||
console.error(`BaseStore error within allMap: ${message}`);
|
||||
});
|
||||
}
|
||||
|
||||
public get (key: string, update: (value: T) => void): void {
|
||||
public async get (key: string, update: (value: T) => void): Promise<void> {
|
||||
const prefixedKey = `${this.#prefix}${key}`;
|
||||
|
||||
chrome.storage.local.get([prefixedKey], (result: StoreValue): void => {
|
||||
await chrome.storage.local.get([prefixedKey]).then(async (result: StoreValue) => {
|
||||
lastError('get');
|
||||
|
||||
update(result[prefixedKey] as T);
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await update(result[prefixedKey] as T);
|
||||
}).catch(({ message }: Error) => {
|
||||
console.error(`BaseStore error within get: ${message}`);
|
||||
});
|
||||
}
|
||||
|
||||
public remove (key: string, update?: () => void): void {
|
||||
public async remove (key: string, update?: () => void): Promise<void> {
|
||||
const prefixedKey = `${this.#prefix}${key}`;
|
||||
|
||||
chrome.storage.local.remove(prefixedKey, (): void => {
|
||||
await chrome.storage.local.remove(prefixedKey).then(async () => {
|
||||
lastError('remove');
|
||||
|
||||
update && update();
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
update && await update();
|
||||
}).catch(({ message }: Error) => {
|
||||
console.error(`BaseStore error within remove: ${message}`);
|
||||
});
|
||||
}
|
||||
|
||||
public set (key: string, value: T, update?: () => void): void {
|
||||
public async set (key: string, value: T, update?: () => void): Promise<void> {
|
||||
const prefixedKey = `${this.#prefix}${key}`;
|
||||
|
||||
chrome.storage.local.set({ [prefixedKey]: value }, (): void => {
|
||||
await chrome.storage.local.set({ [prefixedKey]: value }).then(async () => {
|
||||
lastError('set');
|
||||
|
||||
update && update();
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
update && await update();
|
||||
}).catch(({ message }: Error) => {
|
||||
console.error(`BaseStore error within set: ${message}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import chrome from 'sinon-chrome';
|
||||
import chrome from './chromeWrapper';
|
||||
|
||||
class MessagingFake {
|
||||
private listeners: ((...params: unknown[]) => unknown)[] = [];
|
||||
@@ -28,6 +28,36 @@ const messagingFake = new MessagingFake();
|
||||
|
||||
chrome.runtime.connect.returns(messagingFake);
|
||||
|
||||
chrome.storage.local.get.returns(
|
||||
new Promise<Record<string, any>>((resolve, reject) => {
|
||||
try {
|
||||
const result = {
|
||||
authUrls: JSON.stringify({
|
||||
'localhost:3000': {
|
||||
authorizedAccounts: ['5FbSap4BsWfjyRhCchoVdZHkDnmDm3NEgLZ25mesq4aw2WvX'],
|
||||
count: 0,
|
||||
id: '11',
|
||||
origin: 'example.com',
|
||||
url: 'http://localhost:3000'
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}));
|
||||
|
||||
chrome.storage.local.set.returns(
|
||||
new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access
|
||||
(window as any).chrome = (globalThis as any).chrome = chrome;
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019-2024 @polkadot/extension-mocks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import sinonChrome from 'sinon-chrome';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace ChromeWrapper {
|
||||
export interface IAction {
|
||||
setBadgeText: (content: object) => Promise<void>;
|
||||
}
|
||||
export const action: IAction = {
|
||||
setBadgeText: (_: object) => {
|
||||
return new Promise<void>((resolve, _reject) => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
const extendedSinonChrome = {
|
||||
...sinonChrome,
|
||||
action: ChromeWrapper.action
|
||||
};
|
||||
|
||||
export default extendedSinonChrome;
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2019-2024 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global chrome */
|
||||
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { PASSWORD_EXPIRY_MIN } from '@polkadot/extension-base/defaults';
|
||||
@@ -30,25 +32,46 @@ function SignArea ({ buttonText, className, error, isExternal, isFirst, setError
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLocked(null);
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
const lockSigner = async () => {
|
||||
setIsLocked(null);
|
||||
|
||||
try {
|
||||
const { isLocked, remainingTime } = await isSignLocked(signId);
|
||||
|
||||
!isExternal && isSignLocked(signId)
|
||||
.then(({ isLocked, remainingTime }) => {
|
||||
setIsLocked(isLocked);
|
||||
timeout = setTimeout(() => {
|
||||
setIsLocked(true);
|
||||
}, remainingTime);
|
||||
|
||||
// if the account was unlocked check the remember me
|
||||
// automatically to prolong the unlock period
|
||||
!isLocked && setSavePass(true);
|
||||
})
|
||||
.catch((error: Error) => console.error(error));
|
||||
await chrome.alarms.create('SIGNER_TIMEOUT', { delayInMinutes: remainingTime / 60000 });
|
||||
|
||||
return () => {
|
||||
!!timeout && clearTimeout(timeout);
|
||||
if (!isLocked) {
|
||||
setSavePass(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error locking signer:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const resetAlarm = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.alarms.clear('SIGNER_TIMEOUT', (cleared) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve(cleared);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const executeLockSigner = async () => {
|
||||
if (!isExternal) {
|
||||
await lockSigner();
|
||||
await resetAlarm();
|
||||
}
|
||||
};
|
||||
|
||||
executeLockSigner().then(async () => {
|
||||
return await executeLockSigner();
|
||||
}).catch((error) => console.error('Error clearing the alarm: ', error));
|
||||
}, [isExternal, signId]);
|
||||
|
||||
const _onSign = useCallback(
|
||||
@@ -80,7 +103,7 @@ function SignArea ({ buttonText, className, error, isExternal, isFirst, setError
|
||||
const RememberPasswordCheckbox = () => (
|
||||
<Checkbox
|
||||
checked={savePass}
|
||||
label={ isLocked
|
||||
label={isLocked
|
||||
? t(
|
||||
'Remember my password for the next {{expiration}} minutes',
|
||||
{ replace: { expiration: PASSWORD_EXPIRY_MIN } }
|
||||
@@ -130,7 +153,7 @@ function SignArea ({ buttonText, className, error, isExternal, isFirst, setError
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(SignArea)<Props>`
|
||||
export default styled(SignArea) <Props>`
|
||||
flex-direction: column;
|
||||
padding: 6px 24px;
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ export default function Popup (): React.ReactElement {
|
||||
const [settingsCtx, setSettingsCtx] = useState<SettingsStruct>(startSettings);
|
||||
const history = useHistory();
|
||||
|
||||
console.log('WINDOW; ', window);
|
||||
|
||||
const _onAction = useCallback(
|
||||
(to?: string): void => {
|
||||
setWelcomeDone(window.localStorage.getItem('welcome_read') === 'ok');
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2019-2024 @polkadot/extension-ui authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
/* global chrome */
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { ToastContext } from '../index.js';
|
||||
import Toast from './Toast.js';
|
||||
@@ -11,18 +13,37 @@ interface ToastProviderProps {
|
||||
}
|
||||
|
||||
const TOAST_TIMEOUT = 1500;
|
||||
const ALARM_NAME = 'toastAlarm';
|
||||
|
||||
const ToastProvider = ({ children }: ToastProviderProps): React.ReactElement<ToastProviderProps> => {
|
||||
const [content, setContent] = useState('');
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const show = useCallback((message: string): () => void => {
|
||||
const timerId = setTimeout(() => setVisible(false), TOAST_TIMEOUT);
|
||||
useEffect(() => {
|
||||
const createAlarm = async () => {
|
||||
try {
|
||||
await chrome.alarms.create(ALARM_NAME, { delayInMinutes: TOAST_TIMEOUT / 60000 });
|
||||
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||
if (alarm.name === ALARM_NAME) {
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating the alarm:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
createAlarm().then(async () => {
|
||||
return await chrome.alarms.clear(ALARM_NAME);
|
||||
}).catch((error) => console.error('Error clearing the alarm: ', error));
|
||||
}, []);
|
||||
|
||||
const show = useCallback((message: string): (() => void) => {
|
||||
setContent(message);
|
||||
setVisible(true);
|
||||
|
||||
return (): void => clearTimeout(timerId);
|
||||
return (): void => setVisible(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"homepage_url": "https://github.com/polkadot-js/extension",
|
||||
"name": "polkadot{.js} extension",
|
||||
"short_name": "polkadot{.js}",
|
||||
"manifest_version": 2,
|
||||
"permissions": ["storage", "tabs"],
|
||||
"manifest_version": 3,
|
||||
"permissions": ["storage", "tabs", "alarms"],
|
||||
"background": {
|
||||
"scripts": ["background.js"],
|
||||
"persistent": true
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_title": "polkadot{.js}",
|
||||
"default_popup": "index.html"
|
||||
},
|
||||
@@ -26,6 +26,17 @@
|
||||
"64": "images/icon-64.png",
|
||||
"128": "images/icon-128.png"
|
||||
},
|
||||
"web_accessible_resources": ["page.js"],
|
||||
"content_security_policy": "script-src 'self' blob: 'unsafe-eval' 'wasm-eval'; object-src 'self'"
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"page.js"
|
||||
],
|
||||
"matches": [
|
||||
"*://*/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { assert } from '@polkadot/util';
|
||||
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||
|
||||
// setup the notification (same a FF default background, white text)
|
||||
withErrorLog(() => chrome.browserAction.setBadgeBackgroundColor({ color: '#d90000' }));
|
||||
withErrorLog(() => chrome.action.setBadgeBackgroundColor({ color: '#d90000' }));
|
||||
|
||||
// listen to all messages and handle appropriately
|
||||
chrome.runtime.onConnect.addListener((port): void => {
|
||||
|
||||
@@ -27,7 +27,7 @@ window.addEventListener('message', ({ data, source }: Message): void => {
|
||||
// inject our data injector
|
||||
const script = document.createElement('script');
|
||||
|
||||
script.src = chrome.extension.getURL('page.js');
|
||||
script.src = chrome.runtime.getURL('page.js');
|
||||
|
||||
script.onload = (): void => {
|
||||
// remove the injecting tag when loaded
|
||||
|
||||
Reference in New Issue
Block a user