libp2r2p 0.11.19 → 0.11.20
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 +21 -16
- package/package.json +1 -1
- package/relay/helpers/error.js +2 -2
- package/relay/helpers/retry-advice.js +9 -14
- package/relay/services/relay-connection.js +16 -13
package/README.md
CHANGED
|
@@ -955,22 +955,26 @@ also admits relay policy refusals (`blocked:`, `restricted:`, `auth-required:`,
|
|
|
955
955
|
Local authentication denials, invalid events and unknown errors qualify for
|
|
956
956
|
neither policy. Predicates classify individual native errors; callers decide
|
|
957
957
|
how to handle mixed per-relay outcomes and application-specific aggregates.
|
|
958
|
-
`parseRelayRetryAdvice(reason,
|
|
958
|
+
`parseRelayRetryAdvice(reason, params, { now = Date.now() })`, exported by
|
|
959
959
|
`libp2r2p/relay`, returns `{ retryAt, retryAfterMs? }` or `null`. It only interprets
|
|
960
|
-
leading `rate-limited:` refusals.
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
960
|
+
leading `rate-limited:` refusals. The optional string `params` is relative
|
|
961
|
+
milliseconds (e.g. "2500") or an absolute Unix timestamp in milliseconds
|
|
962
|
+
prefixed with `@` (e.g. "@1791738154332"). Values must be positive safe integers
|
|
963
|
+
in canonical decimal form, without signs, whitespace, fractions or leading zeros.
|
|
964
|
+
Relative values produce both `retryAfterMs` and `retryAt`; absolute values produce
|
|
965
|
+
only `retryAt` and retain an expired deadline without starting a new wait.
|
|
966
|
+
Future waits are capped at five minutes. Missing or invalid params are ignored
|
|
967
|
+
and do not instruct clients to retry immediately. Schedulers wait until
|
|
967
968
|
max(local backoff deadline, retryAt), never sum the two or renew the deadline on
|
|
968
969
|
online notifications.
|
|
969
970
|
|
|
970
|
-
|
|
971
|
-
`
|
|
972
|
-
|
|
973
|
-
|
|
971
|
+
Rate limits apply only to the client message type that triggered the refusal:
|
|
972
|
+
`EVENT`, `AUTH`, `REQ` and `COUNT` have independent connection cooldowns. Responses
|
|
973
|
+
are correlated with pending operations; unknown IDs never create cooldowns.
|
|
974
|
+
CLOSE cleanup remains immediate. The launcher can replace valid relative params
|
|
975
|
+
with `@` timestamps before queueing bridge delivery because its consumers share
|
|
976
|
+
its clock. No legacy timing objects are interpreted. Params never supply provenance
|
|
977
|
+
or retry eligibility; native messages, causes and codes remain intact.
|
|
974
978
|
|
|
975
979
|
## Binary encodings
|
|
976
980
|
|
|
@@ -1091,10 +1095,11 @@ user-facing send error.
|
|
|
1091
1095
|
|
|
1092
1096
|
### Relay backpressure and bounded synchronization (0.11.7)
|
|
1093
1097
|
|
|
1094
|
-
Optional `
|
|
1095
|
-
responses are preserved as
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
+
Optional decimal `params` milliseconds or `@` Unix milliseconds on relay `CLOSED`
|
|
1099
|
+
and rejected `OK` responses are preserved as normalized `retryAt` (and
|
|
1100
|
+
`retryAfterMs` for relative params) on native relay errors. Only `rate-limited:`
|
|
1101
|
+
responses apply this advice; invalid values are ignored
|
|
1102
|
+
and delays are capped at five minutes. New operations of the refused type wait
|
|
1098
1103
|
within their existing deadlines; active subscriptions remain live and `CLOSE`
|
|
1099
1104
|
never waits. Cancelled or timed-out operations cannot be sent later. A rejection
|
|
1100
1105
|
is still reported to the caller; this does not silently republish failed events.
|
package/package.json
CHANGED
package/relay/helpers/error.js
CHANGED
|
@@ -33,8 +33,8 @@ export function relayCloseError (event, category, cause) {
|
|
|
33
33
|
|
|
34
34
|
// Keep native diagnostics and attach only bounded timing advice. An absolute
|
|
35
35
|
// deadline survives delayed delivery through a launcher-owned relay bridge.
|
|
36
|
-
export function relayRejectionError (reason,
|
|
36
|
+
export function relayRejectionError (reason, params, fallback) {
|
|
37
37
|
const error = categorizeRelayError(reason, 'relay', fallback)
|
|
38
|
-
Object.assign(error, parseRelayRetryAdvice(error.message,
|
|
38
|
+
Object.assign(error, parseRelayRetryAdvice(error.message, params))
|
|
39
39
|
return error
|
|
40
40
|
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
// Nostr
|
|
1
|
+
// Nostr params use relative milliseconds or @Unix milliseconds. Advice never
|
|
2
2
|
// supplies an error category, provenance or a routing/retry policy.
|
|
3
|
-
export function parseRelayRetryAdvice (reason,
|
|
3
|
+
export function parseRelayRetryAdvice (reason, params, { now = Date.now() } = {}) {
|
|
4
4
|
if (typeof reason !== 'string' || !reason.startsWith('rate-limited:') ||
|
|
5
|
-
|
|
5
|
+
typeof params !== 'string' || params.match(/^@?[1-9]\d*$/)?.[0] !== params || !Number.isFinite(now)) return null
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
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 }
|
|
7
|
+
const absolute = params.startsWith('@')
|
|
8
|
+
const value = Number(absolute ? params.slice(1) : params)
|
|
9
|
+
if (!Number.isSafeInteger(value)) return null
|
|
10
|
+
if (absolute) return { retryAt: Math.min(value, now + 300000) }
|
|
11
|
+
const retryAfterMs = Math.min(value, 300000)
|
|
12
|
+
return { retryAfterMs, retryAt: now + retryAfterMs }
|
|
18
13
|
}
|
|
@@ -50,7 +50,7 @@ export class RelayConnection {
|
|
|
50
50
|
#publishes = new Map()
|
|
51
51
|
#authentications = new Map()
|
|
52
52
|
#counts = new Map()
|
|
53
|
-
#
|
|
53
|
+
#retryAtByType = new Map()
|
|
54
54
|
|
|
55
55
|
constructor (url, { WebSocket: WebSocketImpl = globalThis.WebSocket } = {}) {
|
|
56
56
|
this.url = url
|
|
@@ -124,12 +124,12 @@ export class RelayConnection {
|
|
|
124
124
|
|
|
125
125
|
// New work respects relay cooldowns; cleanup uses send() directly. These
|
|
126
126
|
// waits stay within the caller's existing deadline and are cancellable.
|
|
127
|
-
#dispatchWork (send, fail) {
|
|
127
|
+
#dispatchWork (type, send, fail) {
|
|
128
128
|
let timer = null
|
|
129
129
|
let cancelled = false
|
|
130
130
|
const dispatch = () => {
|
|
131
131
|
if (cancelled) return
|
|
132
|
-
const remaining = this.#
|
|
132
|
+
const remaining = (this.#retryAtByType.get(type) ?? 0) - Date.now()
|
|
133
133
|
if (remaining > 0) {
|
|
134
134
|
timer = maybeUnref(setTimeout(dispatch, remaining))
|
|
135
135
|
return
|
|
@@ -140,9 +140,9 @@ export class RelayConnection {
|
|
|
140
140
|
return () => { cancelled = true; clearTimeout(timer) }
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
#rejection (reason,
|
|
144
|
-
const error = relayRejectionError(reason,
|
|
145
|
-
this.#
|
|
143
|
+
#rejection (type, reason, params, fallback) {
|
|
144
|
+
const error = relayRejectionError(reason, params, fallback)
|
|
145
|
+
if (error.retryAt !== undefined) this.#retryAtByType.set(type, Math.max(this.#retryAtByType.get(type) ?? 0, error.retryAt))
|
|
146
146
|
return error
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -163,7 +163,7 @@ export class RelayConnection {
|
|
|
163
163
|
}
|
|
164
164
|
const subscription = { filters, handlers, close, cancelSend: null }
|
|
165
165
|
this.#subscriptions.set(id, subscription)
|
|
166
|
-
subscription.cancelSend = this.#dispatchWork(() => {
|
|
166
|
+
subscription.cancelSend = this.#dispatchWork('REQ', () => {
|
|
167
167
|
this.send(JSON.stringify(['REQ', id, ...filters]))
|
|
168
168
|
sent = true
|
|
169
169
|
}, error => {
|
|
@@ -209,7 +209,7 @@ export class RelayConnection {
|
|
|
209
209
|
pending.waiters.add(waiter)
|
|
210
210
|
signal?.addEventListener('abort', onAbort, { once: true })
|
|
211
211
|
if (created) {
|
|
212
|
-
pending.cancelSend = this.#dispatchWork(() => {
|
|
212
|
+
pending.cancelSend = this.#dispatchWork(type, () => {
|
|
213
213
|
this.send(JSON.stringify([type, event]))
|
|
214
214
|
}, error => this.#settleEvent(map, event.id, error))
|
|
215
215
|
}
|
|
@@ -224,7 +224,7 @@ export class RelayConnection {
|
|
|
224
224
|
const pending = { ...deferred, signal, onAbort, cancelSend: null }
|
|
225
225
|
this.#counts.set(id, pending)
|
|
226
226
|
signal?.addEventListener('abort', onAbort, { once: true })
|
|
227
|
-
pending.cancelSend = this.#dispatchWork(() => this.send(JSON.stringify(['COUNT', id, ...filters])), error => {
|
|
227
|
+
pending.cancelSend = this.#dispatchWork('COUNT', () => this.send(JSON.stringify(['COUNT', id, ...filters])), error => {
|
|
228
228
|
this.#settleCount(id, null, error)
|
|
229
229
|
})
|
|
230
230
|
return deferred.promise
|
|
@@ -277,7 +277,8 @@ export class RelayConnection {
|
|
|
277
277
|
if (data[0] === 'CLOSED') {
|
|
278
278
|
const id = data[1]
|
|
279
279
|
const subscription = this.#subscriptions.get(id)
|
|
280
|
-
|
|
280
|
+
if (!subscription && !this.#counts.has(id)) return
|
|
281
|
+
const reason = this.#rejection(subscription ? 'REQ' : 'COUNT', data[2], data[3], subscription ? 'SUBSCRIPTION_CLOSED' : 'COUNT_CLOSED')
|
|
281
282
|
if (subscription) {
|
|
282
283
|
this.#subscriptions.delete(id)
|
|
283
284
|
subscription.cancelSend?.()
|
|
@@ -286,9 +287,11 @@ export class RelayConnection {
|
|
|
286
287
|
return
|
|
287
288
|
}
|
|
288
289
|
if (data[0] === 'OK') {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
for (const [type, pending] of [['EVENT', this.#publishes], ['AUTH', this.#authentications]]) {
|
|
291
|
+
if (!pending.has(data[1])) continue
|
|
292
|
+
const reason = data[2] === true ? null : this.#rejection(type, data[3], data[4], 'EVENT_REJECTED')
|
|
293
|
+
this.#settleEvent(pending, data[1], reason, data[3])
|
|
294
|
+
}
|
|
292
295
|
return
|
|
293
296
|
}
|
|
294
297
|
if (data[0] === 'AUTH' && typeof data[1] === 'string') {
|