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:
@@ -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 =
|
||||||
|
|||||||
@@ -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),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user