@xnetjs/react 0.0.2 → 0.0.3
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 +206 -2
- package/dist/chunk-4ND4LERM.js +4333 -0
- package/dist/chunk-6VOICQZ3.js +760 -0
- package/dist/chunk-EJ5RW5GI.js +93 -0
- package/dist/chunk-IHTMVTTE.js +1108 -0
- package/dist/chunk-JCOFKBOB.js +11 -0
- package/dist/chunk-KSHTDZ2V.js +893 -0
- package/dist/chunk-QHNYQVUM.js +989 -0
- package/dist/context-CFu9i136.d.ts +392 -0
- package/dist/core.d.ts +372 -0
- package/dist/core.js +29 -0
- package/dist/database.d.ts +383 -0
- package/dist/database.js +19 -0
- package/dist/experimental-Dmz4kYvX.d.ts +1560 -0
- package/dist/experimental.d.ts +15 -0
- package/dist/experimental.js +248 -0
- package/dist/index.d.ts +601 -2700
- package/dist/index.js +4781 -4427
- package/dist/{instrumentation-Cn94kn8-.d.ts → instrumentation-CpIuG2y5.d.ts} +32 -1
- package/dist/internal.d.ts +32 -22
- package/dist/internal.js +16 -4
- package/dist/telemetry-context-B7r6H1KW.d.ts +35 -0
- package/dist/useNodeStore-DTCSBF51.d.ts +28 -0
- package/dist/useQuery-D7ajycrc.d.ts +302 -0
- package/package.json +28 -10
- package/dist/chunk-CWHCGYDW.js +0 -2238
|
@@ -0,0 +1,1108 @@
|
|
|
1
|
+
// src/context/security-context.tsx
|
|
2
|
+
import { DEFAULT_SECURITY_LEVEL } from "@xnetjs/crypto";
|
|
3
|
+
import { MemoryPQKeyRegistry } from "@xnetjs/identity";
|
|
4
|
+
import { createContext, useContext, useState, useMemo, useEffect } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
var SecurityContext = createContext(null);
|
|
7
|
+
function SecurityProvider({
|
|
8
|
+
children,
|
|
9
|
+
level: initialLevel = DEFAULT_SECURITY_LEVEL,
|
|
10
|
+
minVerificationLevel: initialMinLevel = 0,
|
|
11
|
+
verificationPolicy: initialPolicy = "strict",
|
|
12
|
+
registry: providedRegistry,
|
|
13
|
+
keyBundle: initialBundle
|
|
14
|
+
}) {
|
|
15
|
+
const [level, setLevel] = useState(initialLevel);
|
|
16
|
+
const [minVerificationLevel, setMinVerificationLevel] = useState(initialMinLevel);
|
|
17
|
+
const [verificationPolicy, setVerificationPolicy] = useState(
|
|
18
|
+
initialPolicy
|
|
19
|
+
);
|
|
20
|
+
const [keyBundle, setKeyBundle] = useState(initialBundle);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setLevel(initialLevel);
|
|
23
|
+
}, [initialLevel]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setMinVerificationLevel(initialMinLevel);
|
|
26
|
+
}, [initialMinLevel]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setVerificationPolicy(initialPolicy);
|
|
29
|
+
}, [initialPolicy]);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setKeyBundle(initialBundle);
|
|
32
|
+
}, [initialBundle]);
|
|
33
|
+
const registry = useMemo(() => providedRegistry ?? new MemoryPQKeyRegistry(), [providedRegistry]);
|
|
34
|
+
const value = useMemo(
|
|
35
|
+
() => ({
|
|
36
|
+
level,
|
|
37
|
+
minVerificationLevel,
|
|
38
|
+
verificationPolicy,
|
|
39
|
+
registry,
|
|
40
|
+
keyBundle,
|
|
41
|
+
setLevel,
|
|
42
|
+
setMinVerificationLevel,
|
|
43
|
+
setVerificationPolicy,
|
|
44
|
+
setKeyBundle
|
|
45
|
+
}),
|
|
46
|
+
[level, minVerificationLevel, verificationPolicy, registry, keyBundle]
|
|
47
|
+
);
|
|
48
|
+
return /* @__PURE__ */ jsx(SecurityContext.Provider, { value, children });
|
|
49
|
+
}
|
|
50
|
+
function useSecurityContext() {
|
|
51
|
+
const context = useContext(SecurityContext);
|
|
52
|
+
if (!context) {
|
|
53
|
+
throw new Error("useSecurityContext must be used within SecurityProvider");
|
|
54
|
+
}
|
|
55
|
+
return context;
|
|
56
|
+
}
|
|
57
|
+
function useSecurityContextOptional() {
|
|
58
|
+
return useContext(SecurityContext);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/context/telemetry-context.ts
|
|
62
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
63
|
+
var TelemetryContext = createContext2(null);
|
|
64
|
+
function useTelemetryReporter() {
|
|
65
|
+
return useContext2(TelemetryContext);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/context/tracing-context.ts
|
|
69
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
70
|
+
var TRACE_STAGES = {
|
|
71
|
+
queryDescriptor: "data.query.descriptor",
|
|
72
|
+
queryBridge: "data.query.bridge",
|
|
73
|
+
queryFlatten: "data.query.flatten",
|
|
74
|
+
queryCommit: "data.query.commit",
|
|
75
|
+
mutateBridge: "data.mutate.bridge"
|
|
76
|
+
};
|
|
77
|
+
var TracingContext = createContext3(null);
|
|
78
|
+
function useTracingReporter() {
|
|
79
|
+
return useContext3(TracingContext);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/hooks/usePlugins.ts
|
|
83
|
+
import { createContext as createContext4, useContext as useContext4, useState as useState2, useEffect as useEffect2 } from "react";
|
|
84
|
+
var PluginRegistryContext = createContext4(null);
|
|
85
|
+
function usePluginRegistry() {
|
|
86
|
+
const registry = useContext4(PluginRegistryContext);
|
|
87
|
+
if (!registry) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"usePluginRegistry must be used within XNetProvider with plugins enabled. Ensure XNetProvider has platform: 'electron' or platform: 'web' in its config."
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return registry;
|
|
93
|
+
}
|
|
94
|
+
function usePluginRegistryOptional() {
|
|
95
|
+
return useContext4(PluginRegistryContext);
|
|
96
|
+
}
|
|
97
|
+
function usePlugins() {
|
|
98
|
+
const registry = usePluginRegistry();
|
|
99
|
+
const [plugins, setPlugins] = useState2(() => registry.getAll());
|
|
100
|
+
useEffect2(() => {
|
|
101
|
+
setPlugins(registry.getAll());
|
|
102
|
+
const disposable = registry.onChange(() => {
|
|
103
|
+
setPlugins(registry.getAll());
|
|
104
|
+
});
|
|
105
|
+
return () => disposable.dispose();
|
|
106
|
+
}, [registry]);
|
|
107
|
+
return plugins;
|
|
108
|
+
}
|
|
109
|
+
function getTypedRegistry(contributions, type) {
|
|
110
|
+
switch (type) {
|
|
111
|
+
case "views":
|
|
112
|
+
return contributions.views;
|
|
113
|
+
case "commands":
|
|
114
|
+
return contributions.commands;
|
|
115
|
+
case "slashCommands":
|
|
116
|
+
return contributions.slashCommands;
|
|
117
|
+
case "sidebar":
|
|
118
|
+
return contributions.sidebar;
|
|
119
|
+
case "editor":
|
|
120
|
+
return contributions.editor;
|
|
121
|
+
case "propertyHandlers":
|
|
122
|
+
return contributions.propertyHandlers;
|
|
123
|
+
case "blocks":
|
|
124
|
+
return contributions.blocks;
|
|
125
|
+
case "settings":
|
|
126
|
+
return contributions.settings;
|
|
127
|
+
case "importers":
|
|
128
|
+
return contributions.importers;
|
|
129
|
+
default:
|
|
130
|
+
throw new Error(`Unknown contribution type: ${type}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function useContributions(type) {
|
|
134
|
+
const registry = usePluginRegistry();
|
|
135
|
+
const contributions = registry.getContributions();
|
|
136
|
+
const typedRegistry = getTypedRegistry(contributions, type);
|
|
137
|
+
const [items, setItems] = useState2(() => typedRegistry.getAll());
|
|
138
|
+
useEffect2(() => {
|
|
139
|
+
setItems(typedRegistry.getAll());
|
|
140
|
+
const unsubscribe = typedRegistry.onChange(() => {
|
|
141
|
+
setItems(typedRegistry.getAll());
|
|
142
|
+
});
|
|
143
|
+
return unsubscribe;
|
|
144
|
+
}, [typedRegistry]);
|
|
145
|
+
return items;
|
|
146
|
+
}
|
|
147
|
+
function useViews() {
|
|
148
|
+
return useContributions("views");
|
|
149
|
+
}
|
|
150
|
+
function useCommands() {
|
|
151
|
+
return useContributions("commands");
|
|
152
|
+
}
|
|
153
|
+
function useSlashCommands() {
|
|
154
|
+
return useContributions("slashCommands");
|
|
155
|
+
}
|
|
156
|
+
function useSidebarItems() {
|
|
157
|
+
return useContributions("sidebar");
|
|
158
|
+
}
|
|
159
|
+
function useImporters() {
|
|
160
|
+
return useContributions("importers");
|
|
161
|
+
}
|
|
162
|
+
function useEditorExtensions() {
|
|
163
|
+
return useContributions("editor");
|
|
164
|
+
}
|
|
165
|
+
function useEditorExtensionsSafe() {
|
|
166
|
+
const registry = usePluginRegistryOptional();
|
|
167
|
+
const [items, setItems] = useState2([]);
|
|
168
|
+
useEffect2(() => {
|
|
169
|
+
if (!registry) {
|
|
170
|
+
setItems([]);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const contributions = registry.getContributions();
|
|
174
|
+
const typedRegistry = contributions.editor;
|
|
175
|
+
setItems(typedRegistry.getAll());
|
|
176
|
+
const unsubscribe = typedRegistry.onChange(() => {
|
|
177
|
+
setItems(typedRegistry.getAll());
|
|
178
|
+
});
|
|
179
|
+
return unsubscribe;
|
|
180
|
+
}, [registry]);
|
|
181
|
+
return items;
|
|
182
|
+
}
|
|
183
|
+
function useView(type) {
|
|
184
|
+
const views = useViews();
|
|
185
|
+
return views.find((v) => v.type === type);
|
|
186
|
+
}
|
|
187
|
+
function useCommand(id) {
|
|
188
|
+
const commands = useCommands();
|
|
189
|
+
return commands.find((c) => c.id === id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// src/runtime.ts
|
|
193
|
+
var DEFAULT_RUNTIME_BY_PLATFORM = {
|
|
194
|
+
web: {
|
|
195
|
+
mode: "worker",
|
|
196
|
+
fallback: "main-thread",
|
|
197
|
+
diagnostics: false
|
|
198
|
+
},
|
|
199
|
+
electron: {
|
|
200
|
+
mode: "ipc",
|
|
201
|
+
fallback: "error",
|
|
202
|
+
diagnostics: false
|
|
203
|
+
},
|
|
204
|
+
mobile: {
|
|
205
|
+
mode: "main-thread",
|
|
206
|
+
fallback: "main-thread",
|
|
207
|
+
diagnostics: false
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
function resolveRuntimeConfig(runtime, platform) {
|
|
211
|
+
const base = DEFAULT_RUNTIME_BY_PLATFORM[platform];
|
|
212
|
+
return {
|
|
213
|
+
...base,
|
|
214
|
+
...runtime,
|
|
215
|
+
worker: runtime?.worker ? { ...runtime.worker } : void 0
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function createRuntimeStatus(runtime, overrides = {}) {
|
|
219
|
+
return {
|
|
220
|
+
requestedMode: overrides.requestedMode ?? runtime.mode,
|
|
221
|
+
activeMode: overrides.activeMode ?? null,
|
|
222
|
+
fallbackMode: overrides.fallbackMode ?? null,
|
|
223
|
+
usedFallback: overrides.usedFallback ?? false,
|
|
224
|
+
phase: overrides.phase ?? "initializing",
|
|
225
|
+
reason: overrides.reason ?? null
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/context.ts
|
|
230
|
+
import { MemoryNodeStorageAdapter, NodeStore } from "@xnetjs/data";
|
|
231
|
+
import {
|
|
232
|
+
createDataBridge,
|
|
233
|
+
createMainThreadBridge,
|
|
234
|
+
MainThreadBridge,
|
|
235
|
+
WorkerBridge
|
|
236
|
+
} from "@xnetjs/data-bridge";
|
|
237
|
+
import { UndoManager } from "@xnetjs/history";
|
|
238
|
+
import { createUCAN } from "@xnetjs/identity";
|
|
239
|
+
import { PluginRegistry } from "@xnetjs/plugins";
|
|
240
|
+
import React, {
|
|
241
|
+
createContext as createContext5,
|
|
242
|
+
useCallback,
|
|
243
|
+
useContext as useContext5,
|
|
244
|
+
useEffect as useEffect3,
|
|
245
|
+
useMemo as useMemo2,
|
|
246
|
+
useRef,
|
|
247
|
+
useState as useState3
|
|
248
|
+
} from "react";
|
|
249
|
+
|
|
250
|
+
// src/hub/auto-backup.ts
|
|
251
|
+
import * as Y from "yjs";
|
|
252
|
+
var AutoBackup = class {
|
|
253
|
+
constructor(upload, options) {
|
|
254
|
+
this.upload = upload;
|
|
255
|
+
this.debounceMs = options?.debounceMs ?? 5e3;
|
|
256
|
+
this.isEnabled = options?.isEnabled ?? (() => true);
|
|
257
|
+
}
|
|
258
|
+
timers = /* @__PURE__ */ new Map();
|
|
259
|
+
debounceMs;
|
|
260
|
+
isEnabled;
|
|
261
|
+
handleDocUpdate(docId, doc) {
|
|
262
|
+
this.scheduleBackup(docId, doc, this.debounceMs);
|
|
263
|
+
}
|
|
264
|
+
handleDocEvict(docId, doc) {
|
|
265
|
+
this.scheduleBackup(docId, doc, 0);
|
|
266
|
+
}
|
|
267
|
+
scheduleBackup(docId, doc, delay) {
|
|
268
|
+
const existing = this.timers.get(docId);
|
|
269
|
+
if (existing) {
|
|
270
|
+
clearTimeout(existing);
|
|
271
|
+
}
|
|
272
|
+
const run = async () => {
|
|
273
|
+
this.timers.delete(docId);
|
|
274
|
+
if (!this.isEnabled()) return;
|
|
275
|
+
try {
|
|
276
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
277
|
+
await this.upload(docId, state);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
console.warn(`[auto-backup] Failed for ${docId}:`, err);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
if (delay <= 0) {
|
|
283
|
+
void run();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
this.timers.set(
|
|
287
|
+
docId,
|
|
288
|
+
setTimeout(() => void run(), delay)
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
async flush() {
|
|
292
|
+
for (const timer of this.timers.values()) {
|
|
293
|
+
clearTimeout(timer);
|
|
294
|
+
}
|
|
295
|
+
this.timers.clear();
|
|
296
|
+
}
|
|
297
|
+
destroy() {
|
|
298
|
+
void this.flush();
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// src/hub/backup.ts
|
|
303
|
+
import { concatBytes, decrypt, encrypt, NONCE_SIZE } from "@xnetjs/crypto";
|
|
304
|
+
var toHttpUrl = (hubUrl) => {
|
|
305
|
+
try {
|
|
306
|
+
const url = new URL(hubUrl);
|
|
307
|
+
if (url.protocol === "ws:") url.protocol = "http:";
|
|
308
|
+
if (url.protocol === "wss:") url.protocol = "https:";
|
|
309
|
+
return url.toString().replace(/\/$/, "");
|
|
310
|
+
} catch {
|
|
311
|
+
return hubUrl;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var encodeEncrypted = (encrypted) => concatBytes(encrypted.nonce, encrypted.ciphertext);
|
|
315
|
+
var decodeEncrypted = (payload) => {
|
|
316
|
+
const nonce = payload.slice(0, NONCE_SIZE);
|
|
317
|
+
const ciphertext = payload.slice(NONCE_SIZE);
|
|
318
|
+
return { nonce, ciphertext };
|
|
319
|
+
};
|
|
320
|
+
var resolveFetch = (fetchFn) => {
|
|
321
|
+
if (fetchFn) return fetchFn;
|
|
322
|
+
if (typeof fetch === "undefined") {
|
|
323
|
+
throw new Error("fetch is not available in this environment");
|
|
324
|
+
}
|
|
325
|
+
return fetch;
|
|
326
|
+
};
|
|
327
|
+
var buildAuthHeader = async (getAuthToken) => {
|
|
328
|
+
if (!getAuthToken) return null;
|
|
329
|
+
const token = await getAuthToken();
|
|
330
|
+
if (!token) return null;
|
|
331
|
+
return `Bearer ${token}`;
|
|
332
|
+
};
|
|
333
|
+
async function uploadBackup(config, docId, plaintext) {
|
|
334
|
+
const encrypted = encrypt(plaintext, config.encryptionKey);
|
|
335
|
+
const payload = encodeEncrypted(encrypted);
|
|
336
|
+
return uploadEncryptedBackup(config, docId, payload);
|
|
337
|
+
}
|
|
338
|
+
async function uploadEncryptedBackup(config, docId, payload) {
|
|
339
|
+
const httpUrl = toHttpUrl(config.hubUrl);
|
|
340
|
+
const fetcher = resolveFetch(config.fetchFn);
|
|
341
|
+
const authHeader = await buildAuthHeader(config.getAuthToken);
|
|
342
|
+
const res = await fetcher(`${httpUrl}/backup/${encodeURIComponent(docId)}`, {
|
|
343
|
+
method: "PUT",
|
|
344
|
+
headers: {
|
|
345
|
+
...authHeader ? { Authorization: authHeader } : {},
|
|
346
|
+
"Content-Type": "application/octet-stream"
|
|
347
|
+
},
|
|
348
|
+
body: payload
|
|
349
|
+
});
|
|
350
|
+
if (!res.ok) {
|
|
351
|
+
throw new Error(`Backup upload failed: ${res.status} ${res.statusText}`);
|
|
352
|
+
}
|
|
353
|
+
return res.json();
|
|
354
|
+
}
|
|
355
|
+
async function downloadBackup(config, docId) {
|
|
356
|
+
const payload = await downloadEncryptedBackup(config, docId);
|
|
357
|
+
if (!payload) return null;
|
|
358
|
+
const encrypted = decodeEncrypted(payload);
|
|
359
|
+
return decrypt(encrypted, config.encryptionKey);
|
|
360
|
+
}
|
|
361
|
+
async function downloadEncryptedBackup(config, docId) {
|
|
362
|
+
const httpUrl = toHttpUrl(config.hubUrl);
|
|
363
|
+
const fetcher = resolveFetch(config.fetchFn);
|
|
364
|
+
const authHeader = await buildAuthHeader(config.getAuthToken);
|
|
365
|
+
const res = await fetcher(`${httpUrl}/backup/${encodeURIComponent(docId)}`, {
|
|
366
|
+
headers: authHeader ? { Authorization: authHeader } : void 0
|
|
367
|
+
});
|
|
368
|
+
if (res.status === 404) return null;
|
|
369
|
+
if (!res.ok) {
|
|
370
|
+
throw new Error(`Backup download failed: ${res.status} ${res.statusText}`);
|
|
371
|
+
}
|
|
372
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/context.ts
|
|
376
|
+
import { createSyncManager } from "@xnetjs/runtime";
|
|
377
|
+
function log(...args) {
|
|
378
|
+
if (typeof localStorage !== "undefined" && localStorage.getItem("xnet:sync:debug") === "true") {
|
|
379
|
+
console.log("[XNetProvider]", ...args);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function scheduleIdle(fn) {
|
|
383
|
+
if (typeof window === "undefined") return;
|
|
384
|
+
const ric = window.requestIdleCallback;
|
|
385
|
+
if (typeof ric === "function") ric(fn);
|
|
386
|
+
else setTimeout(fn, 1e3);
|
|
387
|
+
}
|
|
388
|
+
function resolveConfiguredSignalingUrls(hubUrl, signalingServers) {
|
|
389
|
+
const seen = /* @__PURE__ */ new Set();
|
|
390
|
+
const configured = hubUrl ? [hubUrl, ...signalingServers ?? []] : signalingServers ?? [];
|
|
391
|
+
const urls = configured.map((url) => url.trim()).filter((url) => {
|
|
392
|
+
if (!url || seen.has(url)) return false;
|
|
393
|
+
seen.add(url);
|
|
394
|
+
return true;
|
|
395
|
+
});
|
|
396
|
+
return urls;
|
|
397
|
+
}
|
|
398
|
+
var HUB_CAPABILITIES = [
|
|
399
|
+
{ with: "*", can: "hub/*" },
|
|
400
|
+
{ with: "*", can: "backup/*" },
|
|
401
|
+
{ with: "*", can: "files/*" },
|
|
402
|
+
{ with: "*", can: "query/*" },
|
|
403
|
+
{ with: "*", can: "index/*" }
|
|
404
|
+
];
|
|
405
|
+
var HUB_TOKEN_TTL_SECONDS = 60 * 60 * 24;
|
|
406
|
+
var HUB_INDEX_DEBOUNCE_MS = 2e3;
|
|
407
|
+
function getRuntimeErrorMessage(err) {
|
|
408
|
+
return err instanceof Error ? err.message : String(err);
|
|
409
|
+
}
|
|
410
|
+
function inferBridgeMode(bridge) {
|
|
411
|
+
if (bridge instanceof WorkerBridge) return "worker";
|
|
412
|
+
if (bridge instanceof MainThreadBridge) return "main-thread";
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
function reportRuntimeStatus(telemetry, status) {
|
|
416
|
+
telemetry?.reportUsage(`react.runtime.request.${status.requestedMode}`, 1);
|
|
417
|
+
if (status.activeMode) {
|
|
418
|
+
telemetry?.reportUsage(`react.runtime.active.${status.activeMode}`, 1);
|
|
419
|
+
}
|
|
420
|
+
if (status.usedFallback && status.fallbackMode) {
|
|
421
|
+
telemetry?.reportUsage(`react.runtime.fallback.${status.fallbackMode}`, 1);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function logRuntimeStatus(runtime, status) {
|
|
425
|
+
if (!runtime.diagnostics) return;
|
|
426
|
+
if (status.phase === "error") {
|
|
427
|
+
console.error("[XNetProvider] Runtime initialization failed:", status);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (status.usedFallback) {
|
|
431
|
+
console.warn("[XNetProvider] Runtime fallback activated:", status);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
console.info("[XNetProvider] Runtime ready:", status);
|
|
435
|
+
}
|
|
436
|
+
async function resolveTagSearchText(store, tagIds) {
|
|
437
|
+
const names = await Promise.all(
|
|
438
|
+
tagIds.map(async (id) => {
|
|
439
|
+
const tag = await store.get(id).catch(() => null);
|
|
440
|
+
const name = tag?.properties?.name;
|
|
441
|
+
return typeof name === "string" && name ? `#${name}` : null;
|
|
442
|
+
})
|
|
443
|
+
);
|
|
444
|
+
const present = names.filter((entry) => entry !== null);
|
|
445
|
+
return present.length > 0 ? present.join(" ") : void 0;
|
|
446
|
+
}
|
|
447
|
+
function resolveRuntimeFailure(runtime, nodeStore, reason, bridgeOptions) {
|
|
448
|
+
if (runtime.fallback === "main-thread") {
|
|
449
|
+
return {
|
|
450
|
+
bridge: createMainThreadBridge(nodeStore, bridgeOptions),
|
|
451
|
+
createdInternally: true,
|
|
452
|
+
status: createRuntimeStatus(runtime, {
|
|
453
|
+
activeMode: "main-thread",
|
|
454
|
+
fallbackMode: "main-thread",
|
|
455
|
+
usedFallback: true,
|
|
456
|
+
phase: "ready",
|
|
457
|
+
reason
|
|
458
|
+
})
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
bridge: null,
|
|
463
|
+
createdInternally: false,
|
|
464
|
+
status: createRuntimeStatus(runtime, {
|
|
465
|
+
phase: "error",
|
|
466
|
+
reason
|
|
467
|
+
})
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async function resolveRuntimeBridge(input) {
|
|
471
|
+
const {
|
|
472
|
+
runtime,
|
|
473
|
+
nodeStore,
|
|
474
|
+
authorDID,
|
|
475
|
+
signingKey,
|
|
476
|
+
signalingUrl,
|
|
477
|
+
dataBridge,
|
|
478
|
+
remoteNodeQueryClient,
|
|
479
|
+
remoteNodeQueryRouting,
|
|
480
|
+
syncManager
|
|
481
|
+
} = input;
|
|
482
|
+
if (runtime.mode === "ipc") {
|
|
483
|
+
if (!syncManager) {
|
|
484
|
+
return resolveRuntimeFailure(
|
|
485
|
+
runtime,
|
|
486
|
+
nodeStore,
|
|
487
|
+
"IPC runtime requires config.syncManager to be provided explicitly.",
|
|
488
|
+
{ remoteNodeQueryClient, remoteNodeQueryRouting }
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
bridge: dataBridge ?? createMainThreadBridge(nodeStore, {
|
|
493
|
+
remoteNodeQueryClient,
|
|
494
|
+
remoteNodeQueryRouting
|
|
495
|
+
}),
|
|
496
|
+
createdInternally: !dataBridge,
|
|
497
|
+
status: createRuntimeStatus(runtime, {
|
|
498
|
+
activeMode: "ipc",
|
|
499
|
+
phase: "ready"
|
|
500
|
+
})
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (dataBridge) {
|
|
504
|
+
const activeMode = inferBridgeMode(dataBridge) ?? runtime.mode;
|
|
505
|
+
const usedFallback = activeMode !== runtime.mode;
|
|
506
|
+
return {
|
|
507
|
+
bridge: dataBridge,
|
|
508
|
+
createdInternally: false,
|
|
509
|
+
status: createRuntimeStatus(runtime, {
|
|
510
|
+
activeMode,
|
|
511
|
+
fallbackMode: usedFallback ? activeMode : null,
|
|
512
|
+
usedFallback,
|
|
513
|
+
phase: "ready",
|
|
514
|
+
reason: usedFallback ? `Configured runtime "${runtime.mode}" resolved to "${activeMode}" through the supplied dataBridge.` : null
|
|
515
|
+
})
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (runtime.mode === "worker") {
|
|
519
|
+
try {
|
|
520
|
+
const bridge2 = await createDataBridge({
|
|
521
|
+
nodeStore,
|
|
522
|
+
config: {
|
|
523
|
+
dbName: runtime.worker?.dbName,
|
|
524
|
+
authorDID,
|
|
525
|
+
signingKey,
|
|
526
|
+
signalingUrl: runtime.worker?.signalingUrl ?? signalingUrl,
|
|
527
|
+
storagePort: runtime.worker?.storagePort,
|
|
528
|
+
remoteNodeQueryClient,
|
|
529
|
+
remoteNodeQueryRouting
|
|
530
|
+
},
|
|
531
|
+
workerUrl: runtime.worker?.url,
|
|
532
|
+
mode: "worker"
|
|
533
|
+
});
|
|
534
|
+
return {
|
|
535
|
+
bridge: bridge2,
|
|
536
|
+
createdInternally: true,
|
|
537
|
+
status: createRuntimeStatus(runtime, {
|
|
538
|
+
activeMode: "worker",
|
|
539
|
+
phase: "ready"
|
|
540
|
+
})
|
|
541
|
+
};
|
|
542
|
+
} catch (err) {
|
|
543
|
+
return resolveRuntimeFailure(
|
|
544
|
+
runtime,
|
|
545
|
+
nodeStore,
|
|
546
|
+
`Worker runtime unavailable: ${getRuntimeErrorMessage(err)}`,
|
|
547
|
+
{ remoteNodeQueryClient, remoteNodeQueryRouting }
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const bridge = await createDataBridge({
|
|
552
|
+
nodeStore,
|
|
553
|
+
config: {
|
|
554
|
+
authorDID,
|
|
555
|
+
signingKey,
|
|
556
|
+
signalingUrl,
|
|
557
|
+
remoteNodeQueryClient,
|
|
558
|
+
remoteNodeQueryRouting
|
|
559
|
+
},
|
|
560
|
+
mode: "main-thread"
|
|
561
|
+
});
|
|
562
|
+
return {
|
|
563
|
+
bridge,
|
|
564
|
+
createdInternally: true,
|
|
565
|
+
status: createRuntimeStatus(runtime, {
|
|
566
|
+
activeMode: "main-thread",
|
|
567
|
+
phase: "ready"
|
|
568
|
+
})
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
var XNetContext = createContext5(null);
|
|
572
|
+
var XNetInternalContext = createContext5({
|
|
573
|
+
authorDID: null,
|
|
574
|
+
signingKey: null,
|
|
575
|
+
sync: void 0
|
|
576
|
+
});
|
|
577
|
+
var DataBridgeContext = createContext5(null);
|
|
578
|
+
function useDataBridge() {
|
|
579
|
+
return useContext5(DataBridgeContext);
|
|
580
|
+
}
|
|
581
|
+
function XNetProvider({ config, children }) {
|
|
582
|
+
const [nodeStore, setNodeStore] = useState3(null);
|
|
583
|
+
const [nodeStoreReady, setNodeStoreReady] = useState3(false);
|
|
584
|
+
const [undoManager, setUndoManager] = useState3(null);
|
|
585
|
+
const [dataBridge, setDataBridge] = useState3(null);
|
|
586
|
+
const [syncManager, setSyncManager] = useState3(null);
|
|
587
|
+
const [hubStatus, setHubStatus] = useState3("disconnected");
|
|
588
|
+
const [pluginRegistry, setPluginRegistry] = useState3(null);
|
|
589
|
+
const nodeStorageRef = useRef(null);
|
|
590
|
+
const platform = config.platform ?? "web";
|
|
591
|
+
const authorDID = config.authorDID ?? config.identity?.did;
|
|
592
|
+
const hubUrl = config.hubUrl ?? null;
|
|
593
|
+
const signalingServersKey = (config.signalingServers ?? []).join("\n");
|
|
594
|
+
const signalingServers = useMemo2(
|
|
595
|
+
() => signalingServersKey ? signalingServersKey.split("\n") : [],
|
|
596
|
+
[signalingServersKey]
|
|
597
|
+
);
|
|
598
|
+
const signalingUrls = useMemo2(
|
|
599
|
+
() => resolveConfiguredSignalingUrls(hubUrl, signalingServers),
|
|
600
|
+
[hubUrl, signalingServers]
|
|
601
|
+
);
|
|
602
|
+
const hubOptions = config.hubOptions;
|
|
603
|
+
const autoAuth = hubOptions?.autoAuth ?? true;
|
|
604
|
+
const staticHubAuthToken = hubOptions?.authToken?.trim() ?? "";
|
|
605
|
+
const autoBackup = hubOptions?.autoBackup ?? false;
|
|
606
|
+
const backupDebounceMs = hubOptions?.backupDebounceMs ?? 5e3;
|
|
607
|
+
const enableSearchIndex = hubOptions?.enableSearchIndex ?? false;
|
|
608
|
+
const nodeSyncRoom = hubOptions?.nodeSyncRoom ?? authorDID ?? "default";
|
|
609
|
+
const encryptionKey = config.encryptionKey ?? null;
|
|
610
|
+
const runtimeWorkerUrlKey = config.runtime?.worker?.url ? String(config.runtime.worker.url) : "";
|
|
611
|
+
const runtimeConfig = useMemo2(
|
|
612
|
+
() => resolveRuntimeConfig(config.runtime, platform),
|
|
613
|
+
[
|
|
614
|
+
config.runtime?.mode,
|
|
615
|
+
config.runtime?.fallback,
|
|
616
|
+
config.runtime?.diagnostics,
|
|
617
|
+
config.runtime?.worker?.dbName,
|
|
618
|
+
config.runtime?.worker?.signalingUrl,
|
|
619
|
+
runtimeWorkerUrlKey,
|
|
620
|
+
platform
|
|
621
|
+
]
|
|
622
|
+
);
|
|
623
|
+
const [runtimeStatus, setRuntimeStatus] = useState3(
|
|
624
|
+
() => createRuntimeStatus(runtimeConfig)
|
|
625
|
+
);
|
|
626
|
+
const getHubAuthToken = useCallback(async () => {
|
|
627
|
+
if (staticHubAuthToken) return staticHubAuthToken;
|
|
628
|
+
if (!hubUrl || !autoAuth) return "";
|
|
629
|
+
if (!authorDID || !config.signingKey) {
|
|
630
|
+
throw new Error("Missing authorDID/signingKey for hub auth");
|
|
631
|
+
}
|
|
632
|
+
return createUCAN({
|
|
633
|
+
issuer: authorDID,
|
|
634
|
+
issuerKey: config.signingKey,
|
|
635
|
+
audience: hubUrl,
|
|
636
|
+
capabilities: HUB_CAPABILITIES,
|
|
637
|
+
expiration: Math.floor(Date.now() / 1e3) + HUB_TOKEN_TTL_SECONDS
|
|
638
|
+
});
|
|
639
|
+
}, [authorDID, autoAuth, config.signingKey, hubUrl, staticHubAuthToken]);
|
|
640
|
+
useEffect3(() => {
|
|
641
|
+
const nodeStorageAdapter = config.nodeStorage ?? new MemoryNodeStorageAdapter();
|
|
642
|
+
nodeStorageRef.current = nodeStorageAdapter;
|
|
643
|
+
const signingKey = config.signingKey;
|
|
644
|
+
setRuntimeStatus(createRuntimeStatus(runtimeConfig));
|
|
645
|
+
if (!authorDID || !signingKey) {
|
|
646
|
+
console.warn(
|
|
647
|
+
"XNetProvider: authorDID and signingKey not provided. NodeStore will not be initialized. Provide these via config.authorDID/config.signingKey or config.identity."
|
|
648
|
+
);
|
|
649
|
+
setRuntimeStatus(
|
|
650
|
+
createRuntimeStatus(runtimeConfig, {
|
|
651
|
+
phase: "error",
|
|
652
|
+
reason: "authorDID and signingKey are required to initialize the runtime."
|
|
653
|
+
})
|
|
654
|
+
);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
let cancelled = false;
|
|
658
|
+
const initializeNodeStore = async () => {
|
|
659
|
+
if ("open" in nodeStorageAdapter && typeof nodeStorageAdapter.open === "function") {
|
|
660
|
+
await nodeStorageAdapter.open();
|
|
661
|
+
}
|
|
662
|
+
if (cancelled) return;
|
|
663
|
+
const ns = new NodeStore({
|
|
664
|
+
storage: nodeStorageAdapter,
|
|
665
|
+
authorDID,
|
|
666
|
+
signingKey
|
|
667
|
+
});
|
|
668
|
+
await ns.initialize();
|
|
669
|
+
if (cancelled) return;
|
|
670
|
+
const resolvedRuntime = await resolveRuntimeBridge({
|
|
671
|
+
runtime: runtimeConfig,
|
|
672
|
+
nodeStore: ns,
|
|
673
|
+
authorDID,
|
|
674
|
+
signingKey,
|
|
675
|
+
signalingUrl: signalingUrls[0],
|
|
676
|
+
dataBridge: config.dataBridge,
|
|
677
|
+
remoteNodeQueryClient: config.remoteNodeQueryClient,
|
|
678
|
+
remoteNodeQueryRouting: config.remoteNodeQueryRouting,
|
|
679
|
+
syncManager: config.syncManager
|
|
680
|
+
});
|
|
681
|
+
if (cancelled) {
|
|
682
|
+
if (resolvedRuntime.createdInternally && resolvedRuntime.bridge) {
|
|
683
|
+
resolvedRuntime.bridge.destroy();
|
|
684
|
+
}
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
setRuntimeStatus(resolvedRuntime.status);
|
|
688
|
+
reportRuntimeStatus(config.telemetry, resolvedRuntime.status);
|
|
689
|
+
logRuntimeStatus(runtimeConfig, resolvedRuntime.status);
|
|
690
|
+
if (resolvedRuntime.status.phase !== "ready" || !resolvedRuntime.bridge) {
|
|
691
|
+
config.telemetry?.reportCrash(
|
|
692
|
+
new Error(resolvedRuntime.status.reason ?? "Runtime failed"),
|
|
693
|
+
{
|
|
694
|
+
codeNamespace: "react.runtime.initialize",
|
|
695
|
+
requestedMode: resolvedRuntime.status.requestedMode
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
setNodeStore(null);
|
|
699
|
+
setNodeStoreReady(false);
|
|
700
|
+
setDataBridge(null);
|
|
701
|
+
bridgeRef = null;
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
setNodeStore(ns);
|
|
705
|
+
setNodeStoreReady(true);
|
|
706
|
+
setDataBridge(resolvedRuntime.bridge);
|
|
707
|
+
bridgeRef = resolvedRuntime.createdInternally ? resolvedRuntime.bridge : null;
|
|
708
|
+
if (typeof window !== "undefined") {
|
|
709
|
+
const win = window;
|
|
710
|
+
win.__xnetNodeStore = ns;
|
|
711
|
+
}
|
|
712
|
+
scheduleIdle(() => {
|
|
713
|
+
if (!cancelled) void ns.optimize();
|
|
714
|
+
});
|
|
715
|
+
};
|
|
716
|
+
let bridgeRef = null;
|
|
717
|
+
initializeNodeStore();
|
|
718
|
+
return () => {
|
|
719
|
+
cancelled = true;
|
|
720
|
+
if (bridgeRef) {
|
|
721
|
+
bridgeRef.destroy();
|
|
722
|
+
}
|
|
723
|
+
setDataBridge(null);
|
|
724
|
+
setNodeStore(null);
|
|
725
|
+
setNodeStoreReady(false);
|
|
726
|
+
if (typeof window !== "undefined") {
|
|
727
|
+
delete window.__xnetNodeStore;
|
|
728
|
+
}
|
|
729
|
+
if ("close" in nodeStorageAdapter && typeof nodeStorageAdapter.close === "function") {
|
|
730
|
+
nodeStorageAdapter.close();
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}, [
|
|
734
|
+
authorDID,
|
|
735
|
+
config.nodeStorage,
|
|
736
|
+
config.signingKey,
|
|
737
|
+
config.dataBridge,
|
|
738
|
+
signalingUrls,
|
|
739
|
+
config.syncManager,
|
|
740
|
+
config.remoteNodeQueryClient,
|
|
741
|
+
config.remoteNodeQueryRouting,
|
|
742
|
+
config.telemetry,
|
|
743
|
+
hubUrl,
|
|
744
|
+
runtimeConfig,
|
|
745
|
+
runtimeWorkerUrlKey
|
|
746
|
+
]);
|
|
747
|
+
useEffect3(() => {
|
|
748
|
+
if (config.syncManager) {
|
|
749
|
+
setSyncManager(config.syncManager);
|
|
750
|
+
const sm2 = config.syncManager;
|
|
751
|
+
if (sm2.setIdentity && authorDID && config.signingKey) {
|
|
752
|
+
sm2.setIdentity(authorDID, config.signingKey);
|
|
753
|
+
}
|
|
754
|
+
if (sm2.configureReplication) {
|
|
755
|
+
sm2.configureReplication(config.sync);
|
|
756
|
+
}
|
|
757
|
+
config.syncManager.start().catch((err) => {
|
|
758
|
+
console.warn("[XNetProvider] External SyncManager failed to start:", err);
|
|
759
|
+
});
|
|
760
|
+
return () => {
|
|
761
|
+
config.syncManager.stop().catch((err) => {
|
|
762
|
+
console.warn("[XNetProvider] External SyncManager failed to stop:", err);
|
|
763
|
+
});
|
|
764
|
+
setSyncManager(null);
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (!nodeStore || !nodeStoreReady || config.disableSyncManager) {
|
|
768
|
+
log("SyncManager disabled or NodeStore not ready", {
|
|
769
|
+
nodeStore: !!nodeStore,
|
|
770
|
+
nodeStoreReady,
|
|
771
|
+
disableSyncManager: config.disableSyncManager
|
|
772
|
+
});
|
|
773
|
+
setSyncManager(null);
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const storage = nodeStorageRef.current;
|
|
777
|
+
if (!storage) {
|
|
778
|
+
log("No storage adapter available");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const signalingUrl = signalingUrls[0] ?? "";
|
|
782
|
+
if (autoAuth && hubUrl && (!authorDID || !config.signingKey)) {
|
|
783
|
+
console.warn("[XNetProvider] Hub auth enabled but authorDID/signingKey missing");
|
|
784
|
+
}
|
|
785
|
+
if (autoBackup && (!hubUrl || !encryptionKey)) {
|
|
786
|
+
console.warn("[XNetProvider] Auto-backup requires hubUrl and encryptionKey");
|
|
787
|
+
}
|
|
788
|
+
console.log("[XNetProvider] Creating SyncManager with signalingUrls:", signalingUrls);
|
|
789
|
+
log("Creating SyncManager with signalingUrls:", signalingUrls);
|
|
790
|
+
let autoBackupManager = null;
|
|
791
|
+
const enableAutoBackup = Boolean(autoBackup && hubUrl && encryptionKey);
|
|
792
|
+
const sm = createSyncManager({
|
|
793
|
+
nodeStore,
|
|
794
|
+
storage,
|
|
795
|
+
signalingUrl,
|
|
796
|
+
signalingUrls,
|
|
797
|
+
authorDID,
|
|
798
|
+
signingKey: config.signingKey,
|
|
799
|
+
replication: config.sync,
|
|
800
|
+
blobStore: config.blobStore,
|
|
801
|
+
nodeSyncRoom: hubUrl ? nodeSyncRoom : void 0,
|
|
802
|
+
getUCANToken: hubUrl ? getHubAuthToken : void 0,
|
|
803
|
+
onDocUpdate: enableAutoBackup ? (nodeId, doc) => {
|
|
804
|
+
autoBackupManager?.handleDocUpdate(nodeId, doc);
|
|
805
|
+
} : void 0,
|
|
806
|
+
onDocEvict: enableAutoBackup ? (nodeId, doc) => {
|
|
807
|
+
autoBackupManager?.handleDocEvict(nodeId, doc);
|
|
808
|
+
} : void 0
|
|
809
|
+
});
|
|
810
|
+
if (enableAutoBackup && hubUrl && encryptionKey) {
|
|
811
|
+
autoBackupManager = new AutoBackup(
|
|
812
|
+
async (docId, plaintext) => {
|
|
813
|
+
await uploadBackup(
|
|
814
|
+
{
|
|
815
|
+
hubUrl,
|
|
816
|
+
encryptionKey,
|
|
817
|
+
getAuthToken: autoAuth ? getHubAuthToken : void 0
|
|
818
|
+
},
|
|
819
|
+
docId,
|
|
820
|
+
plaintext
|
|
821
|
+
);
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
debounceMs: backupDebounceMs,
|
|
825
|
+
isEnabled: () => sm.connection?.status === "connected"
|
|
826
|
+
}
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
setSyncManager(sm);
|
|
830
|
+
console.log("[XNetProvider] SyncManager created and set in context");
|
|
831
|
+
log("SyncManager created, starting...");
|
|
832
|
+
sm.start().then(() => {
|
|
833
|
+
log("SyncManager started successfully");
|
|
834
|
+
}).catch((err) => {
|
|
835
|
+
console.warn("[XNetProvider] SyncManager failed to start:", err);
|
|
836
|
+
log("SyncManager start failed:", err);
|
|
837
|
+
});
|
|
838
|
+
return () => {
|
|
839
|
+
sm.stop().catch((err) => {
|
|
840
|
+
console.warn("[XNetProvider] SyncManager failed to stop:", err);
|
|
841
|
+
});
|
|
842
|
+
autoBackupManager?.destroy();
|
|
843
|
+
setSyncManager(null);
|
|
844
|
+
};
|
|
845
|
+
}, [
|
|
846
|
+
nodeStore,
|
|
847
|
+
nodeStoreReady,
|
|
848
|
+
config.disableSyncManager,
|
|
849
|
+
config.syncManager,
|
|
850
|
+
signalingUrls,
|
|
851
|
+
config.blobStore,
|
|
852
|
+
config.sync,
|
|
853
|
+
authorDID,
|
|
854
|
+
autoAuth,
|
|
855
|
+
autoBackup,
|
|
856
|
+
backupDebounceMs,
|
|
857
|
+
encryptionKey,
|
|
858
|
+
getHubAuthToken,
|
|
859
|
+
hubUrl,
|
|
860
|
+
nodeSyncRoom
|
|
861
|
+
]);
|
|
862
|
+
useEffect3(() => {
|
|
863
|
+
if (!dataBridge || !syncManager) return;
|
|
864
|
+
const bridge = dataBridge;
|
|
865
|
+
if (typeof bridge.setSyncManager === "function") {
|
|
866
|
+
bridge.setSyncManager(syncManager);
|
|
867
|
+
log("Connected SyncManager to DataBridge");
|
|
868
|
+
}
|
|
869
|
+
return () => {
|
|
870
|
+
if (typeof bridge.setSyncManager === "function") {
|
|
871
|
+
bridge.setSyncManager(null);
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
}, [dataBridge, syncManager]);
|
|
875
|
+
useEffect3(() => {
|
|
876
|
+
if (!syncManager) {
|
|
877
|
+
setHubStatus("disconnected");
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
setHubStatus(syncManager.status);
|
|
881
|
+
return syncManager.on("status", (status) => {
|
|
882
|
+
setHubStatus(status);
|
|
883
|
+
});
|
|
884
|
+
}, [syncManager]);
|
|
885
|
+
useEffect3(() => {
|
|
886
|
+
if (!nodeStore || !syncManager || !hubUrl || !enableSearchIndex) return;
|
|
887
|
+
const connection = syncManager.connection;
|
|
888
|
+
if (!connection) return;
|
|
889
|
+
const timers = /* @__PURE__ */ new Map();
|
|
890
|
+
const pending = /* @__PURE__ */ new Map();
|
|
891
|
+
const schedule = (docId, payload) => {
|
|
892
|
+
pending.set(docId, payload);
|
|
893
|
+
const existing = timers.get(docId);
|
|
894
|
+
if (existing) clearTimeout(existing);
|
|
895
|
+
timers.set(
|
|
896
|
+
docId,
|
|
897
|
+
setTimeout(() => {
|
|
898
|
+
timers.delete(docId);
|
|
899
|
+
const next = pending.get(docId);
|
|
900
|
+
pending.delete(docId);
|
|
901
|
+
if (!next) return;
|
|
902
|
+
if (connection.status !== "connected") return;
|
|
903
|
+
if (next.type === "remove") {
|
|
904
|
+
connection.sendRaw({ type: "index-remove", docId });
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
connection.sendRaw({
|
|
908
|
+
type: "index-update",
|
|
909
|
+
docId,
|
|
910
|
+
meta: next.meta,
|
|
911
|
+
...next.text !== void 0 ? { text: next.text } : {}
|
|
912
|
+
});
|
|
913
|
+
}, HUB_INDEX_DEBOUNCE_MS)
|
|
914
|
+
);
|
|
915
|
+
};
|
|
916
|
+
const handleChange = (event) => {
|
|
917
|
+
const node = event.node;
|
|
918
|
+
if (!node || node.deleted) {
|
|
919
|
+
schedule(event.change.payload.nodeId, { type: "remove" });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (!node.schemaId) return;
|
|
923
|
+
const title = typeof node.properties.title === "string" ? node.properties.title : typeof node.properties.name === "string" ? node.properties.name : "";
|
|
924
|
+
const meta = { schemaIri: node.schemaId, title, properties: node.properties };
|
|
925
|
+
const tagIds = Array.isArray(node.properties.tags) ? node.properties.tags.filter((id) => typeof id === "string") : [];
|
|
926
|
+
if (tagIds.length === 0) {
|
|
927
|
+
schedule(node.id, { type: "update", meta });
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
void resolveTagSearchText(nodeStore, tagIds).then((text) => {
|
|
931
|
+
schedule(node.id, { type: "update", meta, ...text ? { text } : {} });
|
|
932
|
+
});
|
|
933
|
+
};
|
|
934
|
+
const unsubscribe = nodeStore.subscribe(handleChange);
|
|
935
|
+
return () => {
|
|
936
|
+
unsubscribe();
|
|
937
|
+
for (const timer of timers.values()) {
|
|
938
|
+
clearTimeout(timer);
|
|
939
|
+
}
|
|
940
|
+
timers.clear();
|
|
941
|
+
pending.clear();
|
|
942
|
+
};
|
|
943
|
+
}, [enableSearchIndex, hubUrl, nodeStore, syncManager]);
|
|
944
|
+
useEffect3(() => {
|
|
945
|
+
if (!nodeStore || !nodeStoreReady || config.disablePlugins) {
|
|
946
|
+
log("PluginRegistry disabled or NodeStore not ready");
|
|
947
|
+
setPluginRegistry(null);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
log("Creating PluginRegistry with platform:", platform);
|
|
951
|
+
const registry = new PluginRegistry(nodeStore, platform);
|
|
952
|
+
setPluginRegistry(registry);
|
|
953
|
+
registry.loadFromStore().catch((err) => {
|
|
954
|
+
console.warn("[XNetProvider] Failed to load plugins from store:", err);
|
|
955
|
+
});
|
|
956
|
+
return () => {
|
|
957
|
+
const plugins = registry.getAll();
|
|
958
|
+
for (const plugin of plugins) {
|
|
959
|
+
if (plugin.status === "active") {
|
|
960
|
+
registry.deactivate(plugin.manifest.id).catch((err) => {
|
|
961
|
+
console.warn(`[XNetProvider] Failed to deactivate plugin ${plugin.manifest.id}:`, err);
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
setPluginRegistry(null);
|
|
966
|
+
};
|
|
967
|
+
}, [nodeStore, nodeStoreReady, config.disablePlugins, config.platform]);
|
|
968
|
+
useEffect3(() => {
|
|
969
|
+
if (!nodeStore || !nodeStoreReady || !authorDID) {
|
|
970
|
+
setUndoManager(null);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const manager = new UndoManager(
|
|
974
|
+
nodeStore,
|
|
975
|
+
authorDID,
|
|
976
|
+
{ localOnly: true, maxStackSize: 200 },
|
|
977
|
+
config.telemetry
|
|
978
|
+
);
|
|
979
|
+
manager.start();
|
|
980
|
+
setUndoManager(manager);
|
|
981
|
+
return () => {
|
|
982
|
+
manager.stop();
|
|
983
|
+
setUndoManager(null);
|
|
984
|
+
};
|
|
985
|
+
}, [nodeStore, nodeStoreReady, authorDID, config.telemetry]);
|
|
986
|
+
const value = useMemo2(
|
|
987
|
+
() => ({
|
|
988
|
+
nodeStore,
|
|
989
|
+
nodeStoreReady,
|
|
990
|
+
identity: config.identity,
|
|
991
|
+
authorDID: authorDID ?? null,
|
|
992
|
+
syncManager,
|
|
993
|
+
hubUrl,
|
|
994
|
+
hubStatus,
|
|
995
|
+
hubConnection: syncManager?.connection ?? null,
|
|
996
|
+
getHubAuthToken: hubUrl ? getHubAuthToken : void 0,
|
|
997
|
+
billing: config.billing,
|
|
998
|
+
encryptionKey,
|
|
999
|
+
blobStore: config.blobStore ?? null,
|
|
1000
|
+
pluginRegistry,
|
|
1001
|
+
runtimeStatus,
|
|
1002
|
+
undoManager
|
|
1003
|
+
}),
|
|
1004
|
+
[
|
|
1005
|
+
nodeStore,
|
|
1006
|
+
nodeStoreReady,
|
|
1007
|
+
config.identity,
|
|
1008
|
+
authorDID,
|
|
1009
|
+
syncManager,
|
|
1010
|
+
hubUrl,
|
|
1011
|
+
hubStatus,
|
|
1012
|
+
getHubAuthToken,
|
|
1013
|
+
config.billing,
|
|
1014
|
+
encryptionKey,
|
|
1015
|
+
config.blobStore,
|
|
1016
|
+
pluginRegistry,
|
|
1017
|
+
runtimeStatus,
|
|
1018
|
+
undoManager
|
|
1019
|
+
]
|
|
1020
|
+
);
|
|
1021
|
+
const internalValue = useMemo2(
|
|
1022
|
+
() => ({
|
|
1023
|
+
authorDID: authorDID ?? null,
|
|
1024
|
+
signingKey: config.signingKey ?? null,
|
|
1025
|
+
sync: config.sync
|
|
1026
|
+
}),
|
|
1027
|
+
[authorDID, config.signingKey, config.sync]
|
|
1028
|
+
);
|
|
1029
|
+
let content = React.createElement(
|
|
1030
|
+
PluginRegistryContext.Provider,
|
|
1031
|
+
{ value: pluginRegistry },
|
|
1032
|
+
children
|
|
1033
|
+
);
|
|
1034
|
+
content = React.createElement(DataBridgeContext.Provider, { value: dataBridge }, content);
|
|
1035
|
+
content = React.createElement(
|
|
1036
|
+
TelemetryContext.Provider,
|
|
1037
|
+
{ value: config.telemetry ?? null },
|
|
1038
|
+
content
|
|
1039
|
+
);
|
|
1040
|
+
content = React.createElement(TracingContext.Provider, { value: config.tracing ?? null }, content);
|
|
1041
|
+
content = React.createElement(SecurityProvider, {
|
|
1042
|
+
level: config.security?.level,
|
|
1043
|
+
minVerificationLevel: config.security?.minVerificationLevel,
|
|
1044
|
+
verificationPolicy: config.security?.verificationPolicy,
|
|
1045
|
+
registry: config.security?.registry,
|
|
1046
|
+
keyBundle: config.keyBundle,
|
|
1047
|
+
children: content
|
|
1048
|
+
});
|
|
1049
|
+
content = React.createElement(XNetInternalContext.Provider, { value: internalValue }, content);
|
|
1050
|
+
return React.createElement(XNetContext.Provider, { value }, content);
|
|
1051
|
+
}
|
|
1052
|
+
function useXNet() {
|
|
1053
|
+
const context = useContext5(XNetContext);
|
|
1054
|
+
if (!context) {
|
|
1055
|
+
throw new Error("useXNet must be used within an XNetProvider");
|
|
1056
|
+
}
|
|
1057
|
+
return context;
|
|
1058
|
+
}
|
|
1059
|
+
function useXNetInternal() {
|
|
1060
|
+
return useContext5(XNetInternalContext);
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
// src/hooks/useNodeStore.ts
|
|
1064
|
+
import { useContext as useContext6 } from "react";
|
|
1065
|
+
function useNodeStore() {
|
|
1066
|
+
const context = useContext6(XNetContext);
|
|
1067
|
+
if (context) {
|
|
1068
|
+
return {
|
|
1069
|
+
store: context.nodeStore,
|
|
1070
|
+
isReady: context.nodeStoreReady,
|
|
1071
|
+
error: null
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
throw new Error("useNodeStore must be used within an XNetProvider");
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
export {
|
|
1078
|
+
SecurityProvider,
|
|
1079
|
+
useSecurityContext,
|
|
1080
|
+
useSecurityContextOptional,
|
|
1081
|
+
TelemetryContext,
|
|
1082
|
+
useTelemetryReporter,
|
|
1083
|
+
TRACE_STAGES,
|
|
1084
|
+
TracingContext,
|
|
1085
|
+
useTracingReporter,
|
|
1086
|
+
PluginRegistryContext,
|
|
1087
|
+
usePluginRegistry,
|
|
1088
|
+
usePluginRegistryOptional,
|
|
1089
|
+
usePlugins,
|
|
1090
|
+
useContributions,
|
|
1091
|
+
useViews,
|
|
1092
|
+
useCommands,
|
|
1093
|
+
useSlashCommands,
|
|
1094
|
+
useSidebarItems,
|
|
1095
|
+
useImporters,
|
|
1096
|
+
useEditorExtensions,
|
|
1097
|
+
useEditorExtensionsSafe,
|
|
1098
|
+
useView,
|
|
1099
|
+
useCommand,
|
|
1100
|
+
uploadBackup,
|
|
1101
|
+
downloadBackup,
|
|
1102
|
+
XNetContext,
|
|
1103
|
+
useDataBridge,
|
|
1104
|
+
XNetProvider,
|
|
1105
|
+
useXNet,
|
|
1106
|
+
useXNetInternal,
|
|
1107
|
+
useNodeStore
|
|
1108
|
+
};
|