diff --git a/packages/repl/src/components/runner/Runner.tsx b/packages/repl/src/components/runner/Runner.tsx index 8614cf6..445f00a 100644 --- a/packages/repl/src/components/runner/Runner.tsx +++ b/packages/repl/src/components/runner/Runner.tsx @@ -1,15 +1,23 @@ import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu' +import type { mtcute } from 'mtcute-repl-worker/client' import type { CustomTypeScriptWorker } from '../editor/utils/custom-worker.ts' import { timers } from '@fuman/utils' import { persistentAtom } from '@nanostores/persistent' import { LucideCheck, LucidePlay, LucidePlug, LucideRefreshCw, LucideSkull, LucideUnplug } from 'lucide-solid' import { languages, Uri } from 'monaco-editor/esm/vs/editor/editor.api.js' -import { type mtcute, workerInvoke } from 'mtcute-repl-worker/client' -import { nanoid } from 'nanoid' import { createEffect, createSignal, on, onCleanup, onMount } from 'solid-js' import { Dynamic } from 'solid-js/web' import { Button } from '../../lib/components/ui/button.tsx' -import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuGroupLabel, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '../../lib/components/ui/dropdown-menu.tsx' +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuGroupLabel, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '../../lib/components/ui/dropdown-menu.tsx' import { cn } from '../../lib/utils.ts' import { $activeAccountId } from '../../store/accounts.ts' import { $tabs } from '../../store/tabs.ts' @@ -41,7 +49,6 @@ export function Runner(props: { isResizing: boolean }) { const enableUpdates = useStore($enableUpdates) const enableVerbose = useStore($enableVerbose) - let currentScriptId: string | undefined let deadTimer: timers.Timer | undefined let inactivityTimer: timers.Timer | undefined let iframeContainerRef!: HTMLIFrameElement @@ -102,8 +109,6 @@ export function Runner(props: { isResizing: boolean }) { } case 'SCRIPT_END': { setRunning(false) - workerInvoke('sw', 'forgetScript', { name: currentScriptId! }) - currentScriptId = undefined rescheduleInactivityTimer() break } @@ -180,12 +185,9 @@ export function Runner(props: { isResizing: boolean }) { } } - currentScriptId = nanoid() - await workerInvoke('sw', 'uploadScript', { name: currentScriptId, files }) - runnerIframe()!.contentWindow!.postMessage({ event: 'RUN', - scriptId: currentScriptId, + files, exports, }, '*') setRunning(true) @@ -328,7 +330,7 @@ export function Runner(props: { isResizing: boolean }) { -
+
>() + +export async function swInvokeMethodInner(request: SwMessage, sw: ServiceWorker) { + if (!registered) { + navigator.serviceWorker.addEventListener('message', (e) => { + const { id, result, error } = (e as MessageEvent).data + const def = pending.get(id) + if (!def) return + if (error) { + def.reject(new Error(error)) + } else { + def.resolve(result) + } + pending.delete(id) + }) + registered = true + } + + const def = new Deferred() + const id = nextId++ + ;(request as any).id = id + pending.set(id, def) + sw.postMessage(request) + return def.promise +} diff --git a/packages/worker/src/sw/client.ts b/packages/worker/src/sw/client.ts index d5d7879..8e98a34 100644 --- a/packages/worker/src/sw/client.ts +++ b/packages/worker/src/sw/client.ts @@ -1,5 +1,6 @@ import type { SwMessage } from './main.ts' -import { asNonNull, Deferred } from '@fuman/utils' +import { asNonNull } from '@fuman/utils' +import { swInvokeMethodInner } from './client-inner.ts' import { waitForServiceWorkerInit } from './register.ts' export async function getServiceWorker() { @@ -7,31 +8,6 @@ export async function getServiceWorker() { return asNonNull(navigator.serviceWorker.controller) } -let registered = false -let nextId = 0 -const pending = new Map>() - export async function swInvokeMethod(request: SwMessage) { - const sw = await getServiceWorker() - if (!registered) { - navigator.serviceWorker.addEventListener('message', (e) => { - const { id, result, error } = (e as MessageEvent).data - const def = pending.get(id) - if (!def) return - if (error) { - def.reject(new Error(error)) - } else { - def.resolve(result) - } - pending.delete(id) - }) - registered = true - } - - const def = new Deferred() - const id = nextId++ - ;(request as any).id = id - pending.set(id, def) - sw.postMessage(request) - return def.promise + return swInvokeMethodInner(request, await getServiceWorker()) } diff --git a/packages/worker/src/sw/iframe/script.ts b/packages/worker/src/sw/iframe/script.ts index 235ab2f..d9e8171 100644 --- a/packages/worker/src/sw/iframe/script.ts +++ b/packages/worker/src/sw/iframe/script.ts @@ -1,4 +1,7 @@ +import { asNonNull } from '@fuman/utils' import { Long, TelegramClient } from '@mtcute/web' +import { nanoid } from 'nanoid' +import { swInvokeMethodInner } from '../client-inner.ts' type ConnectionState = import('@mtcute/web').ConnectionState type TelegramClientOptions = import('@mtcute/web').TelegramClientOptions @@ -31,6 +34,7 @@ chobitsu.setOnMessage((message: string) => { let lastAccountId: string | undefined let lastConnectionState: ConnectionState | undefined +let currentScriptId: string | undefined let logUpdates = false let verboseLogs = false @@ -70,7 +74,7 @@ function initClient(accountId: string, verbose: boolean) { }) } -window.addEventListener('message', ({ data }) => { +window.addEventListener('message', async ({ data }) => { if (data.event === 'INIT') { sendToDevtools({ method: 'Page.frameNavigated', @@ -102,9 +106,12 @@ window.addEventListener('message', ({ data }) => { window.parent.postMessage({ event: 'PING' }, HOST_ORIGIN) }, 500) } else if (data.event === 'RUN') { + currentScriptId = nanoid() + await swInvokeMethodInner({ event: 'UPLOAD_SCRIPT', name: currentScriptId, files: data.files }, asNonNull(navigator.serviceWorker.controller)) + const el = document.createElement('script') el.type = 'module' - let script = `import * as result from "/sw/runtime/script/${data.scriptId}/main.js";` + let script = `import * as result from "/sw/runtime/script/${currentScriptId}/main.js";` for (const exportName of data.exports ?? []) { script += `window.${exportName} = result.${exportName};` } @@ -114,9 +121,11 @@ window.addEventListener('message', ({ data }) => { script += 'console.log("[mtcute-repl] Script ended");' } script += 'window.__handleScriptEnd();' + el.textContent = script el.addEventListener('error', e => window.__handleScriptEnd(e.error)) window.__currentScript = el + document.body.appendChild(el) } else if (data.event === 'FROM_DEVTOOLS') { chobitsu.sendRawMessage(data.value) @@ -152,6 +161,10 @@ window.addEventListener('message', ({ data }) => { window.__handleScriptEnd = (error) => { if (!window.__currentScript) return + if (currentScriptId) { + swInvokeMethodInner({ event: 'FORGET_SCRIPT', name: currentScriptId }, asNonNull(navigator.serviceWorker.controller)) + .catch(console.error) + } window.parent.postMessage({ event: 'SCRIPT_END', error }, HOST_ORIGIN) window.__currentScript.remove() window.__currentScript = undefined diff --git a/packages/worker/src/sw/main.ts b/packages/worker/src/sw/main.ts index 5d295da..8b967ae 100644 --- a/packages/worker/src/sw/main.ts +++ b/packages/worker/src/sw/main.ts @@ -59,19 +59,19 @@ export type SwMessage = | { event: 'CLEAR_AVATAR_CACHE', accountId: string } | { event: 'CLEAR_CACHE' } -function handleMessage(msg: SwMessage) { +async function handleMessage(msg: SwMessage) { switch (msg.event) { case 'UPLOAD_SCRIPT': { - uploadScript(msg.name, msg.files) + await uploadScript(msg.name, msg.files) break } case 'FORGET_SCRIPT': { - forgetScript(msg.name) + await forgetScript(msg.name) break } case 'CLEAR_CACHE': { clearCache() - forgetAllScripts() + await forgetAllScripts() break } case 'CLEAR_AVATAR_CACHE': {