Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: DB restart should reset service worker communication protocol and services #1230

Merged
merged 17 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/angry-cats-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-wallet/types": patch
"fuels-wallet": patch
---

fixes service worker services not restarting communication protocol when DB closes or blocks
4 changes: 3 additions & 1 deletion packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { Providers } from '~/systems/Core';
import { Providers, useRecoverWelcomeFromError } from '~/systems/Core';

import { IS_DEVELOPMENT, IS_TEST } from './config';
import { getRoutes } from './routes';
Expand All @@ -9,6 +9,8 @@ const ThrowError = React.lazy(
);

export function App() {
useRecoverWelcomeFromError();

return (
<Providers>
{getRoutes()}
Expand Down
14 changes: 12 additions & 2 deletions packages/app/src/systems/CRX/background/actions/onInstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ import { executeContentScript } from '../../scripts/executeContentScript';
// Execute everytime the background service starts
executeContentScript();

chrome.runtime.onInstalled.addListener((object) => {
if (object.reason === chrome.runtime.OnInstalledReason.INSTALL) {
chrome.runtime.onInstalled.addListener(async (object) => {
const { shouldRecoverWelcomeFromError } = await chrome.storage.local.get(
'shouldRecoverWelcomeFromError'
);

chrome.storage.local.remove('shouldRecoverWelcomeFromError');

if (
shouldRecoverWelcomeFromError ||
object.reason === chrome.runtime.OnInstalledReason.INSTALL
) {
chrome.tabs.create({ url: welcomeLink() });
}

executeContentScript();
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { POPUP_SCRIPT_NAME, VAULT_SCRIPT_NAME } from '@fuel-wallet/types';
import { MessageTypes } from '@fuels/connectors';
import {
type DatabaseRestartEvent,
POPUP_SCRIPT_NAME,
VAULT_SCRIPT_NAME,
} from '@fuel-wallet/types';
import { MessageTypes, type RequestMessage } from '@fuels/connectors';
import { AUTO_LOCK_IN_MINUTES } from '~/config';
import { VaultServer } from '~/systems/Vault/services/VaultServer';

Expand All @@ -11,6 +15,7 @@ import {
saveSecret,
} from '../../utils';

import { db } from '../../../../systems/Core/utils/database';
import type { CommunicationProtocol } from './CommunicationProtocol';

export class VaultService extends VaultServer {
Expand All @@ -24,6 +29,15 @@ export class VaultService extends VaultServer {
this.setupListeners();
}

async checkVaultIntegrity() {
// Ensure integrity of database connection
const dbLoadedCorrectly = (await db.open().catch(() => false)) && true;
const secret = await loadSecret().catch(() => null);
const isLocked = await super.isLocked().catch(() => true);

return dbLoadedCorrectly && (!isLocked || !!(secret && isLocked));
}

async unlock({ password }: { password: string }): Promise<void> {
await super.unlock({ password });
saveSecret(password, AUTO_LOCK_IN_MINUTES);
Expand Down Expand Up @@ -72,7 +86,7 @@ export class VaultService extends VaultServer {
}

setupListeners() {
this.communicationProtocol.on(MessageTypes.request, async (event) => {
const handleRequest = async (event: RequestMessage) => {
if (!event.sender?.origin?.includes(chrome.runtime.id)) return;
if (event.sender?.id !== chrome.runtime.id) return;
if (event.target !== VAULT_SCRIPT_NAME) return;
Expand All @@ -85,7 +99,22 @@ export class VaultService extends VaultServer {
response,
});
}
});
};

const handleRestartEvent = async (message: DatabaseRestartEvent) => {
const { type: eventType, payload } = message ?? {};
const integrity = await this.checkVaultIntegrity();

if (
eventType === 'DB_EVENT' &&
payload.event === 'restarted' &&
!integrity
) {
this.resetAndReload();
}
};
chrome.runtime.onMessage.addListener(handleRestartEvent);
this.communicationProtocol.on(MessageTypes.request, handleRequest);
}

emitLockEvent() {
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/systems/Core/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './useIsLogged';
export * from './useUnlockForm';
export * from './useRecoverWelcomeFromError';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { DatabaseRestartEvent } from '@fuel-wallet/types';
import { useLayoutEffect } from 'react';
import { IS_CRX } from '../../../config';

/**
* @description This hook is used to detect and flag the welcome screen to be recovered from an error state.
*/
export function useRecoverWelcomeFromError() {
useLayoutEffect(() => {
const handleRestartEvent = async (message: DatabaseRestartEvent) => {
const { type: eventType, payload } = message ?? {};
const isErrorState =
eventType === 'DB_EVENT' && payload.event === 'restarted';

if (isErrorState) {
chrome.storage.local.set({ shouldRecoverWelcomeFromError: true });
}
};

if (IS_CRX) {
chrome.runtime.onMessage.addListener(handleRestartEvent);
}

return () => {
chrome.runtime.onMessage.removeListener(handleRestartEvent);
};
}, []);
}
22 changes: 22 additions & 0 deletions packages/app/src/systems/Core/utils/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
Account,
AssetData,
Connection,
DatabaseRestartEvent,
FuelWalletError,
NetworkData,
Vault,
Expand All @@ -23,6 +24,7 @@ export class FuelDB extends Dexie {
assets!: Table<AssetData, string>;
abis!: Table<AbiTable, string>;
errors!: Table<FuelWalletError, string>;
readonly alwaysOpen = true;

constructor() {
super('FuelDB');
Expand All @@ -49,6 +51,26 @@ export class FuelDB extends Dexie {
id: createUUID(),
});
});
this.on('blocked', () => this.restart('closed'));
this.on('close', () => this.restart('blocked'));
}

async restart(eventName: 'blocked' | 'closed') {
if (!this.alwaysOpen) {
return;
}
if (eventName !== 'closed') {
this.close();
}

this.open();

chrome.runtime.sendMessage({
type: 'DB_EVENT',
payload: {
event: 'restarted',
},
} as DatabaseRestartEvent);
}

async clear() {
Expand Down
9 changes: 8 additions & 1 deletion packages/app/src/systems/Vault/services/VaultServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type VaultInputs = {

export class VaultServer extends EventEmitter {
readonly server: JSONRPCServer;
readonly manager: WalletManager;
manager: WalletManager;
static readonly methods: Array<string> = [
'isLocked',
'unlock',
Expand Down Expand Up @@ -194,6 +194,13 @@ export class VaultServer extends EventEmitter {
await this.manager.removeVault(vault.vaultId);
}
}

resetAndReload() {
const storage = new IndexedDBStorage();
const manager = new WalletManager({ storage });
this.manager = manager;
chrome.runtime.reload();
}
}

export type VaultMethods = {
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/database.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface DatabaseRestartEvent {
type: 'DB_EVENT';
payload: {
event: 'restarted';
};
}
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './fuel';
export * from './constants';
export * from './abi';
export * from './error';
export * from './database';
Loading