1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[EC-598] feat: add initial one-way support for aborting

This commit is contained in:
Andreas Coroiu
2023-01-27 15:38:01 +01:00
parent 18dbaf2a4e
commit 3c5900250d
3 changed files with 85 additions and 27 deletions

View File

@@ -40,10 +40,12 @@ export type CredentialGetResponse = {
export type AbortRequest = { export type AbortRequest = {
type: MessageType.AbortRequest; type: MessageType.AbortRequest;
abortedRequestId: string;
}; };
export type AbortResponse = { export type AbortResponse = {
type: MessageType.AbortResponse; type: MessageType.AbortResponse;
abortedRequestId: string;
}; };
export type Message = export type Message =

View File

@@ -18,8 +18,8 @@ describe("Messenger", () => {
handlerA = new TestMessageHandler(); handlerA = new TestMessageHandler();
handlerB = new TestMessageHandler(); handlerB = new TestMessageHandler();
messengerA.addHandler(handlerA.handler); messengerA.handler = handlerA.handler;
messengerB.addHandler(handlerB.handler); messengerB.handler = handlerB.handler;
}); });
it("should deliver message to B when sending request from A", () => { it("should deliver message to B when sending request from A", () => {
@@ -43,6 +43,25 @@ describe("Messenger", () => {
expect(returned).toMatchObject(response); expect(returned).toMatchObject(response);
}); });
it("should deliver abort signal to B when requesting abort", () => {
const abortController = new AbortController();
messengerA.request(createRequest(), abortController);
abortController.abort();
const received = handlerB.recieve();
expect(received[0].abortController.signal.aborted).toBe(true);
});
it.skip("should abort request and throw error when abort is requested from A", () => {
const abortController = new AbortController();
const requestPromise = messengerA.request(createRequest(), abortController);
abortController.abort();
expect(requestPromise).toThrow();
});
}); });
type TestMessage = Message & { testId: string }; type TestMessage = Message & { testId: string };
@@ -78,16 +97,23 @@ class TestChannelPair {
} }
class TestMessageHandler { class TestMessageHandler {
readonly handler: (message: TestMessage) => Promise<Message | undefined>; readonly handler: (
message: TestMessage,
abortController?: AbortController
) => Promise<Message | undefined>;
private recievedMessages: { message: TestMessage; respond: (response: TestMessage) => void }[] = private recievedMessages: {
[]; message: TestMessage;
respond: (response: TestMessage) => void;
abortController?: AbortController;
}[] = [];
constructor() { constructor() {
this.handler = (message) => this.handler = (message, abortController) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
this.recievedMessages.push({ this.recievedMessages.push({
message, message,
abortController,
respond: (response) => resolve(response), respond: (response) => resolve(response),
}); });
}); });

View File

@@ -1,6 +1,6 @@
import { concatMap, filter, firstValueFrom, Observable } from "rxjs"; import { concatMap, filter, firstValueFrom, Observable } from "rxjs";
import { Message } from "./message"; import { Message, MessageType } from "./message";
type PostMessageFunction = (message: MessageWithMetadata) => void; type PostMessageFunction = (message: MessageWithMetadata) => void;
@@ -11,6 +11,10 @@ export type Channel = {
export type Metadata = { requestId: string }; export type Metadata = { requestId: string };
export type MessageWithMetadata = Message & { metadata: Metadata }; export type MessageWithMetadata = Message & { metadata: Metadata };
type Handler = (
message: Message,
abortController?: AbortController
) => Promise<Message | undefined>;
// TODO: This class probably duplicates functionality but I'm not especially familiar with // TODO: This class probably duplicates functionality but I'm not especially familiar with
// the inner workings of the browser extension yet. // the inner workings of the browser extension yet.
@@ -32,9 +36,42 @@ export class Messenger {
}); });
} }
constructor(private channel: Channel) {} handler?: Handler;
abortControllers = new Map<string, AbortController>();
request(request: Message): Promise<Message> { constructor(private channel: Channel) {
this.channel.messages$
.pipe(
concatMap(async (message) => {
if (this.handler === undefined) {
return;
}
const abortController = new AbortController();
this.abortControllers.set(message.metadata.requestId, abortController);
const handlerResponse = await this.handler(message, abortController);
this.abortControllers.delete(message.metadata.requestId);
if (handlerResponse === undefined) {
return;
}
const metadata: Metadata = { requestId: message.metadata.requestId };
this.channel.postMessage({ ...handlerResponse, metadata });
})
)
.subscribe();
this.channel.messages$.subscribe((message) => {
if (message.type !== MessageType.AbortRequest) {
return;
}
this.abortControllers.get(message.abortedRequestId)?.abort();
});
}
request(request: Message, abortController?: AbortController): Promise<Message> {
const requestId = Date.now().toString(); const requestId = Date.now().toString();
const metadata: Metadata = { requestId }; const metadata: Metadata = { requestId };
@@ -46,25 +83,18 @@ export class Messenger {
) )
); );
const abortListener = () =>
this.channel.postMessage({
metadata: { requestId: `${requestId}-abort` },
type: MessageType.AbortRequest,
abortedRequestId: requestId,
});
abortController?.signal.addEventListener("abort", abortListener);
this.channel.postMessage({ ...request, metadata }); this.channel.postMessage({ ...request, metadata });
return promise; return promise.finally(() =>
} abortController?.signal.removeEventListener("abort", abortListener)
);
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();
} }
} }