Files
pezkuwi-extension/packages/extension/src/background.ts
T
pezkuwichain f5495f7cac fix: ensure crypto initialized before handling messages
Fixed race condition where message handlers could process requests
before cryptoWaitReady() and keyring.loadAll() completed.

Now all message handlers wait for initialization before processing,
which fixes the seed creation failure on first account creation.
2026-02-02 19:34:50 +03:00

126 lines
4.1 KiB
TypeScript

// Copyright 2019-2026 @pezkuwi/extension authors & contributors
// SPDX-License-Identifier: Apache-2.0
// Runs in the extension background, handling all keyring access
/* global chrome */
import '@pezkuwi/extension-inject/crossenv';
import type { RequestSignatures, TransportRequestMessage } from '@pezkuwi/extension-base/background/types';
import { handlers, withErrorLog } from '@pezkuwi/extension-base/background';
import { PORT_CONTENT, PORT_EXTENSION } from '@pezkuwi/extension-base/defaults';
import { AccountsStore } from '@pezkuwi/extension-base/stores';
import { keyring } from '@pezkuwi/ui-keyring';
import { assert } from '@pezkuwi/util';
import { cryptoWaitReady } from '@pezkuwi/util-crypto';
// setup the notification (same a FF default background, white text)
withErrorLog(() => chrome.action.setBadgeBackgroundColor({ color: '#d90000' }));
// Initialization promise - handlers must wait for this
let initPromise: Promise<void> | null = null;
function initializeExtension (): Promise<void> {
if (!initPromise) {
initPromise = cryptoWaitReady()
.then((): void => {
console.log('crypto initialized');
keyring.loadAll({ store: new AccountsStore(), type: 'sr25519' });
console.log('initialization completed');
});
}
return initPromise;
}
// Start initialization immediately
initializeExtension().catch((error): void => {
console.error('initialization failed', error);
});
// listen to all messages and handle appropriately
chrome.runtime.onConnect.addListener((port): void => {
// shouldn't happen, however... only listen to what we know about
assert([PORT_CONTENT, PORT_EXTENSION].includes(port.name), `Unknown connection from ${port.name}`);
// message and disconnect handlers - wait for init before handling
port.onMessage.addListener((data: TransportRequestMessage<keyof RequestSignatures>) => {
initializeExtension()
.then(() => handlers(data, port))
.catch((error) => console.error('Handler error:', error));
});
port.onDisconnect.addListener(() => console.log(`Disconnected from ${port.name}`));
});
function isValidUrl (url: string) {
try {
const urlObj = new URL(url);
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
} catch (_e) {
return false;
}
}
function getActiveTabs () {
// queriing the current active tab in the current window should only ever return 1 tab
// although an array is specified here
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
// get the urls of the active tabs. Only http or https urls are supported. Other urls will be filtered out.
// e.g. browser tabs like chrome://newtab/, chrome://extensions/, about:addons etc will be filtered out
// we filter these out
const urls: string[] = tabs
.map(({ url }) => url)
.filter((url) => !!url && isValidUrl(url)) as string[];
const request: TransportRequestMessage<'pri(activeTabsUrl.update)'> = {
id: 'background',
message: 'pri(activeTabsUrl.update)',
origin: 'background',
request: { urls }
};
// Wait for initialization before handling
initializeExtension()
.then(() => handlers(request))
.catch((error) => console.error('Handler error:', error));
});
}
chrome.runtime.onMessage.addListener((message: { type: string }, _, sendResponse) => {
if (message.type === 'wakeup') {
sendResponse({ status: 'awake' });
}
});
// listen to tab updates this is fired on url change
chrome.tabs.onUpdated.addListener((_, changeInfo) => {
// we are only interested in url change
if (!changeInfo.url) {
return;
}
getActiveTabs();
});
// the list of active tab changes when switching window
// in a mutli window setup
chrome.windows.onFocusChanged.addListener(() =>
getActiveTabs()
);
// when clicking on an existing tab or opening a new tab this will be fired
// before the url is entered by users
chrome.tabs.onActivated.addListener(() => {
getActiveTabs();
});
// when deleting a tab this will be fired
chrome.tabs.onRemoved.addListener(() => {
getActiveTabs();
});
// Note: initialization is handled by initializeExtension() above