libp2r2p 0.12.0 → 0.12.2
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 +51 -2
- package/helpers/secp256k1-wasm/backend.js +2 -3
- package/package.json +3 -3
- package/private-channel/helpers/prepared.js +12 -0
- package/private-channel/index.js +271 -32
- package/private-message/index.js +44 -23
- package/private-messenger/file/index.js +4 -2
- package/private-messenger/helpers/fallback-race.js +13 -2
- package/private-messenger/helpers/send-routing.js +2 -1
- package/private-messenger/index.js +30 -15
- package/private-messenger/services/pending-decryption.js +20 -4
- package/private-messenger/session/README.md +17 -3
- package/private-messenger/session/helpers/prepared-state.js +17 -0
- package/private-messenger/session/helpers/publication-journal.js +38 -10
- package/private-messenger/session/index.js +21 -9
- package/relay/services/relay-pool.js +127 -58
|
@@ -8,6 +8,7 @@ import { maybeUnref } from '../helpers/timer.js'
|
|
|
8
8
|
import { categorizeRelayError, relayTimeoutError } from '../helpers/error.js'
|
|
9
9
|
import { ReadRetry, isRetryableReadFailure, needsConnectivityCheck, waitUntil } from '../helpers/read-retry.js'
|
|
10
10
|
import { normalizeRelayUrl } from '../../url/index.js'
|
|
11
|
+
import { isValidEvent } from '../../event/index.js'
|
|
11
12
|
import { RelayConnection } from './relay-connection.js'
|
|
12
13
|
|
|
13
14
|
const CONNECTION_TIMEOUT_MS = 3000
|
|
@@ -69,7 +70,19 @@ function notifyRelayResult (onRelayResult, result) {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
function requiresNip42Auth (reason) {
|
|
72
|
-
|
|
73
|
+
const message = reason?.message || reason?.code || ''
|
|
74
|
+
return message.startsWith('auth-required:') || message.startsWith('restricted:')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function requiresOuterRefresh (reason) {
|
|
78
|
+
return requiresNip42Auth(reason) || (reason?.message || reason?.code || '').startsWith('invalid:')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isDefinitiveOuterRejection (report) {
|
|
82
|
+
return report?.success === false &&
|
|
83
|
+
Array.isArray(report.errors) &&
|
|
84
|
+
report.errors.length > 0 &&
|
|
85
|
+
report.errors.every(({ reason }) => requiresOuterRefresh(reason))
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
function countResponseError () {
|
|
@@ -1199,15 +1212,19 @@ export class RelayPool {
|
|
|
1199
1212
|
// { relay, success, outcome, reason? } result per relay as it settles; outcome
|
|
1200
1213
|
// is published, duplicate, muted, failed, or timed-out. getAuthEvent is used
|
|
1201
1214
|
// only after auth-required or restricted publish failures, then retries once.
|
|
1215
|
+
// getRetryEvent may return one fresh signed event after every relay rejected
|
|
1216
|
+
// the original with auth-required/restricted/invalid; it shares the operation deadline.
|
|
1202
1217
|
// Await `promise` for the complete report, including every relay outcome.
|
|
1203
1218
|
async sendEvent (event, relays, {
|
|
1204
1219
|
timeout = SEND_TIMEOUT_MS,
|
|
1205
1220
|
timeoutUntilFirstFulfillment = null,
|
|
1206
1221
|
getAuthEvent,
|
|
1222
|
+
getRetryEvent,
|
|
1207
1223
|
onRelayResult,
|
|
1208
1224
|
signal
|
|
1209
1225
|
} = {}) {
|
|
1210
1226
|
if (signal != null && (typeof signal.aborted !== 'boolean' || typeof signal.addEventListener !== 'function' || typeof signal.removeEventListener !== 'function')) throw new ValidationError('INVALID_RELAY_SIGNAL')
|
|
1227
|
+
if (getRetryEvent != null && typeof getRetryEvent !== 'function') throw new ValidationError('INVALID_RETRY_EVENT_CALLBACK')
|
|
1211
1228
|
const urls = normalizedRelayUrls(relays)
|
|
1212
1229
|
if (!urls.length) {
|
|
1213
1230
|
const promise = Promise.resolve(publishSummary([], urls, {
|
|
@@ -1216,69 +1233,121 @@ export class RelayPool {
|
|
|
1216
1233
|
return { total: 0, success: false, promise }
|
|
1217
1234
|
}
|
|
1218
1235
|
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1236
|
+
const deadline = timeout === null ? null : Date.now() + timeout
|
|
1237
|
+
const remainingTimeout = () => deadline === null ? null : Math.max(0, deadline - Date.now())
|
|
1238
|
+
const sendAttempt = async (eventToSend, { notify = true } = {}) => {
|
|
1239
|
+
const attemptTimeout = remainingTimeout()
|
|
1240
|
+
const eventForAttempt = eventToSend.meta ? { ...eventToSend } : eventToSend
|
|
1241
|
+
if (eventForAttempt.meta) delete eventForAttempt.meta
|
|
1242
|
+
const sendControllers = urls.map(() => new AbortController())
|
|
1243
|
+
const sendDeferreds = urls.map(() => Promise.withResolvers())
|
|
1244
|
+
const sendPromises = sendDeferreds.map(({ promise }) => promise)
|
|
1245
|
+
const bufferedResults = []
|
|
1246
|
+
const delivery = { notify }
|
|
1247
|
+
|
|
1248
|
+
// Starts before connection work so every relay shares one real deadline.
|
|
1249
|
+
const settlement = createPublishSettlements(sendPromises, attemptTimeout, {
|
|
1250
|
+
onSettled: (settlement, index) => {
|
|
1251
|
+
sendControllers[index].abort(settlement.reason)
|
|
1252
|
+
if (settlement.reason?.category === 'timeout' && !settlement.reason.cause) {
|
|
1253
|
+
const relay = this.#relays.get(normalizeRelayUrl(urls[index]))
|
|
1254
|
+
if (relay?.lastTransportError) settlement.reason.cause = relay.lastTransportError
|
|
1255
|
+
}
|
|
1256
|
+
const result = relayResultForSettlement(urls[index], settlement)
|
|
1257
|
+
if (delivery.notify) notifyRelayResult(onRelayResult, result)
|
|
1258
|
+
else bufferedResults.push(result)
|
|
1233
1259
|
}
|
|
1234
|
-
|
|
1235
|
-
}
|
|
1236
|
-
})
|
|
1237
|
-
|
|
1238
|
-
// Resolves after every relay settles (or reaches the operation timeout) as
|
|
1239
|
-
// { success, total, fulfilled, succeededRelays, errors },
|
|
1240
|
-
// where errors contains { relay, reason } entries for failed relays.
|
|
1241
|
-
const cancel = () => settlement.cancel(signal.reason)
|
|
1242
|
-
if (signal?.aborted) cancel()
|
|
1243
|
-
else signal?.addEventListener('abort', cancel, { once: true })
|
|
1244
|
-
const promise = settlement.promise
|
|
1245
|
-
.finally(() => signal?.removeEventListener('abort', cancel))
|
|
1246
|
-
.then(settlements => publishSummary(settlements, urls, {
|
|
1247
|
-
includeSucceededRelays: true
|
|
1248
|
-
}))
|
|
1260
|
+
})
|
|
1249
1261
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1262
|
+
// Resolves after every relay settles (or reaches the operation timeout) as
|
|
1263
|
+
// { success, total, fulfilled, succeededRelays, errors },
|
|
1264
|
+
// where errors contains { relay, reason } entries for failed relays.
|
|
1265
|
+
const cancel = () => settlement.cancel(signal.reason)
|
|
1266
|
+
if (signal?.aborted) cancel()
|
|
1267
|
+
else signal?.addEventListener('abort', cancel, { once: true })
|
|
1268
|
+
const promise = settlement.promise
|
|
1269
|
+
.finally(() => signal?.removeEventListener('abort', cancel))
|
|
1270
|
+
.then(settlements => publishSummary(settlements, urls, {
|
|
1271
|
+
includeSucceededRelays: true
|
|
1272
|
+
}))
|
|
1273
|
+
|
|
1274
|
+
urls.forEach((url, index) => {
|
|
1275
|
+
const deferred = sendDeferreds[index]
|
|
1276
|
+
;(async () => {
|
|
1277
|
+
try {
|
|
1278
|
+
const operationSignal = sendControllers[index].signal
|
|
1279
|
+
if (operationSignal.aborted) throw operationSignal.reason
|
|
1280
|
+
const relay = await this.#getRelay(url)
|
|
1281
|
+
if (operationSignal.aborted) throw operationSignal.reason
|
|
1282
|
+
return await this.#publishEvent(relay, eventForAttempt, getAuthEvent, operationSignal)
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
const reason = err instanceof Error ? err : new Error(String(err))
|
|
1285
|
+
if (reason instanceof Nip42AuthenticationError) throw reason
|
|
1286
|
+
if (reason.message.startsWith('duplicate:')) return 'duplicate'
|
|
1287
|
+
if (reason.message.startsWith('mute:')) {
|
|
1288
|
+
console.info([url, reason.message].filter(Boolean).join(' - '))
|
|
1289
|
+
return 'muted'
|
|
1290
|
+
}
|
|
1291
|
+
throw reason
|
|
1266
1292
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
})().then(deferred.resolve, deferred.reject)
|
|
1270
|
-
})
|
|
1293
|
+
})().then(deferred.resolve, deferred.reject)
|
|
1294
|
+
})
|
|
1271
1295
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1296
|
+
const earlyTimeout = timeoutUntilFirstFulfillment === null || attemptTimeout === null
|
|
1297
|
+
? timeoutUntilFirstFulfillment
|
|
1298
|
+
: Math.min(timeoutUntilFirstFulfillment, attemptTimeout)
|
|
1299
|
+
const success = await firstFulfillment(sendPromises, earlyTimeout, {
|
|
1300
|
+
fallback: promise.then(report => report.success)
|
|
1301
|
+
})
|
|
1302
|
+
if (!success) settlement.timeout()
|
|
1276
1303
|
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1304
|
+
const result = {
|
|
1305
|
+
total: urls.length,
|
|
1306
|
+
success,
|
|
1307
|
+
promise
|
|
1308
|
+
}
|
|
1309
|
+
Object.defineProperty(result, 'flushRelayResults', {
|
|
1310
|
+
value: () => {
|
|
1311
|
+
delivery.notify = true
|
|
1312
|
+
for (const result of bufferedResults.splice(0)) notifyRelayResult(onRelayResult, result)
|
|
1313
|
+
}
|
|
1314
|
+
})
|
|
1315
|
+
return result
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const first = await sendAttempt(event, { notify: !getRetryEvent })
|
|
1319
|
+
if (first.success || !getRetryEvent) {
|
|
1320
|
+
first.flushRelayResults()
|
|
1321
|
+
return first
|
|
1322
|
+
}
|
|
1323
|
+
let report
|
|
1324
|
+
try {
|
|
1325
|
+
report = await first.promise
|
|
1326
|
+
} catch {
|
|
1327
|
+
first.flushRelayResults()
|
|
1328
|
+
return first
|
|
1329
|
+
}
|
|
1330
|
+
if (!isDefinitiveOuterRejection(report) || (deadline !== null && Date.now() >= deadline)) {
|
|
1331
|
+
first.flushRelayResults()
|
|
1332
|
+
return first
|
|
1333
|
+
}
|
|
1334
|
+
let retryEvent
|
|
1335
|
+
try {
|
|
1336
|
+
retryEvent = await getRetryEvent({ event, report })
|
|
1337
|
+
} catch (cause) {
|
|
1338
|
+
throw Object.assign(new Error('RETRY_EVENT_PREPARATION_FAILED', { cause }), {
|
|
1339
|
+
code: 'RETRY_EVENT_PREPARATION_FAILED',
|
|
1340
|
+
cause
|
|
1341
|
+
})
|
|
1342
|
+
}
|
|
1343
|
+
if ((deadline !== null && Date.now() >= deadline) ||
|
|
1344
|
+
!isValidEvent(retryEvent) || retryEvent.id === event.id || retryEvent.kind !== event.kind) {
|
|
1345
|
+
first.flushRelayResults()
|
|
1346
|
+
return first
|
|
1281
1347
|
}
|
|
1348
|
+
const retried = await sendAttempt(retryEvent, { notify: true })
|
|
1349
|
+
Object.defineProperty(retried, 'retriedEvent', { value: retryEvent })
|
|
1350
|
+
return retried
|
|
1282
1351
|
}
|
|
1283
1352
|
}
|
|
1284
1353
|
|