diff --git a/eslint.config.js b/eslint.config.js
index 081f9e9..9e6db90 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,6 +1,15 @@
import antfu from '@antfu/eslint-config'
import tailwind from 'eslint-plugin-tailwindcss'
+import tailwindConfig from 'eslint-plugin-tailwindcss/lib/config/rules.js'
+
+const mappedTailwindConfig = {}
+for (const [key, value] of Object.entries(tailwindConfig)) {
+ mappedTailwindConfig[key.replace('tailwindcss/', 'tw/')] = [value, {
+ config: 'packages/repl/tailwind.config.js',
+ }]
+}
+
export default antfu({
ignores: [
'src/components/Editor/utils/*.json',
@@ -10,6 +19,7 @@ export default antfu({
solid: true,
yaml: false,
rules: {
+ 'node/prefer-global/process': 'off',
'style/multiline-ternary': 'off',
'curly': ['error', 'multi-line'],
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
@@ -21,8 +31,9 @@ export default antfu({
'ts/no-redeclare': 'off',
'unused-imports/no-unused-imports': 'error',
'ts/no-empty-object-type': 'off',
+ ...mappedTailwindConfig,
},
plugins: {
tw: tailwind,
},
-}, tailwind.configs['flat/recommended'])
+})
diff --git a/package.json b/package.json
index 59c1295..36f12e0 100644
--- a/package.json
+++ b/package.json
@@ -1,68 +1,27 @@
{
- "name": "mtcute-repl",
+ "name": "mtcute-repl-workspace",
"type": "module",
"version": "0.0.0",
"private": true,
"packageManager": "pnpm@9.5.0",
"scripts": {
- "dev": "vite",
- "build": "tsc -b && vite build",
- "preview": "vite preview"
- },
- "dependencies": {
- "@badrap/valita": "^0.4.2",
- "@corvu/otp-field": "^0.1.4",
- "@corvu/resizable": "^0.2.3",
- "@fuman/fetch": "^0.0.8",
- "@fuman/io": "0.0.8",
- "@fuman/utils": "0.0.4",
- "@kobalte/core": "^0.13.7",
- "@mtcute/convert": "^0.19.4",
- "@mtcute/web": "^0.19.5",
- "@nanostores/persistent": "^0.10.2",
- "class-variance-authority": "^0.7.1",
- "clsx": "^2.1.1",
- "esbuild": "^0.24.2",
- "fflate": "^0.8.2",
- "filesize": "^10.1.6",
- "idb": "^8.0.1",
- "lucide-solid": "^0.445.0",
- "memfs": "^4.17.0",
- "monaco-editor": "0.52.0",
- "monaco-editor-core": "0.52.0",
- "monaco-editor-textmate": "^4.0.0",
- "monaco-textmate": "^3.0.1",
- "nanoid": "^5.0.9",
- "nanostores": "^0.11.3",
- "onigasm": "^2.2.5",
- "semver": "^7.6.3",
- "solid-icons": "^1.1.0",
- "solid-js": "^1.9.4",
- "solid-transition-group": "^0.2.3",
- "tailwind-merge": "^2.6.0",
- "tailwindcss-animate": "^1.0.7",
- "ts-blank-space": "^0.4.4",
- "uqr": "^0.1.2"
+ "dev": "pnpm -r --parallel dev"
},
"devDependencies": {
"@antfu/eslint-config": "^3.13.0",
"@catppuccin/vscode": "^3.16.0",
+ "@fuman/fetch": "^0.0.8",
"@types/node": "^22.10.5",
"@types/semver": "^7.5.8",
"autoprefixer": "^10.4.20",
+ "esbuild": "^0.24.2",
"eslint-plugin-solid": "^0.14.5",
"eslint-plugin-tailwindcss": "^3.17.5",
"monaco-vscode-textmate-theme-converter": "^0.1.7",
"plist2": "^1.1.4",
- "postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3",
"vite": "^5.4.11",
"vite-plugin-solid": "^2.11.0"
- },
- "pnpm": {
- "patchedDependencies": {
- "vite-plugin-externalize-dependencies@1.0.1": "patches/vite-plugin-externalize-dependencies@1.0.1.patch"
- }
}
}
diff --git a/index.html b/packages/repl/index.html
similarity index 89%
rename from index.html
rename to packages/repl/index.html
index d812434..9e27e6a 100644
--- a/index.html
+++ b/packages/repl/index.html
@@ -4,7 +4,7 @@
-
@mtcute/repl
+ @mtcute/playground
diff --git a/packages/repl/package.json b/packages/repl/package.json
new file mode 100644
index 0000000..42e8d17
--- /dev/null
+++ b/packages/repl/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "mtcute-repl",
+ "type": "module",
+ "version": "0.0.0",
+ "private": true,
+ "packageManager": "pnpm@9.5.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@corvu/otp-field": "^0.1.4",
+ "@corvu/resizable": "^0.2.3",
+ "@fuman/utils": "0.0.4",
+ "@kobalte/core": "^0.13.7",
+
+ "@nanostores/persistent": "^0.10.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "filesize": "^10.1.6",
+ "lucide-solid": "^0.445.0",
+ "monaco-editor": "0.52.0",
+ "monaco-editor-core": "0.52.0",
+ "monaco-editor-textmate": "^4.0.0",
+ "monaco-textmate": "^3.0.1",
+ "mtcute-repl-worker": "workspace:*",
+ "nanoid": "^5.0.9",
+ "nanostores": "^0.11.3",
+ "onigasm": "^2.2.5",
+ "solid-icons": "^1.1.0",
+ "solid-js": "^1.9.4",
+ "solid-transition-group": "^0.2.3",
+ "ts-blank-space": "^0.4.4"
+ },
+ "devDependencies": {
+ "postcss": "^8.4.49",
+ "tailwind-merge": "^2.6.0",
+ "tailwindcss-animate": "^1.0.7"
+ }
+}
diff --git a/postcss.config.js b/packages/repl/postcss.config.js
similarity index 100%
rename from postcss.config.js
rename to packages/repl/postcss.config.js
diff --git a/src/App.tsx b/packages/repl/src/App.tsx
similarity index 73%
rename from src/App.tsx
rename to packages/repl/src/App.tsx
index 021fea8..b04cbfc 100644
--- a/src/App.tsx
+++ b/packages/repl/src/App.tsx
@@ -1,22 +1,33 @@
-import Resizable from '@corvu/resizable'
-import { createSignal, lazy } from 'solid-js'
+import { workerInit } from 'mtcute-repl-worker/client'
+import { createSignal, lazy, onMount, Show } from 'solid-js'
import { EditorTabs } from './components/editor/EditorTabs.tsx'
import { NavbarMenu } from './components/nav/NavbarMenu.tsx'
import { Runner } from './components/runner/Runner.tsx'
import { SettingsDialog, type SettingsTab } from './components/settings/Settings.tsx'
import { Updater } from './components/Updater.tsx'
-import { ResizableHandle, ResizablePanel } from './lib/components/ui/resizable.tsx'
+import { Resizable, ResizableHandle, ResizablePanel } from './lib/components/ui/resizable.tsx'
const Editor = lazy(() => import('./components/editor/Editor.tsx'))
export function App() {
- const [versions, setVersions] = createSignal | undefined>(undefined)
+ const [updating, setUpdating] = createSignal(true)
const [showSettings, setShowSettings] = createSignal(false)
const [settingsTab, setSettingsTab] = createSignal('accounts')
+ let workerIframe!: HTMLIFrameElement
+
+ onMount(() => {
+ workerInit(workerIframe)
+ })
+
return (
+
- {versions() === undefined ? (
-
- ) : (
+
setUpdating(false)}
+ />
+ )}
+ >
@@ -52,7 +66,7 @@ export function App() {
- )}
+
()
+ onMount(async () => {
+ try {
+ const buf = await workerInvoke('telegram', 'fetchAvatar', props.account.id)
+ if (!buf) return
+
+ const url = URL.createObjectURL(new Blob([buf], { type: 'image/jpeg' }))
+ setUrl(url)
+ } catch (e) {
+ console.error(e)
+ }
+ })
+ onCleanup(() => url() && URL.revokeObjectURL(url()!))
+
+ return (
+
+
+
+ {makeAvatarFallbackText(props.account.name)}
+
+
+ )
+}
diff --git a/packages/repl/src/components/Updater.tsx b/packages/repl/src/components/Updater.tsx
new file mode 100644
index 0000000..5df0852
--- /dev/null
+++ b/packages/repl/src/components/Updater.tsx
@@ -0,0 +1,58 @@
+import { filesize } from 'filesize'
+import { workerInvoke, workerOn } from 'mtcute-repl-worker/client'
+import { createSignal, onMount } from 'solid-js'
+import { Spinner } from '../lib/components/ui/spinner.tsx'
+
+export interface UpdaterProps {
+ onComplete: () => void
+}
+
+export function Updater(props: UpdaterProps) {
+ const [downloadedBytes, setDownloadedBytes] = createSignal(0)
+ const [totalBytes, setTotalBytes] = createSignal(Infinity)
+ const [step, setStep] = createSignal('Idle')
+
+ async function runUpdater() {
+ setStep('Checking for updates...')
+ const updates = await workerInvoke('vfs', 'checkForUpdates')
+
+ if (Object.keys(updates).length === 0) {
+ props.onComplete()
+ return
+ }
+
+ setStep('Downloading...')
+
+ const cleanup = workerOn('UpdateProgress', ({ progress, total }) => {
+ setDownloadedBytes(progress)
+ setTotalBytes(total)
+ })
+ await workerInvoke('vfs', 'downloadPackages', updates)
+ cleanup()
+
+ props.onComplete()
+ }
+
+ onMount(() => {
+ runUpdater()
+ })
+
+ return (
+
+
+
+ {step()}
+ {totalBytes() !== Infinity && (
+
+ {filesize(downloadedBytes())}
+ {' / '}
+ {filesize(totalBytes())}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/editor/Editor.css b/packages/repl/src/components/editor/Editor.css
similarity index 100%
rename from src/components/editor/Editor.css
rename to packages/repl/src/components/editor/Editor.css
diff --git a/src/components/editor/Editor.tsx b/packages/repl/src/components/editor/Editor.tsx
similarity index 97%
rename from src/components/editor/Editor.tsx
rename to packages/repl/src/components/editor/Editor.tsx
index de6755c..eef96b6 100644
--- a/src/components/editor/Editor.tsx
+++ b/packages/repl/src/components/editor/Editor.tsx
@@ -1,7 +1,6 @@
import { editor as mEditor, Uri } from 'monaco-editor'
import { createEffect, on, onMount } from 'solid-js'
import { useColorScheme } from '../../lib/use-color-scheme'
-import { VfsStorage } from '../../lib/vfs/storage.ts'
import { $activeTab, $tabs, type EditorTab } from '../../store/tabs.ts'
import { useStore } from '../../store/use-store.ts'
@@ -44,8 +43,6 @@ export default function Editor(props: EditorProps) {
const modelsByTab = new Map()
onMount(async () => {
- const vfs = await VfsStorage.create()
-
editor = mEditor.create(ref, {
model: null,
automaticLayout: true,
@@ -76,7 +73,7 @@ export default function Editor(props: EditorProps) {
scrollBeyondLastLine: false,
})
- await setupMonaco(vfs)
+ await setupMonaco()
for (const tab of tabs()) {
const model = mEditor.createModel(tab.main ? DEFAULT_CODE : '', 'typescript', Uri.parse(`file:///${tab.id}.ts`))
diff --git a/src/components/editor/EditorTabs.tsx b/packages/repl/src/components/editor/EditorTabs.tsx
similarity index 100%
rename from src/components/editor/EditorTabs.tsx
rename to packages/repl/src/components/editor/EditorTabs.tsx
diff --git a/src/components/editor/utils/custom-worker.ts b/packages/repl/src/components/editor/utils/custom-worker.ts
similarity index 100%
rename from src/components/editor/utils/custom-worker.ts
rename to packages/repl/src/components/editor/utils/custom-worker.ts
diff --git a/src/components/editor/utils/latte.json b/packages/repl/src/components/editor/utils/latte.json
similarity index 100%
rename from src/components/editor/utils/latte.json
rename to packages/repl/src/components/editor/utils/latte.json
diff --git a/src/components/editor/utils/mocha.json b/packages/repl/src/components/editor/utils/mocha.json
similarity index 100%
rename from src/components/editor/utils/mocha.json
rename to packages/repl/src/components/editor/utils/mocha.json
diff --git a/src/components/editor/utils/setup.ts b/packages/repl/src/components/editor/utils/setup.ts
similarity index 91%
rename from src/components/editor/utils/setup.ts
rename to packages/repl/src/components/editor/utils/setup.ts
index e25ea06..1d84e81 100644
--- a/src/components/editor/utils/setup.ts
+++ b/packages/repl/src/components/editor/utils/setup.ts
@@ -1,11 +1,11 @@
-import type { VfsStorage } from '../../../lib/vfs/storage.ts'
import { asNonNull, asyncPool, utf8 } from '@fuman/utils'
+import { wireTmGrammars } from 'monaco-editor-textmate'
import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api.js'
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
-import { wireTmGrammars } from 'monaco-editor-textmate'
import { Registry } from 'monaco-textmate'
-import { loadWASM } from 'onigasm'
+import { workerInvoke } from 'mtcute-repl-worker/client'
+import { loadWASM } from 'onigasm'
import onigasmWasm from 'onigasm/lib/onigasm.wasm?url'
import TypeScriptWorker from './custom-worker.ts?worker'
import latte from './latte.json'
@@ -57,18 +57,19 @@ const compilerOptions: languages.typescript.CompilerOptions = {
languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions)
languages.typescript.javascriptDefaults.setCompilerOptions(compilerOptions)
-export async function setupMonaco(vfs: VfsStorage) {
+export async function setupMonaco() {
if (!loadingWasm) loadingWasm = loadWASM(onigasmWasm)
await loadingWasm
- const libs = await vfs.getAvailableLibs()
+ const libs = await workerInvoke('vfs', 'getLibraryNames')
const extraLibs: {
content: string
filePath?: string
}[] = []
await asyncPool(libs, async (lib) => {
- const { files } = asNonNull(await vfs.readLibrary(lib))
+ const { files } = asNonNull(await workerInvoke('vfs', 'getLibrary', lib))
+
for (const file of files) {
const { path, contents } = file
if (!path.endsWith('.d.ts')) continue
diff --git a/src/components/editor/utils/typescript.tmLanguage.json b/packages/repl/src/components/editor/utils/typescript.tmLanguage.json
similarity index 100%
rename from src/components/editor/utils/typescript.tmLanguage.json
rename to packages/repl/src/components/editor/utils/typescript.tmLanguage.json
diff --git a/src/components/editor/utils/worker.d.ts b/packages/repl/src/components/editor/utils/worker.d.ts
similarity index 100%
rename from src/components/editor/utils/worker.d.ts
rename to packages/repl/src/components/editor/utils/worker.d.ts
diff --git a/packages/repl/src/components/nav/NavbarMenu.tsx b/packages/repl/src/components/nav/NavbarMenu.tsx
new file mode 100644
index 0000000..03f704c
--- /dev/null
+++ b/packages/repl/src/components/nav/NavbarMenu.tsx
@@ -0,0 +1,109 @@
+import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
+import { LucideCheck, LucideChevronDown, LucideExternalLink, LucideLogIn, LucideSettings, LucideUsers } from 'lucide-solid'
+import { SiGithub } from 'solid-icons/si'
+import { For, Show } from 'solid-js'
+import { Button } from '../../lib/components/ui/button.tsx'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuGroupLabel,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '../../lib/components/ui/dropdown-menu.tsx'
+import { cn } from '../../lib/utils.ts'
+import { $accounts, $activeAccount, $activeAccountId } from '../../store/accounts.ts'
+import { useStore } from '../../store/use-store.ts'
+import { AccountAvatar } from '../AccountAvatar.tsx'
+
+export function NavbarMenu(props: {
+ onShowAccounts: () => void
+ onShowSettings: () => void
+}) {
+ const activeAccount = useStore($activeAccount)
+ const accounts = useStore($accounts)
+
+ return (
+
+
+ Log in
+
+ )}
+ >
+
+ (
+
+ )}
+ />
+
+
+ Accounts
+
+ {account => (
+ $activeAccountId.set(account.id)}>
+
+
+ {account.name}
+
+ {account.id === activeAccount()?.id && }
+
+ )}
+
+
+
+ Manage accounts
+
+
+
+
+
+
+ Settings
+
+
+
+ GitHub
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/runner/Actions.tsx b/packages/repl/src/components/runner/Actions.tsx
similarity index 100%
rename from src/components/runner/Actions.tsx
rename to packages/repl/src/components/runner/Actions.tsx
diff --git a/src/components/runner/Devtools.tsx b/packages/repl/src/components/runner/Devtools.tsx
similarity index 83%
rename from src/components/runner/Devtools.tsx
rename to packages/repl/src/components/runner/Devtools.tsx
index f9af104..c1a966d 100644
--- a/src/components/runner/Devtools.tsx
+++ b/packages/repl/src/components/runner/Devtools.tsx
@@ -13,13 +13,15 @@ const HTML = `
`
const INJECTED_SCRIPT = `
-async function waitForElement(selector, container) {
- let tabbedPane;
- while(!tabbedPane) {
- tabbedPane = container.querySelector(selector);
- if (!tabbedPane) await new Promise(resolve => setTimeout(resolve, 50));
+async function waitForElement(selector, container, waitForShadowRoot = false) {
+ let el;
+ while(!el) {
+ el = container.querySelector(selector);
+ if (!el || (waitForShadowRoot && !el.shadowRoot)) {
+ await new Promise(resolve => setTimeout(resolve, 50));
+ }
}
- return tabbedPane;
+ return el;
}
function hideBySelector(root, selector) {
@@ -28,8 +30,8 @@ function hideBySelector(root, selector) {
el.style.display = 'none';
}
-function focusConsole(tabbedPane) {
- const consoleTab = tabbedPane.shadowRoot.querySelector('#tab-console');
+async function focusConsole(tabbedPane) {
+ const consoleTab = await waitForElement('#tab-console', tabbedPane.shadowRoot);
// tabs get focused on mousedown instead of click
consoleTab.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
@@ -38,7 +40,7 @@ function focusConsole(tabbedPane) {
(async ()=> {
const tabbedPane = await waitForElement('.tabbed-pane', document.body);
- focusConsole(tabbedPane);
+ await focusConsole(tabbedPane);
hideBySelector(tabbedPane, '.tabbed-pane-header');
const consoleToolbar = await waitForElement('.console-main-toolbar', document.body);
diff --git a/src/components/runner/Runner.tsx b/packages/repl/src/components/runner/Runner.tsx
similarity index 59%
rename from src/components/runner/Runner.tsx
rename to packages/repl/src/components/runner/Runner.tsx
index 0d5c025..644a278 100644
--- a/src/components/runner/Runner.tsx
+++ b/packages/repl/src/components/runner/Runner.tsx
@@ -1,8 +1,10 @@
import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
-import type { ConnectionState } from '@mtcute/web'
import type { CustomTypeScriptWorker } from '../editor/utils/custom-worker.ts'
-import { LucideCheck, LucidePlay, LucidePlug, LucideRefreshCw, LucideUnplug } from 'lucide-solid'
+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 { Button } from '../../lib/components/ui/button.tsx'
@@ -11,22 +13,74 @@ import { cn } from '../../lib/utils.ts'
import { $activeAccountId } from '../../store/accounts.ts'
import { $tabs } from '../../store/tabs.ts'
import { useStore } from '../../store/use-store.ts'
-import { swForgetScript, swUploadScript } from '../../sw/client.ts'
import { Devtools } from './Devtools.tsx'
+const $disconnectAfterSecs = persistentAtom('repl:disconnectAfterSecs', 60, {
+ encode: String,
+ decode: Number,
+})
+
export function Runner() {
const [devtoolsIframe, setDevtoolsIframe] = createSignal()
+ const [runnerIframe, setRunnerIframe] = createSignal()
const [runnerLoaded, setRunnerLoaded] = createSignal(false)
const [running, setRunning] = createSignal(false)
- const [connectionState, setConnectionState] = createSignal('offline')
+ const [dead, setDead] = createSignal(false)
+ const [connectionState, setConnectionState] = createSignal('offline')
const currentAccountId = useStore($activeAccountId)
+ const disconnectAfterSecs = useStore($disconnectAfterSecs)
let currentScriptId: string | undefined
+ let deadTimer: timers.Timer | undefined
+ let inactivityTimer: timers.Timer | undefined
+ let iframeContainerRef!: HTMLIFrameElement
- let runnerIframeRef!: HTMLIFrameElement
+ function rescheduleInactivityTimer() {
+ if (inactivityTimer) timers.clearTimeout(inactivityTimer)
+ if (connectionState() === 'offline') return
+ if (disconnectAfterSecs() === -1) return
+
+ inactivityTimer = timers.setTimeout(() => {
+ inactivityTimer = undefined
+ handleDisconnect()
+ }, disconnectAfterSecs() * 1000)
+ }
+
+ function setInactivityTimeout(secs: number) {
+ $disconnectAfterSecs.set(secs)
+ rescheduleInactivityTimer()
+ }
+
+ function recreateIframe() {
+ runnerIframe()?.remove()
+
+ setRunnerIframe(undefined)
+ setRunnerLoaded(false)
+ setConnectionState('offline')
+ setDead(false)
+ timers.clearTimeout(deadTimer)
+
+ const iframe = document.createElement('iframe')
+ iframe.className = 'invisible size-0'
+ iframe.src = `${import.meta.env.VITE_IFRAME_URL}/sw/runtime/_iframe.html`
+ iframe.onload = () => {
+ iframe.contentWindow!.postMessage({
+ event: 'INIT',
+ accountId: currentAccountId(),
+ }, '*')
+ setRunnerLoaded(true)
+ deadTimer = timers.setTimeout(() => {
+ setDead(true)
+ }, 2000)
+ }
+ iframeContainerRef.appendChild(iframe)
+ setRunnerIframe(iframe)
+ }
+
+ onMount(recreateIframe)
function handleMessage(e: MessageEvent) {
- if (e.source === runnerIframeRef.contentWindow) {
+ if (e.source === runnerIframe()?.contentWindow) {
// event from runner iframe
switch (e.data.event) {
case 'TO_DEVTOOLS': {
@@ -35,12 +89,22 @@ export function Runner() {
}
case 'SCRIPT_END': {
setRunning(false)
- swForgetScript(currentScriptId!)
+ workerInvoke('sw', 'forgetScript', { name: currentScriptId! })
currentScriptId = undefined
+ rescheduleInactivityTimer()
break
}
case 'CONNECTION_STATE': {
setConnectionState(e.data.value)
+ if (e.data.value === 'connected') {
+ rescheduleInactivityTimer()
+ }
+ break
+ }
+ case 'PING': {
+ if (deadTimer) {
+ timers.clearTimeout(deadTimer)
+ }
break
}
}
@@ -62,7 +126,11 @@ export function Runner() {
return
}
- runnerIframeRef.contentWindow!.postMessage({ event: 'FROM_DEVTOOLS', value: e.data }, '*')
+ if (data.method === 'Runtime.evaluate' && data.params.userGesture) {
+ rescheduleInactivityTimer()
+ }
+
+ runnerIframe()?.contentWindow!.postMessage({ event: 'FROM_DEVTOOLS', value: e.data }, '*')
}
}
@@ -75,7 +143,7 @@ export function Runner() {
createEffect(on(currentAccountId, (accountId) => {
if (!runnerLoaded()) return
- runnerIframeRef.contentWindow!.postMessage({
+ runnerIframe()!.contentWindow!.postMessage({
event: 'ACCOUNT_CHANGED',
accountId,
}, '*')
@@ -100,55 +168,54 @@ export function Runner() {
}
currentScriptId = nanoid()
- await swUploadScript(currentScriptId, files)
+ await workerInvoke('sw', 'uploadScript', { name: currentScriptId, files })
- runnerIframeRef.contentWindow!.postMessage({
+ runnerIframe()!.contentWindow!.postMessage({
event: 'RUN',
scriptId: currentScriptId,
exports,
}, '*')
setRunning(true)
+ timers.clearTimeout(inactivityTimer)
}
function handleDisconnect() {
- runnerIframeRef.contentWindow!.postMessage({ event: 'DISCONNECT' }, '*')
+ runnerIframe()?.contentWindow!.postMessage({ event: 'DISCONNECT' }, '*')
}
function handleConnect() {
- runnerIframeRef.contentWindow!.postMessage({ event: 'RECONNECT' }, '*')
- }
-
- function handleRestart() {
- runnerIframeRef.contentWindow!.location.reload()
+ runnerIframe()?.contentWindow!.postMessage({ event: 'RECONNECT' }, '*')
}
return (
<>
@@ -186,21 +191,6 @@ export function AccountsTab() {
setAddAccountTestMode(e.ctrlKey || e.metaKey)
}
- function handleAccountCreated(accountId: string, user: User, dcId: number) {
- $accounts.set([
- ...$accounts.get(),
- {
- id: accountId,
- name: user.displayName,
- telegramId: user.id,
- bot: user.isBot,
- testMode: addAccountTestMode(),
- dcId,
- },
- ])
- $activeAccountId.set(accountId)
- }
-
const filteredAccounts = createMemo(() => {
const query = searchQuery().toLowerCase().trim()
if (query === '') {
@@ -219,14 +209,17 @@ export function AccountsTab() {
fallback={(
No accounts yet
-
+
+
+
+
)}
@@ -260,7 +253,7 @@ export function AccountsTab() {
Log in
-
+
@@ -280,7 +273,6 @@ export function AccountsTab() {
show={showAddAccount()}
testMode={addAccountTestMode()}
onClose={() => setShowAddAccount(false)}
- onAccountCreated={handleAccountCreated}
/>
>
)
diff --git a/src/components/settings/Settings.tsx b/packages/repl/src/components/settings/Settings.tsx
similarity index 100%
rename from src/components/settings/Settings.tsx
rename to packages/repl/src/components/settings/Settings.tsx
diff --git a/src/components/settings/import/AuthKeyImportDialog.tsx b/packages/repl/src/components/settings/import/AuthKeyImportDialog.tsx
similarity index 96%
rename from src/components/settings/import/AuthKeyImportDialog.tsx
rename to packages/repl/src/components/settings/import/AuthKeyImportDialog.tsx
index c18ad84..ff56e4b 100644
--- a/src/components/settings/import/AuthKeyImportDialog.tsx
+++ b/packages/repl/src/components/settings/import/AuthKeyImportDialog.tsx
@@ -1,12 +1,9 @@
-import type { InputStringSessionData } from '@mtcute/web/utils.js'
import { hex } from '@fuman/utils'
-import { DC_MAPPING_PROD, DC_MAPPING_TEST } from '@mtcute/convert'
import { createEffect, createSignal, on } from 'solid-js'
import { Button } from '../../../lib/components/ui/button.tsx'
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../lib/components/ui/checkbox.tsx'
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
-import { deleteAccount, importAccount } from '../../../lib/telegram.ts'
import { $accounts } from '../../../store/accounts.ts'
export function AuthKeyImportDialog(props: {
diff --git a/src/components/settings/import/ImportDropdown.tsx b/packages/repl/src/components/settings/import/ImportDropdown.tsx
similarity index 86%
rename from src/components/settings/import/ImportDropdown.tsx
rename to packages/repl/src/components/settings/import/ImportDropdown.tsx
index 7406be9..a8eec37 100644
--- a/src/components/settings/import/ImportDropdown.tsx
+++ b/packages/repl/src/components/settings/import/ImportDropdown.tsx
@@ -12,10 +12,11 @@ import {
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '../../../lib/components/ui/dropdown-menu.tsx'
+import { cn } from '../../../lib/utils.ts'
import { AuthKeyImportDialog } from './AuthKeyImportDialog.tsx'
import { StringSessionDefs, StringSessionImportDialog } from './StringSessionImportDialog.tsx'
-export function ImportDropdown() {
+export function ImportDropdown(props: { size: 'xs' | 'sm' }) {
const [showImportStringSession, setShowImportStringSession] = createSignal(false)
const [stringSessionLibName, setStringSessionLibName] = createSignal('mtcute')
const [showImportAuthKey, setShowImportAuthKey] = createSignal(false)
@@ -24,13 +25,21 @@ export function ImportDropdown() {
<>
(
+ as={(triggerProps: DropdownMenuTriggerProps) => (
)}
diff --git a/src/components/settings/import/StringSessionImportDialog.tsx b/packages/repl/src/components/settings/import/StringSessionImportDialog.tsx
similarity index 83%
rename from src/components/settings/import/StringSessionImportDialog.tsx
rename to packages/repl/src/components/settings/import/StringSessionImportDialog.tsx
index cd6cf37..9d5a570 100644
--- a/src/components/settings/import/StringSessionImportDialog.tsx
+++ b/packages/repl/src/components/settings/import/StringSessionImportDialog.tsx
@@ -1,11 +1,8 @@
-import type { StringSessionData } from '@mtcute/web/utils.js'
-import { readStringSession } from '@mtcute/web/utils.js'
import { createEffect, createSignal, on } from 'solid-js'
import { Button } from '../../../lib/components/ui/button.tsx'
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../lib/components/ui/dialog.tsx'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../lib/components/ui/select.tsx'
import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
-import { deleteAccount, importAccount } from '../../../lib/telegram.ts'
import { $accounts } from '../../../store/accounts.ts'
export type StringSessionLibName =
@@ -26,29 +23,29 @@ export const StringSessionDefs: {
{ name: 'mtkruto', displayName: 'MTKruto' },
]
-async function convert(libName: StringSessionLibName, session: string): Promise {
- switch (libName) {
- case 'mtcute': {
- return readStringSession(session)
- }
- case 'telethon': {
- const { convertFromTelethonSession } = await import('@mtcute/convert')
- return convertFromTelethonSession(session)
- }
- case 'gramjs': {
- const { convertFromGramjsSession } = await import('@mtcute/convert')
- return convertFromGramjsSession(session)
- }
- case 'pyrogram': {
- const { convertFromPyrogramSession } = await import('@mtcute/convert')
- return convertFromPyrogramSession(session)
- }
- case 'mtkruto': {
- const { convertFromMtkrutoSession } = await import('@mtcute/convert')
- return convertFromMtkrutoSession(session)
- }
- }
-}
+// async function convert(libName: StringSessionLibName, session: string): Promise {
+// switch (libName) {
+// case 'mtcute': {
+// return readStringSession(session)
+// }
+// case 'telethon': {
+// const { convertFromTelethonSession } = await import('@mtcute/convert')
+// return convertFromTelethonSession(session)
+// }
+// case 'gramjs': {
+// const { convertFromGramjsSession } = await import('@mtcute/convert')
+// return convertFromGramjsSession(session)
+// }
+// case 'pyrogram': {
+// const { convertFromPyrogramSession } = await import('@mtcute/convert')
+// return convertFromPyrogramSession(session)
+// }
+// case 'mtkruto': {
+// const { convertFromMtkrutoSession } = await import('@mtcute/convert')
+// return convertFromMtkrutoSession(session)
+// }
+// }
+// }
export function StringSessionImportDialog(props: {
open: boolean
diff --git a/src/components/login/Login.tsx b/packages/repl/src/components/settings/login/Login.tsx
similarity index 76%
rename from src/components/login/Login.tsx
rename to packages/repl/src/components/settings/login/Login.tsx
index 21c6b9c..4d14c11 100644
--- a/src/components/login/Login.tsx
+++ b/packages/repl/src/components/settings/login/Login.tsx
@@ -1,17 +1,15 @@
-import type { BaseTelegramClient, SentCode, User } from '@mtcute/web'
-import { base64 } from '@fuman/utils'
-import { tl } from '@mtcute/web'
-import { checkPassword, downloadAsBuffer, resendCode, sendCode, signIn, signInQr } from '@mtcute/web/methods.js'
+import type { mtcute, TelegramAccount } from 'mtcute-repl-worker/client'
+import { unknownToError } from '@fuman/utils'
import { LucideChevronRight } from 'lucide-solid'
+import { workerInvoke, workerOn } from 'mtcute-repl-worker/client'
import { createEffect, createSignal, For, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
-import { renderSVG } from 'uqr'
-import { Avatar, AvatarFallback, AvatarImage, makeAvatarFallbackText } from '../../lib/components/ui/avatar.tsx'
-import { Button } from '../../lib/components/ui/button.tsx'
-import { OTPField, OTPFieldGroup, OTPFieldInput, OTPFieldSlot } from '../../lib/components/ui/otp-field.tsx'
-import { Spinner } from '../../lib/components/ui/spinner.tsx'
-import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../lib/components/ui/text-field.tsx'
-import { TransitionSlideLtr } from '../../lib/components/ui/transition.tsx'
-import { cn } from '../../lib/utils.ts'
+import { Button } from '../../../lib/components/ui/button.tsx'
+import { OTPField, OTPFieldGroup, OTPFieldInput, OTPFieldSlot } from '../../../lib/components/ui/otp-field.tsx'
+import { Spinner } from '../../../lib/components/ui/spinner.tsx'
+import { TextField, TextFieldErrorMessage, TextFieldFrame, TextFieldLabel, TextFieldRoot } from '../../../lib/components/ui/text-field.tsx'
+import { TransitionSlideLtr } from '../../../lib/components/ui/transition.tsx'
+import { cn } from '../../../lib/utils.ts'
+import { AccountAvatar } from '../../AccountAvatar.tsx'
import { PhoneInput } from './PhoneInput.tsx'
export type LoginStep =
@@ -25,14 +23,14 @@ export interface StepContext {
phone: void
otp: {
phone: string
- code: SentCode
+ code: mtcute.SentCode
}
password: void
- done: { user: User }
+ done: { account: TelegramAccount }
}
type StepProps = {
- client: BaseTelegramClient
+ accountId: string
setStep: (step: T, data?: StepContext[T]) => void
} & (StepContext[T] extends void ? {} : { ctx: StepContext[T] })
@@ -41,23 +39,29 @@ function QrLoginStep(props: StepProps<'qr'>) {
const [finalizing, setFinalizing] = createSignal(false)
const abortController = new AbortController()
- onMount(() => {
- signInQr(props.client, {
- abortSignal: abortController.signal,
- onUrlUpdated: qr => setQr(renderSVG(qr)),
- onQrScanned: () => setFinalizing(true),
- }).then((user) => {
- props.setStep('done', { user })
- }).catch((e) => {
- setFinalizing(false)
- if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
- props.setStep('password')
- } else if (abortController.signal.aborted) {
- // ignore
- } else {
- throw e
- }
+ onMount(async () => {
+ const cleanup1 = workerOn('QrCodeUpdate', (e) => {
+ if (e.accountId !== props.accountId) return
+ setQr(e.qrCode)
})
+ const cleanup2 = workerOn('QrCodeScanned', (e) => {
+ if (e.accountId !== props.accountId) return
+ setFinalizing(true)
+ })
+ onCleanup(() => {
+ cleanup1()
+ cleanup2()
+ })
+
+ const result = await workerInvoke('telegram', 'signInQr', {
+ accountId: props.accountId,
+ abortSignal: abortController.signal,
+ })
+ if (result === 'need_password') {
+ props.setStep('password')
+ } else {
+ props.setStep('done', { account: result })
+ }
})
onCleanup(() => abortController.abort())
@@ -104,26 +108,29 @@ function PhoneNumberStep(props: StepProps<'phone'>) {
const [inputRef, setInputRef] = createSignal()
const abortController = new AbortController()
- const handleSubmit = () => {
+ const handleSubmit = async () => {
setError(undefined)
setLoading(true)
- sendCode(props.client, {
- phone: phone(),
- abortSignal: abortController.signal,
- }).then((code) => {
+
+ try {
+ const code = await workerInvoke('telegram', 'sendCode', {
+ accountId: props.accountId,
+ phone: phone(),
+ abortSignal: abortController.signal,
+ })
setLoading(false)
props.setStep('otp', {
code,
phone: phone(),
})
- }).catch((e) => {
+ } catch (e) {
setLoading(false)
if (abortController.signal.aborted) {
// ignore
} else {
- setError(e.message)
+ setError(unknownToError(e).message)
}
- })
+ }
}
onCleanup(() => abortController.abort())
createEffect(() => inputRef()?.focus())
@@ -150,7 +157,7 @@ function PhoneNumberStep(props: StepProps<'phone'>) {
) {
const [inputRef, setInputRef] = createSignal()
const abortController = new AbortController()
- const handleSubmit = () => {
+ const handleSubmit = async () => {
setError(undefined)
setLoading(true)
- signIn(props.client, {
- phone: props.ctx.phone,
- phoneCodeHash: props.ctx.code.phoneCodeHash,
- phoneCode: otp(),
- abortSignal: abortController.signal,
- }).then((user) => {
- setLoading(false)
- props.setStep('done', { user })
- }).catch((e) => {
+
+ try {
+ const account = await workerInvoke('telegram', 'signIn', {
+ accountId: props.accountId,
+ phone: props.ctx.phone,
+ phoneCodeHash: props.ctx.code.phoneCodeHash,
+ phoneCode: otp(),
+ abortSignal: abortController.signal,
+ })
+ if (account === 'need_password') {
+ props.setStep('password')
+ } else {
+ props.setStep('done', { account })
+ }
+ } catch (e) {
setLoading(false)
if (abortController.signal.aborted) {
// ignore
- } else if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
- props.setStep('password')
} else {
- setError(e.message)
+ setError(unknownToError(e).message)
}
- })
+ }
}
- const handleResend = () => {
+ const handleResend = async () => {
setError(undefined)
setLoading(true)
- resendCode(props.client, {
- phone: props.ctx.phone,
- phoneCodeHash: props.ctx.code.phoneCodeHash,
- abortSignal: abortController.signal,
- }).then((code) => {
+ try {
+ const code = await workerInvoke('telegram', 'resendCode', {
+ accountId: props.accountId,
+ phone: props.ctx.phone,
+ phoneCodeHash: props.ctx.code.phoneCodeHash,
+ abortSignal: abortController.signal,
+ })
setLoading(false)
props.setStep('otp', {
code,
phone: props.ctx.phone,
})
- }).catch((e) => {
+ } catch (e) {
setLoading(false)
if (abortController.signal.aborted) {
// ignore
} else {
- setError(e.message)
+ setError(unknownToError(e).message)
}
- })
+ }
}
+
const handleSetOtp = (otp: string) => {
setOtp(otp)
if (otp.length === props.ctx.code.length) {
@@ -385,8 +399,10 @@ function PasswordStep(props: StepProps<'password'>) {
const [loading, setLoading] = createSignal(false)
const [inputRef, setInputRef] = createSignal()
- // todo abort controller
- const handleSubmit = () => {
+ const abortController = new AbortController()
+ onCleanup(() => abortController.abort())
+
+ const handleSubmit = async () => {
if (!password()) {
setError('Password is required')
return
@@ -394,19 +410,17 @@ function PasswordStep(props: StepProps<'password'>) {
setError(undefined)
setLoading(true)
- checkPassword(props.client, password())
- .then((user) => {
- setLoading(false)
- props.setStep('done', { user })
- })
- .catch((e) => {
- setLoading(false)
- if (tl.RpcError.is(e, 'PASSWORD_HASH_INVALID')) {
- setError('Incorrect password')
- } else {
- setError(e.message)
- }
+ try {
+ const user = await workerInvoke('telegram', 'checkPassword', {
+ accountId: props.accountId,
+ password: password(),
+ abortSignal: abortController.signal,
})
+ props.setStep('done', { account: user })
+ } catch (e) {
+ setLoading(false)
+ setError(unknownToError(e).message)
+ }
}
createEffect(() => inputRef()?.focus())
@@ -456,47 +470,16 @@ function PasswordStep(props: StepProps<'password'>) {
}
function DoneStep(props: StepProps<'done'>) {
- const [avatar, setAvatar] = createSignal(null)
-
- onMount(() => {
- if (!props.ctx.user.photo) {
- props.client.close()
- return
- }
-
- downloadAsBuffer(props.client, props.ctx.user.photo.big)
- .then((buf) => {
- const url = URL.createObjectURL(new Blob([buf], { type: 'image/jpeg' }))
- setAvatar(url)
- })
- .catch((e) => {
- console.error(e)
- })
- })
-
- onCleanup(() => {
- if (avatar()) {
- URL.revokeObjectURL(avatar()!)
- }
- })
-
return (
-
- {props.ctx.user.photo && (
- <>
- {avatar() && }
-
- >
- )}
-
- {makeAvatarFallbackText(props.ctx.user.displayName)}
-
-
+
Welcome,
{' '}
- {props.ctx.user.displayName}
+ {props.ctx.account.name}
!
@@ -505,7 +488,7 @@ function DoneStep(props: StepProps<'done'>) {
export function LoginForm(props: {
class?: string
- client: BaseTelegramClient
+ accountId: string
onStepChange?: (step: LoginStep, ctx: Partial) => void
}) {
const [step, setStep] = createSignal('qr')
@@ -522,20 +505,20 @@ export function LoginForm(props: {
-
+
-
+
-
+
-
+
diff --git a/src/components/login/PhoneInput.tsx b/packages/repl/src/components/settings/login/PhoneInput.tsx
similarity index 78%
rename from src/components/login/PhoneInput.tsx
rename to packages/repl/src/components/settings/login/PhoneInput.tsx
index e4c92a2..1e95241 100644
--- a/src/components/login/PhoneInput.tsx
+++ b/packages/repl/src/components/settings/login/PhoneInput.tsx
@@ -1,10 +1,8 @@
-import type { BaseTelegramClient, tl } from '@mtcute/web'
-
-import { assert } from '@fuman/utils'
+import { type mtcute, workerInvoke } from 'mtcute-repl-worker/client'
import { createSignal, onMount, Show } from 'solid-js'
-import { CountryIcon } from '../../lib/components/country-icon.tsx'
-import { TextField, TextFieldFrame } from '../../lib/components/ui/text-field.tsx'
-import { cn } from '../../lib/utils.ts'
+import { CountryIcon } from '../../../lib/components/country-icon.tsx'
+import { TextField, TextFieldFrame } from '../../../lib/components/ui/text-field.tsx'
+import { cn } from '../../../lib/utils.ts'
interface ChosenCode {
patterns?: string[]
@@ -12,7 +10,7 @@ interface ChosenCode {
iso2: string
}
-function mapCountryCode(country: tl.help.RawCountry, code: tl.help.RawCountryCode): ChosenCode {
+function mapCountryCode(country: mtcute.RawCountry, code: mtcute.RawCountryCode): ChosenCode {
return {
patterns: code.patterns,
countryCode: code.countryCode,
@@ -25,35 +23,30 @@ interface PhoneInputProps {
phone?: string
onChange?: (phone: string) => void
onSubmit?: () => void
- client: BaseTelegramClient
+ accountId: string
disabled?: boolean
ref?: (el: HTMLInputElement) => void
}
export function PhoneInput(props: PhoneInputProps) {
- const [countriesList, setCountriesList] = createSignal([])
+ const [countriesList, setCountriesList] = createSignal([])
const [chosenCode, setChosenCode] = createSignal()
const [inputValue, setInputValue] = createSignal('+')
- onMount(() => {
- Promise.all([
- props.client.call({ _: 'help.getCountriesList', langCode: 'en', hash: 0 }),
- props.client.call({ _: 'help.getNearestDc' }),
- ]).then(([countriesList, nearestDc]) => {
- assert(countriesList._ === 'help.countriesList') // todo caching
- setCountriesList(countriesList.countries)
+ onMount(async () => {
+ const { countries, countryByIp } = await workerInvoke('telegram', 'loadCountries', { accountId: props.accountId })
+ setCountriesList(countries)
- if (inputValue() === '+') {
- // guess the country code
- for (const country of countriesList.countries) {
- if (country.iso2 === nearestDc.country.toUpperCase()) {
- setChosenCode(mapCountryCode(country, country.countryCodes[0]))
- setInputValue(`+${country.countryCodes[0].countryCode} `)
- break
- }
+ if (inputValue() === '+') {
+ // guess the country code
+ for (const country of countries) {
+ if (country.iso2 === countryByIp) {
+ setChosenCode(mapCountryCode(country, country.countryCodes[0]))
+ setInputValue(`+${country.countryCodes[0].countryCode} `)
+ break
}
}
- })
+ }
})
const handleInput = (e: InputEvent) => {
@@ -77,7 +70,7 @@ export function PhoneInput(props: PhoneInputProps) {
el.value = `+${value.replace(/[^\d ]/g, '')}`
// pass 1: find matching countries by country code
- const matching: [tl.help.RawCountry, tl.help.RawCountryCode][] = []
+ const matching: [mtcute.RawCountry, mtcute.RawCountryCode][] = []
let hasPrefixes = false
for (const country of countriesList()) {
diff --git a/src/index.tsx b/packages/repl/src/index.tsx
similarity index 69%
rename from src/index.tsx
rename to packages/repl/src/index.tsx
index 4678df8..0c64c52 100644
--- a/src/index.tsx
+++ b/packages/repl/src/index.tsx
@@ -2,11 +2,10 @@
import { render } from 'solid-js/web'
import { App } from './App'
-import { registerServiceWorker } from './sw/register.ts'
import './app.css'
const root = document.getElementById('root')
-registerServiceWorker()
+// registerServiceWorker()
render(() => , root!)
diff --git a/src/lib/components/country-icon.tsx b/packages/repl/src/lib/components/country-icon.tsx
similarity index 100%
rename from src/lib/components/country-icon.tsx
rename to packages/repl/src/lib/components/country-icon.tsx
diff --git a/src/lib/components/ui/avatar.tsx b/packages/repl/src/lib/components/ui/avatar.tsx
similarity index 100%
rename from src/lib/components/ui/avatar.tsx
rename to packages/repl/src/lib/components/ui/avatar.tsx
diff --git a/src/lib/components/ui/badge.tsx b/packages/repl/src/lib/components/ui/badge.tsx
similarity index 100%
rename from src/lib/components/ui/badge.tsx
rename to packages/repl/src/lib/components/ui/badge.tsx
diff --git a/src/lib/components/ui/button.tsx b/packages/repl/src/lib/components/ui/button.tsx
similarity index 96%
rename from src/lib/components/ui/button.tsx
rename to packages/repl/src/lib/components/ui/button.tsx
index 3fa2c18..1fa3293 100644
--- a/src/lib/components/ui/button.tsx
+++ b/packages/repl/src/lib/components/ui/button.tsx
@@ -17,6 +17,7 @@ const buttonVariants = cva(
outline: 'border border-input text-muted-foreground hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
+ ghostDestructive: 'text-error-foreground hover:bg-error',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
diff --git a/src/lib/components/ui/checkbox.tsx b/packages/repl/src/lib/components/ui/checkbox.tsx
similarity index 100%
rename from src/lib/components/ui/checkbox.tsx
rename to packages/repl/src/lib/components/ui/checkbox.tsx
diff --git a/src/lib/components/ui/dialog.tsx b/packages/repl/src/lib/components/ui/dialog.tsx
similarity index 100%
rename from src/lib/components/ui/dialog.tsx
rename to packages/repl/src/lib/components/ui/dialog.tsx
diff --git a/src/lib/components/ui/dropdown-menu.tsx b/packages/repl/src/lib/components/ui/dropdown-menu.tsx
similarity index 100%
rename from src/lib/components/ui/dropdown-menu.tsx
rename to packages/repl/src/lib/components/ui/dropdown-menu.tsx
diff --git a/src/lib/components/ui/label.tsx b/packages/repl/src/lib/components/ui/label.tsx
similarity index 100%
rename from src/lib/components/ui/label.tsx
rename to packages/repl/src/lib/components/ui/label.tsx
diff --git a/src/lib/components/ui/otp-field.tsx b/packages/repl/src/lib/components/ui/otp-field.tsx
similarity index 100%
rename from src/lib/components/ui/otp-field.tsx
rename to packages/repl/src/lib/components/ui/otp-field.tsx
diff --git a/src/lib/components/ui/resizable.tsx b/packages/repl/src/lib/components/ui/resizable.tsx
similarity index 100%
rename from src/lib/components/ui/resizable.tsx
rename to packages/repl/src/lib/components/ui/resizable.tsx
diff --git a/src/lib/components/ui/select.tsx b/packages/repl/src/lib/components/ui/select.tsx
similarity index 100%
rename from src/lib/components/ui/select.tsx
rename to packages/repl/src/lib/components/ui/select.tsx
diff --git a/src/lib/components/ui/spinner.tsx b/packages/repl/src/lib/components/ui/spinner.tsx
similarity index 100%
rename from src/lib/components/ui/spinner.tsx
rename to packages/repl/src/lib/components/ui/spinner.tsx
diff --git a/src/lib/components/ui/tabs.tsx b/packages/repl/src/lib/components/ui/tabs.tsx
similarity index 100%
rename from src/lib/components/ui/tabs.tsx
rename to packages/repl/src/lib/components/ui/tabs.tsx
diff --git a/src/lib/components/ui/text-field.tsx b/packages/repl/src/lib/components/ui/text-field.tsx
similarity index 100%
rename from src/lib/components/ui/text-field.tsx
rename to packages/repl/src/lib/components/ui/text-field.tsx
diff --git a/src/lib/components/ui/tooltip.tsx b/packages/repl/src/lib/components/ui/tooltip.tsx
similarity index 100%
rename from src/lib/components/ui/tooltip.tsx
rename to packages/repl/src/lib/components/ui/tooltip.tsx
diff --git a/src/lib/components/ui/transition.tsx b/packages/repl/src/lib/components/ui/transition.tsx
similarity index 100%
rename from src/lib/components/ui/transition.tsx
rename to packages/repl/src/lib/components/ui/transition.tsx
diff --git a/src/lib/use-color-scheme.ts b/packages/repl/src/lib/use-color-scheme.ts
similarity index 100%
rename from src/lib/use-color-scheme.ts
rename to packages/repl/src/lib/use-color-scheme.ts
diff --git a/packages/repl/src/lib/utils.ts b/packages/repl/src/lib/utils.ts
new file mode 100644
index 0000000..abba253
--- /dev/null
+++ b/packages/repl/src/lib/utils.ts
@@ -0,0 +1,7 @@
+import type { ClassValue } from 'clsx'
+import { clsx } from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/packages/repl/src/store/accounts.ts b/packages/repl/src/store/accounts.ts
new file mode 100644
index 0000000..8e6d22a
--- /dev/null
+++ b/packages/repl/src/store/accounts.ts
@@ -0,0 +1,15 @@
+import type { TelegramAccount } from 'mtcute-repl-worker/client'
+import { computed } from 'nanostores'
+import { linkedAtom } from './link.ts'
+
+export const $accounts = linkedAtom('accounts')
+export const $activeAccountId = linkedAtom('activeAccountId')
+
+export const $activeAccount = computed([$accounts, $activeAccountId], (accounts, activeAccountId) => {
+ if (!activeAccountId) return null
+
+ const account = accounts.find(account => account.id === activeAccountId)
+ if (!account) return null
+
+ return account
+})
diff --git a/packages/repl/src/store/link.ts b/packages/repl/src/store/link.ts
new file mode 100644
index 0000000..f7d656b
--- /dev/null
+++ b/packages/repl/src/store/link.ts
@@ -0,0 +1,25 @@
+import { workerInvoke, workerOn } from 'mtcute-repl-worker/client'
+import { atom, type ReadableAtom, type WritableAtom } from 'nanostores'
+
+const linkedAtoms = new Map>()
+let registered = false
+let isInternalWrite = false
+
+export function linkedAtom(id: string): ReadableAtom & WritableAtom {
+ if (!registered) {
+ workerOn('AtomUpdate', ({ id, value }) => {
+ isInternalWrite = true
+ linkedAtoms.get(id)?.set(value)
+ isInternalWrite = false
+ })
+ registered = true
+ }
+
+ const store = atom(null!)
+ store.listen((value) => {
+ if (isInternalWrite) return
+ workerInvoke('atom', 'write', { id, value })
+ })
+ linkedAtoms.set(id, store)
+ return store
+}
diff --git a/src/store/tabs.ts b/packages/repl/src/store/tabs.ts
similarity index 100%
rename from src/store/tabs.ts
rename to packages/repl/src/store/tabs.ts
diff --git a/src/store/use-store.ts b/packages/repl/src/store/use-store.ts
similarity index 100%
rename from src/store/use-store.ts
rename to packages/repl/src/store/use-store.ts
diff --git a/packages/repl/src/vite-env.d.ts b/packages/repl/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/packages/repl/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tailwind.config.js b/packages/repl/tailwind.config.js
similarity index 100%
rename from tailwind.config.js
rename to packages/repl/tailwind.config.js
diff --git a/ui.config.json b/packages/repl/ui.config.json
similarity index 100%
rename from ui.config.json
rename to packages/repl/ui.config.json
diff --git a/packages/repl/vite.config.ts b/packages/repl/vite.config.ts
new file mode 100644
index 0000000..1509bf0
--- /dev/null
+++ b/packages/repl/vite.config.ts
@@ -0,0 +1,25 @@
+import type { UserConfig } from 'vite'
+import { join } from 'node:path'
+import { defineConfig, loadEnv } from 'vite'
+import solid from 'vite-plugin-solid'
+// eslint-disable-next-line import/no-relative-packages
+import externalizeDeps from '../../scripts/vite-plugin-externalize-dependencies.ts'
+
+export default defineConfig((env): UserConfig => {
+ process.env = {
+ ...process.env,
+ ...loadEnv(env.mode, join(__dirname, '../..')),
+ }
+
+ return {
+ server: {
+ port: 3000,
+ },
+ plugins: [
+ solid(),
+ externalizeDeps({
+ externals: [],
+ }),
+ ],
+ }
+})
diff --git a/packages/worker/index.html b/packages/worker/index.html
new file mode 100644
index 0000000..9a317d9
--- /dev/null
+++ b/packages/worker/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ @mtcute/playground worker
+
+
+
+
+
+
diff --git a/packages/worker/package.json b/packages/worker/package.json
new file mode 100644
index 0000000..e132d25
--- /dev/null
+++ b/packages/worker/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "mtcute-repl-worker",
+ "type": "module",
+ "version": "0.0.0",
+ "private": true,
+ "packageManager": "pnpm@9.5.0",
+ "exports": {
+ "./client": "./src/client.ts"
+ },
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@badrap/valita": "^0.4.2",
+ "@fuman/fetch": "^0.0.8",
+ "@fuman/io": "0.0.8",
+ "@fuman/utils": "0.0.4",
+ "@mtcute/convert": "^0.19.4",
+ "@mtcute/web": "^0.19.5",
+ "@nanostores/persistent": "^0.10.2",
+ "fflate": "^0.8.2",
+ "idb": "^8.0.1",
+ "nanoid": "^5.0.9",
+ "nanostores": "^0.11.3",
+ "uqr": "^0.1.2"
+ }
+}
diff --git a/packages/worker/src/client.ts b/packages/worker/src/client.ts
new file mode 100644
index 0000000..23ed978
--- /dev/null
+++ b/packages/worker/src/client.ts
@@ -0,0 +1,114 @@
+import type * as mtcuteTypes from '@mtcute/web'
+import type { ReplWorker } from './worker/main.ts'
+import type { ReplWorkerEvents } from './worker/utils.ts'
+import { Deferred, unknownToError } from '@fuman/utils'
+
+export type { TelegramAccount } from './store/accounts.ts'
+
+// eslint-disable-next-line ts/no-namespace
+export namespace mtcute {
+ export type RawCountry = mtcuteTypes.tl.help.RawCountry
+ export type RawCountryCode = mtcuteTypes.tl.help.RawCountryCode
+ export type SentCode = mtcuteTypes.SentCode
+ export type ConnectionState = mtcuteTypes.ConnectionState
+}
+
+const pending = new Map>()
+const listeners = new Map void)[]>()
+
+let nextId = 0
+let iframe: HTMLIFrameElement
+let loadedDeferred: Deferred | undefined
+
+export function workerInit(iframe_: HTMLIFrameElement) {
+ iframe = iframe_
+ loadedDeferred = new Deferred()
+ iframe.addEventListener('error', () => {
+ loadedDeferred?.reject(new Error('Failed to load worker iframe'))
+ loadedDeferred = undefined
+ })
+ window.addEventListener('message', (e) => {
+ if (e.source !== iframe.contentWindow) return
+ if (e.data.event === 'LOADED') {
+ loadedDeferred?.resolve()
+ loadedDeferred = undefined
+ return
+ }
+ if (e.data.event) {
+ const fns = listeners.get(e.data.event)
+ if (fns) {
+ for (const fn of fns) {
+ fn(e.data.data)
+ }
+ }
+ return
+ }
+
+ const { id, result, error } = e.data
+ const def = pending.get(id)
+ if (!def) return
+ if (error) {
+ def.reject(unknownToError(error))
+ } else {
+ def.resolve(result)
+ }
+ })
+}
+
+type ForceFunction = T extends (...args: any) => any ? T : never
+
+export async function workerInvoke<
+ Domain extends keyof ReplWorker,
+ Method extends keyof ReplWorker[Domain],
+>(
+ domain: Domain,
+ method: Method,
+ ...params: Parameters> extends [infer Params] ? [Params] : []
+): Promise>>
+
+export async function workerInvoke(domain: string, method: string, params?: any) {
+ if (loadedDeferred) {
+ await loadedDeferred.promise
+ }
+
+ const id = nextId++
+ const def = new Deferred()
+ pending.set(id, def)
+
+ let withAbort = false
+ if (params?.abortSignal) {
+ const signal = params.abortSignal
+ signal.addEventListener('abort', () => {
+ iframe.contentWindow!.postMessage({ id, abort: true }, '*')
+ def.reject(signal.reason)
+ pending.delete(id)
+ })
+ delete params.abortSignal
+ withAbort = true
+ }
+
+ iframe.contentWindow!.postMessage({
+ id,
+ domain,
+ method,
+ params,
+ withAbort,
+ }, '*')
+ return def.promise
+}
+
+export function workerOn(event: Event, listener: (e: ReplWorkerEvents[Event]) => void) {
+ if (!listeners.has(event)) {
+ listeners.set(event, [])
+ }
+
+ const arr = listeners.get(event)!
+ arr.push(listener)
+
+ return () => {
+ const idx = arr.indexOf(listener)
+ if (idx !== -1) {
+ arr.splice(idx, 1)
+ }
+ }
+}
diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts
new file mode 100644
index 0000000..9d50dbd
--- /dev/null
+++ b/packages/worker/src/index.ts
@@ -0,0 +1,18 @@
+import { registerServiceWorker } from './sw/register.ts'
+import { registerWorker, ReplWorker } from './worker/main.ts'
+import './store/accounts.ts'
+
+registerServiceWorker()
+
+if (!window.parent || window.parent === window) {
+ document.querySelector('#root')!.innerHTML = 'This is an internal page used by the mtcute-repl app, and must be loaded in an iframe.'
+ throw new Error('Not in iframe')
+}
+
+if (new URL(document.referrer).origin !== import.meta.env.VITE_HOST_ORIGIN) {
+ throw new Error(`Invalid origin: this page must be loaded in an iframe from ${import.meta.env.VITE_HOST_ORIGIN}`)
+}
+
+const worker = new ReplWorker()
+
+registerWorker(worker)
diff --git a/src/store/accounts.ts b/packages/worker/src/store/accounts.ts
similarity index 74%
rename from src/store/accounts.ts
rename to packages/worker/src/store/accounts.ts
index c1e0e7f..decbe87 100644
--- a/src/store/accounts.ts
+++ b/packages/worker/src/store/accounts.ts
@@ -1,6 +1,6 @@
import * as v from '@badrap/valita'
import { persistentAtom } from '@nanostores/persistent'
-import { computed } from 'nanostores'
+import { linkAtom } from './link.ts'
export interface TelegramAccount {
id: string
@@ -37,13 +37,7 @@ export const $accounts = persistentAtom('repl:accounts', [],
return res
},
})
+linkAtom($accounts, 'accounts')
export const $activeAccountId = persistentAtom('repl:activeAccountId')
-export const $activeAccount = computed([$accounts, $activeAccountId], (accounts, activeAccountId) => {
- if (!activeAccountId) return null
-
- const account = accounts.find(account => account.id === activeAccountId)
- if (!account) return null
-
- return account
-})
+linkAtom($activeAccountId, 'activeAccountId')
diff --git a/packages/worker/src/store/link.ts b/packages/worker/src/store/link.ts
new file mode 100644
index 0000000..1736314
--- /dev/null
+++ b/packages/worker/src/store/link.ts
@@ -0,0 +1,22 @@
+import type { ReadableAtom, WritableAtom } from 'nanostores'
+import { emitEvent } from '../worker/utils.ts'
+
+const linkedAtoms = new Map & WritableAtom>()
+export function linkAtom(atom: ReadableAtom & WritableAtom, id: string) {
+ atom.subscribe((value) => {
+ emitEvent('AtomUpdate', { id, value })
+ })
+ linkedAtoms.set(id, atom)
+}
+
+export function publishLinkedAtoms() {
+ for (const [id, atom] of linkedAtoms) {
+ emitEvent('AtomUpdate', { id, value: atom.get() })
+ }
+}
+
+export function writeLinkedAtom(id: string, value: T) {
+ const atom = linkedAtoms.get(id)
+ if (!atom) return
+ atom.set(value)
+}
diff --git a/src/sw/avatar.ts b/packages/worker/src/sw/avatar.ts
similarity index 88%
rename from src/sw/avatar.ts
rename to packages/worker/src/sw/avatar.ts
index 1b7a428..416b474 100644
--- a/src/sw/avatar.ts
+++ b/packages/worker/src/sw/avatar.ts
@@ -1,8 +1,7 @@
import type { BaseTelegramClient } from '@mtcute/web'
import { downloadAsBuffer, getMe } from '@mtcute/web/methods.js'
-import { createInternalClient } from '../lib/telegram.ts'
-import { timeout } from '../lib/utils.ts'
-import { $accounts } from '../store/accounts.ts'
+import { createInternalClient } from '../utils/telegram.ts'
+import { timeout } from '../utils/timeout.ts'
import { getCacheStorage } from './cache.ts'
const clients = new Map()
@@ -38,8 +37,10 @@ export async function handleAvatarRequest(accountId: string) {
headers: {
'Content-Type': 'image/jpeg',
'Cache-Control': 'public, max-age=86400',
+ 'Access-Control-Allow-Origin': '*',
},
})
+
await cache.put(cacheKey, res.clone())
return res
diff --git a/src/sw/cache.ts b/packages/worker/src/sw/cache.ts
similarity index 88%
rename from src/sw/cache.ts
rename to packages/worker/src/sw/cache.ts
index 830b268..56aff7e 100644
--- a/src/sw/cache.ts
+++ b/packages/worker/src/sw/cache.ts
@@ -1,4 +1,4 @@
-import { timeout } from '../lib/utils.ts'
+import { timeout } from '../utils/timeout.ts'
let _cacheStorage: Cache | undefined
@@ -14,7 +14,6 @@ export async function getCacheStorage() {
export async function requestCache(event: FetchEvent) {
try {
- // const cache = await ctx.caches.open(CACHE_ASSETS_NAME);
const cache = await timeout(getCacheStorage(), 10000)
const cachedRes = await timeout(cache.match(event.request), 10000)
diff --git a/src/sw/client.ts b/packages/worker/src/sw/client.ts
similarity index 68%
rename from src/sw/client.ts
rename to packages/worker/src/sw/client.ts
index d76b584..96e29b9 100644
--- a/src/sw/client.ts
+++ b/packages/worker/src/sw/client.ts
@@ -9,7 +9,7 @@ let registered = false
let nextId = 0
const pending = new Map>()
-function swInvokeMethod(request: SwMessage) {
+export function swInvokeMethod(request: SwMessage) {
const sw = getServiceWorker()
if (!registered) {
navigator.serviceWorker.addEventListener('message', (e) => {
@@ -33,15 +33,3 @@ function swInvokeMethod(request: SwMessage) {
sw.postMessage(request)
return def.promise
}
-
-export function swUploadScript(name: string, files: Record) {
- return swInvokeMethod({ event: 'UPLOAD_SCRIPT', name, files })
-}
-
-export function swForgetScript(name: string) {
- return swInvokeMethod({ event: 'FORGET_SCRIPT', name })
-}
-
-export function swClearCache() {
- return swInvokeMethod({ event: 'CLEAR_CACHE' })
-}
diff --git a/packages/worker/src/sw/iframe.ts b/packages/worker/src/sw/iframe.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/vite-env.d.ts b/packages/worker/src/sw/iframe/external.d.ts
similarity index 64%
rename from src/vite-env.d.ts
rename to packages/worker/src/sw/iframe/external.d.ts
index 2596f01..83946d2 100644
--- a/src/vite-env.d.ts
+++ b/packages/worker/src/sw/iframe/external.d.ts
@@ -1,5 +1,3 @@
-///
-
declare module '@mtcute/web?external' {
export * from '@mtcute/web'
}
diff --git a/src/lib/runtime.ts b/packages/worker/src/sw/iframe/html.ts
similarity index 95%
rename from src/lib/runtime.ts
rename to packages/worker/src/sw/iframe/html.ts
index 543aa6c..ec80075 100644
--- a/src/lib/runtime.ts
+++ b/packages/worker/src/sw/iframe/html.ts
@@ -1,6 +1,6 @@
// eslint-disable-next-line antfu/no-import-dist
-import chobitsuUrl from '../../vendor/chobitsu/dist/chobitsu.js?url'
-import runnerScriptUrl from '../components/runner/iframe.ts?url'
+import chobitsuUrl from '../../../../../vendor/chobitsu/dist/chobitsu.js?url'
+import runnerScriptUrl from './script.ts?url'
export async function generateImportMap(packageJsons: any[]) {
const importMap: Record = {}
diff --git a/src/components/runner/iframe.ts b/packages/worker/src/sw/iframe/script.ts
similarity index 91%
rename from src/components/runner/iframe.ts
rename to packages/worker/src/sw/iframe/script.ts
index 0ff45ed..0cf522c 100644
--- a/src/components/runner/iframe.ts
+++ b/packages/worker/src/sw/iframe/script.ts
@@ -3,6 +3,8 @@ import { TelegramClient } from '@mtcute/web?external'
type ConnectionState = import('@mtcute/web').ConnectionState
type TelegramClientOptions = import('@mtcute/web').TelegramClientOptions
+const HOST_ORIGIN = import.meta.env.VITE_HOST_ORIGIN
+
declare const chobitsu: any
declare const window: typeof globalThis & {
@@ -12,7 +14,7 @@ declare const window: typeof globalThis & {
}
function sendToDevtools(message: any) {
- window.parent.postMessage({ event: 'TO_DEVTOOLS', value: message }, '*')
+ window.parent.postMessage({ event: 'TO_DEVTOOLS', value: message }, HOST_ORIGIN)
}
function sendToChobitsu(message: any) {
@@ -37,6 +39,7 @@ function initClient(accountId: string) {
if (storedAccounts) {
const accounts = JSON.parse(storedAccounts)
const ourAccount = accounts.find((it: any) => it.id === accountId)
+ if (!ourAccount) return
if (ourAccount && ourAccount.testMode) {
extraConfig = {
@@ -53,7 +56,7 @@ function initClient(accountId: string) {
})
window.tg.onConnectionState.add((state) => {
lastConnectionState = state
- window.parent.postMessage({ event: 'CONNECTION_STATE', value: state }, '*')
+ window.parent.postMessage({ event: 'CONNECTION_STATE', value: state }, HOST_ORIGIN)
})
}
@@ -78,7 +81,11 @@ window.addEventListener('message', ({ data }) => {
sendToDevtools({ method: 'DOM.documentUpdated' })
initClient(data.accountId)
- window.tg.connect()
+ window.tg?.connect()
+
+ setInterval(() => {
+ window.parent.postMessage({ event: 'PING' }, HOST_ORIGIN)
+ }, 500)
} else if (data.event === 'RUN') {
const el = document.createElement('script')
el.type = 'module'
@@ -103,7 +110,7 @@ window.addEventListener('message', ({ data }) => {
initClient(data.accountId)
if (lastConnectionState !== 'offline') {
- window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, '*')
+ window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, HOST_ORIGIN)
window.tg.connect()
}
} else if (data.event === 'DISCONNECT') {
@@ -112,7 +119,7 @@ window.addEventListener('message', ({ data }) => {
if (lastAccountId) {
initClient(lastAccountId)
}
- window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, '*')
+ window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, HOST_ORIGIN)
} else if (data.event === 'RECONNECT') {
window.tg.connect()
}
@@ -120,7 +127,7 @@ window.addEventListener('message', ({ data }) => {
window.__handleScriptEnd = (error) => {
if (!window.__currentScript) return
- window.parent.postMessage({ event: 'SCRIPT_END', error }, '*')
+ window.parent.postMessage({ event: 'SCRIPT_END', error }, HOST_ORIGIN)
window.__currentScript.remove()
window.__currentScript = undefined
}
diff --git a/src/sw/main.ts b/packages/worker/src/sw/main.ts
similarity index 98%
rename from src/sw/main.ts
rename to packages/worker/src/sw/main.ts
index 061cbb4..3455639 100644
--- a/src/sw/main.ts
+++ b/packages/worker/src/sw/main.ts
@@ -1,5 +1,5 @@
import { unknownToError } from '@fuman/utils'
-import { IS_SAFARI } from '../lib/env.ts'
+import { IS_SAFARI } from '../utils/env.ts'
import { handleAvatarRequest } from './avatar.ts'
import { requestCache } from './cache.ts'
import { clearCache, forgetScript, handleRuntimeRequest, uploadScript } from './runtime.ts'
diff --git a/src/sw/register.ts b/packages/worker/src/sw/register.ts
similarity index 100%
rename from src/sw/register.ts
rename to packages/worker/src/sw/register.ts
diff --git a/src/sw/runtime.ts b/packages/worker/src/sw/runtime.ts
similarity index 91%
rename from src/sw/runtime.ts
rename to packages/worker/src/sw/runtime.ts
index 66a1efb..3782222 100644
--- a/src/sw/runtime.ts
+++ b/packages/worker/src/sw/runtime.ts
@@ -1,6 +1,6 @@
import { utf8 } from '@fuman/utils'
-import { generateImportMap, generateRunnerHtml } from '../lib/runtime.ts'
-import { VfsStorage } from '../lib/vfs/storage.ts'
+import { VfsStorage } from '../vfs/storage.ts'
+import { generateImportMap, generateRunnerHtml } from './iframe/html.ts'
const libraryCache = new Map>()
let importMapCache: Record | undefined
@@ -59,9 +59,6 @@ export async function handleRuntimeRequest(url: URL) {
const path = url.pathname.slice('/sw/runtime/'.length)
if (path === '_iframe.html') {
- // primarily a workaround for chrome bug: https://crbug.com/880768
- // (tldr: iframes created with blob: do not inherit service worker from the parent page)
- // but also a nice way to warm up the cache
if (!importMapCache) {
const vfs = await getVfs()
const libNames = (await vfs.getAvailableLibs()).filter(lib => !lib.startsWith('@types/'))
diff --git a/src/lib/env.ts b/packages/worker/src/utils/env.ts
similarity index 100%
rename from src/lib/env.ts
rename to packages/worker/src/utils/env.ts
diff --git a/src/lib/ffetch.ts b/packages/worker/src/utils/ffetch.ts
similarity index 100%
rename from src/lib/ffetch.ts
rename to packages/worker/src/utils/ffetch.ts
diff --git a/src/lib/telegram.ts b/packages/worker/src/utils/telegram.ts
similarity index 100%
rename from src/lib/telegram.ts
rename to packages/worker/src/utils/telegram.ts
diff --git a/src/lib/utils.ts b/packages/worker/src/utils/timeout.ts
similarity index 66%
rename from src/lib/utils.ts
rename to packages/worker/src/utils/timeout.ts
index 7259966..8d4a41a 100644
--- a/src/lib/utils.ts
+++ b/packages/worker/src/utils/timeout.ts
@@ -1,11 +1,3 @@
-import type { ClassValue } from 'clsx'
-import { clsx } from 'clsx'
-import { twMerge } from 'tailwind-merge'
-
-export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
-}
-
export function timeout(promise: Promise, timeout: number): Promise {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
diff --git a/src/lib/vfs/downloader.ts b/packages/worker/src/vfs/downloader.ts
similarity index 95%
rename from src/lib/vfs/downloader.ts
rename to packages/worker/src/vfs/downloader.ts
index 898f2cf..94b44b1 100644
--- a/src/lib/vfs/downloader.ts
+++ b/packages/worker/src/vfs/downloader.ts
@@ -1,7 +1,7 @@
import type { VfsFile, VfsStorage } from './storage'
import { read, webReadableToFuman } from '@fuman/io'
import { asyncPool, AsyncQueue, utf8 } from '@fuman/utils'
-import { ffetch } from '../ffetch.ts'
+import { ffetch } from '../utils/ffetch.ts'
import { GunzipStream } from './gzip.ts'
import { extractTar, type TarEntry } from './tar.ts'
@@ -15,7 +15,9 @@ const PACKAGES_TO_SKIP = new Set([
])
export async function getLatestVersions() {
- const versions = await fetch('https://raw.githubusercontent.com/mtcute/mtcute/refs/heads/master/scripts/latest-versions.json').then(res => res.json())
+ const versions = await ffetch(`https://raw.githubusercontent.com/mtcute/mtcute/refs/heads/master/scripts/latest-versions.json?v=${Date.now()}`)
+ .json>()
+
for (const pkg of Object.keys(versions)) {
if (PACKAGES_TO_SKIP.has(pkg)) {
delete versions[pkg]
@@ -148,7 +150,6 @@ export async function downloadNpmPackage(params: {
storage: VfsStorage
progress: (downloaded: number, total: number, file: string) => void
filterFiles?: (file: TarEntry) => boolean
- signal: AbortSignal
}) {
const {
packageName,
@@ -156,12 +157,11 @@ export async function downloadNpmPackage(params: {
storage,
progress,
filterFiles,
- signal,
} = params
const tgzUrl = `https://registry.npmjs.org/${packageName}/-/${packageName.replace(/^.*?\//, '')}-${version}.tgz`
- const response = await fetch(tgzUrl, { signal })
+ const response = await fetch(tgzUrl)
if (!response.ok || !response.body) {
throw new Error(`Failed to download: HTTP ${response.status}`)
}
diff --git a/src/lib/vfs/gzip.ts b/packages/worker/src/vfs/gzip.ts
similarity index 100%
rename from src/lib/vfs/gzip.ts
rename to packages/worker/src/vfs/gzip.ts
diff --git a/src/lib/vfs/storage.ts b/packages/worker/src/vfs/storage.ts
similarity index 100%
rename from src/lib/vfs/storage.ts
rename to packages/worker/src/vfs/storage.ts
diff --git a/src/lib/vfs/tar.ts b/packages/worker/src/vfs/tar.ts
similarity index 100%
rename from src/lib/vfs/tar.ts
rename to packages/worker/src/vfs/tar.ts
diff --git a/packages/worker/src/worker/main.ts b/packages/worker/src/worker/main.ts
new file mode 100644
index 0000000..90cd9f9
--- /dev/null
+++ b/packages/worker/src/worker/main.ts
@@ -0,0 +1,69 @@
+import { publishLinkedAtoms, writeLinkedAtom } from '../store/link.ts'
+import { ReplWorkerSw } from './sw.ts'
+import { ReplWorkerTelegram } from './telegram.ts'
+import { ReplWorkerVfs } from './vfs.ts'
+
+export class ReplWorker {
+ readonly telegram = new ReplWorkerTelegram()
+ readonly vfs = new ReplWorkerVfs()
+ readonly sw = new ReplWorkerSw()
+
+ readonly atom = {
+ write({ id, value }: { id: string, value: any }) {
+ writeLinkedAtom(id, value)
+ },
+ }
+}
+
+const pendingAborts = new Map()
+
+export function registerWorker(worker: ReplWorker) {
+ globalThis.onmessage = async (e) => {
+ if (e.source !== window.parent) return
+ if (e.origin !== import.meta.env.VITE_HOST_ORIGIN) {
+ console.error('Ignoring message from invalid origin', e.origin)
+ return
+ }
+
+ if (e.data.abort) {
+ const abortController = pendingAborts.get(e.data.id)
+ if (abortController) {
+ abortController.abort()
+ }
+ return
+ }
+
+ const {
+ id,
+ domain,
+ method,
+ params,
+ withAbort,
+ } = e.data
+
+ if (!(domain in worker) || !(method in (worker as any)[domain])) {
+ window.parent.postMessage({ id, error: `Method ${domain}.${method} not found` }, '*')
+ return
+ }
+
+ if (withAbort) {
+ const abortController = new AbortController()
+ pendingAborts.set(id, abortController)
+ params.abortSignal = abortController.signal
+ }
+
+ try {
+ const result = await (worker as any)[domain][method](params)
+ window.parent.postMessage({ id, result }, '*')
+ } catch (error) {
+ window.parent.postMessage({ id, error }, '*')
+ }
+
+ if (withAbort) {
+ pendingAborts.delete(id)
+ }
+ }
+
+ window.parent.postMessage({ event: 'LOADED' }, '*')
+ publishLinkedAtoms()
+}
diff --git a/packages/worker/src/worker/sw.ts b/packages/worker/src/worker/sw.ts
new file mode 100644
index 0000000..edb1571
--- /dev/null
+++ b/packages/worker/src/worker/sw.ts
@@ -0,0 +1,16 @@
+import { swInvokeMethod } from '../sw/client.ts'
+
+export class ReplWorkerSw {
+ async uploadScript(params: {
+ name: string
+ files: Record
+ }) {
+ return swInvokeMethod({ event: 'UPLOAD_SCRIPT', name: params.name, files: params.files })
+ }
+
+ async forgetScript(params: {
+ name: string
+ }) {
+ return swInvokeMethod({ event: 'FORGET_SCRIPT', name: params.name })
+ }
+}
diff --git a/packages/worker/src/worker/telegram.ts b/packages/worker/src/worker/telegram.ts
new file mode 100644
index 0000000..fe0544b
--- /dev/null
+++ b/packages/worker/src/worker/telegram.ts
@@ -0,0 +1,197 @@
+import type { BaseTelegramClient, SentCode, User } from '@mtcute/web'
+import type { TelegramAccount } from '../store/accounts.ts'
+import { assert } from '@fuman/utils'
+import { tl } from '@mtcute/web'
+import { checkPassword, resendCode, sendCode, signIn, signInQr } from '@mtcute/web/methods.js'
+import { renderSVG } from 'uqr'
+import { $accounts, $activeAccountId } from '../store/accounts.ts'
+import { createInternalClient, deleteAccount } from '../utils/telegram.ts'
+import { emitEvent } from './utils.ts'
+
+const clients = new Map()
+function getClient(accountId: string) {
+ const client = clients.get(accountId)
+ if (!client) throw new Error('Client not found')
+ return client
+}
+
+async function handleAuthSuccess(accountId: string, user: User) {
+ const client = getClient(accountId)
+ const dcs = await client.mt.storage.dcs.fetch()
+ const dcId = dcs?.main.id ?? 2
+ const testMode = client.params.testMode ?? false
+
+ const account: TelegramAccount = {
+ id: accountId,
+ name: user.displayName,
+ telegramId: user.id,
+ bot: user.isBot,
+ testMode,
+ dcId,
+ }
+ $accounts.set([
+ ...$accounts.get(),
+ account,
+ ])
+ $activeAccountId.set(accountId)
+
+ return account
+}
+
+export class ReplWorkerTelegram {
+ async createClient(params: {
+ accountId: string
+ testMode?: boolean
+ }) {
+ const client = createInternalClient(params.accountId, params.testMode)
+ clients.set(params.accountId, client)
+ }
+
+ async disposeClient(params: {
+ accountId: string
+ forget?: boolean
+ }) {
+ const client = clients.get(params.accountId)
+ if (!client) return
+ await client.close()
+ clients.delete(params.accountId)
+
+ if (params.forget) {
+ await deleteAccount(params.accountId)
+ }
+ }
+
+ async loadCountries(params: {
+ accountId: string
+ }): Promise<{ countries: tl.help.RawCountry[], countryByIp: string }> {
+ const client = getClient(params.accountId)
+
+ const [
+ countries,
+ nearestDc,
+ ] = await Promise.all([
+ client.call({ _: 'help.getCountriesList', langCode: 'en', hash: 0 }),
+ client.call({ _: 'help.getNearestDc' }),
+ ])
+
+ assert(countries._ === 'help.countriesList') // todo caching
+
+ return {
+ countries: countries.countries,
+ countryByIp: nearestDc.country.toUpperCase(),
+ }
+ }
+
+ async signInQr(params: {
+ accountId: string
+ abortSignal: AbortSignal
+ }): Promise {
+ const { accountId, abortSignal } = params
+ const client = getClient(accountId)
+
+ try {
+ const user = await signInQr(client, {
+ abortSignal,
+ onUrlUpdated: qr => emitEvent('QrCodeUpdate', { accountId, qrCode: renderSVG(qr) }),
+ onQrScanned: () => emitEvent('QrCodeScanned', { accountId }),
+ })
+
+ return await handleAuthSuccess(accountId, user)
+ } catch (e) {
+ if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
+ return 'need_password'
+ } else {
+ throw e
+ }
+ }
+ }
+
+ async sendCode(params: {
+ accountId: string
+ phone: string
+ abortSignal: AbortSignal
+ }): Promise {
+ const { accountId, phone, abortSignal } = params
+
+ const code = await sendCode(getClient(accountId), {
+ phone,
+ abortSignal,
+ })
+
+ return (code as any).toJSON()
+ }
+
+ async resendCode(params: {
+ accountId: string
+ phone: string
+ phoneCodeHash: string
+ abortSignal: AbortSignal
+ }): Promise {
+ const { accountId, phone, phoneCodeHash, abortSignal } = params
+
+ const code = await resendCode(getClient(accountId), {
+ phone,
+ phoneCodeHash,
+ abortSignal,
+ })
+
+ return (code as any).toJSON()
+ }
+
+ async signIn(params: {
+ accountId: string
+ phone: string
+ phoneCodeHash: string
+ phoneCode: string
+ abortSignal?: AbortSignal
+ }): Promise {
+ const { accountId, phone, phoneCodeHash, phoneCode, abortSignal } = params
+ try {
+ const user = await signIn(getClient(accountId), {
+ phone,
+ phoneCodeHash,
+ phoneCode,
+ abortSignal,
+ })
+
+ return await handleAuthSuccess(accountId, user)
+ } catch (e) {
+ if (tl.RpcError.is(e, 'SESSION_PASSWORD_NEEDED')) {
+ return 'need_password'
+ } else {
+ throw e
+ }
+ }
+ }
+
+ async checkPassword(params: {
+ accountId: string
+ password: string
+ abortSignal: AbortSignal
+ }): Promise {
+ const { accountId, password, abortSignal } = params
+
+ try {
+ const user = await checkPassword(getClient(accountId), {
+ password,
+ abortSignal,
+ })
+ return await handleAuthSuccess(accountId, user)
+ } catch (e) {
+ if (tl.RpcError.is(e, 'PASSWORD_HASH_INVALID')) {
+ throw new Error('Incorrect password')
+ } else {
+ throw e
+ }
+ }
+ }
+
+ async fetchAvatar(accountId: string) {
+ const res = await fetch(`/sw/avatar/${accountId}/avatar.jpg`)
+ if (!res.ok) {
+ return null
+ } else {
+ return new Uint8Array(await res.arrayBuffer())
+ }
+ }
+}
diff --git a/packages/worker/src/worker/utils.ts b/packages/worker/src/worker/utils.ts
new file mode 100644
index 0000000..d7487f4
--- /dev/null
+++ b/packages/worker/src/worker/utils.ts
@@ -0,0 +1,11 @@
+export interface ReplWorkerEvents {
+ UpdateProgress: { progress: number, total: number }
+ AtomUpdate: { id: string, value: any }
+
+ QrCodeUpdate: { accountId: string, qrCode: string }
+ QrCodeScanned: { accountId: string }
+}
+
+export function emitEvent(event: Event, data: ReplWorkerEvents[Event]) {
+ window.parent!.postMessage({ event, data }, '*')
+}
diff --git a/packages/worker/src/worker/vfs.ts b/packages/worker/src/worker/vfs.ts
new file mode 100644
index 0000000..c50a8a4
--- /dev/null
+++ b/packages/worker/src/worker/vfs.ts
@@ -0,0 +1,67 @@
+import { asyncPool } from '@fuman/utils'
+import { swInvokeMethod } from '../sw/client.ts'
+import { downloadNpmPackage, getLatestVersions, getPackagesToDownload } from '../vfs/downloader.ts'
+import { VfsStorage } from '../vfs/storage.ts'
+import { emitEvent } from './utils.ts'
+
+let _vfs: VfsStorage | undefined
+async function getVfs() {
+ if (!_vfs) {
+ _vfs = await VfsStorage.create()
+ }
+ return _vfs
+}
+
+export class ReplWorkerVfs {
+ async checkForUpdates(): Promise> {
+ const latestVersions = await getLatestVersions()
+ const versions = await getPackagesToDownload(latestVersions, await getVfs())
+
+ return versions
+ }
+
+ async downloadPackages(packages: Record) {
+ await swInvokeMethod({ event: 'CLEAR_CACHE' })
+ const vfs = await getVfs()
+
+ let downloadedBytes = 0
+ let totalBytes = 0
+ await asyncPool(Object.entries(packages), async ([lib, version]) => {
+ let isFirst = true
+ let prevDownloaded = 0
+
+ function onProgress(downloaded: number, total: number) {
+ if (isFirst) {
+ totalBytes += total
+ isFirst = false
+ }
+
+ const diff = downloaded - prevDownloaded
+ downloadedBytes += diff
+ prevDownloaded = downloaded
+
+ emitEvent('UpdateProgress', { progress: downloadedBytes, total: totalBytes })
+ }
+
+ await downloadNpmPackage({
+ packageName: lib,
+ version,
+ storage: vfs,
+ progress: onProgress,
+ filterFiles: (file) => {
+ if (!file.header) return true
+ const name = file.header.name
+ return !name.endsWith('.cjs') && !name.endsWith('.d.cts') && name !== 'LICENSE' && name !== 'README.md'
+ },
+ })
+ })
+ }
+
+ async getLibraryNames() {
+ return (await getVfs()).getAvailableLibs()
+ }
+
+ async getLibrary(name: string) {
+ return (await getVfs()).readLibrary(name)
+ }
+}
diff --git a/sw.ts b/packages/worker/sw.ts
similarity index 100%
rename from sw.ts
rename to packages/worker/sw.ts
diff --git a/packages/worker/vite.config.ts b/packages/worker/vite.config.ts
new file mode 100644
index 0000000..384a0fb
--- /dev/null
+++ b/packages/worker/vite.config.ts
@@ -0,0 +1,26 @@
+import type { UserConfig } from 'vite'
+import { join } from 'node:path'
+import { defineConfig, loadEnv } from 'vite'
+// eslint-disable-next-line import/no-relative-packages
+import externalizeDeps from '../../scripts/vite-plugin-externalize-dependencies.ts'
+
+export default defineConfig((env): UserConfig => {
+ process.env = {
+ ...process.env,
+ ...loadEnv(env.mode, join(__dirname, '../..')),
+ }
+
+ return {
+ server: {
+ port: 3001,
+ },
+ optimizeDeps: {
+ exclude: ['@mtcute/wasm'],
+ },
+ plugins: [
+ externalizeDeps({
+ externals: [],
+ }),
+ ],
+ }
+})
diff --git a/patches/vite-plugin-externalize-dependencies@1.0.1.patch b/patches/vite-plugin-externalize-dependencies@1.0.1.patch
deleted file mode 100644
index 43c4559..0000000
--- a/patches/vite-plugin-externalize-dependencies@1.0.1.patch
+++ /dev/null
@@ -1,16 +0,0 @@
-diff --git a/dist/index.js b/dist/index.js
-index 4b5eff9ae46b5fa0af75b16ef21f5b93142a7251..bc1651dfd82210b69ec9b5709c5f6a6320ca31b0 100644
---- a/dist/index.js
-+++ b/dist/index.js
-@@ -14,9 +14,9 @@ const r = /* @__PURE__ */ new Set(), s = (n, e) => e.some((t) => typeof t == "st
- if (r.size === 0)
- return e;
- const t = "@id/", u = new RegExp(
-- `${n}${t}(${[...r].join(
-+ `${n}${t}(${[...r].map(it => it.replace(/\?/g, "\\?")).join(
- "|"
-- )})`,
-+ )})(?:\\?external)?`,
- "g"
- );
- return u.test(e) ? e.replace(
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d198f0d..6ad0230 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,114 +4,9 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
-patchedDependencies:
- vite-plugin-externalize-dependencies@1.0.1:
- hash: 5hvukli72fmd6w753a6bzflwga
- path: patches/vite-plugin-externalize-dependencies@1.0.1.patch
-
importers:
.:
- dependencies:
- '@badrap/valita':
- specifier: ^0.4.2
- version: 0.4.2
- '@corvu/otp-field':
- specifier: ^0.1.4
- version: 0.1.4(solid-js@1.9.4)
- '@corvu/resizable':
- specifier: ^0.2.3
- version: 0.2.3(solid-js@1.9.4)
- '@fuman/fetch':
- specifier: ^0.0.8
- version: 0.0.8(@badrap/valita@0.4.2)(zod@3.24.1)
- '@fuman/io':
- specifier: 0.0.8
- version: 0.0.8
- '@fuman/utils':
- specifier: 0.0.4
- version: 0.0.4
- '@kobalte/core':
- specifier: ^0.13.7
- version: 0.13.7(solid-js@1.9.4)
- '@mtcute/convert':
- specifier: ^0.19.4
- version: 0.19.4
- '@mtcute/web':
- specifier: ^0.19.5
- version: 0.19.5
- '@nanostores/persistent':
- specifier: ^0.10.2
- version: 0.10.2(nanostores@0.11.3)
- class-variance-authority:
- specifier: ^0.7.1
- version: 0.7.1
- clsx:
- specifier: ^2.1.1
- version: 2.1.1
- esbuild:
- specifier: ^0.24.2
- version: 0.24.2
- fflate:
- specifier: ^0.8.2
- version: 0.8.2
- filesize:
- specifier: ^10.1.6
- version: 10.1.6
- idb:
- specifier: ^8.0.1
- version: 8.0.1
- lucide-solid:
- specifier: ^0.445.0
- version: 0.445.0(solid-js@1.9.4)
- memfs:
- specifier: ^4.17.0
- version: 4.17.0
- monaco-editor:
- specifier: 0.52.0
- version: 0.52.0
- monaco-editor-core:
- specifier: 0.52.0
- version: 0.52.0
- monaco-editor-textmate:
- specifier: ^4.0.0
- version: 4.0.0(monaco-editor@0.52.0)(monaco-textmate@3.0.1(onigasm@2.2.5))
- monaco-textmate:
- specifier: ^3.0.1
- version: 3.0.1(onigasm@2.2.5)
- nanoid:
- specifier: ^5.0.9
- version: 5.0.9
- nanostores:
- specifier: ^0.11.3
- version: 0.11.3
- onigasm:
- specifier: ^2.2.5
- version: 2.2.5
- semver:
- specifier: ^7.6.3
- version: 7.6.3
- solid-icons:
- specifier: ^1.1.0
- version: 1.1.0(solid-js@1.9.4)
- solid-js:
- specifier: ^1.9.4
- version: 1.9.4
- solid-transition-group:
- specifier: ^0.2.3
- version: 0.2.3(solid-js@1.9.4)
- tailwind-merge:
- specifier: ^2.6.0
- version: 2.6.0
- tailwindcss-animate:
- specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.4.17)
- ts-blank-space:
- specifier: ^0.4.4
- version: 0.4.4
- uqr:
- specifier: ^0.1.2
- version: 0.1.2
devDependencies:
'@antfu/eslint-config':
specifier: ^3.13.0
@@ -119,6 +14,9 @@ importers:
'@catppuccin/vscode':
specifier: ^3.16.0
version: 3.16.0
+ '@fuman/fetch':
+ specifier: ^0.0.8
+ version: 0.0.8(@badrap/valita@0.4.2)(zod@3.24.1)
'@types/node':
specifier: ^22.10.5
version: 22.10.5
@@ -128,6 +26,9 @@ importers:
autoprefixer:
specifier: ^10.4.20
version: 10.4.20(postcss@8.4.49)
+ esbuild:
+ specifier: ^0.24.2
+ version: 0.24.2
eslint-plugin-solid:
specifier: ^0.14.5
version: 0.14.5(eslint@9.11.0(jiti@1.21.7))(typescript@5.7.3)
@@ -140,9 +41,6 @@ importers:
plist2:
specifier: ^1.1.4
version: 1.1.4
- postcss:
- specifier: ^8.4.49
- version: 8.4.49
tailwindcss:
specifier: ^3.4.17
version: 3.4.17
@@ -156,114 +54,120 @@ importers:
specifier: ^2.11.0
version: 2.11.0(solid-js@1.9.4)(vite@5.4.11(@types/node@22.10.5))
- ../fuman/packages/bun:
+ packages/repl:
dependencies:
- '@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/net':
- specifier: workspace:^
- version: link:../net
+ '@corvu/otp-field':
+ specifier: ^0.1.4
+ version: 0.1.4(solid-js@1.9.4)
+ '@corvu/resizable':
+ specifier: ^0.2.3
+ version: 0.2.3(solid-js@1.9.4)
'@fuman/utils':
- specifier: workspace:^
- version: link:../utils
+ specifier: 0.0.4
+ version: 0.0.4
+ '@kobalte/core':
+ specifier: ^0.13.7
+ version: 0.13.7(solid-js@1.9.4)
+ '@nanostores/persistent':
+ specifier: ^0.10.2
+ version: 0.10.2(nanostores@0.11.3)
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ filesize:
+ specifier: ^10.1.6
+ version: 10.1.6
+ lucide-solid:
+ specifier: ^0.445.0
+ version: 0.445.0(solid-js@1.9.4)
+ monaco-editor:
+ specifier: 0.52.0
+ version: 0.52.0
+ monaco-editor-core:
+ specifier: 0.52.0
+ version: 0.52.0
+ monaco-editor-textmate:
+ specifier: ^4.0.0
+ version: 4.0.0(monaco-editor@0.52.0)(monaco-textmate@3.0.1(onigasm@2.2.5))
+ monaco-textmate:
+ specifier: ^3.0.1
+ version: 3.0.1(onigasm@2.2.5)
+ mtcute-repl-worker:
+ specifier: workspace:*
+ version: link:../worker
+ nanoid:
+ specifier: ^5.0.9
+ version: 5.0.9
+ nanostores:
+ specifier: ^0.11.3
+ version: 0.11.3
+ onigasm:
+ specifier: ^2.2.5
+ version: 2.2.5
+ solid-icons:
+ specifier: ^1.1.0
+ version: 1.1.0(solid-js@1.9.4)
+ solid-js:
+ specifier: ^1.9.4
+ version: 1.9.4
+ solid-transition-group:
+ specifier: ^0.2.3
+ version: 0.2.3(solid-js@1.9.4)
+ ts-blank-space:
+ specifier: ^0.4.4
+ version: 0.4.4
+ devDependencies:
+ postcss:
+ specifier: ^8.4.49
+ version: 8.4.49
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.17)
- ../fuman/packages/deno:
+ packages/worker:
dependencies:
+ '@badrap/valita':
+ specifier: ^0.4.2
+ version: 0.4.2
+ '@fuman/fetch':
+ specifier: ^0.0.8
+ version: 0.0.8(@badrap/valita@0.4.2)(zod@3.24.1)
'@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/net':
- specifier: workspace:^
- version: link:../net
+ specifier: 0.0.8
+ version: 0.0.8
'@fuman/utils':
- specifier: workspace:^
- version: link:../utils
-
- ../fuman/packages/gzip-web:
- dependencies:
- '@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
+ specifier: 0.0.4
+ version: 0.0.4
+ '@mtcute/convert':
+ specifier: ^0.19.4
+ version: 0.19.4
+ '@mtcute/web':
+ specifier: ^0.19.5
+ version: 0.19.5
+ '@nanostores/persistent':
+ specifier: ^0.10.2
+ version: 0.10.2(nanostores@0.11.3)
fflate:
specifier: ^0.8.2
version: 0.8.2
-
- ../fuman/packages/io:
- dependencies:
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
-
- ../fuman/packages/jsr:
- dependencies:
- '@fuman/node':
- specifier: workspace:^
- version: link:../node
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
- gunzip-maybe:
- specifier: 1.4.2
- version: 1.4.2
- semver:
- specifier: 7.6.3
- version: 7.6.3
- tar-stream:
- specifier: 3.1.7
- version: 3.1.7
- typescript:
- specifier: 5.2.2
- version: 5.2.2
- devDependencies:
- '@types/gunzip-maybe':
- specifier: 1.4.0
- version: 1.4.0
- '@types/semver':
- specifier: 7.5.4
- version: 7.5.4
- '@types/tar-stream':
- specifier: 3.1.2
- version: 3.1.2
-
- ../fuman/packages/net:
- dependencies:
- '@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
- devDependencies:
- ipaddr.js:
- specifier: 2.2.0
- version: 2.2.0
-
- ../fuman/packages/node:
- dependencies:
- '@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/net':
- specifier: workspace:^
- version: link:../net
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
-
- ../fuman/packages/tar:
- dependencies:
- '@fuman/io':
- specifier: workspace:^
- version: link:../io
- '@fuman/utils':
- specifier: workspace:^
- version: link:../utils
-
- ../fuman/packages/utils: {}
+ idb:
+ specifier: ^8.0.1
+ version: 8.0.1
+ nanoid:
+ specifier: ^5.0.9
+ version: 5.0.9
+ nanostores:
+ specifier: ^0.11.3
+ version: 0.11.3
+ uqr:
+ specifier: ^0.1.2
+ version: 0.1.2
packages:
@@ -858,24 +762,6 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
- '@jsonjoy.com/base64@1.1.2':
- resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==}
- engines: {node: '>=10.0'}
- peerDependencies:
- tslib: '2'
-
- '@jsonjoy.com/json-pack@1.1.1':
- resolution: {integrity: sha512-osjeBqMJ2lb/j/M8NCPjs1ylqWIcTRTycIhVB5pt6LgzgeRSb0YRZ7j9RfA8wIUrsr/medIuhVyonXRZWLyfdw==}
- engines: {node: '>=10.0'}
- peerDependencies:
- tslib: '2'
-
- '@jsonjoy.com/util@1.5.0':
- resolution: {integrity: sha512-ojoNsrIuPI9g6o8UxhraZQSyF2ByJanAY4cTFbc8Mf2AXEF4aQRGY1dJxyJpuyav8r9FGflEt/Ff3u5Nt6YMPA==}
- engines: {node: '>=10.0'}
- peerDependencies:
- tslib: '2'
-
'@kobalte/core@0.13.7':
resolution: {integrity: sha512-COhjWk1KnCkl3qMJDvdrOsvpTlJ9gMLdemkAn5SWfbPn/lxJYabejnNOk+b/ILGg7apzQycgbuo48qb8ppqsAg==}
peerDependencies:
@@ -1124,9 +1010,6 @@ packages:
'@types/events@3.0.0':
resolution: {integrity: sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==}
- '@types/gunzip-maybe@1.4.0':
- resolution: {integrity: sha512-dFP9GrYAR9KhsjTkWJ8q8Gsfql75YIKcg9DuQOj/IrlPzR7W+1zX+cclw1McV82UXAQ+Lpufvgk3e9bC8+HzgA==}
-
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
@@ -1139,21 +1022,12 @@ packages:
'@types/node@22.10.5':
resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==}
- '@types/node@22.5.5':
- resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==}
-
'@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
- '@types/semver@7.5.4':
- resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==}
-
'@types/semver@7.5.8':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
- '@types/tar-stream@3.1.2':
- resolution: {integrity: sha512-qnIpUItVb5u8jl3kbrHofkM40ggO3YKSzc7TWqLYjDdwlrL7CiEAkDySaGfeUBLtC50RTfh2acdz51ItUbV7pQ==}
-
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -1285,9 +1159,6 @@ packages:
peerDependencies:
postcss: ^8.1.0
- b4a@1.6.6:
- resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==}
-
babel-plugin-jsx-dom-expressions@0.39.5:
resolution: {integrity: sha512-dwyVkszHRsZCXfFusu3xq1DJS7twhgLrjEpMC1gtTfJG1xSrMMKWWhdl1SFFFNXrvYDsoHiRxSbku/TzLxHNxg==}
peerDependencies:
@@ -1301,9 +1172,6 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
- bare-events@2.5.0:
- resolution: {integrity: sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==}
-
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
@@ -1321,17 +1189,11 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- browserify-zlib@0.1.4:
- resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==}
-
browserslist@4.24.4:
resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
- buffer-from@1.1.2:
- resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
-
builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
@@ -1411,9 +1273,6 @@ packages:
core-js-compat@3.40.0:
resolution: {integrity: sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==}
- core-util-is@1.0.3:
- resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
-
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -1466,9 +1325,6 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
- duplexify@3.7.1:
- resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==}
-
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@@ -1481,9 +1337,6 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
- end-of-stream@1.4.4:
- resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
-
enhanced-resolve@5.18.0:
resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==}
engines: {node: '>=10.13.0'}
@@ -1727,9 +1580,6 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
- fast-fifo@1.3.2:
- resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
-
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
@@ -1844,10 +1694,6 @@ packages:
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
- gunzip-maybe@1.4.2:
- resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
- hasBin: true
-
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -1866,10 +1712,6 @@ packages:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
- hyperdyperid@1.2.0:
- resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==}
- engines: {node: '>=10.18'}
-
idb@8.0.1:
resolution: {integrity: sha512-EkBCzUZSdhJV8PxMSbeEV//xguVKZu9hZZulM+2gHXI0t2hGVU3eYE6/XnH77DS6FM2FY8wl17aDcu9vXpvLWQ==}
@@ -1889,16 +1731,9 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
- inherits@2.0.4:
- resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
- ipaddr.js@2.2.0:
- resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
- engines: {node: '>= 10'}
-
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@@ -1914,9 +1749,6 @@ packages:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
- is-deflate@1.0.0:
- resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==}
-
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -1929,10 +1761,6 @@ packages:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
- is-gzip@1.0.0:
- resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==}
- engines: {node: '>=0.10.0'}
-
is-html@2.0.0:
resolution: {integrity: sha512-S+OpgB5i7wzIue/YSE5hg0e5ZYfG3hhpNh9KGl6ayJ38p7ED6wxQLd1TV91xHpcTvw90KMJ9EwN3F/iNflHBVg==}
engines: {node: '>=8'}
@@ -1949,9 +1777,6 @@ packages:
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
engines: {node: '>=12.13'}
- isarray@1.0.0:
- resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
-
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -2100,10 +1925,6 @@ packages:
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
- memfs@4.17.0:
- resolution: {integrity: sha512-4eirfZ7thblFmqFjywlTmuWVSvccHAJbn1r8qQLzmTO11qcqpohOjmY2mFce6x7x7WtskzRqApPD0hv+Oa74jg==}
- engines: {node: '>= 4.0.0'}
-
merge-anything@5.1.7:
resolution: {integrity: sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==}
engines: {node: '>=12.13'}
@@ -2293,9 +2114,6 @@ packages:
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
engines: {node: '>= 6'}
- once@1.4.0:
- resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
-
onigasm@2.2.5:
resolution: {integrity: sha512-F+th54mPc0l1lp1ZcFMyL/jTs2Tlq4SqIHKIXGZOR/VkHkF9A7Fr5rRr5+ZG/lWeRsyrClLYRq7s/yFQ/XhWCA==}
@@ -2329,9 +2147,6 @@ packages:
package-manager-detector@0.2.8:
resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==}
- pako@0.2.9:
- resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
-
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -2372,9 +2187,6 @@ packages:
pathe@2.0.1:
resolution: {integrity: sha512-6jpjMpOth5S9ITVu5clZ7NOgHNsv5vRQdheL9ztp2vZmM6fRbLvyua1tiBIL4lk8SAe3ARzeXEly6siXCjDHDw==}
- peek-stream@1.1.3:
- resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2450,15 +2262,6 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
- process-nextick-args@2.0.1:
- resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
-
- pump@2.0.1:
- resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==}
-
- pumpify@1.5.1:
- resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -2466,9 +2269,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- queue-tick@1.0.1:
- resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
-
read-cache@1.0.0:
resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
@@ -2480,9 +2280,6 @@ packages:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'}
- readable-stream@2.3.8:
- resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
-
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -2531,9 +2328,6 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- safe-buffer@5.1.2:
- resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
-
scslre@0.3.0:
resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
engines: {node: ^14.0.0 || >=16.0.0}
@@ -2630,12 +2424,6 @@ packages:
stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
- stream-shift@1.0.3:
- resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
-
- streamx@2.20.1:
- resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==}
-
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -2644,9 +2432,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
- string_decoder@1.1.1:
- resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
-
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -2704,12 +2489,6 @@ packages:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}
- tar-stream@3.1.7:
- resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
-
- text-decoder@1.2.0:
- resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==}
-
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@@ -2720,15 +2499,6 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
- thingies@1.21.0:
- resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==}
- engines: {node: '>=10.18'}
- peerDependencies:
- tslib: ^2
-
- through2@2.0.5:
- resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==}
-
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -2740,12 +2510,6 @@ packages:
resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- tree-dump@1.0.2:
- resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==}
- engines: {node: '>=10.0'}
- peerDependencies:
- tslib: '2'
-
ts-api-utils@2.0.0:
resolution: {integrity: sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==}
engines: {node: '>=18.12'}
@@ -2778,11 +2542,6 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
- typescript@5.2.2:
- resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==}
- engines: {node: '>=14.17'}
- hasBin: true
-
typescript@5.7.3:
resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==}
engines: {node: '>=14.17'}
@@ -2791,9 +2550,6 @@ packages:
ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
- undici-types@6.19.8:
- resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
-
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
@@ -2906,17 +2662,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
- wrappy@1.0.2:
- resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-
xml-name-validator@4.0.0:
resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
engines: {node: '>=12'}
- xtend@4.0.2:
- resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
- engines: {node: '>=0.4'}
-
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -3449,22 +3198,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
- '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)':
- dependencies:
- tslib: 2.8.1
-
- '@jsonjoy.com/json-pack@1.1.1(tslib@2.8.1)':
- dependencies:
- '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1)
- '@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
- hyperdyperid: 1.2.0
- thingies: 1.21.0(tslib@2.8.1)
- tslib: 2.8.1
-
- '@jsonjoy.com/util@1.5.0(tslib@2.8.1)':
- dependencies:
- tslib: 2.8.1
-
'@kobalte/core@0.13.7(solid-js@1.9.4)':
dependencies:
'@floating-ui/dom': 1.6.13
@@ -3722,10 +3455,6 @@ snapshots:
'@types/events@3.0.0': {}
- '@types/gunzip-maybe@1.4.0':
- dependencies:
- '@types/node': 22.5.5
-
'@types/json-schema@7.0.15': {}
'@types/mdast@4.0.4':
@@ -3738,20 +3467,10 @@ snapshots:
dependencies:
undici-types: 6.20.0
- '@types/node@22.5.5':
- dependencies:
- undici-types: 6.19.8
-
'@types/normalize-package-data@2.4.4': {}
- '@types/semver@7.5.4': {}
-
'@types/semver@7.5.8': {}
- '@types/tar-stream@3.1.2':
- dependencies:
- '@types/node': 22.5.5
-
'@types/unist@3.0.3': {}
'@typescript-eslint/eslint-plugin@8.19.1(@typescript-eslint/parser@8.19.1(eslint@9.11.0(jiti@1.21.7))(typescript@5.7.3))(eslint@9.11.0(jiti@1.21.7))(typescript@5.7.3)':
@@ -3916,8 +3635,6 @@ snapshots:
postcss: 8.4.49
postcss-value-parser: 4.2.0
- b4a@1.6.6: {}
-
babel-plugin-jsx-dom-expressions@0.39.5(@babel/core@7.26.0):
dependencies:
'@babel/core': 7.26.0
@@ -3935,9 +3652,6 @@ snapshots:
balanced-match@1.0.2: {}
- bare-events@2.5.0:
- optional: true
-
binary-extensions@2.3.0: {}
boolbase@1.0.0: {}
@@ -3955,10 +3669,6 @@ snapshots:
dependencies:
fill-range: 7.1.1
- browserify-zlib@0.1.4:
- dependencies:
- pako: 0.2.9
-
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001692
@@ -3966,8 +3676,6 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.2(browserslist@4.24.4)
- buffer-from@1.1.2: {}
-
builtin-modules@3.3.0: {}
callsites@3.1.0: {}
@@ -4037,8 +3745,6 @@ snapshots:
dependencies:
browserslist: 4.24.4
- core-util-is@1.0.3: {}
-
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -4077,13 +3783,6 @@ snapshots:
dependencies:
esutils: 2.0.3
- duplexify@3.7.1:
- dependencies:
- end-of-stream: 1.4.4
- inherits: 2.0.4
- readable-stream: 2.3.8
- stream-shift: 1.0.3
-
eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.80: {}
@@ -4092,10 +3791,6 @@ snapshots:
emoji-regex@9.2.2: {}
- end-of-stream@1.4.4:
- dependencies:
- once: 1.4.0
-
enhanced-resolve@5.18.0:
dependencies:
graceful-fs: 4.2.11
@@ -4480,8 +4175,6 @@ snapshots:
fast-deep-equal@3.1.3: {}
- fast-fifo@1.3.2: {}
-
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4588,15 +4281,6 @@ snapshots:
graphemer@1.4.0: {}
- gunzip-maybe@1.4.2:
- dependencies:
- browserify-zlib: 0.1.4
- is-deflate: 1.0.0
- is-gzip: 1.0.0
- peek-stream: 1.1.3
- pumpify: 1.5.1
- through2: 2.0.5
-
has-flag@4.0.0: {}
hasown@2.0.2:
@@ -4609,8 +4293,6 @@ snapshots:
html-tags@3.3.1: {}
- hyperdyperid@1.2.0: {}
-
idb@8.0.1: {}
ignore@5.3.2: {}
@@ -4624,12 +4306,8 @@ snapshots:
indent-string@4.0.0: {}
- inherits@2.0.4: {}
-
inline-style-parser@0.2.4: {}
- ipaddr.js@2.2.0: {}
-
is-arrayish@0.2.1: {}
is-binary-path@2.1.0:
@@ -4644,8 +4322,6 @@ snapshots:
dependencies:
hasown: 2.0.2
- is-deflate@1.0.0: {}
-
is-extglob@2.1.1: {}
is-fullwidth-code-point@3.0.0: {}
@@ -4654,8 +4330,6 @@ snapshots:
dependencies:
is-extglob: 2.1.1
- is-gzip@1.0.0: {}
-
is-html@2.0.0:
dependencies:
html-tags: 3.3.1
@@ -4666,8 +4340,6 @@ snapshots:
is-what@4.1.16: {}
- isarray@1.0.0: {}
-
isexe@2.0.0: {}
jackspeak@3.4.3:
@@ -4867,13 +4539,6 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
- memfs@4.17.0:
- dependencies:
- '@jsonjoy.com/json-pack': 1.1.1(tslib@2.8.1)
- '@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
- tree-dump: 1.0.2(tslib@2.8.1)
- tslib: 2.8.1
-
merge-anything@5.1.7:
dependencies:
is-what: 4.1.16
@@ -5154,10 +4819,6 @@ snapshots:
object-hash@3.0.0: {}
- once@1.4.0:
- dependencies:
- wrappy: 1.0.2
-
onigasm@2.2.5:
dependencies:
lru-cache: 5.1.1
@@ -5193,8 +4854,6 @@ snapshots:
package-manager-detector@0.2.8: {}
- pako@0.2.9: {}
-
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -5232,12 +4891,6 @@ snapshots:
pathe@2.0.1: {}
- peek-stream@1.1.3:
- dependencies:
- buffer-from: 1.1.2
- duplexify: 3.7.1
- through2: 2.0.5
-
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -5299,25 +4952,10 @@ snapshots:
prelude-ls@1.2.1: {}
- process-nextick-args@2.0.1: {}
-
- pump@2.0.1:
- dependencies:
- end-of-stream: 1.4.4
- once: 1.4.0
-
- pumpify@1.5.1:
- dependencies:
- duplexify: 3.7.1
- inherits: 2.0.4
- pump: 2.0.1
-
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
- queue-tick@1.0.1: {}
-
read-cache@1.0.0:
dependencies:
pify: 2.3.0
@@ -5335,16 +4973,6 @@ snapshots:
parse-json: 5.2.0
type-fest: 0.6.0
- readable-stream@2.3.8:
- dependencies:
- core-util-is: 1.0.3
- inherits: 2.0.4
- isarray: 1.0.0
- process-nextick-args: 2.0.1
- safe-buffer: 5.1.2
- string_decoder: 1.1.1
- util-deprecate: 1.0.2
-
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
@@ -5407,8 +5035,6 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
- safe-buffer@5.1.2: {}
-
scslre@0.3.0:
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -5497,16 +5123,6 @@ snapshots:
stable-hash@0.0.4: {}
- stream-shift@1.0.3: {}
-
- streamx@2.20.1:
- dependencies:
- fast-fifo: 1.3.2
- queue-tick: 1.0.1
- text-decoder: 1.2.0
- optionalDependencies:
- bare-events: 2.5.0
-
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -5519,10 +5135,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
- string_decoder@1.1.1:
- dependencies:
- safe-buffer: 5.1.2
-
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -5601,16 +5213,6 @@ snapshots:
tapable@2.2.1: {}
- tar-stream@3.1.7:
- dependencies:
- b4a: 1.6.6
- fast-fifo: 1.3.2
- streamx: 2.20.1
-
- text-decoder@1.2.0:
- dependencies:
- b4a: 1.6.6
-
text-table@0.2.0: {}
thenify-all@1.6.0:
@@ -5621,15 +5223,6 @@ snapshots:
dependencies:
any-promise: 1.3.0
- thingies@1.21.0(tslib@2.8.1):
- dependencies:
- tslib: 2.8.1
-
- through2@2.0.5:
- dependencies:
- readable-stream: 2.3.8
- xtend: 4.0.2
-
tinyexec@0.3.2: {}
to-regex-range@5.0.1:
@@ -5640,10 +5233,6 @@ snapshots:
dependencies:
eslint-visitor-keys: 3.4.3
- tree-dump@1.0.2(tslib@2.8.1):
- dependencies:
- tslib: 2.8.1
-
ts-api-utils@2.0.0(typescript@5.7.3):
dependencies:
typescript: 5.7.3
@@ -5666,14 +5255,10 @@ snapshots:
type-fest@0.8.1: {}
- typescript@5.2.2: {}
-
typescript@5.7.3: {}
ufo@1.5.4: {}
- undici-types@6.19.8: {}
-
undici-types@6.20.0: {}
unist-util-is@6.0.0:
@@ -5775,12 +5360,8 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.0
- wrappy@1.0.2: {}
-
xml-name-validator@4.0.0: {}
- xtend@4.0.2: {}
-
y18n@5.0.8: {}
yallist@3.1.1: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..4340350
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - 'packages/*'
\ No newline at end of file
diff --git a/src/components/AccountAvatar.tsx b/src/components/AccountAvatar.tsx
deleted file mode 100644
index 77dd433..0000000
--- a/src/components/AccountAvatar.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { TelegramAccount } from '../store/accounts.ts'
-import { Avatar, AvatarFallback, AvatarImage, makeAvatarFallbackText } from '../lib/components/ui/avatar.tsx'
-
-export function AccountAvatar(props: {
- class?: string
- account: TelegramAccount
-}) {
- return (
-
-
- {makeAvatarFallbackText(props.account.name)}
-
- )
-}
diff --git a/src/components/Updater.tsx b/src/components/Updater.tsx
deleted file mode 100644
index 82b1696..0000000
--- a/src/components/Updater.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-import { asyncPool } from '@fuman/utils'
-
-import { filesize } from 'filesize'
-import { createSignal, onCleanup, onMount } from 'solid-js'
-import { Spinner } from '../lib/components/ui/spinner.tsx'
-import { downloadNpmPackage, getLatestVersions, getPackagesToDownload } from '../lib/vfs/downloader'
-import { VfsStorage } from '../lib/vfs/storage'
-import { swClearCache } from '../sw/client.ts'
-
-export interface UpdaterProps {
- onComplete: (versions: Record) => void
-}
-
-export function Updater(props: UpdaterProps) {
- const [downloadedBytes, setDownloadedBytes] = createSignal(0)
- const [totalBytes, setTotalBytes] = createSignal(Infinity)
- const [step, setStep] = createSignal('Idle')
-
- let abortController: AbortController | undefined
-
- async function runUpdater() {
- if (abortController) abortController.abort()
-
- abortController = new AbortController()
- const signal = abortController.signal
-
- setStep('Checking for updates...')
- const vfs = await VfsStorage.create()
- const latestVersions = await getLatestVersions()
- const versions = await getPackagesToDownload(latestVersions, vfs)
-
- if (Object.keys(versions).length === 0) {
- props.onComplete(latestVersions)
- return
- }
-
- const entries = Object.entries(versions)
-
- setStep('Downloading...')
- await swClearCache()
- await asyncPool(entries, async ([lib, version]) => {
- let isFirst = true
- let prevDownloaded = 0
-
- function onProgress(downloaded: number, total: number) {
- if (isFirst) {
- setTotalBytes(prev => prev === Infinity ? total : prev + total)
- isFirst = false
- }
-
- const diff = downloaded - prevDownloaded
- setDownloadedBytes(prev => prev + diff)
- prevDownloaded = downloaded
- }
-
- await downloadNpmPackage({
- packageName: lib,
- version,
- storage: vfs,
- progress: onProgress,
- filterFiles: (file) => {
- if (!file.header) return true
- const name = file.header.name
- return !name.endsWith('.cjs') && !name.endsWith('.d.cts') && name !== 'LICENSE' && name !== 'README.md'
- },
- signal,
- })
- })
-
- props.onComplete(latestVersions)
- }
-
- onMount(() => {
- runUpdater()
- })
-
- onCleanup(() => {
- abortController?.abort()
- })
-
- return (
-
-
-
- {step()}
- {totalBytes() !== Infinity && (
-
- {filesize(downloadedBytes())}
- {' / '}
- {filesize(totalBytes())}
-
- )}
-
-
- )
-}
diff --git a/src/components/nav/NavbarMenu.tsx b/src/components/nav/NavbarMenu.tsx
deleted file mode 100644
index 7b3cec9..0000000
--- a/src/components/nav/NavbarMenu.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
-import { ChevronDownIcon, ExternalLinkIcon, LucideCheck, SettingsIcon, UsersIcon } from 'lucide-solid'
-import { SiGithub } from 'solid-icons/si'
-import { For } from 'solid-js'
-import { Button } from '../../lib/components/ui/button.tsx'
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuGroupLabel,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from '../../lib/components/ui/dropdown-menu.tsx'
-import { cn } from '../../lib/utils.ts'
-import { $accounts, $activeAccount, $activeAccountId } from '../../store/accounts.ts'
-import { useStore } from '../../store/use-store.ts'
-import { AccountAvatar } from '../AccountAvatar.tsx'
-
-export function NavbarMenu(props: {
- onShowAccounts: () => void
- onShowSettings: () => void
-}) {
- const activeAccount = useStore($activeAccount)
- const accounts = useStore($accounts)
-
- return (
-
- (
-
- )}
- />
-
-
- Accounts
-
- {account => (
- $activeAccountId.set(account.id)}>
-
-
- {account.name}
-
- {account.id === activeAccount()?.id && }
-
- )}
-
-
-
- Manage accounts
-
-
-
-
-
-
- Settings
-
-
-
- GitHub
-
-
-
-
-
- )
-}
diff --git a/src/lib/vfs/system.ts b/src/lib/vfs/system.ts
deleted file mode 100644
index f7c274c..0000000
--- a/src/lib/vfs/system.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-// roughly based on https://github.com/microsoft/TypeScript-Website/blob/v2/packages/typescript-vfs/src/index.ts
-
-import type { CompilerHost, CompilerOptions, SourceFile, System } from 'typescript'
-
-import type { VfsStorage } from './storage'
-
-import { iter, utf8 } from '@fuman/utils'
-
-function notImplemented(methodName: string): any {
- throw new Error(`Method '${methodName}' is not implemented.`)
-}
-
-// "/DOM.d.ts" => "/lib.dom.d.ts"
-const libize = (path: string) => path.replace('/', '/lib.').toLowerCase()
-
-export function createSystem(files: Map): System {
- return {
- args: [],
- createDirectory: () => notImplemented('createDirectory'),
- // TODO: could make a real file tree
- directoryExists: (directory) => {
- return Array.from(files.keys()).some(path => path.startsWith(directory))
- },
- exit: () => notImplemented('exit'),
- fileExists: fileName => files.has(fileName) || files.has(libize(fileName)),
- getCurrentDirectory: () => '/',
- getDirectories: () => [],
- getExecutingFilePath: () => notImplemented('getExecutingFilePath'),
- readDirectory: (directory) => {
- return (directory === '/' ? Array.from(files.keys()) : [])
- },
- readFile: (fileName) => {
- const bytes = files.get(fileName) ?? files.get(libize(fileName))
- if (!bytes) return undefined
-
- return utf8.decoder.decode(bytes)
- },
- resolvePath: path => path,
- newLine: '\n',
- useCaseSensitiveFileNames: true,
- write: () => notImplemented('write'),
- writeFile: (fileName, contents) => {
- files.set(fileName, utf8.encoder.encode(contents))
- },
- deleteFile: (fileName) => {
- files.delete(fileName)
- },
- }
-}
-
-export function createVirtualCompilerHost(
- sys: System,
- compilerOptions: CompilerOptions,
- ts: typeof import('typescript'),
-) {
- const sourceFiles = new Map()
- const save = (sourceFile: SourceFile) => {
- sourceFiles.set(sourceFile.fileName, sourceFile)
- return sourceFile
- }
-
- interface Return {
- compilerHost: CompilerHost
- updateFile: (sourceFile: SourceFile) => boolean
- deleteFile: (sourceFile: SourceFile) => boolean
- }
-
- const vHost: Return = {
- compilerHost: {
- ...sys,
- getCanonicalFileName: fileName => fileName,
- getDefaultLibFileName: () => '/lib.d.ts',
- // getDefaultLibLocation: () => '/',
- getNewLine: () => sys.newLine,
- getSourceFile: (fileName, languageVersionOrOptions) => {
- const existing = sourceFiles.get(fileName)
- if (existing) return existing
-
- const content = sys.readFile(fileName)
- if (!content) return undefined
-
- return save(
- ts.createSourceFile(
- fileName,
- content,
- languageVersionOrOptions ?? compilerOptions.target ?? ts.ScriptTarget.ESNext,
- false,
- ),
- )
- },
- useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
- },
- updateFile: (sourceFile) => {
- const alreadyExists = sourceFiles.has(sourceFile.fileName)
- sys.writeFile(sourceFile.fileName, sourceFile.text)
- sourceFiles.set(sourceFile.fileName, sourceFile)
- return alreadyExists
- },
- deleteFile: (sourceFile) => {
- const alreadyExists = sourceFiles.has(sourceFile.fileName)
- sourceFiles.delete(sourceFile.fileName)
- sys.deleteFile!(sourceFile.fileName)
- return alreadyExists
- },
- }
- return vHost
-}
-
-export async function createFilesMapFromVfs(vfs: VfsStorage, libs: string[]): Promise