@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 +1 -1
- package/discovery/bt/index.mjs +4 -12
- package/discovery/bt/runtime.mjs +55 -5
- package/link/providers/ble_gatt.mjs +3 -14
- package/package.json +1 -1
- package/transport/link_registry.mjs +3 -1
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
|
|
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
|
|
package/discovery/bt/index.mjs
CHANGED
|
@@ -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
|
-
* 探测本机
|
|
19
|
-
* @returns {Promise<boolean>}
|
|
18
|
+
* 探测本机 BT discovery 是否可用;失败则回落其它 discovery,不抛错。
|
|
19
|
+
* @returns {Promise<boolean>} 可用为 true
|
|
20
20
|
*/
|
|
21
21
|
export async function canUseBluetoothDiscovery() {
|
|
22
|
-
|
|
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
|
/**
|
package/discovery/bt/runtime.mjs
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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
|
|
18
|
+
* 探测 BLE GATT 数据链路是否可用;失败则回落其它 link provider。
|
|
21
19
|
* @returns {Promise<boolean>} 可用为 true
|
|
22
20
|
*/
|
|
23
21
|
export async function canUseBleGattLink() {
|
|
24
|
-
|
|
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
|
@@ -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
|
-
|
|
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
|