Skip to content

Commit 36cd3bb

Browse files
authored
Create single message bridge and simplify message types (#1461)
1 parent 4a58340 commit 36cd3bb

File tree

9 files changed

+40
-123
lines changed

9 files changed

+40
-123
lines changed

.changeset/lazy-teachers-roll.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"apollo-client-devtools": patch
3+
---
4+
5+
Create a universal message bridge to handle both actor and rpc messages.

src/extension/__tests__/actor.test.ts

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -222,38 +222,3 @@ test("re-adds listener on adapter when subscribing actor listener after disconne
222222
actor.on("connect", handleConnect);
223223
expect(adapter.addListener).toHaveBeenCalledTimes(1);
224224
});
225-
226-
test("forwards messages to another actor", () => {
227-
type Message = { type: "connect"; payload: string } | { type: "disconnect" };
228-
229-
const proxyAdapter = createTestAdapter<Message>();
230-
const actorAdapter = createTestAdapter<Message>();
231-
const proxy = createActor<Message>(proxyAdapter);
232-
const actor = createActor<Message>(actorAdapter);
233-
234-
proxy.forward("connect", actor);
235-
proxy.forward("disconnect", actor);
236-
237-
proxyAdapter.simulateDevtoolsMessage({ type: "connect", payload: "Hello!" });
238-
239-
expect(actorAdapter.postMessage).toHaveBeenCalledTimes(1);
240-
expect(actorAdapter.postMessage).toHaveBeenCalledWith({
241-
id: expect.any(String),
242-
source: "apollo-client-devtools",
243-
type: MessageType.Event,
244-
message: {
245-
type: "connect",
246-
payload: "Hello!",
247-
},
248-
});
249-
250-
proxyAdapter.simulateDevtoolsMessage({ type: "disconnect" });
251-
252-
expect(actorAdapter.postMessage).toHaveBeenCalledTimes(2);
253-
expect(actorAdapter.postMessage).toHaveBeenCalledWith({
254-
id: expect.any(String),
255-
source: "apollo-client-devtools",
256-
type: MessageType.Event,
257-
message: { type: "disconnect" },
258-
});
259-
});

src/extension/__tests__/rpc.test.ts

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { DistributiveOmit } from "../../types";
22
import { RPC_MESSAGE_TIMEOUT } from "../errorMessages";
33
import type { MessageAdapter } from "../messageAdapters";
4+
import { createMessageBridge } from "../messageAdapters";
45
import type { RPCMessage, RPCRequestMessage } from "../messages";
56
import { MessageType } from "../messages";
6-
import { createRPCBridge, createRpcClient, createRpcHandler } from "../rpc";
7+
import { createRpcClient, createRpcHandler } from "../rpc";
78

