@matter-server/ble-proxy 0.7.2-alpha.0-20260601-e778038 → 0.8.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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { Observable } from "@matter/main";
7
+ import { WebSocket } from "ws";
8
+ import { type BinaryFrame, type BleProxyCommandMap, type BleProxyCommandName, type BleProxyEventName } from "./BleProxyProtocol.js";
9
+ /**
10
+ * One BLE proxy client socket. Owns that client's handshake state, pending
11
+ * command map, and command/event/binary-frame plumbing. The hub
12
+ * (`BleProxyHandler`) creates one per accepted WebSocket connection.
13
+ */
14
+ export declare class BleProxyConnection {
15
+ #private;
16
+ readonly binaryFrameReceived: Observable<[frame: BinaryFrame], void>;
17
+ readonly eventReceived: Observable<[event: BleProxyEventName, data: Record<string, unknown>], void>;
18
+ /** Emitted once when this connection completes the protocol handshake. */
19
+ readonly handshakeCompleted: Observable<[], void>;
20
+ /** Emitted once when this connection is torn down (close, error, or handshake failure). */
21
+ readonly closed: Observable<[], void>;
22
+ constructor(ws: WebSocket);
23
+ get id(): string;
24
+ get connected(): boolean;
25
+ /**
26
+ * Send a typed command to this client and wait for the response.
27
+ * Rejects if the connection is not ready or the client disconnects first.
28
+ */
29
+ sendCommand<C extends BleProxyCommandName>(command: C, ...rest: BleProxyCommandMap[C]["args"] extends undefined ? [] : [args: BleProxyCommandMap[C]["args"]]): Promise<BleProxyCommandMap[C]["result"]>;
30
+ sendBinaryFrame(opcode: number, connectionHandle: number, payload: Uint8Array): void;
31
+ /** Close the socket if open, reject all pending commands, emit `closed` once. */
32
+ close(): void;
33
+ }
34
+ //# sourceMappingURL=BleProxyConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BleProxyConnection.d.ts","sourceRoot":"","sources":["../../src/BleProxyConnection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAyB,UAAU,EAA8B,MAAM,cAAc,CAAC;AAE7F,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAIH,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAKzB,MAAM,uBAAuB,CAAC;AAiC/B;;;;GAIG;AACH,qBAAa,kBAAkB;;IAY3B,QAAQ,CAAC,mBAAmB,yCAA0C;IACtE,QAAQ,CAAC,aAAa,8EAA+E;IACrG,0EAA0E;IAC1E,QAAQ,CAAC,kBAAkB,uBAAwB;IACnD,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,uBAAwB;IAEvC,YAAY,EAAE,EAAE,SAAS,EA+BxB;IAED,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;OAGG;IACG,WAAW,CAAC,CAAC,SAAS,mBAAmB,EAC3C,OAAO,EAAE,CAAC,EACV,GAAG,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GACtG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CA0B1C;IAED,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAQnF;IAkGD,iFAAiF;IACjF,KAAK,IAAI,IAAI,CAKZ;CAeJ"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { createPromise, Logger, Observable, Seconds, Time, withTimeout } from "@matter/main";
7
+ import { Mark } from "@matter/main/protocol";
8
+ import { WebSocket } from "ws";
9
+ import {
10
+ BLE_PROXY_PROTOCOL_VERSION,
11
+ decodeBinaryFrame,
12
+ encodeBinaryFrame
13
+ } from "./BleProxyProtocol.js";
14
+ const logger = Logger.get("BleProxyConnection");
15
+ const frameHead = (payload) => Array.from(payload.subarray(0, 8)).map((b) => b.toString(16).padStart(2, "0")).join("");
16
+ let connectionIdCounter = 0;
17
+ function generateConnectionId() {
18
+ const id = connectionIdCounter;
19
+ connectionIdCounter = connectionIdCounter + 1 & 65535;
20
+ return `ble${id.toString(16)}`;
21
+ }
22
+ const HANDSHAKE_TIMEOUT = Seconds(10);
23
+ const COMMAND_TIMEOUT = Seconds(60);
24
+ class BleProxyConnection {
25
+ #ws;
26
+ #id = generateConnectionId();
27
+ #handshakeComplete = false;
28
+ #closedEmitted = false;
29
+ #pendingCommands = /* @__PURE__ */ new Map();
30
+ /** Command ID counter. Rolls over at 0xFFFF to stay in safe integer range. */
31
+ #nextCommandId = 0;
32
+ binaryFrameReceived = new Observable();
33
+ eventReceived = new Observable();
34
+ /** Emitted once when this connection completes the protocol handshake. */
35
+ handshakeCompleted = new Observable();
36
+ /** Emitted once when this connection is torn down (close, error, or handshake failure). */
37
+ closed = new Observable();
38
+ constructor(ws) {
39
+ this.#ws = ws;
40
+ logger.info(`[${this.#id}] BLE proxy connection established, waiting for handshake`);
41
+ const handshakeTimer = Time.getTimer("BLE proxy handshake timeout", HANDSHAKE_TIMEOUT, () => {
42
+ if (!this.#handshakeComplete) {
43
+ logger.warn(`[${this.#id}] Handshake timeout - closing connection`);
44
+ ws.close(4001, "Handshake timeout");
45
+ this.#cleanup();
46
+ }
47
+ }).start();
48
+ ws.on("message", (data, isBinary) => {
49
+ if (isBinary) {
50
+ this.#handleBinaryMessage(data);
51
+ } else {
52
+ this.#handleTextMessage(data.toString(), handshakeTimer);
53
+ }
54
+ });
55
+ ws.on("close", () => {
56
+ handshakeTimer.stop();
57
+ logger.info(`[${this.#id}] BLE proxy connection closed`);
58
+ this.#cleanup();
59
+ });
60
+ ws.on("error", (err) => {
61
+ logger.error(`[${this.#id}] BLE proxy WebSocket error`, err);
62
+ handshakeTimer.stop();
63
+ this.#cleanup();
64
+ });
65
+ }
66
+ get id() {
67
+ return this.#id;
68
+ }
69
+ get connected() {
70
+ return this.#handshakeComplete && this.#ws.readyState === WebSocket.OPEN;
71
+ }
72
+ /**
73
+ * Send a typed command to this client and wait for the response.
74
+ * Rejects if the connection is not ready or the client disconnects first.
75
+ */
76
+ async sendCommand(command, ...rest) {
77
+ if (!this.connected) {
78
+ throw new Error("BLE proxy client not connected");
79
+ }
80
+ const args = rest[0];
81
+ const id = this.#nextCommandId;
82
+ this.#nextCommandId = this.#nextCommandId + 1 & 65535;
83
+ const message = { id, command };
84
+ if (args !== void 0) {
85
+ message.args = args;
86
+ }
87
+ const { promise, resolver, rejecter } = createPromise();
88
+ this.#pendingCommands.set(id, { resolver, rejecter });
89
+ this.#ws.send(JSON.stringify(message), (err) => {
90
+ if (err) {
91
+ this.#pendingCommands.delete(id);
92
+ rejecter(new Error(`Failed to send command ${command}: ${err.message}`));
93
+ }
94
+ });
95
+ return withTimeout(COMMAND_TIMEOUT, promise, () => this.#pendingCommands.delete(id));
96
+ }
97
+ sendBinaryFrame(opcode, connectionHandle, payload) {
98
+ if (!this.connected) {
99
+ throw new Error("BLE proxy client not connected");
100
+ }
101
+ logger.debug(
102
+ `[${this.#id}] [FRAME] ${Mark.OUTBOUND} opcode=${opcode} handle=${connectionHandle} len=${payload.length} head=${frameHead(payload)}`
103
+ );
104
+ this.#ws.send(encodeBinaryFrame(opcode, connectionHandle, payload));
105
+ }
106
+ #handleTextMessage(raw, handshakeTimer) {
107
+ let parsed;
108
+ try {
109
+ parsed = JSON.parse(raw);
110
+ } catch {
111
+ logger.warn(`[${this.#id}] Received invalid JSON from BLE proxy client`);
112
+ return;
113
+ }
114
+ const msg = parsed;
115
+ if (!this.#handshakeComplete) {
116
+ this.#handleHandshake(msg, handshakeTimer);
117
+ return;
118
+ }
119
+ if ("id" in msg && "success" in msg) {
120
+ this.#handleResponse(msg);
121
+ return;
122
+ }
123
+ if ("event" in msg && "data" in msg) {
124
+ const evt = msg;
125
+ this.eventReceived.emit(evt.event, evt.data);
126
+ return;
127
+ }
128
+ logger.warn(`[${this.#id}] Received unknown message from BLE proxy client:`, msg);
129
+ }
130
+ #handleHandshake(msg, handshakeTimer) {
131
+ if (msg.type !== "hello") {
132
+ logger.warn(`[${this.#id}] Expected hello message, got: ${JSON.stringify(msg)}`);
133
+ this.#ws.close(4002, "Expected hello message");
134
+ this.#cleanup();
135
+ return;
136
+ }
137
+ const hello = msg;
138
+ handshakeTimer.stop();
139
+ if (hello.version !== BLE_PROXY_PROTOCOL_VERSION) {
140
+ logger.warn(
141
+ `[${this.#id}] BLE proxy client version ${hello.version} not supported (server supports ${BLE_PROXY_PROTOCOL_VERSION})`
142
+ );
143
+ this.#ws.send(
144
+ JSON.stringify({
145
+ type: "hello_response",
146
+ version: BLE_PROXY_PROTOCOL_VERSION,
147
+ error: "unsupported_version",
148
+ message: `Server supports protocol version ${BLE_PROXY_PROTOCOL_VERSION}, client sent version ${hello.version}`
149
+ })
150
+ );
151
+ this.#ws.close(4003, "Unsupported protocol version");
152
+ this.#cleanup();
153
+ return;
154
+ }
155
+ this.#handshakeComplete = true;
156
+ this.#ws.send(JSON.stringify({ type: "hello_response", version: BLE_PROXY_PROTOCOL_VERSION }));
157
+ logger.info(`[${this.#id}] BLE proxy handshake complete (version ${BLE_PROXY_PROTOCOL_VERSION})`);
158
+ this.handshakeCompleted.emit();
159
+ }
160
+ #handleResponse(msg) {
161
+ const pending = this.#pendingCommands.get(msg.id);
162
+ if (!pending) {
163
+ logger.warn(`[${this.#id}] Received response for unknown command id ${msg.id}`);
164
+ return;
165
+ }
166
+ this.#pendingCommands.delete(msg.id);
167
+ if (msg.success) {
168
+ pending.resolver(msg.result);
169
+ } else {
170
+ pending.rejecter(new Error(`${msg.error}: ${msg.message}`));
171
+ }
172
+ }
173
+ #handleBinaryMessage(data) {
174
+ if (!this.#handshakeComplete) {
175
+ logger.warn(`[${this.#id}] Received binary frame before handshake`);
176
+ return;
177
+ }
178
+ try {
179
+ const frame = decodeBinaryFrame(new Uint8Array(data));
180
+ logger.debug(
181
+ `[${this.#id}] [FRAME] ${Mark.INBOUND} opcode=${frame.opcode} handle=${frame.connectionHandle} len=${frame.payload.length} head=${frameHead(frame.payload)}`
182
+ );
183
+ this.binaryFrameReceived.emit(frame);
184
+ } catch (err) {
185
+ logger.warn(`[${this.#id}] Failed to decode binary frame:`, err);
186
+ }
187
+ }
188
+ /** Close the socket if open, reject all pending commands, emit `closed` once. */
189
+ close() {
190
+ if (this.#ws.readyState === WebSocket.OPEN) {
191
+ this.#ws.close();
192
+ }
193
+ this.#cleanup();
194
+ }
195
+ #cleanup() {
196
+ this.#handshakeComplete = false;
197
+ for (const [id, pending] of this.#pendingCommands) {
198
+ pending.rejecter(new Error("BLE proxy client disconnected"));
199
+ this.#pendingCommands.delete(id);
200
+ }
201
+ if (!this.#closedEmitted) {
202
+ this.#closedEmitted = true;
203
+ this.closed.emit();
204
+ }
205
+ }
206
+ }
207
+ export {
208
+ BleProxyConnection
209
+ };
210
+ //# sourceMappingURL=BleProxyConnection.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/BleProxyConnection.ts"],
4
+ "mappings": "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",
5
+ "names": []
6
+ }
@@ -6,36 +6,31 @@
6
6
  import type { WebServerHandler } from "@matter-server/ws-controller";
