libp2r2p 0.11.15 → 0.11.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -936,9 +936,22 @@ also admits relay policy refusals (`blocked:`, `restricted:`, `auth-required:`,
936
936
  Local authentication denials, invalid events and unknown errors qualify for
937
937
  neither policy. Predicates classify individual native errors; callers decide
938
938
  how to handle mixed per-relay outcomes and application-specific aggregates.
939
- Existing rate-limit parsing preserves numeric positive `retry_after` seconds as
940
- `retryAfterMs` and absolute `retryAt`, bounded to five minutes. Schedulers must
941
- use the later of their backoff deadline and `retryAt`, not add the two delays.
939
+ `parseRelayRetryAdvice(reason, extra, { now = Date.now() })`, exported by
940
+ `libp2r2p/relay`, returns `{ retryAt, retryAfterMs? }` or `null`. It only interprets
941
+ leading `rate-limited:` refusals. Optional `retry_at` is a Unix timestamp in
942
+ seconds (fractions allowed), converted to milliseconds. Finite positive absolute
943
+ advice takes precedence over `retry_after`, including when already expired;
944
+ expired advice does not restart a relative wait. Future waits are capped at five
945
+ minutes from processing. Missing/invalid absolute advice falls back to finite
946
+ positive relative seconds, capped at 300. A valid relative value is retained as
947
+ `retryAfterMs` even when the absolute deadline wins. Schedulers wait until
948
+ max(local backoff deadline, retryAt), never sum the two or renew the deadline on
949
+ online notifications.
950
+
951
+ The optional Nostr extension works with older relays that only send
952
+ `retry_after` and older consumers that ignore `retry_at`. Neither field supplies
953
+ provenance or retry eligibility: `local`, `origin`, `retryable`, category and code
954
+ claims in extras are ignored. Native messages, causes and codes remain intact.
942
955
 
943
956
  ## Binary encodings
944
957
 
@@ -1059,7 +1072,7 @@ user-facing send error.
1059
1072
 
1060
1073
  ### Relay backpressure and bounded synchronization (0.11.7)
1061
1074
 
1062
- Optional numeric `retry_after` seconds on relay `CLOSED` and rejected `OK`
1075
+ Optional `retry_at` Unix seconds or numeric `retry_after` seconds on relay `CLOSED` and rejected `OK`
1063
1076
  responses are preserved as `retryAfterMs` and `retryAt` on native relay errors.
1064
1077
  Only `rate-limited:` responses apply this advice; invalid values are ignored
1065
1078
  and delays are capped at five minutes. New operations on that connection wait
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libp2r2p",
3
- "version": "0.11.15",
3
+ "version": "0.11.16",
4
4
  "description": "Peer-to-relay-to-peer",
5
5
  "keywords": [
6
6
  "p2r2p",
@@ -1,3 +1,5 @@
1
+ import { parseRelayRetryAdvice } from './retry-advice.js'
2
+
1
3
  // Adds transport context without replacing native messages, codes or causes.
2
4
  export function categorizeRelayError (reason, category, fallback = 'RELAY_OPERATION_FAILED') {
3
5
  const error = reason instanceof Error ? reason : new Error(String(reason || fallback))
@@ -29,14 +31,10 @@ export function relayCloseError (event, category, cause) {
29
31
  return categorizeRelayError(error, category)
30
32
  }
31
33
 
32
- // retry_after is an optional relay extension, in seconds. Bound remote advice
33
- // to five minutes; invalid metadata must not stall unrelated operations.
34
+ // Keep native diagnostics and attach only bounded timing advice. An absolute
35
+ // deadline survives delayed delivery through a launcher-owned relay bridge.
34
36
  export function relayRejectionError (reason, extra, fallback) {
35
37
  const error = categorizeRelayError(reason, 'relay', fallback)
36
- const seconds = extra?.retry_after
37
- if (error.message.startsWith('rate-limited:') && typeof seconds === 'number' && Number.isFinite(seconds) && seconds > 0) {
38
- error.retryAfterMs = Math.min(seconds, 300) * 1000
39
- error.retryAt = Date.now() + error.retryAfterMs
40
- }
38
+ Object.assign(error, parseRelayRetryAdvice(error.message, extra))
41
39
  return error
42
40
  }
@@ -0,0 +1,18 @@
1
+ // Nostr transport extensions, in seconds. Timing advice is bounded and never
2
+ // supplies an error category, provenance or a routing/retry policy.
3
+ export function parseRelayRetryAdvice (reason, extra, { now = Date.now() } = {}) {
4
+ if (typeof reason !== 'string' || !reason.startsWith('rate-limited:') ||
5
+ !extra || typeof extra !== 'object' || Array.isArray(extra) || !Number.isFinite(now)) return null
6
+
7
+ const after = extra.retry_after
8
+ const retryAfterMs = typeof after === 'number' && Number.isFinite(after) && after > 0
9
+ ? Math.min(after, 300) * 1000
10
+ : undefined
11
+ const at = extra.retry_at
12
+ const absolute = typeof at === 'number' && Number.isFinite(at) && at > 0 && Number.isFinite(at * 1000)
13
+ ? Math.min(at * 1000, now + 300000)
14
+ : undefined
15
+ const retryAt = absolute ?? (retryAfterMs === undefined ? undefined : now + retryAfterMs)
16
+ if (retryAt === undefined) return null
17
+ return { ...(retryAfterMs === undefined ? {} : { retryAfterMs }), retryAt }
18
+ }
package/relay/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { freeRelays, nappRelays, seedRelays } from './constants/index.js'
2
2
  export { isRetryableRelayFailure, isReplaceableRelayFailure } from './helpers/failure.js'
3
+ export { parseRelayRetryAdvice } from './helpers/retry-advice.js'
3
4
  export { pickRelaysForPubkeys } from './helpers/routing.js'
4
5
  export { RelayPool, relayPool } from './services/relay-pool.js'
5
6
  export { getLatestEventsByPubkey } from './services/events.js'