Skip to content

Commit 9bd0240

Browse files
committed
Add optional listener options to track message type subscriptions
1 parent 755b672 commit 9bd0240

File tree

8 files changed

+164
-68
lines changed

8 files changed

+164
-68
lines changed

sandpack-client/src/clients/base.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type {
66
ListenerFunction,
77
SandpackMessage,
88
UnsubscribeFunction,
9+
SandpackMessageType,
10+
ListenerOptions,
911
} from "../types";
1012

1113
export class SandpackClient {
@@ -59,7 +61,10 @@ export class SandpackClient {
5961
throw Error("Method not implemented");
6062
}
6163

62-
public listen(_listener: ListenerFunction): UnsubscribeFunction {
64+
public listen<Type extends SandpackMessageType = SandpackMessageType>(
65+
_listener: ListenerFunction<Type>,
66+
_opts?: ListenerOptions<Type>
67+
): UnsubscribeFunction {
6368
throw Error("Method not implemented");
6469
}
6570
}

sandpack-client/src/clients/node/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type {
1313
ListenerFunction,
1414
SandboxSetup,
1515
UnsubscribeFunction,
16+
SandpackMessageType,
17+
ListenerOptions,
1618
} from "../..";
1719
import { nullthrows } from "../..";
1820
import { createError } from "../..";
@@ -434,8 +436,11 @@ export class SandpackNode extends SandpackClient {
434436
}
435437
}
436438

