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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/ESPDevice.d.ts +66 -0
  4. package/dist/ESPDevice.d.ts.map +1 -0
  5. package/dist/ESPProvisionManager.d.ts +34 -0
  6. package/dist/ESPProvisionManager.d.ts.map +1 -0
  7. package/dist/constants.d.ts +19 -0
  8. package/dist/constants.d.ts.map +1 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.es.js +5085 -0
  12. package/dist/protocol/protocol.d.ts +4 -0
  13. package/dist/protocol/protocol.d.ts.map +1 -0
  14. package/dist/protocol/wifiConfig.d.ts +8 -0
  15. package/dist/protocol/wifiConfig.d.ts.map +1 -0
  16. package/dist/protocol/wifiScan.d.ts +8 -0
  17. package/dist/protocol/wifiScan.d.ts.map +1 -0
  18. package/dist/security/Security.d.ts +26 -0
  19. package/dist/security/Security.d.ts.map +1 -0
  20. package/dist/security/Security0.d.ts +13 -0
  21. package/dist/security/Security0.d.ts.map +1 -0
  22. package/dist/transport/BLETransport.d.ts +22 -0
  23. package/dist/transport/BLETransport.d.ts.map +1 -0
  24. package/dist/transport/SoftAPTransport.d.ts +27 -0
  25. package/dist/transport/SoftAPTransport.d.ts.map +1 -0
  26. package/dist/transport/Transport.d.ts +22 -0
  27. package/dist/transport/Transport.d.ts.map +1 -0
  28. package/dist/types.d.ts +73 -0
  29. package/dist/types.d.ts.map +1 -0
  30. package/dist/utils/crypto.d.ts +44 -0
  31. package/dist/utils/crypto.d.ts.map +1 -0
  32. package/package.json +60 -0
  33. package/src/ESPDevice.ts +231 -0
  34. package/src/ESPProvisionManager.ts +78 -0
  35. package/src/constants.ts +29 -0
  36. package/src/generated/.gitkeep +0 -0
  37. package/src/generated/proto.d.ts +3418 -0
  38. package/src/generated/proto.js +10006 -0
  39. package/src/index.ts +31 -0
  40. package/src/proto/constants.proto +14 -0
  41. package/src/proto/sec0.proto +28 -0
  42. package/src/proto/sec1.proto +45 -0
  43. package/src/proto/sec2.proto +48 -0
  44. package/src/proto/session.proto +25 -0
  45. package/src/proto/wifi_config.proto +57 -0
  46. package/src/proto/wifi_constants.proto +32 -0
  47. package/src/proto/wifi_ctrl.proto +38 -0
  48. package/src/proto/wifi_scan.proto +63 -0
  49. package/src/protocol/protocol.ts +23 -0
  50. package/src/protocol/wifiConfig.ts +108 -0
  51. package/src/protocol/wifiScan.ts +91 -0
  52. package/src/security/Security.ts +29 -0
  53. package/src/security/Security0.ts +40 -0
  54. package/src/transport/BLETransport.ts +111 -0
  55. package/src/transport/SoftAPTransport.ts +89 -0
  56. package/src/transport/Transport.ts +24 -0
  57. package/src/types.ts +86 -0
  58. package/src/utils/crypto.ts +129 -0