89
interface TestAdapter extends MessageAdapter<RPCMessage> {
910
mocks: { listeners: Set<(message: unknown) => void>; messages: unknown[] };
@@ -524,7 +525,7 @@ test("forwards rpc messages from one adapter to another with bridge", () => {
524525
const adapter1 = createTestAdapter();
525526
const adapter2 = createTestAdapter();
526527

527-
createRPCBridge(adapter1, adapter2);
528+
createMessageBridge(adapter1, adapter2);
528529

529530
adapter1.simulateRPCMessage({
530531
id: "abc",
@@ -563,7 +564,7 @@ test("unsubscribes connection on bridge when calling returned function", () => {
563564
const adapter1 = createTestAdapter();
564565
const adapter2 = createTestAdapter();
565566

566-
const unsubscribe = createRPCBridge(adapter1, adapter2);
567+
const unsubscribe = createMessageBridge(adapter1, adapter2);
567568

568569
adapter1.simulateRPCMessage({
569570
id: "abc",
@@ -601,21 +602,3 @@ test("unsubscribes connection on bridge when calling returned function", () => {
601602
});
602603
expect(adapter1.postMessage).not.toHaveBeenCalled();
603604
});
604-
605-
test.each([MessageType.Event])(
606-
"does not forward %s messages",
607-
(messageType) => {
608-
const adapter1 = createTestAdapter();
609-
const adapter2 = createTestAdapter();
610-
611-
createRPCBridge(adapter1, adapter2);
612-
613-
adapter1.simulateMessage({
614-
id: 1,
615-
type: messageType,
616-
payload: { type: "add", params: { x: 1, y: 2 } },
617-
});
618-
619-
expect(adapter2.postMessage).not.toHaveBeenCalled();
620-
}
621-
);

src/extension/actor.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type {
33
MessageFormat,
44
} from "./messages";
55
import { MessageType, isEventMessage } from "./messages";
6-
import type { NoInfer } from "../types";
76
import type { MessageAdapter } from "./messageAdapters";
87
import { createWindowMessageAdapter } from "./messageAdapters";
98
import { createId } from "../utils/createId";
@@ -16,10 +15,6 @@ export interface Actor<Messages extends MessageFormat> {
1615
: never
1716
) => () => void;
1817
send: (message: Messages) => void;
19-
forward: <TName extends Messages["type"]>(
20-
name: TName,
21-
actor: Actor<Extract<Messages, { type: NoInfer<TName> }>>
22-
) => () => void;
2318
}
2419

2520
export function createActor<
@@ -96,8 +91,6 @@ export function createActor<
9691
message,
9792
});
9893
},
99-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
100-
forward: (name, actor) => on(name, actor.send as unknown as any),
10194
};
10295
}
10396

src/extension/devtools/devtools.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
import browser from "webextension-polyfill";
22
import type { Actor } from "../actor";
3-
import { createActor } from "../actor";
4-
import type { ClientMessage, PanelMessage } from "../messages";
3+
import type { PanelMessage } from "../messages";
54
import { getPanelActor } from "./panelActor";
65
import {
6+
createMessageBridge,
77
createPortMessageAdapter,
88
createWindowMessageAdapter,
99
} from "../messageAdapters";
10-
import { createRPCBridge } from "../rpc";
1110

1211
const inspectedTabId = browser.devtools.inspectedWindow.tabId;
1312

1413
const portAdapter = createPortMessageAdapter(() =>
1514
browser.runtime.connect({ name: inspectedTabId.toString() })
1615
);
1716

18-
const clientPort = createActor<ClientMessage>(portAdapter);
19-
2017
let connectedToPanel = false;
2118
let panelWindow: Actor<PanelMessage>;
2219

@@ -33,14 +30,7 @@ async function createDevtoolsPanel() {
3330
if (connectedToPanel) {
3431
panelWindow.send({ type: "panelShown" });
3532
} else {
36-
createRPCBridge(createWindowMessageAdapter(window), portAdapter);
37-
38-
clientPort.forward("explorerResponse", panelWindow);
39-
clientPort.forward("registerClient", panelWindow);
40-
clientPort.forward("clientTerminated", panelWindow);
41-
42-
panelWindow.forward("explorerRequest", clientPort);
43-
panelWindow.forward("explorerSubscriptionTermination", clientPort);
33+
createMessageBridge(createWindowMessageAdapter(window), portAdapter);
4434

4535
panelWindow.send({ type: "initializePanel" });
4636

src/extension/messageAdapters.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type browser from "webextension-polyfill";
2+
import { isDevtoolsMessage } from "./messages";
23
import type { ApolloClientDevtoolsMessage } from "./messages";
4+
import type { SafeAny } from "../types";
35

46
export interface MessageAdapter<
57
PostMessageFormat extends ApolloClientDevtoolsMessage<
@@ -88,3 +90,25 @@ export function createWindowMessageAdapter<
8890
},
8991
};
9092
}
93+
94+
export function createMessageBridge(
95+
adapter1: MessageAdapter<SafeAny>,
96+
adapter2: MessageAdapter<SafeAny>
97+
) {
98+
const removeListener1 = adapter1.addListener((message) => {
99+
if (isDevtoolsMessage(message)) {
100+
adapter2.postMessage(message);
101+
}
102+
});
103+
104+
const removeListener2 = adapter2.addListener((message) => {
105+
if (isDevtoolsMessage(message)) {
106+
adapter1.postMessage(message);
107+
}
108+
});
109+
110+
return () => {
111+
removeListener1();
112+
removeListener2();
113+
};
114+
}

