import { useCallback } from "react"; import { Channel } from "./ipc"; import type { FromErrorPage, SourcesLoadedMessage, ToErrorPage } from "./ipc"; import type { TypedModule } from "./types"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); function getFilesFromModules( entrypoint: string, modules: Record, serviceWorkerScript: string | undefined ) { const isServiceWorker = Object.entries(modules).length === 1 && serviceWorkerScript !== undefined; const files = isServiceWorker ? [ { path: entrypoint, contents: encoder.encode(serviceWorkerScript), }, ] : Object.entries(modules) .filter( ([path, m]) => path.endsWith(".js") || m.type === "application/javascript" || m.type === "application/javascript+module" ) .map(([path, m]) => ({ path, contents: m.contents, })); let internalLines: number | undefined; if (isServiceWorker && modules[entrypoint]) { const totalScriptLength = serviceWorkerScript?.split("\n").length ?? 0; const userScriptLength = decoder .decode(modules[entrypoint].contents) .split("\n").length; internalLines = totalScriptLength - userScriptLength; } return { files, internalLines }; } /** * This hook works in tandem with the format-errors worker * See https://github.com/cloudflare/workers-sdk/tree/main/packages/format-errors/README.md * * When given a Worker's source, which has: * - `entrypoint` & `modules` for a modules-syntax Worker * - `entrypoint`, `modules` & `serviceWorkerScript` for a service-worker syntax Worker * In this context, `serviceWorkerScript` is the contents of the entrypoint module with * potentially some additional lines of code prepended. The calculated `internalLines` number * represents how many internal lines of code have been prepended, and should be stripped * before displaying to the user. The prepended lines of code can be arbitrary, but in our context * usually represent the middleware system: https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler/templates/middleware/loader-sw.ts * * this hook will return a callback that can be used to respond to the `load` event of * an iframe which renders the `format-errors` worker. It will construct the `MessageChannel` * & `MessagePort`s appropriately to construct the right messages for inflating the `format-errors` * stack traces: https://github.com/cloudflare/workers-sdk/tree/main/packages/format-errors/README.md#L28 */ export function useInjectSources( entrypoint: string | undefined, modules: Record | undefined, serviceWorkerScript?: string ): (frame: HTMLIFrameElement) => void { return useCallback( (frame) => { // Create a new channel on every load, since they'll be sent to the iframe and can't be used on the next load. const errorPage = Channel( new MessageChannel() ); try { frame.contentWindow?.postMessage("PORT", "*", [errorPage.remote]); } catch (e) { console.error("new load", e); } errorPage.onMessage((data) => { if (!entrypoint || !modules) { return; } if (data.type === "RequestSources") { const { files, internalLines } = getFilesFromModules( entrypoint, modules, serviceWorkerScript ); const message: SourcesLoadedMessage = { type: "SourcesLoaded", body: { name: "worker", entrypoint, files, internalLines: internalLines ?? 0, }, }; errorPage.postMessage(message); } }); }, [entrypoint, modules, serviceWorkerScript] ); }