7
7
  import { Observable } from "@matter/main";
8
8
  import type { createServer } from "node:http";
9
- import { type BinaryFrame, type BleProxyCommandMap, type BleProxyCommandName, type BleProxyEventName } from "./BleProxyProtocol.js";
9
+ import { BleProxyConnection } from "./BleProxyConnection.js";
10
+ import { type DeviceDiscoveredData, type StartScanArgs } from "./BleProxyProtocol.js";
10
11
  type HttpServer = ReturnType<typeof createServer>;
11
12
  /**
12
- * WebSocket server handler for the /ble BLE proxy endpoint.
13
+ * WebSocket server handler (hub) for the `/ble` BLE proxy endpoint.
13
14
  *
14
- * Accepts a single client connection, performs the protocol handshake,
15
- * and routes commands/events/binary frames between the proxy implementation
16
- * and the connected BLE proxy client.
15
+ * Accepts any number of proxy client connections, broadcasts scan commands to
16
+ * all of them, and tracks which client owns each discovered peripheral so that
17
+ * per-peripheral traffic routes to a single client.
17
18
  */
18
19
  export declare class BleProxyHandler implements WebServerHandler {
19
20
  #private;
20
- /** Emitted when a JSON event is received from the BLE proxy client. */
21
- readonly eventReceived: Observable<[event: BleProxyEventName, data: Record<string, unknown>], void>;
22
- /** Emitted when a binary frame is received from the BLE proxy client. */
23
- readonly binaryFrameReceived: Observable<[frame: BinaryFrame], void>;
24
- /** Emitted once a proxy client has completed the handshake and `connected` is true. */
21
+ /** Emitted whenever a connection completes its handshake. */
25
22
  readonly connectionEstablished: Observable<[], void>;
23
+ /** Emitted for each `device_discovered`, after ownership is updated. */
24
+ readonly deviceDiscovered: Observable<[data: DeviceDiscoveredData, connection: BleProxyConnection], void>;
25
+ /** Emitted once no connection is scanning anymore. */
26
+ readonly scanStopped: Observable<[reason: string], void>;
26
27
  get connected(): boolean;
27
28
  register(server: HttpServer): Promise<void>;
28
29
  unregister(): Promise<void>;
29
- /**
30
- * Send a typed command to the BLE proxy client and wait for the response.
31
- * The return type is inferred from the command name via BleProxyCommandMap.
32
- * Rejects if no client is connected or the client disconnects before responding.
33
- */
34
- sendCommand<C extends BleProxyCommandName>(command: C, ...rest: BleProxyCommandMap[C]["args"] extends undefined ? [] : [args: BleProxyCommandMap[C]["args"]]): Promise<BleProxyCommandMap[C]["result"]>;
35
- /**
36
- * Send a binary frame to the BLE proxy client.
37
- */
38
- sendBinaryFrame(opcode: number, connectionHandle: number, payload: Uint8Array): void;
30
+ /** Returns the connection that owns `address`, if it is still connected. */
31
+ getOwner(address: string): BleProxyConnection | undefined;
32
+ startScan(args: StartScanArgs): Promise<void>;
33
+ stopScan(): Promise<void>;
39
34
  }