src/extension/messages.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export type DevtoolsRPCMessage = {
119119
getCache(clientId: string): JSONObject;
120120
};
121121

122-
function isDevtoolsMessage<Message extends Record<string, unknown>>(
122+
export function isDevtoolsMessage<Message extends Record<string, unknown>>(
123123
message: unknown
124124
): message is ApolloClientDevtoolsMessage<Message> {
125125
return (
@@ -142,10 +142,6 @@ export function isRPCResponseMessage(
142142
return isDevtoolsMessage(message) && message.type === MessageType.RPCResponse;
143143
}
144144

145-
export function isRPCMessage(message: unknown): message is RPCMessage {
146-
return isRPCRequestMessage(message) || isRPCResponseMessage(message);
147-
}
148-
149145
export function isEventMessage<Message extends Record<string, unknown>>(
150146
message: unknown
151147
): message is ApolloClientDevtoolsEventMessage<Message> {

src/extension/rpc.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@ import { createId } from "../utils/createId";
33
import { RPC_MESSAGE_TIMEOUT } from "./errorMessages";
44
import { deserializeError, serializeError } from "./errorSerialization";
55
import type { MessageAdapter } from "./messageAdapters";
6-
import type {
7-
RPCMessage,
8-
RPCRequestMessage,
9-
RPCResponseMessage,
10-
} from "./messages";
6+
import type { RPCRequestMessage, RPCResponseMessage } from "./messages";
117
import {
128
MessageType,
13-
isRPCMessage,
149
isRPCRequestMessage,
1510
isRPCResponseMessage,
1611
} from "./messages";
@@ -144,25 +139,3 @@ export function createRpcHandler<Messages extends MessageCollection>(
144139
};
145140
};
146141
}
147-
148-
export function createRPCBridge(
149-
adapter1: MessageAdapter<RPCMessage>,
150-
adapter2: MessageAdapter<RPCMessage>
151-
) {
152-
const removeListener1 = adapter1.addListener((message) => {
153-
if (isRPCMessage(message)) {
154-
adapter2.postMessage(message);
155-
}
156-
});
157-
158-
const removeListener2 = adapter2.addListener((message) => {
159-
if (isRPCMessage(message)) {
160-
adapter1.postMessage(message);
161-
}
162-
});
163-
164-
return () => {
165-
removeListener1();
166-
removeListener2();
167-
};
168-
}

src/extension/tab/tab.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,18 @@
11
// This script is injected into each tab.
22
import browser from "webextension-polyfill";
3-
import type { ClientMessage } from "../messages";
4-
import { createActor, createWindowActor } from "../actor";
53
import {
4+
createMessageBridge,
65
createPortMessageAdapter,
76
createWindowMessageAdapter,
87
} from "../messageAdapters";
9-
import { createRPCBridge } from "../rpc";
108

119
declare const __IS_FIREFOX__: boolean;
1210

1311
const portAdapter = createPortMessageAdapter(() =>
1412
browser.runtime.connect({ name: "tab" })
1513
);
1614

17-
const tab = createWindowActor<ClientMessage>(window);
18-
const devtools = createActor<ClientMessage>(portAdapter);
19-
20-
createRPCBridge(portAdapter, createWindowMessageAdapter(window));
21-
22-
devtools.forward("explorerSubscriptionTermination", tab);
23-
devtools.forward("explorerRequest", tab);
24-
25-
tab.forward("registerClient", devtools);
26-
tab.forward("clientTerminated", devtools);
27-
tab.forward("explorerResponse", devtools);
15+
createMessageBridge(portAdapter, createWindowMessageAdapter(window));
2816

2917
// We run the hook.js script on the page as a content script in Manifest v3
3018
// extensions (chrome for now). We do this using execution world MAIN.

0 commit comments

Comments
 (0)