Skip to content

Commit

Permalink
fix: duplicate instance of ContentScript causing black screen (#1550)
Browse files Browse the repository at this point in the history
- Fixed duplicate instances of ContentScript after extension
sleep/wakeup. Closes
[#326](FuelLabs/fuel-connectors#326)
- Ensure extension PopUp is closed on service resolve timeout/rejection.
- Ensure extension PopUp is closed on class destroy
  • Loading branch information
arthurgeron authored Oct 6, 2024
1 parent b16cbf4 commit c38db1a
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 16 deletions.
7 changes: 7 additions & 0 deletions .changeset/breezy-glasses-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@fuel-wallet/connections": patch
"@fuel-wallet/types": patch
"fuels-wallet": patch
---

Fixed duplicate instances of ContentScript after extension sleep/wakeup. Closes [#326](https://github.com/FuelLabs/fuel-connectors/issues/326)
5 changes: 5 additions & 0 deletions .changeset/fast-terms-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

Ensure popup is closed on resolve timeout
32 changes: 28 additions & 4 deletions packages/app/src/systems/CRX/background/services/PopUpService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type { MessageInputs, PopUpServiceInputs } from './types';
const popups = new Map<string, PopUpService>();

export class PopUpService {
openingPromise: DeferPromise<PopUpService>;
openingPromise: DeferPromise<PopUpService> | undefined;
session: string | null = null;
tabId: number | null = null;
windowId: number | null = null;
Expand All @@ -37,18 +37,37 @@ export class PopUpService {
// Bind methods to ensure correct `this` context
this.onUIEvent = this.onUIEvent.bind(this);
this.onResponse = this.onResponse.bind(this);
this.rejectOpeningPromise = this.rejectOpeningPromise.bind(this);
this.resolveOpeningPromise = this.resolveOpeningPromise.bind(this);

this.origin = origin;
this.communicationProtocol = communicationProtocol;
this.openingPromise = deferPromise<PopUpService>();
this.setTimeout();
this.client = new JSONRPCClient(this.sendRequest);
this.setupUIListeners();
this.setTimeout();
}

rejectOpeningPromise(message = 'PopUp not opened!') {
if (this.openingPromise) {
this.clearTimeout();
this.openingPromise?.reject(new Error(message));
this.openingPromise = undefined;
closePopUp(this.tabId!);
}
}

resolveOpeningPromise<T extends PopUpService>(resolver: T) {
if (this.openingPromise) {
this.clearTimeout();
this.openingPromise?.resolve(resolver);
this.openingPromise = undefined;
}
}

setTimeout(delay = 5000) {
this.timeoutId = setTimeout(() => {
this.openingPromise.reject(new Error('PopUp not opened!'));
this.rejectOpeningPromise('PopUp timed out waiting for event');
}, delay);
}

Expand Down Expand Up @@ -113,7 +132,7 @@ export class PopUpService {
this.tab = tab!;
this.tabId = tab?.id!;
this.eventId = message.id;
this.openingPromise.resolve(this);
this.resolveOpeningPromise(this);
}
};

Expand Down Expand Up @@ -168,6 +187,10 @@ export class PopUpService {
);
}

if (!popupService.openingPromise?.promise) {
throw new Error('PopUp has no opening promise');
}

return popupService.openingPromise.promise;
};

Expand Down Expand Up @@ -211,6 +234,7 @@ export class PopUpService {
this.removeUIListeners();
this.client.rejectAllPendingRequests('Service is being cleaned up');
popups.delete(this.origin);
closePopUp(this.tabId!);
}

static destroyAll() {
Expand Down
44 changes: 33 additions & 11 deletions packages/app/src/systems/CRX/scripts/executeContentScript.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
import { ContentScriptMessageTypes } from '@fuel-wallet/types';
import fileName from './contentScript?script';

/*
* This function is called by the background script to register the content script
* on all tabs. This is necessary because the content script is not automatically
* registered on tabs that are already open when the extension is installed.
*/

// Ping to check if the content script is already injected
export async function executeContentScript() {
chrome.tabs.query({ url: '<all_urls>' }, (tabs) => {
for (const tab of tabs) {
if (!tab.id || tab.url?.startsWith('chrome://')) continue;
chrome.scripting
.executeScript({
target: { tabId: tab.id, allFrames: true },
files: [fileName],
injectImmediately: true,
})
// Ignore errors on tabs when executing script
.catch((err) => {
if (process.env?.NODE_ENV === 'development') {
console.warn(err);

// Send a ping message to check if content script is already injected
chrome.tabs.sendMessage(
tab.id,
{
type: ContentScriptMessageTypes.PING,
},
(response) => {
if (
response?.type !== ContentScriptMessageTypes.PONG ||
chrome.runtime.lastError
) {
// No response, content script is not injected
injectContentScript(tab.id!);
}
});
}
);
}
});
}

function injectContentScript(tabId: number) {
chrome.scripting
.executeScript({
target: { tabId: tabId, allFrames: true },
files: [fileName],
injectImmediately: true,
})
.catch((err) => {
if (process.env?.NODE_ENV === 'development') {
console.warn(err);
}
});
}
19 changes: 18 additions & 1 deletion packages/connections/src/ContentProxyConnection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { BACKGROUND_SCRIPT_NAME } from '@fuel-wallet/types';
import {
BACKGROUND_SCRIPT_NAME,
ContentScriptMessageTypes,
} from '@fuel-wallet/types';
import {
CONNECTOR_SCRIPT,
CONTENT_SCRIPT_NAME,
Expand Down Expand Up @@ -47,6 +50,7 @@ export class ContentProxyConnection {
name: BACKGROUND_SCRIPT_NAME,
});
connection.onMessage.addListener(this.onMessageFromExtension);
chrome.runtime.onMessage.addListener(this.handlePing);
connection.onDisconnect.addListener(this.onDisconnect);
this.connection = connection;
window.addEventListener(EVENT_MESSAGE, this.onMessageFromWindow);
Expand All @@ -55,6 +59,7 @@ export class ContentProxyConnection {

destroy(keepWindowListener = true) {
this.connection?.onMessage.removeListener(this.onMessageFromExtension);
chrome.runtime.onMessage.removeListener(this.handlePing);
this.connection?.onDisconnect.removeListener(this.onDisconnect);
this.connection?.disconnect();
this.connection = undefined;
Expand Down Expand Up @@ -120,6 +125,18 @@ export class ContentProxyConnection {
}
};

handlePing(
event: { type: string } | undefined,
_: chrome.runtime.MessageSender,
sendResponse: (response: unknown) => void
) {
if (event?.type === ContentScriptMessageTypes.PING) {
sendResponse({
type: ContentScriptMessageTypes.PONG,
});
}
}

postMessage(message: CommunicationMessage) {
const postMessage = {
...message,
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/contentScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ContentScriptMessageTypes {
PING = 'ping',
PONG = 'pong',
}
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './constants';
export * from './abi';
export * from './error';
export * from './database';
export * from './contentScript';

0 comments on commit c38db1a

Please sign in to comment.