@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.
@@ -21,12 +21,12 @@ export type SyncMessageEntry = {
21
21
  */
22
22
  export declare function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMessage?: GenericMessage): boolean;
23
23
  /**
24
- * Determines whether a failed push reply represents a permanent failure that
25
- * should NOT be retried. Permanent failures include protocol violations (400),
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
- * Transient failures (5xx, network errors) are worth retrying.
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":"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"}
1
+ {"version":3,"file":"sync-messages.d.ts","sourceRoot":"","sources":["../../src/sync-messages.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enbox/agent",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "type": "module",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -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.initializeLinkTarget(t)));
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. */
@@ -54,17 +54,33 @@ export function syncMessageReplyIsSuccessful(reply: UnionMessageReply, pushedMes
54
54
  }
55
55
 
56
56
  /**
57
- * Determines whether a failed push reply represents a permanent failure that
58
- * should NOT be retried. Permanent failures include protocol violations (400),
59
- * authorization errors (401/403), and schema validation errors that will never
60
- * succeed regardless of retry.
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
- * Transient failures (5xx, network errors) are worth retrying.
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
- return reply.status.code === 400 ||
66
- reply.status.code === 401 ||
67
- reply.status.code === 403;
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: Push messages in dependency order, consuming each stream as we go.
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 : entry.dataStream,
406
+ data,
379
407
  message : entry.message
380
408
  });
381
409