clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/prf-provision-custody
lofi / package / runtime / probe.ts
probe.ts 4.6K
1import { activeServerUrl, syncing } from "./config.ts";2// Package-owned development probe.3import { readDeviceCapabilityReport } from "./device-capabilities.ts";4import { type InspectorAdapter, type InspectorSnapshot, mountInspector } from "./inspector.ts";5import { getForegroundRecoveryState, subscribeForegroundRecovery } from "./lifecycle.ts";6import {7 getRuntime,8 getRuntimeDiagnostics,9 reloadBrowserRuntime,10 subscribeRuntimeDiagnostics,11} from "./runtime.ts";12import { isTransportPausedByInspector, setTransportPausedByInspector } from "./transport-gate.ts";1314export type LofiDevelopmentBridge = InspectorAdapter;1516type LofiDevelopmentGlobal = typeof globalThis & {17 __LOFI_INSPECTOR__?: LofiDevelopmentBridge;18};1920const stateListeners = new Set<() => void>();2122function notifyState(): void {23 for (const listener of stateListeners) listener();24}2526async function readSnapshot(): Promise<InspectorSnapshot> {27 const [runtime, device] = await Promise.all([28 getRuntime(),29 readDeviceCapabilityReport(),30 ]);31 const diagnostics = getRuntimeDiagnostics();32 const lifecycle = getForegroundRecoveryState();33 const authMode = runtime.db.getAuthState().authMode;34 return {35 identity: {36 state: authMode === "local-first"37 ? "device-local key active"38 : authMode === "external"39 ? "external"40 : authMode === "anonymous"41 ? "anonymous"42 : "unavailable",43 backup: syncing() ? "recovery phrase" : "local-only",44 },45 storage: {46 driver: diagnostics.storageState === "persistent-driver-open"47 ? "persistent open"48 : diagnostics.storageState === "persistent-requested"49 ? "persistent requested"50 : "failed",51 persistence: device.persistentPermission === "not-granted"52 ? "not granted"53 : device.persistentPermission,54 fallback: diagnostics.storageState === "failed" ? "unavailable" : "none",55 startupFailure: diagnostics.startupFailure?.code ?? "none",56 },57 sync: {58 mode: activeServerUrl() ? "managed configured" : "local-only",59 transport: activeServerUrl()60 ? isTransportPausedByInspector() ? "paused by inspector" : "live detail unavailable"61 : "not configured",62 pendingLocalWrites: diagnostics.pendingLocalWrites,63 pendingGlobalWrites: diagnostics.pendingGlobalWrites,64 lastWrite: diagnostics.lastWriteDurability,65 },66 runtime: {67 clients: diagnostics.activeClients,68 consumers: diagnostics.activeConsumers,69 vendorSubscriptions: diagnostics.activeVendorSubscriptions,70 mutationListeners: diagnostics.activeMutationListeners,71 mutationErrors: diagnostics.mutationErrors,72 },73 lifecycle: {74 ...lifecycle,75 transportDetail: activeServerUrl() ? "live detail unavailable" : "not configured",76 },77 multiTab: {78 role: "unavailable",79 detail: "Jazz alpha.53 exposes no supported leader/follower signal",80 },81 };82}8384const bridge: LofiDevelopmentBridge = {85 readSnapshot,86 subscribe(listener) {87 stateListeners.add(listener);88 const unsubscribeRuntime = subscribeRuntimeDiagnostics(listener);89 const unsubscribeLifecycle = subscribeForegroundRecovery(listener);90 let active = true;91 return () => {92 if (!active) return;93 active = false;94 stateListeners.delete(listener);95 unsubscribeRuntime();96 unsubscribeLifecycle();97 };98 },99 async setTransportPaused(paused) {100 if (!activeServerUrl()) {101 throw new Error("Cloud transport pause is unavailable in local-only mode");102 }103 const runtime = await getRuntime();104 // Record the pause before touching the transport so foreground recovery105 // cannot race a reconnect in between and silently override the inspector.106 setTransportPausedByInspector(paused);107 try {108 if (paused) await runtime.db.disconnect();109 else await runtime.db.reconnect();110 } catch (error) {111 setTransportPausedByInspector(!paused);112 throw error;113 }114 notifyState();115 },116 restartClient() {117 // The shutdown-then-navigate boundary is the supported clean-runtime path118 // for the pinned Jazz alpha; reloading without it leaves the persistent119 // worker attached during navigation.120 return reloadBrowserRuntime();121 },122 async clearLocalReplica() {123 const runtime = await getRuntime();124 await runtime.db.deleteClientStorage();125 return await reloadBrowserRuntime();126 },127};128129if (typeof document !== "undefined") {130 (globalThis as LofiDevelopmentGlobal).__LOFI_INSPECTOR__ = bridge;131 const mounted = mountInspector(bridge);132 import.meta.hot?.dispose(() => {133 mounted.dispose();134 delete (globalThis as LofiDevelopmentGlobal).__LOFI_INSPECTOR__;135 });136}