@kbve/droid 0.0.5 → 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/README.md +3 -1
- 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 +509 -272
- 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 -1089
- package/src/types.d.ts +0 -31
package/src/lib/workers/main.ts
CHANGED
|
@@ -1,107 +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
|
-
function deepProxy<T>(obj: T): T {
|
|
32
|
-
if (typeof obj === 'function') return proxy(obj) as T;
|
|
213
|
+
// --- Worker Init Helpers ---
|
|
33
214
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|
|
44
285
|
|
|
45
286
|
async function initWsComlink(opts?: {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}): Promise<Remote<WSInstance
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
type: 'module',
|
|
55
|
-
});
|
|
56
|
-
worker.port.start();
|
|
57
|
-
api = wrap<WSInstance>(worker.port);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
console.warn('[DROID] ws-worker import.meta fallback failed:', err);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 2. Try hardcoded path
|
|
63
|
-
if (!api) {
|
|
64
|
-
try {
|
|
65
|
-
const worker = new SharedWorker('/ws-worker.js', { type: 'module' });
|
|
66
|
-
worker.port.start();
|
|
67
|
-
api = wrap<WSInstance>(worker.port);
|
|
68
|
-
} catch (err) {
|
|
69
|
-
console.warn('[DROID] ws-worker hardcoded fallback failed:', err);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 3. Try provided SharedWorker reference
|
|
74
|
-
if (!api && opts?.workerRef) {
|
|
75
|
-
try {
|
|
76
|
-
opts.workerRef.port.start();
|
|
77
|
-
api = wrap<WSInstance>(opts.workerRef.port);
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.warn('[DROID] ws-worker workerRef failed:', err);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 4. Try provided URL
|
|
84
|
-
if (!api && opts?.workerURL) {
|
|
85
|
-
try {
|
|
86
|
-
const worker = new SharedWorker(opts.workerURL, { type: 'module' });
|
|
87
|
-
worker.port.start();
|
|
88
|
-
api = wrap<WSInstance>(worker.port);
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.warn('[DROID] ws-worker workerURL failed:', err);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 5. Final failure
|
|
95
|
-
if (!api) {
|
|
96
|
-
console.error('[DROID] No WS Worker Comlink Initialized');
|
|
97
|
-
throw new Error('[DROID] Failed to initialize ws-worker');
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return api;
|
|
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 };
|
|
101
295
|
}
|
|
102
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
|
+
}
|
|
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 ---
|
|
103
323
|
|
|
104
|
-
// * UIUX
|
|
105
324
|
const uiuxState = persistentMap<{
|
|
106
325
|
panelManager: Record<
|
|
107
326
|
PanelId,
|
|
@@ -111,7 +330,7 @@ const uiuxState = persistentMap<{
|
|
|
111
330
|
}
|
|
112
331
|
>;
|
|
113
332
|
themeManager: { theme: 'light' | 'dark' | 'auto' };
|
|
114
|
-
toastManager: Record<string,
|
|
333
|
+
toastManager: Record<string, unknown>;
|
|
115
334
|
scrollY: number;
|
|
116
335
|
}>(
|
|
117
336
|
'uiux-state',
|
|
@@ -132,65 +351,20 @@ const uiuxState = persistentMap<{
|
|
|
132
351
|
},
|
|
133
352
|
);
|
|
134
353
|
|
|
135
|
-
async function initCanvasComlink(opts?: {
|
|
136
|
-
workerRef?: Worker;
|
|
137
|
-
workerURL?: string;
|
|
138
|
-
}): Promise<Remote<CanvasWorkerAPI>> {
|
|
139
|
-
// 1. Try Vite-style import resolution
|
|
140
|
-
try {
|
|
141
|
-
const worker = new Worker(
|
|
142
|
-
new URL('./canvas-worker.ts', import.meta.url),
|
|
143
|
-
{ type: 'module' },
|
|
144
|
-
);
|
|
145
|
-
return wrap<CanvasWorkerAPI>(worker);
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.warn('[DROID] Vite-style canvas-worker import failed:', err);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 2. Try hardcoded path fallback
|
|
151
|
-
try {
|
|
152
|
-
const worker = new Worker('./canvas-worker.js', { type: 'module' });
|
|
153
|
-
return wrap<CanvasWorkerAPI>(worker);
|
|
154
|
-
} catch (err) {
|
|
155
|
-
console.warn('[DROID] Fallback /canvas-worker.js failed:', err);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// 3. Try direct Worker instance
|
|
159
|
-
if (opts?.workerRef) {
|
|
160
|
-
try {
|
|
161
|
-
return wrap<CanvasWorkerAPI>(opts.workerRef);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.warn('[DROID] Provided workerRef failed:', err);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 4. Try provided URL
|
|
168
|
-
if (opts?.workerURL) {
|
|
169
|
-
try {
|
|
170
|
-
const worker = new Worker(opts.workerURL, { type: 'module' });
|
|
171
|
-
return wrap<CanvasWorkerAPI>(worker);
|
|
172
|
-
} catch (err) {
|
|
173
|
-
console.warn('[DROID] Provided workerURL failed:', err);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// 5. Failure
|
|
178
|
-
console.error('[DROID] No Canvas Comlink Initialized');
|
|
179
|
-
throw new Error('[DROID] Failed to initialize canvas worker');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
354
|
export const uiux = {
|
|
183
355
|
state: uiuxState,
|
|
184
356
|
openPanel(id: PanelId, payload?: PanelPayload) {
|
|
185
357
|
const panels = { ...uiuxState.get().panelManager };
|
|
186
358
|
panels[id] = { open: true, payload };
|
|
187
359
|
uiuxState.setKey('panelManager', panels);
|
|
360
|
+
DroidEvents.emit('panel-open', { id, payload });
|
|
188
361
|
},
|
|
189
362
|
|
|
190
363
|
closePanel(id: PanelId) {
|
|
191
364
|
const panels = { ...uiuxState.get().panelManager };
|
|
192
365
|
panels[id] = { open: false, payload: undefined };
|
|
193
366
|
uiuxState.setKey('panelManager', panels);
|
|
367
|
+
DroidEvents.emit('panel-close', { id });
|
|
194
368
|
},
|
|
195
369
|
|
|
196
370
|
togglePanel(id: PanelId, payload?: PanelPayload) {
|
|
@@ -198,44 +372,61 @@ export const uiux = {
|
|
|
198
372
|
const isOpen = panels[id]?.open ?? false;
|
|
199
373
|
panels[id] = { open: !isOpen, payload: !isOpen ? payload : undefined };
|
|
200
374
|
uiuxState.setKey('panelManager', panels);
|
|
375
|
+
if (!isOpen) {
|
|
376
|
+
DroidEvents.emit('panel-open', { id, payload });
|
|
377
|
+
} else {
|
|
378
|
+
DroidEvents.emit('panel-close', { id });
|
|
379
|
+
}
|
|
201
380
|
},
|
|
202
381
|
|
|
203
382
|
setTheme(theme: 'light' | 'dark' | 'auto') {
|
|
204
383
|
uiuxState.setKey('themeManager', { theme });
|
|
205
384
|
},
|
|
206
385
|
|
|
207
|
-
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
|
+
);
|
|
208
391
|
const toasts = { ...uiuxState.get().toastManager, [id]: data };
|
|
209
392
|
uiuxState.setKey('toastManager', toasts);
|
|
210
393
|
},
|
|
211
394
|
|
|
395
|
+
/** @deprecated Use removeToast() from '@kbve/droid' state exports instead. */
|
|
212
396
|
removeToast(id: string) {
|
|
397
|
+
console.warn(
|
|
398
|
+
'[KBVE] uiux.removeToast is deprecated. Use removeToast() from @kbve/droid.',
|
|
399
|
+
);
|
|
213
400
|
const toasts = { ...uiuxState.get().toastManager };
|
|
214
401
|
delete toasts[id];
|
|
215
402
|
uiuxState.setKey('toastManager', toasts);
|
|
216
403
|
},
|
|
404
|
+
|
|
217
405
|
async dispatchCanvasRequest(
|
|
218
406
|
panelId: PanelId,
|
|
219
407
|
canvasEl: HTMLCanvasElement,
|
|
220
408
|
mode: 'static' | 'animated' | 'dynamic' = 'animated',
|
|
221
409
|
) {
|
|
222
410
|
const offscreen = canvasEl.transferControlToOffscreen();
|
|
223
|
-
|
|
411
|
+
const worker = (window.kbve?.uiux as Record<string, unknown>)?.[
|
|
412
|
+
'worker'
|
|
413
|
+
] as Remote<CanvasWorkerAPI> | undefined;
|
|
414
|
+
await worker?.bindCanvas(panelId, offscreen, mode);
|
|
224
415
|
},
|
|
225
416
|
|
|
226
417
|
closeAllPanels() {
|
|
227
418
|
const panels = { ...uiuxState.get().panelManager };
|
|
228
|
-
console.log('error panel is closing');
|
|
229
|
-
|
|
230
419
|
for (const id of Object.keys(panels) as Array<PanelId>) {
|
|
231
420
|
panels[id] = { open: false, payload: undefined };
|
|
232
421
|
}
|
|
233
|
-
|
|
234
422
|
uiuxState.setKey('panelManager', panels);
|
|
235
423
|
},
|
|
236
424
|
|
|
237
|
-
emitFromWorker(msg:
|
|
238
|
-
|
|
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;
|
|
239
430
|
dispatchAsync(() => {
|
|
240
431
|
const target = document.getElementById('bento-grid-inject');
|
|
241
432
|
if (!target) {
|
|
@@ -245,20 +436,80 @@ export const uiux = {
|
|
|
245
436
|
return;
|
|
246
437
|
}
|
|
247
438
|
|
|
248
|
-
const el = renderVNode(
|
|
439
|
+
const el = renderVNode(vnode);
|
|
249
440
|
el.classList.add('animate-fade-in');
|
|
250
|
-
if (
|
|
251
|
-
const existing = document.getElementById(
|
|
441
|
+
if (vnode.id) {
|
|
442
|
+
const existing = document.getElementById(vnode.id);
|
|
252
443
|
if (existing) existing.remove();
|
|
253
444
|
}
|
|
254
445
|
|
|
255
446
|
target.appendChild(el);
|
|
256
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;
|
|
257
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;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.warn('[KBVE] Unknown worker UI message type:', msg.type);
|
|
258
509
|
},
|
|
259
510
|
};
|
|
260
511
|
|
|
261
|
-
//
|
|
512
|
+
// --- i18n ---
|
|
262
513
|
|
|
263
514
|
const i18nStore = persistentMap<Record<string, string>>(
|
|
264
515
|
'i18n-cache',
|
|
@@ -317,7 +568,6 @@ export const i18n = {
|
|
|
317
568
|
const translations = await this.api.getTranslations(localeKeys);
|
|
318
569
|
|
|
319
570
|
for (const [key, value] of Object.entries(translations)) {
|
|
320
|
-
console.log(`[i18n.setKey] ${key} = ${value}`);
|
|
321
571
|
this.store.setKey(key, value);
|
|
322
572
|
}
|
|
323
573
|
},
|
|
@@ -332,94 +582,23 @@ function initSWComlink() {
|
|
|
332
582
|
channel.port1.start();
|
|
333
583
|
}
|
|
334
584
|
|
|
335
|
-
|
|
336
|
-
workerRef?: SharedWorker;
|
|
337
|
-
workerURL?: string;
|
|
338
|
-
}): Promise<Remote<LocalStorageAPI>> {
|
|
339
|
-
let api: Remote<LocalStorageAPI> | null = null;
|
|
340
|
-
// 1. Try Vite-style import
|
|
341
|
-
try {
|
|
342
|
-
const worker = new SharedWorker(
|
|
343
|
-
new URL('./db-worker.ts', import.meta.url),
|
|
344
|
-
{
|
|
345
|
-
type: 'module',
|
|
346
|
-
},
|
|
347
|
-
);
|
|
348
|
-
worker.port.start();
|
|
349
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
350
|
-
} catch (err) {
|
|
351
|
-
console.warn('[DROID] db-worker import.meta fallback failed:', err);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// 2. Try hardcoded path
|
|
355
|
-
if (!api) {
|
|
356
|
-
try {
|
|
357
|
-
const worker = new SharedWorker('/db-worker.js', {
|
|
358
|
-
type: 'module',
|
|
359
|
-
});
|
|
360
|
-
worker.port.start();
|
|
361
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
362
|
-
} catch (err) {
|
|
363
|
-
console.warn('[DROID] db-worker hardcoded fallback failed:', err);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// 3. Try provided SharedWorker reference
|
|
368
|
-
if (!api && opts?.workerRef) {
|
|
369
|
-
try {
|
|
370
|
-
opts.workerRef.port.start();
|
|
371
|
-
api = wrap<LocalStorageAPI>(opts.workerRef.port);
|
|
372
|
-
} catch (err) {
|
|
373
|
-
console.warn('[DROID] db-worker workerRef failed:', err);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// 4. Try provided URL
|
|
378
|
-
if (!api && opts?.workerURL) {
|
|
379
|
-
try {
|
|
380
|
-
const worker = new SharedWorker(opts.workerURL, { type: 'module' });
|
|
381
|
-
worker.port.start();
|
|
382
|
-
api = wrap<LocalStorageAPI>(worker.port);
|
|
383
|
-
} catch (err) {
|
|
384
|
-
console.warn('[DROID] db-worker workerURL failed:', err);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// 5. Final failure
|
|
389
|
-
if (!api) {
|
|
390
|
-
console.error('[DROID] No DB Worker Comlink Initialized');
|
|
391
|
-
throw new Error('[DROID] Failed to initialize db-worker');
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const version = await api.getVersion();
|
|
395
|
-
if (version !== EXPECTED_DB_VERSION) {
|
|
396
|
-
await initializeWorkerDatabase(api, {
|
|
397
|
-
version: EXPECTED_DB_VERSION,
|
|
398
|
-
i18nPath: 'https://discord.sh/i18n/db.json',
|
|
399
|
-
locale: 'en',
|
|
400
|
-
defaults: { welcome: 'Welcome!', theme: 'dark' },
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return api;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// * Bridge
|
|
585
|
+
// --- Bridge ---
|
|
408
586
|
export function bridgeWsToDb(
|
|
409
587
|
ws: Remote<WSInstance>,
|
|
410
588
|
db: Remote<LocalStorageAPI>,
|
|
411
589
|
) {
|
|
412
|
-
const handler = proxy(async (
|
|
590
|
+
const handler = proxy(async (data: string | ArrayBuffer) => {
|
|
591
|
+
if (typeof data === 'string') return;
|
|
413
592
|
dispatchAsync(() => {
|
|
414
593
|
const key = `ws:${Date.now()}`;
|
|
415
|
-
void db.storeWsMessage(key,
|
|
594
|
+
void db.storeWsMessage(key, data);
|
|
416
595
|
});
|
|
417
596
|
});
|
|
418
597
|
|
|
419
|
-
ws.onMessage(
|
|
598
|
+
ws.onMessage(handler);
|
|
420
599
|
}
|
|
421
600
|
|
|
422
|
-
//
|
|
601
|
+
// --- MAIN ---
|
|
423
602
|
export async function main(opts?: {
|
|
424
603
|
workerURLs?: Record<string, string>;
|
|
425
604
|
workerRefs?: {
|
|
@@ -427,13 +606,15 @@ export async function main(opts?: {
|
|
|
427
606
|
dbWorker?: SharedWorker;
|
|
428
607
|
wsWorker?: SharedWorker;
|
|
429
608
|
};
|
|
609
|
+
gateway?: GatewayConfig;
|
|
610
|
+
i18nPath?: string;
|
|
611
|
+
dataPath?: string;
|
|
430
612
|
}) {
|
|
431
613
|
console.log('[DROID]: Main<T>');
|
|
432
614
|
|
|
433
615
|
if (!initialized) {
|
|
434
616
|
initialized = true;
|
|
435
617
|
|
|
436
|
-
// Attach to existing service worker (or wait for one to take control)
|
|
437
618
|
if (navigator.serviceWorker?.controller) {
|
|
438
619
|
initSWComlink();
|
|
439
620
|
} else {
|
|
@@ -444,87 +625,143 @@ export async function main(opts?: {
|
|
|
444
625
|
}
|
|
445
626
|
}
|
|
446
627
|
|
|
447
|
-
|
|
628
|
+
if (_initPromise) {
|
|
629
|
+
await _initPromise;
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
448
632
|
|
|
449
633
|
const needsInit =
|
|
450
634
|
!window.kbve?.api || !window.kbve?.i18n || !window.kbve?.uiux;
|
|
451
635
|
|
|
452
636
|
if (needsInit) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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
|
+
});
|
|
471
669
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const events = DroidEvents;
|
|
670
|
+
const { ws, isFirstConnection: wsFirst } = await initWsComlink({
|
|
671
|
+
workerRef: opts?.workerRefs?.wsWorker,
|
|
672
|
+
workerURL: opts?.workerURLs?.['wsWorker'],
|
|
673
|
+
});
|
|
477
674
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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(),
|
|
482
694
|
});
|
|
483
695
|
}
|
|
484
|
-
console.log('[Event] -> Fire Mod Ready');
|
|
485
|
-
events.emit('droid-mod-ready', {
|
|
486
|
-
meta: handle.meta,
|
|
487
|
-
timestamp: Date.now(),
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
696
|
|
|
491
|
-
|
|
697
|
+
bridgeWsToDb(ws, api);
|
|
492
698
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
699
|
+
const data = scopeData;
|
|
700
|
+
i18n.api = api;
|
|
701
|
+
i18n.ready = i18n.hydrateLocale('en');
|
|
496
702
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
mod,
|
|
505
|
-
events,
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
await i18n.ready;
|
|
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
|
+
}
|
|
509
710
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
711
|
+
const overlay = new OverlayManager({
|
|
712
|
+
preferredPath: 'auto',
|
|
713
|
+
canvasWorker: canvas,
|
|
714
|
+
});
|
|
513
715
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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', {
|
|
519
738
|
timestamp: Date.now(),
|
|
520
739
|
});
|
|
521
|
-
});
|
|
522
740
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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;
|
|
528
765
|
} else {
|
|
529
766
|
console.log('[KBVE] Already initialized');
|
|
530
767
|
}
|