@steve02081504/fount-p2p 0.0.16 → 0.0.18

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.
@@ -1,8 +1,5 @@
1
- import { spawn } from 'node:child_process'
2
1
  import { existsSync, readdirSync } from 'node:fs'
3
- import { dirname, join } from 'node:path'
4
2
  import process from 'node:process'
5
- import { fileURLToPath } from 'node:url'
6
3
 
7
4
  /**
8
5
  * 解析 Bluetooth 角色(scan / dual)。
@@ -31,50 +28,34 @@ export function probeBluetoothHardware() {
31
28
 
32
29
  /** @type {boolean | null} canUseBluetoothRuntime 缓存 */
33
30
  let cachedRuntimeOk = null
34
- /** @type {Promise<boolean> | null} 并发 canUse 合并为一次子进程探测 */
31
+ /** @type {Promise<boolean> | null} 并发 canUse 合并为一次进程内探测 */
35
32
  let probeInflight = null
36
33
 
37
- const PROBE_CHILD = join(dirname(fileURLToPath(import.meta.url)), 'probe_child.mjs')
38
-
39
34
  /**
40
- * 在子进程中探测 BT(父进程 waitPoweredOn 会拖住事件循环;stop() 还可能 AV)。
35
+ * 进程内 BT 可用性探测:load poweredOn → stop。
36
+ * 需要 @stoprocent/noble>=2.5.9(Windows stop() 与事件循环释放,见 stoprocent/noble#95)。
41
37
  * @param {number} timeoutMs waitPoweredOn 超时
42
- * @returns {Promise<boolean>} 子进程 exit 0 为可用
38
+ * @returns {Promise<boolean>} poweredOn stop 成功为 true
43
39
  */
