@kbve/droid 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/comlink.js +1 -7
- package/droid.mjs +191 -61
- package/index.d.ts +29 -0
- package/index.js +9054 -2763
- package/lib/gateway/SupabaseGateway.d.ts +38 -0
- package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
- package/lib/gateway/WorkerCommunication.d.ts +39 -0
- package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
- package/lib/gateway/WorkerPool.d.ts +55 -0
- package/lib/gateway/WorkerPool.d.ts.map +1 -0
- package/lib/gateway/capabilities.d.ts +23 -0
- package/lib/gateway/capabilities.d.ts.map +1 -0
- package/lib/gateway/index.d.ts +9 -0
- package/lib/gateway/index.d.ts.map +1 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
- package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
- package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
- package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
- package/lib/gateway/types.d.ts +81 -0
- package/lib/gateway/types.d.ts.map +1 -0
- package/lib/workers/canvas-ui-renderer.d.ts +25 -0
- package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
- package/lib/workers/canvas-worker.d.ts +14 -0
- package/lib/workers/canvas-worker.d.ts.map +1 -0
- package/lib/workers/canvas-worker.js +291 -145
- package/lib/workers/data.d.ts +40 -0
- package/lib/workers/data.d.ts.map +1 -0
- package/lib/workers/db-worker.d.ts +69 -0
- package/lib/workers/db-worker.d.ts.map +1 -0
- package/lib/workers/db-worker.js +2680 -2147
- package/lib/workers/events.d.ts +11 -0
- package/lib/workers/events.d.ts.map +1 -0
- package/lib/workers/flexbuilder.d.ts +4 -0
- package/lib/workers/flexbuilder.d.ts.map +1 -0
- package/lib/workers/init.d.ts +11 -0
- package/lib/workers/init.d.ts.map +1 -0
- package/lib/workers/main.d.ts +107 -0
- package/lib/workers/main.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.d.ts +2 -0
- package/lib/workers/supabase-db-worker.d.ts.map +1 -0
- package/lib/workers/supabase-db-worker.js +9337 -0
- package/lib/workers/supabase-shared-worker.d.ts +2 -0
- package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
- package/lib/workers/supabase-shared-worker.js +12635 -0
- package/lib/workers/tools.d.ts +4 -0
- package/lib/workers/tools.d.ts.map +1 -0
- package/lib/workers/validate.d.ts +15 -0
- package/lib/workers/validate.d.ts.map +1 -0
- package/lib/workers/ws-worker.d.ts +11 -0
- package/lib/workers/ws-worker.d.ts.map +1 -0
- package/lib/workers/ws-worker.js +174 -115
- package/main.js +4875 -0
- package/package.json +17 -6
- package/reference.js +688 -228
- package/src/index.ts +38 -13
- package/src/lib/droid.spec.ts +14 -19
- package/src/lib/droid.ts +48 -4
- package/src/lib/gateway/SupabaseGateway.ts +183 -0
- package/src/lib/gateway/WorkerCommunication.ts +132 -0
- package/src/lib/gateway/WorkerPool.ts +304 -0
- package/src/lib/gateway/capabilities.ts +80 -0
- package/src/lib/gateway/index.ts +15 -0
- package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
- package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
- package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
- package/src/lib/gateway/types.ts +112 -0
- package/src/lib/mod/mod-manager.ts +13 -8
- package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
- package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
- package/src/lib/mod/module/supabase/mod-supabase.ts +68 -0
- package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
- package/src/lib/state/auth.spec.ts +58 -0
- package/src/lib/state/auth.ts +30 -0
- package/src/lib/state/index.ts +34 -0
- package/src/lib/state/overlay-manager.ts +125 -0
- package/src/lib/state/router.ts +24 -0
- package/src/lib/state/theme-sync.ts +75 -0
- package/src/lib/state/toasts.spec.ts +106 -0
- package/src/lib/state/toasts.ts +34 -0
- package/src/lib/state/ui.spec.ts +148 -0
- package/src/lib/state/ui.ts +45 -0
- package/src/lib/state/welcome-toast.ts +76 -0
- package/src/lib/types/bento.spec.ts +116 -0
- package/src/lib/types/bento.ts +94 -55
- package/src/lib/types/event-types.spec.ts +93 -0
- package/src/lib/types/event-types.ts +39 -3
- package/src/lib/types/modules.ts +12 -5
- package/src/lib/types/ui-event-types.ts +66 -0
- package/src/lib/workers/canvas-ui-renderer.ts +266 -0
- package/src/lib/workers/canvas-worker.ts +94 -9
- package/src/lib/workers/data.ts +59 -36
- package/src/lib/workers/db-worker.ts +79 -32
- package/src/lib/workers/events.spec.ts +208 -0
- package/src/lib/workers/events.ts +27 -14
- package/src/lib/workers/flexbuilder.ts +2 -2
- package/src/lib/workers/init.ts +25 -17
- package/src/lib/workers/main.ts +510 -316
- package/src/lib/workers/supabase-db-worker.ts +221 -0
- package/src/lib/workers/supabase-shared-worker.ts +572 -0
- package/src/lib/workers/validate.ts +64 -0
- package/src/lib/workers/ws-worker.ts +156 -25
- package/src/setup-vitest.ts +4 -2
- package/src/types.ts +30 -0
- package/src/vite-env.d.ts +1 -0
- package/workers/main.js +10 -1123
- package/src/types.d.ts +0 -31
package/src/lib/workers/main.ts
CHANGED
|
@@ -1,119 +1,326 @@
|
|
|
1
|
-
import { wrap,
|
|
1
|
+
import { wrap, proxy } from 'comlink';
|
|
2
2
|
import type { Remote } from 'comlink';
|
|
3
3
|
import { persistentMap } from '@nanostores/persistent';
|
|
4
4
|
import type { LocalStorageAPI } from './db-worker';
|
|
5
5
|
import type { WSInstance } from './ws-worker';
|
|
6
|
-
import { initializeWorkerDatabase
|
|
6
|
+
import { initializeWorkerDatabase } from './init';
|
|
7
7
|
import type { CanvasWorkerAPI } from './canvas-worker';
|
|
8
8
|
import { getModManager } from '../mod/mod-manager';
|
|
9
9
|
import { scopeData } from './data';
|
|
10
10
|
import { dispatchAsync, renderVNode } from './tools';
|
|
11
|
+
import type { VirtualNode, ModManager } from '../types/modules';
|
|
11
12
|
import type { PanelPayload, PanelId } from '../types/panel-types';
|
|
12
13
|
import { DroidEvents } from './events';
|
|
14
|
+
import {
|
|
15
|
+
addToast as _addToast,
|
|
16
|
+
removeToast as _removeToast,
|
|
17
|
+
} from '../state/toasts';
|
|
18
|
+
import { openTooltip, closeTooltip, openModal, closeModal } from '../state/ui';
|
|
19
|
+
import {
|
|
20
|
+
ToastPayloadSchema,
|
|
21
|
+
TooltipPayloadSchema,
|
|
22
|
+
ModalPayloadSchema,
|
|
23
|
+
} from '../types/ui-event-types';
|
|
24
|
+
import { SupabaseGateway } from '../gateway/SupabaseGateway';
|
|
25
|
+
import type { GatewayConfig } from '../gateway/types';
|
|
26
|
+
import { observeThemeChanges } from '../state/theme-sync';
|
|
27
|
+
import { OverlayManager } from '../state/overlay-manager';
|
|
28
|
+
import { showWelcomeToast } from '../state/welcome-toast';
|
|
29
|
+
|
|
30
|
+
/** Messages emitted from workers to the main thread for UI side-effects. */
|
|
31
|
+
type WorkerUIMessage =
|
|
32
|
+
| { type: 'injectVNode'; vnode: VirtualNode }
|
|
33
|
+
| { type: 'toast'; payload: unknown }
|
|
34
|
+
| { type: 'toast-remove'; payload: { id: string } }
|
|
35
|
+
| { type: 'tooltip-open'; payload: unknown }
|
|
36
|
+
| { type: 'tooltip-close'; payload?: { id: string } }
|
|
37
|
+
| { type: 'modal-open'; payload: unknown }
|
|
38
|
+
| { type: 'modal-close'; payload?: { id: string } }
|
|
39
|
+
| { type: string; [key: string]: unknown };
|
|
13
40
|
|
|
14
41
|
const EXPECTED_DB_VERSION = '1.0.3';
|
|
15
42
|
let initialized = false;
|
|
43
|
+
let _initPromise: Promise<void> | null = null;
|
|
44
|
+
|
|
45
|
+
// --- Scale State ---
|
|
46
|
+
export type DroidScaleLevel = 'full' | 'minimal';
|
|
47
|
+
|
|
48
|
+
interface DroidWorkerRefs {
|
|
49
|
+
canvasWorkerRaw?: Worker;
|
|
50
|
+
canvasWorkerProxy?: Remote<CanvasWorkerAPI>;
|
|
51
|
+
canvasWorkerOpts?: { workerRef?: Worker; workerURL?: string };
|
|
52
|
+
modInitOpts?: { resolver?: (url: string) => string };
|
|
53
|
+
gatewayConfig?: GatewayConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let _scaleLevel: DroidScaleLevel = 'full';
|
|
57
|
+
const _workerRefs: DroidWorkerRefs = {};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns the current droid scale level.
|
|
61
|
+
*/
|
|
62
|
+
export function getScaleLevel(): DroidScaleLevel {
|
|
63
|
+
return _scaleLevel;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Downscale droid to minimal mode: terminates canvas worker, mod workers,
|
|
68
|
+
* gateway worker pool, and overlay manager. Keeps DB SharedWorker + WS
|
|
69
|
+
* SharedWorker alive for session/auth persistence.
|
|
70
|
+
*
|
|
71
|
+
* Call this before loading heavy WASM/pthread workloads.
|
|
72
|
+
*/
|
|
73
|
+
export async function downscale(): Promise<void> {
|
|
74
|
+
if (_scaleLevel === 'minimal') {
|
|
75
|
+
console.log('[DROID] Already in minimal mode');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log('[DROID] Downscaling to minimal mode...');
|
|
80
|
+
|
|
81
|
+
// 1. Destroy overlay manager (unbinds canvas)
|
|
82
|
+
if (window.kbve?.overlay) {
|
|
83
|
+
try {
|
|
84
|
+
await (window.kbve.overlay as OverlayManager).destroy();
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.warn('[DROID] Overlay destroy error:', e);
|
|
87
|
+
}
|
|
88
|
+
delete window.kbve.overlay;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. Terminate all mods
|
|
92
|
+
if (window.kbve?.mod) {
|
|
93
|
+
const mod = window.kbve.mod as ModManager;
|
|
94
|
+
for (const handle of Object.values(mod.registry)) {
|
|
95
|
+
try {
|
|
96
|
+
mod.unload(handle.id);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
console.warn('[DROID] Mod unload error:', e);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. Terminate gateway (worker pool + strategy workers)
|
|
104
|
+
if (window.kbve?.gateway) {
|
|
105
|
+
try {
|
|
106
|
+
(window.kbve.gateway as SupabaseGateway).terminate();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.warn('[DROID] Gateway terminate error:', e);
|
|
109
|
+
}
|
|
110
|
+
delete window.kbve.gateway;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 4. Terminate canvas worker
|
|
114
|
+
if (_workerRefs.canvasWorkerRaw) {
|
|
115
|
+
try {
|
|
116
|
+
_workerRefs.canvasWorkerRaw.terminate();
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.warn('[DROID] Canvas worker terminate error:', e);
|
|
119
|
+
}
|
|
120
|
+
_workerRefs.canvasWorkerRaw = undefined;
|
|
121
|
+
_workerRefs.canvasWorkerProxy = undefined;
|
|
122
|
+
if (window.kbve?.uiux) {
|
|
123
|
+
(window.kbve.uiux as Record<string, unknown>)['worker'] = undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_scaleLevel = 'minimal';
|
|
128
|
+
|
|
129
|
+
window.kbve?.events?.emit('droid-downscale', {
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
level: 'minimal',
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log('[DROID] Downscaled to minimal mode (DB + WS workers active)');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Upscale droid back to full mode: re-initializes canvas worker, mod manager,
|
|
139
|
+
* overlay manager, and gateway.
|
|
140
|
+
*
|
|
141
|
+
* Call this when the heavy workload is done and you want full droid features.
|
|
142
|
+
*/
|
|
143
|
+
export async function upscale(): Promise<void> {
|
|
144
|
+
if (_scaleLevel === 'full') {
|
|
145
|
+
console.log('[DROID] Already in full mode');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log('[DROID] Upscaling to full mode...');
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// 1. Re-init canvas worker
|
|
153
|
+
const canvasWorker = initDedicatedWorker(
|
|
154
|
+
'canvas-worker',
|
|
155
|
+
_workerRefs.canvasWorkerOpts,
|
|
156
|
+
);
|
|
157
|
+
_workerRefs.canvasWorkerRaw = canvasWorker;
|
|
158
|
+
const canvasProxy = wrap<CanvasWorkerAPI>(canvasWorker);
|
|
159
|
+
_workerRefs.canvasWorkerProxy = canvasProxy;
|
|
160
|
+
|
|
161
|
+
if (window.kbve?.uiux) {
|
|
162
|
+
(window.kbve.uiux as Record<string, unknown>)['worker'] =
|
|
163
|
+
canvasProxy;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 2. Re-init mod manager
|
|
167
|
+
if (window.kbve?.mod) {
|
|
168
|
+
const mod = await getModManager(_workerRefs.modInitOpts?.resolver);
|
|
169
|
+
window.kbve.mod = mod;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 3. Re-init gateway if we had one
|
|
173
|
+
if (_workerRefs.gatewayConfig) {
|
|
174
|
+
const gateway = new SupabaseGateway(_workerRefs.gatewayConfig);
|
|
175
|
+
window.kbve.gateway = gateway;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 4. Re-init overlay manager
|
|
179
|
+
const overlay = new OverlayManager({
|
|
180
|
+
preferredPath: 'auto',
|
|
181
|
+
canvasWorker: canvasProxy,
|
|
182
|
+
});
|
|
183
|
+
window.kbve.overlay = overlay;
|
|
184
|
+
|
|
185
|
+
_scaleLevel = 'full';
|
|
186
|
+
|
|
187
|
+
window.kbve?.events?.emit('droid-upscale', {
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
level: 'full',
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
console.log('[DROID] Upscaled to full mode');
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error('[DROID] Upscale failed:', err);
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
16
198
|
|
|
17
199
|
export function resolveWorkerURL(name: string, fallback?: string): string {
|
|
18
200
|
if (!name)
|
|
19
201
|
throw new Error('[resolveWorkerURL] Worker name must be defined');
|
|
20
202
|
|
|
21
203
|
if (typeof window !== 'undefined') {
|
|
22
|
-
const globalMap = (
|
|
204
|
+
const globalMap = (
|
|
205
|
+
window as unknown as Record<string, Record<string, string>>
|
|
206
|
+
)['kbveWorkerURLs'];
|
|
23
207
|
if (globalMap?.[name]) return globalMap[name];
|
|
24
208
|
}
|
|
25
209
|
|
|
26
|
-
// No bundler-specific resolution: just return fallback or root-relative
|
|
27
210
|
return fallback ?? `/workers/${name}`;
|
|
28
211
|
}
|
|
29
212
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
213
|
+
// --- Worker Init Helpers ---
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Simplified worker init — tries provided ref/URL, then falls back to
|
|
217
|
+
* resolveWorkerURL which checks window.kbveWorkerURLs or /workers/{name}.
|
|
218
|
+
*/
|
|
219
|
+
function initSharedWorker(
|
|
220
|
+
name: string,
|
|
221
|
+
opts?: { workerRef?: SharedWorker; workerURL?: string },
|
|
222
|
+
): SharedWorker {
|
|
223
|
+
if (opts?.workerRef) {
|
|
224
|
+
opts.workerRef.port.start();
|
|
225
|
+
return opts.workerRef;
|
|
40
226
|
}
|
|
41
227
|
|
|
42
|
-
|
|
228
|
+
const url = opts?.workerURL ?? resolveWorkerURL(name);
|
|
229
|
+
const worker = new SharedWorker(url, { type: 'module' });
|
|
230
|
+
worker.port.start();
|
|
231
|
+
|
|
232
|
+
// Notify worker to clean up this port on tab close/reload
|
|
233
|
+
window.addEventListener('beforeunload', () => {
|
|
234
|
+
worker.port.postMessage({ type: 'close' });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return worker;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function initDedicatedWorker(
|
|
241
|
+
name: string,
|
|
242
|
+
opts?: { workerRef?: Worker; workerURL?: string },
|
|
243
|
+
): Worker {
|
|
244
|
+
if (opts?.workerRef) return opts.workerRef;
|
|
245
|
+
|
|
246
|
+
const url = opts?.workerURL ?? resolveWorkerURL(name);
|
|
247
|
+
return new Worker(url, { type: 'module' });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function listenFirstConnect(port: MessagePort): Promise<boolean> {
|
|
251
|
+
return new Promise<boolean>((resolve) => {
|
|
252
|
+
const handler = (e: MessageEvent) => {
|
|
253
|
+
if (e.data?.type === 'first-connect') {
|
|
254
|
+
resolve(true);
|
|
255
|
+
port.removeEventListener('message', handler);
|
|
256
|
+
} else if (e.data?.type === 'reconnect') {
|
|
257
|
+
resolve(false);
|
|
258
|
+
port.removeEventListener('message', handler);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
port.addEventListener('message', handler);
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
port.removeEventListener('message', handler);
|
|
264
|
+
resolve(false);
|
|
265
|
+
}, 5_000);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function initStorageComlink(opts?: {
|
|
270
|
+
workerRef?: SharedWorker;
|
|
271
|
+
workerURL?: string;
|
|
272
|
+
i18nPath?: string;
|
|
273
|
+
dataPath?: string;
|
|
274
|
+
}): Promise<{ api: Remote<LocalStorageAPI>; isFirstConnection: boolean }> {
|
|
275
|
+
const worker = initSharedWorker('db-worker', opts);
|
|
276
|
+
const firstPromise = listenFirstConnect(worker.port);
|
|
277
|
+
const api = wrap<LocalStorageAPI>(worker.port);
|
|
278
|
+
const finalApi = await finalize(api, {
|
|
279
|
+
i18nPath: opts?.i18nPath,
|
|
280
|
+
dataPath: opts?.dataPath,
|
|
281
|
+
});
|
|
282
|
+
const isFirstConnection = await firstPromise;
|
|
283
|
+
return { api: finalApi, isFirstConnection };
|
|
43
284
|
}
|
|
285
|
+
|
|
44
286
|
async function initWsComlink(opts?: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}): Promise<Remote<WSInstance
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
opts.workerRef.port.start();
|
|
54
|
-
api = wrap<WSInstance>(opts.workerRef.port);
|
|
55
|
-
return api;
|
|
56
|
-
} catch (err) {
|
|
57
|
-
console.warn('[DROID] ws-worker workerRef failed:', err);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 2. Try provided URL (e.g. from CDN or blob)
|
|
62
|
-
if (opts?.workerURL) {
|
|
63
|
-
try {
|
|
64
|
-
const worker = new SharedWorker(opts.workerURL, { type: 'module' });
|
|
65
|
-
worker.port.start();
|
|
66
|
-
api = wrap<WSInstance>(worker.port);
|
|
67
|
-
return api;
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.warn('[DROID] ws-worker workerURL failed:', err);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 3. Vite-style TS import
|
|
74
|
-
try {
|
|
75
|
-
const worker = new SharedWorker(
|
|
76
|
-
new URL('./ws-worker.ts', import.meta.url),
|
|
77
|
-
{ type: 'module' },
|
|
78
|
-
);
|
|
79
|
-
worker.port.start();
|
|
80
|
-
api = wrap<WSInstance>(worker.port);
|
|
81
|
-
return api;
|
|
82
|
-
} catch (err) {
|
|
83
|
-
console.warn('[DROID] ws-worker vite-style .ts import failed:', err);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 4. Same-dir JS fallback (post-build)
|
|
87
|
-
try {
|
|
88
|
-
const worker = new SharedWorker(
|
|
89
|
-
new URL('./ws-worker.js', import.meta.url),
|
|
90
|
-
{ type: 'module' },
|
|
91
|
-
);
|
|
92
|
-
worker.port.start();
|
|
93
|
-
api = wrap<WSInstance>(worker.port);
|
|
94
|
-
return api;
|
|
95
|
-
} catch (err) {
|
|
96
|
-
console.warn('[DROID] ws-worker ./ws-worker.js fallback failed:', err);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 5. Browser-root hardcoded path (static /public/)
|
|
100
|
-
try {
|
|
101
|
-
const worker = new SharedWorker('/ws-worker.js', { type: 'module' });
|
|
102
|
-
worker.port.start();
|
|
103
|
-
api = wrap<WSInstance>(worker.port);
|
|
104
|
-
return api;
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.warn('[DROID] ws-worker absolute hardcoded fallback failed:', err);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 6. Final failure
|
|
110
|
-
console.error('[DROID] No WS Worker Comlink Initialized');
|
|
111
|
-
throw new Error('[DROID] Failed to initialize ws-worker');
|
|
287
|
+
workerRef?: SharedWorker;
|
|
288
|
+
workerURL?: string;
|
|
289
|
+
}): Promise<{ ws: Remote<WSInstance>; isFirstConnection: boolean }> {
|
|
290
|
+
const worker = initSharedWorker('ws-worker', opts);
|
|
291
|
+
const firstPromise = listenFirstConnect(worker.port);
|
|
292
|
+
const ws = wrap<WSInstance>(worker.port);
|
|
293
|
+
const isFirstConnection = await firstPromise;
|
|
294
|
+
return { ws, isFirstConnection };
|
|
112
295
|
}
|
|
113
296
|
|
|
297
|
+
async function initCanvasComlink(opts?: {
|
|
298
|
+
workerRef?: Worker;
|
|
299
|
+
workerURL?: string;
|
|
300
|
+
}): Promise<{ proxy: Remote<CanvasWorkerAPI>; raw: Worker }> {
|
|
301
|
+
const worker = initDedicatedWorker('canvas-worker', opts);
|
|
302
|
+
return { proxy: wrap<CanvasWorkerAPI>(worker), raw: worker };
|
|
303
|
+
}
|
|
114
304
|
|
|
305
|
+
async function finalize(
|
|
306
|
+
api: Remote<LocalStorageAPI>,
|
|
307
|
+
initOpts?: { i18nPath?: string; dataPath?: string },
|
|
308
|
+
): Promise<Remote<LocalStorageAPI>> {
|
|
309
|
+
const version = await api.getVersion();
|
|
310
|
+
if (version !== EXPECTED_DB_VERSION) {
|
|
311
|
+
await initializeWorkerDatabase(api, {
|
|
312
|
+
version: EXPECTED_DB_VERSION,
|
|
313
|
+
i18nPath: initOpts?.i18nPath,
|
|
314
|
+
dataPath: initOpts?.dataPath,
|
|
315
|
+
locale: 'en',
|
|
316
|
+
defaults: { welcome: 'Welcome!', theme: 'dark' },
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return api;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// --- UIUX ---
|
|
115
323
|
|
|
116
|
-
// * UIUX
|
|
117
324
|
const uiuxState = persistentMap<{
|
|
118
325
|
panelManager: Record<
|
|
119
326
|
PanelId,
|
|
@@ -123,7 +330,7 @@ const uiuxState = persistentMap<{
|
|
|
123
330
|
}
|
|
124
331
|
>;
|
|
125
332
|
themeManager: { theme: 'light' | 'dark' | 'auto' };
|
|
126
|
-
toastManager: Record<string,
|
|
333
|
+
toastManager: Record<string, unknown>;
|
|
127
334
|
scrollY: number;
|
|
128
335
|
}>(
|
|
129
336
|
'uiux-state',
|
|
@@ -144,84 +351,20 @@ const uiuxState = persistentMap<{
|
|
|
144
351
|
},
|
|
145
352
|
);
|
|
146
353
|
|
|
147
|
-
async function initCanvasComlink(opts?: {
|
|
148
|
-
workerRef?: Worker;
|
|
149
|
-
workerURL?: string;
|
|
150
|
-
}): Promise<Remote<CanvasWorkerAPI>> {
|
|
151
|
-
let api: Remote<CanvasWorkerAPI> | null = null;
|
|
152
|
-
|
|
153
|
-
// 1. Try direct Worker reference
|
|
154
|
-
if (opts?.workerRef) {
|
|
155
|
-
try {
|
|
156
|
-
api = wrap<CanvasWorkerAPI>(opts.workerRef);
|
|
157
|
-
return api;
|
|
158
|
-
} catch (err) {
|
|
159
|
-
console.warn('[DROID] Provided canvasWorkerRef failed:', err);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 2. Try provided worker URL
|
|
164
|
-
if (opts?.workerURL) {
|
|
165
|
-
try {
|
|
166
|
-
const worker = new Worker(opts.workerURL, { type: 'module' });
|
|
167
|
-
api = wrap<CanvasWorkerAPI>(worker);
|
|
168
|
-
return api;
|
|
169
|
-
} catch (err) {
|
|
170
|
-
console.warn('[DROID] Provided canvasWorkerURL failed:', err);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// 3. Vite-style dev import (.ts)
|
|
175
|
-
try {
|
|
176
|
-
const worker = new Worker(
|
|
177
|
-
new URL('./canvas-worker.ts', import.meta.url),
|
|
178
|
-
{ type: 'module' },
|
|
179
|
-
);
|
|
180
|
-
api = wrap<CanvasWorkerAPI>(worker);
|
|
181
|
-
return api;
|
|
182
|
-
} catch (err) {
|
|
183
|
-
console.warn('[DROID] Vite-style canvas-worker.ts import failed:', err);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 4. Same-dir .js fallback (build output)
|
|
187
|
-
try {
|
|
188
|
-
const worker = new Worker(
|
|
189
|
-
new URL('./canvas-worker.js', import.meta.url),
|
|
190
|
-
{ type: 'module' },
|
|
191
|
-
);
|
|
192
|
-
api = wrap<CanvasWorkerAPI>(worker);
|
|
193
|
-
return api;
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.warn('[DROID] canvas-worker.js (same dir) fallback failed:', err);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 5. Runtime hardcoded absolute path (public/)
|
|
199
|
-
try {
|
|
200
|
-
const worker = new Worker('/canvas-worker.js', { type: 'module' });
|
|
201
|
-
api = wrap<CanvasWorkerAPI>(worker);
|
|
202
|
-
return api;
|
|
203
|
-
} catch (err) {
|
|
204
|
-
console.warn('[DROID] canvas-worker.js (absolute root) fallback failed:', err);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 6. Final failure
|
|
208
|
-
console.error('[DROID] No Canvas Comlink Initialized');
|
|
209
|
-
throw new Error('[DROID] Failed to initialize canvas worker');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
354
|
export const uiux = {
|
|
214
355
|
state: uiuxState,
|
|
215
356
|
openPanel(id: PanelId, payload?: PanelPayload) {
|
|
216
357
|
const panels = { ...uiuxState.get().panelManager };
|
|
217
358
|
panels[id] = { open: true, payload };
|
|
218
359
|
uiuxState.setKey('panelManager', panels);
|
|
360
|
+
DroidEvents.emit('panel-open', { id, payload });
|
|
219
361
|
},
|
|
220
362
|
|
|
221
363
|
closePanel(id: PanelId) {
|
|
222
364
|
const panels = { ...uiuxState.get().panelManager };
|
|
223
365
|
panels[id] = { open: false, payload: undefined };
|
|
224
366
|
uiuxState.setKey('panelManager', panels);
|
|
367
|
+
DroidEvents.emit('panel-close', { id });
|
|
225
368
|
},
|
|
226
369
|
|
|
227
370
|
togglePanel(id: PanelId, payload?: PanelPayload) {
|
|
@@ -229,44 +372,61 @@ export const uiux = {
|
|
|
229
372
|
const isOpen = panels[id]?.open ?? false;
|
|
230
373
|
panels[id] = { open: !isOpen, payload: !isOpen ? payload : undefined };
|
|
231
374
|
uiuxState.setKey('panelManager', panels);
|
|
375
|
+
if (!isOpen) {
|
|
376
|
+
DroidEvents.emit('panel-open', { id, payload });
|
|
377
|
+
} else {
|
|
378
|
+
DroidEvents.emit('panel-close', { id });
|
|
379
|
+
}
|
|
232
380
|
},
|
|
233
381
|
|
|
234
382
|
setTheme(theme: 'light' | 'dark' | 'auto') {
|
|
235
383
|
uiuxState.setKey('themeManager', { theme });
|
|
236
384
|
},
|
|
237
385
|
|
|
238
|
-
addToast(
|
|
386
|
+
/** @deprecated Use addToast() from '@kbve/droid' state exports instead. */
|
|
387
|
+
addToast(id: string, data: unknown) {
|
|
388
|
+
console.warn(
|
|
389
|
+
'[KBVE] uiux.addToast is deprecated. Use addToast() from @kbve/droid.',
|
|
390
|
+
);
|
|
239
391
|
const toasts = { ...uiuxState.get().toastManager, [id]: data };
|
|
240
392
|
uiuxState.setKey('toastManager', toasts);
|
|
241
393
|
},
|
|
242
394
|
|
|
395
|
+
/** @deprecated Use removeToast() from '@kbve/droid' state exports instead. */
|
|
243
396
|
removeToast(id: string) {
|
|
397
|
+
console.warn(
|
|
398
|
+
'[KBVE] uiux.removeToast is deprecated. Use removeToast() from @kbve/droid.',
|
|
399
|
+
);
|
|
244
400
|
const toasts = { ...uiuxState.get().toastManager };
|
|
245
401
|
delete toasts[id];
|
|
246
402
|
uiuxState.setKey('toastManager', toasts);
|
|
247
403
|
},
|
|
404
|
+
|
|
248
405
|
async dispatchCanvasRequest(
|
|
249
406
|
panelId: PanelId,
|
|
250
407
|
canvasEl: HTMLCanvasElement,
|
|
251
408
|
mode: 'static' | 'animated' | 'dynamic' = 'animated',
|
|
252
409
|
) {
|
|
253
410
|
const offscreen = canvasEl.transferControlToOffscreen();
|
|
254
|
-
|
|
411
|
+
const worker = (window.kbve?.uiux as Record<string, unknown>)?.[
|
|
412
|
+
'worker'
|
|
413
|
+
] as Remote<CanvasWorkerAPI> | undefined;
|
|
414
|
+
await worker?.bindCanvas(panelId, offscreen, mode);
|
|
255
415
|
},
|
|
256
416
|
|
|
257
417
|
closeAllPanels() {
|
|
258
418
|
const panels = { ...uiuxState.get().panelManager };
|
|
259
|
-
console.log('error panel is closing');
|
|
260
|
-
|
|
261
419
|
for (const id of Object.keys(panels) as Array<PanelId>) {
|
|
262
420
|
panels[id] = { open: false, payload: undefined };
|
|
263
421
|
}
|
|
264
|
-
|
|
265
422
|
uiuxState.setKey('panelManager', panels);
|
|
266
423
|
},
|
|
267
424
|
|
|
268
|
-
emitFromWorker(msg:
|
|
269
|
-
|
|
425
|
+
emitFromWorker(msg: WorkerUIMessage) {
|
|
426
|
+
// Existing: VNode injection
|
|
427
|
+
if (msg.type === 'injectVNode') {
|
|
428
|
+
const vnode = msg.vnode as VirtualNode | undefined;
|
|
429
|
+
if (!vnode) return;
|
|
270
430
|
dispatchAsync(() => {
|
|
271
431
|
const target = document.getElementById('bento-grid-inject');
|
|
272
432
|
if (!target) {
|
|
@@ -276,20 +436,80 @@ export const uiux = {
|
|
|
276
436
|
return;
|
|
277
437
|
}
|
|
278
438
|
|
|
279
|
-
const el = renderVNode(
|
|
439
|
+
const el = renderVNode(vnode);
|
|
280
440
|
el.classList.add('animate-fade-in');
|
|
281
|
-
if (
|
|
282
|
-
const existing = document.getElementById(
|
|
441
|
+
if (vnode.id) {
|
|
442
|
+
const existing = document.getElementById(vnode.id);
|
|
283
443
|
if (existing) existing.remove();
|
|
284
444
|
}
|
|
285
445
|
|
|
286
446
|
target.appendChild(el);
|
|
287
447
|
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Toast from worker
|
|
452
|
+
if (msg.type === 'toast' && msg.payload) {
|
|
453
|
+
const parsed = ToastPayloadSchema.safeParse(msg.payload);
|
|
454
|
+
if (!parsed.success) {
|
|
455
|
+
console.error(
|
|
456
|
+
'[KBVE] Invalid toast payload from worker:',
|
|
457
|
+
parsed.error,
|
|
458
|
+
);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
_addToast(parsed.data);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (msg.type === 'toast-remove') {
|
|
465
|
+
const payload = msg.payload as { id: string } | undefined;
|
|
466
|
+
if (payload?.id) _removeToast(payload.id);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Tooltip from worker
|
|
471
|
+
if (msg.type === 'tooltip-open' && msg.payload) {
|
|
472
|
+
const parsed = TooltipPayloadSchema.safeParse(msg.payload);
|
|
473
|
+
if (!parsed.success) {
|
|
474
|
+
console.error(
|
|
475
|
+
'[KBVE] Invalid tooltip payload from worker:',
|
|
476
|
+
parsed.error,
|
|
477
|
+
);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
openTooltip(parsed.data.id);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (msg.type === 'tooltip-close') {
|
|
484
|
+
const payload = msg.payload as { id?: string } | undefined;
|
|
485
|
+
closeTooltip(payload?.id);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Modal from worker
|
|
490
|
+
if (msg.type === 'modal-open' && msg.payload) {
|
|
491
|
+
const parsed = ModalPayloadSchema.safeParse(msg.payload);
|
|
492
|
+
if (!parsed.success) {
|
|
493
|
+
console.error(
|
|
494
|
+
'[KBVE] Invalid modal payload from worker:',
|
|
495
|
+
parsed.error,
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
openModal(parsed.data.id);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (msg.type === 'modal-close') {
|
|
503
|
+
const payload = msg.payload as { id?: string } | undefined;
|
|
504
|
+
closeModal(payload?.id);
|
|
505
|
+
return;
|
|
288
506
|
}
|
|
507
|
+
|
|
508
|
+
console.warn('[KBVE] Unknown worker UI message type:', msg.type);
|
|
289
509
|
},
|
|
290
510
|
};
|
|
291
511
|
|
|
292
|
-
//
|
|
512
|
+
// --- i18n ---
|
|
293
513
|
|
|
294
514
|
const i18nStore = persistentMap<Record<string, string>>(
|
|
295
515
|
'i18n-cache',
|
|
@@ -348,7 +568,6 @@ export const i18n = {
|
|
|
348
568
|
const translations = await this.api.getTranslations(localeKeys);
|
|
349
569
|
|
|
350
570
|
for (const [key, value] of Object.entries(translations)) {
|
|
351
|
-
console.log(`[i18n.setKey] ${key} = ${value}`);
|
|
352
571
|
this.store.setKey(key, value);
|
|
353
572
|
}
|
|
354
573
|
},
|
|
@@ -362,107 +581,24 @@ function initSWComlink() {
|
|
|
362
581
|
]);
|
|
363
582
|
channel.port1.start();
|
|
364
583
|
}
|
|
365
|
-
async function initStorageComlink(opts?: {
|
|
366
|
-
workerRef?: SharedWorker;
|
|
367
|
-
workerURL?: string;
|
|
368
|
-
}): Promise<Remote<LocalStorageAPI>> {
|
|
369
|
-
let api: Remote<LocalStorageAPI> | null = null;
|
|
370
|
-
|
|
371
|
-
// 1. Try provided SharedWorker reference
|
|
372
|
-
if (opts?.workerRef) {
|
|
373
|
-
try {
|
|
374
|
-
opts.workerRef.port.start();
|
|
375
|
-
api = wrap<LocalStorageAPI>(opts.workerRef.port);
|
|
376
|
-
return await finalize(api);
|
|
377
|
-
} catch (err) {
|
|
378
|
-
console.warn('[DROID] db-worker workerRef failed:', err);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// 2. Try provided worker URL
|
|
383
|
-
if (opts?.workerURL) {
|
|
384
|
-
try {
|
|
385
|
-
const worker = new SharedWorker(opts.workerURL, { type: 'module' });
|
|
386
|
-
worker.port.start();
|
|
387
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
388
|
-
return await finalize(api);
|
|
389
|
-
} catch (err) {
|
|
390
|
-
console.warn('[DROID] db-worker workerURL failed:', err);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// 3. Vite-style TS dev import
|
|
395
|
-
try {
|
|
396
|
-
const worker = new SharedWorker(
|
|
397
|
-
new URL('./db-worker.ts', import.meta.url),
|
|
398
|
-
{ type: 'module' },
|
|
399
|
-
);
|
|
400
|
-
worker.port.start();
|
|
401
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
402
|
-
return await finalize(api);
|
|
403
|
-
} catch (err) {
|
|
404
|
-
console.warn('[DROID] db-worker .ts vite-style import failed:', err);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// 4. Same-dir .js fallback (production build output)
|
|
408
|
-
try {
|
|
409
|
-
const worker = new SharedWorker(
|
|
410
|
-
new URL('./db-worker.js', import.meta.url),
|
|
411
|
-
{ type: 'module' },
|
|
412
|
-
);
|
|
413
|
-
worker.port.start();
|
|
414
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
415
|
-
return await finalize(api);
|
|
416
|
-
} catch (err) {
|
|
417
|
-
console.warn('[DROID] db-worker.js same-dir fallback failed:', err);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// 5. Hardcoded browser-root fallback (e.g. /public)
|
|
421
|
-
try {
|
|
422
|
-
const worker = new SharedWorker('/db-worker.js', { type: 'module' });
|
|
423
|
-
worker.port.start();
|
|
424
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
425
|
-
return await finalize(api);
|
|
426
|
-
} catch (err) {
|
|
427
|
-
console.warn('[DROID] db-worker.js absolute hardcoded fallback failed:', err);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// 6. Final failure
|
|
431
|
-
console.error('[DROID] No DB Worker Comlink Initialized');
|
|
432
|
-
throw new Error('[DROID] Failed to initialize db-worker');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// Internal helper to finalize storage worker setup
|
|
437
|
-
async function finalize(api: Remote<LocalStorageAPI>): Promise<Remote<LocalStorageAPI>> {
|
|
438
|
-
const version = await api.getVersion();
|
|
439
|
-
if (version !== EXPECTED_DB_VERSION) {
|
|
440
|
-
await initializeWorkerDatabase(api, {
|
|
441
|
-
version: EXPECTED_DB_VERSION,
|
|
442
|
-
i18nPath: 'https://discord.sh/i18n/db.json',
|
|
443
|
-
locale: 'en',
|
|
444
|
-
defaults: { welcome: 'Welcome!', theme: 'dark' },
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
return api;
|
|
448
|
-
}
|
|
449
584
|
|
|
450
|
-
//
|
|
585
|
+
// --- Bridge ---
|
|
451
586
|
export function bridgeWsToDb(
|
|
452
587
|
ws: Remote<WSInstance>,
|
|
453
588
|
db: Remote<LocalStorageAPI>,
|
|
454
589
|
) {
|
|
455
|
-
const handler = proxy(async (
|
|
590
|
+
const handler = proxy(async (data: string | ArrayBuffer) => {
|
|
591
|
+
if (typeof data === 'string') return;
|
|
456
592
|
dispatchAsync(() => {
|
|
457
593
|
const key = `ws:${Date.now()}`;
|
|
458
|
-
void db.storeWsMessage(key,
|
|
594
|
+
void db.storeWsMessage(key, data);
|
|
459
595
|
});
|
|
460
596
|
});
|
|
461
597
|
|
|
462
|
-
ws.onMessage(
|
|
598
|
+
ws.onMessage(handler);
|
|
463
599
|
}
|
|
464
600
|
|
|
465
|
-
//
|
|
601
|
+
// --- MAIN ---
|
|
466
602
|
export async function main(opts?: {
|
|
467
603
|
workerURLs?: Record<string, string>;
|
|
468
604
|
workerRefs?: {
|
|
@@ -470,13 +606,15 @@ export async function main(opts?: {
|
|
|
470
606
|
dbWorker?: SharedWorker;
|
|
471
607
|
wsWorker?: SharedWorker;
|
|
472
608
|
};
|
|
609
|
+
gateway?: GatewayConfig;
|
|
610
|
+
i18nPath?: string;
|
|
611
|
+
dataPath?: string;
|
|
473
612
|
}) {
|
|
474
613
|
console.log('[DROID]: Main<T>');
|
|
475
614
|
|
|
476
615
|
if (!initialized) {
|
|
477
616
|
initialized = true;
|
|
478
617
|
|
|
479
|
-
// Attach to existing service worker (or wait for one to take control)
|
|
480
618
|
if (navigator.serviceWorker?.controller) {
|
|
481
619
|
initSWComlink();
|
|
482
620
|
} else {
|
|
@@ -487,87 +625,143 @@ export async function main(opts?: {
|
|
|
487
625
|
}
|
|
488
626
|
}
|
|
489
627
|
|
|
490
|
-
|
|
628
|
+
if (_initPromise) {
|
|
629
|
+
await _initPromise;
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
491
632
|
|
|
492
633
|
const needsInit =
|
|
493
634
|
!window.kbve?.api || !window.kbve?.i18n || !window.kbve?.uiux;
|
|
494
635
|
|
|
495
636
|
if (needsInit) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
637
|
+
// Fire welcome toast early — it only depends on $auth (set by
|
|
638
|
+
// initSupa/bootAuth in the NavBar island) and must not wait for
|
|
639
|
+
// the full worker-init chain which can hang if a SharedWorker
|
|
640
|
+
// fails to respond (Comlink calls have no timeout).
|
|
641
|
+
showWelcomeToast();
|
|
642
|
+
|
|
643
|
+
_initPromise = (async () => {
|
|
644
|
+
try {
|
|
645
|
+
console.log('[DROID] Initializing workers...');
|
|
646
|
+
|
|
647
|
+
const canvasOpts = {
|
|
648
|
+
workerRef: opts?.workerRefs?.canvasWorker,
|
|
649
|
+
workerURL: opts?.workerURLs?.['canvasWorker'],
|
|
650
|
+
};
|
|
651
|
+
const { proxy: canvas, raw: canvasRaw } =
|
|
652
|
+
await initCanvasComlink(canvasOpts);
|
|
653
|
+
|
|
654
|
+
// Store refs for downscale/upscale
|
|
655
|
+
_workerRefs.canvasWorkerRaw = canvasRaw;
|
|
656
|
+
_workerRefs.canvasWorkerProxy = canvas;
|
|
657
|
+
_workerRefs.canvasWorkerOpts = canvasOpts;
|
|
658
|
+
|
|
659
|
+
const { api, isFirstConnection: dbFirst } =
|
|
660
|
+
await initStorageComlink({
|
|
661
|
+
workerURL:
|
|
662
|
+
typeof opts?.workerURLs?.['dbWorker'] === 'string'
|
|
663
|
+
? opts.workerURLs['dbWorker']
|
|
664
|
+
: undefined,
|
|
665
|
+
workerRef: opts?.workerRefs?.dbWorker,
|
|
666
|
+
i18nPath: opts?.i18nPath,
|
|
667
|
+
dataPath: opts?.dataPath,
|
|
668
|
+
});
|
|
514
669
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const events = DroidEvents;
|
|
670
|
+
const { ws, isFirstConnection: wsFirst } = await initWsComlink({
|
|
671
|
+
workerRef: opts?.workerRefs?.wsWorker,
|
|
672
|
+
workerURL: opts?.workerURLs?.['wsWorker'],
|
|
673
|
+
});
|
|
520
674
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
675
|
+
const isLeaderTab = dbFirst && wsFirst;
|
|
676
|
+
|
|
677
|
+
console.log('[DROID] Initializing mod manager...');
|
|
678
|
+
const modResolver = (url: string) =>
|
|
679
|
+
opts?.workerURLs?.[url] ?? url;
|
|
680
|
+
_workerRefs.modInitOpts = { resolver: modResolver };
|
|
681
|
+
const mod = await getModManager(modResolver);
|
|
682
|
+
const events = DroidEvents;
|
|
683
|
+
|
|
684
|
+
for (const handle of Object.values(mod.registry)) {
|
|
685
|
+
if (typeof handle.instance.init === 'function') {
|
|
686
|
+
await handle.instance.init({
|
|
687
|
+
emitFromWorker: (msg: unknown) =>
|
|
688
|
+
uiux.emitFromWorker(msg as WorkerUIMessage),
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
events.emit('droid-mod-ready', {
|
|
692
|
+
meta: handle.meta,
|
|
693
|
+
timestamp: Date.now(),
|
|
525
694
|
});
|
|
526
695
|
}
|
|
527
|
-
console.log('[Event] -> Fire Mod Ready');
|
|
528
|
-
events.emit('droid-mod-ready', {
|
|
529
|
-
meta: handle.meta,
|
|
530
|
-
timestamp: Date.now(),
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
bridgeWsToDb(ws, api);
|
|
535
696
|
|
|
536
|
-
|
|
537
|
-
i18n.api = api;
|
|
538
|
-
i18n.ready = i18n.hydrateLocale('en');
|
|
697
|
+
bridgeWsToDb(ws, api);
|
|
539
698
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
i18n,
|
|
544
|
-
uiux: { ...uiux, worker: canvas },
|
|
545
|
-
ws,
|
|
546
|
-
data,
|
|
547
|
-
mod,
|
|
548
|
-
events,
|
|
549
|
-
};
|
|
699
|
+
const data = scopeData;
|
|
700
|
+
i18n.api = api;
|
|
701
|
+
i18n.ready = i18n.hydrateLocale('en');
|
|
550
702
|
|
|
551
|
-
|
|
703
|
+
// Initialize SupabaseGateway if config is provided
|
|
704
|
+
let gateway: SupabaseGateway | undefined;
|
|
705
|
+
if (opts?.gateway) {
|
|
706
|
+
console.log('[DROID] Initializing SupabaseGateway...');
|
|
707
|
+
_workerRefs.gatewayConfig = opts.gateway;
|
|
708
|
+
gateway = new SupabaseGateway(opts.gateway);
|
|
709
|
+
}
|
|
552
710
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
711
|
+
const overlay = new OverlayManager({
|
|
712
|
+
preferredPath: 'auto',
|
|
713
|
+
canvasWorker: canvas,
|
|
714
|
+
});
|
|
556
715
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
716
|
+
window.kbve = {
|
|
717
|
+
...(window.kbve || {}),
|
|
718
|
+
api,
|
|
719
|
+
i18n,
|
|
720
|
+
uiux: { ...uiux, worker: canvas },
|
|
721
|
+
ws,
|
|
722
|
+
data,
|
|
723
|
+
mod,
|
|
724
|
+
events,
|
|
725
|
+
overlay,
|
|
726
|
+
downscale,
|
|
727
|
+
upscale,
|
|
728
|
+
scaleLevel: getScaleLevel,
|
|
729
|
+
...(gateway ? { gateway } : {}),
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// Sync theme CSS vars to Dexie for worker access
|
|
733
|
+
observeThemeChanges(api);
|
|
734
|
+
|
|
735
|
+
await i18n.ready;
|
|
736
|
+
|
|
737
|
+
window.kbve.events.emit('droid-ready', {
|
|
562
738
|
timestamp: Date.now(),
|
|
563
739
|
});
|
|
564
|
-
});
|
|
565
740
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
741
|
+
document.addEventListener('astro:page-load', () => {
|
|
742
|
+
console.debug(
|
|
743
|
+
'[KBVE] Re-dispatched droid-ready after astro:page-load',
|
|
744
|
+
);
|
|
745
|
+
window.kbve?.events.emit('droid-ready', {
|
|
746
|
+
timestamp: Date.now(),
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
if (isLeaderTab) {
|
|
751
|
+
events.emit('droid-first-connect', {
|
|
752
|
+
timestamp: Date.now(),
|
|
753
|
+
workersFirst: { db: dbFirst, ws: wsFirst },
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
console.log('[KBVE] Global API ready');
|
|
758
|
+
} catch (err) {
|
|
759
|
+
_initPromise = null;
|
|
760
|
+
console.error('[DROID] Initialization error:', err);
|
|
761
|
+
throw err;
|
|
762
|
+
}
|
|
763
|
+
})();
|
|
764
|
+
await _initPromise;
|
|
571
765
|
} else {
|
|
572
766
|
console.log('[KBVE] Already initialized');
|
|
573
767
|
}
|