@steve02081504/fount-p2p 0.0.9 → 0.0.10

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 CHANGED
@@ -54,7 +54,7 @@ During development: `node scripts/check-imports.mjs` validates relative imports.
54
54
 
55
55
  ## Optional dependencies
56
56
 
57
- - `@stoprocent/noble` / `@stoprocent/bleno` — Bluetooth discovery (attempted by default; skipped when not installed)
57
+ - `@stoprocent/noble` / `@stoprocent/bleno` — Bluetooth (optionalDependencies). Unavailable when there is no adapter, load fails, or the radio never reaches poweredOn — other discovery/link paths continue.
58
58
 
59
59
  Group chunk remote storage (S3, etc.) is implemented by the shell as `GroupStoragePlugin` and injected; see `node/storage_plugins.mjs` for the local reference implementation.
60
60
 
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
 
3
3
  import { getBtPeerHint } from './peer_hints.mjs'
4
- import { loadBleno, loadNoble, resolveBtRole, waitPoweredOn } from './runtime.mjs'
4
+ import { canUseBluetoothRuntime, loadBleno, loadNoble, resolveBtRole, waitPoweredOn } from './runtime.mjs'
5
5
 
6
6
  /** 重导出 waitPoweredOn,供 discovery 调用方使用。 */
7
7
  export { waitPoweredOn } from './runtime.mjs'
@@ -15,19 +15,11 @@ const MAX_SIGNAL_BLOB_BYTES = 8 * 1024
15
15
  const PERIPHERAL_RESCAN_MS = 15_000
16
16
 
17
17
  /**
18
- * 探测本机 noble 运行时是否具备 BT 扫描所需 API。
19
- * @returns {Promise<boolean>} noble 可加载且具备 startScanningAsync 与 waitForPoweredOn(Async) 时为 true
18
+ * 探测本机 BT discovery 是否可用;失败则回落其它 discovery,不抛错。
19
+ * @returns {Promise<boolean>} 可用为 true
20
20
  */
21
21
  export async function canUseBluetoothDiscovery() {
22
- try {
23
- const noble = await loadNoble()
24
- if (typeof noble.startScanningAsync !== 'function') return false
25
- const wait = noble.waitForPoweredOnAsync ?? noble.waitForPoweredOn
26
- return typeof wait === 'function'
27
- }
28
- catch {
29
- return false
30
- }
22
+ return canUseBluetoothRuntime()
31
23
  }
32
24
 
33
25
  /**
@@ -1,3 +1,4 @@
1
+ import { existsSync, readdirSync } from 'node:fs'
1
2
  import process from 'node:process'
2
3
 
3
4
  /**
@@ -13,20 +14,69 @@ export function resolveBtRole() {
13
14
  }
14
15
 
15
16
  /**
16
- * 加载 Noble BLE central。
17
+ * 廉价硬件探测:明确无适配器时跳过 noble/bleno import(加载本身会拉起 native,无适配器时常在 teardown SIGSEGV)。
18
+ * @returns {boolean | null} true=有迹象,false=明确无,null=未知(继续尝试加载,失败则回落)
19
+ */
20
+ export function probeBluetoothHardware() {
21
+ if (process.platform === 'linux') try {
22
+ const dir = '/sys/class/bluetooth'
23
+ if (!existsSync(dir)) return false
24
+ return readdirSync(dir).some(name => name && !name.startsWith('.'))
25
+ }
26
+ catch {
27
+ return false
28
+ }
29
+ return null
30
+ }
31
+
32
+ /** @type {boolean | null} canUseBluetoothRuntime 缓存 */
33
+ let cachedRuntimeOk = null
34
+
35
+ /**
36
+ * 探测 BT 栈是否真正可用(有适配器迹象 → 能 load → poweredOn)。
37
+ * 任一步失败返回 false,调用方回落其它 discovery/link;不抛错。
38
+ * @param {number} [timeoutMs=3000] waitPoweredOn 超时
39
+ * @returns {Promise<boolean>} 可用为 true
40
+ */
41
+ export async function canUseBluetoothRuntime(timeoutMs = 3000) {
42
+ if (cachedRuntimeOk !== null) return cachedRuntimeOk
43
+ if (probeBluetoothHardware() === false) {
44
+ cachedRuntimeOk = false
45
+ return false
46
+ }
47
+ try {
48
+ const noble = await loadNoble()
49
+ if (!noble.startScanningAsync) {
50
+ cachedRuntimeOk = false
51
+ return false
52
+ }
53
+ await waitPoweredOn(noble, timeoutMs)
54
+ cachedRuntimeOk = true
55
+ }
56
+ catch {
57
+ cachedRuntimeOk = false
58
+ }
59
+ return cachedRuntimeOk
60
+ }
61
+
62
+ /**
63
+ * 加载 Noble BLE central。明确无适配器时直接抛错,避免无意义的 native import。
17
64
  * @returns {Promise<any>} noble 运行时
18
65
  */