44
- function probeBluetoothInSubprocess(timeoutMs) {
45
- return new Promise(resolve => {
46
- const child = spawn(process.execPath, [PROBE_CHILD, String(timeoutMs)], {
47
- stdio: 'ignore',
48
- windowsHide: true,
49
- })
50
- // 探测是旁路缓存填充;勿拖住父进程事件循环 / shutdown 退出。
51
- child.unref()
52
- let settled = false
53
- /**
54
- * @param {boolean} ok 探测结果
55
- * @returns {void}
56
- */
57
- const finish = ok => {
58
- if (settled) return
59
- settled = true
60
- clearTimeout(timer)
61
- resolve(ok)
62
- }
63
- const timer = setTimeout(() => {
64
- child.kill('SIGKILL')
65
- finish(false)
66
- }, timeoutMs + 5_000)
67
- timer.unref()
68
- child.once('error', () => finish(false))
69
- child.once('exit', (code, signal) => {
70
- if (signal) finish(false)
71
- else finish(code === 0)
72
- })
73
- })
40
+ async function probeBluetoothInProcess(timeoutMs) {
41
+ /** @type {any} */
42
+ let noble = null
43
+ try {
44
+ noble = await loadNoble()
45
+ if (!noble?.startScanningAsync) return false
46
+ await waitPoweredOn(noble, timeoutMs)
47
+ return true
48
+ }
49
+ catch {
50
+ return false
51
+ }
52
+ finally {
53
+ if (noble) try { noble.stop() } catch { /* ignore */ }
54
+ }
74
55
  }
75
56
 
76
57
  /**
77
- * 探测 BT 栈是否真正可用(硬件迹象 → 子进程 load → poweredOn)。
58
+ * 探测 BT 栈是否真正可用(硬件迹象 → 进程内 load → poweredOn → stop)。
78
59
  * 任一步失败返回 false;不抛错。
79
60
  * @param {number} [timeoutMs=3000] waitPoweredOn 超时
80
61
  * @returns {Promise<boolean>} 可用为 true
@@ -86,7 +67,7 @@ export async function canUseBluetoothRuntime(timeoutMs = 3000) {
86
67
  return false
87
68
  }
88
69
  if (!probeInflight)
89
- probeInflight = probeBluetoothInSubprocess(timeoutMs)
70
+ probeInflight = probeBluetoothInProcess(timeoutMs)
90
71
  .then(ok => {
91
72
  cachedRuntimeOk = ok
92
73
  return ok
@@ -15,9 +15,12 @@ import { fanoutFedFetch } from './fetch_fanout.mjs'
15
15
  import { normalizeFileManifest } from './manifest.mjs'
16
16
  import { shouldPreferIncomingPublicManifest } from './public_manifest.mjs'
17
17
 
18
+ const DEFAULT_MANIFEST_FETCH_TIMEOUT_MS = 8000
19
+
18
20
  /**
19
21
  * 拉取公开 manifest;默认不写盘。`cache: true` 或 `cachePublicManifest` 才缓存。
20
- * @param {{ username: string, ownerEntityHash: string, logicalPath: string, cache?: boolean }} context - 拉取上下文
22
+ * 本地已有 publicSig 时仍会 fanout 再校验,按 publishedAt 择新;超时则回退本地。
23
+ * @param {{ username: string, ownerEntityHash: string, logicalPath: string, cache?: boolean, timeoutMs?: number }} context - 拉取上下文
21
24
  * @returns {Promise<import('./manifest.mjs').FileManifest | null>} 验签后的 manifest,失败为 null
22
25
  */
23
26
  export async function fetchPublicManifest(context) {
@@ -27,16 +30,19 @@ export async function fetchPublicManifest(context) {
27
30
  if (!ownerEntityHash || !logicalPath || !username) return null
28
31
 
29
32
  const local = await loadFileManifest(ownerEntityHash, logicalPath)
30
- if (local?.transferKeyDescriptor?.type === 'public' && local?.meta?.publicSig)
31
- return local
33
+ const hasLocalPublic = local?.transferKeyDescriptor?.type === 'public' && !!local?.meta?.publicSig
32
34
 
33
- if (pendingManifestFetches.size >= MAX_PENDING_MANIFEST_FETCHES) return null
35
+ if (pendingManifestFetches.size >= MAX_PENDING_MANIFEST_FETCHES)
36
+ return hasLocalPublic ? local : null
34
37
 
38
+ const timeoutMs = Number(context.timeoutMs) > 0
39
+ ? Number(context.timeoutMs)
40
+ : DEFAULT_MANIFEST_FETCH_TIMEOUT_MS
35
41
  const requestId = randomUUID()
36
42
  const { done } = registerManifestFetchWait(
37
43
  requestId,
38
44
  manifestFetchExpectedKey(ownerEntityHash, logicalPath),
39
- 8000,
45
+ timeoutMs,
40
46
  )
41
47
  const { nodeHash } = await resolveNodeHash(username)
42
48
  const payload = {
@@ -47,11 +53,14 @@ export async function fetchPublicManifest(context) {
47
53
  }
48
54
  await fanoutFedFetch(username, 'fed_manifest_get', payload)
49
55
  const result = await done
50
- if (!result) return null
51
56
 
52
- if (context.cache === true)
53
- await cachePublicManifest(ownerEntityHash, logicalPath, result)
54
- return result
57
+ if (result && (!hasLocalPublic || shouldPreferIncomingPublicManifest(local, result))) {
58
+ if (context.cache === true)
59
+ await cachePublicManifest(ownerEntityHash, logicalPath, result)
60
+ return result
61
+ }
62
+ if (hasLocalPublic) return local
63
+ return null
55
64
  }
56
65
 
57
66
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -1,21 +0,0 @@
1
- /**
2
- * 子进程 BT 可用性探测:load → poweredOn → stop → exit。
3
- * 由 `canUseBluetoothRuntime` spawn;勿在父进程直接跑 waitPoweredOn(会拖住事件循环)。
4
- * argv[2]:waitPoweredOn 超时毫秒(默认 3000)。
5
- */
6
- import process from 'node:process'
7
-
8
- import { loadNoble, waitPoweredOn } from './runtime.mjs'
9
-
10
- const timeoutMs = Number(process.argv[2] || 3000)
11
-
12
- try {
13
- const noble = await loadNoble()
14
- if (!noble?.startScanningAsync) process.exit(2)
15
- await waitPoweredOn(noble, timeoutMs)
16
- try { noble.stop() } catch { /* Windows 上 stop 偶发崩;子进程反正要退出 */ }
17
- process.exit(0)
18
- }
19
- catch {
20
- process.exit(1)
21
- }