@steve02081504/fount-p2p 0.0.26 → 0.0.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 +1 -0
- 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 +3 -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 +16 -21
- 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 +2 -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 +1 -2
- 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 +1 -1
- 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 +2 -2
- package/transport/ice_servers.mjs +2 -2
- package/transport/link_registry.mjs +6 -18
- package/transport/mesh_keepalive.mjs +1 -1
- package/transport/node_scope/wire.mjs +1 -1
- 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/governance/branch.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
1
2
|
import { sortedPrevEventIds } from '../dag/index.mjs'
|
|
2
3
|
import { getGovernanceAuthzTypes } from '../registries/event_type.mjs'
|
|
3
4
|
|
|
@@ -104,7 +105,7 @@ function governanceAuthzScoreForTip(tipId, byId, reputationBySender) {
|
|
|
104
105
|
for (const eventId of ancestorClosureFromTip(tipId, byId)) {
|
|
105
106
|
const event = byId.get(eventId)
|
|
106
107
|
if (!event || !getGovernanceAuthzTypes().has(event.type)) continue
|
|
107
|
-
const sender =
|
|
108
|
+
const sender = normalizeHex64(event.sender)
|
|
108
109
|
if (sender) score += Number(reputationBySender[sender] ?? 0)
|
|
109
110
|
}
|
|
110
111
|
return score
|
package/governance/join_pow.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { createHash } from 'node:crypto'
|
|
5
5
|
|
|
6
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
7
|
+
|
|
6
8
|
import admissionTunables from './tunables.json' with { type: 'json' }
|
|
7
9
|
|
|
8
10
|
/** 默认 epoch 窗口(1 小时) */
|
|
@@ -21,7 +23,7 @@ export const JOIN_POW_DEFAULT_EPOCH_SKEW = 1
|
|
|
21
23
|
* @returns {string} SHA-256 十六进制
|
|
22
24
|
*/
|
|
23
25
|
export function computeJoinPowHash({ groupId, anchorRef, joinerNodeHash, epoch, nonce }) {
|
|
24
|
-
const preimage = `${
|
|
26
|
+
const preimage = `${groupId}:${anchorRef}:${joinerNodeHash}:${String(epoch)}:${String(nonce)}`
|
|
25
27
|
return createHash('sha256').update(preimage, 'utf8').digest('hex')
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -100,16 +102,16 @@ export function verifyJoinPow(powSolution, options) {
|
|
|
100
102
|
if (floorBits <= 0) return { ok: true, achievedBits: 0 }
|
|
101
103
|
if (!powSolution || typeof powSolution !== 'object') return { ok: false, achievedBits: 0 }
|
|
102
104
|
|
|
103
|
-
const anchorRef = String(powSolution.anchorRef ?? '')
|
|
105
|
+
const anchorRef = String(powSolution.anchorRef ?? '')
|
|
104
106
|
const { nonce } = powSolution
|
|
105
107
|
const epoch = Number(powSolution.epoch)
|
|
106
|
-
const joinerNodeHash =
|
|
107
|
-
const senderNodeHash =
|
|
108
|
+
const joinerNodeHash = normalizeHex64(powSolution.joinerNodeHash ?? options.senderNodeHash)
|
|
109
|
+
const senderNodeHash = normalizeHex64(options.senderNodeHash)
|
|
108
110
|
|
|
109
111
|
if (!anchorRef || nonce == null || !Number.isFinite(epoch)) return { ok: false, achievedBits: 0 }
|
|
110
112
|
if (!joinerNodeHash || joinerNodeHash !== senderNodeHash) return { ok: false, achievedBits: 0 }
|
|
111
113
|
|
|
112
|
-
const anchors = (options.knownAnchors ?? []).
|
|
114
|
+
const anchors = (options.knownAnchors ?? []).filter(Boolean)
|
|
113
115
|
if (!anchors.length || !anchors.includes(anchorRef)) return { ok: false, achievedBits: 0 }
|
|
114
116
|
|
|
115
117
|
const epochMs = Math.max(60_000, Number(options.epochMs) || JOIN_POW_DEFAULT_EPOCH_MS)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Buffer } from 'node:buffer'
|
|
5
5
|
|
|
6
6
|
import { canonicalStringify } from '../core/canonical_json.mjs'
|
|
7
|
-
import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
|
|
7
|
+
import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
|
|
8
8
|
import { pubKeyHash, verify } from '../crypto/crypto.mjs'
|
|
9
9
|
|
|
10
10
|
const BALLOT_DOMAIN = 'fount-owner-succession'
|
|
@@ -15,9 +15,9 @@ const BALLOT_DOMAIN = 'fount-owner-succession'
|
|
|
15
15
|
*/
|
|
16
16
|
export function ownerSuccessionBallotSignBytes(ballot) {
|
|
17
17
|
const body = {
|
|
18
|
-
proposedOwnerPubKeyHash:
|
|
19
|
-
groupId:
|
|
20
|
-
ballotId:
|
|
18
|
+
proposedOwnerPubKeyHash: normalizeHex64(ballot.proposedOwnerPubKeyHash),
|
|
19
|
+
groupId: ballot.groupId,
|
|
20
|
+
ballotId: ballot.ballotId,
|
|
21
21
|
}
|
|
22
22
|
return Buffer.from(`${BALLOT_DOMAIN}\0${canonicalStringify(body)}`, 'utf8')
|
|
23
23
|
}
|
|
@@ -42,8 +42,8 @@ export async function verifyOwnerSuccessionThreshold(ballot, adminPubKeyHashes,
|
|
|
42
42
|
let valid = 0
|
|
43
43
|
|
|
44
44
|
for (const entry of ballot.adminSignatures || []) {
|
|
45
|
-
const pubKeyHex =
|
|
46
|
-
const signatureHex =
|
|
45
|
+
const pubKeyHex = normalizeHex64(entry.pubKeyHex)
|
|
46
|
+
const signatureHex = entry.signature
|
|
47
47
|
if (!isHex64(pubKeyHex) || !isSignatureHex128(signatureHex)) continue
|
|
48
48
|
|
|
49
49
|
const hash = pubKeyHash(Buffer.from(pubKeyHex, 'hex'))
|
package/index.mjs
CHANGED
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
attachReputationSyncWire,
|
|
23
23
|
getReputationExportAllowlist,
|
|
24
24
|
getReputationLocks,
|
|
25
|
-
getReputationTable,
|
|
26
25
|
getTrustSyncDonors,
|
|
27
26
|
lockReputationMax,
|
|
28
27
|
pullReputationFromNode,
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
setTrustSyncDonors,
|
|
32
31
|
unlockReputationMax,
|
|
33
32
|
} from './node/reputation_sync.mjs'
|
|
33
|
+
import { loadReputation } from './node/reputation_store.mjs'
|
|
34
34
|
import { getRoutingProfile, setRoutingProfile } from './node/routing_profile.mjs'
|
|
35
35
|
import { createGroupLinkSet } from './transport/group_link_set.mjs'
|
|
36
36
|
import {
|
|
@@ -68,13 +68,13 @@ export {
|
|
|
68
68
|
getNodeHash,
|
|
69
69
|
getReputationExportAllowlist,
|
|
70
70
|
getReputationLocks,
|
|
71
|
-
getReputationTable,
|
|
72
71
|
getRoutingProfile,
|
|
73
72
|
getTrustSyncDonors,
|
|
74
73
|
getUserRoomSlot,
|
|
75
74
|
initNode,
|
|
76
75
|
isInfraRunning,
|
|
77
76
|
isNodeInitialized,
|
|
77
|
+
loadReputation,
|
|
78
78
|
lockReputationMax,
|
|
79
79
|
pullReputationFromNode,
|
|
80
80
|
registerDiscoveryProvider,
|
|
@@ -17,6 +17,6 @@ export function defaultNodeDir() {
|
|
|
17
17
|
* @returns {string} 解析后的绝对 node 目录
|
|
18
18
|
*/
|
|
19
19
|
export function resolveNodeDir(override) {
|
|
20
|
-
const
|
|
21
|
-
return
|
|
20
|
+
const value = override || ''
|
|
21
|
+
return value ? path.resolve(value) : defaultNodeDir()
|
|
22
22
|
}
|
package/infra/priority.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
2
|
-
import {
|
|
2
|
+
import { loadReputation } from '../node/reputation_store.mjs'
|
|
3
3
|
import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
|
|
4
4
|
import { getLinkRegistry } from '../transport/link_registry.mjs'
|
|
5
5
|
|
|
@@ -39,7 +39,7 @@ export function applyPriorityToRegistry() {
|
|
|
39
39
|
}
|
|
40
40
|
registry.setPriorityWeightFunction(nodeHash => {
|
|
41
41
|
const score = pickNodeScoreFromReputation(
|
|
42
|
-
|
|
42
|
+
loadReputation(),
|
|
43
43
|
normalizeHex64(nodeHash) || nodeHash,
|
|
44
44
|
)
|
|
45
45
|
return Math.floor(score * PRIORITY_BOOST)
|
package/link/frame.mjs
CHANGED
|
@@ -25,11 +25,10 @@ function normalizeFrameIdBytes(frameId) {
|
|
|
25
25
|
throw new Error(`p2p: frameId must be ${FRAME_ID_BYTES} bytes`)
|
|
26
26
|
return frameId
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
if (text.length !== FRAME_ID_BYTES * 2)
|
|
28
|
+
if (frameId.length !== FRAME_ID_BYTES * 2)
|
|
30
29
|
throw new Error('p2p: frameId must be 32 hex characters')
|
|
31
30
|
try {
|
|
32
|
-
return hexToBytes(
|
|
31
|
+
return hexToBytes(frameId)
|
|
33
32
|
}
|
|
34
33
|
catch {
|
|
35
34
|
throw new Error('p2p: frameId must be 32 hex characters')
|
package/link/handshake.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer'
|
|
2
2
|
import { randomBytes } from 'node:crypto'
|
|
3
3
|
|
|
4
|
-
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
4
|
+
import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
5
|
import { normalizeTcpPort } from '../core/tcp_port.mjs'
|
|
6
6
|
import { keyPairFromSeed, pubKeyHash, sign, verify } from '../crypto/crypto.mjs'
|
|
7
7
|
import { normalizeLanHosts } from '../discovery/lan_interfaces.mjs'
|
|
@@ -117,9 +117,9 @@ export function parseHello(hello) {
|
|
|
117
117
|
export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
|
|
118
118
|
const parsedHello = parseHello(hello)
|
|
119
119
|
if (!parsedHello) return null
|
|
120
|
-
const signatureHex = String(auth?.sig ?? '')
|
|
120
|
+
const signatureHex = String(auth?.sig ?? '')
|
|
121
121
|
const binding = normalizeLinkBinding(remoteBinding)
|
|
122
|
-
if (
|
|
122
|
+
if (!isSignatureHex128(signatureHex) || !binding) return null
|
|
123
123
|
const normalizedNonce = normalizeHex64(expectedNonce)
|
|
124
124
|
if (!isHex64(normalizedNonce)) return null
|
|
125
125
|
const message = buildAuthMessage(normalizedNonce, binding, parsedHello.nodeHash)
|
|
@@ -141,7 +141,7 @@ export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
|
|
|
141
141
|
* @returns {Uint8Array} 待签名消息字节
|
|
142
142
|
*/
|
|
143
143
|
export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null, lanHosts = null) {
|
|
144
|
-
const base = `fount-advert\0${
|
|
144
|
+
const base = `fount-advert\0${rendezvousKey}\0${ts}\0${normalizeHex64(nodeHash)}`
|
|
145
145
|
const port = normalizeTcpPort(tcpPort)
|
|
146
146
|
let message = port ? `${base}\0${port}` : base
|
|
147
147
|
const hosts = normalizeLanHosts(lanHosts)
|
|
@@ -194,8 +194,8 @@ export async function verifySignedAdvert(rendezvousKey, advert, now = Date.now()
|
|
|
194
194
|
const parsedHello = parseHello({ nodeHash: advert?.nodeHash, nodePubKey: advert?.nodePubKey, nonce: '0'.repeat(64) })
|
|
195
195
|
if (!parsedHello) return null
|
|
196
196
|
const ts = Number(advert?.ts)
|
|
197
|
-
const sig = String(advert?.sig ?? '')
|
|
198
|
-
if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs ||
|
|
197
|
+
const sig = String(advert?.sig ?? '')
|
|
198
|
+
if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !isSignatureHex128(sig)) return null
|
|
199
199
|
const hasTcpPortField = !!advert?.tcpPort
|
|
200
200
|
const tcpPort = normalizeTcpPort(advert?.tcpPort)
|
|
201
201
|
if (hasTcpPortField && !tcpPort) return null
|
package/link/pipe.mjs
CHANGED
|
@@ -299,8 +299,7 @@ export function createLinkPipe(options) {
|
|
|
299
299
|
}))
|
|
300
300
|
for (const frame of encodeFrames(frameId, bytes)) {
|
|
301
301
|
const sent = options.sendFrame(envelope.action, frame)
|
|
302
|
-
if (sent
|
|
303
|
-
await sent
|
|
302
|
+
if (sent?.then) await sent
|
|
304
303
|
sentFrames++
|
|
305
304
|
}
|
|
306
305
|
lastOutboundAt = Date.now()
|
|
@@ -15,14 +15,6 @@ export const BLE_DATA_SERVICE_UUID = 'f017f017f017f017f017f017f017f019'
|
|
|
15
15
|
export const BLE_DATA_CHAR_UUID = 'f017f017f017f017f017f017f017f01a'
|
|
16
16
|
const BT_DEVICE_NAME = 'fount-bt'
|
|
17
17
|
|
|
18
|
-
/**
|
|
19
|
-
* 探测 BLE GATT 数据链路是否可用;失败则回落其它 link provider。
|
|
20
|
-
* @returns {Promise<boolean>} 可用为 true
|
|
21
|
-
*/
|
|
22
|
-
export async function canUseBleGattLink() {
|
|
23
|
-
return canUseBluetoothRuntime()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
18
|
/**
|
|
27
19
|
* 在 GATT write/notify 上建立 pipe。
|
|
28
20
|
* @param {object} options 配置
|
|
@@ -279,7 +271,7 @@ export function createBleGattLinkProvider() {
|
|
|
279
271
|
id: instanceId,
|
|
280
272
|
level: LINK_LEVEL_BLE_GATT,
|
|
281
273
|
caps: { needsOfferAnswer: false, needsDiscoverySignal: false, probe: 'native' },
|
|
282
|
-
isAvailable:
|
|
274
|
+
isAvailable: canUseBluetoothRuntime,
|
|
283
275
|
/**
|
|
284
276
|
* @param {{ nodeHash: string }} remote 远端
|
|
285
277
|
* @returns {boolean} 是否有 BT peer hint
|
package/link/providers/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ const providers = new Map()
|
|
|
35
35
|
*/
|
|
36
36
|
export function registerLinkProvider(provider) {
|
|
37
37
|
if (!provider?.id) throw new Error('p2p: link provider requires id')
|
|
38
|
-
providers.set(
|
|
38
|
+
providers.set(provider.id, provider)
|
|
39
39
|
return () => unregisterLinkProvider(provider.id)
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -45,7 +45,7 @@ export function registerLinkProvider(provider) {
|
|
|
45
45
|
* @returns {void}
|
|
46
46
|
*/
|
|
47
47
|
export function unregisterLinkProvider(id) {
|
|
48
|
-
providers.delete(
|
|
48
|
+
providers.delete(id)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
package/link/providers/nostr.mjs
CHANGED
|
@@ -48,9 +48,7 @@ async function publishLinkPacket(remoteNodeHash, packet) {
|
|
|
48
48
|
* @returns {import('./index.mjs').LinkProvider & { deliverPacket: (packet: object) => void }} provider
|
|
49
49
|
*/
|
|
50
50
|
export function createNostrLinkProvider(options = {}) {
|
|
51
|
-
const resolveRelayUrls =
|
|
52
|
-
? options.getRelayUrls
|
|
53
|
-
: resolveDefaultRelayUrls
|
|
51
|
+
const resolveRelayUrls = options.getRelayUrls || resolveDefaultRelayUrls
|
|
54
52
|
|
|
55
53
|
/** @type {((link: import('./index.mjs').LinkHandle) => void) | null} */
|
|
56
54
|
let onInbound = null
|
|
@@ -304,7 +304,7 @@ export async function createWebRtcLink(options) {
|
|
|
304
304
|
if (trickleIceOff || !event.candidate) return
|
|
305
305
|
void sendSignal({
|
|
306
306
|
type: 'ice',
|
|
307
|
-
candidate:
|
|
307
|
+
candidate: event.candidate.toJSON?.() || event.candidate,
|
|
308
308
|
}).catch(error => pipe.close(`signal-send-failed:${formatErrorReason(error)}`))
|
|
309
309
|
}
|
|
310
310
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @returns {string | null} 处理后的 SDP;drop 策略下不可用时返回 null
|
|
12
12
|
*/
|
|
13
13
|
export function applyIceLocalHostnamePolicy(candidateSdp, policy) {
|
|
14
|
-
const sdp =
|
|
14
|
+
const sdp = candidateSdp || ''
|
|
15
15
|
if (!sdp || !/\.local/i.test(sdp)) return sdp || null
|
|
16
16
|
if (!/\btyp host\b/i.test(sdp)) return sdp
|
|
17
17
|
if (policy === 'drop') return null
|
|
@@ -34,9 +34,10 @@ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor,
|
|
|
34
34
|
const rewritten = applyIceLocalHostnamePolicy(raw, policy)
|
|
35
35
|
if (!rewritten) return null
|
|
36
36
|
if (rewritten === raw) return candidate
|
|
37
|
-
const init =
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const init = {
|
|
38
|
+
...(candidate.toJSON?.() || { sdpMid: candidate.sdpMid, sdpMLineIndex: candidate.sdpMLineIndex }),
|
|
39
|
+
candidate: rewritten,
|
|
40
|
+
}
|
|
40
41
|
return new RTCIceCandidateCtor(init)
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -49,7 +50,7 @@ export function filterIceLocalHostnameCandidate(candidate, RTCIceCandidateCtor,
|
|
|
49
50
|
export function wrapRtcPeerConnectionForIceLocalHostname(BaseRTC, RTCIceCandidate, policy = 'drop') {
|
|
50
51
|
if (policy === 'none') return BaseRTC
|
|
51
52
|
|
|
52
|
-
const baseRoutesIce =
|
|
53
|
+
const baseRoutesIce = !!BaseRTC.prototype.prepareIceCandidateEvent
|
|
53
54
|
|
|
54
55
|
return class IceLocalHostnameFilteredRTCPeerConnection extends BaseRTC {
|
|
55
56
|
/** @type {((event: RTCPeerConnectionIceEvent) => void) | null} */
|
package/link/rtc/w3c_bridge.mjs
CHANGED
|
@@ -36,7 +36,7 @@ function defineEmitterHandler(target, property, eventName, adapt = payload => pa
|
|
|
36
36
|
* @returns {RTCDataChannel} 已挂 W3C handler 的通道
|
|
37
37
|
*/
|
|
38
38
|
export function bridgeDataChannel(channel) {
|
|
39
|
-
if (bridgedChannels.has(channel) ||
|
|
39
|
+
if (bridgedChannels.has(channel) || !channel.on) return channel
|
|
40
40
|
bridgedChannels.add(channel)
|
|
41
41
|
defineEmitterHandler(channel, 'onmessage', 'message')
|
|
42
42
|
defineEmitterHandler(channel, 'onopen', 'open')
|
package/link/sdp_fingerprint.mjs
CHANGED
|
@@ -20,6 +20,6 @@ export function normalizeDtlsFingerprint(value) {
|
|
|
20
20
|
* @returns {string | null} 规范化 fingerprint,未找到时返回 null
|
|
21
21
|
*/
|
|
22
22
|
export function extractDtlsFingerprint(sdp) {
|
|
23
|
-
const line =
|
|
23
|
+
const line = sdp.match(/^a=fingerprint:sha-256\s+([\d:A-Fa-f]+)$/mu)?.[1]
|
|
24
24
|
return normalizeDtlsFingerprint(line)
|
|
25
25
|
}
|
|
@@ -19,7 +19,7 @@ const consumers = new Map()
|
|
|
19
19
|
* @returns {void}
|
|
20
20
|
*/
|
|
21
21
|
export function registerMailboxConsumer(app, handler) {
|
|
22
|
-
consumers.set(
|
|
22
|
+
consumers.set(app, { handler })
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -27,7 +27,7 @@ export function registerMailboxConsumer(app, handler) {
|
|
|
27
27
|
* @returns {void}
|
|
28
28
|
*/
|
|
29
29
|
export function unregisterMailboxConsumer(app) {
|
|
30
|
-
consumers.delete(
|
|
30
|
+
consumers.delete(app)
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
async function resolveRemoteNodeHashForPeer(username, peerId) {
|
|
26
26
|
if (!peerId) return null
|
|
27
27
|
const entry = activeLinkRoster().find(row => row.peerId === peerId)
|
|
28
|
-
const remote = entry?.remoteNodeHash
|
|
28
|
+
const remote = normalizeHex64(entry?.remoteNodeHash)
|
|
29
29
|
return remote || null
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -76,7 +76,7 @@ export async function deliverOrStoreMailboxPut(username, options) {
|
|
|
76
76
|
fromNodeHash: options.record?.fromNodeHash || nodeHash,
|
|
77
77
|
}
|
|
78
78
|
const stored = await storeMailboxRecord(record)
|
|
79
|
-
const toNodeHash = options.toNodeHash
|
|
79
|
+
const toNodeHash = normalizeHex64(options.toNodeHash)
|
|
80
80
|
const delivered = toNodeHash && isMailboxRecordWithinSizeLimit(record)
|
|
81
81
|
? await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, toNodeHash, 'mailbox_put', { nodeHash, record })
|
|
82
82
|
: false
|
|
@@ -116,7 +116,7 @@ export async function ingestMailboxPut(wireContext, put, peerId = '') {
|
|
|
116
116
|
if (!record?.envelope || !record?.toPubKeyHash) return
|
|
117
117
|
const fromNode = normalizeHex64(put.nodeHash)
|
|
118
118
|
if (!fromNode || !takeIncomingMailboxPutSlot(fromNode)) return
|
|
119
|
-
const username =
|
|
119
|
+
const username = wireContext.replicaUsername || ''
|
|
120
120
|
if (!username) return
|
|
121
121
|
if (peerId) {
|
|
122
122
|
const remote = await resolveRemoteNodeHashForPeer(username, peerId)
|
|
@@ -162,7 +162,7 @@ export async function respondMailboxWant(want, sendGive, peerId) {
|
|
|
162
162
|
export async function ingestMailboxGive(wireContext, give) {
|
|
163
163
|
const records = (give.records || []).filter(isDeliverableMailboxRecord)
|
|
164
164
|
if (!records.length) return 0
|
|
165
|
-
const username =
|
|
165
|
+
const username = wireContext.replicaUsername || ''
|
|
166
166
|
if (!username) return 0
|
|
167
167
|
const { dispatchMailboxRecordsToConsumers } = await import('./consumer_registry.mjs')
|
|
168
168
|
const { deleteMailboxRecords } = await import('./store.mjs')
|
package/mailbox/parse.mjs
CHANGED
|
@@ -64,7 +64,7 @@ export function parseMailboxGive(payload) {
|
|
|
64
64
|
return { ok: false, code: shape.code, field: `records[${i}].${shape.field}` }
|
|
65
65
|
if (!record.envelope || typeof record.envelope !== 'object')
|
|
66
66
|
return { ok: false, code: 'required', field: `records[${i}].envelope` }
|
|
67
|
-
const app = String(record.app || '')
|
|
67
|
+
const app = String(record.app || '')
|
|
68
68
|
if (!app)
|
|
69
69
|
return { ok: false, code: 'required', field: `records[${i}].app` }
|
|
70
70
|
records.push(record)
|
package/mailbox/prune.mjs
CHANGED
|
@@ -32,8 +32,8 @@ export function mailboxRecordBytes(record) {
|
|
|
32
32
|
*/
|
|
33
33
|
export function mailboxBucketKey(record) {
|
|
34
34
|
const to = normalizeHex64(record.toPubKeyHash) || ''
|
|
35
|
-
const from = record.fromNodeHash
|
|
36
|
-
|| record.envelope?.sender
|
|
35
|
+
const from = normalizeHex64(record.fromNodeHash)
|
|
36
|
+
|| normalizeHex64(record.envelope?.sender)
|
|
37
37
|
|| 'unknown'
|
|
38
38
|
return `${to}\0${from}`
|
|
39
39
|
}
|
package/mailbox/rate.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mailbox 入站 put 限速(按来源节点,节点级单例)。
|
|
3
3
|
*/
|
|
4
|
+
import { normalizeHex64 } from '../core/hexIds.mjs'
|
|
4
5
|
|
|
5
6
|
const DEFAULT_WINDOW_MS = 60_000
|
|
6
7
|
const DEFAULT_MAX_PUTS = 20
|
|
@@ -14,21 +15,13 @@ const inboundByKey = new Map()
|
|
|
14
15
|
* @param {object} [limits] 可选限额
|
|
15
16
|
* @returns {{ windowMs: number, maxPuts: number }} 生效限额
|
|
16
17
|
*/
|
|
17
|
-
|
|
18
|
+
function resolveMailboxRateLimits(limits = {}) {
|
|
18
19
|
return {
|
|
19
20
|
windowMs: Math.max(1000, Number(limits.windowMs) || DEFAULT_WINDOW_MS),
|
|
20
21
|
maxPuts: Math.max(1, Math.min(256, Number(limits.maxPuts) || DEFAULT_MAX_PUTS)),
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} fromNodeHash 来源节点
|
|
26
|
-
* @returns {string} 限速键
|
|
27
|
-
*/
|
|
28
|
-
export function mailboxRateKey(fromNodeHash) {
|
|
29
|
-
return String(fromNodeHash || '').trim()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
25
|
/**
|
|
33
26
|
* @param {number} now 当前时间戳
|
|
34
27
|
* @returns {void}
|
|
@@ -69,7 +62,7 @@ function evictLruIfNeeded(key) {
|
|
|
69
62
|
*/
|
|
70
63
|
export function takeIncomingMailboxPutSlot(fromNodeHash, limits) {
|
|
71
64
|
const { windowMs, maxPuts } = resolveMailboxRateLimits(limits)
|
|
72
|
-
const key =
|
|
65
|
+
const key = normalizeHex64(fromNodeHash)
|
|
73
66
|
const now = Date.now()
|
|
74
67
|
if (inboundByKey.size >= MAX_KEYS) sweepExpiredEntries(now)
|
|
75
68
|
evictLruIfNeeded(key)
|
package/mailbox/store.mjs
CHANGED
|
@@ -73,7 +73,7 @@ export function mailboxTierFromHop(hop) {
|
|
|
73
73
|
*/
|
|
74
74
|
export function isDeliverableMailboxRecord(row) {
|
|
75
75
|
if (!row?.envelope || typeof row.envelope !== 'object') return false
|
|
76
|
-
if (!String(row.app || '')
|
|
76
|
+
if (!String(row.app || '')) return false
|
|
77
77
|
return row.tier === 'trusted' || row.tier === 'normal'
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -139,7 +139,7 @@ async function writeAll(rows) {
|
|
|
139
139
|
* @returns {string} 稳定信封 id
|
|
140
140
|
*/
|
|
141
141
|
export function mailboxEnvelopeId(envelope) {
|
|
142
|
-
const id = String(envelope?.id || '')
|
|
142
|
+
const id = String(envelope?.id || '')
|
|
143
143
|
if (!id) throw new Error('mailbox envelope id required')
|
|
144
144
|
return id
|
|
145
145
|
}
|
|
@@ -155,7 +155,7 @@ export async function storeMailboxRecord(record) {
|
|
|
155
155
|
const hop = normalizeMailboxHop(record.hop)
|
|
156
156
|
const { tier } = record
|
|
157
157
|
if (!['trusted', 'normal', 'quarantine'].includes(tier)) return false
|
|
158
|
-
const app = String(record.app || '')
|
|
158
|
+
const app = String(record.app || '')
|
|
159
159
|
if (!app) return false
|
|
160
160
|
let id
|
|
161
161
|
try {
|
|
@@ -172,13 +172,13 @@ export async function storeMailboxRecord(record) {
|
|
|
172
172
|
id,
|
|
173
173
|
app,
|
|
174
174
|
toPubKeyHash,
|
|
175
|
-
dmSessionTag: record.dmSessionTag
|
|
175
|
+
dmSessionTag: record.dmSessionTag ? String(record.dmSessionTag) : undefined,
|
|
176
176
|
groupId: record.groupId || undefined,
|
|
177
177
|
channelId: record.channelId || undefined,
|
|
178
178
|
envelope: record.envelope,
|
|
179
179
|
storedAt: Date.now(),
|
|
180
180
|
expiresAt: Date.now() + ttlMs,
|
|
181
|
-
fromNodeHash:
|
|
181
|
+
fromNodeHash: normalizeHex64(record.fromNodeHash),
|
|
182
182
|
hop,
|
|
183
183
|
tier,
|
|
184
184
|
importance: Number.isFinite(Number(record.importance)) ? Number(record.importance) : undefined,
|
|
@@ -204,7 +204,7 @@ export async function listMailboxIdsForRecipient(toPubKeyHash) {
|
|
|
204
204
|
* @returns {Promise<MailboxRecord[]>} 匹配记录
|
|
205
205
|
*/
|
|
206
206
|
export async function getMailboxRecords(ids) {
|
|
207
|
-
const want = new Set(ids.map(id => String(id)
|
|
207
|
+
const want = new Set(ids.map(id => String(id)))
|
|
208
208
|
return (await readAll()).filter(record => want.has(record.id))
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -213,7 +213,7 @@ export async function getMailboxRecords(ids) {
|
|
|
213
213
|
* @returns {Promise<void>}
|
|
214
214
|
*/
|
|
215
215
|
export async function deleteMailboxRecords(ids) {
|
|
216
|
-
const drop = new Set(ids.map(id => String(id)
|
|
216
|
+
const drop = new Set(ids.map(id => String(id)))
|
|
217
217
|
return withAsyncMutex(mailboxStoreMutexKey(), async () => {
|
|
218
218
|
await writeAll((await readAll()).filter(record => !drop.has(record.id)))
|
|
219
219
|
})
|