memeloop 0.1.3 → 0.2.0

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.
Files changed (72) hide show
  1. package/dist/browser.d.cts +14 -14
  2. package/dist/browser.d.ts +14 -14
  3. package/dist/{chunk-5JEV7632.js → chunk-5VYZV33O.js} +2 -1
  4. package/dist/{chunk-5JEV7632.js.map → chunk-5VYZV33O.js.map} +1 -1
  5. package/dist/chunk-ECNKBH5A.js +1235 -0
  6. package/dist/chunk-ECNKBH5A.js.map +1 -0
  7. package/dist/{chunk-MRKY4HGF.js → chunk-EPHRIRAL.js} +20 -11
  8. package/dist/chunk-EPHRIRAL.js.map +1 -0
  9. package/dist/{chunk-P6L3444O.js → chunk-KV6TC5UJ.js} +2 -2
  10. package/dist/chunk-MXRXYGWW.js +1 -0
  11. package/dist/conversation.d.cts +3 -3
  12. package/dist/conversation.d.ts +3 -3
  13. package/dist/device-network-portable.cjs +757 -144
  14. package/dist/device-network-portable.cjs.map +1 -1
  15. package/dist/device-network-portable.d.cts +24 -14
  16. package/dist/device-network-portable.d.ts +24 -14
  17. package/dist/device-network-portable.js +18 -2
  18. package/dist/device-network.cjs +776 -154
  19. package/dist/device-network.cjs.map +1 -1
  20. package/dist/device-network.d.cts +5 -5
  21. package/dist/device-network.d.ts +5 -5
  22. package/dist/device-network.js +19 -3
  23. package/dist/{fetchProvider-WWnWnQFP.d.ts → fetchProvider-BhqRqi5-.d.ts} +1 -1
  24. package/dist/{fetchProvider-toTHy5Nu.d.cts → fetchProvider-HJ_FkORN.d.cts} +1 -1
  25. package/dist/index.cjs +782 -155
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +16 -16
  28. package/dist/index.d.ts +16 -16
  29. package/dist/index.js +24 -6
  30. package/dist/index.js.map +1 -1
  31. package/dist/{interface-IrKBFtL1.d.ts → interface-3C3eG8Hn.d.cts} +13 -1
  32. package/dist/{interface-IrKBFtL1.d.cts → interface-3C3eG8Hn.d.ts} +13 -1
  33. package/dist/llm-providers.d.cts +2 -2
  34. package/dist/llm-providers.d.ts +2 -2
  35. package/dist/loop-api.cjs +1 -0
  36. package/dist/loop-api.cjs.map +1 -1
  37. package/dist/loop-api.d.cts +7 -7
  38. package/dist/loop-api.d.ts +7 -7
  39. package/dist/loop-api.js +2 -2
  40. package/dist/mobile.d.cts +5 -5
  41. package/dist/mobile.d.ts +5 -5
  42. package/dist/{orchestration-portable-BHB_YrNN.d.ts → orchestration-portable-BbkwLbC9.d.ts} +4 -4
  43. package/dist/{orchestration-portable-mvYBvGBb.d.cts → orchestration-portable-DNKzAtNq.d.cts} +4 -4
  44. package/dist/orchestration-portable.d.cts +5 -5
  45. package/dist/orchestration-portable.d.ts +5 -5
  46. package/dist/pairingInvite-DDx_-IAp.d.cts +271 -0
  47. package/dist/pairingInvite-Dkz8CLsb.d.ts +271 -0
  48. package/dist/{promptConcat-0WNFTg4l.d.ts → promptConcat-CUEIA_3T.d.ts} +3 -3
  49. package/dist/{promptConcat-D6wr5V-V.d.cts → promptConcat-VLe5SEN4.d.cts} +3 -3
  50. package/dist/{providerRegistry-CPsktuiX.d.cts → providerRegistry-BDGGs5gz.d.cts} +2 -2
  51. package/dist/{providerRegistry-Bi6pAvSj.d.ts → providerRegistry-CaFK_aUd.d.ts} +2 -2
  52. package/dist/{registry-D0kbxksn.d.cts → registry-DNSEVYug.d.cts} +3 -3
  53. package/dist/{registry-DDbvhlhs.d.ts → registry-yGnYHWhq.d.ts} +3 -3
  54. package/dist/{remoteClient-CBggnRy3.d.cts → remoteClient-BKmQEQY9.d.cts} +1 -1
  55. package/dist/{remoteClient-B_spK019.d.ts → remoteClient-D5hco6cS.d.ts} +1 -1
  56. package/dist/{runtime-UONPTAYK.js → runtime-6EELHWVB.js} +2 -2
  57. package/dist/{scriptDeploymentPipeline-CXJcC82H.d.ts → scriptDeploymentPipeline-B1q-UhrO.d.ts} +1 -1
  58. package/dist/{scriptDeploymentPipeline-yG1ZhVVI.d.cts → scriptDeploymentPipeline-XJzQyYys.d.cts} +1 -1
  59. package/dist/{syncCloudDevices-Ds_Kcgrv.d.cts → syncCloudDevices-BFw_c1g5.d.cts} +6 -5
  60. package/dist/{syncCloudDevices-BEqwgrhD.d.ts → syncCloudDevices-C8fCUhnX.d.ts} +6 -5
  61. package/dist/{types-CdFVSntj.d.ts → types-C8gnhG9M.d.cts} +6 -4
  62. package/dist/{types-7pYti-k9.d.cts → types-CcFFIk8W.d.ts} +6 -4
  63. package/package.json +1 -1
  64. package/dist/chunk-426CJUZP.js +0 -630
  65. package/dist/chunk-426CJUZP.js.map +0 -1
  66. package/dist/chunk-MRKY4HGF.js.map +0 -1
  67. package/dist/chunk-VZKQX5PP.js +0 -1
  68. package/dist/pairingInvite-BXPWY3aM.d.cts +0 -149
  69. package/dist/pairingInvite-Cuz0wuq9.d.ts +0 -149
  70. /package/dist/{chunk-P6L3444O.js.map → chunk-KV6TC5UJ.js.map} +0 -0
  71. /package/dist/{chunk-VZKQX5PP.js.map → chunk-MXRXYGWW.js.map} +0 -0
  72. /package/dist/{runtime-UONPTAYK.js.map → runtime-6EELHWVB.js.map} +0 -0
