libp2r2p 0.10.26 → 0.10.28
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
|
@@ -519,6 +519,29 @@ This also covers abrupt termination without `close()` and channels with no
|
|
|
519
519
|
messages. Applications still supply signers and desired channels on reopening;
|
|
520
520
|
storage is scoped by origin and user pubkey, not the transient session ID.
|
|
521
521
|
Failed ingestion or incomplete relay fetch reports never complete the gap.
|
|
522
|
+
`private-channel.fetch` reports incomplete reads as an `AggregateError` with
|
|
523
|
+
stable `code: 'PRIVATE_CHANNEL_FETCH_INCOMPLETE'`. Its message names relays,
|
|
524
|
+
completion statuses and native failure messages, including status-only failures.
|
|
525
|
+
`relays` retains all `{ relay, status, error? }` outcomes; `relayErrors` preserves
|
|
526
|
+
`{ relay, reason }` attribution and `errors` retains unique native causes from
|
|
527
|
+
both sources. `request` records selected relays, channel pubkeys, receiver pubkey,
|
|
528
|
+
`since`/`until`/`limit` and the 5000ms read timeout. `elapsedMs` measures the read
|
|
529
|
+
including admission wait, excluding decryption/storage; `receivedEventCount`
|
|
530
|
+
counts fetched outer events, not delivered messages. No event bodies or signers
|
|
531
|
+
are attached. These diagnostics reach the messenger's `onError` unchanged.
|
|
532
|
+
A single incomplete relay keeps the interval pending even if another relay
|
|
533
|
+
returned events; received messages may already have been queued. This is a
|
|
534
|
+
history completeness failure, not evidence that an outgoing message failed.
|
|
535
|
+
Existing rewatch/resume and relay-list refresh paths retry pending intervals;
|
|
536
|
+
there is no dedicated periodic retry timer for a failed history fetch.
|
|
537
|
+
Subscription relay errors forwarded to `onError` include `relay` when known
|
|
538
|
+
and `operation: 'private-channel.subscribe'`. A per-notification wrapper retains
|
|
539
|
+
the native message, name, code, category and WebSocket close fields; `cause`
|
|
540
|
+
references the untouched original error, including its stack and causes.
|
|
541
|
+
Aggregate errors keep their nested errors. This also works with frozen/shared
|
|
542
|
+
errors without leaking attribution between subscribers. Missing relay metadata
|
|
543
|
+
is not inferred from the selected relay list. This does not change reconnection,
|
|
544
|
+
delivery or error classification; processing errors keep their existing path.
|
|
522
545
|
`lastSeenAt` tracks messages persisted in the incoming queue, not app commits.
|
|
523
546
|
The default recovery window is seven days; recovery still depends on available
|
|
524
547
|
relay/seeder data, and retention limits cannot guarantee indefinite delivery.
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const describe = reason => `${reason?.category ? `[${reason.category}] ` : ''}${reason?.message || String(reason)}`
|
|
2
|
+
|
|
3
|
+
export function incompleteFetchError ({ errors, report, request, receivedEventCount, elapsedMs }) {
|
|
4
|
+
// Keep only read metadata, never events, decrypted payloads or signers.
|
|
5
|
+
const relayErrors = errors.map(({ relay, reason }) => ({ relay, reason }))
|
|
6
|
+
const relays = report.map(({ relay, status, error }) => ({ relay, status, ...(error ? { error } : {}) }))
|
|
7
|
+
const reasons = [...new Set([...relayErrors.map(entry => entry.reason), ...relays.map(entry => entry.error)].filter(reason => reason !== undefined))]
|
|
8
|
+
const details = relays.map(({ relay, status, error }) => {
|
|
9
|
+
const failures = relayErrors.filter(entry => entry.relay === relay).map(entry => entry.reason)
|
|
10
|
+
if (error && !failures.includes(error)) failures.push(error)
|
|
11
|
+
return `${relay} [${status}]${failures.length ? `: ${failures.map(describe).join('; ')}` : ''}`
|
|
12
|
+
})
|
|
13
|
+
for (const { relay, reason } of relayErrors) {
|
|
14
|
+
if (!relays.some(entry => entry.relay === relay)) details.push(`${relay || 'unknown relay'}: ${describe(reason)}`)
|
|
15
|
+
}
|
|
16
|
+
return Object.assign(new AggregateError(reasons,
|
|
17
|
+
`PRIVATE_CHANNEL_FETCH_INCOMPLETE: ${details.join(' | ')} (received=${receivedEventCount}, readElapsedMs=${elapsedMs}, timeoutMs=${request.timeoutMs})`), {
|
|
18
|
+
code: 'PRIVATE_CHANNEL_FETCH_INCOMPLETE',
|
|
19
|
+
operation: 'private-channel.fetch',
|
|
20
|
+
request,
|
|
21
|
+
receivedEventCount,
|
|
22
|
+
elapsedMs,
|
|
23
|
+
relays,
|
|
24
|
+
relayErrors
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Relay errors can be shared by multiple subscriptions or be frozen. Attach
|
|
2
|
+
// consumer context to a new error and retain the untouched original as cause.
|
|
3
|
+
export function subscriptionError (reason, relay) {
|
|
4
|
+
const message = reason?.message ?? String(reason)
|
|
5
|
+
const error = reason instanceof AggregateError
|
|
6
|
+
? new AggregateError(reason.errors, message, { cause: reason })
|
|
7
|
+
: new Error(message, { cause: reason })
|
|
8
|
+
for (const key of ['name', 'code', 'category', 'closeCode', 'closeReason', 'wasClean']) {
|
|
9
|
+
if (reason?.[key] !== undefined) error[key] = reason[key]
|
|
10
|
+
}
|
|
11
|
+
error.operation = 'private-channel.subscribe'
|
|
12
|
+
if (typeof relay === 'string') error.relay = relay
|
|
13
|
+
return error
|
|
14
|
+
}
|
package/private-channel/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { isValidContentKeyProof, isValidIykcProof, makeContentKeyEventForPubkey,
|
|
|
6
6
|
import { getIykcProofs } from '../content-key/index.js'
|
|
7
7
|
import { ValidationError } from '../error/index.js'
|
|
8
8
|
import { normalizeRumor, deliveryInfo } from './helpers/rumor.js'
|
|
9
|
+
import { incompleteFetchError } from './helpers/fetch-error.js'
|
|
10
|
+
import { subscriptionError } from './helpers/subscription-error.js'
|
|
9
11
|
import * as nip44v3 from '../nip44-v3/index.js'
|
|
10
12
|
import { relayPool } from '../relay/index.js'
|
|
11
13
|
import { JSONL_CHUNK_BYTES, NYM_CARRIER_CHUNK_CHARS } from './helpers/chunk-size.js'
|
|
@@ -907,11 +909,14 @@ export async function fetch ({ signal, receiverSigner, iykcSigner, privateChanne
|
|
|
907
909
|
if (until != null) filter.until = until
|
|
908
910
|
if (limit != null) filter.limit = limit
|
|
909
911
|
|
|
912
|
+
const timeoutMs = 5000
|
|
913
|
+
const startedAt = performance.now()
|
|
910
914
|
const { result, errors = [], relays: report = [] } = await _getEvents(filter, relays, {
|
|
911
|
-
timeout:
|
|
915
|
+
timeout: timeoutMs,
|
|
912
916
|
timeoutAfterFirstEose: null,
|
|
913
917
|
...(signal ? { signal } : {})
|
|
914
918
|
})
|
|
919
|
+
const elapsedMs = Math.round(performance.now() - startedAt)
|
|
915
920
|
const events = result.map(({ event }) => event)
|
|
916
921
|
events.sort((a, b) => a.created_at - b.created_at)
|
|
917
922
|
const processOuterEvent = createProcessor({ receiverSigner, iykcSigner, privateChannelSigner, privateChannelSignersByPubkey, privateChannelReaderSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, receiverPubkey, mode, modeByPubkey, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, receivedChunkScope, receivedChunkTtlMs, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes, receivedChunkIndexedDB, ignoredGroupTtlMs, ignoredGroupMaxEntries })
|
|
@@ -922,7 +927,10 @@ export async function fetch ({ signal, receiverSigner, iykcSigner, privateChanne
|
|
|
922
927
|
}
|
|
923
928
|
signal?.throwIfAborted()
|
|
924
929
|
if (errors.length || report.some(entry => !['eose', 'satisfied'].includes(entry.status))) {
|
|
925
|
-
throw
|
|
930
|
+
throw incompleteFetchError({
|
|
931
|
+
errors, report, elapsedMs, receivedEventCount: events.length,
|
|
932
|
+
request: { relays: [...relays], channelPubkeys: [...authors], receiverPubkey, since, until, limit, timeoutMs }
|
|
933
|
+
})
|
|
926
934
|
}
|
|
927
935
|
return events
|
|
928
936
|
} finally {
|
|
@@ -955,7 +963,7 @@ export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner =
|
|
|
955
963
|
try {
|
|
956
964
|
for await (const item of events) {
|
|
957
965
|
if (controller.signal.aborted) continue
|
|
958
|
-
if (item.type === 'error') onError?.(item.error)
|
|
966
|
+
if (item.type === 'error') onError?.(subscriptionError(item.error, item.relay))
|
|
959
967
|
else if (item.type === 'event') await processOuterEvent(item.event)
|
|
960
968
|
}
|
|
961
969
|
} catch (error) {
|