437-
public listen(listener: ListenerFunction): UnsubscribeFunction {
438-
return this.emitter.listener(listener);
439+
public listen<Type extends SandpackMessageType = SandpackMessageType>(
440+
listener: ListenerFunction<Type>,
441+
_opts?: ListenerOptions<Type>
442+
): UnsubscribeFunction {
443+
return this.emitter.listener(listener as any as ListenerFunction);
439444
}
440445

441446
public destroy(): void {

sandpack-client/src/clients/runtime/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type {
1313
SandpackBundlerFiles,
1414
SandpackError,
1515
UnsubscribeFunction,
16+
SandpackMessageType,
17+
ListenerOptions,
1618
} from "../../types";
1719
import { SandpackLogLevel } from "../../types";
1820
import {
@@ -265,8 +267,13 @@ export class SandpackRuntime extends SandpackClient {
265267
this.iframeProtocol.dispatch(message);
266268
}
267269

268-
public listen(listener: ListenerFunction): UnsubscribeFunction {
269-
return this.iframeProtocol.channelListen(listener);
270+
public listen<Type extends SandpackMessageType = SandpackMessageType>(
271+
listener: ListenerFunction<Type>,
272+
_opts?: ListenerOptions<Type>
273+
): UnsubscribeFunction {
274+
return this.iframeProtocol.channelListen(
275+
listener as any as ListenerFunction
276+
);
270277
}
271278

272279
/**

sandpack-client/src/clients/static/index.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import { PreviewController } from "static-browser-server";
44

55
import type {
66
ClientOptions,
7+
ListenerOptions,
78
ListenerFunction,
89
SandboxSetup,
10+
SandpackMessageType,
11+
SandpackMessageByType,
912
UnsubscribeFunction,
1013
} from "../..";
1114
import { SandpackClient } from "../base";
@@ -23,6 +26,7 @@ export class SandpackStatic extends SandpackClient {
2326
private emitter: EventEmitter;
2427
private previewController: PreviewController;
2528
private files: Map<string, string | Uint8Array> = new Map();
29+
private registeredMessageTypes: Map<SandpackMessageType, number> = new Map();
2630

2731
public iframe!: HTMLIFrameElement;
2832
public selector!: string;
@@ -54,9 +58,12 @@ export class SandpackStatic extends SandpackClient {
5458
content,
5559
options.externalResources
5660
);
57-
content = this.injectScriptIntoHead(content, {
58-
script: consoleHook,
59-
});
61+
62+
if (this.registeredMessageTypes.has("console")) {
63+
content = this.injectScriptIntoHead(content, {
64+
script: consoleHook,
65+
});
66+
}
6067
} catch (err) {
6168
console.error("Runtime injection failed", err);
6269
}
@@ -226,8 +233,37 @@ export class SandpackStatic extends SandpackClient {
226233
}
227234
}
228235

229-
public listen(listener: ListenerFunction): UnsubscribeFunction {
230-
return this.emitter.listener(listener);
236+
public listen<Type extends SandpackMessageType = SandpackMessageType>(
237+
listener: ListenerFunction<Type>,
238+
opts?: ListenerOptions<Type>
239+
): UnsubscribeFunction {
240+
opts?.messageTypes?.forEach((type) => {
241+
const count = this.registeredMessageTypes.get(type) ?? 0;
242+
this.registeredMessageTypes.set(type, count + 1);
243+
});
244+
245+
const unsubscribe = this.emitter.listener((message) => {
246+
if (
247+
!opts?.messageTypes ||
248+
opts.messageTypes.includes(message.type as Type)
249+
) {
250+
listener(message as SandpackMessageByType<Type>);
251+
}
252+
});
253+
254+
return () => {
255+
unsubscribe();
256+
257+
opts?.messageTypes?.forEach((type) => {
258+
const count = this.registeredMessageTypes.get(type)!;
259+
260+
if (count === 1) {
261+
this.registeredMessageTypes.delete(type);
262+
} else {
263+
this.registeredMessageTypes.set(type, count - 1);
264+
}
265+
});
266+
};
231267
}
232268

233269
public destroy(): void {

sandpack-client/src/types.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,27 @@ export type ClientStatus =
174174
| "idle";
175175

176176
export type SandpackMessage = SandpackRuntimeMessage | SandpackNodeMessage;
177+
export type SandpackMessageType = SandpackMessage["type"];
178+
export type SandpackMessageByType<Type extends SandpackMessageType> = Extract<
179+
SandpackMessage,
180+
{ type: Type }
181+
>;
177182

178-
export type ListenerFunction = (msg: SandpackMessage) => void;
183+
export type ListenerFunction<
184+
Type extends SandpackMessageType = SandpackMessageType
185+
> = (msg: SandpackMessageByType<Type>) => void;
179186
export type UnsubscribeFunction = () => void;
180187

181-
export type Listen = (
182-
listener: ListenerFunction,
183-
clientId?: string
188+
export type ListenerOptions<
189+
Type extends SandpackMessageType = SandpackMessageType
190+
> = {
191+
messageTypes?: Array<Type>;
192+
};
193+
194+
export type Listen = <Type extends SandpackMessageType = SandpackMessageType>(
195+
listener: ListenerFunction<Type>,
196+
clientId?: string,
197+
opts?: ListenerOptions<Type>
184198
) => UnsubscribeFunction;
185199
export type Dispatch = (msg: SandpackMessage, clientId?: string) => void;
186200

sandpack-react/src/components/Console/useSandpackConsole.ts

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,52 +29,56 @@ export const useSandpackConsole = ({
2929
const { listen } = useSandpack();
3030

3131
React.useEffect(() => {
32-
const unsubscribe = listen((message) => {
33-
if (resetOnPreviewRestart && message.type === "start") {
34-
setLogs([]);
35-
} else if (message.type === "console" && message.codesandbox) {
36-
const payloadLog = Array.isArray(message.log)
37-
? message.log
38-
: [message.log];
32+
const unsubscribe = listen(
33+
(message) => {
34+
if (resetOnPreviewRestart && message.type === "start") {
35+
setLogs([]);
36+
} else if (message.type === "console" && message.codesandbox) {
37+
const payloadLog = Array.isArray(message.log)
38+
? message.log
39+
: [message.log];
3940

40-
if (payloadLog.find(({ method }) => method === "clear")) {
41-
return setLogs([CLEAR_LOG]);
42-
}
41+
if (payloadLog.find(({ method }) => method === "clear")) {
42+
return setLogs([CLEAR_LOG]);
43+
}
4344

44-
const logsMessages = showSyntaxError
45-
? payloadLog
46-
: payloadLog.filter((messageItem) => {
47-
const messagesWithoutSyntaxErrors =
48-
messageItem?.data?.filter?.((dataItem) => {
49-
if (typeof dataItem !== "string") return true;
45+
const logsMessages = showSyntaxError
46+
? payloadLog
47+
: payloadLog.filter((messageItem) => {
48+
const messagesWithoutSyntaxErrors =
49+
messageItem?.data?.filter?.((dataItem) => {
50+
if (typeof dataItem !== "string") return true;
5051

51-
const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) =>
52-
dataItem.startsWith(lookFor)
53-
);
52+
const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) =>
53+
dataItem.startsWith(lookFor)
54+
);
5455

55-
return matches.length === 0;
56-
}) ?? [];
56+
return matches.length === 0;
57+
}) ?? [];
5758

58-
return messagesWithoutSyntaxErrors.length > 0;
59-
});
59+
return messagesWithoutSyntaxErrors.length > 0;
60+
});
6061

61-
if (!logsMessages) return;
62+
if (!logsMessages) return;
6263

63-
setLogs((prev) => {
64-
const messages = [...prev, ...logsMessages].filter(
65-
(value, index, self) => {
66-
return index === self.findIndex((s) => s.id === value.id);
67-
}
68-
);
64+
setLogs((prev) => {
65+
const messages = [...prev, ...logsMessages].filter(
66+
(value, index, self) => {
67+
return index === self.findIndex((s) => s.id === value.id);
68+
}
69+
);
6970

70-
while (messages.length > maxMessageCount) {
71-
messages.shift();
72-
}
71+
while (messages.length > maxMessageCount) {
72+
messages.shift();
73+
}
7374

74-
return messages;
75-
});
76-
}
77-
}, clientId);
75+
return messages;
76+
});
77+
}
78+
},
79+
clientId,
80+
{ messageTypes: ["console", "start"] }
81+
);
7882

7983
return unsubscribe;
8084
}, [showSyntaxError, maxMessageCount, clientId, resetOnPreviewRestart]);

sandpack-react/src/contexts/utils/useClient.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type {
66
SandpackMessage,
77
UnsubscribeFunction,
88
SandpackClient,
9+
SandpackMessageType,
10+
ListenerOptions,
911
} from "@codesandbox/sandpack-client";
1012
import {
1113
loadSandpackClient,
@@ -49,9 +51,10 @@ export interface UseClientOperations {
4951
clientPropsOverride?: ClientPropsOverride
5052
) => Promise<void>;
5153
registerReactDevTools: (value: ReactDevToolsMode) => void;
52-
addListener: (
54+
addListener: <Type extends SandpackMessageType = SandpackMessageType>(
5355
listener: ListenerFunction,
54-
clientId?: string
56+
clientId?: string,
57+
opts?: ListenerOptions<Type>
5558
) => UnsubscribeFunction;
5659
dispatchMessage: (message: SandpackMessage, clientId?: string) => void;
5760
lazyAnchorRef: React.RefObject<HTMLDivElement>;
@@ -61,7 +64,10 @@ export interface UseClientOperations {
6164
Record<string, Record<string, UnsubscribeFunction>>
6265
>;
6366
queuedListenersRef: React.MutableRefObject<
64-
Record<string, Record<string, ListenerFunction>>
67+
Record<
68+
string,
69+
Record<string, { listener: ListenerFunction; opts?: ListenerOptions }>
70+
>
6571
>;
6672
}
6773

@@ -106,7 +112,10 @@ export const useClient: UseClient = (
106112
>({});
107113
const unsubscribe = useRef<() => void | undefined>();
108114
const queuedListeners = useRef<
109-
Record<string, Record<string, ListenerFunction>>
115+
Record<
116+
string,
117+
Record<string, { listener: ListenerFunction; opts?: ListenerOptions }>
118+
>
110119
>({ global: {} });
111120
const debounceHook = useRef<number | undefined>();
112121
const loadingScreenRegisteredRef = useRef<boolean>(true);
@@ -185,8 +194,9 @@ export const useClient: UseClient = (
185194
*/
186195
if (queuedListeners.current[clientId]) {
187196
Object.keys(queuedListeners.current[clientId]).forEach((listenerId) => {
188-
const listener = queuedListeners.current[clientId][listenerId];
189-
const unsubscribe = client.listen(listener) as () => void;
197+
const { listener, opts } =
198+
queuedListeners.current[clientId][listenerId];
199+
const unsubscribe = client.listen(listener, opts) as () => void;
190200
unsubscribeClientListeners.current[clientId][listenerId] =
191201
unsubscribe;
192202
});
@@ -199,8 +209,8 @@ export const useClient: UseClient = (
199209
* Register global listeners
200210
*/
201211
const globalListeners = Object.entries(queuedListeners.current.global);
202-
globalListeners.forEach(([listenerId, listener]) => {
203-
const unsubscribe = client.listen(listener) as () => void;
212+
globalListeners.forEach(([listenerId, { listener, opts }]) => {
213+
const unsubscribe = client.listen(listener, opts) as () => void;
204214
unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe;
205215

206216
/**
@@ -392,13 +402,17 @@ export const useClient: UseClient = (
392402
}
393403
};
394404

395-
const addListener = (
396-
listener: ListenerFunction,
397-
clientId?: string
405+
const addListener = <Type extends SandpackMessageType = SandpackMessageType>(
406+
listener: ListenerFunction<Type>,
407+
clientId?: string,
408+
opts?: ListenerOptions<Type>
398409
): UnsubscribeFunction => {
399410
if (clientId) {
400411
if (clients.current[clientId]) {
401-
const unsubscribeListener = clients.current[clientId].listen(listener);
412+
const unsubscribeListener = clients.current[clientId].listen(
413+
listener,
414+
opts
415+
);
402416

403417
return unsubscribeListener;
404418
} else {
@@ -413,7 +427,10 @@ export const useClient: UseClient = (
413427
unsubscribeClientListeners.current[clientId] =
414428
unsubscribeClientListeners.current[clientId] || {};
415429

416-
queuedListeners.current[clientId][listenerId] = listener;
430+
queuedListeners.current[clientId][listenerId] = {
431+
listener: listener as any as ListenerFunction,
432+
opts: opts as any as ListenerOptions,
433+
};
417434

418435
const unsubscribeListener = (): void => {
419436
if (queuedListeners.current[clientId][listenerId]) {
@@ -437,7 +454,10 @@ export const useClient: UseClient = (
437454
} else {
438455
// Push to the **global** queue
439456
const listenerId = generateRandomId();
440-
queuedListeners.current.global[listenerId] = listener;
457+
queuedListeners.current.global[listenerId] = {
458+
listener: listener as any as ListenerFunction,
459+
opts: opts as any as ListenerOptions,
460+
};
441461

442462
// Add to the current clients
443463
const clientsList = Object.values(clients.current);

0 commit comments

Comments
 (0)