@kbve/droid 0.0.6 → 0.1.2
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/droid.mjs +2312 -74
- package/index.d.ts +32 -0
- package/lib/agents/api-types.d.ts +121 -0
- package/lib/agents/api-types.d.ts.map +1 -0
- package/lib/agents/auth/init.d.ts +8 -0
- package/lib/agents/auth/init.d.ts.map +1 -0
- package/lib/agents/cache.d.ts +9 -0
- package/lib/agents/cache.d.ts.map +1 -0
- package/lib/agents/client/callGuildVault.d.ts +3 -0
- package/lib/agents/client/callGuildVault.d.ts.map +1 -0
- package/lib/agents/client/callJson.d.ts +3 -0
- package/lib/agents/client/callJson.d.ts.map +1 -0
- package/lib/agents/config.d.ts +36 -0
- package/lib/agents/config.d.ts.map +1 -0
- package/lib/agents/constants.d.ts +11 -0
- package/lib/agents/constants.d.ts.map +1 -0
- package/lib/agents/createAgents.d.ts +4 -0
- package/lib/agents/createAgents.d.ts.map +1 -0
- package/lib/agents/ctx.d.ts +46 -0
- package/lib/agents/ctx.d.ts.map +1 -0
- package/lib/agents/discord/bot.d.ts +21 -0
- package/lib/agents/discord/bot.d.ts.map +1 -0
- package/lib/agents/discord/guilds.d.ts +8 -0
- package/lib/agents/discord/guilds.d.ts.map +1 -0
- package/lib/agents/formDrafts.d.ts +5 -0
- package/lib/agents/formDrafts.d.ts.map +1 -0
- package/lib/agents/generated/agents-schema.d.ts +73 -0
- package/lib/agents/generated/agents-schema.d.ts.map +1 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts +71 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts.map +1 -0
- package/lib/agents/github/backfill.d.ts +23 -0
- package/lib/agents/github/backfill.d.ts.map +1 -0
- package/lib/agents/github/events.d.ts +10 -0
- package/lib/agents/github/events.d.ts.map +1 -0
- package/lib/agents/github/pat.d.ts +13 -0
- package/lib/agents/github/pat.d.ts.map +1 -0
- package/lib/agents/github/repoConfig.d.ts +25 -0
- package/lib/agents/github/repoConfig.d.ts.map +1 -0
- package/lib/agents/github/tokens.d.ts +22 -0
- package/lib/agents/github/tokens.d.ts.map +1 -0
- package/lib/agents/github/webhook.d.ts +37 -0
- package/lib/agents/github/webhook.d.ts.map +1 -0
- package/lib/agents/index.d.ts +8 -0
- package/lib/agents/index.d.ts.map +1 -0
- package/lib/agents/state/atoms.d.ts +122 -0
- package/lib/agents/state/atoms.d.ts.map +1 -0
- package/lib/agents/types.d.ts +65 -0
- package/lib/agents/types.d.ts.map +1 -0
- 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 +5 -302
- 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 +13 -3636
- 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 +105 -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 +23 -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 +25 -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 +10 -0
- package/lib/workers/ws-worker.d.ts.map +1 -0
- package/lib/workers/ws-worker.js +5 -273
- package/main.js +5711 -0
- package/package.json +18 -6
- package/src/index.ts +41 -13
- package/src/lib/agents/api-types.ts +170 -0
- package/src/lib/agents/auth/init.ts +200 -0
- package/src/lib/agents/cache.ts +127 -0
- package/src/lib/agents/client/callGuildVault.ts +71 -0
- package/src/lib/agents/client/callJson.ts +64 -0
- package/src/lib/agents/config.ts +83 -0
- package/src/lib/agents/constants.ts +11 -0
- package/src/lib/agents/createAgents.ts +62 -0
- package/src/lib/agents/ctx.ts +63 -0
- package/src/lib/agents/discord/bot.ts +169 -0
- package/src/lib/agents/discord/guilds.ts +106 -0
- package/src/lib/agents/formDrafts.ts +55 -0
- package/src/lib/agents/generated/agents-schema.ts +155 -0
- package/src/lib/agents/generated/discordsh-agents-schema.ts +96 -0
- package/src/lib/agents/github/backfill.ts +126 -0
- package/src/lib/agents/github/events.ts +131 -0
- package/src/lib/agents/github/pat.ts +150 -0
- package/src/lib/agents/github/repoConfig.ts +240 -0
- package/src/lib/agents/github/tokens.ts +222 -0
- package/src/lib/agents/github/webhook.ts +349 -0
- package/src/lib/agents/index.ts +35 -0
- package/src/lib/agents/state/atoms.ts +122 -0
- package/src/lib/agents/types.ts +83 -0
- package/src/lib/api.ts +71 -0
- package/src/lib/droid.spec.ts +14 -19
- package/src/lib/droid.ts +48 -4
- package/src/lib/gateway/SupabaseGateway.ts +236 -0
- package/src/lib/gateway/WorkerCommunication.ts +132 -0
- package/src/lib/gateway/WorkerPool.ts +304 -0
- package/src/lib/gateway/capabilities.spec.ts +144 -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 +139 -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 +73 -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 +148 -0
- package/src/lib/state/bento.spec.ts +76 -0
- package/src/lib/state/bento.ts +51 -0
- package/src/lib/state/index.ts +98 -0
- package/src/lib/state/overlay-manager.ts +125 -0
- package/src/lib/state/profile-sync.ts +214 -0
- package/src/lib/state/profile.ts +155 -0
- package/src/lib/state/router.ts +24 -0
- package/src/lib/state/staff.ts +128 -0
- package/src/lib/state/sync-bus.ts +109 -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.spec.ts +101 -0
- package/src/lib/state/welcome-toast.ts +76 -0
- package/src/lib/sw-recovery.spec.ts +165 -0
- package/src/lib/sw-recovery.ts +141 -0
- package/src/lib/types/bento.spec.ts +116 -0
- package/src/lib/types/bento.ts +94 -55
- package/src/lib/types/event-schemas.spec.ts +120 -0
- package/src/lib/types/event-types.spec.ts +96 -0
- package/src/lib/types/event-types.ts +113 -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 +64 -37
- package/src/lib/workers/db-worker.ts +105 -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 +506 -327
- package/src/lib/workers/supabase-db-worker.ts +243 -0
- package/src/lib/workers/supabase-shared-worker.ts +607 -0
- package/src/lib/workers/validate.ts +64 -0
- package/src/lib/workers/ws-worker.spec.ts +41 -0
- package/src/lib/workers/ws-worker.ts +255 -31
- 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 +2 -1125
- package/comlink.js +0 -253
- package/index.js +0 -2972
- package/reference.js +0 -259
- package/src/types.d.ts +0 -31
package/src/lib/workers/main.ts
CHANGED
|
@@ -1,119 +1,326 @@
|
|
|
1
|
-
import { wrap
|
|
1
|
+
import { wrap } 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;
|
|
43
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 };
|
|
284
|
+
}
|
|
285
|
+
|
|
44
286
|
async function initWsComlink(opts?: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}): Promise<Remote<WSInstance
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 };
|
|
295
|
+
}
|
|
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 };
|
|
112
303
|
}
|
|
113
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
|
+
}
|
|
114
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;
|
|
288
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);
|
|
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,8 @@ 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
584
|
|
|
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
|
-
|
|
450
|
-
// * Bridge
|
|
451
|
-
export function bridgeWsToDb(
|
|
452
|
-
ws: Remote<WSInstance>,
|
|
453
|
-
db: Remote<LocalStorageAPI>,
|
|
454
|
-
) {
|
|
455
|
-
const handler = proxy(async (buf: ArrayBuffer) => {
|
|
456
|
-
dispatchAsync(() => {
|
|
457
|
-
const key = `ws:${Date.now()}`;
|
|
458
|
-
void db.storeWsMessage(key, buf);
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
ws.onMessage(transfer(handler, [0]));
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// * MAIN
|
|
585
|
+
// --- MAIN ---
|
|
466
586
|
export async function main(opts?: {
|
|
467
587
|
workerURLs?: Record<string, string>;
|
|
468
588
|
workerRefs?: {
|
|
@@ -470,13 +590,15 @@ export async function main(opts?: {
|
|
|
470
590
|
dbWorker?: SharedWorker;
|
|
471
591
|
wsWorker?: SharedWorker;
|
|
472
592
|
};
|
|
593
|
+
gateway?: GatewayConfig;
|
|
594
|
+
i18nPath?: string;
|
|
595
|
+
dataPath?: string;
|
|
473
596
|
}) {
|
|
474
597
|
console.log('[DROID]: Main<T>');
|
|
475
598
|
|
|
476
599
|
if (!initialized) {
|
|
477
600
|
initialized = true;
|
|
478
601
|
|
|
479
|
-
// Attach to existing service worker (or wait for one to take control)
|
|
480
602
|
if (navigator.serviceWorker?.controller) {
|
|
481
603
|
initSWComlink();
|
|
482
604
|
} else {
|
|
@@ -487,87 +609,144 @@ export async function main(opts?: {
|
|
|
487
609
|
}
|
|
488
610
|
}
|
|
489
611
|
|
|
490
|
-
|
|
612
|
+
if (_initPromise) {
|
|
613
|
+
await _initPromise;
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
491
616
|
|
|
492
617
|
const needsInit =
|
|
493
618
|
!window.kbve?.api || !window.kbve?.i18n || !window.kbve?.uiux;
|
|
494
619
|
|
|
495
620
|
if (needsInit) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
621
|
+
// Fire welcome toast early — it only depends on $auth (set by
|
|
622
|
+
// initSupa/bootAuth in the NavBar island) and must not wait for
|
|
623
|
+
// the full worker-init chain which can hang if a SharedWorker
|
|
624
|
+
// fails to respond (Comlink calls have no timeout).
|
|
625
|
+
showWelcomeToast();
|
|
626
|
+
|
|
627
|
+
_initPromise = (async () => {
|
|
628
|
+
try {
|
|
629
|
+
console.log('[DROID] Initializing workers...');
|
|
630
|
+
|
|
631
|
+
const canvasOpts = {
|
|
632
|
+
workerRef: opts?.workerRefs?.canvasWorker,
|
|
633
|
+
workerURL: opts?.workerURLs?.['canvasWorker'],
|
|
634
|
+
};
|
|
635
|
+
const { proxy: canvas, raw: canvasRaw } =
|
|
636
|
+
await initCanvasComlink(canvasOpts);
|
|
637
|
+
|
|
638
|
+
// Store refs for downscale/upscale
|
|
639
|
+
_workerRefs.canvasWorkerRaw = canvasRaw;
|
|
640
|
+
_workerRefs.canvasWorkerProxy = canvas;
|
|
641
|
+
_workerRefs.canvasWorkerOpts = canvasOpts;
|
|
642
|
+
|
|
643
|
+
const { api, isFirstConnection: dbFirst } =
|
|
644
|
+
await initStorageComlink({
|
|
645
|
+
workerURL:
|
|
646
|
+
typeof opts?.workerURLs?.['dbWorker'] === 'string'
|
|
647
|
+
? opts.workerURLs['dbWorker']
|
|
648
|
+
: undefined,
|
|
649
|
+
workerRef: opts?.workerRefs?.dbWorker,
|
|
650
|
+
i18nPath: opts?.i18nPath,
|
|
651
|
+
dataPath: opts?.dataPath,
|
|
652
|
+
});
|
|
514
653
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const events = DroidEvents;
|
|
654
|
+
const { ws, isFirstConnection: wsFirst } = await initWsComlink({
|
|
655
|
+
workerRef: opts?.workerRefs?.wsWorker,
|
|
656
|
+
workerURL: opts?.workerURLs?.['wsWorker'],
|
|
657
|
+
});
|
|
520
658
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
659
|
+
const isLeaderTab = dbFirst && wsFirst;
|
|
660
|
+
|
|
661
|
+
console.log('[DROID] Initializing mod manager...');
|
|
662
|
+
const modResolver = (url: string) =>
|
|
663
|
+
opts?.workerURLs?.[url] ?? url;
|
|
664
|
+
_workerRefs.modInitOpts = { resolver: modResolver };
|
|
665
|
+
const mod = await getModManager(modResolver);
|
|
666
|
+
const events = DroidEvents;
|
|
667
|
+
|
|
668
|
+
for (const handle of Object.values(mod.registry)) {
|
|
669
|
+
if (typeof handle.instance.init === 'function') {
|
|
670
|
+
await handle.instance.init({
|
|
671
|
+
emitFromWorker: (msg: unknown) =>
|
|
672
|
+
uiux.emitFromWorker(msg as WorkerUIMessage),
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
events.emit('droid-mod-ready', {
|
|
676
|
+
meta: handle.meta,
|
|
677
|
+
timestamp: Date.now(),
|
|
525
678
|
});
|
|
526
679
|
}
|
|
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
680
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
i18n.ready = i18n.hydrateLocale('en');
|
|
681
|
+
// ws-worker → db-worker storage now happens directly via
|
|
682
|
+
// BroadcastChannel ('kbve_ws_data'), no main-thread bridge needed.
|
|
539
683
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
i18n,
|
|
544
|
-
uiux: { ...uiux, worker: canvas },
|
|
545
|
-
ws,
|
|
546
|
-
data,
|
|
547
|
-
mod,
|
|
548
|
-
events,
|
|
549
|
-
};
|
|
684
|
+
const data = scopeData;
|
|
685
|
+
i18n.api = api;
|
|
686
|
+
i18n.ready = i18n.hydrateLocale('en');
|
|
550
687
|
|
|
551
|
-
|
|
688
|
+
// Initialize SupabaseGateway if config is provided
|
|
689
|
+
let gateway: SupabaseGateway | undefined;
|
|
690
|
+
if (opts?.gateway) {
|
|
691
|
+
console.log('[DROID] Initializing SupabaseGateway...');
|
|
692
|
+
_workerRefs.gatewayConfig = opts.gateway;
|
|
693
|
+
gateway = new SupabaseGateway(opts.gateway);
|
|
694
|
+
}
|
|
552
695
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
696
|
+
const overlay = new OverlayManager({
|
|
697
|
+
preferredPath: 'auto',
|
|
698
|
+
canvasWorker: canvas,
|
|
699
|
+
});
|
|
556
700
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
701
|
+
window.kbve = {
|
|
702
|
+
...(window.kbve || {}),
|
|
703
|
+
api,
|
|
704
|
+
i18n,
|
|
705
|
+
uiux: { ...uiux, worker: canvas },
|
|
706
|
+
ws,
|
|
707
|
+
data,
|
|
708
|
+
mod,
|
|
709
|
+
events,
|
|
710
|
+
overlay,
|
|
711
|
+
downscale,
|
|
712
|
+
upscale,
|
|
713
|
+
scaleLevel: getScaleLevel,
|
|
714
|
+
...(gateway ? { gateway } : {}),
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
// Sync theme CSS vars to Dexie for worker access
|
|
718
|
+
observeThemeChanges(api);
|
|
719
|
+
|
|
720
|
+
await i18n.ready;
|
|
721
|
+
|
|
722
|
+
window.kbve.events.emit('droid-ready', {
|
|
562
723
|
timestamp: Date.now(),
|
|
563
724
|
});
|
|
564
|
-
});
|
|
565
725
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
726
|
+
document.addEventListener('astro:page-load', () => {
|
|
727
|
+
console.debug(
|
|
728
|
+
'[KBVE] Re-dispatched droid-ready after astro:page-load',
|
|
729
|
+
);
|
|
730
|
+
window.kbve?.events.emit('droid-ready', {
|
|
731
|
+
timestamp: Date.now(),
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
if (isLeaderTab) {
|
|
736
|
+
events.emit('droid-first-connect', {
|
|
737
|
+
timestamp: Date.now(),
|
|
738
|
+
workersFirst: { db: dbFirst, ws: wsFirst },
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
console.log('[KBVE] Global API ready');
|
|
743
|
+
} catch (err) {
|
|
744
|
+
_initPromise = null;
|
|
745
|
+
console.error('[DROID] Initialization error:', err);
|
|
746
|
+
throw err;
|
|
747
|
+
}
|
|
748
|
+
})();
|
|
749
|
+
await _initPromise;
|
|
571
750
|
} else {
|
|
572
751
|
console.log('[KBVE] Already initialized');
|
|
573
752
|
}
|