1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-02-01 23:01:28 +01:00

[EC-598] feat: add better support for messaging

This commit is contained in:
Andreas Coroiu 2022-11-18 13:12:22 +01:00
parent 02fe5bf0c5
commit e52e3dba45
No known key found for this signature in database
GPG Key ID: E70B5FFC81DFEC1A
4 changed files with 149 additions and 4 deletions

View File

@ -1,6 +1,22 @@
import { MessageType } from "./messaging/message";
import { Messenger } from "./messaging/messenger";
// eslint-disable-next-line no-console
console.log("content-script loaded");
const s = document.createElement("script");
s.src = chrome.runtime.getURL("content/webauthn/page-script.js");
(document.head || document.documentElement).appendChild(s);
const messenger = Messenger.createInExtensionContext(window, chrome.runtime.connect());
messenger.addHandler(async (message) => {
if (message.type === MessageType.CredentialCreationRequest) {
return {
type: MessageType.CredentialCreationResponse,
approved: true,
};
}
return undefined;
});

View File

@ -0,0 +1,42 @@
export enum MessageType {
CredentialCreationRequest,
CredentialCreationResponse,
CredentialGetRequest,
CredentialGetResponse,
AbortRequest,
AbortResponse,
}
export type CredentialCreationRequest = {
type: MessageType.CredentialCreationRequest;
rpId: string;
};
export type CredentialCreationResponse = {
type: MessageType.CredentialCreationResponse;
approved: boolean;
};
export type CredentialGetRequest = {
type: MessageType.CredentialGetRequest;
};
export type CredentialGetResponse = {
type: MessageType.CredentialGetResponse;
};
export type AbortRequest = {
type: MessageType.AbortRequest;
};
export type AbortResponse = {
type: MessageType.AbortResponse;
};
export type Message =
| CredentialCreationRequest
| CredentialCreationResponse
| CredentialGetRequest
| CredentialGetResponse
| AbortRequest
| AbortResponse;

View File

@ -0,0 +1,83 @@
import { concatMap, filter, firstValueFrom, Observable } from "rxjs";
import { Message } from "./message";
type PostMessageFunction = Window["postMessage"] | chrome.runtime.Port["postMessage"];
type Channel = {
messages$: Observable<MessageWithMetadata>;
postMessage: PostMessageFunction;
};
type Metadata = { requestId: string };
type MessageWithMetadata = Message & { metadata: Metadata };
// TODO: This class probably duplicates functionality but I'm not especially familiar with
// the inner workings of the browser extension yet.
// If you see this in a code review please comment on it!
export class Messenger {
static createInPageContext(window: Window) {
return new Messenger({
postMessage: window.postMessage.bind(window),
messages$: new Observable((subscriber) => {
const eventListener = (event: MessageEvent<MessageWithMetadata>) => {
subscriber.next(event.data);
};
window.addEventListener("message", eventListener);
return () => window.removeEventListener("message", eventListener);
}),
});
}
static createInExtensionContext(window: Window, port: chrome.runtime.Port) {
return new Messenger({
postMessage: window.postMessage.bind(window),
messages$: new Observable((subscriber) => {
const eventListener = (event: MessageEvent<MessageWithMetadata>) => {
subscriber.next(event.data);
};
window.addEventListener("message", eventListener);
return () => window.removeEventListener("message", eventListener);
}),
});
}
private constructor(private channel: Channel) {}
request(request: Message): Promise<Message> {
const requestId = Date.now().toString();
const metadata: Metadata = { requestId };
const promise = firstValueFrom(
this.channel.messages$.pipe(
filter((m) => m.metadata.requestId === requestId && m.type !== request.type)
)
);
this.channel.postMessage({ ...request, metadata });
return promise;
}
addHandler(handler: (message: Message) => Promise<Message | undefined>) {
this.channel.messages$
.pipe(
concatMap(async (message) => {
const handlerResponse = await handler(message);
if (handlerResponse === undefined) {
return;
}
const metadata: Metadata = { requestId: message.metadata.requestId };
this.channel.postMessage({ ...handlerResponse, metadata });
})
)
.subscribe();
}
}

View File

@ -1,3 +1,6 @@
import { MessageType } from "./messaging/message";
import { Messenger } from "./messaging/messenger";
// eslint-disable-next-line no-console
console.log("page-script loaded");
@ -6,16 +9,17 @@ const browserCredentials = {
get: navigator.credentials.get.bind(navigator.credentials),
};
// Intercept
const messenger = Messenger.createInPageContext(window);
navigator.credentials.create = async (options?: CredentialCreationOptions): Promise<Credential> => {
alert("Intercepted: create");
await messenger.request({
type: MessageType.CredentialCreationRequest,
rpId: options.publicKey.rp.id,
});
return await browserCredentials.create(options);
};
navigator.credentials.get = async (options?: CredentialRequestOptions): Promise<Credential> => {
alert("Intercepted: get");
return await browserCredentials.get(options);
};