@enbox/agent 0.7.3 → 0.7.5
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.mjs +7 -7
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/sync-engine-level.js +37 -1
- package/dist/esm/sync-engine-level.js.map +1 -1
- package/dist/esm/sync-messages.js +33 -10
- package/dist/esm/sync-messages.js.map +1 -1
- package/dist/types/sync-engine-level.d.ts +9 -0
- package/dist/types/sync-engine-level.d.ts.map +1 -1
- package/dist/types/sync-messages.d.ts +5 -5
- package/dist/types/sync-messages.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/sync-engine-level.ts +34 -1
- package/src/sync-messages.ts +38 -10
|
@@ -21,12 +21,12 @@ export type SyncMessageEntry = {
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMessage?: GenericMessage): boolean;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* authorization errors (401/403), and schema validation errors that will never
|
|
27
|
-
* succeed regardless of retry.
|
|
24
|
+
* Classifies a failed push reply as permanent (dead-letter) or
|
|
25
|
+
* transient (retry with backoff).
|
|
28
26
|
*
|
|
29
|
-
*
|
|
27
|
+
* Permanent: 401/403 auth errors, most 400 validation errors.
|
|
28
|
+
* Transient: 5xx, network errors, and 400 protocol-dependency errors
|
|
29
|
+
* that self-heal once the dependency arrives on the remote.
|
|
30
30
|
*/
|
|
31
31
|
export declare function isPermanentPushFailure(reply: UnionMessageReply): boolean;
|
|
32
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-messages.d.ts","sourceRoot":"","sources":["../../src/sync-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAErH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAwB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAWxE,kFAAkF;AAClF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,8FAA8F;IAC9F,YAAY,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iBAAiB,EAAE,aAAa,CAAC,EAAE,cAAc,GAAG,OAAO,CAoB9G;
|
|
1
|
+
{"version":3,"file":"sync-messages.d.ts","sourceRoot":"","sources":["../../src/sync-messages.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/sync-engine-level.ts
CHANGED
|
@@ -1396,6 +1396,39 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
1396
1396
|
}
|
|
1397
1397
|
}
|
|
1398
1398
|
|
|
1399
|
+
/**
|
|
1400
|
+
* Wrapper around {@link initializeLinkTarget} that retries on DID
|
|
1401
|
+
* resolution failures. Newly published `did:dht` DIDs take a few
|
|
1402
|
+
* seconds to propagate through the DHT network. During this window,
|
|
1403
|
+
* the remote DWN can't resolve the DID to verify request signatures,
|
|
1404
|
+
* causing a 401. Retrying with exponential backoff lets the
|
|
1405
|
+
* propagation settle before giving up.
|
|
1406
|
+
*/
|
|
1407
|
+
private async initializeLinkTargetWithRetry(target: {
|
|
1408
|
+
did: string; dwnUrl: string; delegateDid?: string; protocol?: string;
|
|
1409
|
+
}): Promise<void> {
|
|
1410
|
+
try {
|
|
1411
|
+
await this.initializeLinkTarget(target);
|
|
1412
|
+
} catch (error: any) {
|
|
1413
|
+
const msg = error.message ?? '';
|
|
1414
|
+
const isDidResolutionFailure = msg.includes('GetPublicKeyNotFound') || msg.includes('notFound');
|
|
1415
|
+
if (!isDidResolutionFailure) { throw error; }
|
|
1416
|
+
|
|
1417
|
+
const delays = [2000, 4000, 8000];
|
|
1418
|
+
for (const delay of delays) {
|
|
1419
|
+
await sleep(delay);
|
|
1420
|
+
try {
|
|
1421
|
+
await this.initializeLinkTarget(target);
|
|
1422
|
+
return;
|
|
1423
|
+
} catch {
|
|
1424
|
+
// Continue to next attempt.
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
// All retries exhausted — the original error was already logged
|
|
1428
|
+
// by initializeLinkTarget's catch block.
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1399
1432
|
// ---------------------------------------------------------------------------
|
|
1400
1433
|
// Hot-add / hot-remove: per-identity live sync management
|
|
1401
1434
|
// ---------------------------------------------------------------------------
|
|
@@ -1430,7 +1463,7 @@ export class SyncEngineLevel implements SyncEngine {
|
|
|
1430
1463
|
}
|
|
1431
1464
|
}
|
|
1432
1465
|
|
|
1433
|
-
await Promise.allSettled(targets.map(t => this.
|
|
1466
|
+
await Promise.allSettled(targets.map(t => this.initializeLinkTargetWithRetry(t)));
|
|
1434
1467
|
}
|
|
1435
1468
|
|
|
1436
1469
|
/** Hot-remove a single identity from the active live sync session. */
|
package/src/sync-messages.ts
CHANGED
|
@@ -54,17 +54,33 @@ export function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMes
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
|
|
57
|
+
* 400 detail substrings that indicate a protocol dependency hasn't
|
|
58
|
+
* arrived on the remote yet. These resolve on retry once the
|
|
59
|
+
* dependency is pushed, so they must NOT be treated as permanent.
|
|
60
|
+
*/
|
|
61
|
+
const TRANSIENT_DEPENDENCY_PATTERNS = [
|
|
62
|
+
'ComposedProtocolNotInstalled',
|
|
63
|
+
'ProtocolNotFound',
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Classifies a failed push reply as permanent (dead-letter) or
|
|
68
|
+
* transient (retry with backoff).
|
|
61
69
|
*
|
|
62
|
-
*
|
|
70
|
+
* Permanent: 401/403 auth errors, most 400 validation errors.
|
|
71
|
+
* Transient: 5xx, network errors, and 400 protocol-dependency errors
|
|
72
|
+
* that self-heal once the dependency arrives on the remote.
|
|
63
73
|
*/
|
|
64
74
|
export function isPermanentPushFailure(reply: UnionMessageReply): boolean {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
const { code, detail } = reply.status;
|
|
76
|
+
|
|
77
|
+
if (code === 401 || code === 403) { return true; }
|
|
78
|
+
|
|
79
|
+
if (code === 400) {
|
|
80
|
+
return !TRANSIENT_DEPENDENCY_PATTERNS.some(pattern => detail?.includes(pattern));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return false;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
/**
|
|
@@ -368,14 +384,26 @@ export async function pushMessages({ did, dwnUrl, delegateDid, protocol, message
|
|
|
368
384
|
// Step 2: Sort in dependency order using topological sort.
|
|
369
385
|
const sorted = topologicalSort(fetched);
|
|
370
386
|
|
|
371
|
-
// Step 3:
|
|
387
|
+
// Step 3: Buffer data streams so they survive fetch retries.
|
|
388
|
+
// ReadableStream is single-use — if sendDwnRequest's underlying fetch
|
|
389
|
+
// retries the HTTP request, the original stream is already consumed.
|
|
390
|
+
await bufferSmallStreams(sorted);
|
|
391
|
+
|
|
392
|
+
// Step 4: Push messages in dependency order.
|
|
372
393
|
for (const entry of sorted) {
|
|
373
394
|
const cid = await getMessageCid(entry.message);
|
|
395
|
+
|
|
396
|
+
// Use a Blob for buffered data — unlike ReadableStream, Blob is
|
|
397
|
+
// replayable so fetchWithRetry can retry the HTTP request on failure.
|
|
398
|
+
const data = entry.bufferedData
|
|
399
|
+
? new Blob([entry.bufferedData] as BlobPart[], { type: 'application/octet-stream' })
|
|
400
|
+
: entry.dataStream;
|
|
401
|
+
|
|
374
402
|
try {
|
|
375
403
|
const reply = await agent.rpc.sendDwnRequest({
|
|
376
404
|
dwnUrl,
|
|
377
405
|
targetDid : did,
|
|
378
|
-
data
|
|
406
|
+
data,
|
|
379
407
|
message : entry.message
|
|
380
408
|
});
|
|
381
409
|
|