esp-idf-provisioning-web 0.0.1
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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/ESPDevice.d.ts +66 -0
- package/dist/ESPDevice.d.ts.map +1 -0
- package/dist/ESPProvisionManager.d.ts +34 -0
- package/dist/ESPProvisionManager.d.ts.map +1 -0
- package/dist/constants.d.ts +19 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +5085 -0
- package/dist/protocol/protocol.d.ts +4 -0
- package/dist/protocol/protocol.d.ts.map +1 -0
- package/dist/protocol/wifiConfig.d.ts +8 -0
- package/dist/protocol/wifiConfig.d.ts.map +1 -0
- package/dist/protocol/wifiScan.d.ts +8 -0
- package/dist/protocol/wifiScan.d.ts.map +1 -0
- package/dist/security/Security.d.ts +26 -0
- package/dist/security/Security.d.ts.map +1 -0
- package/dist/security/Security0.d.ts +13 -0
- package/dist/security/Security0.d.ts.map +1 -0
- package/dist/transport/BLETransport.d.ts +22 -0
- package/dist/transport/BLETransport.d.ts.map +1 -0
- package/dist/transport/SoftAPTransport.d.ts +27 -0
- package/dist/transport/SoftAPTransport.d.ts.map +1 -0
- package/dist/transport/Transport.d.ts +22 -0
- package/dist/transport/Transport.d.ts.map +1 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/crypto.d.ts +44 -0
- package/dist/utils/crypto.d.ts.map +1 -0
- package/package.json +60 -0
- package/src/ESPDevice.ts +231 -0
- package/src/ESPProvisionManager.ts +78 -0
- package/src/constants.ts +29 -0
- package/src/generated/.gitkeep +0 -0
- package/src/generated/proto.d.ts +3418 -0
- package/src/generated/proto.js +10006 -0
- package/src/index.ts +31 -0
- package/src/proto/constants.proto +14 -0
- package/src/proto/sec0.proto +28 -0
- package/src/proto/sec1.proto +45 -0
- package/src/proto/sec2.proto +48 -0
- package/src/proto/session.proto +25 -0
- package/src/proto/wifi_config.proto +57 -0
- package/src/proto/wifi_constants.proto +32 -0
- package/src/proto/wifi_ctrl.proto +38 -0
- package/src/proto/wifi_scan.proto +63 -0
- package/src/protocol/protocol.ts +23 -0
- package/src/protocol/wifiConfig.ts +108 -0
- package/src/protocol/wifiScan.ts +91 -0
- package/src/security/Security.ts +29 -0
- package/src/security/Security0.ts +40 -0
- package/src/transport/BLETransport.ts +111 -0
- package/src/transport/SoftAPTransport.ts +89 -0
- package/src/transport/Transport.ts +24 -0
- package/src/types.ts +86 -0
- package/src/utils/crypto.ts +129 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/protocol/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,GAAG,eAAe,CAmBvF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESPWifiStatus } from '../types.js';
|
|
2
|
+
export declare function getConfigStatusRequest(): Uint8Array;
|
|
3
|
+
export declare function processConfigStatusResponse(response: Uint8Array): ESPWifiStatus;
|
|
4
|
+
export declare function getConfigUpdateRequest(ssid: string, passphrase: string): Uint8Array;
|
|
5
|
+
export declare function processConfigUpdateResponse(response: Uint8Array): void;
|
|
6
|
+
export declare function getConfigApplyRequest(): Uint8Array;
|
|
7
|
+
export declare function processConfigApplyResponse(response: Uint8Array): void;
|
|
8
|
+
//# sourceMappingURL=wifiConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wifiConfig.d.ts","sourceRoot":"","sources":["../../src/protocol/wifiConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAoD,aAAa,EAAE,MAAM,aAAa,CAAC;AAI9F,wBAAgB,sBAAsB,IAAI,UAAU,CAKnD;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,UAAU,GAAG,aAAa,CAsD/E;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAQnF;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAUtE;AAED,wBAAgB,qBAAqB,IAAI,UAAU,CAKlD;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CAUrE"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ESPScanStatusResponse, ESPWifiAp } from '../types.js';
|
|
2
|
+
export declare function getScanStartRequest(blocking?: boolean, passive?: boolean, groupChannels?: number, periodMs?: number): Uint8Array;
|
|
3
|
+
export declare function processScanStartResponse(response: Uint8Array): void;
|
|
4
|
+
export declare function getScanStatusRequest(): Uint8Array;
|
|
5
|
+
export declare function processScanStatusResponse(response: Uint8Array): ESPScanStatusResponse;
|
|
6
|
+
export declare function getScanResultRequest(index: number, count: number): Uint8Array;
|
|
7
|
+
export declare function processScanResultResponse(response: Uint8Array): ESPWifiAp[];
|
|
8
|
+
//# sourceMappingURL=wifiScan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wifiScan.d.ts","sourceRoot":"","sources":["../../src/protocol/wifiScan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI/D,wBAAgB,mBAAmB,CACjC,QAAQ,GAAE,OAAc,EACxB,OAAO,GAAE,OAAe,EACxB,aAAa,GAAE,MAAU,EACzB,QAAQ,GAAE,MAAY,GACrB,UAAU,CAWZ;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,UAAU,GAAG,IAAI,CASnE;AAED,wBAAgB,oBAAoB,IAAI,UAAU,CAMjD;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,UAAU,GAAG,qBAAqB,CAcrF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAS7E;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,UAAU,GAAG,SAAS,EAAE,CAqB3E"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for security implementations
|
|
3
|
+
*/
|
|
4
|
+
export declare abstract class Security {
|
|
5
|
+
/**
|
|
6
|
+
* Encrypt data for transmission
|
|
7
|
+
*/
|
|
8
|
+
abstract encrypt(data: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
/**
|
|
10
|
+
* Decrypt received data
|
|
11
|
+
*/
|
|
12
|
+
abstract decrypt(data: Uint8Array): Promise<Uint8Array>;
|
|
13
|
+
/**
|
|
14
|
+
* Get session setup request data
|
|
15
|
+
*/
|
|
16
|
+
abstract getSessionSetupRequest(): Promise<Uint8Array>;
|
|
17
|
+
/**
|
|
18
|
+
* Process session setup response
|
|
19
|
+
*/
|
|
20
|
+
abstract processSessionSetupResponse(response: Uint8Array): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Check if security is established
|
|
23
|
+
*/
|
|
24
|
+
abstract isEstablished(): boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=Security.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Security.d.ts","sourceRoot":"","sources":["../../src/security/Security.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,8BAAsB,QAAQ;IAC5B;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAEvD;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAEvD;;OAEG;IACH,QAAQ,CAAC,sBAAsB,IAAI,OAAO,CAAC,UAAU,CAAC;IAEtD;;OAEG;IACH,QAAQ,CAAC,2BAA2B,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAEzE;;OAEG;IACH,QAAQ,CAAC,aAAa,IAAI,OAAO;CAClC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Security } from './Security';
|
|
2
|
+
/**
|
|
3
|
+
* Security0 - No encryption (unsecure mode)
|
|
4
|
+
*/
|
|
5
|
+
export declare class Security0 extends Security {
|
|
6
|
+
private established;
|
|
7
|
+
encrypt(data: Uint8Array): Promise<Uint8Array>;
|
|
8
|
+
decrypt(data: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
getSessionSetupRequest(): Promise<Uint8Array>;
|
|
10
|
+
processSessionSetupResponse(response: Uint8Array): Promise<void>;
|
|
11
|
+
isEstablished(): boolean;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=Security0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Security0.d.ts","sourceRoot":"","sources":["../../src/security/Security0.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC;;GAEG;AACH,qBAAa,SAAU,SAAQ,QAAQ;IACrC,OAAO,CAAC,WAAW,CAAkB;IAE/B,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAI9C,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAI9C,sBAAsB,IAAI,OAAO,CAAC,UAAU,CAAC;IAU7C,2BAA2B,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAStE,aAAa,IAAI,OAAO;CAGzB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Transport } from './Transport';
|
|
2
|
+
/**
|
|
3
|
+
* BLE Transport implementation using Web Bluetooth API
|
|
4
|
+
*/
|
|
5
|
+
export declare class BLETransport extends Transport {
|
|
6
|
+
private device;
|
|
7
|
+
private serviceUUID;
|
|
8
|
+
private nuLookup;
|
|
9
|
+
private server?;
|
|
10
|
+
private service?;
|
|
11
|
+
private connected;
|
|
12
|
+
constructor(device: BluetoothDevice, serviceUUID: string, nuLookup: Map<string, string>);
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
disconnect(): void;
|
|
15
|
+
sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array>;
|
|
16
|
+
isConnected(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Get the underlying Bluetooth device
|
|
19
|
+
*/
|
|
20
|
+
getDevice(): BluetoothDevice;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=BLETransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BLETransport.d.ts","sourceRoot":"","sources":["../../src/transport/BLETransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAIxC;;GAEG;AACH,qBAAa,YAAa,SAAQ,SAAS;IACzC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAsB;IAEtC,OAAO,CAAC,MAAM,CAAC,CAA4B;IAC3C,OAAO,CAAC,OAAO,CAAC,CAA6B;IAC7C,OAAO,CAAC,SAAS,CAAkB;gBAEvB,MAAM,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAOjF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C9B,UAAU,IAAI,IAAI;IASZ,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAsBvE,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,SAAS,IAAI,eAAe;CAG7B"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Transport } from './Transport';
|
|
2
|
+
/**
|
|
3
|
+
* SoftAP Transport implementation using HTTP
|
|
4
|
+
* Note: Browser cannot automatically connect to WiFi networks.
|
|
5
|
+
* User must manually connect to the device's SoftAP before using this transport.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SoftAPTransport extends Transport {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private connected;
|
|
10
|
+
/**
|
|
11
|
+
* @param baseUrl - Base URL of the device (e.g., http://192.168.4.1)
|
|
12
|
+
*/
|
|
13
|
+
constructor(baseUrl: string);
|
|
14
|
+
connect(): Promise<void>;
|
|
15
|
+
disconnect(): void;
|
|
16
|
+
sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array>;
|
|
17
|
+
isConnected(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Set custom base URL
|
|
20
|
+
*/
|
|
21
|
+
setBaseUrl(url: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get current base URL
|
|
24
|
+
*/
|
|
25
|
+
getBaseUrl(): string;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=SoftAPTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SoftAPTransport.d.ts","sourceRoot":"","sources":["../../src/transport/SoftAPTransport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAkB;IAEnC;;OAEG;gBACS,OAAO,EAAE,MAAM;IAKrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB9B,UAAU,IAAI,IAAI;IAIZ,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IA2BvE,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAI7B;;OAEG;IACH,UAAU,IAAI,MAAM;CAGrB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for transport implementations
|
|
3
|
+
*/
|
|
4
|
+
export declare abstract class Transport {
|
|
5
|
+
/**
|
|
6
|
+
* Connect to the device
|
|
7
|
+
*/
|
|
8
|
+
abstract connect(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Disconnect from the device
|
|
11
|
+
*/
|
|
12
|
+
abstract disconnect(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Send data to a specific endpoint
|
|
15
|
+
*/
|
|
16
|
+
abstract sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array>;
|
|
17
|
+
/**
|
|
18
|
+
* Check if transport is connected
|
|
19
|
+
*/
|
|
20
|
+
abstract isConnected(): boolean;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=Transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Transport.d.ts","sourceRoot":"","sources":["../../src/transport/Transport.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,8BAAsB,SAAS;IAC7B;;OAEG;IACH,QAAQ,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAEjC;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,IAAI;IAE3B;;OAEG;IACH,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAE1E;;OAEG;IACH,QAAQ,CAAC,WAAW,IAAI,OAAO;CAChC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type ESPSecurityConfig = ESPSecurity1Config | ESPSecurity2Config;
|
|
2
|
+
export interface ESPSecurity1Config {
|
|
3
|
+
type: 'Security1';
|
|
4
|
+
proofOfPossession: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ESPSecurity2Config {
|
|
7
|
+
type: 'Security2';
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* WiFi authentication modes
|
|
13
|
+
*/
|
|
14
|
+
export declare enum ESPWifiAuthMode {
|
|
15
|
+
open = 0,
|
|
16
|
+
wep = 1,
|
|
17
|
+
wpa2Enterprise = 2,
|
|
18
|
+
wpa2Psk = 3,
|
|
19
|
+
wpaPsk = 4,
|
|
20
|
+
wpaWpa2Psk = 5,
|
|
21
|
+
wpa3Psk = 6,
|
|
22
|
+
wpa2Wpa3Psk = 7
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* WiFi STA state
|
|
26
|
+
*/
|
|
27
|
+
export declare enum ESPWifiStaState {
|
|
28
|
+
connected = 0,
|
|
29
|
+
connecting = 1,
|
|
30
|
+
disconnected = 2,
|
|
31
|
+
failed = 3
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* WiFi scan failure reasons
|
|
35
|
+
*/
|
|
36
|
+
export declare enum ESPConnectFailReason {
|
|
37
|
+
authError = 1,
|
|
38
|
+
networkNotFound = 2
|
|
39
|
+
}
|
|
40
|
+
export interface ESPWifiStatus {
|
|
41
|
+
staState: ESPWifiStaState;
|
|
42
|
+
failedReason?: ESPConnectFailReason;
|
|
43
|
+
connected?: ESPWifiAp;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* WiFi network information returned by device scan
|
|
47
|
+
*/
|
|
48
|
+
export interface ESPWifiAp {
|
|
49
|
+
ssid: string;
|
|
50
|
+
rssi: number;
|
|
51
|
+
auth: ESPWifiAuthMode;
|
|
52
|
+
bssid: string;
|
|
53
|
+
channel: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Scan status response
|
|
57
|
+
*/
|
|
58
|
+
export interface ESPScanStatusResponse {
|
|
59
|
+
scanFinished: boolean;
|
|
60
|
+
count: number;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Device interface for ESP provisioning
|
|
64
|
+
*/
|
|
65
|
+
export interface ESPDeviceInterface {
|
|
66
|
+
connect(security?: ESPSecurityConfig): Promise<void>;
|
|
67
|
+
scanWifiList(): Promise<ESPWifiAp[]>;
|
|
68
|
+
disconnect(): void;
|
|
69
|
+
provision(ssid: string, passphrase: string): Promise<void>;
|
|
70
|
+
fetchWifiStatus(): Promise<ESPWifiStatus>;
|
|
71
|
+
getDevice(): BluetoothDevice | URL;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,oBAAY,eAAe;IACzB,IAAI,IAAI;IACR,GAAG,IAAI;IACP,cAAc,IAAI;IAClB,OAAO,IAAI;IACX,MAAM,IAAI;IACV,UAAU,IAAI;IACd,OAAO,IAAI;IACX,WAAW,IAAI;CAChB;AAED;;GAEG;AACH,oBAAY,eAAe;IACzB,SAAS,IAAI;IACb,UAAU,IAAI;IACd,YAAY,IAAI;IAChB,MAAM,IAAI;CACX;AAED;;GAEG;AACH,oBAAY,oBAAoB;IAC9B,SAAS,IAAI;IACb,eAAe,IAAI;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,eAAe,CAAC;IAC1B,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErD,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IAErC,UAAU,IAAI,IAAI,CAAC;IAEnB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAE1C,SAAS,IAAI,eAAe,GAAG,GAAG,CAAC;CACpC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic utilities for ESP provisioning protocol
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate random bytes
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateRandomBytes(length: number): Uint8Array;
|
|
8
|
+
/**
|
|
9
|
+
* Convert hex string to Uint8Array
|
|
10
|
+
*/
|
|
11
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* Convert Uint8Array to hex string
|
|
14
|
+
*/
|
|
15
|
+
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
16
|
+
/**
|
|
17
|
+
* Convert string to Uint8Array
|
|
18
|
+
*/
|
|
19
|
+
export declare function stringToBytes(str: string): Uint8Array;
|
|
20
|
+
/**
|
|
21
|
+
* Convert Uint8Array to string
|
|
22
|
+
*/
|
|
23
|
+
export declare function bytesToString(bytes: Uint8Array): string;
|
|
24
|
+
/**
|
|
25
|
+
* SHA-256 hash function
|
|
26
|
+
*/
|
|
27
|
+
export declare function sha256(data: Uint8Array): Promise<Uint8Array>;
|
|
28
|
+
/**
|
|
29
|
+
* HMAC-SHA-256
|
|
30
|
+
*/
|
|
31
|
+
export declare function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
32
|
+
/**
|
|
33
|
+
* AES-CTR encryption
|
|
34
|
+
*/
|
|
35
|
+
export declare function aesEncrypt(key: Uint8Array, counter: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
36
|
+
/**
|
|
37
|
+
* AES-CTR decryption
|
|
38
|
+
*/
|
|
39
|
+
export declare function aesDecrypt(key: Uint8Array, counter: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
40
|
+
/**
|
|
41
|
+
* Concatenate multiple Uint8Arrays
|
|
42
|
+
*/
|
|
43
|
+
export declare function concatBytes(...arrays: Uint8Array[]): Uint8Array;
|
|
44
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAI9D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAMlD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIpD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAErD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEvD;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAUvF;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,UAAU,CAAC,CAcrB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAS/D"}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "esp-idf-provisioning-web",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "ESP-IDF WiFi Provisioning SDK for web browsers",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"esp32",
|
|
10
|
+
"esp-idf",
|
|
11
|
+
"provisioning",
|
|
12
|
+
"wifi",
|
|
13
|
+
"ble",
|
|
14
|
+
"bluetooth",
|
|
15
|
+
"web-bluetooth",
|
|
16
|
+
"softap",
|
|
17
|
+
"iot"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"protobufjs": "^7.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/jest": "^29.5.0",
|
|
26
|
+
"@types/web-bluetooth": "0.0.21",
|
|
27
|
+
"@typescript-eslint/eslint-plugin": "8.46.2",
|
|
28
|
+
"@typescript-eslint/parser": "8.46.2",
|
|
29
|
+
"eslint": "9.38.0",
|
|
30
|
+
"jest": "30.2.0",
|
|
31
|
+
"prettier": "^3.0.0",
|
|
32
|
+
"protobufjs-cli": "^1.0.0",
|
|
33
|
+
"ts-jest": "^29.0.0",
|
|
34
|
+
"typescript": "^5.0.0",
|
|
35
|
+
"vite": "7.1.11"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/ho-229/esp-idf-provisioning-web.git"
|
|
46
|
+
},
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/ho-229/esp-idf-provisioning-web/issues"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"prebuild": "pbjs -t static-module -w es6 -o src/generated/proto.js src/proto/*.proto && pbts -o src/generated/proto.d.ts src/generated/proto.js",
|
|
52
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
53
|
+
"dev": "vite build --watch",
|
|
54
|
+
"example": "npx http-server -p 8080 -o /examples/ble-provisioning.html",
|
|
55
|
+
"serve": "npx http-server -p 8080",
|
|
56
|
+
"test": "jest",
|
|
57
|
+
"lint": "eslint src --ext .ts",
|
|
58
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/ESPDevice.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ESPDeviceInterface,
|
|
3
|
+
ESPWifiAp,
|
|
4
|
+
ESPWifiStatus,
|
|
5
|
+
ESPSecurityConfig,
|
|
6
|
+
} from './types';
|
|
7
|
+
import { Transport } from './transport/Transport';
|
|
8
|
+
import { BLETransport } from './transport/BLETransport';
|
|
9
|
+
import { SoftAPTransport } from './transport/SoftAPTransport';
|
|
10
|
+
import { Security } from './security/Security';
|
|
11
|
+
import { Security0 } from './security/Security0';
|
|
12
|
+
import { FALLBACK_NU_LOOKUP, FALLBACK_SERVICE_UUID, PROTO_ENDPOINTS } from './constants';
|
|
13
|
+
import {
|
|
14
|
+
getScanResultRequest,
|
|
15
|
+
getScanStartRequest,
|
|
16
|
+
getScanStatusRequest,
|
|
17
|
+
processScanResultResponse,
|
|
18
|
+
processScanStartResponse,
|
|
19
|
+
processScanStatusResponse,
|
|
20
|
+
} from './protocol/wifiScan';
|
|
21
|
+
import {
|
|
22
|
+
getConfigApplyRequest,
|
|
23
|
+
getConfigStatusRequest,
|
|
24
|
+
getConfigUpdateRequest,
|
|
25
|
+
processConfigApplyResponse,
|
|
26
|
+
processConfigStatusResponse,
|
|
27
|
+
processConfigUpdateResponse,
|
|
28
|
+
} from './protocol/wifiConfig';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ESP Device class for provisioning operations
|
|
32
|
+
*/
|
|
33
|
+
export class ESPDevice implements ESPDeviceInterface {
|
|
34
|
+
private device: BluetoothDevice | URL;
|
|
35
|
+
private transportImpl?: Transport;
|
|
36
|
+
private securityImpl?: Security;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a new ESPDevice instance
|
|
40
|
+
* @param device - BluetoothDevice for BLE or URL for SoftAP
|
|
41
|
+
*/
|
|
42
|
+
constructor(device: BluetoothDevice | URL) {
|
|
43
|
+
this.device = device;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Connect to the device
|
|
48
|
+
* @param proofOfPossession - Proof of possession for secure modes
|
|
49
|
+
* @param softAPPassword - Password for SoftAP mode (not used in browser)
|
|
50
|
+
* @param username - Username for authentication
|
|
51
|
+
*/
|
|
52
|
+
async connect(security?: ESPSecurityConfig): Promise<void> {
|
|
53
|
+
// Initialize transport
|
|
54
|
+
await this.initializeTransport();
|
|
55
|
+
|
|
56
|
+
// Initialize security
|
|
57
|
+
this.initializeSecurity(security);
|
|
58
|
+
|
|
59
|
+
// Establish secure session if needed
|
|
60
|
+
if (security) {
|
|
61
|
+
await this.establishSession();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Scan for available WiFi networks
|
|
67
|
+
* @returns List of WiFi networks
|
|
68
|
+
*/
|
|
69
|
+
async scanWifiList(): Promise<ESPWifiAp[]> {
|
|
70
|
+
if (!this.transportImpl || !this.securityImpl) {
|
|
71
|
+
throw new Error('Device not connected');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const scanRequest = getScanStartRequest();
|
|
76
|
+
const scanResponse = await this.sendData(PROTO_ENDPOINTS.PROV_SCAN, scanRequest);
|
|
77
|
+
processScanStartResponse(scanResponse);
|
|
78
|
+
|
|
79
|
+
const scanStatusRequest = getScanStatusRequest();
|
|
80
|
+
const scanStatusResponse = await this.sendData(PROTO_ENDPOINTS.PROV_SCAN, scanStatusRequest);
|
|
81
|
+
const { scanFinished, count } = processScanStatusResponse(scanStatusResponse);
|
|
82
|
+
if (!scanFinished) {
|
|
83
|
+
throw new Error('WiFi scan should have finished');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let wifiList: ESPWifiAp[] = [];
|
|
87
|
+
for (let i = 0; i < count; i++) {
|
|
88
|
+
const scanResultRequest = getScanResultRequest(i, 1);
|
|
89
|
+
const scanResultResponse = await this.sendData(
|
|
90
|
+
PROTO_ENDPOINTS.PROV_SCAN,
|
|
91
|
+
scanResultRequest
|
|
92
|
+
);
|
|
93
|
+
const list = processScanResultResponse(scanResultResponse);
|
|
94
|
+
wifiList = wifiList.concat(list);
|
|
95
|
+
}
|
|
96
|
+
return wifiList;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new Error(`WiFi scan failed: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Provision device with WiFi credentials
|
|
104
|
+
* @note Call fetchWifiStatus() after provisioning to check provisioning status
|
|
105
|
+
* @param ssid - WiFi network SSID
|
|
106
|
+
* @param passphrase - WiFi network password
|
|
107
|
+
*/
|
|
108
|
+
async provision(ssid: string, passphrase: string): Promise<void> {
|
|
109
|
+
if (!this.transportImpl || !this.securityImpl) {
|
|
110
|
+
throw new Error('Device not connected');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const configRequest = getConfigUpdateRequest(ssid, passphrase);
|
|
115
|
+
const configResponse = await this.sendData(PROTO_ENDPOINTS.PROV_CONFIG, configRequest);
|
|
116
|
+
processConfigUpdateResponse(configResponse);
|
|
117
|
+
|
|
118
|
+
const applyRequest = getConfigApplyRequest();
|
|
119
|
+
const applyResponse = await this.sendData(PROTO_ENDPOINTS.PROV_CONFIG, applyRequest);
|
|
120
|
+
processConfigApplyResponse(applyResponse);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw new Error(`Provisioning failed: ${error}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Fetch WiFi status from device
|
|
128
|
+
* @returns WiFi status
|
|
129
|
+
*/
|
|
130
|
+
async fetchWifiStatus(): Promise<ESPWifiStatus> {
|
|
131
|
+
if (!this.transportImpl || !this.securityImpl) {
|
|
132
|
+
throw new Error('Device not connected');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const statusRequest = getConfigStatusRequest();
|
|
137
|
+
const statusResponse = await this.sendData(PROTO_ENDPOINTS.PROV_CONFIG, statusRequest);
|
|
138
|
+
return processConfigStatusResponse(statusResponse);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw new Error(`Fetching WiFi status failed: ${error}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Disconnect from device
|
|
146
|
+
*/
|
|
147
|
+
disconnect(): void {
|
|
148
|
+
if (this.transportImpl) {
|
|
149
|
+
this.transportImpl.disconnect();
|
|
150
|
+
this.transportImpl = undefined;
|
|
151
|
+
}
|
|
152
|
+
this.securityImpl = undefined;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get device
|
|
157
|
+
*/
|
|
158
|
+
getDevice(): BluetoothDevice | URL {
|
|
159
|
+
return this.device;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Initialize security layer based on security type
|
|
164
|
+
*/
|
|
165
|
+
private initializeSecurity(security?: ESPSecurityConfig): void {
|
|
166
|
+
if (!security) {
|
|
167
|
+
this.securityImpl = new Security0();
|
|
168
|
+
} else {
|
|
169
|
+
throw new Error('Unsupported security configuration');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Establish secure session with device
|
|
175
|
+
*/
|
|
176
|
+
private async establishSession(): Promise<void> {
|
|
177
|
+
if (!this.transportImpl || !this.securityImpl) {
|
|
178
|
+
throw new Error('Transport or security not initialized');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Get session setup request
|
|
182
|
+
const setupRequest = await this.securityImpl.getSessionSetupRequest();
|
|
183
|
+
|
|
184
|
+
// Send session setup request
|
|
185
|
+
const setupResponse = await this.transportImpl.sendData(
|
|
186
|
+
PROTO_ENDPOINTS.PROV_SESSION,
|
|
187
|
+
setupRequest
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Process session setup response
|
|
191
|
+
await this.securityImpl.processSessionSetupResponse(setupResponse);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Initialize transport layer based on transport type
|
|
196
|
+
*/
|
|
197
|
+
private async initializeTransport(): Promise<void> {
|
|
198
|
+
if (this.device instanceof URL) {
|
|
199
|
+
this.transportImpl = new SoftAPTransport(this.device.toString());
|
|
200
|
+
} else { // BluetoothDevice
|
|
201
|
+
this.transportImpl = new BLETransport(
|
|
202
|
+
this.device,
|
|
203
|
+
FALLBACK_SERVICE_UUID,
|
|
204
|
+
new Map(Object.entries(FALLBACK_NU_LOOKUP))
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
await this.transportImpl.connect();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Send custom data to device
|
|
212
|
+
* @param path - Data endpoint path
|
|
213
|
+
* @param data - Data to send
|
|
214
|
+
* @returns Response data from device
|
|
215
|
+
*/
|
|
216
|
+
private async sendData(path: string, data: Uint8Array): Promise<Uint8Array> {
|
|
217
|
+
if (!this.transportImpl || !this.securityImpl) {
|
|
218
|
+
throw new Error('Device not connected');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Encrypt data if security is established
|
|
222
|
+
const encryptedData = await this.securityImpl.encrypt(data);
|
|
223
|
+
console.debug('Send encrypted data length:', encryptedData.length);
|
|
224
|
+
|
|
225
|
+
// Send encrypted data
|
|
226
|
+
const encryptedResponse = await this.transportImpl.sendData(path, encryptedData);
|
|
227
|
+
|
|
228
|
+
// Decrypt response
|
|
229
|
+
return await this.securityImpl.decrypt(encryptedResponse);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ESPDevice } from './ESPDevice';
|
|
2
|
+
import { DEFAULT_BLE_DEVICE_OPTIONS } from './constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ESP Provision Manager for device discovery
|
|
6
|
+
*/
|
|
7
|
+
export class ESPProvisionManager {
|
|
8
|
+
/**
|
|
9
|
+
* Search for ESP devices using BLE
|
|
10
|
+
* @param devicePrefix - Prefix to filter device names
|
|
11
|
+
* @param transport - Transport type (only BLE supported for search)
|
|
12
|
+
* @param security - Security type to use
|
|
13
|
+
* @returns Promise resolving to array of discovered devices
|
|
14
|
+
*/
|
|
15
|
+
static async searchBLEDevice(
|
|
16
|
+
bleDeviceOptions: RequestDeviceOptions = DEFAULT_BLE_DEVICE_OPTIONS,
|
|
17
|
+
): Promise<ESPDevice | null> {
|
|
18
|
+
// Check if Web Bluetooth is available
|
|
19
|
+
const availability = await ESPProvisionManager.getBluetoothAvailability();
|
|
20
|
+
if (!availability) {
|
|
21
|
+
throw new Error('Web Bluetooth is not available.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// Request Bluetooth device with filters
|
|
26
|
+
const device = await navigator.bluetooth.requestDevice(bleDeviceOptions);
|
|
27
|
+
if (!device.name) {
|
|
28
|
+
throw new Error('Device name not available');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create ESPDevice instance
|
|
32
|
+
return new ESPDevice(device);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if ((error as Error).name === 'NotFoundError') {
|
|
35
|
+
// User cancelled the device selection
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`Device search failed: ${error}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create an ESPDevice instance for SoftAP mode
|
|
44
|
+
* Note: Browser cannot automatically connect to WiFi.
|
|
45
|
+
* User must manually connect to the device's access point before using this.
|
|
46
|
+
*
|
|
47
|
+
* @param baseURL - Base URL for the SoftAP
|
|
48
|
+
* @returns ESPDevice instance configured for SoftAP
|
|
49
|
+
*/
|
|
50
|
+
static createSoftAPDevice(baseURL: URL): ESPDevice {
|
|
51
|
+
return new ESPDevice(baseURL);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check if Web Bluetooth is supported
|
|
56
|
+
* @returns true if Web Bluetooth is available
|
|
57
|
+
*/
|
|
58
|
+
static isBluetoothSupported(): boolean {
|
|
59
|
+
return typeof navigator !== 'undefined' && 'bluetooth' in navigator;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get Bluetooth availability status
|
|
64
|
+
* @returns Promise resolving to availability status
|
|
65
|
+
*/
|
|
66
|
+
static async getBluetoothAvailability(): Promise<boolean> {
|
|
67
|
+
if (!this.isBluetoothSupported()) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
return await navigator.bluetooth.getAvailability();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error('Failed to get Bluetooth availability:', error);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|