Skip to content

Commit

Permalink
Transition from manifest v2 to v3 for chrome (#1367)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
TarikGul and bee344 authored Jul 8, 2024
1 parent 5d36b76 commit b6d42c2
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 107 deletions.
21 changes: 6 additions & 15 deletions packages/extension-base/src/background/handlers/Extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -313,7 +304,7 @@ describe('Extension', () => {
userExtensions
};

state.saveMetadata(meta);
await state.saveMetadata(meta);

const payload: SignerPayloadJSON = {
address,
Expand Down Expand Up @@ -388,7 +379,7 @@ describe('Extension', () => {
userExtensions
};

state.saveMetadata(meta);
await state.saveMetadata(meta);

const registry = new TypeRegistry();

Expand Down Expand Up @@ -444,7 +435,7 @@ describe('Extension', () => {
userExtensions
};

state.saveMetadata(meta);
await state.saveMetadata(meta);

const payload = {
address,
Expand Down
24 changes: 13 additions & 11 deletions packages/extension-base/src/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);

Expand Down Expand Up @@ -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 {
Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
70 changes: 41 additions & 29 deletions packages/extension-base/src/background/handlers/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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[] = [];
Expand Down Expand Up @@ -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> = {};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))
Expand All @@ -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)] = {
Expand All @@ -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 });
}
};
Expand Down Expand Up @@ -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> => {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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> {
Expand Down Expand Up @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/extension-base/src/background/handlers/Tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packages/extension-base/src/background/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions packages/extension-base/src/stores/Accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ 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();

return;
}

super.set(key, value, update);
await super.set(key, value, update);
}
}
Loading

0 comments on commit b6d42c2

Please sign in to comment.