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

don't retain port in content scripts #285

Merged
merged 1 commit into from
Feb 19, 2025
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: 4 additions & 2 deletions apps/extension/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"$schema": "https://json.schemastore.org/chrome-manifest",
"manifest_version": 3,
"name": "Prax wallet",
"version": "15.1.0",
Expand All @@ -17,8 +18,9 @@
"content_scripts": [
{
"matches": ["https://*/*", "http://localhost/*"],
"js": ["injected-connection-port.js", "injected-request-listener.js"],
"run_at": "document_start"
"js": ["injected-listeners.js"],
"run_at": "document_start",
"world": "ISOLATED"
},
{
"matches": ["https://*/*", "http://localhost/*"],
Expand Down
37 changes: 0 additions & 37 deletions apps/extension/src/content-scripts/injected-connection-port.ts

This file was deleted.

102 changes: 102 additions & 0 deletions apps/extension/src/content-scripts/injected-listeners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { PenumbraRequestFailure } from '@penumbra-zone/client/error';
import { CRSessionClient } from '@penumbra-zone/transport-chrome/session-client';
import { PraxConnection } from '../message/prax';
import { PraxMessage, isPraxMessageEvent, unwrapPraxMessageEvent } from './message-event';

const endMessage = { [PRAX]: PraxConnection.End } satisfies PraxMessage<PraxConnection.End>;
const portMessage = (port: MessagePort) => ({ [PRAX]: port }) satisfies PraxMessage<MessagePort>;
const requestFailureMessage = (failure?: unknown): PraxMessage<PenumbraRequestFailure> =>
typeof failure === 'string' && failure in PenumbraRequestFailure
? { [PRAX]: failure }
: { [PRAX]: PenumbraRequestFailure.BadResponse };

const praxRequest = async (req: PraxConnection.Connect | PraxConnection.Disconnect) => {
console.debug('praxRequest req', req);
const res = await chrome.runtime
.sendMessage<
PraxConnection.Connect | PraxConnection.Disconnect,
null | PenumbraRequestFailure
>(req)
.catch(e => {
console.debug('praxRequest error', e);
return PenumbraRequestFailure.NotHandled;
});
console.debug('praxRequest res', res);
return res;
};

const praxDocumentListener = (ev: MessageEvent<unknown>) => {
if (ev.origin === window.origin && isPraxMessageEvent(ev)) {
const req = unwrapPraxMessageEvent(ev);
if (typeof req === 'string' && req in PraxConnection) {
console.debug('window event', req);

void (async () => {
let response: unknown;

switch (req as PraxConnection) {
case PraxConnection.Connect:
console.debug('using window event', PraxConnection.Connect);
response = await praxRequest(PraxConnection.Connect);
break;
case PraxConnection.Disconnect:
console.debug('using window event', PraxConnection.Disconnect);
response = await praxRequest(PraxConnection.Disconnect);
break;
default: // message is not for this handler
return;
}

// response should be null, or content for a failure message
if (response != null) {
// failure, send failure message
console.debug('window event failure', response);
window.postMessage(requestFailureMessage(response), '/');
} else {
// success, no response
console.debug('window event success');
}
})();
}
}
};

const praxExtensionListener = (
req: unknown,
sender: chrome.runtime.MessageSender,
ok: (no?: never) => void,
) => {
if (sender.id === PRAX && typeof req === 'string' && req in PraxConnection) {
console.debug('extension event', req);

switch (req as PraxConnection) {
case PraxConnection.Init: {
console.debug('using extension event', req);
const port = CRSessionClient.init(PRAX);
window.postMessage(portMessage(port), '/', [port]);
break;
}
case PraxConnection.End: {
console.debug('using extension event', req);
window.postMessage(endMessage, '/');
break;
}
default: // message is not for this handler
return false;
}

// success, send empty response
ok();
return true;
} else {
// message is not for this handler
return false;
}
};

// attach
window.addEventListener('message', praxDocumentListener);
chrome.runtime.onMessage.addListener(praxExtensionListener);
Comment on lines +98 to +99
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add code comments to the two event listeners, praxDocumentListener and praxExtensionListener?


// announce
void chrome.runtime.sendMessage(PraxConnection.Init);
40 changes: 23 additions & 17 deletions apps/extension/src/content-scripts/injected-penumbra-global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

import '@penumbra-zone/client/global';

import { createPenumbraStateEvent, type PenumbraProvider } from '@penumbra-zone/client';
import { createPenumbraStateEvent } from '@penumbra-zone/client/event';
import type { PenumbraProvider } from '@penumbra-zone/client/provider';
Comment on lines +22 to +23
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not strictly necessary, but due to poor webpack tree-shaking, importing from the root package apparently caused the generated script to be very large.

a more specific import significantly reduces the size of the content script.

import { PenumbraState } from '@penumbra-zone/client/state';
import { PenumbraSymbol } from '@penumbra-zone/client/symbol';

Expand Down Expand Up @@ -51,7 +52,6 @@ class PraxInjection {
return new PraxInjection().injection;
}

private port?: MessagePort;
private presentState: PenumbraState = PenumbraState.Disconnected;
private manifestUrl = `${PRAX_ORIGIN}/manifest.json`;
private stateEvents = new EventTarget();
Expand All @@ -63,13 +63,11 @@ class PraxInjection {
* @todo Remove when bundled frontends are updated beyond `a31d54a`
* @issue https://github.com/prax-wallet/web/issues/175
*/
request: async () => {
await Promise.resolve(this.port ?? this.postConnectRequest());
},
request: () => this.postConnectRequest(),

connect: () => Promise.resolve(this.port ?? this.postConnectRequest()),
connect: () => this.postConnectRequest(),
disconnect: () => this.postDisconnectRequest(),
isConnected: () => Boolean(this.port && this.presentState === PenumbraState.Connected),
isConnected: () => this.presentState === PenumbraState.Connected,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would like to do some more investigation to identify possibly unknown problems with these changes to state transitions

state: () => this.presentState,
manifest: String(this.manifestUrl),
addEventListener: this.stateEvents.addEventListener.bind(this.stateEvents),
Expand All @@ -84,20 +82,17 @@ class PraxInjection {
window.postMessage(initMessage, '/');
}

private setConnected(port: MessagePort) {
this.port = port;
private setConnected() {
this.presentState = PenumbraState.Connected;
this.stateEvents.dispatchEvent(createPenumbraStateEvent(PRAX_ORIGIN, this.presentState));
}

private setDisconnected() {
this.port = undefined;
this.presentState = PenumbraState.Disconnected;
this.stateEvents.dispatchEvent(createPenumbraStateEvent(PRAX_ORIGIN, this.presentState));
}

private setPending() {
this.port = undefined;
this.presentState = PenumbraState.Pending;
this.stateEvents.dispatchEvent(createPenumbraStateEvent(PRAX_ORIGIN, this.presentState));
}
Expand All @@ -108,10 +103,19 @@ class PraxInjection {
return attempt;
}

private postDisconnectRequest() {
const attempt = this.listenEndMessage();
window.postMessage(disconnectMessage, '/', []);
return attempt;
}
Comment on lines +106 to +110
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulled this out into its own method to match the structure of the connect request logic


private listenPortMessage() {
this.setPending();
if (this.presentState !== PenumbraState.Connected) {
this.setPending();
}
Comment on lines +113 to +115
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change needs some closer inspection too


const connection = Promise.withResolvers<MessagePort>();

const listener = (msg: MessageEvent<unknown>) => {
if (msg.origin === window.origin) {
if (isPraxPortMessageEvent(msg)) {
Expand All @@ -125,16 +129,20 @@ class PraxInjection {
};

void connection.promise
.then(port => this.setConnected(port))
.then(() => this.setConnected())
.catch(() => this.setDisconnected())
.finally(() => window.removeEventListener('message', listener));

window.addEventListener('message', listener);

return connection.promise;
}

private postDisconnectRequest() {
private listenEndMessage() {
this.setDisconnected();

const disconnection = Promise.withResolvers<void>();

const listener = (msg: MessageEvent<unknown>) => {
if (msg.origin === window.origin) {
if (isPraxEndMessageEvent(msg)) {
Expand All @@ -147,11 +155,9 @@ class PraxInjection {
}
};

this.setDisconnected();
void disconnection.promise.finally(() => window.removeEventListener('message', listener));
window.addEventListener('message', listener);

window.postMessage(disconnectMessage, '/');
window.addEventListener('message', listener);

return disconnection.promise;
}
Expand Down
45 changes: 0 additions & 45 deletions apps/extension/src/content-scripts/injected-request-listener.ts

This file was deleted.

6 changes: 2 additions & 4 deletions apps/extension/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ export default ({

return {
entry: {
'injected-connection-port': path.join(injectDir, 'injected-connection-port.ts'),
'injected-listeners': path.join(injectDir, 'injected-listeners.ts'),
'injected-penumbra-global': path.join(injectDir, 'injected-penumbra-global.ts'),
'injected-request-listener': path.join(injectDir, 'injected-request-listener.ts'),
'offscreen-handler': path.join(entryDir, 'offscreen-handler.ts'),
'page-root': path.join(entryDir, 'page-root.tsx'),
'popup-root': path.join(entryDir, 'popup-root.tsx'),
Expand All @@ -129,9 +128,8 @@ export default ({
splitChunks: {
chunks: chunk => {
const filesNotToChunk = [
'injected-connection-port',
'injected-listeners',
'injected-penumbra-global',
'injected-request-listner',
'service-worker',
'wasm-build-action',
];
Expand Down