From d81cad892fec5be175074af52d40bb2992186cc5 Mon Sep 17 00:00:00 2001 From: SSHari Date: Sun, 23 Apr 2023 00:54:24 -0400 Subject: [PATCH] Add optional listener options to track message type subscriptions --- sandpack-client/src/clients/base.ts | 7 +- sandpack-client/src/clients/node/index.ts | 9 ++- sandpack-client/src/clients/runtime/index.ts | 11 ++- sandpack-client/src/clients/static/index.ts | 46 +++++++++-- sandpack-client/src/types.ts | 22 +++++- .../components/Console/useSandpackConsole.ts | 78 ++++++++++--------- .../src/contexts/utils/useClient.ts | 48 ++++++++---- sandpack-react/src/types.ts | 11 ++- 8 files changed, 164 insertions(+), 68 deletions(-) diff --git a/sandpack-client/src/clients/base.ts b/sandpack-client/src/clients/base.ts index 366bd8c72..33df0c25f 100644 --- a/sandpack-client/src/clients/base.ts +++ b/sandpack-client/src/clients/base.ts @@ -6,6 +6,8 @@ import type { ListenerFunction, SandpackMessage, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../types"; export class SandpackClient { @@ -59,7 +61,10 @@ export class SandpackClient { throw Error("Method not implemented"); } - public listen(_listener: ListenerFunction): UnsubscribeFunction { + public listen( + _listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { throw Error("Method not implemented"); } } diff --git a/sandpack-client/src/clients/node/index.ts b/sandpack-client/src/clients/node/index.ts index 36c792c90..d77071f16 100644 --- a/sandpack-client/src/clients/node/index.ts +++ b/sandpack-client/src/clients/node/index.ts @@ -13,6 +13,8 @@ import type { ListenerFunction, SandboxSetup, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../.."; import { nullthrows } from "../.."; import { createError } from "../.."; @@ -434,8 +436,11 @@ export class SandpackNode extends SandpackClient { } } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.emitter.listener(listener); + public listen( + listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { + return this.emitter.listener(listener as any as ListenerFunction); } public destroy(): void { diff --git a/sandpack-client/src/clients/runtime/index.ts b/sandpack-client/src/clients/runtime/index.ts index 5b7c2e818..eaf843917 100644 --- a/sandpack-client/src/clients/runtime/index.ts +++ b/sandpack-client/src/clients/runtime/index.ts @@ -13,6 +13,8 @@ import type { SandpackBundlerFiles, SandpackError, UnsubscribeFunction, + SandpackMessageType, + ListenerOptions, } from "../../types"; import { SandpackLogLevel } from "../../types"; import { @@ -265,8 +267,13 @@ export class SandpackRuntime extends SandpackClient { this.iframeProtocol.dispatch(message); } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.iframeProtocol.channelListen(listener); + public listen( + listener: ListenerFunction, + _opts?: ListenerOptions + ): UnsubscribeFunction { + return this.iframeProtocol.channelListen( + listener as any as ListenerFunction + ); } /** diff --git a/sandpack-client/src/clients/static/index.ts b/sandpack-client/src/clients/static/index.ts index 56d14062b..7683b2f60 100644 --- a/sandpack-client/src/clients/static/index.ts +++ b/sandpack-client/src/clients/static/index.ts @@ -4,8 +4,11 @@ import { PreviewController } from "static-browser-server"; import type { ClientOptions, + ListenerOptions, ListenerFunction, SandboxSetup, + SandpackMessageType, + SandpackMessageByType, UnsubscribeFunction, } from "../.."; import { SandpackClient } from "../base"; @@ -23,6 +26,7 @@ export class SandpackStatic extends SandpackClient { private emitter: EventEmitter; private previewController: PreviewController; private files: Map = new Map(); + private registeredMessageTypes: Map = new Map(); public iframe!: HTMLIFrameElement; public selector!: string; @@ -54,9 +58,12 @@ export class SandpackStatic extends SandpackClient { content, options.externalResources ); - content = this.injectScriptIntoHead(content, { - script: consoleHook, - }); + + if (this.registeredMessageTypes.has("console")) { + content = this.injectScriptIntoHead(content, { + script: consoleHook, + }); + } } catch (err) { console.error("Runtime injection failed", err); } @@ -223,8 +230,37 @@ export class SandpackStatic extends SandpackClient { } } - public listen(listener: ListenerFunction): UnsubscribeFunction { - return this.emitter.listener(listener); + public listen( + listener: ListenerFunction, + opts?: ListenerOptions + ): UnsubscribeFunction { + opts?.messageTypes?.forEach((type) => { + const count = this.registeredMessageTypes.get(type) ?? 0; + this.registeredMessageTypes.set(type, count + 1); + }); + + const unsubscribe = this.emitter.listener((message) => { + if ( + !opts?.messageTypes || + opts.messageTypes.includes(message.type as Type) + ) { + listener(message as SandpackMessageByType); + } + }); + + return () => { + unsubscribe(); + + opts?.messageTypes?.forEach((type) => { + const count = this.registeredMessageTypes.get(type)!; + + if (count === 1) { + this.registeredMessageTypes.delete(type); + } else { + this.registeredMessageTypes.set(type, count - 1); + } + }); + }; } public destroy(): void { diff --git a/sandpack-client/src/types.ts b/sandpack-client/src/types.ts index 3e11816ec..5b5ff7973 100644 --- a/sandpack-client/src/types.ts +++ b/sandpack-client/src/types.ts @@ -174,13 +174,27 @@ export type ClientStatus = | "idle"; export type SandpackMessage = SandpackRuntimeMessage | SandpackNodeMessage; +export type SandpackMessageType = SandpackMessage["type"]; +export type SandpackMessageByType = Extract< + SandpackMessage, + { type: Type } +>; -export type ListenerFunction = (msg: SandpackMessage) => void; +export type ListenerFunction< + Type extends SandpackMessageType = SandpackMessageType +> = (msg: SandpackMessageByType) => void; export type UnsubscribeFunction = () => void; -export type Listen = ( - listener: ListenerFunction, - clientId?: string +export type ListenerOptions< + Type extends SandpackMessageType = SandpackMessageType +> = { + messageTypes?: Array; +}; + +export type Listen = ( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; export type Dispatch = (msg: SandpackMessage, clientId?: string) => void; diff --git a/sandpack-react/src/components/Console/useSandpackConsole.ts b/sandpack-react/src/components/Console/useSandpackConsole.ts index 1080ba172..22d05b32f 100644 --- a/sandpack-react/src/components/Console/useSandpackConsole.ts +++ b/sandpack-react/src/components/Console/useSandpackConsole.ts @@ -29,52 +29,56 @@ export const useSandpackConsole = ({ const { listen } = useSandpack(); React.useEffect(() => { - const unsubscribe = listen((message) => { - if (resetOnPreviewRestart && message.type === "start") { - setLogs([]); - } else if (message.type === "console" && message.codesandbox) { - const payloadLog = Array.isArray(message.log) - ? message.log - : [message.log]; + const unsubscribe = listen( + (message) => { + if (resetOnPreviewRestart && message.type === "start") { + setLogs([]); + } else if (message.type === "console" && message.codesandbox) { + const payloadLog = Array.isArray(message.log) + ? message.log + : [message.log]; - if (payloadLog.find(({ method }) => method === "clear")) { - return setLogs([CLEAR_LOG]); - } + if (payloadLog.find(({ method }) => method === "clear")) { + return setLogs([CLEAR_LOG]); + } - const logsMessages = showSyntaxError - ? payloadLog - : payloadLog.filter((messageItem) => { - const messagesWithoutSyntaxErrors = - messageItem?.data?.filter?.((dataItem) => { - if (typeof dataItem !== "string") return true; + const logsMessages = showSyntaxError + ? payloadLog + : payloadLog.filter((messageItem) => { + const messagesWithoutSyntaxErrors = + messageItem?.data?.filter?.((dataItem) => { + if (typeof dataItem !== "string") return true; - const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => - dataItem.startsWith(lookFor) - ); + const matches = SYNTAX_ERROR_PATTERN.filter((lookFor) => + dataItem.startsWith(lookFor) + ); - return matches.length === 0; - }) ?? []; + return matches.length === 0; + }) ?? []; - return messagesWithoutSyntaxErrors.length > 0; - }); + return messagesWithoutSyntaxErrors.length > 0; + }); - if (!logsMessages) return; + if (!logsMessages) return; - setLogs((prev) => { - const messages = [...prev, ...logsMessages].filter( - (value, index, self) => { - return index === self.findIndex((s) => s.id === value.id); - } - ); + setLogs((prev) => { + const messages = [...prev, ...logsMessages].filter( + (value, index, self) => { + return index === self.findIndex((s) => s.id === value.id); + } + ); - while (messages.length > maxMessageCount) { - messages.shift(); - } + while (messages.length > maxMessageCount) { + messages.shift(); + } - return messages; - }); - } - }, clientId); + return messages; + }); + } + }, + clientId, + { messageTypes: ["console", "start"] } + ); return unsubscribe; }, [showSyntaxError, maxMessageCount, clientId, resetOnPreviewRestart]); diff --git a/sandpack-react/src/contexts/utils/useClient.ts b/sandpack-react/src/contexts/utils/useClient.ts index 69cd79d40..a578014a2 100644 --- a/sandpack-react/src/contexts/utils/useClient.ts +++ b/sandpack-react/src/contexts/utils/useClient.ts @@ -6,6 +6,8 @@ import type { SandpackMessage, UnsubscribeFunction, SandpackClient, + SandpackMessageType, + ListenerOptions, } from "@codesandbox/sandpack-client"; import { loadSandpackClient, @@ -49,9 +51,10 @@ export interface UseClientOperations { clientPropsOverride?: ClientPropsOverride ) => Promise; registerReactDevTools: (value: ReactDevToolsMode) => void; - addListener: ( + addListener: ( listener: ListenerFunction, - clientId?: string + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; dispatchMessage: (message: SandpackMessage, clientId?: string) => void; lazyAnchorRef: React.RefObject; @@ -61,7 +64,10 @@ export interface UseClientOperations { Record> >; queuedListenersRef: React.MutableRefObject< - Record> + Record< + string, + Record + > >; } @@ -106,7 +112,10 @@ export const useClient: UseClient = ( >({}); const unsubscribe = useRef<() => void | undefined>(); const queuedListeners = useRef< - Record> + Record< + string, + Record + > >({ global: {} }); const debounceHook = useRef(); const loadingScreenRegisteredRef = useRef(true); @@ -175,8 +184,9 @@ export const useClient: UseClient = ( */ if (queuedListeners.current[clientId]) { Object.keys(queuedListeners.current[clientId]).forEach((listenerId) => { - const listener = queuedListeners.current[clientId][listenerId]; - const unsubscribe = client.listen(listener) as () => void; + const { listener, opts } = + queuedListeners.current[clientId][listenerId]; + const unsubscribe = client.listen(listener, opts) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; }); @@ -189,8 +199,8 @@ export const useClient: UseClient = ( * Register global listeners */ const globalListeners = Object.entries(queuedListeners.current.global); - globalListeners.forEach(([listenerId, listener]) => { - const unsubscribe = client.listen(listener) as () => void; + globalListeners.forEach(([listenerId, { listener, opts }]) => { + const unsubscribe = client.listen(listener, opts) as () => void; unsubscribeClientListeners.current[clientId][listenerId] = unsubscribe; /** @@ -394,13 +404,17 @@ export const useClient: UseClient = ( } }; - const addListener = ( - listener: ListenerFunction, - clientId?: string + const addListener = ( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ): UnsubscribeFunction => { if (clientId) { if (clients.current[clientId]) { - const unsubscribeListener = clients.current[clientId].listen(listener); + const unsubscribeListener = clients.current[clientId].listen( + listener, + opts + ); return unsubscribeListener; } else { @@ -415,7 +429,10 @@ export const useClient: UseClient = ( unsubscribeClientListeners.current[clientId] = unsubscribeClientListeners.current[clientId] || {}; - queuedListeners.current[clientId][listenerId] = listener; + queuedListeners.current[clientId][listenerId] = { + listener: listener as any as ListenerFunction, + opts: opts as any as ListenerOptions, + }; const unsubscribeListener = (): void => { if (queuedListeners.current[clientId][listenerId]) { @@ -439,7 +456,10 @@ export const useClient: UseClient = ( } else { // Push to the **global** queue const listenerId = generateRandomId(); - queuedListeners.current.global[listenerId] = listener; + queuedListeners.current.global[listenerId] = { + listener: listener as any as ListenerFunction, + opts: opts as any as ListenerOptions, + }; // Add to the current clients const clientsList = Object.values(clients.current); diff --git a/sandpack-react/src/types.ts b/sandpack-react/src/types.ts index 0510b3cb7..aef7ee1ac 100644 --- a/sandpack-react/src/types.ts +++ b/sandpack-react/src/types.ts @@ -12,6 +12,8 @@ import type { UnsubscribeFunction, SandpackLogLevel, NpmRegistry, + SandpackMessageType, + ListenerOptions, } from "@codesandbox/sandpack-client"; import type React from "react"; @@ -517,9 +519,12 @@ export type SandpackClientDispatch = ( clientId?: string ) => void; -export type SandpackClientListen = ( - listener: ListenerFunction, - clientId?: string +export type SandpackClientListen = < + Type extends SandpackMessageType = SandpackMessageType +>( + listener: ListenerFunction, + clientId?: string, + opts?: ListenerOptions ) => UnsubscribeFunction; export type SandpackContext = SandpackState & {