@dg-kit/transport-webbluetooth 0.1.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.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # @dg-kit/transport-webbluetooth
2
+
3
+ Browser-side `DeviceClient` for DG-Lab Coyote, built on Web Bluetooth.
4
+
5
+ This package is the glue between the browser's `navigator.bluetooth.requestDevice()` chooser and `@dg-kit/protocol`. The protocol code itself is environment-agnostic; this package provides the GATT connection layer that drives it.
6
+
7
+ ## Usage
8
+
9
+ ```ts
10
+ import { CoyoteProtocolAdapter } from '@dg-kit/protocol';
11
+ import {
12
+ WebBluetoothDeviceClient,
13
+ getWebBluetoothAvailability,
14
+ } from '@dg-kit/transport-webbluetooth';
15
+
16
+ const availability = getWebBluetoothAvailability();
17
+ if (!availability.supported) {
18
+ throw new Error(availability.reason);
19
+ }
20
+
21
+ const protocol = new CoyoteProtocolAdapter();
22
+ const device = new WebBluetoothDeviceClient({ protocol });
23
+ await device.connect(); // pops the browser chooser
24
+ ```
25
+
26
+ `CoyoteProtocolAdapter` auto-detects V2 vs V3 from the connecting device's name prefix.
27
+
28
+ ## Requirements
29
+
30
+ - Browser: Chrome/Edge with Web Bluetooth (HTTPS or localhost).
31
+ - Mobile: Bluefy (iOS) / Chrome (Android).
@@ -0,0 +1,3 @@
1
+ import type { NavigatorBluetoothLike, WebBluetoothAvailability } from '@dg-kit/protocol';
2
+ export declare function getWebBluetoothAvailability(nav?: NavigatorBluetoothLike | undefined): WebBluetoothAvailability;
3
+ //# sourceMappingURL=availability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability.d.ts","sourceRoot":"","sources":["../src/availability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEzF,wBAAgB,2BAA2B,CACzC,GAAG,GAAE,sBAAsB,GAAG,SAEsB,GACnD,wBAAwB,CAa1B"}
@@ -0,0 +1,15 @@
1
+ export function getWebBluetoothAvailability(nav = typeof navigator === 'undefined'
2
+ ? undefined
3
+ : navigator) {
4
+ if (!nav) {
5
+ return { supported: false, reason: '当前环境中无法访问浏览器导航对象' };
6
+ }
7
+ if (!nav.bluetooth) {
8
+ return {
9
+ supported: false,
10
+ reason: '当前环境不支持 Web Bluetooth,请使用 Chrome 或 Edge,并通过 HTTPS 或 localhost 访问',
11
+ };
12
+ }
13
+ return { supported: true };
14
+ }
15
+ //# sourceMappingURL=availability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"availability.js","sourceRoot":"","sources":["../src/availability.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,2BAA2B,CACzC,MAA0C,OAAO,SAAS,KAAK,WAAW;IACxE,CAAC,CAAC,SAAS;IACX,CAAC,CAAE,SAA+C;IAEpD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAC1D,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACnB,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,kEAAkE;SAC3E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { DeviceClient } from '@dg-kit/core';
2
+ import type { DeviceCommand, DeviceCommandResult, DeviceState } from '@dg-kit/core';
3
+ import { type NavigatorBluetoothLike, type RequestDeviceOptionsLike, type WebBluetoothProtocolAdapter } from '@dg-kit/protocol';
4
+ export interface WebBluetoothDeviceClientOptions {
5
+ protocol: WebBluetoothProtocolAdapter;
6
+ navigatorRef?: NavigatorBluetoothLike;
7
+ requestDeviceOptions?: RequestDeviceOptionsLike;
8
+ }
9
+ export declare class WebBluetoothDeviceClient implements DeviceClient {
10
+ private readonly options;
11
+ private readonly listeners;
12
+ private readonly nav;
13
+ private device;
14
+ private disconnecting;
15
+ constructor(options: WebBluetoothDeviceClientOptions);
16
+ connect(): Promise<void>;
17
+ disconnect(): Promise<void>;
18
+ getState(): Promise<DeviceState>;
19
+ execute(command: DeviceCommand): Promise<DeviceCommandResult>;
20
+ emergencyStop(): Promise<void>;
21
+ onStateChanged(listener: (state: DeviceState) => void): () => void;
22
+ private readonly onDisconnected;
23
+ private emit;
24
+ }
25
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EACjC,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,2BAA2B,CAAC;IACtC,YAAY,CAAC,EAAE,sBAAsB,CAAC;IACtC,oBAAoB,CAAC,EAAE,wBAAwB,CAAC;CACjD;AAED,qBAAa,wBAAyB,YAAW,YAAY;IAM/C,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;IACrE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;IACzD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,aAAa,CAAS;gBAED,OAAO,EAAE,+BAA+B;IAY/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgDxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC;IAIhC,OAAO,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI7D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,cAAc,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,MAAM,IAAI;IAOlE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAO7B;IAEF,OAAO,CAAC,IAAI;CAKb"}
package/dist/client.js ADDED
@@ -0,0 +1,117 @@
1
+ import { COYOTE_REQUEST_DEVICE_OPTIONS, } from '@dg-kit/protocol';
2
+ import { getWebBluetoothAvailability } from './availability.js';
3
+ export class WebBluetoothDeviceClient {
4
+ options;
5
+ listeners = new Set();
6
+ nav;
7
+ device = null;
8
+ disconnecting = false;
9
+ constructor(options) {
10
+ this.options = options;
11
+ this.nav =
12
+ options.navigatorRef ??
13
+ (typeof navigator === 'undefined'
14
+ ? undefined
15
+ : navigator);
16
+ this.options.protocol.subscribe((state) => {
17
+ this.emit(state);
18
+ });
19
+ }
20
+ async connect() {
21
+ const availability = getWebBluetoothAvailability(this.nav);
22
+ if (!availability.supported) {
23
+ throw new Error(availability.reason);
24
+ }
25
+ const bluetooth = this.nav?.bluetooth;
26
+ if (!bluetooth) {
27
+ throw new Error('当前环境不支持 Web Bluetooth');
28
+ }
29
+ const nextDevice = await bluetooth.requestDevice(this.options.requestDeviceOptions ?? COYOTE_REQUEST_DEVICE_OPTIONS);
30
+ const gatt = nextDevice.gatt;
31
+ if (!gatt) {
32
+ throw new Error('所选蓝牙设备不支持 GATT');
33
+ }
34
+ const server = await gatt.connect();
35
+ const previousDevice = this.device;
36
+ const shouldReplacePrevious = !!previousDevice && previousDevice !== nextDevice;
37
+ if (shouldReplacePrevious) {
38
+ previousDevice.removeEventListener('gattserverdisconnected', this.onDisconnected);
39
+ }
40
+ try {
41
+ await this.options.protocol.onConnected({ device: nextDevice, server });
42
+ }
43
+ catch (error) {
44
+ if (shouldReplacePrevious && isGattConnected(previousDevice)) {
45
+ previousDevice.addEventListener('gattserverdisconnected', this.onDisconnected);
46
+ }
47
+ if (gatt.connected) {
48
+ gatt.disconnect();
49
+ }
50
+ throw error;
51
+ }
52
+ this.device = nextDevice;
53
+ nextDevice.addEventListener('gattserverdisconnected', this.onDisconnected);
54
+ if (shouldReplacePrevious) {
55
+ disconnectDevice(previousDevice);
56
+ }
57
+ }
58
+ async disconnect() {
59
+ this.disconnecting = true;
60
+ try {
61
+ await this.options.protocol.emergencyStop();
62
+ const device = this.device;
63
+ if (device?.gatt?.connected) {
64
+ device.gatt.disconnect();
65
+ }
66
+ await this.options.protocol.onDisconnected();
67
+ }
68
+ finally {
69
+ if (this.device) {
70
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnected);
71
+ }
72
+ this.device = null;
73
+ this.disconnecting = false;
74
+ }
75
+ }
76
+ async getState() {
77
+ return this.options.protocol.getState();
78
+ }
79
+ async execute(command) {
80
+ return this.options.protocol.execute(command);
81
+ }
82
+ async emergencyStop() {
83
+ await this.options.protocol.emergencyStop();
84
+ }
85
+ onStateChanged(listener) {
86
+ this.listeners.add(listener);
87
+ return () => {
88
+ this.listeners.delete(listener);
89
+ };
90
+ }
91
+ onDisconnected = async () => {
92
+ if (this.disconnecting)
93
+ return;
94
+ await this.options.protocol.onDisconnected();
95
+ if (this.device) {
96
+ this.device.removeEventListener('gattserverdisconnected', this.onDisconnected);
97
+ }
98
+ this.device = null;
99
+ };
100
+ emit(state) {
101
+ for (const listener of this.listeners) {
102
+ listener(state);
103
+ }
104
+ }
105
+ }
106
+ function isGattConnected(device) {
107
+ return !!device?.gatt?.connected;
108
+ }
109
+ function disconnectDevice(device) {
110
+ if (!device)
111
+ return;
112
+ const gatt = device.gatt;
113
+ if (!gatt?.connected)
114
+ return;
115
+ gatt.disconnect();
116
+ }
117
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { getWebBluetoothAvailability } from './availability.js';
2
+ export { WebBluetoothDeviceClient, type WebBluetoothDeviceClientOptions } from './client.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,KAAK,+BAA+B,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { getWebBluetoothAvailability } from './availability.js';
2
+ export { WebBluetoothDeviceClient } from './client.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAwC,MAAM,aAAa,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@dg-kit/transport-webbluetooth",
3
+ "version": "0.1.0",
4
+ "description": "Web Bluetooth DeviceClient for DG-Lab Coyote (browser-only)",
5
+ "license": "MIT",
6
+ "author": "0xNullAI",
7
+ "homepage": "https://github.com/0xNullAI/DG-Kit/tree/main/packages/transport-webbluetooth",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/0xNullAI/DG-Kit.git",
11
+ "directory": "packages/transport-webbluetooth"
12
+ },
13
+ "type": "module",
14
+ "main": "./src/index.ts",
15
+ "types": "./src/index.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./src/index.ts",
19
+ "import": "./src/index.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@dg-kit/core": "0.1.0",
39
+ "@dg-kit/protocol": "0.1.0"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.json",
43
+ "typecheck": "tsc -p tsconfig.json --noEmit"
44
+ },
45
+ "keywords": [
46
+ "dg-lab",
47
+ "coyote",
48
+ "ble",
49
+ "web-bluetooth",
50
+ "browser"
51
+ ]
52
+ }