19
66
  export async function loadNoble() {
67
+ if (probeBluetoothHardware() === false)
68
+ throw new Error('p2p: no bluetooth adapter')
20
69
  const mod = await import('@stoprocent/noble')
21
- if (typeof mod.withBindings === 'function') return mod.withBindings('default')
22
- return mod.default ?? mod
70
+ return mod?.withBindings?.('default') || mod?.default || mod
23
71
  }
24
72
 
25
73
  /**
26
- * 加载 Bleno BLE peripheral。
74
+ * 加载 Bleno BLE peripheral。明确无适配器时直接抛错,避免无意义的 native import
27
75
  * @returns {Promise<any>} bleno 运行时
28
76
  */
29
77
  export async function loadBleno() {
78
+ if (probeBluetoothHardware() === false)
79
+ throw new Error('p2p: no bluetooth adapter')
30
80
  const mod = await import('@stoprocent/bleno')
31
81
  if (typeof mod.withBindings === 'function') return mod.withBindings('default')
32
82
  return mod.default ?? mod
@@ -40,7 +90,7 @@ export async function loadBleno() {
40
90
  */
41
91
  export async function waitPoweredOn(runtime, timeout) {
42
92
  const wait = runtime.waitForPoweredOnAsync ?? runtime.waitForPoweredOn
43
- if (typeof wait !== 'function')
93
+ if (!wait)
44
94
  throw new Error('p2p: bluetooth runtime missing waitForPoweredOn(Async)')
45
95
  return wait.call(runtime, timeout)
46
96
  }
@@ -3,7 +3,7 @@ import { randomBytes } from 'node:crypto'
3
3
 
4
4
  import { normalizeHex64 } from '../../core/hexIds.mjs'
5
5
  import { getBtPeerHint } from '../../discovery/bt/peer_hints.mjs'
6
- import { loadBleno, loadNoble, resolveBtRole, waitPoweredOn } from '../../discovery/bt/runtime.mjs'
6
+ import { canUseBluetoothRuntime, loadBleno, loadNoble, resolveBtRole, waitPoweredOn } from '../../discovery/bt/runtime.mjs'
7
7
  import { asLinkHandle, coercePipeInbound, createLinkPipe } from '../pipe.mjs'
8
8
 
9
9
  import { LINK_LEVEL_BLE_GATT } from './levels.mjs'
@@ -14,23 +14,12 @@ export const BLE_DATA_SERVICE_UUID = 'f017f017f017f017f017f017f017f019'
14
14
  export const BLE_DATA_CHAR_UUID = 'f017f017f017f017f017f017f017f01a'
15
15
  const BT_DEVICE_NAME = 'fount-bt'
16
16
 
17
- let cachedAvailable = null
18
-
19
17
  /**
20
- * 探测 BLE GATT 数据链路是否可用(至少 noble central)。
18
+ * 探测 BLE GATT 数据链路是否可用;失败则回落其它 link provider。
21
19
  * @returns {Promise<boolean>} 可用为 true
22
20
  */
23
21
  export async function canUseBleGattLink() {
24
- if (cachedAvailable !== null) return cachedAvailable
25
- try {
26
- const noble = await loadNoble()
27
- const wait = noble.waitForPoweredOnAsync ?? noble.waitForPoweredOn
28
- cachedAvailable = typeof noble.startScanningAsync === 'function' && typeof wait === 'function'
29
- }
30
- catch {
31
- cachedAvailable = false
32
- }
33
- return cachedAvailable
22
+ return canUseBluetoothRuntime()
34
23
  }
35
24
 
36
25
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -315,7 +315,9 @@ export function createLinkRegistry(options = {}) {
315
315
  /** @type {import('../link/providers/index.mjs').LinkProvider[]} */
316
316
  const listenProviders = []
317
317
  if (ownedLanTcp) listenProviders.push(ownedLanTcp)
318
- if (ownedBleGatt) listenProviders.push(ownedBleGatt)
318
+ // 无适配器时 isAvailable 应在 import native 前返回 false;勿无条件 ensureListening(会 loadBleno)。
319
+ if (ownedBleGatt && await Promise.resolve(ownedBleGatt.isAvailable()))
320
+ listenProviders.push(ownedBleGatt)
319
321
  for (const provider of await listAvailableLinkProviders()) {
320
322
  const id = String(provider.id)
321
323
  if (id.startsWith('lan_tcp') || id.startsWith('ble_gatt')) continue