@@ -1,630 +0,0 @@
1
- import {
2
- OrchestrationError
3
- } from "./chunk-FWSY5HOA.js";
4
-
5
- // src/sync/attachmentRpcCodec.ts
6
- function decodeAttachmentBlobRpc(result) {
7
- if (result == null || typeof result !== "object") return null;
8
- const r = result;
9
- if (r.error || !r.found || typeof r.dataBase64 !== "string") return null;
10
- let data;
11
- try {
12
- const binary = atob(r.dataBase64);
13
- if (!binary.length) return null;
14
- data = Uint8Array.from(binary, (character) => character.charCodeAt(0));
15
- } catch {
16
- return null;
17
- }
18
- return {
19
- data,
20
- filename: typeof r.filename === "string" ? r.filename : "attachment",
21
- mimeType: typeof r.mimeType === "string" ? r.mimeType : "application/octet-stream",
22
- size: typeof r.size === "number" && r.size > 0 ? r.size : data.byteLength
23
- };
24
- }
25
-
26
- // src/sync/chatSyncEngine.ts
27
- var ChatSyncEngine = class {
28
- nodeId;
29
- storage;
30
- getPeers;
31
- versionVector = {};
32
- constructor(options) {
33
- this.nodeId = options.nodeId;
34
- this.storage = options.storage;
35
- this.getPeers = options.peers;
36
- this.versionVector[this.nodeId] = 0;
37
- }
38
- /**
39
- * 在本节点新产生一条消息时调用,增加本节点的 lamportClock。
40
- */
41
- bumpLocalVersion() {
42
- this.versionVector[this.nodeId] = (this.versionVector[this.nodeId] ?? 0) + 1;
43
- }
44
- /**
45
- * 执行一次与所有 peer 的增量同步(metadata 级别),并尽力拉取缺失消息。
46
- */
47
- async syncOnce() {
48
- const peers = this.getPeers();
49
- if (peers.length === 0) return;
50
- const localVersion = { ...this.versionVector };
51
- const pulledConversations = /* @__PURE__ */ new Set();
52
- for (const peer of peers) {
53
- const { remoteVersion, missingForRemote } = await peer.exchangeVersionVector(localVersion);
54
- const missingForLocal = await peer.pullMissingMetadata(this.versionVector);
55
- for (const meta of missingForLocal) {
56
- await this.storage.upsertConversationMetadata(meta);
57
- pulledConversations.add(meta.conversationId);
58
- }
59
- for (const [nodeId, clock] of Object.entries(remoteVersion)) {
60
- const current = this.versionVector[nodeId] ?? 0;
61
- if (clock > current) {
62
- this.versionVector[nodeId] = clock;
63
- }
64
- }
65
- if (missingForRemote.length > 0) {
66
- this.bumpLocalVersion();
67
- }
68
- }
69
- for (const conversationId of pulledConversations) {
70
- await this.pullMessagesForConversationFromPeers(conversationId, peers);
71
- }
72
- }
73
- /**
74
- * Anti-entropy:先 syncOnce,再对已知会话逐个向各 peer 拉取缺失消息(参考审查单「定期对账」)。
75
- */
76
- async antiEntropyOnce() {
77
- await this.syncOnce();
78
- const list = await this.storage.listConversations({ limit: 500 });
79
- const peers = this.getPeers();
80
- for (const meta of list) {
81
- await this.pullMessagesForConversationFromPeers(meta.conversationId, peers);
82
- }
83
- }
84
- async pullMessagesForConversationFromPeers(conversationId, peers) {
85
- const localMsgs = await this.storage.getMessages(conversationId, { mode: "full-content" });
86
- const knownIds = localMsgs.map((m) => m.messageId);
87
- for (const peer of peers) {
88
- if (!peer.pullMissingMessages) continue;
89
- try {
90
- const incoming = await peer.pullMissingMessages(conversationId, knownIds);
91
- if (incoming.length > 0) {
92
- await this.storage.insertMessagesIfAbsent(incoming);
93
- await this.ensureAttachmentsFromMessages(incoming, peers);
94
- }
95
- } catch {
96
- }
97
- }
98
- }
99
- /** 从消息元数据收集 `attachments[].contentHash`,对本地缺失 BLOB 的哈希向各 peer 拉取并 `saveAttachment`。 */
100
- async ensureAttachmentsFromMessages(messages, peers) {
101
- const hashes = /* @__PURE__ */ new Set();
102
- for (const m of messages) {
103
- for (const a of m.attachments ?? []) {
104
- if (a.contentHash) hashes.add(a.contentHash);
105
- }
106
- }
107
- for (const h of hashes) {
108
- const reference = await this.storage.getAttachment(h);
109
- if (reference) {
110
- const reader = this.storage.readAttachmentData?.bind(this.storage);
111
- if (!reader) continue;
112
- const bytes = await reader(h);
113
- if (bytes && bytes.length > 0) continue;
114
- }
115
- for (const peer of peers) {
116
- const pull = peer.pullAttachmentBlob?.bind(peer);
117
- if (!pull) continue;
118
- try {
119
- const blob = await pull(h);
120
- if (blob?.data?.length) {
121
- const ar = {
122
- contentHash: h,
123
- filename: blob.filename,
124
- mimeType: blob.mimeType,
125
- size: blob.size > 0 ? blob.size : blob.data.length
126
- };
127
- await this.storage.saveAttachment(ar, blob.data);
128
- break;
129
- }
130
- } catch {
131
- }
132
- }
133
- }
134
- }
135
- getVersionVector() {
136
- return { ...this.versionVector };
137
- }
138
- getStorage() {
139
- return this.storage;
140
- }
141
- };
142
-
143
- // src/sync/peerNodeAdapter.ts
144
- var PeerNodeSyncAdapter = class {
145
- nodeId;
146
- transport;
147
- constructor(nodeId, transport) {
148
- this.nodeId = nodeId;
149
- this.transport = transport;
150
- }
151
- exchangeVersionVector(localVersion) {
152
- return this.transport.exchangeVersionVector(this.nodeId, localVersion);
153
- }
154
- pullMissingMetadata(sinceVersion) {
155
- return this.transport.pullMissingMetadata(this.nodeId, sinceVersion);
156
- }
157
- pullMissingMessages(conversationId, knownMessageIds) {
158
- const function_ = this.transport.pullMissingMessages?.bind(this.transport);
159
- if (!function_) {
160
- return Promise.resolve([]);
161
- }
162
- return function_(this.nodeId, conversationId, knownMessageIds);
163
- }
164
- pullAttachmentBlob(contentHash) {
165
- const function_ = this.transport.pullAttachmentBlob?.bind(this.transport);
166
- if (!function_) {
167
- return Promise.resolve(null);
168
- }
169
- return function_(this.nodeId, contentHash);
170
- }
171
- };
172
-
173
- // src/device-network/deviceOrchestrationTransport.ts
174
- var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/1.0.0";
175
- var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
176
- function orchestrationTransportError(code, message) {
177
- return new OrchestrationError({
178
- code,
179
- message,
180
- retryable: code === "UNAVAILABLE"
181
- });
182
- }
183
- function assertFrameLimit(limit) {
184
- if (!Number.isSafeInteger(limit) || limit < 256) {
185
- throw new TypeError("maxFrameBytes must be a safe integer of at least 256");
186
- }
187
- }
188
- async function* encodeLines(values, maxFrameBytes) {
189
- const encoder = new TextEncoder();
190
- for await (const value of values) {
191
- const frame = encoder.encode(`${JSON.stringify(value)}
192
- `);
193
- if (frame.byteLength > maxFrameBytes) {
194
- throw orchestrationTransportError(
195
- "EXHAUSTED",
196
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
197
- );
198
- }
199
- yield frame;
200
- }
201
- }
202
- async function* decodeLines(source, maxFrameBytes) {
203
- const decoder = new TextDecoder();
204
- let buffered = "";
205
- for await (const chunk of source) {
206
- buffered += decoder.decode(chunk, { stream: true });
207
- for (; ; ) {
208
- const newline = buffered.indexOf("\n");
209
- if (newline < 0) break;
210
- const line = buffered.slice(0, newline);
211
- buffered = buffered.slice(newline + 1);
212
- if (!line) continue;
213
- if (new TextEncoder().encode(line).byteLength > maxFrameBytes) {
214
- throw orchestrationTransportError(
215
- "EXHAUSTED",
216
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
217
- );
218
- }
219
- try {
220
- yield JSON.parse(line);
221
- } catch {
222
- throw orchestrationTransportError(
223
- "INVALID",
224
- "device orchestration stream contains invalid JSON"
225
- );
226
- }
227
- }
228
- if (new TextEncoder().encode(buffered).byteLength > maxFrameBytes) {
229
- throw orchestrationTransportError(
230
- "EXHAUSTED",
231
- `device orchestration frame exceeds ${maxFrameBytes} bytes`
232
- );
233
- }
234
- }
235
- buffered += decoder.decode();
236
- if (buffered.trim()) {
237
- try {
238
- yield JSON.parse(buffered);
239
- } catch {
240
- throw orchestrationTransportError(
241
- "INVALID",
242
- "device orchestration stream contains invalid trailing JSON"
243
- );
244
- }
245
- }
246
- }
247
- function throwIfAborted(signal) {
248
- if (signal?.aborted) {
249
- throw orchestrationTransportError("CANCELLED", "device orchestration request was cancelled");
250
- }
251
- }
252
- async function openAuthenticatedStream(options) {
253
- const grant = await options.grantProvider?.(options.peerId);
254
- return {
255
- stream: await options.deviceNetwork.openStream(
256
- options.peerId,
257
- DEVICE_ORCHESTRATION_PROTOCOL,
258
- grant
259
- ),
260
- grant
261
- };
262
- }
263
- async function writeRequest(stream, request, grant, maxFrameBytes) {
264
- const envelope = {
265
- type: "memeloop-device-orchestration-request-v1",
266
- request,
267
- ...grant ? { grant } : {}
268
- };
269
- await stream.sink(encodeLines([envelope], maxFrameBytes));
270
- }
271
- function createDeviceOrchestrationTransport(options) {
272
- const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
273
- assertFrameLimit(maxFrameBytes);
274
- return {
275
- async request(request, transportOptions = {}) {
276
- throwIfAborted(transportOptions.signal);
277
- const { stream, grant } = await openAuthenticatedStream(options);
278
- const closeOnAbort = () => {
279
- void stream.close();
280
- };
281
- transportOptions.signal?.addEventListener("abort", closeOnAbort, {
282
- once: true
283
- });
284
- try {
285
- await writeRequest(stream, request, grant, maxFrameBytes);
286
- let response;
287
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
288
- if (response) {
289
- throw orchestrationTransportError(
290
- "INVALID",
291
- "device orchestration request returned multiple responses"
292
- );
293
- }
294
- response = value;
295
- }
296
- throwIfAborted(transportOptions.signal);
297
- if (!response) {
298
- throw orchestrationTransportError(
299
- "UNAVAILABLE",
300
- "device orchestration request returned no response"
301
- );
302
- }
303
- return response;
304
- } finally {
305
- transportOptions.signal?.removeEventListener("abort", closeOnAbort);
306
- await stream.close().catch(() => void 0);
307
- }
308
- },
309
- async *watch(request, transportOptions = {}) {
310
- throwIfAborted(transportOptions.signal);
311
- const { stream, grant } = await openAuthenticatedStream(options);
312
- const closeOnAbort = () => {
313
- void stream.close();
314
- };
315
- transportOptions.signal?.addEventListener("abort", closeOnAbort, {
316
- once: true
317
- });
318
- try {
319
- await writeRequest(stream, request, grant, maxFrameBytes);
320
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
321
- throwIfAborted(transportOptions.signal);
322
- yield value;
323
- }
324
- throwIfAborted(transportOptions.signal);
325
- } finally {
326
- transportOptions.signal?.removeEventListener("abort", closeOnAbort);
327
- await stream.close().catch(() => void 0);
328
- }
329
- }
330
- };
331
- }
332
- async function readSingleRequest(stream, maxFrameBytes) {
333
- let envelope;
334
- for await (const value of decodeLines(stream.source, maxFrameBytes)) {
335
- if (envelope) {
336
- throw orchestrationTransportError(
337
- "INVALID",
338
- "device orchestration stream accepts exactly one request"
339
- );
340
- }
341
- if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v1" || value.request === null || typeof value.request !== "object") {
342
- throw orchestrationTransportError(
343
- "INVALID",
344
- "device orchestration request envelope is invalid"
345
- );
346
- }
347
- envelope = value;
348
- }
349
- if (!envelope) {
350
- throw orchestrationTransportError("INVALID", "device orchestration request is missing");
351
- }
352
- return envelope;
353
- }
354
- function createDeviceOrchestrationStreamHandler(options) {
355
- const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
356
- assertFrameLimit(maxFrameBytes);
357
- return async ({ remotePeerId, stream, authorize }) => {
358
- const abort = new AbortController();
359
- try {
360
- const envelope = await readSingleRequest(stream, maxFrameBytes);
361
- if (authorize && !await authorize(envelope.grant)) {
362
- throw orchestrationTransportError("UNAVAILABLE", "device_not_trusted");
363
- }
364
- const request = envelope.request;
365
- const handler = await options.resolveHandler(remotePeerId);
366
- if (request.operation === "watch") {
367
- await stream.sink(
368
- encodeLines(handler.watch(request, { signal: abort.signal }), maxFrameBytes)
369
- );
370
- } else {
371
- await stream.sink(encodeLines([await handler.request(request)], maxFrameBytes));
372
- }
373
- } finally {
374
- abort.abort();
375
- await stream.close().catch(() => void 0);
376
- }
377
- };
378
- }
379
-
380
- // src/device-network/libp2pSyncProtocol.ts
381
- var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v1";
382
- var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v1";
383
- var syncMethods = /* @__PURE__ */ new Set([
384
- "exchangeVersionVector",
385
- "pullMissingMetadata",
386
- "pullMissingMessages",
387
- "pullAttachmentBlob"
388
- ]);
389
- function isLibp2pSyncRequest(value) {
390
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
391
- const record = value;
392
- return record.type === LIBP2P_SYNC_REQUEST_TYPE && typeof record.id === "string" && typeof record.method === "string" && syncMethods.has(record.method);
393
- }
394
- function isLibp2pSyncResponse(value) {
395
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
396
- const record = value;
397
- if (record.type !== LIBP2P_SYNC_RESPONSE_TYPE || typeof record.id !== "string" || typeof record.ok !== "boolean") return false;
398
- if (record.ok) return "result" in record;
399
- return typeof record.error === "string";
400
- }
401
- function isAttachmentBlobWire(value) {
402
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
403
- const record = value;
404
- return typeof record.dataBase64Url === "string" && typeof record.filename === "string" && typeof record.mimeType === "string" && typeof record.size === "number";
405
- }
406
- async function attachmentBlobFromWire(value) {
407
- if (value === null) return null;
408
- if (!isAttachmentBlobWire(value)) throw new Error("invalid_sync_response");
409
- let data;
410
- try {
411
- const base64 = value.dataBase64Url.replace(/-/g, "+").replace(/_/g, "/");
412
- const paddedBase64 = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
413
- const binary = atob(paddedBase64);
414
- data = Uint8Array.from(binary, (character) => character.charCodeAt(0));
415
- } catch {
416
- throw new Error("invalid_sync_response");
417
- }
418
- return {
419
- data,
420
- filename: value.filename,
421
- mimeType: value.mimeType,
422
- size: value.size
423
- };
424
- }
425
-
426
- // src/device-network/libp2pDeviceSyncTransport.ts
427
- var Libp2pDeviceSyncTransport = class {
428
- nodeId;
429
- deviceNetwork;
430
- grantProvider;
431
- constructor(options) {
432
- this.nodeId = options.nodeId;
433
- this.deviceNetwork = options.deviceNetwork;
434
- this.grantProvider = options.grantProvider;
435
- }
436
- listPeers() {
437
- return this.deviceNetwork.listDevices();
438
- }
439
- exchangeVersionVector(peerId, localVersion) {
440
- return this.request(peerId, "exchangeVersionVector", { localVersion });
441
- }
442
- pullMissingMetadata(peerId, sinceVersion) {
443
- return this.request(peerId, "pullMissingMetadata", { sinceVersion });
444
- }
445
- pullMissingMessages(peerId, conversationId, knownMessageIds) {
446
- return this.request(peerId, "pullMissingMessages", { conversationId, knownMessageIds });
447
- }
448
- async pullAttachmentBlob(peerId, contentHash) {
449
- const result = await this.request(peerId, "pullAttachmentBlob", { contentHash });
450
- return attachmentBlobFromWire(result);
451
- }
452
- async request(peerId, method, parameters) {
453
- const grant = await this.grantProvider?.(peerId);
454
- const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/1.0.0", grant);
455
- const request = {
456
- type: LIBP2P_SYNC_REQUEST_TYPE,
457
- id: crypto.randomUUID(),
458
- method,
459
- params: parameters,
460
- grant
461
- };
462
- await writeJsonToStream(stream, request);
463
- const response = await readJsonFromStream(stream);
464
- await stream.close();
465
- if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
466
- if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
467
- if (!response.ok) throw new Error(response.error);
468
- return response.result;
469
- }
470
- };
471
- async function writeJsonToStream(stream, message) {
472
- const payload = new TextEncoder().encode(JSON.stringify(message));
473
- await stream.sink((async function* () {
474
- yield payload;
475
- })());
476
- }
477
- async function readJsonFromStream(stream) {
478
- const reader = stream.source[Symbol.asyncIterator]();
479
- const result = await reader.next();
480
- if (result.done || !result.value) throw new Error("sync_response_missing");
481
- return JSON.parse(new TextDecoder().decode(result.value));
482
- }
483
-
484
- // src/device-network/libp2pRpcProtocol.ts
485
- var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v1";
486
- var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v1";
487
- function isLibp2pRpcRequest(value) {
488
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
489
- const record = value;
490
- return record.type === LIBP2P_RPC_REQUEST_TYPE && typeof record.id === "string" && typeof record.method === "string";
491
- }
492
- function isLibp2pRpcResponse(value) {
493
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
494
- const record = value;
495
- if (record.type !== LIBP2P_RPC_RESPONSE_TYPE || typeof record.id !== "string" || typeof record.ok !== "boolean") return false;
496
- if (record.ok) return "result" in record;
497
- return typeof record.error === "string";
498
- }
499
-
500
- // src/device-network/localTrustDeviceAuthorizer.ts
501
- var PAIRING_PROTOCOL = "/memeloop/pairing/1.0.0";
502
- var LocalTrustDeviceAuthorizer = class {
503
- constructor(options = {}) {
504
- this.options = options;
505
- this.allowPairingProtocol = options.allowPairingProtocol ?? true;
506
- for (const record of options.trustedDevices ?? []) {
507
- this.trustedDevices.set(record.peerId, record);
508
- }
509
- }
510
- trustedDevices = /* @__PURE__ */ new Map();
511
- allowPairingProtocol;
512
- setTrustedDevice(record) {
513
- this.trustedDevices.set(record.peerId, record);
514
- }
515
- removeTrustedDevice(peerId) {
516
- this.trustedDevices.delete(peerId);
517
- }
518
- async canOpenProtocol(input) {
519
- const record = this.options.getTrustedDevice?.(input.remotePeerId) ?? this.trustedDevices.get(input.remotePeerId);
520
- if (record?.revokedAt) return false;
521
- if (input.protocol === PAIRING_PROTOCOL) return this.allowPairingProtocol;
522
- return record !== void 0;
523
- }
524
- };
525
-
526
- // src/device-network/pairingInvite.ts
527
- var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v1";
528
- var DEVICE_PAIRING_INVITE_TTL_MS = 5 * 6e4;
529
- var MAX_INVITE_LENGTH = 16 * 1024;
530
- var MAX_ADDRESSES = 8;
531
- function requireText(value, name, maxLength) {
532
- const trimmed = value.trim();
533
- if (!trimmed || trimmed.length > maxLength) {
534
- throw new Error(`invalid device pairing invite ${name}`);
535
- }
536
- return trimmed;
537
- }
538
- function validateAddresses(addresses) {
539
- const unique = [...new Set(addresses.map((address) => address.trim()))].filter(Boolean);
540
- if (unique.length === 0 || unique.length > MAX_ADDRESSES || unique.some(
541
- (address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss")
542
- )) {
543
- throw new Error("device pairing invite requires WebSocket multiaddrs");
544
- }
545
- return unique;
546
- }
547
- function createDevicePairingInvite(device, options = {}) {
548
- const createdAt = options.now ?? Date.now();
549
- const ttlMs = options.ttlMs ?? DEVICE_PAIRING_INVITE_TTL_MS;
550
- if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0 || ttlMs > DEVICE_PAIRING_INVITE_TTL_MS) {
551
- throw new Error("invalid device pairing invite lifetime");
552
- }
553
- return {
554
- protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
555
- peerId: requireText(device.peerId, "peerId", 256),
556
- deviceName: requireText(device.displayName, "deviceName", 256),
557
- multiaddrs: validateAddresses(device.multiaddrs ?? []),
558
- createdAt,
559
- expiresAt: createdAt + ttlMs
560
- };
561
- }
562
- function encodeDevicePairingInvite(invite) {
563
- const validated = parseDevicePairingInvite(JSON.stringify(invite), {
564
- now: invite.createdAt
565
- });
566
- return JSON.stringify(validated);
567
- }
568
- function parseDevicePairingInvite(serialized, options = {}) {
569
- if (!serialized || serialized.length > MAX_INVITE_LENGTH) {
570
- throw new Error("invalid device pairing invite length");
571
- }
572
- let parsed;
573
- try {
574
- parsed = JSON.parse(serialized);
575
- } catch {
576
- throw new Error("device pairing invite is not valid JSON");
577
- }
578
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
579
- throw new Error("invalid device pairing invite");
580
- }
581
- const record = parsed;
582
- if (record.protocol !== DEVICE_PAIRING_INVITE_PROTOCOL || typeof record.peerId !== "string" || typeof record.deviceName !== "string" || !Array.isArray(record.multiaddrs) || record.multiaddrs.some((address) => typeof address !== "string") || typeof record.createdAt !== "number" || typeof record.expiresAt !== "number" || !Number.isSafeInteger(record.createdAt) || !Number.isSafeInteger(record.expiresAt) || record.expiresAt <= record.createdAt || record.expiresAt - record.createdAt > DEVICE_PAIRING_INVITE_TTL_MS) {
583
- throw new Error("invalid device pairing invite");
584
- }
585
- if (record.expiresAt <= (options.now ?? Date.now())) {
586
- throw new Error("device pairing invite has expired");
587
- }
588
- return {
589
- protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
590
- peerId: requireText(record.peerId, "peerId", 256),
591
- deviceName: requireText(record.deviceName, "deviceName", 256),
592
- multiaddrs: validateAddresses(record.multiaddrs),
593
- createdAt: record.createdAt,
594
- expiresAt: record.expiresAt
595
- };
596
- }
597
-
598
- // src/sync/protocol.ts
599
- function isConversationMeta(value) {
600
- if (value === null || typeof value !== "object") return false;
601
- const o = value;
602
- return typeof o.conversationId === "string" && typeof o.title === "string" && typeof o.originNodeId === "string" && typeof o.definitionId === "string" && typeof o.messageCount === "number" && typeof o.lastMessageTimestamp === "number" && typeof o.isUserInitiated === "boolean";
603
- }
604
-
605
- export {
606
- decodeAttachmentBlobRpc,
607
- ChatSyncEngine,
608
- PeerNodeSyncAdapter,
609
- DEVICE_ORCHESTRATION_PROTOCOL,
610
- createDeviceOrchestrationTransport,
611
- createDeviceOrchestrationStreamHandler,
612
- LIBP2P_SYNC_REQUEST_TYPE,
613
- LIBP2P_SYNC_RESPONSE_TYPE,
614
- isLibp2pSyncRequest,
615
- isLibp2pSyncResponse,
616
- attachmentBlobFromWire,
617
- Libp2pDeviceSyncTransport,
618
- LIBP2P_RPC_REQUEST_TYPE,
619
- LIBP2P_RPC_RESPONSE_TYPE,
620
- isLibp2pRpcRequest,
621
- isLibp2pRpcResponse,
622
- LocalTrustDeviceAuthorizer,
623
- DEVICE_PAIRING_INVITE_PROTOCOL,
624
- DEVICE_PAIRING_INVITE_TTL_MS,
625
- createDevicePairingInvite,
626
- encodeDevicePairingInvite,
627
- parseDevicePairingInvite,
628
- isConversationMeta
629
- };
630
- //# sourceMappingURL=chunk-426CJUZP.js.map