libp2r2p 0.10.25 → 0.10.27
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,21 @@ 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.
|
|
522
537
|
`lastSeenAt` tracks messages persisted in the incoming queue, not app commits.
|
|
523
538
|
The default recovery window is seven days; recovery still depends on available
|
|
524
539
|
relay/seeder data, and retention limits cannot guarantee indefinite delivery.
|
|
@@ -854,3 +869,16 @@ A stale token cannot acknowledge a newly reserved or replaced record.
|
|
|
854
869
|
|
|
855
870
|
`queue.getCapacity()` returns `{ usedBytes, maxBytes }`; capacity rejections
|
|
856
871
|
also expose `requiredBytes` and `maxBytes`, without retaining the rejected item.
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
### Publication acknowledgement deadlines
|
|
875
|
+
|
|
876
|
+
`RelayPool.sendEvent` returns immediately when the first relay accepts an event,
|
|
877
|
+
when all relays fail, or when the operation's `timeout` expires (30 seconds by
|
|
878
|
+
default, including connection work). `timeoutUntilFirstFulfillment` now defaults
|
|
879
|
+
to `null`, removing the former 3-second cutoff. Explicit shorter deadlines remain
|
|
880
|
+
supported and still finalize pending reports as timeouts. `promise` provides the
|
|
881
|
+
complete per-relay report without delaying the first-success return. Connection
|
|
882
|
+
establishment retains its separate 3-second deadline. PrivateMessenger inherits
|
|
883
|
+
this publication policy, including seeder/recovery messages. A timeout indicates
|
|
884
|
+
missing acknowledgement and cannot prove that the recipient did not receive it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libp2r2p",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.27",
|
|
4
4
|
"description": "Peer-to-relay-to-peer",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"p2r2p",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"@noble/curves": "2.2.0",
|
|
98
98
|
"@noble/hashes": "2.2.0",
|
|
99
99
|
"@scure/base": "2.0.0",
|
|
100
|
-
"nmmr": "2.0.
|
|
100
|
+
"nmmr": "2.0.1"
|
|
101
101
|
},
|
|
102
102
|
"devDependencies": {
|
|
103
103
|
"eslint": "^9.39.5",
|
|
@@ -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
|
+
}
|
package/private-channel/index.js
CHANGED
|
@@ -6,6 +6,7 @@ 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'
|
|
9
10
|
import * as nip44v3 from '../nip44-v3/index.js'
|
|
10
11
|
import { relayPool } from '../relay/index.js'
|
|
11
12
|
import { JSONL_CHUNK_BYTES, NYM_CARRIER_CHUNK_CHARS } from './helpers/chunk-size.js'
|
|
@@ -907,11 +908,14 @@ export async function fetch ({ signal, receiverSigner, iykcSigner, privateChanne
|
|
|
907
908
|
if (until != null) filter.until = until
|
|
908
909
|
if (limit != null) filter.limit = limit
|
|
909
910
|
|
|
911
|
+
const timeoutMs = 5000
|
|
912
|
+
const startedAt = performance.now()
|
|
910
913
|
const { result, errors = [], relays: report = [] } = await _getEvents(filter, relays, {
|
|
911
|
-
timeout:
|
|
914
|
+
timeout: timeoutMs,
|
|
912
915
|
timeoutAfterFirstEose: null,
|
|
913
916
|
...(signal ? { signal } : {})
|
|
914
917
|
})
|
|
918
|
+
const elapsedMs = Math.round(performance.now() - startedAt)
|
|
915
919
|
const events = result.map(({ event }) => event)
|
|
916
920
|
events.sort((a, b) => a.created_at - b.created_at)
|
|
917
921
|
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 +926,10 @@ export async function fetch ({ signal, receiverSigner, iykcSigner, privateChanne
|
|
|
922
926
|
}
|
|
923
927
|
signal?.throwIfAborted()
|
|
924
928
|
if (errors.length || report.some(entry => !['eose', 'satisfied'].includes(entry.status))) {
|
|
925
|
-
throw
|
|
929
|
+
throw incompleteFetchError({
|
|
930
|
+
errors, report, elapsedMs, receivedEventCount: events.length,
|
|
931
|
+
request: { relays: [...relays], channelPubkeys: [...authors], receiverPubkey, since, until, limit, timeoutMs }
|
|
932
|
+
})
|
|
926
933
|
}
|
|
927
934
|
return events
|
|
928
935
|
} finally {
|
|
@@ -11,7 +11,6 @@ import { RelayConnection } from './relay-connection.js'
|
|
|
11
11
|
const CONNECTION_TIMEOUT_MS = 3000
|
|
12
12
|
const COUNT_TIMEOUT_MS = 5000
|
|
13
13
|
const COUNT_TIMEOUT_AFTER_FIRST_COUNT_MS = 500
|
|
14
|
-
const SEND_TIMEOUT_UNTIL_FIRST_FULFILLMENT_MS = 3000
|
|
15
14
|
const SEND_TIMEOUT_MS = 30000
|
|
16
15
|
const LIVE_OVERLAP_SECONDS = 600
|
|
17
16
|
const LIVE_PROGRESS_INTERVAL_MS = 60000
|
|
@@ -1120,8 +1119,8 @@ export class RelayPool {
|
|
|
1120
1119
|
}
|
|
1121
1120
|
}
|
|
1122
1121
|
|
|
1123
|
-
// Returns
|
|
1124
|
-
//
|
|
1122
|
+
// Returns on the first acknowledgement, all failures, or the 30s deadline.
|
|
1123
|
+
// An optional timeoutUntilFirstFulfillment imposes an earlier deadline on this
|
|
1125
1124
|
// initial return and closes pending reports when it fails. null disables either
|
|
1126
1125
|
// timer independently. onRelayResult receives one
|
|
1127
1126
|
// { relay, success, outcome, reason? } result per relay as it settles; outcome
|
|
@@ -1130,7 +1129,7 @@ export class RelayPool {
|
|
|
1130
1129
|
// Await `promise` for the complete report, including every relay outcome.
|
|
1131
1130
|
async sendEvent (event, relays, {
|
|
1132
1131
|
timeout = SEND_TIMEOUT_MS,
|
|
1133
|
-
timeoutUntilFirstFulfillment =
|
|
1132
|
+
timeoutUntilFirstFulfillment = null,
|
|
1134
1133
|
getAuthEvent,
|
|
1135
1134
|
onRelayResult
|
|
1136
1135
|
} = {}) {
|