Skip to content

Commit d81cad8

Browse files
committed
Add optional listener options to track message type subscriptions
1 parent 9c18f08 commit d81cad8

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
}
@@ -223,8 +230,37 @@ export class SandpackStatic extends SandpackClient {
223230
}
224231
}
225232

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

230266
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);
@@ -175,8 +184,9 @@ export const useClient: UseClient = (
175184
*/
176185
if (queuedListeners.current[clientId]) {
177186
Object.keys(queuedListeners.current[clientId]).forEach((listenerId) => {
178-
const listener = queuedListeners.current[clientId][listenerId];
179-
const unsubscribe = client.listen(listener) as () => void;
187+
const { listener, opts } =
188+
queuedListeners.current[clientId][listenerId];
189+
const unsubscribe = client.listen(listener, opts) as () => void;
180190
unsubscribeClientListeners.current[clientId][listenerId] =
181191
unsubscribe;
182192
});
@@ -189,8 +199,8 @@ export const useClient: UseClient = (
189199
* Register global listeners
190200
*/
191201
const globalListeners = Object.entries(queuedListeners.current.global);
192-
globalListeners.forEach(([listenerId, listener]) => {
193-
const unsubscribe = client.listen(listener) as () => void;
202+
globalListeners.forEach(([listenerId, { listener, opts }]) => {
203+
const unsubscribe = client.listen(listener, opts) as () => void;
194204
unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe;
195205

196206
/**
@@ -394,13 +404,17 @@ export const useClient: UseClient = (
394404
}
395405
};
396406

397-
const addListener = (
398-
listener: ListenerFunction,
399-
clientId?: string
407+
const addListener = <Type extends SandpackMessageType = SandpackMessageType>(
408+
listener: ListenerFunction<Type>,
409+
clientId?: string,
410+
opts?: ListenerOptions<Type>
400411
): UnsubscribeFunction => {
401412
if (clientId) {
402413
if (clients.current[clientId]) {
403-
const unsubscribeListener = clients.current[clientId].listen(listener);
414+
const unsubscribeListener = clients.current[clientId].listen(
415+
listener,
416+
opts
417+
);
404418

405419
return unsubscribeListener;
406420
} else {
@@ -415,7 +429,10 @@ export const useClient: UseClient = (
415429
unsubscribeClientListeners.current[clientId] =
416430
unsubscribeClientListeners.current[clientId] || {};
417431

418-
queuedListeners.current[clientId][listenerId] = listener;
432+
queuedListeners.current[clientId][listenerId] = {
433+
listener: listener as any as ListenerFunction,
434+
opts: opts as any as ListenerOptions,
435+
};
419436

420437
const unsubscribeListener = (): void => {
421438
if (queuedListeners.current[clientId][listenerId]) {
@@ -439,7 +456,10 @@ export const useClient: UseClient = (
439456
} else {
440457
// Push to the **global** queue
441458
const listenerId = generateRandomId();
442-
queuedListeners.current.global[listenerId] = listener;
459+
queuedListeners.current.global[listenerId] = {
460+
listener: listener as any as ListenerFunction,
461+
opts: opts as any as ListenerOptions,
462+
};
443463

444464
// Add to the current clients
445465
const clientsList = Object.values(clients.current);

0 commit comments

Comments
 (0)