@@ -0,0 +1,111 @@
1
+ import { Transport } from './Transport';
2
+ // import { BLE_SERVICE_UUID, BLE_PROVISIONING_UUID, BLE_SESSION_UUID } from '../constants';
3
+ import { bytesToString } from '../utils/crypto';
4
+
5
+ /**
6
+ * BLE Transport implementation using Web Bluetooth API
7
+ */
8
+ export class BLETransport extends Transport {
9
+ private device: BluetoothDevice;
10
+ private serviceUUID: string;
11
+ private nuLookup: Map<string, string>;
12
+
13
+ private server?: BluetoothRemoteGATTServer;
14
+ private service?: BluetoothRemoteGATTService;
15
+ private connected: boolean = false;
16
+
17
+ constructor(device: BluetoothDevice, serviceUUID: string, nuLookup: Map<string, string>) {
18
+ super();
19
+ this.device = device;
20
+ this.serviceUUID = serviceUUID;
21
+ this.nuLookup = nuLookup;
22
+ }
23
+
24
+ async connect(): Promise<void> {
25
+ if (this.isConnected()) {
26
+ return;
27
+ }
28
+
29
+ try {
30
+ // Connect to GATT server
31
+ this.server = await this.device.gatt?.connect();
32
+ if (!this.server) {
33
+ throw new Error('Failed to connect to GATT server');
34
+ }
35
+
36
+ // Get provisioning services
37
+ try {
38
+ const services = await this.server.getPrimaryServices(this.serviceUUID);
39
+ if (services.length > 1) {
40
+ const service = services.find((s) => s.uuid === this.serviceUUID);
41
+ if (!service) {
42
+ throw new Error(`Service with UUID ${this.serviceUUID} not found`);
43
+ }
44
+ this.service = service;
45
+ } else {
46
+ this.service = services[0];
47
+ }
48
+ } catch (error) {
49
+ throw new Error(`Failed to get primary services: ${error}`);
50
+ }
51
+
52
+ const characteristics = await this.service.getCharacteristics();
53
+ for (const char of characteristics) {
54
+ const descriptors = await char.getDescriptors();
55
+ for (const desc of descriptors) {
56
+ if (desc.uuid.slice(4, 8) !== '2901') continue;
57
+ const rawName = await desc.readValue();
58
+ this.nuLookup.set(bytesToString(new Uint8Array(rawName.buffer)).toLowerCase(), char.uuid);
59
+ }
60
+ }
61
+
62
+ console.debug('BLE discovered characteristics:', this.nuLookup);
63
+ this.connected = true;
64
+ } catch (error) {
65
+ this.connected = false;
66
+ throw new Error(`BLE connection failed: ${error}`);
67
+ }
68
+ }
69
+
70
+ disconnect(): void {
71
+ if (this.server?.connected) {
72
+ this.server.disconnect();
73
+ }
74
+ this.server = undefined;
75
+ this.service = undefined;
76
+ this.connected = false;
77
+ }
78
+
79
+ async sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array> {
80
+ if (!this.isConnected()) {
81
+ throw new Error('Not connected to device');
82
+ }
83
+
84
+ const charUUID = this.nuLookup.get(endpoint.toLowerCase());
85
+ if (!charUUID) {
86
+ throw new Error(`Characteristic UUID for endpoint ${endpoint} not found`);
87
+ }
88
+
89
+ const characteristic = await this.service?.getCharacteristic(charUUID);
90
+ if (!characteristic) {
91
+ throw new Error(`Characteristic for UUID ${charUUID} not found`);
92
+ }
93
+
94
+ // FIXME: Uint8Array have wrong generic type constraints in this case
95
+ const cleanData = new Uint8Array(data.buffer as ArrayBuffer, data.byteOffset, data.byteLength);
96
+ await characteristic.writeValueWithResponse(cleanData);
97
+ const response = await characteristic.readValue();
98
+ return new Uint8Array(response.buffer);
99
+ }
100
+
101
+ isConnected(): boolean {
102
+ return this.connected && this.server?.connected === true;
103
+ }
104
+
105
+ /**
106
+ * Get the underlying Bluetooth device
107
+ */
108
+ getDevice(): BluetoothDevice {
109
+ return this.device;
110
+ }
111
+ }
@@ -0,0 +1,89 @@
1
+ import { Transport } from './Transport';
2
+
3
+ /**
4
+ * SoftAP Transport implementation using HTTP
5
+ * Note: Browser cannot automatically connect to WiFi networks.
6
+ * User must manually connect to the device's SoftAP before using this transport.
7
+ */
8
+ export class SoftAPTransport extends Transport {
9
+ private baseUrl: string;
10
+ private connected: boolean = false;
11
+
12
+ /**
13
+ * @param baseUrl - Base URL of the device (e.g., http://192.168.4.1)
14
+ */
15
+ constructor(baseUrl: string) {
16
+ super();
17
+ this.baseUrl = baseUrl;
18
+ }
19
+
20
+ async connect(): Promise<void> {
21
+ try {
22
+ // Verify connection by making a test request
23
+ const response = await fetch(`${this.baseUrl}/proto-ver`, {
24
+ method: 'GET',
25
+ mode: 'cors',
26
+ });
27
+
28
+ if (!response.ok) {
29
+ throw new Error(`HTTP ${response.status}`);
30
+ }
31
+
32
+ this.connected = true;
33
+ } catch (error) {
34
+ this.connected = false;
35
+ throw new Error(
36
+ `SoftAP connection failed. Please ensure you are connected to the device's WiFi network. Error: ${error}`
37
+ );
38
+ }
39
+ }
40
+
41
+ disconnect(): void {
42
+ this.connected = false;
43
+ }
44
+
45
+ async sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array> {
46
+ if (!this.connected) {
47
+ throw new Error('Not connected to device');
48
+ }
49
+
50
+ try {
51
+ const response = await fetch(`${this.baseUrl}/${endpoint}`, {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Accept': 'text/plain',
55
+ 'Content-Type': 'application/x-www-form-urlencoded',
56
+ },
57
+ body: data.buffer as ArrayBuffer,
58
+ mode: 'cors',
59
+ });
60
+
61
+ if (!response.ok) {
62
+ throw new Error(`HTTP ${response.status}`);
63
+ }
64
+
65
+ const arrayBuffer = await response.arrayBuffer();
66
+ return new Uint8Array(arrayBuffer);
67
+ } catch (error) {
68
+ throw new Error(`SoftAP communication failed: ${error}`);
69
+ }
70
+ }
71
+
72
+ isConnected(): boolean {
73
+ return this.connected;
74
+ }
75
+
76
+ /**
77
+ * Set custom base URL
78
+ */
79
+ setBaseUrl(url: string): void {
80
+ this.baseUrl = url;
81
+ }
82
+
83
+ /**
84
+ * Get current base URL
85
+ */
86
+ getBaseUrl(): string {
87
+ return this.baseUrl;
88
+ }
89
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Base class for transport implementations
3
+ */
4
+ export abstract class Transport {
5
+ /**
6
+ * Connect to the device
7
+ */
8
+ abstract connect(): Promise<void>;
9
+
10
+ /**
11
+ * Disconnect from the device
12
+ */
13
+ abstract disconnect(): void;
14
+
15
+ /**
16
+ * Send data to a specific endpoint
17
+ */
18
+ abstract sendData(endpoint: string, data: Uint8Array): Promise<Uint8Array>;
19
+
20
+ /**
21
+ * Check if transport is connected
22
+ */
23
+ abstract isConnected(): boolean;
24
+ }
package/src/types.ts ADDED
@@ -0,0 +1,86 @@
1
+ export type ESPSecurityConfig = ESPSecurity1Config | ESPSecurity2Config;
2
+
3
+ export interface ESPSecurity1Config {
4
+ type: 'Security1';
5
+ proofOfPossession: string;
6
+ }
7
+
8
+ export interface ESPSecurity2Config {
9
+ type: 'Security2';
10
+ username: string;
11
+ password: string;
12
+ }
13
+
14
+ /**
15
+ * WiFi authentication modes
16
+ */
17
+ export enum ESPWifiAuthMode {
18
+ open = 0,
19
+ wep = 1,
20
+ wpa2Enterprise = 2,
21
+ wpa2Psk = 3,
22
+ wpaPsk = 4,
23
+ wpaWpa2Psk = 5,
24
+ wpa3Psk = 6,
25
+ wpa2Wpa3Psk = 7,
26
+ }
27
+
28
+ /**
29
+ * WiFi STA state
30
+ */
31
+ export enum ESPWifiStaState {
32
+ connected = 0,
33
+ connecting = 1,
34
+ disconnected = 2,
35
+ failed = 3,
36
+ }
37
+
38
+ /**
39
+ * WiFi scan failure reasons
40
+ */
41
+ export enum ESPConnectFailReason {
42
+ authError = 1,
43
+ networkNotFound = 2,
44
+ }
45
+
46
+ export interface ESPWifiStatus {
47
+ staState: ESPWifiStaState;
48
+ failedReason?: ESPConnectFailReason;
49
+ connected?: ESPWifiAp;
50
+ }
51
+
52
+ /**
53
+ * WiFi network information returned by device scan
54
+ */
55
+ export interface ESPWifiAp {
56
+ ssid: string;
57
+ rssi: number;
58
+ auth: ESPWifiAuthMode;
59
+ bssid: string;
60
+ channel: number;
61
+ }
62
+
63
+ /**
64
+ * Scan status response
65
+ */
66
+ export interface ESPScanStatusResponse {
67
+ scanFinished: boolean;
68
+ count: number;
69
+ }
70
+
71
+ /**
72
+ * Device interface for ESP provisioning
73
+ */
74
+ export interface ESPDeviceInterface {
75
+ connect(security?: ESPSecurityConfig): Promise<void>;
76
+
77
+ scanWifiList(): Promise<ESPWifiAp[]>;
78
+
79
+ disconnect(): void;
80
+
81
+ provision(ssid: string, passphrase: string): Promise<void>;
82
+
83
+ fetchWifiStatus(): Promise<ESPWifiStatus>;
84
+
85
+ getDevice(): BluetoothDevice | URL;
86
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Cryptographic utilities for ESP provisioning protocol
3
+ */
4
+
5
+ /**
6
+ * Generate random bytes
7
+ */
8
+ export function generateRandomBytes(length: number): Uint8Array {
9
+ const bytes = new Uint8Array(length);
10
+ crypto.getRandomValues(bytes);
11
+ return bytes;
12
+ }
13
+
14
+ /**
15
+ * Convert hex string to Uint8Array
16
+ */
17
+ export function hexToBytes(hex: string): Uint8Array {
18
+ const bytes = new Uint8Array(hex.length / 2);
19
+ for (let i = 0; i < hex.length; i += 2) {
20
+ bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
21
+ }
22
+ return bytes;
23
+ }
24
+
25
+ /**
26
+ * Convert Uint8Array to hex string
27
+ */
28
+ export function bytesToHex(bytes: Uint8Array): string {
29
+ return Array.from(bytes)
30
+ .map(b => b.toString(16).padStart(2, '0'))
31
+ .join('');
32
+ }
33
+
34
+ /**
35
+ * Convert string to Uint8Array
36
+ */
37
+ export function stringToBytes(str: string): Uint8Array {
38
+ return new TextEncoder().encode(str);
39
+ }
40
+
41
+ /**
42
+ * Convert Uint8Array to string
43
+ */
44
+ export function bytesToString(bytes: Uint8Array): string {
45
+ return new TextDecoder().decode(bytes);
46
+ }
47
+
48
+ /**
49
+ * SHA-256 hash function
50
+ */
51
+ export async function sha256(data: Uint8Array): Promise<Uint8Array> {
52
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data.buffer as ArrayBuffer);
53
+ return new Uint8Array(hashBuffer);
54
+ }
55
+
56
+ /**
57
+ * HMAC-SHA-256
58
+ */
59
+ export async function hmacSha256(key: Uint8Array, data: Uint8Array): Promise<Uint8Array> {
60
+ const cryptoKey = await crypto.subtle.importKey(
61
+ 'raw',
62
+ key.buffer as ArrayBuffer,
63
+ { name: 'HMAC', hash: 'SHA-256' },
64
+ false,
65
+ ['sign']
66
+ );
67
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, data.buffer as ArrayBuffer);
68
+ return new Uint8Array(signature);
69
+ }
70
+
71
+ /**
72
+ * AES-CTR encryption
73
+ */
74
+ export async function aesEncrypt(
75
+ key: Uint8Array,
76
+ counter: Uint8Array,
77
+ data: Uint8Array
78
+ ): Promise<Uint8Array> {
79
+ const cryptoKey = await crypto.subtle.importKey(
80
+ 'raw',
81
+ key.buffer as ArrayBuffer,
82
+ { name: 'AES-CTR' },
83
+ false,
84
+ ['encrypt']
85
+ );
86
+ const encrypted = await crypto.subtle.encrypt(
87
+ { name: 'AES-CTR', counter: counter.buffer as ArrayBuffer, length: 128 },
88
+ cryptoKey,
89
+ data.buffer as ArrayBuffer
90
+ );
91
+ return new Uint8Array(encrypted);
92
+ }
93
+
94
+ /**
95
+ * AES-CTR decryption
96
+ */
97
+ export async function aesDecrypt(
98
+ key: Uint8Array,
99
+ counter: Uint8Array,
100
+ data: Uint8Array
101
+ ): Promise<Uint8Array> {
102
+ const cryptoKey = await crypto.subtle.importKey(
103
+ 'raw',
104
+ key.buffer as ArrayBuffer,
105
+ { name: 'AES-CTR' },
106
+ false,
107
+ ['decrypt']
108
+ );
109
+ const decrypted = await crypto.subtle.decrypt(
110
+ { name: 'AES-CTR', counter: counter.buffer as ArrayBuffer, length: 128 },
111
+ cryptoKey,
112
+ data.buffer as ArrayBuffer
113
+ );
114
+ return new Uint8Array(decrypted);
115
+ }
116
+
117
+ /**
118
+ * Concatenate multiple Uint8Arrays
119
+ */
120
+ export function concatBytes(...arrays: Uint8Array[]): Uint8Array {
121
+ const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0);
122
+ const result = new Uint8Array(totalLength);
123
+ let offset = 0;
124
+ for (const arr of arrays) {
125
+ result.set(arr, offset);
126
+ offset += arr.length;
127
+ }
128
+ return result;
129
+ }