40
35
  export {};
41
36
  //# sourceMappingURL=BleProxyHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BleProxyHandler.d.ts","sourceRoot":"","sources":["../../src/BleProxyHandler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAyB,UAAU,EAAqC,MAAM,cAAc,CAAC;AACpG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAIH,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EAKzB,MAAM,uBAAuB,CAAC;AAE/B,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAclD;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;;IAcpD,uEAAuE;IACvE,QAAQ,CAAC,aAAa,8EAA+E;IAErG,yEAAyE;IACzE,QAAQ,CAAC,mBAAmB,yCAA0C;IAEtE,uFAAuF;IACvF,QAAQ,CAAC,qBAAqB,uBAAwB;IAEtD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAEK,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EhD;IAED,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAmC1B;IAED;;;;OAIG;IACG,WAAW,CAAC,CAAC,SAAS,mBAAmB,EAC3C,OAAO,EAAE,CAAC,EACV,GAAG,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GACtG,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CA0B1C;IAED;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAMnF;CA+HJ"}
1
+ {"version":3,"file":"BleProxyHandler.d.ts","sourceRoot":"","sources":["../../src/BleProxyHandler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAU,UAAU,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAkC,KAAK,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAEtH,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAIlD;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;;IAgBpD,6DAA6D;IAC7D,QAAQ,CAAC,qBAAqB,uBAAwB;IACtD,wEAAwE;IACxE,QAAQ,CAAC,gBAAgB,iFAAkF;IAC3G,sDAAsD;IACtD,QAAQ,CAAC,WAAW,qCAAsC;IAE1D,IAAI,SAAS,IAAI,OAAO,CAKvB;IAEK,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChD;IAED,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CA4B1B;IAED,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAIxD;IAEK,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBlD;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAc9B;CAuEJ"}