@cero-base/core 1.12.0 → 1.13.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.
@@ -1,42 +0,0 @@
1
- import { Duplex } from 'streamx'
2
-
3
- // A single GATT write/notify caps at ATT_MTU − 3 ≈ 182 bytes; 150 stays under
4
- // that without negotiating an MTU.
5
- const PAYLOAD = 150
6
-
7
- /**
8
- * A dumb byte-carrying duplex for the GATT transport. Framing and session logic
9
- * live in BLETransport; this only fragments outbound writes to fit a GATT
10
- * write and pushes inbound payload bytes.
11
- *
12
- * @extends Duplex
13
- */
14
- export class GattStream extends Duplex {
15
- /**
16
- * @param {object} opts
17
- * @param {(buffer: Uint8Array) => Promise<void>} opts.send Transmit one payload piece (transport frames it).
18
- */
19
- constructor({ send } = {}) {
20
- super()
21
- this._send = send
22
- }
23
-
24
- async _write(chunk, cb) {
25
- try {
26
- for (let offset = 0; offset < chunk.byteLength; offset += PAYLOAD) {
27
- await this._send(chunk.subarray(offset, offset + PAYLOAD))
28
- }
29
- cb(null)
30
- } catch (err) {
31
- cb(err)
32
- }
33
- }
34
-
35
- receive(buffer) {
36
- this.push(buffer)
37
- }
38
-
39
- remoteEnd() {
40
- this.push(null)
41
- }
42
- }
@@ -1,90 +0,0 @@
1
- import { Duplex } from 'streamx'
2
- import b4a from 'b4a'
3
-
4
- /**
5
- * Byte pipe over an L2CAP channel — the GattStream-shaped wrapper so
6
- * BLETransport can treat both pipes identically. The channel is already a
7
- * reliable ordered duplex with credit-based flow control, so no framing or
8
- * fragmentation is needed.
9
- *
10
- * @extends Duplex
11
- */
12
- export class L2CAPStream extends Duplex {
13
- /**
14
- * @param {any} channel bare-bluetooth L2CAPChannel (a duplex).
15
- */
16
- constructor(channel) {
17
- super()
18
- this.channel = channel
19
-
20
- channel.on('data', (data) => {
21
- // propagate read backpressure: hold the channel while our buffer is full
22
- if (!this.push(b4a.from(data))) channel.pause()
23
- })
24
- channel.on('end', () => this.push(null))
25
- channel.on('error', () => this.destroy())
26
- channel.on('close', () => this.destroy())
27
- }
28
-
29
- _read(cb) {
30
- this.channel.resume()
31
- cb(null)
32
- }
33
-
34
- _write(chunk, cb) {
35
- if (this.channel.write(chunk)) cb(null)
36
- else this.channel.once('drain', () => cb(null))
37
- }
38
-
39
- receive(buffer) {
40
- this.push(buffer)
41
- }
42
-
43
- remoteEnd() {
44
- this.push(null)
45
- }
46
-
47
- _destroy(cb) {
48
- try {
49
- this.channel.destroy()
50
- } catch {
51
- // channel may already be gone
52
- }
53
- cb(null)
54
- }
55
- }
56
-
57
- /**
58
- * Accumulate the session-id preamble a central writes first on a fresh
59
- * channel. Resolves { id, rest } — id as a hex string and rest being any bytes
60
- * delivered past it (the channel is a byte stream; the first payload bytes may
61
- * arrive glued to the id). Owns the channel's data events until then so no
62
- * bytes race past the switchover. Timeout resolves { id: null, rest: null }.
63
- *
64
- * @param {any} channel
65
- * @param {number} idLen
66
- * @param {number} timeout
67
- * @returns {Promise<{ id: string | null, rest: Uint8Array | null }>}
68
- */
69
- export function readIdPreamble(channel, idLen, timeout) {
70
- return new Promise((resolve) => {
71
- let buf = b4a.alloc(0)
72
-
73
- const finish = (id, rest) => {
74
- clearTimeout(timer)
75
- channel.removeListener('data', onData)
76
- resolve({ id, rest })
77
- }
78
-
79
- const onData = (data) => {
80
- buf = b4a.concat([buf, b4a.from(data)])
81
- if (buf.byteLength >= idLen) {
82
- finish(b4a.toString(buf.subarray(0, idLen), 'hex'), buf.subarray(idLen))
83
- }
84
- }
85
-
86
- const timer = setTimeout(() => finish(null, null), timeout)
87
- if (timer.unref) timer.unref()
88
- channel.on('data', onData)
89
- })
90
- }
@@ -1,180 +0,0 @@
1
- /**
2
- * Derive a stable 128-bit BLE service UUID from a topic. Only devices that
3
- * compute the same UUID (same channel / same invite) ever discover each other.
4
- *
5
- * @param {Uint8Array} topic
6
- * @param {string} [tag] Namespace so channel and invite meshes never collide.
7
- * @returns {string}
8
- */
9
- export function toServiceUUID(topic: Uint8Array, tag?: string): string;
10
- /**
11
- * Dual-role BLE transport: advertises + scans one service UUID, opens a GATT
12
- * byte-stream to each discovered peer, and feeds it into `network.inject`. From
13
- * there replication and pairing are transport-agnostic (see Network.inject).
14
- *
15
- * The server adds one data characteristic (write + notify) and advertises. The
16
- * central connects, discovers the characteristic, subscribes, then framed bytes
17
- * flow both ways — central→server as GATT writes, server→central as
18
- * notifications — each tagged with an 8-byte session id. `backend` is
19
- * bare-bluetooth in production and a mock in tests.
20
- *
21
- * @extends ReadyResource
22
- */
23
- export class BLETransport extends ReadyResource {
24
- /**
25
- * @param {object} opts
26
- * @param {any} opts.backend bare-bluetooth-shaped module (Central, Server, Service, Characteristic).
27
- * @param {import('../index.js').Network} opts.network
28
- * @param {Uint8Array} opts.uuid The 32-byte topic the service UUID derives from.
29
- * @param {Uint8Array} opts.nodeId Stable local id (identity/device key) for the initiate tie-break.
30
- * @param {string} [opts.tag] UUID namespace (channel mesh vs invite mesh).
31
- * @param {number} [opts.maxOutbound] Max concurrent outbound dials/links; gossip covers the rest.
32
- * @param {number} [opts.maxInbound] Max concurrent inbound sessions; newcomers past this are refused.
33
- * @param {{ scanMode?: any }} [opts.scanOptions] Platform scan options (e.g. Android low-power).
34
- * @param {boolean} [opts.keepLinks] On close, stop the radio but leave established links alive (invite rendezvous: the link outlives the QR and carries the initial replication).
35
- * @param {string} [opts.name] Local app-user display name, sent to peers over a hello frame.
36
- * @param {'l2cap' | 'gatt'} [opts.pipe] Data pipe: 'l2cap' (default — a real channel per session, several times faster) or 'gatt' (framed characteristic stream). Both peers must match.
37
- * @param {{ timeout?: number }} [opts.l2cap] Deadline for an l2cap channel open.
38
- */
39
- constructor({ backend, network, uuid, nodeId, tag, maxOutbound, maxInbound, scanOptions, keepLinks, name, pipe, l2cap }: {
40
- backend: any;
41
- network: import("../index.js").Network;
42
- uuid: Uint8Array;
43
- nodeId: Uint8Array;
44
- tag?: string;
45
- maxOutbound?: number;
46
- maxInbound?: number;
47
- scanOptions?: {
48
- scanMode?: any;
49
- };
50
- keepLinks?: boolean;
51
- name?: string;
52
- pipe?: "l2cap" | "gatt";
53
- l2cap?: {
54
- timeout?: number;
55
- };
56
- });
57
- backend: any;
58
- network: import("../index.js").Network;
59
- name: string;
60
- nodeId: Uint8Array<ArrayBufferLike>;
61
- nodeHex: any;
62
- serviceUUID: string;
63
- maxOutbound: number;
64
- maxInbound: number;
65
- scanOptions: {
66
- scanMode?: any;
67
- };
68
- keepLinks: boolean;
69
- pipe: "l2cap" | "gatt";
70
- _l2capTimeout: number;
71
- state: string;
72
- central: any;
73
- server: any;
74
- _dataChar: any;
75
- /** the published l2cap listener's psm, advertised to centrals over hello */
76
- _psm: any;
77
- /** id → { id, stream, conn, name, pipeTimer } for server-side (peripheral) sessions */
78
- _sessions: Map<any, any>;
79
- /** serialized server notify queue: { frame, resolve, reject } */
80
- _notifyQueue: any[];
81
- _scanning: boolean;
82
- _advertising: boolean;
83
- _serviceAdded: boolean;
84
- /** peripheral id → per-peer dial state { timer, linked, coolUntil, failures, peerKey, peripheral } */
85
- _devices: Map<any, any>;
86
- /** rate-limited discoveries held for the next dial window */
87
- _candidates: Map<any, any>;
88
- _dialTimer: any;
89
- /** last central.connect timestamp — global inter-dial rate limit */
90
- _lastDial: number;
91
- _scanTimer: any;
92
- _cyclePending: boolean;
93
- _suspended: boolean;
94
- /** live injected links keyed by remote node id hex */
95
- peers: Map<any, any>;
96
- /**
97
- * Whether we should be the one to open the connection to `peerNodeId`. The
98
- * lexicographically smaller id initiates; the larger waits — so a pair
99
- * connects once, not twice. Equal (our own reflection) → false.
100
- *
101
- * @param {Uint8Array} peerNodeId
102
- * @returns {boolean}
103
- */
104
- shouldInitiate(peerNodeId: Uint8Array): boolean;
105
- get linkCount(): number;
106
- _device(id: any): any;
107
- _prune(id: any): void;
108
- _startServer(Service: any, Characteristic: any): void;
109
- _publishListener(): void;
110
- _unpublishListener(): void;
111
- _cycleListener(): void;
112
- _maybeAdvertise(): void;
113
- _onWriteRequests(requests: any): void;
114
- _onServerFrame(data: any): void;
115
- _openServerGatt(session: any): void;
116
- _onServerChannel(channel: any): Promise<void>;
117
- _bindServerStream(session: any, stream: any): void;
118
- _reapSession(id: any, session: any): void;
119
- _closeServerSession(id: any): void;
120
- _notifyClose(id: any): void;
121
- _helloPayload(): any;
122
- /**
123
- * @param {Uint8Array} payload
124
- * @returns {{ name: string, psm: number | null } | null}
125
- */
126
- _parseHello(payload: Uint8Array): {
127
- name: string;
128
- psm: number | null;
129
- } | null;
130
- _applyPeerName(session: any, payload: any): void;
131
- _enqueueNotify(f: any): Promise<any>;
132
- _drainNotify(): void;
133
- _startScan(): void;
134
- _armScanRestart(): void;
135
- _stopScan(): void;
136
- /**
137
- * A radio power cycle invalidates the GATT service, advertising, scans,
138
- * subscriptions and every open link, but the bookkeeping flags survive —
139
- * without a reset the device never re-registers or re-advertises and goes
140
- * dark until the app-level toggle is cycled. Reset so the poweredOn
141
- * handlers bootstrap everything from scratch.
142
- */
143
- _onRadioDown(): void;
144
- _onState(raw: any): void;
145
- _onDiscover(peripheral: any): void;
146
- _flushCandidates(): void;
147
- _clearCandidates(): void;
148
- _onConnect(peripheral: any): void;
149
- _startCentralSession(peripheral: any, char: any): void;
150
- _openCentralGatt(peripheral: any, sess: any): void;
151
- _bindCentralStream(peripheral: any, sess: any, stream: any): void;
152
- _closeCentralSession(peripheral: any, sess: any): void;
153
- _openCentralL2CAP(peripheral: any, sess: any, psm: any): Promise<void>;
154
- _openChannel(peripheral: any, psm: any): Promise<any>;
155
- _onCentralNotify(peripheral: any, data: any): void;
156
- _centralSend(peripheral: any, char: any, f: any): any;
157
- _writeOnce(peripheral: any, char: any, f: any): Promise<any>;
158
- _abortDial(peripheral: any, _reason: any): void;
159
- _clearDial(id: any): void;
160
- _isDialing(): boolean;
161
- _onCentralError(err: any): void;
162
- _onChannel(stream: any, isInitiator: any, peripheralId: any): any;
163
- _track(conn: any, peripheralId: any, isInitiator: any): void;
164
- _untrack(conn: any): void;
165
- _sayGoodbye(): Promise<void>;
166
- /**
167
- * Pause radio activity but KEEP the Server/Central instances and the
168
- * registered GATT service alive — the toggle-friendly counterpart to _close.
169
- * CoreBluetooth managers can't be destroy()ed (native double-free), so one
170
- * transport is reused across toggles rather than recreated. Idempotent.
171
- */
172
- suspend(): Promise<void>;
173
- /**
174
- * Restart advertising + scanning on the SAME Server/Central. `_serviceAdded`
175
- * is still true (the service was never removed) so advertising resumes
176
- * immediately. Safe to call repeatedly; no-op once closing/closed.
177
- */
178
- resume(): void;
179
- }
180
- import ReadyResource from 'ready-resource';
@@ -1,21 +0,0 @@
1
- /**
2
- * A dumb byte-carrying duplex for the GATT transport. Framing and session logic
3
- * live in BLETransport; this only fragments outbound writes to fit a GATT
4
- * write and pushes inbound payload bytes.
5
- *
6
- * @extends Duplex
7
- */
8
- export class GattStream extends Duplex<import("streamx").DuplexEvents> {
9
- /**
10
- * @param {object} opts
11
- * @param {(buffer: Uint8Array) => Promise<void>} opts.send Transmit one payload piece (transport frames it).
12
- */
13
- constructor({ send }?: {
14
- send: (buffer: Uint8Array) => Promise<void>;
15
- });
16
- _send: (buffer: Uint8Array) => Promise<void>;
17
- _write(chunk: any, cb: any): Promise<void>;
18
- receive(buffer: any): void;
19
- remoteEnd(): void;
20
- }
21
- import { Duplex } from 'streamx';
@@ -1,37 +0,0 @@
1
- /**
2
- * Accumulate the session-id preamble a central writes first on a fresh
3
- * channel. Resolves { id, rest } — id as a hex string and rest being any bytes
4
- * delivered past it (the channel is a byte stream; the first payload bytes may
5
- * arrive glued to the id). Owns the channel's data events until then so no
6
- * bytes race past the switchover. Timeout resolves { id: null, rest: null }.
7
- *
8
- * @param {any} channel
9
- * @param {number} idLen
10
- * @param {number} timeout
11
- * @returns {Promise<{ id: string | null, rest: Uint8Array | null }>}
12
- */
13
- export function readIdPreamble(channel: any, idLen: number, timeout: number): Promise<{
14
- id: string | null;
15
- rest: Uint8Array | null;
16
- }>;
17
- /**
18
- * Byte pipe over an L2CAP channel — the GattStream-shaped wrapper so
19
- * BLETransport can treat both pipes identically. The channel is already a
20
- * reliable ordered duplex with credit-based flow control, so no framing or
21
- * fragmentation is needed.
22
- *
23
- * @extends Duplex
24
- */
25
- export class L2CAPStream extends Duplex<import("streamx").DuplexEvents> {
26
- /**
27
- * @param {any} channel bare-bluetooth L2CAPChannel (a duplex).
28
- */
29
- constructor(channel: any);
30
- channel: any;
31
- _read(cb: any): void;
32
- _write(chunk: any, cb: any): void;
33
- receive(buffer: any): void;
34
- remoteEnd(): void;
35
- _destroy(cb: any): void;
36
- }
37
- import { Duplex } from 'streamx';