@steve02081504/fount-p2p 0.0.26 → 0.0.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 +3 -2
- package/core/bytes_codec.mjs +5 -6
- package/core/composite_key.mjs +0 -79
- package/core/entity_id_parse.mjs +2 -3
- package/core/evfs_logical_path.mjs +2 -3
- package/core/hexIds.mjs +5 -5
- package/core/logical_entity.mjs +3 -4
- package/crypto/channel.mjs +2 -25
- package/crypto/checkpoint_sign.mjs +5 -4
- package/crypto/crypto.mjs +0 -8
- package/crypto/key.mjs +9 -10
- package/dag/canonicalize_row.mjs +1 -1
- package/discovery/advert_peer_hints.mjs +1 -1
- package/discovery/adverts.mjs +7 -12
- package/discovery/bt/index.mjs +6 -11
- package/discovery/bt/peer_hints.mjs +2 -3
- package/discovery/bt/runtime.mjs +1 -1
- package/discovery/index.mjs +14 -14
- package/discovery/lan.mjs +3 -3
- package/discovery/lan_interfaces.mjs +2 -2
- package/discovery/lan_peer_hints.mjs +1 -1
- package/discovery/nostr.mjs +19 -24
- package/federation/entity_key_chain.mjs +5 -24
- package/federation/message_rate_limit.mjs +3 -2
- package/federation/part_query/runtime.mjs +9 -20
- package/files/assemble.mjs +3 -3
- package/files/assemble_stream.mjs +1 -1
- package/files/chunk/fetch.mjs +1 -2
- package/files/chunk/responder.mjs +4 -41
- package/files/chunk/store.mjs +2 -3
- package/files/chunk/verify.mjs +2 -3
- package/files/evfs.mjs +1 -1
- package/files/evfs_ref.mjs +3 -4
- package/files/fetch_fanout.mjs +3 -3
- package/files/manifest/fetch.mjs +2 -3
- package/files/manifest/normalize.mjs +9 -9
- package/files/manifest/pending.mjs +1 -4
- package/files/manifest/public.mjs +8 -8
- package/files/transfer_key.mjs +3 -3
- package/files/transfer_key_registry.mjs +6 -6
- package/governance/branch.mjs +2 -1
- package/governance/join_pow.mjs +7 -5
- package/governance/owner_succession_ballot.mjs +6 -6
- package/index.mjs +8 -2
- package/infra/default_node_dir.mjs +2 -2
- package/infra/priority.mjs +2 -2
- package/link/frame.mjs +2 -3
- package/link/handshake.mjs +6 -6
- package/link/pipe.mjs +69 -5
- package/link/providers/ble_gatt.mjs +1 -9
- package/link/providers/index.mjs +2 -2
- package/link/providers/nostr.mjs +1 -3
- package/link/providers/webrtc.mjs +2 -2
- package/link/rtc/ice_local_hostname.mjs +6 -5
- package/link/rtc/w3c_bridge.mjs +1 -1
- package/link/sdp_fingerprint.mjs +1 -1
- package/mailbox/consumer_registry.mjs +2 -2
- package/mailbox/deliver_or_store.mjs +4 -4
- package/mailbox/parse.mjs +1 -1
- package/mailbox/prune.mjs +2 -2
- package/mailbox/rate.mjs +3 -10
- package/mailbox/store.mjs +7 -7
- package/node/denylist.mjs +38 -38
- package/node/entity_store.mjs +5 -5
- package/node/identity.mjs +7 -8
- package/node/instance.mjs +1 -1
- package/node/log.mjs +1 -1
- package/node/network.mjs +14 -16
- package/node/personal_block.mjs +22 -20
- package/node/reputation_store.mjs +11 -19
- package/node/reputation_sync.mjs +0 -7
- package/node/signaling_config.mjs +1 -1
- package/node/storage_plugins.mjs +2 -2
- package/overlay/index.mjs +1 -1
- package/package.json +1 -1
- package/registries/event_type.mjs +2 -2
- package/registries/room_provider.mjs +2 -2
- package/reputation/engine.mjs +17 -15
- package/schemas/discovery.mjs +5 -5
- package/schemas/federation_pull.mjs +16 -24
- package/schemas/part_query.mjs +4 -4
- package/timeline/verify_remote.mjs +4 -4
- package/transport/group_link_set.mjs +3 -3
- package/transport/ice_servers.mjs +2 -2
- package/transport/link_registry.mjs +51 -20
- package/transport/mesh_keepalive.mjs +1 -1
- package/transport/node_scope/wire.mjs +1 -1
- package/transport/peer_health.mjs +127 -0
- package/transport/remote_user_room.mjs +13 -15
- package/transport/rtc_connection_budget.mjs +6 -6
- package/transport/runtime_bootstrap.mjs +12 -12
- package/trust_graph/cache.mjs +2 -3
- package/trust_graph/engine.mjs +1 -1
- package/trust_graph/registry.mjs +2 -2
- package/trust_graph/send.mjs +1 -1
- package/utils/async_mutex.mjs +1 -1
- package/wire/ingress.mjs +1 -1
- package/wire/part/ingress.mjs +6 -6
- package/wire/part/query.mjs +3 -3
- package/wire/volatile_signature.mjs +4 -9
package/overlay/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ const defsByOwner = new Map()
|
|
|
15
15
|
* @returns {void}
|
|
16
16
|
*/
|
|
17
17
|
export function registerEventTypeDefs(ownerId, defs) {
|
|
18
|
-
defsByOwner.set(
|
|
18
|
+
defsByOwner.set(ownerId, defs)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -23,7 +23,7 @@ export function registerEventTypeDefs(ownerId, defs) {
|
|
|
23
23
|
* @returns {void}
|
|
24
24
|
*/
|
|
25
25
|
export function unregisterEventTypeDefs(ownerId) {
|
|
26
|
-
defsByOwner.delete(
|
|
26
|
+
defsByOwner.delete(ownerId)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** @returns {void} */
|
|
@@ -21,7 +21,7 @@ const providers = new Map()
|
|
|
21
21
|
* @returns {void}
|
|
22
22
|
*/
|
|
23
23
|
export function registerFederationRoomProvider(ownerId, enumerateRooms) {
|
|
24
|
-
providers.set(
|
|
24
|
+
providers.set(ownerId, enumerateRooms)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -29,7 +29,7 @@ export function registerFederationRoomProvider(ownerId, enumerateRooms) {
|
|
|
29
29
|
* @returns {void}
|
|
30
30
|
*/
|
|
31
31
|
export function unregisterFederationRoomProvider(ownerId) {
|
|
32
|
-
providers.delete(
|
|
32
|
+
providers.delete(ownerId)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
package/reputation/engine.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 信誉纯内存算子(模拟器与磁盘 store 共用)。
|
|
3
3
|
*/
|
|
4
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
clampReputationScore,
|
|
6
8
|
computeRepMaxEff,
|
|
@@ -176,9 +178,9 @@ export function pruneReputationFile(data, tunables = reputationTunables, now = D
|
|
|
176
178
|
* @returns {boolean} 是否已加分
|
|
177
179
|
*/
|
|
178
180
|
export function bumpReputationOnRelayPure(data, peerNodeHash, dedupeKey, now = Date.now(), tunables = reputationTunables) {
|
|
179
|
-
const id =
|
|
181
|
+
const id = normalizeHex64(peerNodeHash)
|
|
180
182
|
if (!id) return false
|
|
181
|
-
const key =
|
|
183
|
+
const key = dedupeKey || `conn:${id}`
|
|
182
184
|
data.relayBumpSeen = data.relayBumpSeen.filter(hit => now - hit.t <= tunables.relayBumpDedupeMs)
|
|
183
185
|
if (relayBumpIsDuplicate(data.relayBumpSeen, id, key, now, tunables.relayBumpDedupeMs)) return false
|
|
184
186
|
data.relayBumpSeen.push({ peerNodeHash: id, key, t: now })
|
|
@@ -213,7 +215,7 @@ export function recordGossipAllUnknownWantPure(data, peerNodeHash, now = Date.no
|
|
|
213
215
|
* @returns {void}
|
|
214
216
|
*/
|
|
215
217
|
export function recordMessageRateViolationPure(data, peerNodeHash, tunables = reputationTunables, excessRatio = 1) {
|
|
216
|
-
const id =
|
|
218
|
+
const id = normalizeHex64(peerNodeHash)
|
|
217
219
|
if (!id) return
|
|
218
220
|
const ratio = Math.max(0, Math.min(1, Number(excessRatio) || 0))
|
|
219
221
|
if (ratio <= 0) return
|
|
@@ -227,7 +229,7 @@ export function recordMessageRateViolationPure(data, peerNodeHash, tunables = re
|
|
|
227
229
|
* @returns {void}
|
|
228
230
|
*/
|
|
229
231
|
export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables = reputationTunables) {
|
|
230
|
-
const id =
|
|
232
|
+
const id = normalizeHex64(storagePeerKey)
|
|
231
233
|
if (!id) return
|
|
232
234
|
adjustNodeReputation(data, id, tunables.chunkStoreRepBump)
|
|
233
235
|
}
|
|
@@ -239,7 +241,7 @@ export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables =
|
|
|
239
241
|
* @returns {void}
|
|
240
242
|
*/
|
|
241
243
|
export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = reputationTunables) {
|
|
242
|
-
const id =
|
|
244
|
+
const id = normalizeHex64(blamePeerKey)
|
|
243
245
|
if (!id) return
|
|
244
246
|
adjustNodeReputation(data, id, -tunables.chunkFetchFailPenalty)
|
|
245
247
|
}
|
|
@@ -251,7 +253,7 @@ export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = r
|
|
|
251
253
|
* @returns {void}
|
|
252
254
|
*/
|
|
253
255
|
export function penalizeArchiveServeMismatchPure(data, peerNodeHash, tunables = reputationTunables) {
|
|
254
|
-
const id =
|
|
256
|
+
const id = normalizeHex64(peerNodeHash)
|
|
255
257
|
if (!id) return
|
|
256
258
|
adjustNodeReputation(data, id, -tunables.archiveServeMismatchPenalty)
|
|
257
259
|
}
|
|
@@ -280,7 +282,7 @@ export function applySubjectiveSlashPure(data, target, sender, claim, verified,
|
|
|
280
282
|
* @returns {Array<{ hop: number, node: string, dRep: number }>} 已应用的上游惩罚
|
|
281
283
|
*/
|
|
282
284
|
export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges, tunables = reputationTunables) {
|
|
283
|
-
const target = targetPubKeyHash
|
|
285
|
+
const target = normalizeHex64(targetPubKeyHash)
|
|
284
286
|
/** @type {Array<{ hop: number, node: string, dRep: number }>} */
|
|
285
287
|
const applied = []
|
|
286
288
|
let frontier = new Set([target])
|
|
@@ -301,8 +303,8 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
|
|
|
301
303
|
for (let hop = 1; hop <= effectiveMaxHop(); hop++) {
|
|
302
304
|
const upstream = new Set()
|
|
303
305
|
for (const edge of inviteEdges) {
|
|
304
|
-
const to =
|
|
305
|
-
const from =
|
|
306
|
+
const to = normalizeHex64(edge.to)
|
|
307
|
+
const from = normalizeHex64(edge.from)
|
|
306
308
|
if (frontier.has(to) && from) upstream.add(from)
|
|
307
309
|
}
|
|
308
310
|
if (!upstream.size) break
|
|
@@ -331,7 +333,7 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
|
|
|
331
333
|
* @returns {void}
|
|
332
334
|
*/
|
|
333
335
|
export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
|
|
334
|
-
const t = targetPubKeyHash
|
|
336
|
+
const t = normalizeHex64(targetPubKeyHash)
|
|
335
337
|
const row = repRow(data, t)
|
|
336
338
|
row.score = 0
|
|
337
339
|
delete row.offenseStreak
|
|
@@ -351,8 +353,8 @@ export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
|
|
|
351
353
|
* @returns {void}
|
|
352
354
|
*/
|
|
353
355
|
export function seedMemberReputationFromIntroducerPure(data, memberPubKeyHash, introducerPubKeyHash, repEdge, tunables = reputationTunables, powBonus = 0) {
|
|
354
|
-
const memberKey = memberPubKeyHash
|
|
355
|
-
const introducerKey = introducerPubKeyHash
|
|
356
|
+
const memberKey = normalizeHex64(memberPubKeyHash)
|
|
357
|
+
const introducerKey = normalizeHex64(introducerPubKeyHash)
|
|
356
358
|
if (data.byNodeHash[memberKey]) return
|
|
357
359
|
const introducerReputation = introducerKey
|
|
358
360
|
? Number(data.byNodeHash[introducerKey]?.score ?? 0)
|
|
@@ -392,7 +394,7 @@ function baselineRow(row) {
|
|
|
392
394
|
* @returns {{ z: number, anomaly: boolean }} 偏离度
|
|
393
395
|
*/
|
|
394
396
|
export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date.now(), tunables = reputationTunables) {
|
|
395
|
-
const id =
|
|
397
|
+
const id = normalizeHex64(peerNodeHash)
|
|
396
398
|
if (!id) return { z: 0, anomaly: false }
|
|
397
399
|
const row = repRow(data, id)
|
|
398
400
|
const baseline = baselineRow(row)
|
|
@@ -427,7 +429,7 @@ export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date
|
|
|
427
429
|
* @returns {void}
|
|
428
430
|
*/
|
|
429
431
|
export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
|
|
430
|
-
const id =
|
|
432
|
+
const id = normalizeHex64(peerNodeHash)
|
|
431
433
|
if (!id) return
|
|
432
434
|
const row = repRow(data, id)
|
|
433
435
|
row.quarantinedUntil = now + Number(tunables.quarantineTtlMs ?? 900_000)
|
|
@@ -441,7 +443,7 @@ export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunabl
|
|
|
441
443
|
* @returns {boolean} 是否处于本地隔离
|
|
442
444
|
*/
|
|
443
445
|
export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
|
|
444
|
-
const id =
|
|
446
|
+
const id = normalizeHex64(peerNodeHash)
|
|
445
447
|
const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
|
|
446
448
|
return Number.isFinite(until) && until > now
|
|
447
449
|
}
|
package/schemas/discovery.mjs
CHANGED
|
@@ -9,9 +9,9 @@ const MAX_DISCOVERY_ADS = 64
|
|
|
9
9
|
*/
|
|
10
10
|
function sanitizeDiscoveryAdvertisement(ad) {
|
|
11
11
|
if (!isPlainObject(ad)) return null
|
|
12
|
-
const groupId = String(ad.groupId || '')
|
|
12
|
+
const groupId = String(ad.groupId || '')
|
|
13
13
|
const advertiserPubKeyHash = normalizeHex64(ad.advertiserPubKeyHash)
|
|
14
|
-
const signature = String(ad.signature || '')
|
|
14
|
+
const signature = String(ad.signature || '')
|
|
15
15
|
if (!groupId || !isHex64(advertiserPubKeyHash) || !isSignatureHex128(signature)) return null
|
|
16
16
|
const advertiserNodeHash = normalizeHex64(ad.advertiserNodeHash)
|
|
17
17
|
const body = {
|
|
@@ -19,7 +19,7 @@ function sanitizeDiscoveryAdvertisement(ad) {
|
|
|
19
19
|
title: String(ad.title || '').slice(0, 200),
|
|
20
20
|
blurb: String(ad.blurb || '').slice(0, 500),
|
|
21
21
|
advertiserPubKeyHash,
|
|
22
|
-
advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || '')
|
|
22
|
+
advertiserNodeHash: isHex64(advertiserNodeHash) ? advertiserNodeHash : String(ad.advertiserNodeHash || ''),
|
|
23
23
|
observedAt: Number(ad.observedAt) || 0,
|
|
24
24
|
signature,
|
|
25
25
|
}
|
|
@@ -47,10 +47,10 @@ export function assertDiscoveryNodeHash(nodeHash) {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* @param {unknown} requestId 请求 id
|
|
50
|
-
* @returns {string}
|
|
50
|
+
* @returns {string} 非空字符串
|
|
51
51
|
*/
|
|
52
52
|
export function assertDiscoveryRequestId(requestId) {
|
|
53
|
-
const id = String(requestId ?? '')
|
|
53
|
+
const id = String(requestId ?? '')
|
|
54
54
|
if (!id) throw new Error('discovery.requestId required')
|
|
55
55
|
return id
|
|
56
56
|
}
|
|
@@ -14,7 +14,7 @@ const ENVELOPE_BLOB_MIN_LEN = 2
|
|
|
14
14
|
* @returns {boolean} 是否像合法 ECIES blob 段
|
|
15
15
|
*/
|
|
16
16
|
function isEnvelopeBlob(value) {
|
|
17
|
-
return String(value ?? '').
|
|
17
|
+
return String(value ?? '').length >= ENVELOPE_BLOB_MIN_LEN
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -24,14 +24,14 @@ function isEnvelopeBlob(value) {
|
|
|
24
24
|
export function parsePullAttestation(attestation) {
|
|
25
25
|
if (!isPlainObject(attestation)) return null
|
|
26
26
|
const requesterPubKeyHash = normalizeHex64(attestation.requesterPubKeyHash)
|
|
27
|
-
const groupId = String(attestation.groupId || '')
|
|
28
|
-
const requestId = String(attestation.requestId || '')
|
|
27
|
+
const groupId = String(attestation.groupId || '')
|
|
28
|
+
const requestId = String(attestation.requestId || '')
|
|
29
29
|
const timestamp = Number(attestation.timestamp)
|
|
30
|
-
const signature = String(attestation.signature || '')
|
|
30
|
+
const signature = String(attestation.signature || '')
|
|
31
31
|
if (!isHex64(requesterPubKeyHash) || !groupId || !Number.isFinite(timestamp) || !isSignatureHex128(signature))
|
|
32
32
|
return null
|
|
33
33
|
const wantIds = attestation.wantIds?.length
|
|
34
|
-
? [...new Set(attestation.wantIds.map(id => id
|
|
34
|
+
? [...new Set(attestation.wantIds.map(id => normalizeHex64(id)).filter(id => EVENT_ID_HEX.test(id)))]
|
|
35
35
|
: undefined
|
|
36
36
|
return {
|
|
37
37
|
requesterPubKeyHash,
|
|
@@ -49,13 +49,13 @@ export function parsePullAttestation(attestation) {
|
|
|
49
49
|
*/
|
|
50
50
|
export function parsePullResponseEnvelope(envelope) {
|
|
51
51
|
if (!isPlainObject(envelope)) return null
|
|
52
|
-
const requestId = String(envelope.requestId || '')
|
|
52
|
+
const requestId = String(envelope.requestId || '')
|
|
53
53
|
const requesterPubKeyHash = normalizeHex64(envelope.requesterPubKeyHash)
|
|
54
|
-
const requesterNodeHash = String(envelope.requesterNodeHash || '')
|
|
55
|
-
const ephemPub = String(envelope.ephemPub || '')
|
|
56
|
-
const iv = String(envelope.iv || '')
|
|
57
|
-
const ciphertext = String(envelope.ciphertext || '')
|
|
58
|
-
const authTag = String(envelope.authTag || '')
|
|
54
|
+
const requesterNodeHash = String(envelope.requesterNodeHash || '')
|
|
55
|
+
const ephemPub = String(envelope.ephemPub || '')
|
|
56
|
+
const iv = String(envelope.iv || '')
|
|
57
|
+
const ciphertext = String(envelope.ciphertext || '')
|
|
58
|
+
const authTag = String(envelope.authTag || '')
|
|
59
59
|
if (!requestId || !isHex64(requesterPubKeyHash) || !requesterNodeHash) return null
|
|
60
60
|
if (!isEnvelopeBlob(ephemPub) || !isEnvelopeBlob(iv) || !isEnvelopeBlob(ciphertext) || !isEnvelopeBlob(authTag))
|
|
61
61
|
return null
|
|
@@ -76,27 +76,19 @@ export function parsePullResponseEnvelope(envelope) {
|
|
|
76
76
|
*/
|
|
77
77
|
export function parseJoinSnapshotRequest(data) {
|
|
78
78
|
if (!isPlainObject(data)) return null
|
|
79
|
-
const requestId = String(data.requestId || '')
|
|
80
|
-
const requesterNodeHash = String(data.requesterNodeHash || '')
|
|
81
|
-
const groupId = String(data.groupId || '')
|
|
79
|
+
const requestId = String(data.requestId || '')
|
|
80
|
+
const requesterNodeHash = String(data.requesterNodeHash || '')
|
|
81
|
+
const groupId = String(data.groupId || '')
|
|
82
82
|
const attestation = parsePullAttestation(data.attestation)
|
|
83
83
|
if (!requestId || !requesterNodeHash || !groupId || !attestation) return null
|
|
84
84
|
if (attestation.groupId !== groupId || attestation.requestId !== requestId) return null
|
|
85
|
-
const tipsHash = String(data.tipsHash || '')
|
|
85
|
+
const tipsHash = String(data.tipsHash || '')
|
|
86
86
|
return {
|
|
87
87
|
requestId,
|
|
88
88
|
requesterNodeHash,
|
|
89
89
|
requesterPubKeyHash: attestation.requesterPubKeyHash,
|
|
90
90
|
groupId,
|
|
91
|
-
tipsHash: tipsHash
|
|
91
|
+
tipsHash: tipsHash || undefined,
|
|
92
92
|
attestation,
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {unknown} data 入群快照响应载荷
|
|
98
|
-
* @returns {object | null} 解析结果
|
|
99
|
-
*/
|
|
100
|
-
export function parseJoinSnapshotResponse(data) {
|
|
101
|
-
return parsePullResponseEnvelope(data)
|
|
102
|
-
}
|
package/schemas/part_query.mjs
CHANGED
|
@@ -82,10 +82,10 @@ const REQUEST_ID_MAX_LENGTH = 128
|
|
|
82
82
|
|
|
83
83
|
/**
|
|
84
84
|
* @param {unknown} value 原始 requestId
|
|
85
|
-
* @returns {string | null}
|
|
85
|
+
* @returns {string | null} 合法 requestId;非法 null
|
|
86
86
|
*/
|
|
87
87
|
function normalizeRequestId(value) {
|
|
88
|
-
const requestId = String(value || '')
|
|
88
|
+
const requestId = String(value || '')
|
|
89
89
|
if (!requestId || requestId.length > REQUEST_ID_MAX_LENGTH) return null
|
|
90
90
|
return requestId
|
|
91
91
|
}
|
|
@@ -103,7 +103,7 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
|
|
|
103
103
|
if (!isHex64(originNodeHash)) return null
|
|
104
104
|
const partpath = parsePartpath(value.partpath)
|
|
105
105
|
if (!partpath) return null
|
|
106
|
-
const kind = String(value.kind || '')
|
|
106
|
+
const kind = String(value.kind || '')
|
|
107
107
|
if (!kind) return null
|
|
108
108
|
if (!Object.prototype.hasOwnProperty.call(value, 'query')) return null
|
|
109
109
|
if (measureJsonBytes(value.query) > (tunables.maxQueryBytes ?? 2048)) return null
|
|
@@ -145,7 +145,7 @@ export function parsePartQueryRes(value, tunables = partQueryTunables) {
|
|
|
145
145
|
*/
|
|
146
146
|
export function normalizePartQueryCacheMaterial(partpath, kind, query) {
|
|
147
147
|
const path = parsePartpath(partpath)
|
|
148
|
-
const k = String(kind || '')
|
|
148
|
+
const k = String(kind || '')
|
|
149
149
|
if (!path || !k) return null
|
|
150
150
|
if (measureJsonBytes(query) > partQueryTunables.maxQueryBytes) return null
|
|
151
151
|
try {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Buffer } from 'node:buffer'
|
|
5
5
|
|
|
6
|
-
import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
|
|
6
|
+
import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
|
|
7
7
|
import { pubKeyHash, verify } from '../crypto/crypto.mjs'
|
|
8
8
|
import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
|
|
9
9
|
|
|
@@ -12,11 +12,11 @@ import { eventBodyForSign, signPayloadBytes } from '../dag/index.mjs'
|
|
|
12
12
|
* @returns {Promise<boolean>} 验签是否通过
|
|
13
13
|
*/
|
|
14
14
|
export async function verifyTimelineRemoteSignature(event) {
|
|
15
|
-
const sender =
|
|
15
|
+
const sender = normalizeHex64(event?.sender)
|
|
16
16
|
if (!isHex64(sender)) return false
|
|
17
|
-
const signatureHex = String(event?.signature || '')
|
|
17
|
+
const signatureHex = String(event?.signature || '')
|
|
18
18
|
if (!isSignatureHex128(signatureHex)) return false
|
|
19
|
-
const pubKeyHex =
|
|
19
|
+
const pubKeyHex = normalizeHex64(event?.senderPubKey)
|
|
20
20
|
if (!isHex64(pubKeyHex)) return false
|
|
21
21
|
const publicKeyBytes = new Uint8Array(Buffer.from(pubKeyHex, 'hex'))
|
|
22
22
|
if (pubKeyHash(publicKeyBytes) !== sender) return false
|
|
@@ -30,7 +30,7 @@ export function createGroupLinkSet(options) {
|
|
|
30
30
|
const allowNode = options.allowNode ?? (() => true)
|
|
31
31
|
const { groupId } = options
|
|
32
32
|
const scope = options.scope ?? `group:${groupId}`
|
|
33
|
-
const roomSecret = options
|
|
33
|
+
const { roomSecret } = options
|
|
34
34
|
const members = new Set(options.members || [])
|
|
35
35
|
const selfNodeHash = registry.localIdentity.nodeHash
|
|
36
36
|
const groupSettings = options.groupSettings ?? {}
|
|
@@ -59,7 +59,7 @@ export function createGroupLinkSet(options) {
|
|
|
59
59
|
* @returns {void}
|
|
60
60
|
*/
|
|
61
61
|
function registerCleanup(cleanup) {
|
|
62
|
-
if (
|
|
62
|
+
if (!cleanup) return
|
|
63
63
|
cleanups.add(cleanup)
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -182,7 +182,7 @@ export function createGroupLinkSet(options) {
|
|
|
182
182
|
}
|
|
183
183
|
started = true
|
|
184
184
|
active = true
|
|
185
|
-
if (
|
|
185
|
+
if (registry.ensureRuntime)
|
|
186
186
|
await registry.ensureRuntime()
|
|
187
187
|
registry.registerScopeInterest(scope, [...members])
|
|
188
188
|
registerCleanup(registry.subscribeScope(scope, (senderNodeHash, envelope) => {
|
|
@@ -27,8 +27,8 @@ function normalizeIceEntry(raw) {
|
|
|
27
27
|
if (!raw || typeof raw !== 'object') return null
|
|
28
28
|
const urlsRaw = raw.urls
|
|
29
29
|
const urlsList = Array.isArray(urlsRaw)
|
|
30
|
-
? urlsRaw.map(u => String(u)
|
|
31
|
-
: [String(urlsRaw || '')
|
|
30
|
+
? urlsRaw.map(u => String(u || '')).filter(Boolean)
|
|
31
|
+
: [String(urlsRaw || '')].filter(Boolean)
|
|
32
32
|
if (!urlsList.length) return null
|
|
33
33
|
for (const u of urlsList)
|
|
34
34
|
if (!ICE_URL_RE.test(u)) return null
|
|
@@ -17,6 +17,7 @@ import { createLruMap } from '../utils/lru.mjs'
|
|
|
17
17
|
import { DEFAULT_ICE_SERVERS } from './ice_servers.mjs'
|
|
18
18
|
import { createMeshKeepalive } from './mesh_keepalive.mjs'
|
|
19
19
|
import { createOfferAnswerDial } from './offer_answer.mjs'
|
|
20
|
+
import { createPeerHealthTracker } from './peer_health.mjs'
|
|
20
21
|
import { pickMeshEvictionVictim } from './peer_pool.mjs'
|
|
21
22
|
import { createRuntimeBootstrap } from './runtime_bootstrap.mjs'
|
|
22
23
|
|
|
@@ -293,14 +294,14 @@ export function createLinkRegistry(options = {}) {
|
|
|
293
294
|
const providers = listLinkProviders()
|
|
294
295
|
for (const provider of providers)
|
|
295
296
|
try {
|
|
296
|
-
if (
|
|
297
|
+
if (provider.canReach) {
|
|
297
298
|
const reachable = await Promise.resolve(provider.canReach({ nodeHash: normalized }))
|
|
298
299
|
if (!reachable) {
|
|
299
300
|
nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'canReach=false' })
|
|
300
301
|
continue
|
|
301
302
|
}
|
|
302
303
|
}
|
|
303
|
-
if (
|
|
304
|
+
if (provider.isAvailable) {
|
|
304
305
|
const available = await Promise.resolve(provider.isAvailable())
|
|
305
306
|
if (!available) {
|
|
306
307
|
nodeDebug('p2p:dial skip', { peer: shortHash(normalized), provider: provider.id, reason: 'isAvailable=false' })
|
|
@@ -470,17 +471,6 @@ export function createLinkRegistry(options = {}) {
|
|
|
470
471
|
|| await relayEnvelopeToNode(remoteNodeHash, envelope)
|
|
471
472
|
}
|
|
472
473
|
|
|
473
|
-
/**
|
|
474
|
-
* 同步结果直接返回;thenable 才 await(避免每条 envelope 造 microtask)。
|
|
475
|
-
* @param {unknown} value 可能为 Promise 的返回值
|
|
476
|
-
* @returns {Promise<unknown>} 已 resolve 的值
|
|
477
|
-
*/
|
|
478
|
-
async function maybeAwait(value) {
|
|
479
|
-
if (value != null && typeof /** @type {{ then?: unknown }} */ value.then === 'function')
|
|
480
|
-
return await value
|
|
481
|
-
return value
|
|
482
|
-
}
|
|
483
|
-
|
|
484
474
|
/**
|
|
485
475
|
* 将入站 envelope 派发到 scope 监听器(经 authorizer 校验)。
|
|
486
476
|
* @param {string} senderNodeHash 发送方节点 64 hex
|
|
@@ -489,16 +479,15 @@ export function createLinkRegistry(options = {}) {
|
|
|
489
479
|
* @returns {Promise<void>}
|
|
490
480
|
*/
|
|
491
481
|
async function dispatchEnvelope(senderNodeHash, envelope, link) {
|
|
492
|
-
const scope = envelope
|
|
482
|
+
const { scope } = envelope
|
|
493
483
|
for (const [prefix, authorizer] of scopeAuthorizers.entries())
|
|
494
|
-
if (scope.startsWith(prefix))
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
484
|
+
if (scope.startsWith(prefix))
|
|
485
|
+
if (!await authorizer(scope, senderNodeHash, envelope, link)) return
|
|
486
|
+
|
|
498
487
|
for (const [prefix, listeners] of scopeListeners.entries())
|
|
499
488
|
if (scope.startsWith(prefix))
|
|
500
489
|
for (const listener of listeners)
|
|
501
|
-
await
|
|
490
|
+
await listener(senderNodeHash, envelope, link)
|
|
502
491
|
}
|
|
503
492
|
|
|
504
493
|
/**
|
|
@@ -522,6 +511,25 @@ export function createLinkRegistry(options = {}) {
|
|
|
522
511
|
return () => scopeAuthorizers.delete(prefix)
|
|
523
512
|
}
|
|
524
513
|
|
|
514
|
+
const peerHealth = createPeerHealthTracker({
|
|
515
|
+
/**
|
|
516
|
+
* @param {(nodeHash: string, link: unknown) => void} listener link up 回调
|
|
517
|
+
* @returns {() => void} 取消订阅
|
|
518
|
+
*/
|
|
519
|
+
onLinkUp: listener => {
|
|
520
|
+
linkUpListeners.add(listener)
|
|
521
|
+
return () => linkUpListeners.delete(listener)
|
|
522
|
+
},
|
|
523
|
+
/**
|
|
524
|
+
* @param {(nodeHash: string) => void} listener link down 回调
|
|
525
|
+
* @returns {() => void} 取消订阅
|
|
526
|
+
*/
|
|
527
|
+
onLinkDown: listener => {
|
|
528
|
+
linkDownListeners.add(listener)
|
|
529
|
+
return () => linkDownListeners.delete(listener)
|
|
530
|
+
},
|
|
531
|
+
})
|
|
532
|
+
|
|
525
533
|
return {
|
|
526
534
|
localIdentity,
|
|
527
535
|
buildLocalAdvert: bootstrap.buildLocalAdvert,
|
|
@@ -567,7 +575,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
567
575
|
* @returns {void}
|
|
568
576
|
*/
|
|
569
577
|
setPriorityWeightFunction(weightFunction) {
|
|
570
|
-
priorityWeightFunction =
|
|
578
|
+
priorityWeightFunction = weightFunction || null
|
|
571
579
|
},
|
|
572
580
|
/**
|
|
573
581
|
* @param {string} nodeHash - 节点 64-hex hash
|
|
@@ -641,6 +649,22 @@ export function createLinkRegistry(options = {}) {
|
|
|
641
649
|
},
|
|
642
650
|
registerScopeAuthorizer,
|
|
643
651
|
subscribeScope,
|
|
652
|
+
/**
|
|
653
|
+
* 获取指定邻居的健康记录。
|
|
654
|
+
* @param {string} nodeHash 节点 64 hex
|
|
655
|
+
* @returns {import('./peer_health.mjs').PeerHealthEntry | null} 健康记录;无记录时 null
|
|
656
|
+
*/
|
|
657
|
+
getPeerHealth: peerHealth.getPeerHealth,
|
|
658
|
+
/**
|
|
659
|
+
* @returns {import('./peer_health.mjs').PeerHealthEntry[]} 所有邻居健康记录
|
|
660
|
+
*/
|
|
661
|
+
listPeerHealth: peerHealth.listPeerHealth,
|
|
662
|
+
/**
|
|
663
|
+
* 订阅邻居健康变化。
|
|
664
|
+
* @param {(nodeHash: string, entry: import('./peer_health.mjs').PeerHealthEntry) => void} listener 回调
|
|
665
|
+
* @returns {() => void} 取消订阅
|
|
666
|
+
*/
|
|
667
|
+
onPeerHealth: peerHealth.onPeerHealth,
|
|
644
668
|
/**
|
|
645
669
|
* 监听指定节点的 advert(per-hash,无 topic)。
|
|
646
670
|
* @param {string} nodeHash 目标节点 64 hex
|
|
@@ -664,6 +688,7 @@ export function createLinkRegistry(options = {}) {
|
|
|
664
688
|
*/
|
|
665
689
|
async shutdown() {
|
|
666
690
|
await meshKeepalive?.stop()
|
|
691
|
+
peerHealth.stop()
|
|
667
692
|
setDiscoveryLinkDialer(null)
|
|
668
693
|
setDiscoveryPeerClueListener(null)
|
|
669
694
|
await bootstrap.shutdown()
|
|
@@ -756,6 +781,12 @@ export const registerScopeInterest = bindRegistryMethod('registerScopeInterest')
|
|
|
756
781
|
export const releaseScopeInterest = bindRegistryMethod('releaseScopeInterest')
|
|
757
782
|
/** @type {(...methodArguments: unknown[]) => unknown} */
|
|
758
783
|
export const subscribeScope = bindRegistryMethod('subscribeScope')
|
|
784
|
+
/** @type {(...methodArguments: unknown[]) => unknown} */
|
|
785
|
+
export const getPeerHealth = bindRegistryMethod('getPeerHealth')
|
|
786
|
+
/** @type {(...methodArguments: unknown[]) => unknown} */
|
|
787
|
+
export const listPeerHealth = bindRegistryMethod('listPeerHealth')
|
|
788
|
+
/** @type {(...methodArguments: unknown[]) => unknown} */
|
|
789
|
+
export const onPeerHealth = bindRegistryMethod('onPeerHealth')
|
|
759
790
|
|
|
760
791
|
/**
|
|
761
792
|
* 注册默认 registry 的 scope authorizer。
|
|
@@ -103,7 +103,7 @@ export function getNodeScopeContext() {
|
|
|
103
103
|
*/
|
|
104
104
|
export function ensureNodeScope(options = {}) {
|
|
105
105
|
if (options.replicaUsername != null)
|
|
106
|
-
nodeScopeContext.replicaUsername =
|
|
106
|
+
nodeScopeContext.replicaUsername = options.replicaUsername
|
|
107
107
|
if (nodeScopeSubscribeCleanup) return nodeScopeSubscribeCleanup
|
|
108
108
|
nodeScopeSubscribeCleanup = subscribeScope('node', (senderNodeHash, envelope) => {
|
|
109
109
|
const handlers = nodeActionHandlers.get(envelope.action)
|