memeloop 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.cjs +1 -1
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +15 -15
- package/dist/browser.d.ts +15 -15
- package/dist/browser.js +1 -1
- package/dist/{chunk-5JEV7632.js → chunk-5VYZV33O.js} +2 -1
- package/dist/{chunk-5JEV7632.js.map → chunk-5VYZV33O.js.map} +1 -1
- package/dist/chunk-E5R24U4I.js +1292 -0
- package/dist/chunk-E5R24U4I.js.map +1 -0
- package/dist/{chunk-MRKY4HGF.js → chunk-EPHRIRAL.js} +20 -11
- package/dist/chunk-EPHRIRAL.js.map +1 -0
- package/dist/{chunk-P6L3444O.js → chunk-KV6TC5UJ.js} +2 -2
- package/dist/chunk-MXRXYGWW.js +1 -0
- package/dist/{chunk-CCEFAOSH.js → chunk-WHXFUMDE.js} +2 -2
- package/dist/{chunk-CCEFAOSH.js.map → chunk-WHXFUMDE.js.map} +1 -1
- package/dist/conversation.d.cts +3 -3
- package/dist/conversation.d.ts +3 -3
- package/dist/device-network-portable.cjs +814 -144
- package/dist/device-network-portable.cjs.map +1 -1
- package/dist/device-network-portable.d.cts +24 -14
- package/dist/device-network-portable.d.ts +24 -14
- package/dist/device-network-portable.js +18 -2
- package/dist/device-network.cjs +833 -154
- package/dist/device-network.cjs.map +1 -1
- package/dist/device-network.d.cts +5 -5
- package/dist/device-network.d.ts +5 -5
- package/dist/device-network.js +19 -3
- package/dist/{fetchProvider-WWnWnQFP.d.ts → fetchProvider-BhqRqi5-.d.ts} +1 -1
- package/dist/{fetchProvider-toTHy5Nu.d.cts → fetchProvider-HJ_FkORN.d.cts} +1 -1
- package/dist/index.cjs +841 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +26 -8
- package/dist/index.js.map +1 -1
- package/dist/{interface-IrKBFtL1.d.ts → interface-3C3eG8Hn.d.cts} +13 -1
- package/dist/{interface-IrKBFtL1.d.cts → interface-3C3eG8Hn.d.ts} +13 -1
- package/dist/llm-providers.d.cts +2 -2
- package/dist/llm-providers.d.ts +2 -2
- package/dist/loop-api.cjs +1 -0
- package/dist/loop-api.cjs.map +1 -1
- package/dist/loop-api.d.cts +7 -7
- package/dist/loop-api.d.ts +7 -7
- package/dist/loop-api.js +2 -2
- package/dist/mobile.d.cts +5 -5
- package/dist/mobile.d.ts +5 -5
- package/dist/{orchestration-portable-BHB_YrNN.d.ts → orchestration-portable-B4udCSVd.d.ts} +4 -4
- package/dist/{orchestration-portable-mvYBvGBb.d.cts → orchestration-portable-BWaCsCGK.d.cts} +4 -4
- package/dist/orchestration-portable.cjs +1 -1
- package/dist/orchestration-portable.cjs.map +1 -1
- package/dist/orchestration-portable.d.cts +5 -5
- package/dist/orchestration-portable.d.ts +5 -5
- package/dist/orchestration-portable.js +1 -1
- package/dist/pairingInvite-DENOiqa0.d.cts +271 -0
- package/dist/pairingInvite-DZy77i0p.d.ts +271 -0
- package/dist/{promptConcat-0WNFTg4l.d.ts → promptConcat-CUEIA_3T.d.ts} +3 -3
- package/dist/{promptConcat-D6wr5V-V.d.cts → promptConcat-VLe5SEN4.d.cts} +3 -3
- package/dist/{providerRegistry-CPsktuiX.d.cts → providerRegistry-BDGGs5gz.d.cts} +2 -2
- package/dist/{providerRegistry-Bi6pAvSj.d.ts → providerRegistry-CaFK_aUd.d.ts} +2 -2
- package/dist/{registry-D0kbxksn.d.cts → registry-DNSEVYug.d.cts} +3 -3
- package/dist/{registry-DDbvhlhs.d.ts → registry-yGnYHWhq.d.ts} +3 -3
- package/dist/{remoteClient-CBggnRy3.d.cts → remoteClient-DFIyCZEv.d.cts} +2 -2
- package/dist/{remoteClient-B_spK019.d.ts → remoteClient-DwBCoHpc.d.ts} +2 -2
- package/dist/{runtime-UONPTAYK.js → runtime-6EELHWVB.js} +2 -2
- package/dist/{scriptDeploymentPipeline-CXJcC82H.d.ts → scriptDeploymentPipeline-B1q-UhrO.d.ts} +1 -1
- package/dist/{scriptDeploymentPipeline-yG1ZhVVI.d.cts → scriptDeploymentPipeline-XJzQyYys.d.cts} +1 -1
- package/dist/{syncCloudDevices-Ds_Kcgrv.d.cts → syncCloudDevices-BFw_c1g5.d.cts} +6 -5
- package/dist/{syncCloudDevices-BEqwgrhD.d.ts → syncCloudDevices-C8fCUhnX.d.ts} +6 -5
- package/dist/{types-CdFVSntj.d.ts → types-C8gnhG9M.d.cts} +6 -4
- package/dist/{types-7pYti-k9.d.cts → types-CcFFIk8W.d.ts} +6 -4
- package/package.json +1 -1
- package/dist/chunk-426CJUZP.js +0 -630
- package/dist/chunk-426CJUZP.js.map +0 -1
- package/dist/chunk-MRKY4HGF.js.map +0 -1
- package/dist/chunk-VZKQX5PP.js +0 -1
- package/dist/pairingInvite-BXPWY3aM.d.cts +0 -149
- package/dist/pairingInvite-Cuz0wuq9.d.ts +0 -149
- /package/dist/{chunk-P6L3444O.js.map → chunk-KV6TC5UJ.js.map} +0 -0
- /package/dist/{chunk-VZKQX5PP.js.map → chunk-MXRXYGWW.js.map} +0 -0
- /package/dist/{runtime-UONPTAYK.js.map → runtime-6EELHWVB.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -21121,6 +21121,7 @@ function createMemeLoopRuntime(context) {
|
|
|
21121
21121
|
lastMessageTimestamp: now,
|
|
21122
21122
|
messageCount: context.runAgentToolLoop || !options.initialMessage ? 0 : 1,
|
|
21123
21123
|
originNodeId: "local",
|
|
21124
|
+
originClock: 1,
|
|
21124
21125
|
definitionId: options.definitionId,
|
|
21125
21126
|
isUserInitiated: true
|
|
21126
21127
|
};
|
|
@@ -22322,7 +22323,7 @@ var init_remoteClient = __esm({
|
|
|
22322
22323
|
"src/orchestration/remoteClient.ts"() {
|
|
22323
22324
|
"use strict";
|
|
22324
22325
|
init_errors();
|
|
22325
|
-
REMOTE_ORCHESTRATION_PROTOCOL = "memeloop.resource.
|
|
22326
|
+
REMOTE_ORCHESTRATION_PROTOCOL = "memeloop.resource.v2";
|
|
22326
22327
|
REMOTE_OPERATIONS = /* @__PURE__ */ new Set([
|
|
22327
22328
|
"capabilities",
|
|
22328
22329
|
"apply",
|
|
@@ -26462,11 +26463,13 @@ __export(src_exports, {
|
|
|
26462
26463
|
DRIVER_MANIFEST_API_VERSION: () => DRIVER_MANIFEST_API_VERSION,
|
|
26463
26464
|
DRIVER_MANIFEST_KIND: () => DRIVER_MANIFEST_KIND,
|
|
26464
26465
|
DRIVER_REQUEST_API_VERSION: () => DRIVER_REQUEST_API_VERSION,
|
|
26466
|
+
DeviceCloudConnectionCoordinator: () => DeviceCloudConnectionCoordinator,
|
|
26465
26467
|
FLEET_ROLLOUT_API_VERSION: () => FLEET_ROLLOUT_API_VERSION,
|
|
26466
26468
|
FLEET_ROLLOUT_KIND: () => FLEET_ROLLOUT_KIND,
|
|
26467
26469
|
HookRegistry: () => HookRegistry,
|
|
26468
26470
|
IMChannelManager: () => IMChannelManager,
|
|
26469
26471
|
IM_SESSION_TOOL_IDS: () => IM_SESSION_TOOL_IDS,
|
|
26472
|
+
JsonFrameError: () => JsonFrameError,
|
|
26470
26473
|
LIBP2P_RPC_REQUEST_TYPE: () => LIBP2P_RPC_REQUEST_TYPE,
|
|
26471
26474
|
LIBP2P_RPC_RESPONSE_TYPE: () => LIBP2P_RPC_RESPONSE_TYPE,
|
|
26472
26475
|
LIBP2P_SYNC_REQUEST_TYPE: () => LIBP2P_SYNC_REQUEST_TYPE,
|
|
@@ -26484,6 +26487,7 @@ __export(src_exports, {
|
|
|
26484
26487
|
MODEL_ENDPOINT_API_VERSION: () => MODEL_ENDPOINT_API_VERSION,
|
|
26485
26488
|
MODEL_ENDPOINT_KIND: () => MODEL_ENDPOINT_KIND,
|
|
26486
26489
|
MemoryDeviceNetworkService: () => MemoryDeviceNetworkService,
|
|
26490
|
+
MemoryDeviceSyncStateStore: () => MemoryDeviceSyncStateStore,
|
|
26487
26491
|
NETWORK_ATTACHMENT_API_VERSION: () => NETWORK_ATTACHMENT_API_VERSION,
|
|
26488
26492
|
NETWORK_ATTACHMENT_KIND: () => NETWORK_ATTACHMENT_KIND,
|
|
26489
26493
|
NETWORK_CLASS_API_VERSION: () => NETWORK_CLASS_API_VERSION,
|
|
@@ -26582,6 +26586,7 @@ __export(src_exports, {
|
|
|
26582
26586
|
canDriverSatisfyClass: () => canDriverSatisfyClass,
|
|
26583
26587
|
cancelPendingApprovals: () => cancelPendingApprovals,
|
|
26584
26588
|
canonicalControlStoreValue: () => canonicalControlStoreValue,
|
|
26589
|
+
canonicalDevicePairingInviteBytes: () => canonicalDevicePairingInviteBytes,
|
|
26585
26590
|
canonicalDriverValue: () => canonicalDriverValue,
|
|
26586
26591
|
canonicalWorkerBootstrapDescriptorBytes: () => canonicalWorkerBootstrapDescriptorBytes,
|
|
26587
26592
|
canonicalWorkerProtocolRequestBytes: () => canonicalWorkerProtocolRequestBytes,
|
|
@@ -26591,6 +26596,7 @@ __export(src_exports, {
|
|
|
26591
26596
|
clearHooks: () => clearHooks,
|
|
26592
26597
|
clearPluginRegistrations: () => clearPluginRegistrations,
|
|
26593
26598
|
compactMessages: () => compactMessages,
|
|
26599
|
+
computeMissingVersionRanges: () => computeMissingVersionRanges,
|
|
26594
26600
|
consumeWorkloadCapabilityGrant: () => consumeWorkloadCapabilityGrant,
|
|
26595
26601
|
containsSecrets: () => containsSecrets,
|
|
26596
26602
|
controlStoreApplyMatches: () => controlStoreApplyMatches,
|
|
@@ -26678,6 +26684,7 @@ __export(src_exports, {
|
|
|
26678
26684
|
createInProcessLoopRuntimeDriver: () => createInProcessLoopRuntimeDriver,
|
|
26679
26685
|
createInProcessToolExecutionDriver: () => createInProcessToolExecutionDriver,
|
|
26680
26686
|
createInstanceDeltaFromDefinition: () => createInstanceDeltaFromDefinition,
|
|
26687
|
+
createJsonFrameReader: () => createJsonFrameReader,
|
|
26681
26688
|
createLoopRuntimeManagementConformanceSuite: () => createLoopRuntimeManagementConformanceSuite,
|
|
26682
26689
|
createManagedArtifactDriverAdapter: () => createManagedArtifactDriverAdapter,
|
|
26683
26690
|
createManagedCredentialBrokerAdapter: () => createManagedCredentialBrokerAdapter,
|
|
@@ -26759,6 +26766,8 @@ __export(src_exports, {
|
|
|
26759
26766
|
detectMimeConfusion: () => detectMimeConfusion,
|
|
26760
26767
|
digestNormalizedScript: () => digestNormalizedScript,
|
|
26761
26768
|
encodeDevicePairingInvite: () => encodeDevicePairingInvite,
|
|
26769
|
+
encodeJsonFrame: () => encodeJsonFrame,
|
|
26770
|
+
encodeJsonFrames: () => encodeJsonFrames,
|
|
26762
26771
|
enrollWorker: () => enrollWorker,
|
|
26763
26772
|
evaluateApproval: () => evaluateApproval,
|
|
26764
26773
|
evaluateToolAdmission: () => evaluateToolAdmission,
|
|
@@ -27163,48 +27172,85 @@ function decodeAttachmentBlobRpc(result) {
|
|
|
27163
27172
|
};
|
|
27164
27173
|
}
|
|
27165
27174
|
|
|
27175
|
+
// src/device-network/deviceSyncStateStore.ts
|
|
27176
|
+
var MemoryDeviceSyncStateStore = class {
|
|
27177
|
+
versionVector;
|
|
27178
|
+
constructor(initial = {}) {
|
|
27179
|
+
this.versionVector = { ...initial };
|
|
27180
|
+
}
|
|
27181
|
+
async loadVersionVector() {
|
|
27182
|
+
return { ...this.versionVector };
|
|
27183
|
+
}
|
|
27184
|
+
async saveVersionVector(versionVector) {
|
|
27185
|
+
this.versionVector = { ...versionVector };
|
|
27186
|
+
}
|
|
27187
|
+
};
|
|
27188
|
+
|
|
27166
27189
|
// src/sync/chatSyncEngine.ts
|
|
27167
27190
|
var ChatSyncEngine = class {
|
|
27168
27191
|
nodeId;
|
|
27169
27192
|
storage;
|
|
27170
27193
|
getPeers;
|
|
27194
|
+
stateStore;
|
|
27171
27195
|
versionVector = {};
|
|
27196
|
+
loadStatePromise;
|
|
27197
|
+
stateMutation = Promise.resolve();
|
|
27172
27198
|
constructor(options) {
|
|
27173
27199
|
this.nodeId = options.nodeId;
|
|
27174
27200
|
this.storage = options.storage;
|
|
27175
27201
|
this.getPeers = options.peers;
|
|
27176
|
-
this.
|
|
27202
|
+
this.stateStore = options.stateStore ?? new MemoryDeviceSyncStateStore();
|
|
27177
27203
|
}
|
|
27178
27204
|
/**
|
|
27179
27205
|
* 在本节点新产生一条消息时调用,增加本节点的 lamportClock。
|
|
27180
27206
|
*/
|
|
27181
27207
|
bumpLocalVersion() {
|
|
27182
|
-
|
|
27208
|
+
let nextClock = 0;
|
|
27209
|
+
const mutation = this.stateMutation.then(async () => {
|
|
27210
|
+
await this.ensureStateLoaded();
|
|
27211
|
+
nextClock = (this.versionVector[this.nodeId] ?? 0) + 1;
|
|
27212
|
+
this.versionVector[this.nodeId] = nextClock;
|
|
27213
|
+
await this.stateStore.saveVersionVector(this.versionVector);
|
|
27214
|
+
});
|
|
27215
|
+
this.stateMutation = mutation.catch(() => void 0);
|
|
27216
|
+
return mutation.then(() => nextClock);
|
|
27183
27217
|
}
|
|
27184
27218
|
/**
|
|
27185
27219
|
* 执行一次与所有 peer 的增量同步(metadata 级别),并尽力拉取缺失消息。
|
|
27186
27220
|
*/
|
|
27187
27221
|
async syncOnce() {
|
|
27222
|
+
await this.ensureStateLoaded();
|
|
27223
|
+
await this.stateMutation;
|
|
27224
|
+
await this.reconcileVersionVectorFromStorage();
|
|
27188
27225
|
const peers = this.getPeers();
|
|
27189
27226
|
if (peers.length === 0) return;
|
|
27190
|
-
const localVersion = { ...this.versionVector };
|
|
27191
27227
|
const pulledConversations = /* @__PURE__ */ new Set();
|
|
27192
27228
|
for (const peer of peers) {
|
|
27193
|
-
const { remoteVersion
|
|
27194
|
-
|
|
27195
|
-
|
|
27196
|
-
await this.
|
|
27197
|
-
|
|
27198
|
-
|
|
27229
|
+
const { remoteVersion } = await peer.exchangeVersionVector({ ...this.versionVector });
|
|
27230
|
+
let cursor;
|
|
27231
|
+
do {
|
|
27232
|
+
const page = await peer.pullMissingMetadata(this.versionVector, cursor);
|
|
27233
|
+
for (const meta of page.items) {
|
|
27234
|
+
await this.storage.upsertConversationMetadata(meta);
|
|
27235
|
+
pulledConversations.add(meta.conversationId);
|
|
27236
|
+
}
|
|
27237
|
+
cursor = page.nextCursor;
|
|
27238
|
+
} while (cursor !== void 0);
|
|
27199
27239
|
for (const [nodeId, clock] of Object.entries(remoteVersion)) {
|
|
27200
27240
|
const current = this.versionVector[nodeId] ?? 0;
|
|
27201
|
-
if (clock > current)
|
|
27202
|
-
this.versionVector[nodeId] = clock;
|
|
27203
|
-
}
|
|
27241
|
+
if (clock > current) this.versionVector[nodeId] = clock;
|
|
27204
27242
|
}
|
|
27205
|
-
|
|
27206
|
-
|
|
27243
|
+
await this.stateStore.saveVersionVector(this.versionVector);
|
|
27244
|
+
}
|
|
27245
|
+
const pageSize = 100;
|
|
27246
|
+
let offset = 0;
|
|
27247
|
+
for (; ; ) {
|
|
27248
|
+
const conversations = await this.storage.listConversations({ limit: pageSize, offset });
|
|
27249
|
+
for (const conversation of conversations) {
|
|
27250
|
+
pulledConversations.add(conversation.conversationId);
|
|
27207
27251
|
}
|
|
27252
|
+
if (conversations.length < pageSize) break;
|
|
27253
|
+
offset += conversations.length;
|
|
27208
27254
|
}
|
|
27209
27255
|
for (const conversationId of pulledConversations) {
|
|
27210
27256
|
await this.pullMessagesForConversationFromPeers(conversationId, peers);
|
|
@@ -27215,11 +27261,6 @@ var ChatSyncEngine = class {
|
|
|
27215
27261
|
*/
|
|
27216
27262
|
async antiEntropyOnce() {
|
|
27217
27263
|
await this.syncOnce();
|
|
27218
|
-
const list = await this.storage.listConversations({ limit: 500 });
|
|
27219
|
-
const peers = this.getPeers();
|
|
27220
|
-
for (const meta of list) {
|
|
27221
|
-
await this.pullMessagesForConversationFromPeers(meta.conversationId, peers);
|
|
27222
|
-
}
|
|
27223
27264
|
}
|
|
27224
27265
|
async pullMessagesForConversationFromPeers(conversationId, peers) {
|
|
27225
27266
|
const localMsgs = await this.storage.getMessages(conversationId, { mode: "full-content" });
|
|
@@ -27272,12 +27313,39 @@ var ChatSyncEngine = class {
|
|
|
27272
27313
|
}
|
|
27273
27314
|
}
|
|
27274
27315
|
}
|
|
27275
|
-
getVersionVector() {
|
|
27316
|
+
async getVersionVector() {
|
|
27317
|
+
await this.ensureStateLoaded();
|
|
27318
|
+
await this.stateMutation;
|
|
27276
27319
|
return { ...this.versionVector };
|
|
27277
27320
|
}
|
|
27278
27321
|
getStorage() {
|
|
27279
27322
|
return this.storage;
|
|
27280
27323
|
}
|
|
27324
|
+
ensureStateLoaded() {
|
|
27325
|
+
this.loadStatePromise ??= (async () => {
|
|
27326
|
+
const stored = await this.stateStore.loadVersionVector();
|
|
27327
|
+
this.versionVector = { ...stored, [this.nodeId]: stored[this.nodeId] ?? 0 };
|
|
27328
|
+
})();
|
|
27329
|
+
return this.loadStatePromise;
|
|
27330
|
+
}
|
|
27331
|
+
async reconcileVersionVectorFromStorage() {
|
|
27332
|
+
const pageSize = 100;
|
|
27333
|
+
let offset = 0;
|
|
27334
|
+
let changed = false;
|
|
27335
|
+
for (; ; ) {
|
|
27336
|
+
const conversations = await this.storage.listConversations({ limit: pageSize, offset });
|
|
27337
|
+
for (const conversation of conversations) {
|
|
27338
|
+
const current = this.versionVector[conversation.originNodeId] ?? 0;
|
|
27339
|
+
if (conversation.originClock > current) {
|
|
27340
|
+
this.versionVector[conversation.originNodeId] = conversation.originClock;
|
|
27341
|
+
changed = true;
|
|
27342
|
+
}
|
|
27343
|
+
}
|
|
27344
|
+
if (conversations.length < pageSize) break;
|
|
27345
|
+
offset += conversations.length;
|
|
27346
|
+
}
|
|
27347
|
+
if (changed) await this.stateStore.saveVersionVector(this.versionVector);
|
|
27348
|
+
}
|
|
27281
27349
|
};
|
|
27282
27350
|
|
|
27283
27351
|
// src/sync/peerNodeAdapter.ts
|
|
@@ -27291,8 +27359,8 @@ var PeerNodeSyncAdapter = class {
|
|
|
27291
27359
|
exchangeVersionVector(localVersion) {
|
|
27292
27360
|
return this.transport.exchangeVersionVector(this.nodeId, localVersion);
|
|
27293
27361
|
}
|
|
27294
|
-
pullMissingMetadata(sinceVersion) {
|
|
27295
|
-
return this.transport.pullMissingMetadata(this.nodeId, sinceVersion);
|
|
27362
|
+
pullMissingMetadata(sinceVersion, cursor) {
|
|
27363
|
+
return this.transport.pullMissingMetadata(this.nodeId, sinceVersion, cursor);
|
|
27296
27364
|
}
|
|
27297
27365
|
pullMissingMessages(conversationId, knownMessageIds) {
|
|
27298
27366
|
const function_ = this.transport.pullMissingMessages?.bind(this.transport);
|
|
@@ -27385,6 +27453,7 @@ async function runStorageConformance(storage, options = {}) {
|
|
|
27385
27453
|
lastMessageTimestamp: Date.now(),
|
|
27386
27454
|
messageCount: 0,
|
|
27387
27455
|
originNodeId: "local",
|
|
27456
|
+
originClock: 1,
|
|
27388
27457
|
definitionId: "conformance-agent",
|
|
27389
27458
|
isUserInitiated: true
|
|
27390
27459
|
});
|
|
@@ -28696,6 +28765,7 @@ async function upsertConversationForRemoteTurn(input) {
|
|
|
28696
28765
|
lastMessageTimestamp: now,
|
|
28697
28766
|
messageCount: 0,
|
|
28698
28767
|
originNodeId: input.localNodeId ?? "remote",
|
|
28768
|
+
originClock: 1,
|
|
28699
28769
|
definitionId: input.definitionId,
|
|
28700
28770
|
isUserInitiated: true
|
|
28701
28771
|
});
|
|
@@ -28747,82 +28817,460 @@ function isChatMessage(value) {
|
|
|
28747
28817
|
function isConversationMetaLike(value) {
|
|
28748
28818
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
28749
28819
|
const record2 = value;
|
|
28750
|
-
return typeof record2.conversationId === "string" && typeof record2.title === "string" && typeof record2.lastMessageTimestamp === "number" && typeof record2.messageCount === "number" && typeof record2.originNodeId === "string" && typeof record2.definitionId === "string" && typeof record2.isUserInitiated === "boolean";
|
|
28820
|
+
return typeof record2.conversationId === "string" && typeof record2.title === "string" && typeof record2.lastMessageTimestamp === "number" && typeof record2.messageCount === "number" && typeof record2.originNodeId === "string" && typeof record2.originClock === "number" && typeof record2.definitionId === "string" && typeof record2.isUserInitiated === "boolean";
|
|
28821
|
+
}
|
|
28822
|
+
|
|
28823
|
+
// src/device-network/deviceCloudConnectionCoordinator.ts
|
|
28824
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
28825
|
+
var DEFAULT_INITIAL_BACKOFF_MS = 1e3;
|
|
28826
|
+
var DEFAULT_MAX_BACKOFF_MS = 6e4;
|
|
28827
|
+
var DEFAULT_JITTER_RATIO = 0.2;
|
|
28828
|
+
function emptyComponents() {
|
|
28829
|
+
return {
|
|
28830
|
+
authorizer: "not-run",
|
|
28831
|
+
registration: "not-run",
|
|
28832
|
+
relay: "not-run",
|
|
28833
|
+
heartbeat: "not-run",
|
|
28834
|
+
directory: "not-run"
|
|
28835
|
+
};
|
|
28751
28836
|
}
|
|
28837
|
+
function assertNonNegativeFinite(value, name) {
|
|
28838
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
28839
|
+
throw new TypeError(`${name} must be a finite non-negative number`);
|
|
28840
|
+
}
|
|
28841
|
+
}
|
|
28842
|
+
var DeviceCloudConnectionCoordinator = class {
|
|
28843
|
+
constructor(options) {
|
|
28844
|
+
this.options = options;
|
|
28845
|
+
this.configuration = options.configuration;
|
|
28846
|
+
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
28847
|
+
this.initialBackoffMs = options.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
28848
|
+
this.maxBackoffMs = options.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
|
|
28849
|
+
this.jitterRatio = options.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
28850
|
+
this.now = options.now ?? Date.now;
|
|
28851
|
+
this.random = options.random ?? Math.random;
|
|
28852
|
+
assertNonNegativeFinite(this.heartbeatIntervalMs, "heartbeatIntervalMs");
|
|
28853
|
+
assertNonNegativeFinite(this.initialBackoffMs, "initialBackoffMs");
|
|
28854
|
+
assertNonNegativeFinite(this.maxBackoffMs, "maxBackoffMs");
|
|
28855
|
+
assertNonNegativeFinite(this.jitterRatio, "jitterRatio");
|
|
28856
|
+
if (this.jitterRatio > 1) throw new TypeError("jitterRatio must not exceed 1");
|
|
28857
|
+
this.resetSnapshot();
|
|
28858
|
+
}
|
|
28859
|
+
configuration;
|
|
28860
|
+
generation = 0;
|
|
28861
|
+
controller = new AbortController();
|
|
28862
|
+
timer;
|
|
28863
|
+
inFlight;
|
|
28864
|
+
started = false;
|
|
28865
|
+
consecutiveFailures = 0;
|
|
28866
|
+
snapshotValue = {
|
|
28867
|
+
status: "not-configured",
|
|
28868
|
+
generation: 0,
|
|
28869
|
+
components: emptyComponents()
|
|
28870
|
+
};
|
|
28871
|
+
heartbeatIntervalMs;
|
|
28872
|
+
initialBackoffMs;
|
|
28873
|
+
maxBackoffMs;
|
|
28874
|
+
jitterRatio;
|
|
28875
|
+
now;
|
|
28876
|
+
random;
|
|
28877
|
+
get snapshot() {
|
|
28878
|
+
return {
|
|
28879
|
+
...this.snapshotValue,
|
|
28880
|
+
components: { ...this.snapshotValue.components }
|
|
28881
|
+
};
|
|
28882
|
+
}
|
|
28883
|
+
async start() {
|
|
28884
|
+
if (this.started) return this.runNow();
|
|
28885
|
+
this.started = true;
|
|
28886
|
+
await this.runNow();
|
|
28887
|
+
}
|
|
28888
|
+
async stop() {
|
|
28889
|
+
this.started = false;
|
|
28890
|
+
this.clearTimer();
|
|
28891
|
+
const stoppedController = this.controller;
|
|
28892
|
+
stoppedController.abort(new Error("device cloud coordinator stopped"));
|
|
28893
|
+
await this.inFlight?.catch(() => void 0);
|
|
28894
|
+
if (this.controller === stoppedController) {
|
|
28895
|
+
this.controller = new AbortController();
|
|
28896
|
+
}
|
|
28897
|
+
}
|
|
28898
|
+
async setConfiguration(configuration) {
|
|
28899
|
+
this.configuration = configuration;
|
|
28900
|
+
this.generation += 1;
|
|
28901
|
+
this.consecutiveFailures = 0;
|
|
28902
|
+
this.clearTimer();
|
|
28903
|
+
this.controller.abort(new Error("device cloud configuration changed"));
|
|
28904
|
+
this.controller = new AbortController();
|
|
28905
|
+
this.inFlight = void 0;
|
|
28906
|
+
this.resetSnapshot();
|
|
28907
|
+
if (this.started && this.options.adapter.isConfigured(configuration)) await this.runNow();
|
|
28908
|
+
}
|
|
28909
|
+
runNow() {
|
|
28910
|
+
if (this.inFlight) return this.inFlight;
|
|
28911
|
+
if (!this.options.adapter.isConfigured(this.configuration)) {
|
|
28912
|
+
this.resetSnapshot();
|
|
28913
|
+
return Promise.resolve();
|
|
28914
|
+
}
|
|
28915
|
+
const generation = this.generation;
|
|
28916
|
+
const configuration = this.configuration;
|
|
28917
|
+
const signal = this.controller.signal;
|
|
28918
|
+
const run = this.maintain(configuration, generation, signal);
|
|
28919
|
+
const tracked = run.finally(() => {
|
|
28920
|
+
if (this.inFlight === tracked) this.inFlight = void 0;
|
|
28921
|
+
});
|
|
28922
|
+
this.inFlight = tracked;
|
|
28923
|
+
return tracked;
|
|
28924
|
+
}
|
|
28925
|
+
async maintain(configuration, generation, signal) {
|
|
28926
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28927
|
+
await this.updateSnapshot(generation, {
|
|
28928
|
+
status: "connecting",
|
|
28929
|
+
lastError: void 0,
|
|
28930
|
+
nextRetryAt: void 0
|
|
28931
|
+
});
|
|
28932
|
+
try {
|
|
28933
|
+
if (this.snapshotValue.components.authorizer !== "ready") {
|
|
28934
|
+
await this.runStep("authorizer", generation, signal, () => this.options.adapter.ensureAuthorizer(configuration, signal));
|
|
28935
|
+
}
|
|
28936
|
+
if (this.snapshotValue.components.registration !== "ready") {
|
|
28937
|
+
await this.runStep("registration", generation, signal, () => this.options.adapter.registerDevice(configuration, signal));
|
|
28938
|
+
}
|
|
28939
|
+
let relayError;
|
|
28940
|
+
try {
|
|
28941
|
+
await this.runStep("relay", generation, signal, () => this.options.adapter.ensureRelay(configuration, signal));
|
|
28942
|
+
} catch (error2) {
|
|
28943
|
+
relayError = error2;
|
|
28944
|
+
this.options.logWarning?.("Cloud relay maintenance failed", error2);
|
|
28945
|
+
}
|
|
28946
|
+
await this.runStep("heartbeat", generation, signal, () => this.options.adapter.heartbeat(configuration, signal));
|
|
28947
|
+
let directoryError;
|
|
28948
|
+
try {
|
|
28949
|
+
await this.runStep("directory", generation, signal, () => this.options.adapter.syncDirectory(configuration, signal));
|
|
28950
|
+
} catch (error2) {
|
|
28951
|
+
directoryError = error2;
|
|
28952
|
+
this.options.logWarning?.("Cloud directory synchronization failed", error2);
|
|
28953
|
+
}
|
|
28954
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28955
|
+
this.consecutiveFailures = 0;
|
|
28956
|
+
const relayRequired = this.options.adapter.relayRequiredForOnline(configuration);
|
|
28957
|
+
await this.updateSnapshot(generation, {
|
|
28958
|
+
status: directoryError || relayRequired && relayError ? "degraded" : "online",
|
|
28959
|
+
lastError: directoryError ?? relayError,
|
|
28960
|
+
nextRetryAt: this.now() + this.heartbeatIntervalMs
|
|
28961
|
+
});
|
|
28962
|
+
this.schedule(this.heartbeatIntervalMs, generation);
|
|
28963
|
+
} catch (error2) {
|
|
28964
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28965
|
+
this.consecutiveFailures += 1;
|
|
28966
|
+
if (this.snapshotValue.components.heartbeat === "failed") {
|
|
28967
|
+
this.snapshotValue.components.registration = "not-run";
|
|
28968
|
+
}
|
|
28969
|
+
const status = this.options.adapter.classifyError?.(error2) ?? "offline";
|
|
28970
|
+
const delay = this.backoffDelay();
|
|
28971
|
+
await this.updateSnapshot(generation, {
|
|
28972
|
+
status,
|
|
28973
|
+
lastError: error2,
|
|
28974
|
+
nextRetryAt: this.now() + delay
|
|
28975
|
+
});
|
|
28976
|
+
this.schedule(delay, generation);
|
|
28977
|
+
throw error2;
|
|
28978
|
+
}
|
|
28979
|
+
}
|
|
28980
|
+
async runStep(component, generation, signal, operation) {
|
|
28981
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28982
|
+
await this.setComponent(component, "pending", generation);
|
|
28983
|
+
try {
|
|
28984
|
+
const result = await operation();
|
|
28985
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28986
|
+
await result?.commit?.();
|
|
28987
|
+
if (!this.isCurrent(generation, signal)) return;
|
|
28988
|
+
await this.setComponent(component, "ready", generation);
|
|
28989
|
+
} catch (error2) {
|
|
28990
|
+
if (this.isCurrent(generation, signal)) await this.setComponent(component, "failed", generation);
|
|
28991
|
+
throw error2;
|
|
28992
|
+
}
|
|
28993
|
+
}
|
|
28994
|
+
backoffDelay() {
|
|
28995
|
+
const exponential = Math.min(
|
|
28996
|
+
this.maxBackoffMs,
|
|
28997
|
+
this.initialBackoffMs * 2 ** Math.max(0, this.consecutiveFailures - 1)
|
|
28998
|
+
);
|
|
28999
|
+
const jitter = exponential * this.jitterRatio * (this.random() * 2 - 1);
|
|
29000
|
+
return Math.max(0, Math.round(exponential + jitter));
|
|
29001
|
+
}
|
|
29002
|
+
schedule(delayMs, generation) {
|
|
29003
|
+
if (!this.started || generation !== this.generation) return;
|
|
29004
|
+
this.clearTimer();
|
|
29005
|
+
this.timer = setTimeout(() => {
|
|
29006
|
+
if (generation !== this.generation) return;
|
|
29007
|
+
void this.runNow().catch((error2) => {
|
|
29008
|
+
this.options.logWarning?.("Cloud maintenance failed", error2);
|
|
29009
|
+
});
|
|
29010
|
+
}, delayMs);
|
|
29011
|
+
}
|
|
29012
|
+
clearTimer() {
|
|
29013
|
+
if (this.timer) clearTimeout(this.timer);
|
|
29014
|
+
this.timer = void 0;
|
|
29015
|
+
}
|
|
29016
|
+
isCurrent(generation, signal) {
|
|
29017
|
+
return generation === this.generation && !signal.aborted;
|
|
29018
|
+
}
|
|
29019
|
+
async setComponent(component, status, generation) {
|
|
29020
|
+
if (generation !== this.generation) return;
|
|
29021
|
+
this.snapshotValue = {
|
|
29022
|
+
...this.snapshotValue,
|
|
29023
|
+
components: { ...this.snapshotValue.components, [component]: status }
|
|
29024
|
+
};
|
|
29025
|
+
await this.emit();
|
|
29026
|
+
}
|
|
29027
|
+
async updateSnapshot(generation, update) {
|
|
29028
|
+
if (generation !== this.generation) return;
|
|
29029
|
+
this.snapshotValue = { ...this.snapshotValue, ...update };
|
|
29030
|
+
await this.emit();
|
|
29031
|
+
}
|
|
29032
|
+
resetSnapshot() {
|
|
29033
|
+
this.snapshotValue = {
|
|
29034
|
+
status: this.options.adapter.isConfigured(this.configuration) ? "offline" : "not-configured",
|
|
29035
|
+
generation: this.generation,
|
|
29036
|
+
components: emptyComponents()
|
|
29037
|
+
};
|
|
29038
|
+
void this.emit();
|
|
29039
|
+
}
|
|
29040
|
+
async emit() {
|
|
29041
|
+
await this.options.onStatus?.(this.snapshot);
|
|
29042
|
+
}
|
|
29043
|
+
};
|
|
28752
29044
|
|
|
28753
29045
|
// src/device-network/deviceOrchestrationTransport.ts
|
|
28754
29046
|
init_errors();
|
|
28755
|
-
|
|
28756
|
-
|
|
28757
|
-
|
|
28758
|
-
|
|
28759
|
-
|
|
28760
|
-
message,
|
|
28761
|
-
|
|
28762
|
-
|
|
29047
|
+
|
|
29048
|
+
// src/device-network/jsonFrame.ts
|
|
29049
|
+
var JsonFrameError = class extends Error {
|
|
29050
|
+
code;
|
|
29051
|
+
constructor(code, message, options) {
|
|
29052
|
+
super(message, options);
|
|
29053
|
+
this.name = "JsonFrameError";
|
|
29054
|
+
this.code = code;
|
|
29055
|
+
}
|
|
29056
|
+
};
|
|
29057
|
+
var HEADER_BYTES = 4;
|
|
29058
|
+
var MAX_UINT32 = 4294967295;
|
|
29059
|
+
function assertPositiveInteger(value, name) {
|
|
29060
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
29061
|
+
throw new TypeError(`${name} must be a positive safe integer`);
|
|
29062
|
+
}
|
|
28763
29063
|
}
|
|
28764
|
-
function
|
|
28765
|
-
|
|
28766
|
-
|
|
29064
|
+
function validateMaxPayloadBytes(maxPayloadBytes) {
|
|
29065
|
+
assertPositiveInteger(maxPayloadBytes, "maxPayloadBytes");
|
|
29066
|
+
if (maxPayloadBytes > MAX_UINT32) {
|
|
29067
|
+
throw new TypeError("maxPayloadBytes must fit in an unsigned 32-bit frame length");
|
|
28767
29068
|
}
|
|
28768
29069
|
}
|
|
28769
|
-
|
|
28770
|
-
|
|
29070
|
+
function encodeJsonFrame(value, maxPayloadBytes = MAX_UINT32) {
|
|
29071
|
+
validateMaxPayloadBytes(maxPayloadBytes);
|
|
29072
|
+
let serialized;
|
|
29073
|
+
try {
|
|
29074
|
+
serialized = JSON.stringify(value);
|
|
29075
|
+
} catch (error2) {
|
|
29076
|
+
throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized", {
|
|
29077
|
+
cause: error2
|
|
29078
|
+
});
|
|
29079
|
+
}
|
|
29080
|
+
if (serialized === void 0) {
|
|
29081
|
+
throw new JsonFrameError("INVALID_JSON", "JSON frame value cannot be serialized");
|
|
29082
|
+
}
|
|
29083
|
+
const payload = new TextEncoder().encode(serialized);
|
|
29084
|
+
if (payload.byteLength > maxPayloadBytes) {
|
|
29085
|
+
throw new JsonFrameError(
|
|
29086
|
+
"FRAME_TOO_LARGE",
|
|
29087
|
+
`JSON frame payload exceeds ${maxPayloadBytes} bytes`
|
|
29088
|
+
);
|
|
29089
|
+
}
|
|
29090
|
+
const frame = new Uint8Array(HEADER_BYTES + payload.byteLength);
|
|
29091
|
+
new DataView(frame.buffer, frame.byteOffset, HEADER_BYTES).setUint32(0, payload.byteLength, false);
|
|
29092
|
+
frame.set(payload, HEADER_BYTES);
|
|
29093
|
+
return frame;
|
|
29094
|
+
}
|
|
29095
|
+
async function* encodeJsonFrames(values, maxPayloadBytes = MAX_UINT32) {
|
|
28771
29096
|
for await (const value of values) {
|
|
28772
|
-
|
|
28773
|
-
`);
|
|
28774
|
-
if (frame.byteLength > maxFrameBytes) {
|
|
28775
|
-
throw orchestrationTransportError(
|
|
28776
|
-
"EXHAUSTED",
|
|
28777
|
-
`device orchestration frame exceeds ${maxFrameBytes} bytes`
|
|
28778
|
-
);
|
|
28779
|
-
}
|
|
28780
|
-
yield frame;
|
|
29097
|
+
yield encodeJsonFrame(value, maxPayloadBytes);
|
|
28781
29098
|
}
|
|
28782
29099
|
}
|
|
28783
|
-
|
|
28784
|
-
|
|
28785
|
-
|
|
28786
|
-
|
|
28787
|
-
|
|
28788
|
-
|
|
28789
|
-
|
|
28790
|
-
|
|
28791
|
-
|
|
28792
|
-
|
|
28793
|
-
|
|
28794
|
-
|
|
28795
|
-
|
|
28796
|
-
|
|
28797
|
-
|
|
29100
|
+
function concatBytes(left, right) {
|
|
29101
|
+
if (left.byteLength === 0) return right.slice();
|
|
29102
|
+
if (right.byteLength === 0) return left;
|
|
29103
|
+
const combined = new Uint8Array(left.byteLength + right.byteLength);
|
|
29104
|
+
combined.set(left);
|
|
29105
|
+
combined.set(right, left.byteLength);
|
|
29106
|
+
return combined;
|
|
29107
|
+
}
|
|
29108
|
+
function abortedError(signal) {
|
|
29109
|
+
return new JsonFrameError("ABORTED", "JSON frame reading was aborted", {
|
|
29110
|
+
cause: signal?.reason
|
|
29111
|
+
});
|
|
29112
|
+
}
|
|
29113
|
+
async function readNextChunk(iterator, idleDeadline, totalDeadline, signal) {
|
|
29114
|
+
if (signal?.aborted) throw abortedError(signal);
|
|
29115
|
+
const now = Date.now();
|
|
29116
|
+
const deadline = totalDeadline === void 0 ? idleDeadline : Math.min(idleDeadline, totalDeadline);
|
|
29117
|
+
if (deadline <= now) {
|
|
29118
|
+
throw new JsonFrameError(
|
|
29119
|
+
totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
|
|
29120
|
+
totalDeadline !== void 0 && totalDeadline <= idleDeadline ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
|
|
29121
|
+
);
|
|
29122
|
+
}
|
|
29123
|
+
return new Promise((resolve, reject2) => {
|
|
29124
|
+
let settled = false;
|
|
29125
|
+
const finish = (callback) => {
|
|
29126
|
+
if (settled) return;
|
|
29127
|
+
settled = true;
|
|
29128
|
+
clearTimeout(timer);
|
|
29129
|
+
signal?.removeEventListener("abort", onAbort);
|
|
29130
|
+
callback();
|
|
29131
|
+
};
|
|
29132
|
+
const onAbort = () => {
|
|
29133
|
+
finish(() => {
|
|
29134
|
+
reject2(abortedError(signal));
|
|
29135
|
+
});
|
|
29136
|
+
};
|
|
29137
|
+
const timer = setTimeout(() => {
|
|
29138
|
+
const currentTime = Date.now();
|
|
29139
|
+
const totalExpired = totalDeadline !== void 0 && currentTime >= totalDeadline;
|
|
29140
|
+
finish(() => {
|
|
29141
|
+
reject2(
|
|
29142
|
+
new JsonFrameError(
|
|
29143
|
+
totalExpired ? "TOTAL_TIMEOUT" : "IDLE_TIMEOUT",
|
|
29144
|
+
totalExpired ? "JSON frame total timeout expired" : "JSON frame idle timeout expired"
|
|
29145
|
+
)
|
|
28798
29146
|
);
|
|
29147
|
+
});
|
|
29148
|
+
}, Math.max(1, deadline - now));
|
|
29149
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
29150
|
+
void iterator.next().then(
|
|
29151
|
+
(result) => {
|
|
29152
|
+
finish(() => {
|
|
29153
|
+
resolve(result);
|
|
29154
|
+
});
|
|
29155
|
+
},
|
|
29156
|
+
(error2) => {
|
|
29157
|
+
finish(() => {
|
|
29158
|
+
reject2(
|
|
29159
|
+
error2 instanceof Error ? error2 : new Error("JSON frame source failed", {
|
|
29160
|
+
cause: error2
|
|
29161
|
+
})
|
|
29162
|
+
);
|
|
29163
|
+
});
|
|
28799
29164
|
}
|
|
28800
|
-
|
|
28801
|
-
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
29165
|
+
);
|
|
29166
|
+
});
|
|
29167
|
+
}
|
|
29168
|
+
function createJsonFrameReader(source, options) {
|
|
29169
|
+
validateMaxPayloadBytes(options.maxPayloadBytes);
|
|
29170
|
+
assertPositiveInteger(options.idleTimeoutMs, "idleTimeoutMs");
|
|
29171
|
+
assertPositiveInteger(options.totalTimeoutMs, "totalTimeoutMs");
|
|
29172
|
+
return (async function* readFrames() {
|
|
29173
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
29174
|
+
let buffered = new Uint8Array(0);
|
|
29175
|
+
let expectedPayloadBytes;
|
|
29176
|
+
let idleDeadline = Date.now() + options.idleTimeoutMs;
|
|
29177
|
+
let totalDeadline;
|
|
29178
|
+
let abortCalled = false;
|
|
29179
|
+
const abortOnce = async (error2) => {
|
|
29180
|
+
if (abortCalled) return;
|
|
29181
|
+
abortCalled = true;
|
|
29182
|
+
await Promise.resolve(options.abort?.(error2)).catch(() => void 0);
|
|
29183
|
+
};
|
|
29184
|
+
try {
|
|
29185
|
+
for (; ; ) {
|
|
29186
|
+
if (expectedPayloadBytes === void 0 && buffered.byteLength >= HEADER_BYTES) {
|
|
29187
|
+
expectedPayloadBytes = new DataView(
|
|
29188
|
+
buffered.buffer,
|
|
29189
|
+
buffered.byteOffset,
|
|
29190
|
+
HEADER_BYTES
|
|
29191
|
+
).getUint32(0, false);
|
|
29192
|
+
buffered = buffered.slice(HEADER_BYTES);
|
|
29193
|
+
totalDeadline ??= Date.now() + options.totalTimeoutMs;
|
|
29194
|
+
if (expectedPayloadBytes > options.maxPayloadBytes) {
|
|
29195
|
+
throw new JsonFrameError(
|
|
29196
|
+
"FRAME_TOO_LARGE",
|
|
29197
|
+
`JSON frame payload exceeds ${options.maxPayloadBytes} bytes`
|
|
29198
|
+
);
|
|
29199
|
+
}
|
|
29200
|
+
}
|
|
29201
|
+
if (expectedPayloadBytes !== void 0 && buffered.byteLength >= expectedPayloadBytes) {
|
|
29202
|
+
const payload = buffered.slice(0, expectedPayloadBytes);
|
|
29203
|
+
buffered = buffered.slice(expectedPayloadBytes);
|
|
29204
|
+
expectedPayloadBytes = void 0;
|
|
29205
|
+
totalDeadline = buffered.byteLength > 0 ? Date.now() + options.totalTimeoutMs : void 0;
|
|
29206
|
+
let text;
|
|
29207
|
+
try {
|
|
29208
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(payload);
|
|
29209
|
+
} catch (error2) {
|
|
29210
|
+
throw new JsonFrameError("INVALID_UTF8", "JSON frame payload is not valid UTF-8", {
|
|
29211
|
+
cause: error2
|
|
29212
|
+
});
|
|
29213
|
+
}
|
|
29214
|
+
let value;
|
|
29215
|
+
try {
|
|
29216
|
+
value = JSON.parse(text);
|
|
29217
|
+
} catch (error2) {
|
|
29218
|
+
throw new JsonFrameError("INVALID_JSON", "JSON frame payload is not valid JSON", {
|
|
29219
|
+
cause: error2
|
|
29220
|
+
});
|
|
29221
|
+
}
|
|
29222
|
+
yield value;
|
|
29223
|
+
idleDeadline = Date.now() + options.idleTimeoutMs;
|
|
29224
|
+
continue;
|
|
29225
|
+
}
|
|
29226
|
+
const result = await readNextChunk(
|
|
29227
|
+
iterator,
|
|
29228
|
+
idleDeadline,
|
|
29229
|
+
totalDeadline,
|
|
29230
|
+
options.signal
|
|
28806
29231
|
);
|
|
29232
|
+
if (result.done) {
|
|
29233
|
+
if (buffered.byteLength > 0 || expectedPayloadBytes !== void 0) {
|
|
29234
|
+
throw new JsonFrameError("TRUNCATED_FRAME", "JSON frame stream ended mid-frame");
|
|
29235
|
+
}
|
|
29236
|
+
return;
|
|
29237
|
+
}
|
|
29238
|
+
if (!(result.value instanceof Uint8Array)) {
|
|
29239
|
+
throw new TypeError("JSON frame source must yield Uint8Array chunks");
|
|
29240
|
+
}
|
|
29241
|
+
if (result.value.byteLength === 0) continue;
|
|
29242
|
+
const now = Date.now();
|
|
29243
|
+
idleDeadline = now + options.idleTimeoutMs;
|
|
29244
|
+
totalDeadline ??= now + options.totalTimeoutMs;
|
|
29245
|
+
buffered = concatBytes(buffered, result.value);
|
|
28807
29246
|
}
|
|
29247
|
+
} catch (error2) {
|
|
29248
|
+
const frameError = error2 instanceof JsonFrameError ? error2 : new JsonFrameError("ABORTED", "JSON frame source failed", { cause: error2 });
|
|
29249
|
+
await abortOnce(frameError);
|
|
29250
|
+
throw frameError;
|
|
29251
|
+
} finally {
|
|
29252
|
+
void iterator.return?.().catch(() => void 0);
|
|
28808
29253
|
}
|
|
28809
|
-
|
|
28810
|
-
|
|
28811
|
-
|
|
28812
|
-
|
|
28813
|
-
|
|
28814
|
-
|
|
28815
|
-
|
|
28816
|
-
|
|
28817
|
-
|
|
28818
|
-
|
|
28819
|
-
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28823
|
-
|
|
28824
|
-
|
|
28825
|
-
|
|
29254
|
+
})();
|
|
29255
|
+
}
|
|
29256
|
+
|
|
29257
|
+
// src/device-network/deviceOrchestrationTransport.ts
|
|
29258
|
+
var DEVICE_ORCHESTRATION_PROTOCOL = "/memeloop/orchestration/2.0.0";
|
|
29259
|
+
var DEFAULT_MAX_FRAME_BYTES = 1024 * 1024;
|
|
29260
|
+
var REQUEST_IDLE_TIMEOUT_MS = 1e4;
|
|
29261
|
+
var REQUEST_TOTAL_TIMEOUT_MS = 3e4;
|
|
29262
|
+
var WATCH_IDLE_TIMEOUT_MS = 9e4;
|
|
29263
|
+
var WATCH_BOOKMARK_INTERVAL_MS = 3e4;
|
|
29264
|
+
function orchestrationTransportError(code, message) {
|
|
29265
|
+
return new OrchestrationError({
|
|
29266
|
+
code,
|
|
29267
|
+
message,
|
|
29268
|
+
retryable: code === "UNAVAILABLE"
|
|
29269
|
+
});
|
|
29270
|
+
}
|
|
29271
|
+
function assertFrameLimit(limit) {
|
|
29272
|
+
if (!Number.isSafeInteger(limit) || limit < 256) {
|
|
29273
|
+
throw new TypeError("maxFrameBytes must be a safe integer of at least 256");
|
|
28826
29274
|
}
|
|
28827
29275
|
}
|
|
28828
29276
|
function throwIfAborted(signal) {
|
|
@@ -28843,11 +29291,41 @@ async function openAuthenticatedStream(options) {
|
|
|
28843
29291
|
}
|
|
28844
29292
|
async function writeRequest(stream, request, grant, maxFrameBytes) {
|
|
28845
29293
|
const envelope = {
|
|
28846
|
-
type: "memeloop-device-orchestration-request-
|
|
29294
|
+
type: "memeloop-device-orchestration-request-v2",
|
|
28847
29295
|
request,
|
|
28848
29296
|
...grant ? { grant } : {}
|
|
28849
29297
|
};
|
|
28850
|
-
await stream.sink(
|
|
29298
|
+
await stream.sink(encodeJsonFrames([envelope], maxFrameBytes));
|
|
29299
|
+
}
|
|
29300
|
+
function abortStreamOnce(stream) {
|
|
29301
|
+
let aborted = false;
|
|
29302
|
+
return async (error2) => {
|
|
29303
|
+
if (aborted) return;
|
|
29304
|
+
aborted = true;
|
|
29305
|
+
await stream.abort(error2);
|
|
29306
|
+
};
|
|
29307
|
+
}
|
|
29308
|
+
function frameReader(stream, maxFrameBytes, idleTimeoutMs, signal, abort) {
|
|
29309
|
+
return createJsonFrameReader(stream.source, {
|
|
29310
|
+
maxPayloadBytes: maxFrameBytes,
|
|
29311
|
+
idleTimeoutMs,
|
|
29312
|
+
totalTimeoutMs: REQUEST_TOTAL_TIMEOUT_MS,
|
|
29313
|
+
signal,
|
|
29314
|
+
abort
|
|
29315
|
+
});
|
|
29316
|
+
}
|
|
29317
|
+
function mapFrameError(error2) {
|
|
29318
|
+
if (!(error2 instanceof JsonFrameError)) throw error2;
|
|
29319
|
+
if (error2.code === "ABORTED") {
|
|
29320
|
+
throw orchestrationTransportError("CANCELLED", error2.message);
|
|
29321
|
+
}
|
|
29322
|
+
if (error2.code === "FRAME_TOO_LARGE") {
|
|
29323
|
+
throw orchestrationTransportError("EXHAUSTED", error2.message);
|
|
29324
|
+
}
|
|
29325
|
+
if (error2.code === "IDLE_TIMEOUT" || error2.code === "TOTAL_TIMEOUT") {
|
|
29326
|
+
throw orchestrationTransportError("UNAVAILABLE", error2.message);
|
|
29327
|
+
}
|
|
29328
|
+
throw orchestrationTransportError("INVALID", error2.message);
|
|
28851
29329
|
}
|
|
28852
29330
|
function createDeviceOrchestrationTransport(options) {
|
|
28853
29331
|
const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
|
|
@@ -28856,8 +29334,9 @@ function createDeviceOrchestrationTransport(options) {
|
|
|
28856
29334
|
async request(request, transportOptions = {}) {
|
|
28857
29335
|
throwIfAborted(transportOptions.signal);
|
|
28858
29336
|
const { stream, grant } = await openAuthenticatedStream(options);
|
|
29337
|
+
const abort = abortStreamOnce(stream);
|
|
28859
29338
|
const closeOnAbort = () => {
|
|
28860
|
-
void
|
|
29339
|
+
void abort(orchestrationTransportError("CANCELLED", "device orchestration request was cancelled"));
|
|
28861
29340
|
};
|
|
28862
29341
|
transportOptions.signal?.addEventListener("abort", closeOnAbort, {
|
|
28863
29342
|
once: true
|
|
@@ -28865,7 +29344,13 @@ function createDeviceOrchestrationTransport(options) {
|
|
|
28865
29344
|
try {
|
|
28866
29345
|
await writeRequest(stream, request, grant, maxFrameBytes);
|
|
28867
29346
|
let response;
|
|
28868
|
-
for await (const value of
|
|
29347
|
+
for await (const value of frameReader(
|
|
29348
|
+
stream,
|
|
29349
|
+
maxFrameBytes,
|
|
29350
|
+
REQUEST_IDLE_TIMEOUT_MS,
|
|
29351
|
+
transportOptions.signal,
|
|
29352
|
+
abort
|
|
29353
|
+
)) {
|
|
28869
29354
|
if (response) {
|
|
28870
29355
|
throw orchestrationTransportError(
|
|
28871
29356
|
"INVALID",
|
|
@@ -28882,6 +29367,9 @@ function createDeviceOrchestrationTransport(options) {
|
|
|
28882
29367
|
);
|
|
28883
29368
|
}
|
|
28884
29369
|
return response;
|
|
29370
|
+
} catch (error2) {
|
|
29371
|
+
if (error2 instanceof JsonFrameError) await abort(error2);
|
|
29372
|
+
mapFrameError(error2);
|
|
28885
29373
|
} finally {
|
|
28886
29374
|
transportOptions.signal?.removeEventListener("abort", closeOnAbort);
|
|
28887
29375
|
await stream.close().catch(() => void 0);
|
|
@@ -28890,19 +29378,29 @@ function createDeviceOrchestrationTransport(options) {
|
|
|
28890
29378
|
async *watch(request, transportOptions = {}) {
|
|
28891
29379
|
throwIfAborted(transportOptions.signal);
|
|
28892
29380
|
const { stream, grant } = await openAuthenticatedStream(options);
|
|
29381
|
+
const abort = abortStreamOnce(stream);
|
|
28893
29382
|
const closeOnAbort = () => {
|
|
28894
|
-
void
|
|
29383
|
+
void abort(orchestrationTransportError("CANCELLED", "device orchestration watch was cancelled"));
|
|
28895
29384
|
};
|
|
28896
29385
|
transportOptions.signal?.addEventListener("abort", closeOnAbort, {
|
|
28897
29386
|
once: true
|
|
28898
29387
|
});
|
|
28899
29388
|
try {
|
|
28900
29389
|
await writeRequest(stream, request, grant, maxFrameBytes);
|
|
28901
|
-
for await (const value of
|
|
29390
|
+
for await (const value of frameReader(
|
|
29391
|
+
stream,
|
|
29392
|
+
maxFrameBytes,
|
|
29393
|
+
WATCH_IDLE_TIMEOUT_MS,
|
|
29394
|
+
transportOptions.signal,
|
|
29395
|
+
abort
|
|
29396
|
+
)) {
|
|
28902
29397
|
throwIfAborted(transportOptions.signal);
|
|
28903
29398
|
yield value;
|
|
28904
29399
|
}
|
|
28905
29400
|
throwIfAborted(transportOptions.signal);
|
|
29401
|
+
} catch (error2) {
|
|
29402
|
+
if (error2 instanceof JsonFrameError) await abort(error2);
|
|
29403
|
+
mapFrameError(error2);
|
|
28906
29404
|
} finally {
|
|
28907
29405
|
transportOptions.signal?.removeEventListener("abort", closeOnAbort);
|
|
28908
29406
|
await stream.close().catch(() => void 0);
|
|
@@ -28911,15 +29409,22 @@ function createDeviceOrchestrationTransport(options) {
|
|
|
28911
29409
|
};
|
|
28912
29410
|
}
|
|
28913
29411
|
async function readSingleRequest(stream, maxFrameBytes) {
|
|
29412
|
+
const abort = abortStreamOnce(stream);
|
|
28914
29413
|
let envelope;
|
|
28915
|
-
for await (const value of
|
|
29414
|
+
for await (const value of frameReader(
|
|
29415
|
+
stream,
|
|
29416
|
+
maxFrameBytes,
|
|
29417
|
+
REQUEST_IDLE_TIMEOUT_MS,
|
|
29418
|
+
void 0,
|
|
29419
|
+
abort
|
|
29420
|
+
)) {
|
|
28916
29421
|
if (envelope) {
|
|
28917
29422
|
throw orchestrationTransportError(
|
|
28918
29423
|
"INVALID",
|
|
28919
29424
|
"device orchestration stream accepts exactly one request"
|
|
28920
29425
|
);
|
|
28921
29426
|
}
|
|
28922
|
-
if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-
|
|
29427
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || value.type !== "memeloop-device-orchestration-request-v2" || value.request === null || typeof value.request !== "object") {
|
|
28923
29428
|
throw orchestrationTransportError(
|
|
28924
29429
|
"INVALID",
|
|
28925
29430
|
"device orchestration request envelope is invalid"
|
|
@@ -28932,6 +29437,94 @@ async function readSingleRequest(stream, maxFrameBytes) {
|
|
|
28932
29437
|
}
|
|
28933
29438
|
return envelope;
|
|
28934
29439
|
}
|
|
29440
|
+
async function* withWatchBookmarks(source, request, signal) {
|
|
29441
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
29442
|
+
let activity;
|
|
29443
|
+
let activityWaiter;
|
|
29444
|
+
let bookmarkTimer;
|
|
29445
|
+
let readInFlight = false;
|
|
29446
|
+
let lastResourceVersion = watchResourceVersion(request.payload.options) ?? "0";
|
|
29447
|
+
const clearBookmarkTimer = () => {
|
|
29448
|
+
if (bookmarkTimer !== void 0) clearTimeout(bookmarkTimer);
|
|
29449
|
+
bookmarkTimer = void 0;
|
|
29450
|
+
};
|
|
29451
|
+
const publishActivity = (value) => {
|
|
29452
|
+
if (activityWaiter) {
|
|
29453
|
+
const resolve = activityWaiter;
|
|
29454
|
+
activityWaiter = void 0;
|
|
29455
|
+
clearBookmarkTimer();
|
|
29456
|
+
resolve(value);
|
|
29457
|
+
return;
|
|
29458
|
+
}
|
|
29459
|
+
activity = value;
|
|
29460
|
+
};
|
|
29461
|
+
const readNext = () => {
|
|
29462
|
+
if (readInFlight) return;
|
|
29463
|
+
readInFlight = true;
|
|
29464
|
+
void iterator.next().then(
|
|
29465
|
+
(value) => {
|
|
29466
|
+
readInFlight = false;
|
|
29467
|
+
publishActivity({ kind: "value", value });
|
|
29468
|
+
},
|
|
29469
|
+
(error2) => {
|
|
29470
|
+
readInFlight = false;
|
|
29471
|
+
publishActivity({ kind: "error", error: error2 });
|
|
29472
|
+
}
|
|
29473
|
+
);
|
|
29474
|
+
};
|
|
29475
|
+
const waitForActivity = () => {
|
|
29476
|
+
if (activity) {
|
|
29477
|
+
const value = activity;
|
|
29478
|
+
activity = void 0;
|
|
29479
|
+
return Promise.resolve(value);
|
|
29480
|
+
}
|
|
29481
|
+
if (signal.aborted) return Promise.resolve({ kind: "aborted" });
|
|
29482
|
+
return new Promise((resolve) => {
|
|
29483
|
+
activityWaiter = resolve;
|
|
29484
|
+
bookmarkTimer = setTimeout(() => {
|
|
29485
|
+
bookmarkTimer = void 0;
|
|
29486
|
+
activityWaiter = void 0;
|
|
29487
|
+
resolve({ kind: "bookmark" });
|
|
29488
|
+
}, WATCH_BOOKMARK_INTERVAL_MS);
|
|
29489
|
+
});
|
|
29490
|
+
};
|
|
29491
|
+
const onAbort = () => {
|
|
29492
|
+
publishActivity({ kind: "aborted" });
|
|
29493
|
+
};
|
|
29494
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
29495
|
+
readNext();
|
|
29496
|
+
try {
|
|
29497
|
+
for (; ; ) {
|
|
29498
|
+
const result = await waitForActivity();
|
|
29499
|
+
if (result.kind === "aborted") return;
|
|
29500
|
+
if (result.kind === "bookmark") {
|
|
29501
|
+
yield {
|
|
29502
|
+
protocol: request.protocol,
|
|
29503
|
+
requestId: request.requestId,
|
|
29504
|
+
ok: true,
|
|
29505
|
+
result: { type: "BOOKMARK", resourceVersion: lastResourceVersion }
|
|
29506
|
+
};
|
|
29507
|
+
continue;
|
|
29508
|
+
}
|
|
29509
|
+
if (result.kind === "error") throw result.error;
|
|
29510
|
+
if (result.value.done) return;
|
|
29511
|
+
const response = result.value.value;
|
|
29512
|
+
lastResourceVersion = response.ok ? watchResourceVersion(response.result) ?? lastResourceVersion : lastResourceVersion;
|
|
29513
|
+
yield response;
|
|
29514
|
+
readNext();
|
|
29515
|
+
}
|
|
29516
|
+
} finally {
|
|
29517
|
+
clearBookmarkTimer();
|
|
29518
|
+
activityWaiter = void 0;
|
|
29519
|
+
signal.removeEventListener("abort", onAbort);
|
|
29520
|
+
void iterator.return?.();
|
|
29521
|
+
}
|
|
29522
|
+
}
|
|
29523
|
+
function watchResourceVersion(value) {
|
|
29524
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
29525
|
+
const resourceVersion = value.resourceVersion;
|
|
29526
|
+
return typeof resourceVersion === "string" && resourceVersion.length > 0 ? resourceVersion : void 0;
|
|
29527
|
+
}
|
|
28935
29528
|
function createDeviceOrchestrationStreamHandler(options) {
|
|
28936
29529
|
const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
|
|
28937
29530
|
assertFrameLimit(maxFrameBytes);
|
|
@@ -28946,10 +29539,13 @@ function createDeviceOrchestrationStreamHandler(options) {
|
|
|
28946
29539
|
const handler = await options.resolveHandler(remotePeerId);
|
|
28947
29540
|
if (request.operation === "watch") {
|
|
28948
29541
|
await stream.sink(
|
|
28949
|
-
|
|
29542
|
+
encodeJsonFrames(
|
|
29543
|
+
withWatchBookmarks(handler.watch(request, { signal: abort.signal }), request, abort.signal),
|
|
29544
|
+
maxFrameBytes
|
|
29545
|
+
)
|
|
28950
29546
|
);
|
|
28951
29547
|
} else {
|
|
28952
|
-
await stream.sink(
|
|
29548
|
+
await stream.sink(encodeJsonFrames([await handler.request(request)], maxFrameBytes));
|
|
28953
29549
|
}
|
|
28954
29550
|
} finally {
|
|
28955
29551
|
abort.abort();
|
|
@@ -28959,8 +29555,8 @@ function createDeviceOrchestrationStreamHandler(options) {
|
|
|
28959
29555
|
}
|
|
28960
29556
|
|
|
28961
29557
|
// src/device-network/libp2pSyncProtocol.ts
|
|
28962
|
-
var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-
|
|
28963
|
-
var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-
|
|
29558
|
+
var LIBP2P_SYNC_REQUEST_TYPE = "memeloop-sync-request-v2";
|
|
29559
|
+
var LIBP2P_SYNC_RESPONSE_TYPE = "memeloop-sync-response-v2";
|
|
28964
29560
|
var syncMethods = /* @__PURE__ */ new Set([
|
|
28965
29561
|
"exchangeVersionVector",
|
|
28966
29562
|
"pullMissingMetadata",
|
|
@@ -29020,8 +29616,11 @@ var Libp2pDeviceSyncTransport = class {
|
|
|
29020
29616
|
exchangeVersionVector(peerId, localVersion) {
|
|
29021
29617
|
return this.request(peerId, "exchangeVersionVector", { localVersion });
|
|
29022
29618
|
}
|
|
29023
|
-
pullMissingMetadata(peerId, sinceVersion) {
|
|
29024
|
-
return this.request(peerId, "pullMissingMetadata", {
|
|
29619
|
+
pullMissingMetadata(peerId, sinceVersion, cursor) {
|
|
29620
|
+
return this.request(peerId, "pullMissingMetadata", {
|
|
29621
|
+
sinceVersion,
|
|
29622
|
+
cursor
|
|
29623
|
+
});
|
|
29025
29624
|
}
|
|
29026
29625
|
pullMissingMessages(peerId, conversationId, knownMessageIds) {
|
|
29027
29626
|
return this.request(peerId, "pullMissingMessages", { conversationId, knownMessageIds });
|
|
@@ -29032,7 +29631,7 @@ var Libp2pDeviceSyncTransport = class {
|
|
|
29032
29631
|
}
|
|
29033
29632
|
async request(peerId, method, parameters) {
|
|
29034
29633
|
const grant = await this.grantProvider?.(peerId);
|
|
29035
|
-
const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/
|
|
29634
|
+
const stream = await this.deviceNetwork.openStream(peerId, "/memeloop/sync/2.0.0", grant);
|
|
29036
29635
|
const request = {
|
|
29037
29636
|
type: LIBP2P_SYNC_REQUEST_TYPE,
|
|
29038
29637
|
id: crypto.randomUUID(),
|
|
@@ -29040,31 +29639,34 @@ var Libp2pDeviceSyncTransport = class {
|
|
|
29040
29639
|
params: parameters,
|
|
29041
29640
|
grant
|
|
29042
29641
|
};
|
|
29043
|
-
|
|
29044
|
-
|
|
29045
|
-
|
|
29046
|
-
|
|
29047
|
-
|
|
29048
|
-
|
|
29049
|
-
|
|
29642
|
+
try {
|
|
29643
|
+
await stream.sink(encodeJsonFrames([request], 16 * 1024 * 1024));
|
|
29644
|
+
let response;
|
|
29645
|
+
for await (const value of createJsonFrameReader(stream.source, {
|
|
29646
|
+
maxPayloadBytes: 16 * 1024 * 1024,
|
|
29647
|
+
idleTimeoutMs: 15e3,
|
|
29648
|
+
totalTimeoutMs: 12e4,
|
|
29649
|
+
abort: async (error2) => stream.abort(error2)
|
|
29650
|
+
})) {
|
|
29651
|
+
if (response !== void 0) throw new Error("sync_response_multiple");
|
|
29652
|
+
response = value;
|
|
29653
|
+
}
|
|
29654
|
+
if (!isLibp2pSyncResponse(response)) throw new Error("invalid_sync_response");
|
|
29655
|
+
if (response.id !== request.id) throw new Error("sync_response_id_mismatch");
|
|
29656
|
+
if (!response.ok) throw new Error(response.error);
|
|
29657
|
+
return response.result;
|
|
29658
|
+
} catch (error2) {
|
|
29659
|
+
if (error2 instanceof JsonFrameError) await stream.abort(error2);
|
|
29660
|
+
throw error2;
|
|
29661
|
+
} finally {
|
|
29662
|
+
await stream.close().catch(() => void 0);
|
|
29663
|
+
}
|
|
29050
29664
|
}
|
|
29051
29665
|
};
|
|
29052
|
-
async function writeJsonToStream(stream, message) {
|
|
29053
|
-
const payload = new TextEncoder().encode(JSON.stringify(message));
|
|
29054
|
-
await stream.sink((async function* () {
|
|
29055
|
-
yield payload;
|
|
29056
|
-
})());
|
|
29057
|
-
}
|
|
29058
|
-
async function readJsonFromStream(stream) {
|
|
29059
|
-
const reader = stream.source[Symbol.asyncIterator]();
|
|
29060
|
-
const result = await reader.next();
|
|
29061
|
-
if (result.done || !result.value) throw new Error("sync_response_missing");
|
|
29062
|
-
return JSON.parse(new TextDecoder().decode(result.value));
|
|
29063
|
-
}
|
|
29064
29666
|
|
|
29065
29667
|
// src/device-network/libp2pRpcProtocol.ts
|
|
29066
|
-
var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-
|
|
29067
|
-
var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-
|
|
29668
|
+
var LIBP2P_RPC_REQUEST_TYPE = "memeloop-rpc-request-v2";
|
|
29669
|
+
var LIBP2P_RPC_RESPONSE_TYPE = "memeloop-rpc-response-v2";
|
|
29068
29670
|
function isLibp2pRpcRequest(value) {
|
|
29069
29671
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
29070
29672
|
const record2 = value;
|
|
@@ -29079,7 +29681,7 @@ function isLibp2pRpcResponse(value) {
|
|
|
29079
29681
|
}
|
|
29080
29682
|
|
|
29081
29683
|
// src/device-network/localTrustDeviceAuthorizer.ts
|
|
29082
|
-
var PAIRING_PROTOCOL = "/memeloop/pairing/
|
|
29684
|
+
var PAIRING_PROTOCOL = "/memeloop/pairing/2.0.0";
|
|
29083
29685
|
var LocalTrustDeviceAuthorizer = class {
|
|
29084
29686
|
constructor(options = {}) {
|
|
29085
29687
|
this.options = options;
|
|
@@ -29241,6 +29843,8 @@ var MemoryDeviceNetworkService = class {
|
|
|
29241
29843
|
async sink() {
|
|
29242
29844
|
},
|
|
29243
29845
|
async close() {
|
|
29846
|
+
},
|
|
29847
|
+
abort() {
|
|
29244
29848
|
}
|
|
29245
29849
|
};
|
|
29246
29850
|
}
|
|
@@ -29273,7 +29877,7 @@ var MemoryDeviceNetworkService = class {
|
|
|
29273
29877
|
};
|
|
29274
29878
|
|
|
29275
29879
|
// src/device-network/pairingInvite.ts
|
|
29276
|
-
var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-
|
|
29880
|
+
var DEVICE_PAIRING_INVITE_PROTOCOL = "memeloop-device-pairing-v2";
|
|
29277
29881
|
var DEVICE_PAIRING_INVITE_TTL_MS = 5 * 6e4;
|
|
29278
29882
|
var MAX_INVITE_LENGTH = 16 * 1024;
|
|
29279
29883
|
var MAX_ADDRESSES = 8;
|
|
@@ -29284,37 +29888,82 @@ function requireText(value, name, maxLength) {
|
|
|
29284
29888
|
}
|
|
29285
29889
|
return trimmed;
|
|
29286
29890
|
}
|
|
29287
|
-
function
|
|
29891
|
+
function finalPeerId(address) {
|
|
29892
|
+
const parts = address.split("/");
|
|
29893
|
+
for (let index = parts.length - 2; index >= 0; index -= 1) {
|
|
29894
|
+
if (parts[index] === "p2p") return parts[index + 1];
|
|
29895
|
+
}
|
|
29896
|
+
return void 0;
|
|
29897
|
+
}
|
|
29898
|
+
function validateAddresses(addresses, peerId) {
|
|
29288
29899
|
const unique = [...new Set(addresses.map((address) => address.trim()))].filter(Boolean);
|
|
29289
29900
|
if (unique.length === 0 || unique.length > MAX_ADDRESSES || unique.some(
|
|
29290
|
-
(address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss")
|
|
29901
|
+
(address) => address.length > 2048 || !address.startsWith("/") || !address.includes("/ws") && !address.includes("/wss") || finalPeerId(address) !== peerId
|
|
29291
29902
|
)) {
|
|
29292
|
-
throw new Error("device pairing invite requires WebSocket multiaddrs");
|
|
29903
|
+
throw new Error("device pairing invite requires PeerId-bound WebSocket multiaddrs");
|
|
29293
29904
|
}
|
|
29294
29905
|
return unique;
|
|
29295
29906
|
}
|
|
29296
|
-
function
|
|
29907
|
+
function validateLifetime(createdAt, expiresAt, now) {
|
|
29908
|
+
if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(expiresAt) || expiresAt <= createdAt || expiresAt - createdAt > DEVICE_PAIRING_INVITE_TTL_MS) {
|
|
29909
|
+
throw new Error("invalid device pairing invite lifetime");
|
|
29910
|
+
}
|
|
29911
|
+
if (expiresAt <= now) throw new Error("device pairing invite has expired");
|
|
29912
|
+
}
|
|
29913
|
+
function unsignedPayload(invite) {
|
|
29914
|
+
const peerId = requireText(invite.peerId, "peerId", 256);
|
|
29915
|
+
return {
|
|
29916
|
+
protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
|
|
29917
|
+
peerId,
|
|
29918
|
+
publicKeyMultibase: requireText(invite.publicKeyMultibase, "publicKeyMultibase", 4096),
|
|
29919
|
+
deviceName: requireText(invite.deviceName, "deviceName", 256),
|
|
29920
|
+
multiaddrs: [...validateAddresses(invite.multiaddrs, peerId)].sort(),
|
|
29921
|
+
createdAt: invite.createdAt,
|
|
29922
|
+
expiresAt: invite.expiresAt
|
|
29923
|
+
};
|
|
29924
|
+
}
|
|
29925
|
+
function canonicalDevicePairingInviteBytes(invite) {
|
|
29926
|
+
const payload = unsignedPayload(invite);
|
|
29927
|
+
const canonical = [
|
|
29928
|
+
DEVICE_PAIRING_INVITE_PROTOCOL,
|
|
29929
|
+
`peerId=${payload.peerId}`,
|
|
29930
|
+
`publicKey=${payload.publicKeyMultibase}`,
|
|
29931
|
+
`deviceName=${payload.deviceName}`,
|
|
29932
|
+
`multiaddrs=${payload.multiaddrs.join(",")}`,
|
|
29933
|
+
`createdAt=${payload.createdAt}`,
|
|
29934
|
+
`expiresAt=${payload.expiresAt}`
|
|
29935
|
+
].join("\n");
|
|
29936
|
+
return new TextEncoder().encode(canonical);
|
|
29937
|
+
}
|
|
29938
|
+
async function createDevicePairingInvite(device, options) {
|
|
29297
29939
|
const createdAt = options.now ?? Date.now();
|
|
29298
29940
|
const ttlMs = options.ttlMs ?? DEVICE_PAIRING_INVITE_TTL_MS;
|
|
29299
|
-
if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0
|
|
29941
|
+
if (!Number.isSafeInteger(createdAt) || !Number.isSafeInteger(ttlMs) || ttlMs <= 0) {
|
|
29300
29942
|
throw new Error("invalid device pairing invite lifetime");
|
|
29301
29943
|
}
|
|
29302
|
-
|
|
29944
|
+
const payload = unsignedPayload({
|
|
29303
29945
|
protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
|
|
29304
|
-
peerId:
|
|
29305
|
-
|
|
29306
|
-
|
|
29946
|
+
peerId: device.peerId,
|
|
29947
|
+
publicKeyMultibase: device.publicKeyMultibase,
|
|
29948
|
+
deviceName: device.displayName,
|
|
29949
|
+
multiaddrs: device.multiaddrs ?? [],
|
|
29307
29950
|
createdAt,
|
|
29308
29951
|
expiresAt: createdAt + ttlMs
|
|
29309
|
-
};
|
|
29952
|
+
});
|
|
29953
|
+
validateLifetime(payload.createdAt, payload.expiresAt, createdAt - 1);
|
|
29954
|
+
const signature = requireText(
|
|
29955
|
+
await options.sign(canonicalDevicePairingInviteBytes(payload)),
|
|
29956
|
+
"signature",
|
|
29957
|
+
4096
|
|
29958
|
+
);
|
|
29959
|
+
return { ...payload, signature };
|
|
29310
29960
|
}
|
|
29311
29961
|
function encodeDevicePairingInvite(invite) {
|
|
29312
|
-
const
|
|
29313
|
-
|
|
29314
|
-
});
|
|
29315
|
-
return JSON.stringify(validated);
|
|
29962
|
+
const payload = unsignedPayload(invite);
|
|
29963
|
+
const signature = requireText(invite.signature, "signature", 4096);
|
|
29964
|
+
return JSON.stringify({ ...payload, signature });
|
|
29316
29965
|
}
|
|
29317
|
-
function parseDevicePairingInvite(serialized, options
|
|
29966
|
+
async function parseDevicePairingInvite(serialized, options) {
|
|
29318
29967
|
if (!serialized || serialized.length > MAX_INVITE_LENGTH) {
|
|
29319
29968
|
throw new Error("invalid device pairing invite length");
|
|
29320
29969
|
}
|
|
@@ -29328,24 +29977,34 @@ function parseDevicePairingInvite(serialized, options = {}) {
|
|
|
29328
29977
|
throw new Error("invalid device pairing invite");
|
|
29329
29978
|
}
|
|
29330
29979
|
const record2 = parsed;
|
|
29331
|
-
if (record2.protocol !== DEVICE_PAIRING_INVITE_PROTOCOL || typeof record2.peerId !== "string" || typeof record2.deviceName !== "string" || !Array.isArray(record2.multiaddrs) || record2.multiaddrs.some((address) => typeof address !== "string") || typeof record2.createdAt !== "number" || typeof record2.expiresAt !== "number" ||
|
|
29980
|
+
if (record2.protocol !== DEVICE_PAIRING_INVITE_PROTOCOL || typeof record2.peerId !== "string" || typeof record2.publicKeyMultibase !== "string" || typeof record2.deviceName !== "string" || !Array.isArray(record2.multiaddrs) || record2.multiaddrs.some((address) => typeof address !== "string") || typeof record2.createdAt !== "number" || typeof record2.expiresAt !== "number" || typeof record2.signature !== "string") {
|
|
29332
29981
|
throw new Error("invalid device pairing invite");
|
|
29333
29982
|
}
|
|
29334
|
-
|
|
29335
|
-
|
|
29336
|
-
}
|
|
29337
|
-
return {
|
|
29983
|
+
validateLifetime(record2.createdAt, record2.expiresAt, options.now ?? Date.now());
|
|
29984
|
+
const payload = unsignedPayload({
|
|
29338
29985
|
protocol: DEVICE_PAIRING_INVITE_PROTOCOL,
|
|
29339
|
-
peerId:
|
|
29340
|
-
|
|
29341
|
-
|
|
29986
|
+
peerId: record2.peerId,
|
|
29987
|
+
publicKeyMultibase: record2.publicKeyMultibase,
|
|
29988
|
+
deviceName: record2.deviceName,
|
|
29989
|
+
multiaddrs: record2.multiaddrs,
|
|
29342
29990
|
createdAt: record2.createdAt,
|
|
29343
29991
|
expiresAt: record2.expiresAt
|
|
29992
|
+
});
|
|
29993
|
+
const invite = {
|
|
29994
|
+
...payload,
|
|
29995
|
+
signature: requireText(record2.signature, "signature", 4096)
|
|
29344
29996
|
};
|
|
29997
|
+
if (!await options.verifyIdentity({
|
|
29998
|
+
invite,
|
|
29999
|
+
payload: canonicalDevicePairingInviteBytes(payload)
|
|
30000
|
+
})) {
|
|
30001
|
+
throw new Error("device pairing invite identity verification failed");
|
|
30002
|
+
}
|
|
30003
|
+
return invite;
|
|
29345
30004
|
}
|
|
29346
30005
|
|
|
29347
30006
|
// src/device-network/peerDriverTransport.ts
|
|
29348
|
-
var PEER_DRIVER_PROTOCOL_VERSION = "memeloop-peer-driver/
|
|
30007
|
+
var PEER_DRIVER_PROTOCOL_VERSION = "memeloop-peer-driver/v2";
|
|
29349
30008
|
var IDENTIFIER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
29350
30009
|
var MAX_ASSIGNMENT_ID_LENGTH = 256;
|
|
29351
30010
|
var MAX_OPERATION_LENGTH = 128;
|
|
@@ -29484,11 +30143,21 @@ function createPeerDriverRpcHandler(handlers) {
|
|
|
29484
30143
|
// src/device-network/syncCloudDevices.ts
|
|
29485
30144
|
async function syncCloudDevices(input) {
|
|
29486
30145
|
const cloudDevices = await input.cloudClient.listDevices();
|
|
29487
|
-
if (cloudDevices.length === 0) return [];
|
|
29488
30146
|
const existingRecords = await input.trustStore.loadTrustedDevices();
|
|
29489
30147
|
const existingByPeerId = new Map(existingRecords.map((r) => [r.peerId, r]));
|
|
29490
|
-
|
|
30148
|
+
const excludedPeerIds = new Set(input.excludePeerIds ?? []);
|
|
30149
|
+
const activeCloudDevices = cloudDevices.filter(
|
|
30150
|
+
(device) => device.revokedAt === void 0 && !excludedPeerIds.has(device.peerId)
|
|
30151
|
+
);
|
|
30152
|
+
const visibleCloudPeerIds = new Set(activeCloudDevices.map((device) => device.peerId));
|
|
30153
|
+
for (const existing of existingRecords) {
|
|
30154
|
+
if (existing.trustMode === "cloud-account" && !visibleCloudPeerIds.has(existing.peerId)) {
|
|
30155
|
+
await input.trustStore.removeTrustedDevice(existing.peerId);
|
|
30156
|
+
}
|
|
30157
|
+
}
|
|
30158
|
+
for (const cloudDevice of activeCloudDevices) {
|
|
29491
30159
|
const existing = existingByPeerId.get(cloudDevice.peerId);
|
|
30160
|
+
if (existing?.trustMode === "local-pairing") continue;
|
|
29492
30161
|
const record2 = {
|
|
29493
30162
|
peerId: cloudDevice.peerId,
|
|
29494
30163
|
publicKeyMultibase: cloudDevice.publicKeyMultibase,
|
|
@@ -29500,17 +30169,14 @@ async function syncCloudDevices(input) {
|
|
|
29500
30169
|
lastSeen: cloudDevice.lastSeen,
|
|
29501
30170
|
revokedAt: cloudDevice.revokedAt
|
|
29502
30171
|
};
|
|
29503
|
-
if (existing) {
|
|
29504
|
-
existing.lastSeen = cloudDevice.lastSeen;
|
|
29505
|
-
existing.revokedAt = cloudDevice.revokedAt;
|
|
29506
|
-
if (existing.publicKeyMultibase !== record2.publicKeyMultibase || existing.deviceName !== record2.deviceName || existing.platform !== record2.platform) {
|
|
29507
|
-
await input.trustStore.saveTrustedDevice(record2);
|
|
29508
|
-
}
|
|
29509
|
-
} else {
|
|
30172
|
+
if (!existing || !sameTrustedDevice(existing, record2)) {
|
|
29510
30173
|
await input.trustStore.saveTrustedDevice(record2);
|
|
29511
30174
|
}
|
|
29512
30175
|
}
|
|
29513
|
-
return
|
|
30176
|
+
return activeCloudDevices;
|
|
30177
|
+
}
|
|
30178
|
+
function sameTrustedDevice(left, right) {
|
|
30179
|
+
return left.peerId === right.peerId && left.publicKeyMultibase === right.publicKeyMultibase && left.deviceName === right.deviceName && left.platform === right.platform && left.trustMode === right.trustMode && left.accountId === right.accountId && left.createdAt === right.createdAt && left.lastSeen === right.lastSeen && left.revokedAt === right.revokedAt;
|
|
29514
30180
|
}
|
|
29515
30181
|
|
|
29516
30182
|
// src/network/terminalNotifications.ts
|
|
@@ -29847,7 +30513,17 @@ async function imSwitchConversation(rt, conversationId) {
|
|
|
29847
30513
|
function isConversationMeta(value) {
|
|
29848
30514
|
if (value === null || typeof value !== "object") return false;
|
|
29849
30515
|
const o = value;
|
|
29850
|
-
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";
|
|
30516
|
+
return typeof o.conversationId === "string" && typeof o.title === "string" && typeof o.originNodeId === "string" && typeof o.originClock === "number" && typeof o.definitionId === "string" && typeof o.messageCount === "number" && typeof o.lastMessageTimestamp === "number" && typeof o.isUserInitiated === "boolean";
|
|
30517
|
+
}
|
|
30518
|
+
function computeMissingVersionRanges(currentVersion, availableVersion) {
|
|
30519
|
+
const ranges = [];
|
|
30520
|
+
for (const [originNodeId, availableClock] of Object.entries(availableVersion)) {
|
|
30521
|
+
const currentClock = currentVersion[originNodeId] ?? 0;
|
|
30522
|
+
if (availableClock > currentClock) {
|
|
30523
|
+
ranges.push({ originNodeId, fromExclusive: currentClock, toInclusive: availableClock });
|
|
30524
|
+
}
|
|
30525
|
+
}
|
|
30526
|
+
return ranges.sort((left, right) => left.originNodeId.localeCompare(right.originNodeId));
|
|
29851
30527
|
}
|
|
29852
30528
|
|
|
29853
30529
|
// src/index.ts
|
|
@@ -31011,11 +31687,13 @@ function getToolDefinition(toolId) {
|
|
|
31011
31687
|
DRIVER_MANIFEST_API_VERSION,
|
|
31012
31688
|
DRIVER_MANIFEST_KIND,
|
|
31013
31689
|
DRIVER_REQUEST_API_VERSION,
|
|
31690
|
+
DeviceCloudConnectionCoordinator,
|
|
31014
31691
|
FLEET_ROLLOUT_API_VERSION,
|
|
31015
31692
|
FLEET_ROLLOUT_KIND,
|
|
31016
31693
|
HookRegistry,
|
|
31017
31694
|
IMChannelManager,
|
|
31018
31695
|
IM_SESSION_TOOL_IDS,
|
|
31696
|
+
JsonFrameError,
|
|
31019
31697
|
LIBP2P_RPC_REQUEST_TYPE,
|
|
31020
31698
|
LIBP2P_RPC_RESPONSE_TYPE,
|
|
31021
31699
|
LIBP2P_SYNC_REQUEST_TYPE,
|
|
@@ -31033,6 +31711,7 @@ function getToolDefinition(toolId) {
|
|
|
31033
31711
|
MODEL_ENDPOINT_API_VERSION,
|
|
31034
31712
|
MODEL_ENDPOINT_KIND,
|
|
31035
31713
|
MemoryDeviceNetworkService,
|
|
31714
|
+
MemoryDeviceSyncStateStore,
|
|
31036
31715
|
NETWORK_ATTACHMENT_API_VERSION,
|
|
31037
31716
|
NETWORK_ATTACHMENT_KIND,
|
|
31038
31717
|
NETWORK_CLASS_API_VERSION,
|
|
@@ -31131,6 +31810,7 @@ function getToolDefinition(toolId) {
|
|
|
31131
31810
|
canDriverSatisfyClass,
|
|
31132
31811
|
cancelPendingApprovals,
|
|
31133
31812
|
canonicalControlStoreValue,
|
|
31813
|
+
canonicalDevicePairingInviteBytes,
|
|
31134
31814
|
canonicalDriverValue,
|
|
31135
31815
|
canonicalWorkerBootstrapDescriptorBytes,
|
|
31136
31816
|
canonicalWorkerProtocolRequestBytes,
|
|
@@ -31140,6 +31820,7 @@ function getToolDefinition(toolId) {
|
|
|
31140
31820
|
clearHooks,
|
|
31141
31821
|
clearPluginRegistrations,
|
|
31142
31822
|
compactMessages,
|
|
31823
|
+
computeMissingVersionRanges,
|
|
31143
31824
|
consumeWorkloadCapabilityGrant,
|
|
31144
31825
|
containsSecrets,
|
|
31145
31826
|
controlStoreApplyMatches,
|
|
@@ -31227,6 +31908,7 @@ function getToolDefinition(toolId) {
|
|
|
31227
31908
|
createInProcessLoopRuntimeDriver,
|
|
31228
31909
|
createInProcessToolExecutionDriver,
|
|
31229
31910
|
createInstanceDeltaFromDefinition,
|
|
31911
|
+
createJsonFrameReader,
|
|
31230
31912
|
createLoopRuntimeManagementConformanceSuite,
|
|
31231
31913
|
createManagedArtifactDriverAdapter,
|
|
31232
31914
|
createManagedCredentialBrokerAdapter,
|
|
@@ -31308,6 +31990,8 @@ function getToolDefinition(toolId) {
|
|
|
31308
31990
|
detectMimeConfusion,
|
|
31309
31991
|
digestNormalizedScript,
|
|
31310
31992
|
encodeDevicePairingInvite,
|
|
31993
|
+
encodeJsonFrame,
|
|
31994
|
+
encodeJsonFrames,
|
|
31311
31995
|
enrollWorker,
|
|
31312
31996
|
evaluateApproval,
|
|
31313
31997
|
evaluateToolAdmission,
|