Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
turbocrime committed Jun 21, 2024
1 parent d11e642 commit 3e0e75c
Show file tree
Hide file tree
Showing 21 changed files with 1,192 additions and 985 deletions.
3 changes: 1 addition & 2 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
"description": "chrome-extension",
"type": "module",
"scripts": {
"build": "pnpm bundle",
"bundle": "NODE_OPTIONS=\"--import=./src/utils/webpack-register.js\" webpack",
"build": "NODE_OPTIONS=\"--import=./src/utils/webpack-register.js\" webpack",
"clean": "rm -rfv dist bin",
"dev": "NODE_ENV=testnet pnpm bundle --watch --mode=development -d inline-source-map",
"lint": "eslint src",
Expand Down
77 changes: 0 additions & 77 deletions apps/extension/src/approve-origin.ts

This file was deleted.

20 changes: 17 additions & 3 deletions apps/extension/src/content-scripts/injected-connection-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,28 @@ import { PraxMessage } from './message-event';
import { CRSessionClient } from '@penumbra-zone/transport-chrome/session-client';
import { PraxConnection } from '../message/prax';

// this inits the session client that transports messages on the DOM channel through the Chrome runtime
const initOnce = (req: unknown, _sender: chrome.runtime.MessageSender, respond: () => void) => {
// content script unconditionally announces itself to extension background.
void chrome.runtime.sendMessage(PraxConnection.Init);

// listen for init command from background. this may arrive soon after announce,
// or much later, after a request is made. this activates the channel session
// that transports messages from the DOM channel into the Chrome runtime
const initOnce = (
req: unknown,
// in a content script, sender is always an extension background script
_: chrome.runtime.MessageSender,
// this handler will only ever send an empty response
emptyResponse: (no?: never) => void,
) => {
if (req !== PraxConnection.Init) return false;

chrome.runtime.onMessage.removeListener(initOnce);

// create session, post port to window where the injected global can catch it
const port = CRSessionClient.init(PRAX);
window.postMessage({ [PRAX]: port } satisfies PraxMessage<MessagePort>, '/', [port]);
respond();

emptyResponse();
return true;
};

Expand Down
17 changes: 5 additions & 12 deletions apps/extension/src/content-scripts/injected-penumbra-global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,12 @@ const requestResponseListener = (msg: MessageEvent<unknown>) => {
if (msg.origin === window.origin) {
if (isPraxFailureMessageEvent(msg)) {
// @ts-expect-error - ts can't understand the injected string
const status = msg.data[PRAX] as PraxConnection;
const status: unknown = msg.data[PRAX];
const failure = new Error('Connection request failed');
switch (status) {
case PraxConnection.Denied:
failure.cause = PenumbraRequestFailure.Denied;
break;
case PraxConnection.NeedsLogin:
failure.cause = PenumbraRequestFailure.NeedsLogin;
break;
default:
failure.cause = 'Unknown';
break;
}
failure.cause =
typeof status === 'string' && status in PenumbraRequestFailure
? status
: `Unknown failure: ${String(status)}`;
request.reject(failure);
}
}
Expand Down
19 changes: 9 additions & 10 deletions apps/extension/src/content-scripts/injected-request-listener.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { PraxMessage, isPraxRequestMessageEvent } from './message-event';
import { PraxConnection } from '../message/prax';
import { PenumbraRequestFailure } from '@penumbra-zone/client';

const handleRequest = (ev: MessageEvent<unknown>) => {
if (ev.origin === window.origin && isPraxRequestMessageEvent(ev)) {
void (async () => {
window.removeEventListener('message', handleRequest);
const result = await chrome.runtime.sendMessage<

// any response to this message only indicates failure. success is
// resolved upon successful connection, and those messages are handled by
// the script in injected-connection-port
const failure = await chrome.runtime.sendMessage<
PraxConnection,
Exclude<PraxConnection, PraxConnection.Request>
undefined | PenumbraRequestFailure
>(PraxConnection.Request);
// init is handled by injected-connection-port
if (result !== PraxConnection.Init)
window.postMessage(
{ [PRAX]: result } satisfies PraxMessage<
PraxConnection.Denied | PraxConnection.NeedsLogin
>,
'/',
);
if (failure)
window.postMessage({ [PRAX]: failure } satisfies PraxMessage<PenumbraRequestFailure>, '/');
})();
}
};
Expand Down
5 changes: 3 additions & 2 deletions apps/extension/src/content-scripts/message-event.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PenumbraRequestFailure } from '@penumbra-zone/client';
import { PraxConnection } from '../message/prax';

// @ts-expect-error - ts can't understand the injected string
Expand All @@ -18,11 +19,11 @@ export const isPraxRequestMessageEvent = (

export const isPraxFailureMessageEvent = (
ev: MessageEvent<unknown>,
): ev is MessageEvent<PraxMessage<PraxConnection.Denied | PraxConnection.NeedsLogin>> => {
): ev is MessageEvent<PraxMessage<PenumbraRequestFailure>> => {
if (!isPraxMessageEventData(ev.data)) return false;
// @ts-expect-error - ts can't understand the injected string
const status = ev.data[PRAX] as unknown;
return status === PraxConnection.Denied || status === PraxConnection.NeedsLogin;
return typeof status === 'string' && status in PenumbraRequestFailure;
};

export const isPraxPortMessageEvent = (
Expand Down
2 changes: 1 addition & 1 deletion apps/extension/src/listeners/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import './message-external';
import './tabs-updated-prax-init';
import './message-prax-init';
import './message-prax-request';
5 changes: 2 additions & 3 deletions apps/extension/src/listeners/message-external.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable */

// this is temporary code to use the externally_connectable permission,
// also providing an easter egg for curious users
chrome.runtime.onMessageExternal.addListener((_, __, response) => {
return response('penumbra is the key');
response('penumbra is the key');
return true;
});
20 changes: 20 additions & 0 deletions apps/extension/src/listeners/message-prax-init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { alreadyApprovedOrigin } from '../origins/approve-origin';
import { PraxConnection } from '../message/prax';
import { assertValidSender } from '../origins/valid-sender';

// trigger injected-connection-port when a known page inits.
chrome.runtime.onMessage.addListener(
(req: unknown, sender, emptyResponse: (no?: never) => void) => {
if (req !== PraxConnection.Init) return false;
emptyResponse();

void (async () => {
const validSender = assertValidSender(sender);
const alreadyApproved = await alreadyApprovedOrigin(validSender.origin);
if (alreadyApproved)
void chrome.runtime.sendMessage(PraxConnection.Init, { documentId: sender.documentId });
})();

return true;
},
);
30 changes: 17 additions & 13 deletions apps/extension/src/listeners/message-prax-request.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
import { Code, ConnectError } from '@connectrpc/connect';
import { approveOrigin } from '../approve-origin';
import { approveOrigin } from '../origins/approve-origin';
import { PraxConnection } from '../message/prax';
import { JsonValue } from '@bufbuild/protobuf';
import { UserChoice } from '@penumbra-zone/types/user-choice';
import { PenumbraRequestFailure } from '@penumbra-zone/client';
import { assertValidSender } from '../origins/valid-sender';

// listen for page connection requests.
// this is the only message we handle from an unapproved content script.
chrome.runtime.onMessage.addListener(
(req: PraxConnection.Request | JsonValue, sender, respond: (arg: PraxConnection) => void) => {
(req, sender, respond: (failure?: PenumbraRequestFailure) => void) => {
if (req !== PraxConnection.Request) return false; // instruct chrome we will not respond

void approveOrigin(sender).then(
status => {
const { documentId } = assertValidSender(sender);
// user made a choice
if (status === UserChoice.Approved) {
respond(PraxConnection.Init);
void chrome.tabs.sendMessage(sender.tab!.id!, PraxConnection.Init, {
documentId: sender.documentId, // Ensures tab has not redirected to another url
});
respond();
void chrome.runtime.sendMessage(PraxConnection.Init, { documentId });
} else {
respond(PraxConnection.Denied);
respond(PenumbraRequestFailure.Denied);
}
},
e => {
if (globalThis.__DEV__) {
console.warn('Connection request listener failed:', e);
}
if (globalThis.__DEV__) console.warn('Connection request listener failed:', e);

if (e instanceof ConnectError && e.code === Code.Unauthenticated) {
respond(PraxConnection.NeedsLogin);
respond(PenumbraRequestFailure.NeedsLogin);
} else {
respond(PraxConnection.Denied);
setTimeout(
() => respond(PenumbraRequestFailure.Denied),
// this was either an error or an automatic denial. apply a random
// rejection delay between 2 and 12 seconds to obfuscate
2000 + Math.random() * 10000,
);
}
},
);
Expand Down
28 changes: 0 additions & 28 deletions apps/extension/src/listeners/tabs-updated-prax-init.ts

This file was deleted.

2 changes: 0 additions & 2 deletions apps/extension/src/message/prax.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export enum PraxConnection {
Init = 'Init',
Request = 'Request',
Denied = 'Denied',
NeedsLogin = 'NeedsLogin',
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { approveOrigin } from './approve-origin';
import { UserChoice } from '@penumbra-zone/types/user-choice';
import { OriginRecord } from './storage/types';
import { PopupType } from './message/popup';
import { OriginRecord } from '../storage/types';
import { PopupType } from '../message/popup';

const mockLocalStorage = vi.hoisted(() => ({
get: vi.fn(),
Expand Down
Loading

0 comments on commit 3e0e75c

Please sign in to comment.