@steve02081504/fount-p2p 0.0.0
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 +62 -0
- package/core/bytes_codec.mjs +51 -0
- package/core/canonical_json.mjs +21 -0
- package/core/composite_key.mjs +91 -0
- package/core/constants.mjs +53 -0
- package/core/entity_id.mjs +53 -0
- package/core/entity_id_parse.mjs +40 -0
- package/core/evfs_logical_path.mjs +15 -0
- package/core/hexIds.mjs +73 -0
- package/core/hlc.mjs +114 -0
- package/core/random_id.mjs +9 -0
- package/crypto/channel.mjs +101 -0
- package/crypto/checkpoint_sign.mjs +67 -0
- package/crypto/crypto.mjs +158 -0
- package/crypto/key.mjs +431 -0
- package/dag/canonicalize_row.mjs +65 -0
- package/dag/event_query.mjs +15 -0
- package/dag/index.mjs +335 -0
- package/dag/storage.mjs +333 -0
- package/dag/strip_extensions.mjs +18 -0
- package/dag/tunables.json +6 -0
- package/discovery/bt.mjs +260 -0
- package/discovery/index.mjs +134 -0
- package/discovery/mdns.mjs +138 -0
- package/discovery/nostr.mjs +273 -0
- package/entity/files/acl.mjs +38 -0
- package/entity/files/evfs.mjs +189 -0
- package/entity/files/evfs_ref.mjs +37 -0
- package/entity/files/manifest_acl_registry.mjs +93 -0
- package/entity/files/replica_host_cache.mjs +46 -0
- package/entity/files/url.mjs +19 -0
- package/entity/hosting_registry.mjs +47 -0
- package/entity/localized_core.mjs +141 -0
- package/entity/logical_entity.mjs +25 -0
- package/entity/logical_entity_id_registry.mjs +40 -0
- package/entity/node_hash.mjs +15 -0
- package/entity/presentation_registry.mjs +44 -0
- package/entity/profile.mjs +256 -0
- package/entity/replica.mjs +20 -0
- package/entity/session_snapshot_registry.mjs +38 -0
- package/federation/chunk_fetch_pending.mjs +128 -0
- package/federation/chunk_fetch_scheduler.mjs +97 -0
- package/federation/dag_order_cache.mjs +124 -0
- package/federation/dedupe_slot.mjs +33 -0
- package/federation/message_rate_limit.mjs +23 -0
- package/federation/operator_key_chain.mjs +200 -0
- package/federation/topo_order_memo.mjs +44 -0
- package/federation/volatile_streams.mjs +52 -0
- package/federation/want_ids.mjs +158 -0
- package/files/assemble.mjs +220 -0
- package/files/assemble_stream.mjs +129 -0
- package/files/chunk_fetch.mjs +113 -0
- package/files/chunk_fetch_verify.mjs +23 -0
- package/files/chunk_provider_registry.mjs +70 -0
- package/files/chunk_responder.mjs +79 -0
- package/files/chunk_store.mjs +87 -0
- package/files/manifest.mjs +99 -0
- package/files/transfer_key.mjs +87 -0
- package/files/transfer_key_registry.mjs +108 -0
- package/governance/branch.mjs +230 -0
- package/governance/join_pow.mjs +165 -0
- package/governance/owner_succession_ballot.mjs +57 -0
- package/governance/tunables.json +7 -0
- package/index.mjs +39 -0
- package/link/channel_mux.mjs +222 -0
- package/link/frame.mjs +246 -0
- package/link/handshake.mjs +175 -0
- package/link/link.mjs +617 -0
- package/link/rtc.mjs +109 -0
- package/link/sdp_fingerprint.mjs +25 -0
- package/mailbox/consumer_registry.mjs +71 -0
- package/mailbox/deliver_or_store.mjs +190 -0
- package/mailbox/importance.mjs +72 -0
- package/mailbox/parse.mjs +73 -0
- package/mailbox/prune.mjs +106 -0
- package/mailbox/rate.mjs +86 -0
- package/mailbox/settings.mjs +53 -0
- package/mailbox/store.mjs +256 -0
- package/mailbox/tunables.json +12 -0
- package/mailbox/wire.mjs +40 -0
- package/node/denylist.mjs +309 -0
- package/node/entity_store.mjs +214 -0
- package/node/identity.mjs +99 -0
- package/node/instance.mjs +115 -0
- package/node/network.mjs +306 -0
- package/node/personal_block.mjs +286 -0
- package/node/reputation_store.mjs +326 -0
- package/node/retention_policy.mjs +82 -0
- package/node/signaling_config.mjs +32 -0
- package/node/storage.mjs +30 -0
- package/node/storage_plugins.mjs +71 -0
- package/node/user_paths.mjs +10 -0
- package/overlay/index.mjs +191 -0
- package/package.json +73 -0
- package/permissions/bitmask.mjs +35 -0
- package/permissions/evaluator.mjs +90 -0
- package/permissions/index.mjs +12 -0
- package/permissions/layered.mjs +39 -0
- package/registries/action.mjs +94 -0
- package/registries/event_type.mjs +75 -0
- package/registries/inbound.mjs +90 -0
- package/registries/p2p_viewer.mjs +35 -0
- package/registries/part_path.mjs +35 -0
- package/registries/room_provider.mjs +47 -0
- package/reputation/engine.mjs +458 -0
- package/reputation/math.mjs +73 -0
- package/reputation/pick_score.mjs +11 -0
- package/reputation/relay_dedupe.mjs +22 -0
- package/reputation/tunables.json +36 -0
- package/rooms/scoped_link.mjs +185 -0
- package/schemas/discovery.mjs +110 -0
- package/schemas/federation_pull.mjs +102 -0
- package/schemas/mailbox.mjs +37 -0
- package/schemas/remote_event.mjs +17 -0
- package/timeline/append_core.mjs +54 -0
- package/timeline/materialize_runner.mjs +26 -0
- package/timeline/prune.mjs +39 -0
- package/timeline/retention.mjs +54 -0
- package/timeline/retention_runner.mjs +35 -0
- package/timeline/verify_remote.mjs +25 -0
- package/transport/group_link_set.mjs +324 -0
- package/transport/ice_servers.mjs +72 -0
- package/transport/link_registry.mjs +749 -0
- package/transport/peer_identity_maps.mjs +94 -0
- package/transport/peer_pool.mjs +355 -0
- package/transport/remote_user_room.mjs +109 -0
- package/transport/room_scopes.mjs +2 -0
- package/transport/room_wire_action.mjs +19 -0
- package/transport/rtc_connection_budget.mjs +180 -0
- package/transport/rtc_mdns_filter.mjs +118 -0
- package/transport/stale_peer_log.mjs +50 -0
- package/transport/user_room.mjs +263 -0
- package/trust_graph/build.mjs +99 -0
- package/trust_graph/cache.mjs +44 -0
- package/trust_graph/engine.mjs +184 -0
- package/trust_graph/registry.mjs +38 -0
- package/trust_graph/resolve.mjs +105 -0
- package/trust_graph/send.mjs +88 -0
- package/trust_graph/tunables.json +13 -0
- package/utils/async_mutex.mjs +69 -0
- package/utils/debug_log.mjs +16 -0
- package/utils/duration.mjs +28 -0
- package/utils/json_io.mjs +57 -0
- package/utils/lru.mjs +22 -0
- package/utils/map_pool.mjs +27 -0
- package/wire/group_part.mjs +43 -0
- package/wire/ingress.mjs +58 -0
- package/wire/part_common.mjs +55 -0
- package/wire/part_fanout.mjs +71 -0
- package/wire/part_ingress.mjs +172 -0
- package/wire/part_invoke.mjs +81 -0
- package/wire/rate_bucket.mjs +49 -0
- package/wire/volatile_signature.mjs +76 -0
- package/wire/wait.mjs +127 -0
package/mailbox/rate.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mailbox 入站 put 限速(按来源节点,节点级单例)。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const DEFAULT_WINDOW_MS = 60_000
|
|
6
|
+
const DEFAULT_MAX_PUTS = 20
|
|
7
|
+
const MAX_KEYS = 8000
|
|
8
|
+
const EXPIRE_SWEEP_BATCH = 64
|
|
9
|
+
|
|
10
|
+
/** @type {Map<string, { count: number, resetAt: number }>} */
|
|
11
|
+
const inboundByKey = new Map()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} [limits] 可选限额
|
|
15
|
+
* @returns {{ windowMs: number, maxPuts: number }} 生效限额
|
|
16
|
+
*/
|
|
17
|
+
export function resolveMailboxRateLimits(limits = {}) {
|
|
18
|
+
return {
|
|
19
|
+
windowMs: Math.max(1000, Number(limits.windowMs) || DEFAULT_WINDOW_MS),
|
|
20
|
+
maxPuts: Math.max(1, Math.min(256, Number(limits.maxPuts) || DEFAULT_MAX_PUTS)),
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} fromNodeHash 来源节点
|
|
26
|
+
* @returns {string} 限速键
|
|
27
|
+
*/
|
|
28
|
+
export function mailboxRateKey(fromNodeHash) {
|
|
29
|
+
return String(fromNodeHash || '').trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} now 当前时间戳
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
function sweepExpiredEntries(now) {
|
|
37
|
+
let scanned = 0
|
|
38
|
+
for (const [rateKey, rateEntry] of inboundByKey) {
|
|
39
|
+
if (now > rateEntry.resetAt) inboundByKey.delete(rateKey)
|
|
40
|
+
if (++scanned >= EXPIRE_SWEEP_BATCH) break
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} key 限速键
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
function touchLruKey(key) {
|
|
49
|
+
const entry = inboundByKey.get(key)
|
|
50
|
+
if (!entry) return
|
|
51
|
+
inboundByKey.delete(key)
|
|
52
|
+
inboundByKey.set(key, entry)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} key 新插入键
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
function evictLruIfNeeded(key) {
|
|
60
|
+
if (inboundByKey.has(key) || inboundByKey.size < MAX_KEYS) return
|
|
61
|
+
const oldest = inboundByKey.keys().next().value
|
|
62
|
+
if (oldest != null) inboundByKey.delete(oldest)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {string} fromNodeHash 来源节点
|
|
67
|
+
* @param {object} [limits] 可选限额
|
|
68
|
+
* @returns {boolean} 允许新 put 则为 true
|
|
69
|
+
*/
|
|
70
|
+
export function takeIncomingMailboxPutSlot(fromNodeHash, limits) {
|
|
71
|
+
const { windowMs, maxPuts } = resolveMailboxRateLimits(limits)
|
|
72
|
+
const key = mailboxRateKey(fromNodeHash)
|
|
73
|
+
const now = Date.now()
|
|
74
|
+
if (inboundByKey.size >= MAX_KEYS) sweepExpiredEntries(now)
|
|
75
|
+
evictLruIfNeeded(key)
|
|
76
|
+
let entry = inboundByKey.get(key)
|
|
77
|
+
if (!entry || now > entry.resetAt) entry = { count: 0, resetAt: now + windowMs }
|
|
78
|
+
if (entry.count >= maxPuts) {
|
|
79
|
+
touchLruKey(key)
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
entry.count++
|
|
83
|
+
inboundByKey.set(key, entry)
|
|
84
|
+
touchLruKey(key)
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getNodeTransportSettings } from '../node/identity.mjs'
|
|
2
|
+
import {
|
|
3
|
+
resolveMailboxRelayFanout,
|
|
4
|
+
resolveMailboxWantFanout,
|
|
5
|
+
} from '../trust_graph/resolve.mjs'
|
|
6
|
+
|
|
7
|
+
import mailboxTunables from './tunables.json' with { type: 'json' }
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} raw 节点 mailbox 配置片段
|
|
11
|
+
* @param {number} [peerCount=0] 已知在线 relay 候选数(0 时用 floor)
|
|
12
|
+
* @returns {{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number }} 规范化 mailbox 配置
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeMailboxSettings(raw = {}, peerCount = 0) {
|
|
15
|
+
const maxHop = Math.max(1, Math.min(8, Number(raw.maxHop) || mailboxTunables.maxHop))
|
|
16
|
+
const capTrusted = mailboxTunables.relayFanoutTrustedCap ?? 32
|
|
17
|
+
const capWant = mailboxTunables.wantFanoutCap ?? 32
|
|
18
|
+
const n = Math.max(0, Math.floor(Number(peerCount) || 0))
|
|
19
|
+
const relayFanoutTrusted = Number.isFinite(Number(raw.relayFanoutTrusted))
|
|
20
|
+
? Math.max(1, Math.min(capTrusted, Math.floor(Number(raw.relayFanoutTrusted))))
|
|
21
|
+
: resolveMailboxRelayFanout(n, mailboxTunables)
|
|
22
|
+
const relayFanoutNormal = Math.max(1, Math.min(capTrusted, Number(raw.relayFanoutNormal) || mailboxTunables.relayFanoutNormal))
|
|
23
|
+
const wantFanout = Number.isFinite(Number(raw.wantFanout))
|
|
24
|
+
? Math.max(1, Math.min(capWant, Math.floor(Number(raw.wantFanout))))
|
|
25
|
+
: resolveMailboxWantFanout(n, mailboxTunables)
|
|
26
|
+
return { maxHop, relayFanoutTrusted, relayFanoutNormal, wantFanout }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {number} peerCount 已知在线 relay 候选数
|
|
31
|
+
* @param {object} [raw] 节点 mailbox 配置片段
|
|
32
|
+
* @param {boolean} [batterySaver=false] 省电模式
|
|
33
|
+
* @returns {{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number, batterySaver: boolean }} 缩放后的路由
|
|
34
|
+
*/
|
|
35
|
+
export function resolveMailboxRoutingForPeerCount(peerCount, raw = {}, batterySaver = false) {
|
|
36
|
+
const base = normalizeMailboxSettings(raw, peerCount)
|
|
37
|
+
if (!batterySaver) return { ...base, batterySaver: false }
|
|
38
|
+
return {
|
|
39
|
+
maxHop: base.maxHop,
|
|
40
|
+
relayFanoutTrusted: Math.max(1, Math.ceil(base.relayFanoutTrusted / 2)),
|
|
41
|
+
relayFanoutNormal: Math.max(1, Math.ceil(base.relayFanoutNormal / 2)),
|
|
42
|
+
wantFanout: Math.max(1, Math.ceil(base.wantFanout / 2)),
|
|
43
|
+
batterySaver: true,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @returns {{ maxHop: number, relayFanoutTrusted: number, relayFanoutNormal: number, wantFanout: number, batterySaver: boolean }} mailbox 路由配置
|
|
49
|
+
*/
|
|
50
|
+
export function getMailboxRoutingSettings() {
|
|
51
|
+
const { batterySaver, mailbox } = getNodeTransportSettings()
|
|
52
|
+
return resolveMailboxRoutingForPeerCount(0, mailbox, batterySaver)
|
|
53
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { mkdir, readFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
|
|
5
|
+
import { jsonlMutexKey, writeJsonl } from '../dag/storage.mjs'
|
|
6
|
+
import { mailboxStorePath } from '../node/user_paths.mjs'
|
|
7
|
+
import { withAsyncMutex } from '../utils/async_mutex.mjs'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
defaultTtlMsForTier,
|
|
11
|
+
sortMailboxForRetention,
|
|
12
|
+
} from './importance.mjs'
|
|
13
|
+
import {
|
|
14
|
+
MAX_BUCKET_BYTES,
|
|
15
|
+
MAX_BUCKET_ENTRIES,
|
|
16
|
+
mailboxBucketKey,
|
|
17
|
+
mailboxRecordBytes,
|
|
18
|
+
pruneMailboxBuckets,
|
|
19
|
+
pruneMailboxGlobalFair,
|
|
20
|
+
} from './prune.mjs'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
export { MAX_BUCKET_BYTES, MAX_BUCKET_ENTRIES, mailboxBucketKey, mailboxRecordBytes }
|
|
26
|
+
|
|
27
|
+
/** 单条 mailbox envelope JSON 字节上限 */
|
|
28
|
+
export const MAX_ENTRY_BYTES = 256 * 1024
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {'trusted' | 'normal' | 'quarantine'} MailboxTier
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {{
|
|
36
|
+
* id: string,
|
|
37
|
+
* app: string,
|
|
38
|
+
* toPubKeyHash: string,
|
|
39
|
+
* dmSessionTag?: string,
|
|
40
|
+
* groupId?: string,
|
|
41
|
+
* channelId?: string,
|
|
42
|
+
* envelope: object,
|
|
43
|
+
* storedAt: number,
|
|
44
|
+
* expiresAt: number,
|
|
45
|
+
* fromNodeHash: string,
|
|
46
|
+
* hop: number,
|
|
47
|
+
* tier?: MailboxTier,
|
|
48
|
+
* importance?: number,
|
|
49
|
+
* }} MailboxRecord
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {unknown} value 入站 hop
|
|
54
|
+
* @returns {number} 非负整数 hop
|
|
55
|
+
*/
|
|
56
|
+
export function normalizeMailboxHop(value) {
|
|
57
|
+
const n = Number(value)
|
|
58
|
+
if (!Number.isFinite(n)) return 0
|
|
59
|
+
return Math.max(0, Math.floor(n))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @returns {string} mailbox store 进程内互斥键
|
|
64
|
+
*/
|
|
65
|
+
function mailboxStoreMutexKey() {
|
|
66
|
+
return jsonlMutexKey(mailboxStorePath())
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {number} hop 转发跳数
|
|
71
|
+
* @returns {MailboxTier} 信誉分层
|
|
72
|
+
*/
|
|
73
|
+
export function mailboxTierFromHop(hop) {
|
|
74
|
+
if (hop <= 0) return 'trusted'
|
|
75
|
+
if (hop === 1) return 'normal'
|
|
76
|
+
return 'quarantine'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {object | null | undefined} row mailbox 记录
|
|
81
|
+
* @returns {boolean} 是否可交给 Part 消费者(排除 quarantine 与缺字段)
|
|
82
|
+
*/
|
|
83
|
+
export function isDeliverableMailboxRecord(row) {
|
|
84
|
+
if (!row?.envelope || typeof row.envelope !== 'object') return false
|
|
85
|
+
if (!String(row.app || '').trim()) return false
|
|
86
|
+
return row.tier === 'trusted' || row.tier === 'normal'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {object | null | undefined} record mailbox 记录
|
|
91
|
+
* @returns {boolean} envelope 是否在存储限额内
|
|
92
|
+
*/
|
|
93
|
+
export function isMailboxRecordWithinSizeLimit(record) {
|
|
94
|
+
if (!record?.envelope || typeof record.envelope !== 'object') return false
|
|
95
|
+
return JSON.stringify(record.envelope).length <= MAX_ENTRY_BYTES
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 入站 wire 转发:不信任自报 hop,至少为 1;若本地已有同 id 则单调递增。
|
|
100
|
+
* @param {unknown} wireHop 线载荷 hop
|
|
101
|
+
* @param {unknown} [existingStoredHop] 本地已存 hop
|
|
102
|
+
* @returns {number} 本节点存储 hop
|
|
103
|
+
*/
|
|
104
|
+
export function relayHopAfterWireIngress(wireHop, existingStoredHop) {
|
|
105
|
+
const fromWire = Math.max(normalizeMailboxHop(wireHop) + 1, 1)
|
|
106
|
+
if (existingStoredHop == null || !Number.isFinite(Number(existingStoredHop)))
|
|
107
|
+
return fromWire
|
|
108
|
+
return Math.max(fromWire, normalizeMailboxHop(existingStoredHop) + 1)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @returns {Promise<MailboxRecord[]>} 全部有效记录
|
|
113
|
+
*/
|
|
114
|
+
async function readAll() {
|
|
115
|
+
try {
|
|
116
|
+
const text = await readFile(mailboxStorePath(), 'utf8')
|
|
117
|
+
/** @type {MailboxRecord[]} */
|
|
118
|
+
const rows = []
|
|
119
|
+
for (const line of text.split('\n')) {
|
|
120
|
+
const trimmed = line.trim()
|
|
121
|
+
if (!trimmed) continue
|
|
122
|
+
try {
|
|
123
|
+
rows.push(JSON.parse(trimmed))
|
|
124
|
+
}
|
|
125
|
+
catch { /* skip corrupt line */ }
|
|
126
|
+
}
|
|
127
|
+
return rows
|
|
128
|
+
}
|
|
129
|
+
catch { return [] }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {MailboxRecord[]} rows 待写入记录
|
|
134
|
+
* @returns {Promise<void>}
|
|
135
|
+
*/
|
|
136
|
+
async function writeAll(rows) {
|
|
137
|
+
const filePath = mailboxStorePath()
|
|
138
|
+
await mkdir(dirname(filePath), { recursive: true })
|
|
139
|
+
const now = Date.now()
|
|
140
|
+
const kept = pruneMailboxGlobalFair(
|
|
141
|
+
pruneMailboxBuckets(sortMailboxForRetention(rows.filter(record => record.expiresAt > now))),
|
|
142
|
+
)
|
|
143
|
+
await writeJsonl(filePath, kept)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {object} envelope 载荷
|
|
148
|
+
* @returns {string} 稳定信封 id
|
|
149
|
+
*/
|
|
150
|
+
export function mailboxEnvelopeId(envelope) {
|
|
151
|
+
const id = String(envelope?.id || '').trim().toLowerCase()
|
|
152
|
+
if (!id) throw new Error('mailbox envelope id required')
|
|
153
|
+
return id
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {object} record mailbox 记录字段
|
|
158
|
+
* @returns {Promise<boolean>} 是否新写入
|
|
159
|
+
*/
|
|
160
|
+
export async function storeMailboxRecord(record) {
|
|
161
|
+
if (!isMailboxRecordWithinSizeLimit(record)) return false
|
|
162
|
+
const toPubKeyHash = normalizeHex64(record.toPubKeyHash)
|
|
163
|
+
if (!isHex64(toPubKeyHash)) return false
|
|
164
|
+
const hop = normalizeMailboxHop(record.hop)
|
|
165
|
+
const tier = record.tier
|
|
166
|
+
if (!['trusted', 'normal', 'quarantine'].includes(tier)) return false
|
|
167
|
+
const app = String(record.app || '').trim()
|
|
168
|
+
if (!app) return false
|
|
169
|
+
let id
|
|
170
|
+
try {
|
|
171
|
+
id = mailboxEnvelopeId(record.envelope)
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return false
|
|
175
|
+
}
|
|
176
|
+
return withAsyncMutex(mailboxStoreMutexKey(), async () => {
|
|
177
|
+
const rows = await readAll()
|
|
178
|
+
if (rows.some(row => row.id === id)) return false
|
|
179
|
+
const ttlMs = Number(record.ttlMs) || defaultTtlMsForTier(tier)
|
|
180
|
+
rows.push({
|
|
181
|
+
id,
|
|
182
|
+
app,
|
|
183
|
+
toPubKeyHash,
|
|
184
|
+
dmSessionTag: record.dmSessionTag?.trim().toLowerCase() || undefined,
|
|
185
|
+
groupId: record.groupId || undefined,
|
|
186
|
+
channelId: record.channelId || undefined,
|
|
187
|
+
envelope: record.envelope,
|
|
188
|
+
storedAt: Date.now(),
|
|
189
|
+
expiresAt: Date.now() + ttlMs,
|
|
190
|
+
fromNodeHash: String(record.fromNodeHash || '').trim(),
|
|
191
|
+
hop,
|
|
192
|
+
tier,
|
|
193
|
+
importance: Number.isFinite(Number(record.importance)) ? Number(record.importance) : undefined,
|
|
194
|
+
})
|
|
195
|
+
await writeAll(rows)
|
|
196
|
+
return true
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {string} toPubKeyHash 收件人
|
|
202
|
+
* @returns {Promise<string[]>} record id 列表
|
|
203
|
+
*/
|
|
204
|
+
export async function listMailboxIdsForRecipient(toPubKeyHash) {
|
|
205
|
+
const recipient = normalizeHex64(toPubKeyHash)
|
|
206
|
+
return (await readAll())
|
|
207
|
+
.filter(record => record.toPubKeyHash === recipient)
|
|
208
|
+
.map(record => record.id)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {string[]} ids record id 列表
|
|
213
|
+
* @returns {Promise<MailboxRecord[]>} 匹配记录
|
|
214
|
+
*/
|
|
215
|
+
export async function getMailboxRecords(ids) {
|
|
216
|
+
const want = new Set(ids.map(id => String(id).trim().toLowerCase()))
|
|
217
|
+
return (await readAll()).filter(record => want.has(record.id))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {string[]} ids 待删除 id
|
|
222
|
+
* @returns {Promise<void>}
|
|
223
|
+
*/
|
|
224
|
+
export async function deleteMailboxRecords(ids) {
|
|
225
|
+
const drop = new Set(ids.map(id => String(id).trim().toLowerCase()))
|
|
226
|
+
return withAsyncMutex(mailboxStoreMutexKey(), async () => {
|
|
227
|
+
await writeAll((await readAll()).filter(record => !drop.has(record.id)))
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @param {string} toPubKeyHash 收件人
|
|
233
|
+
* @returns {Promise<MailboxRecord[]>} 待发记录(不删除)
|
|
234
|
+
*/
|
|
235
|
+
export async function takeMailboxForRecipient(toPubKeyHash) {
|
|
236
|
+
const recipient = normalizeHex64(toPubKeyHash)
|
|
237
|
+
return (await readAll()).filter(record => record.toPubKeyHash === recipient)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} toPubKeyHash 收件人 pubKeyHash
|
|
242
|
+
* @returns {Promise<number>} 未过期条数
|
|
243
|
+
*/
|
|
244
|
+
export async function countMailboxPendingForRecipient(toPubKeyHash) {
|
|
245
|
+
const recipient = normalizeHex64(toPubKeyHash)
|
|
246
|
+
const now = Date.now()
|
|
247
|
+
return (await readAll()).filter(record => record.toPubKeyHash === recipient && record.expiresAt > now).length
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @returns {Promise<number>} 未过期条数
|
|
252
|
+
*/
|
|
253
|
+
export async function countMailboxPending() {
|
|
254
|
+
const now = Date.now()
|
|
255
|
+
return (await readAll()).filter(record => record.expiresAt > now).length
|
|
256
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"maxHop": 3,
|
|
3
|
+
"relayFanoutTrustedFloor": 3,
|
|
4
|
+
"relayFanoutTrustedRatio": 0.3,
|
|
5
|
+
"relayFanoutTrustedCap": 32,
|
|
6
|
+
"relayFanoutNormal": 3,
|
|
7
|
+
"wantFanoutFloor": 3,
|
|
8
|
+
"wantFanoutRatio": 0.4,
|
|
9
|
+
"wantFanoutCap": 32,
|
|
10
|
+
"networkBudgetPerRound": 64,
|
|
11
|
+
"networkBudgetPerPeerRatio": 2
|
|
12
|
+
}
|
package/mailbox/wire.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ingestMailboxGive,
|
|
3
|
+
ingestMailboxPut,
|
|
4
|
+
respondMailboxWant,
|
|
5
|
+
} from './deliver_or_store.mjs'
|
|
6
|
+
import { parseMailboxGive, parseMailboxPut, parseMailboxWant } from './parse.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{ replicaUsername?: string }} MailboxWireContext
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {MailboxWireContext} ctx 入站上下文
|
|
14
|
+
* @param {{ on: (name: string, handler: (payload: unknown, peerId: string) => void) => void, send: (name: string, payload: unknown, peerId: string | null) => void }} wire Trystero 适配器
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
export function attachMailboxWire(ctx, wire) {
|
|
18
|
+
wire.on('mailbox_put', (payload, peerId) => {
|
|
19
|
+
const put = parseMailboxPut(payload)
|
|
20
|
+
if (!put.ok) return
|
|
21
|
+
void ingestMailboxPut(ctx, put.value, peerId).catch(err => console.error('mailbox: put ingest failed', err))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
wire.on('mailbox_want', (payload, peerId) => {
|
|
25
|
+
const want = parseMailboxWant(payload)
|
|
26
|
+
if (!want.ok) return
|
|
27
|
+
void respondMailboxWant(want.value, (giveWire, targetPeerId) => {
|
|
28
|
+
try {
|
|
29
|
+
wire.send('mailbox_give', giveWire, targetPeerId)
|
|
30
|
+
}
|
|
31
|
+
catch { /* disconnected */ }
|
|
32
|
+
}, peerId).catch(err => console.error('mailbox: want failed', err))
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
wire.on('mailbox_give', payload => {
|
|
36
|
+
const give = parseMailboxGive(payload)
|
|
37
|
+
if (!give.ok) return
|
|
38
|
+
void ingestMailboxGive(ctx, give.value).catch(err => console.error('mailbox: give ingest failed', err))
|
|
39
|
+
})
|
|
40
|
+
}
|