@bota.dev/react-native-app-sdk 2.0.0-beta.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/BotaDeviceSDK.podspec +55 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/android/build.gradle +76 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevices.kt +172 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycle.kt +100 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogs.kt +82 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidModels.kt +476 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTA.kt +58 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploads.kt +180 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordings.kt +751 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurity.kt +509 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFi.kt +125 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKEncryptedUploadV2Materials.kt +46 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKModule.kt +723 -0
- package/android/src/main/java/dev/bota/sdk/reactnative/BotaDeviceSDKPackage.kt +33 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidDevicesTest.kt +211 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLifecycleTest.kt +132 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidLogsTest.kt +57 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidOTATest.kt +75 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingUploadsTest.kt +147 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidRecordingsTest.kt +355 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidSecurityTest.kt +523 -0
- package/android/src/test/java/dev/bota/sdk/reactnative/BotaDeviceSDKAndroidWiFiTest.kt +100 -0
- package/generated/codegen-contract.json +3593 -0
- package/ios/BotaDeviceSDK.mm +1300 -0
- package/ios/BotaDeviceSDKAppleBridge.swift +2260 -0
- package/ios/BotaDeviceSDKAppleDevices.swift +191 -0
- package/ios/BotaDeviceSDKAppleLifecycle.swift +167 -0
- package/ios/BotaDeviceSDKAppleLogs.swift +79 -0
- package/ios/BotaDeviceSDKAppleOTA.swift +73 -0
- package/ios/BotaDeviceSDKAppleRecordingUploads.swift +178 -0
- package/ios/BotaDeviceSDKAppleRecordings.swift +670 -0
- package/ios/BotaDeviceSDKAppleSecurity.swift +586 -0
- package/ios/BotaDeviceSDKAppleWiFi.swift +128 -0
- package/ios/BotaDeviceSDKEncryptedUploadV2Materials.swift +99 -0
- package/lib/commonjs/BotaClient.js +194 -0
- package/lib/commonjs/BotaClient.js.map +1 -0
- package/lib/commonjs/ble/constants.js +10 -0
- package/lib/commonjs/ble/constants.js.map +1 -0
- package/lib/commonjs/ble/deviceLogs.js +50 -0
- package/lib/commonjs/ble/deviceLogs.js.map +1 -0
- package/lib/commonjs/cache/DeviceStateCache.js +6 -0
- package/lib/commonjs/cache/DeviceStateCache.js.map +1 -0
- package/lib/commonjs/client.js +746 -0
- package/lib/commonjs/client.js.map +1 -0
- package/lib/commonjs/compatibility/runtime.js +44 -0
- package/lib/commonjs/compatibility/runtime.js.map +1 -0
- package/lib/commonjs/index.js +123 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/managers/DeviceManager.js +647 -0
- package/lib/commonjs/managers/DeviceManager.js.map +1 -0
- package/lib/commonjs/managers/OTAManager.js +140 -0
- package/lib/commonjs/managers/OTAManager.js.map +1 -0
- package/lib/commonjs/managers/RecordingManager.js +444 -0
- package/lib/commonjs/managers/RecordingManager.js.map +1 -0
- package/lib/commonjs/managers/StreamingSession.js +175 -0
- package/lib/commonjs/managers/StreamingSession.js.map +1 -0
- package/lib/commonjs/managers/types.js +6 -0
- package/lib/commonjs/managers/types.js.map +1 -0
- package/lib/commonjs/models/Device.js +2 -0
- package/lib/commonjs/models/Device.js.map +1 -0
- package/lib/commonjs/models/Recording.js +2 -0
- package/lib/commonjs/models/Recording.js.map +1 -0
- package/lib/commonjs/models/Status.js +6 -0
- package/lib/commonjs/models/Status.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js +9 -0
- package/lib/commonjs/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/commonjs/sync/syncStatus.js +178 -0
- package/lib/commonjs/sync/syncStatus.js.map +1 -0
- package/lib/commonjs/utils/errors.js +194 -0
- package/lib/commonjs/utils/errors.js.map +1 -0
- package/lib/commonjs/utils/logger.js +84 -0
- package/lib/commonjs/utils/logger.js.map +1 -0
- package/lib/module/BotaClient.js +189 -0
- package/lib/module/BotaClient.js.map +1 -0
- package/lib/module/ble/constants.js +6 -0
- package/lib/module/ble/constants.js.map +1 -0
- package/lib/module/ble/deviceLogs.js +45 -0
- package/lib/module/ble/deviceLogs.js.map +1 -0
- package/lib/module/cache/DeviceStateCache.js +4 -0
- package/lib/module/cache/DeviceStateCache.js.map +1 -0
- package/lib/module/client.js +740 -0
- package/lib/module/client.js.map +1 -0
- package/lib/module/compatibility/runtime.js +35 -0
- package/lib/module/compatibility/runtime.js.map +1 -0
- package/lib/module/index.js +17 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/managers/DeviceManager.js +641 -0
- package/lib/module/managers/DeviceManager.js.map +1 -0
- package/lib/module/managers/OTAManager.js +134 -0
- package/lib/module/managers/OTAManager.js.map +1 -0
- package/lib/module/managers/RecordingManager.js +438 -0
- package/lib/module/managers/RecordingManager.js.map +1 -0
- package/lib/module/managers/StreamingSession.js +168 -0
- package/lib/module/managers/StreamingSession.js.map +1 -0
- package/lib/module/managers/types.js +4 -0
- package/lib/module/managers/types.js.map +1 -0
- package/lib/module/models/Device.js +2 -0
- package/lib/module/models/Device.js.map +1 -0
- package/lib/module/models/Recording.js +2 -0
- package/lib/module/models/Recording.js.map +1 -0
- package/lib/module/models/Status.js +4 -0
- package/lib/module/models/Status.js.map +1 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js +5 -0
- package/lib/module/specs/NativeBotaDeviceSDK.js.map +1 -0
- package/lib/module/sync/syncStatus.js +174 -0
- package/lib/module/sync/syncStatus.js.map +1 -0
- package/lib/module/utils/errors.js +183 -0
- package/lib/module/utils/errors.js.map +1 -0
- package/lib/module/utils/logger.js +80 -0
- package/lib/module/utils/logger.js.map +1 -0
- package/lib/typescript/BotaClient.d.ts +40 -0
- package/lib/typescript/BotaClient.d.ts.map +1 -0
- package/lib/typescript/ble/constants.d.ts +4 -0
- package/lib/typescript/ble/constants.d.ts.map +1 -0
- package/lib/typescript/ble/deviceLogs.d.ts +10 -0
- package/lib/typescript/ble/deviceLogs.d.ts.map +1 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts +14 -0
- package/lib/typescript/cache/DeviceStateCache.d.ts.map +1 -0
- package/lib/typescript/client.d.ts +208 -0
- package/lib/typescript/client.d.ts.map +1 -0
- package/lib/typescript/compatibility/runtime.d.ts +11 -0
- package/lib/typescript/compatibility/runtime.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +19 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/managers/DeviceManager.d.ts +117 -0
- package/lib/typescript/managers/DeviceManager.d.ts.map +1 -0
- package/lib/typescript/managers/OTAManager.d.ts +25 -0
- package/lib/typescript/managers/OTAManager.d.ts.map +1 -0
- package/lib/typescript/managers/RecordingManager.d.ts +54 -0
- package/lib/typescript/managers/RecordingManager.d.ts.map +1 -0
- package/lib/typescript/managers/StreamingSession.d.ts +35 -0
- package/lib/typescript/managers/StreamingSession.d.ts.map +1 -0
- package/lib/typescript/managers/types.d.ts +19 -0
- package/lib/typescript/managers/types.d.ts.map +1 -0
- package/lib/typescript/models/Device.d.ts +392 -0
- package/lib/typescript/models/Device.d.ts.map +1 -0
- package/lib/typescript/models/Recording.d.ts +259 -0
- package/lib/typescript/models/Recording.d.ts.map +1 -0
- package/lib/typescript/models/Status.d.ts +106 -0
- package/lib/typescript/models/Status.d.ts.map +1 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts +393 -0
- package/lib/typescript/specs/NativeBotaDeviceSDK.d.ts.map +1 -0
- package/lib/typescript/sync/syncStatus.d.ts +123 -0
- package/lib/typescript/sync/syncStatus.d.ts.map +1 -0
- package/lib/typescript/utils/errors.d.ts +83 -0
- package/lib/typescript/utils/errors.d.ts.map +1 -0
- package/lib/typescript/utils/logger.d.ts +36 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/package.json +100 -0
- package/scripts/bota_device_sdk_spm_workaround.rb +53 -0
- package/src/BotaClient.ts +231 -0
- package/src/ble/constants.ts +3 -0
- package/src/ble/deviceLogs.ts +61 -0
- package/src/cache/DeviceStateCache.ts +20 -0
- package/src/client.ts +1410 -0
- package/src/compatibility/runtime.ts +55 -0
- package/src/index.ts +146 -0
- package/src/managers/DeviceManager.ts +927 -0
- package/src/managers/OTAManager.ts +195 -0
- package/src/managers/RecordingManager.ts +578 -0
- package/src/managers/StreamingSession.ts +212 -0
- package/src/managers/types.ts +36 -0
- package/src/models/Device.ts +484 -0
- package/src/models/Recording.ts +281 -0
- package/src/models/Status.ts +128 -0
- package/src/specs/NativeBotaDeviceSDK.ts +542 -0
- package/src/sync/syncStatus.ts +243 -0
- package/src/utils/errors.ts +327 -0
- package/src/utils/logger.ts +122 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import EventEmitter from 'eventemitter3';
|
|
4
|
+
import { getCompatibilityClient, subscribeToCompatibilityDisconnections } from '../compatibility/runtime';
|
|
5
|
+
import { DeviceError, ProvisioningError } from '../utils/errors';
|
|
6
|
+
export class DeviceManager extends EventEmitter {
|
|
7
|
+
discoveredDevices = new Map();
|
|
8
|
+
connectedDevices = new Map();
|
|
9
|
+
knownBleIds = new Map();
|
|
10
|
+
cache = new Map();
|
|
11
|
+
cacheListeners = new Set();
|
|
12
|
+
statusSubscriptions = new Map();
|
|
13
|
+
statusCallbacks = new Map();
|
|
14
|
+
wifiStatusSubscriptions = new Map();
|
|
15
|
+
logSubscriptions = new Map();
|
|
16
|
+
recordingStateCache = new Map();
|
|
17
|
+
recordingStatePending = new Map();
|
|
18
|
+
recordingStateSubscriptions = new Map();
|
|
19
|
+
reconnectInFlight = new Map();
|
|
20
|
+
reconnectChain = Promise.resolve();
|
|
21
|
+
scanSubscription = null;
|
|
22
|
+
scanActive = false;
|
|
23
|
+
autoReconnectEnabled = false;
|
|
24
|
+
autoReconnectSerial = null;
|
|
25
|
+
autoReconnectTimer = null;
|
|
26
|
+
autoReconnectAttempting = false;
|
|
27
|
+
userDisconnected = false;
|
|
28
|
+
destroyed = false;
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this.client = getCompatibilityClient();
|
|
32
|
+
this.disconnectionSubscription = subscribeToCompatibilityDisconnections(this.client, error => this.handleNativeDisconnection(error));
|
|
33
|
+
}
|
|
34
|
+
async initialize() {}
|
|
35
|
+
async startScan(options = {}) {
|
|
36
|
+
this.assertAlive();
|
|
37
|
+
if (this.scanActive) return;
|
|
38
|
+
this.emit('scanStarted');
|
|
39
|
+
try {
|
|
40
|
+
this.scanSubscription = await this.client.devices.startScan(options, device => {
|
|
41
|
+
this.discoveredDevices.set(device.id, device);
|
|
42
|
+
this.emit('deviceDiscovered', device);
|
|
43
|
+
});
|
|
44
|
+
this.scanActive = true;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.emit('scanError', asError(error));
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
stopScan() {
|
|
51
|
+
if (!this.scanActive) return;
|
|
52
|
+
this.scanActive = false;
|
|
53
|
+
this.scanSubscription?.remove();
|
|
54
|
+
this.scanSubscription = null;
|
|
55
|
+
void this.client.devices.stopScan().catch(error => {
|
|
56
|
+
this.emit('scanError', asError(error));
|
|
57
|
+
});
|
|
58
|
+
this.emit('scanStopped');
|
|
59
|
+
}
|
|
60
|
+
getDiscoveredDevices() {
|
|
61
|
+
return Array.from(this.discoveredDevices.values());
|
|
62
|
+
}
|
|
63
|
+
getConnectedDevices() {
|
|
64
|
+
return Array.from(this.connectedDevices.values());
|
|
65
|
+
}
|
|
66
|
+
getKnownBleIds() {
|
|
67
|
+
return Object.fromEntries(this.knownBleIds);
|
|
68
|
+
}
|
|
69
|
+
async connect(device, priority = 'user') {
|
|
70
|
+
void priority;
|
|
71
|
+
this.assertAlive();
|
|
72
|
+
this.userDisconnected = false;
|
|
73
|
+
this.emit('connectionStateChanged', device.id, 'connecting');
|
|
74
|
+
try {
|
|
75
|
+
const connected = await this.client.devices.connect(device);
|
|
76
|
+
this.rememberConnected(connected);
|
|
77
|
+
if (this.autoReconnectEnabled) this.ensureStatusWatchdog(connected);
|
|
78
|
+
this.emit('connectionStateChanged', connected.id, 'connected');
|
|
79
|
+
this.emit('deviceConnected', connected);
|
|
80
|
+
return connected;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.emit('connectionStateChanged', device.id, 'disconnected');
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async reconnect(serialNumber, options) {
|
|
87
|
+
this.assertAlive();
|
|
88
|
+
const existing = this.findConnectedBySerial(serialNumber);
|
|
89
|
+
if (existing) return existing;
|
|
90
|
+
this.userDisconnected = false;
|
|
91
|
+
const inFlight = this.reconnectInFlight.get(serialNumber);
|
|
92
|
+
if (inFlight) return inFlight;
|
|
93
|
+
const run = this.reconnectChain.catch(() => undefined).then(async () => {
|
|
94
|
+
const connected = await this.client.devices.reconnect(serialNumber, options);
|
|
95
|
+
this.rememberConnected(connected);
|
|
96
|
+
if (this.autoReconnectEnabled) this.ensureStatusWatchdog(connected);
|
|
97
|
+
this.emit('connectionStateChanged', connected.id, 'connected');
|
|
98
|
+
this.emit('deviceConnected', connected);
|
|
99
|
+
return connected;
|
|
100
|
+
});
|
|
101
|
+
this.reconnectChain = run.catch(() => undefined);
|
|
102
|
+
this.reconnectInFlight.set(serialNumber, run);
|
|
103
|
+
void run.finally(() => {
|
|
104
|
+
if (this.reconnectInFlight.get(serialNumber) === run) {
|
|
105
|
+
this.reconnectInFlight.delete(serialNumber);
|
|
106
|
+
}
|
|
107
|
+
}).catch(() => undefined);
|
|
108
|
+
return run;
|
|
109
|
+
}
|
|
110
|
+
async disconnect(device) {
|
|
111
|
+
this.assertAlive();
|
|
112
|
+
this.userDisconnected = true;
|
|
113
|
+
this.stopAutoReconnectLoop();
|
|
114
|
+
this.emit('connectionStateChanged', device.id, 'disconnecting');
|
|
115
|
+
await this.removeOwned(this.statusSubscriptions, device.id);
|
|
116
|
+
this.statusCallbacks.delete(device.id);
|
|
117
|
+
await this.removeOwned(this.wifiStatusSubscriptions, device.id);
|
|
118
|
+
await this.removeOwned(this.logSubscriptions, device.id);
|
|
119
|
+
await this.removeOwned(this.recordingStateSubscriptions, device.id);
|
|
120
|
+
await this.client.devices.disconnect();
|
|
121
|
+
this.connectedDevices.delete(device.id);
|
|
122
|
+
this.recordingStateCache.delete(device.id);
|
|
123
|
+
this.recordingStatePending.delete(device.id);
|
|
124
|
+
this.emit('connectionStateChanged', device.id, 'disconnected');
|
|
125
|
+
this.emit('deviceDisconnected', device.id);
|
|
126
|
+
}
|
|
127
|
+
isConnected(deviceId) {
|
|
128
|
+
return this.connectedDevices.get(deviceId)?.connectionState === 'connected';
|
|
129
|
+
}
|
|
130
|
+
async getStatus(device) {
|
|
131
|
+
this.requireConnected(device.id);
|
|
132
|
+
return this.client.devices.readStatus();
|
|
133
|
+
}
|
|
134
|
+
async isProvisioned(device) {
|
|
135
|
+
this.requireConnected(device.id);
|
|
136
|
+
return this.client.controls.isProvisioned(device);
|
|
137
|
+
}
|
|
138
|
+
async readPublicKey(device) {
|
|
139
|
+
this.requireConnected(device.id);
|
|
140
|
+
return this.client.controls.readPublicKey(device);
|
|
141
|
+
}
|
|
142
|
+
async readAuthNonce(device) {
|
|
143
|
+
this.requireConnected(device.id);
|
|
144
|
+
return this.client.controls.readAuthNonce(device);
|
|
145
|
+
}
|
|
146
|
+
async setApiEndpoint(device, environment) {
|
|
147
|
+
this.requireConnected(device.id);
|
|
148
|
+
await this.client.controls.setApiEndpoint(device, environment);
|
|
149
|
+
}
|
|
150
|
+
async deliverCert(device, certPem, privkeyPem) {
|
|
151
|
+
this.requireConnected(device.id);
|
|
152
|
+
await this.client.controls.deliverCertificate(device, certPem, privkeyPem);
|
|
153
|
+
}
|
|
154
|
+
async deliverBackendPubkey(device, pubkey) {
|
|
155
|
+
this.requireConnected(device.id);
|
|
156
|
+
await this.client.controls.deliverBackendPublicKey(device, pubkey);
|
|
157
|
+
}
|
|
158
|
+
async writeGrant(device, grantBlob) {
|
|
159
|
+
this.requireConnected(device.id);
|
|
160
|
+
await this.client.controls.writeGrant(device, grantBlob);
|
|
161
|
+
}
|
|
162
|
+
async syncTime(deviceId) {
|
|
163
|
+
await this.client.controls.syncTime(this.requireConnected(deviceId));
|
|
164
|
+
}
|
|
165
|
+
async provision(device, deviceToken, environment = 'production', options) {
|
|
166
|
+
this.requireConnected(device.id);
|
|
167
|
+
const provision = () => this.client.provisioning.provision(device, async () => ({
|
|
168
|
+
apiEndpoint: String.fromCharCode(environmentCode(environment)),
|
|
169
|
+
deviceToken,
|
|
170
|
+
mtu: device.mtu
|
|
171
|
+
}));
|
|
172
|
+
try {
|
|
173
|
+
await provision();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (!isAlreadyPaired(error) || !options?.fetchDeprovisionGrant) {
|
|
176
|
+
throw provisioningError(error, device.id);
|
|
177
|
+
}
|
|
178
|
+
const nonce = await this.readAuthNonce(device).catch(() => null);
|
|
179
|
+
const grantBlob = await options.fetchDeprovisionGrant(nonce);
|
|
180
|
+
const result = await this.deprovision(device, grantBlob);
|
|
181
|
+
if (!result.success) {
|
|
182
|
+
throw new ProvisioningError(`Auto-deprovision failed: ${result.error ?? 'unknown'}`, 'PROVISIONING_FAILED', device.id);
|
|
183
|
+
}
|
|
184
|
+
await provision();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async deprovision(device, grantBlob) {
|
|
188
|
+
this.requireConnected(device.id);
|
|
189
|
+
return this.client.provisioning.deprovision(device, grantBlob);
|
|
190
|
+
}
|
|
191
|
+
async factoryReset(device) {
|
|
192
|
+
this.requireConnected(device.id);
|
|
193
|
+
throw ProvisioningError.invalidToken(device.id);
|
|
194
|
+
}
|
|
195
|
+
async bleFactoryReset(device, grantBlob, persistResult) {
|
|
196
|
+
this.requireConnected(device.id);
|
|
197
|
+
let persisted;
|
|
198
|
+
try {
|
|
199
|
+
await this.client.factoryReset.factoryReset(device, {
|
|
200
|
+
commandId: nextCompatibilityResetCommandId(),
|
|
201
|
+
bindingGeneration: 0
|
|
202
|
+
}, async () => grantBlob, async result => {
|
|
203
|
+
await persistResult(result);
|
|
204
|
+
persisted = result;
|
|
205
|
+
});
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const rejected = factoryResetRejection(error);
|
|
208
|
+
if (rejected) return rejected;
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
if (!persisted) {
|
|
212
|
+
throw new Error('Factory reset completed without a persisted result');
|
|
213
|
+
}
|
|
214
|
+
return persisted;
|
|
215
|
+
}
|
|
216
|
+
async resumeBleFactoryReset(device, persistResult) {
|
|
217
|
+
this.requireConnected(device.id);
|
|
218
|
+
let persisted;
|
|
219
|
+
try {
|
|
220
|
+
await this.client.factoryReset.resumePendingFactoryReset(device, 0, async result => {
|
|
221
|
+
await persistResult(result);
|
|
222
|
+
persisted = result;
|
|
223
|
+
});
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const rejected = factoryResetRejection(error);
|
|
226
|
+
if (rejected) return rejected;
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
if (!persisted) {
|
|
230
|
+
throw new Error('Factory reset resume completed without a persisted result');
|
|
231
|
+
}
|
|
232
|
+
return persisted;
|
|
233
|
+
}
|
|
234
|
+
async requestStartRecording(device, grantOrFetcher, _options) {
|
|
235
|
+
this.requireConnected(device.id);
|
|
236
|
+
if (typeof grantOrFetcher === 'string') {
|
|
237
|
+
return this.runRecordingControl(device, grantOrFetcher, 'start');
|
|
238
|
+
}
|
|
239
|
+
const grantBlob = await this.fetchRecordingGrant(device, grantOrFetcher);
|
|
240
|
+
return this.runRecordingControl(device, grantBlob, 'start');
|
|
241
|
+
}
|
|
242
|
+
async requestStopRecording(device, grantOrFetcher, _options) {
|
|
243
|
+
this.requireConnected(device.id);
|
|
244
|
+
if (typeof grantOrFetcher === 'string') {
|
|
245
|
+
return this.runRecordingControl(device, grantOrFetcher, 'stop');
|
|
246
|
+
}
|
|
247
|
+
const grantBlob = await this.fetchRecordingGrant(device, grantOrFetcher);
|
|
248
|
+
return this.runRecordingControl(device, grantBlob, 'stop');
|
|
249
|
+
}
|
|
250
|
+
async getRecordingState(device) {
|
|
251
|
+
this.requireConnected(device.id);
|
|
252
|
+
const pending = this.recordingStatePending.get(device.id);
|
|
253
|
+
if (pending) return pending;
|
|
254
|
+
try {
|
|
255
|
+
return await this.readAndCacheRecordingState(device);
|
|
256
|
+
} catch {
|
|
257
|
+
return this.cachedRecordingState(device.id);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
subscribeToRecordingState(device, callback) {
|
|
261
|
+
this.requireConnected(device.id);
|
|
262
|
+
const promise = this.replaceOwned(this.recordingStateSubscriptions, device.id, this.client.controls.subscribeToRecordingState(device, callback));
|
|
263
|
+
return idempotentRemoval(() => {
|
|
264
|
+
void this.removeExpected(this.recordingStateSubscriptions, device.id, promise);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
subscribeToStatus(device, callback) {
|
|
268
|
+
this.requireConnected(device.id);
|
|
269
|
+
const callbacks = this.statusCallbacks.get(device.id) ?? new Set();
|
|
270
|
+
callbacks.add(callback);
|
|
271
|
+
this.statusCallbacks.set(device.id, callbacks);
|
|
272
|
+
this.ensureStatusWatchdog(device);
|
|
273
|
+
return idempotentRemoval(() => {
|
|
274
|
+
callbacks.delete(callback);
|
|
275
|
+
if (callbacks.size === 0) {
|
|
276
|
+
this.statusCallbacks.delete(device.id);
|
|
277
|
+
if (!this.autoReconnectEnabled) {
|
|
278
|
+
void this.removeOwned(this.statusSubscriptions, device.id).catch(() => undefined);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async subscribeToDeviceLogs(device, callback) {
|
|
284
|
+
this.requireConnected(device.id);
|
|
285
|
+
const promise = this.replaceOwned(this.logSubscriptions, device.id, this.client.logs.subscribe(device, callback));
|
|
286
|
+
await promise;
|
|
287
|
+
return idempotentRemoval(() => {
|
|
288
|
+
void this.removeExpected(this.logSubscriptions, device.id, promise);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async readConnectionSettings(device) {
|
|
292
|
+
this.requireConnected(device.id);
|
|
293
|
+
return this.client.provisioning.readConnectionSettings(device);
|
|
294
|
+
}
|
|
295
|
+
async writeConnectionSettings(device, settings) {
|
|
296
|
+
this.requireConnected(device.id);
|
|
297
|
+
await this.client.provisioning.writeConnectionSettings(device, settings);
|
|
298
|
+
}
|
|
299
|
+
async configureWiFi(deviceId, credentials, grant) {
|
|
300
|
+
return this.client.wifi.configure(this.requireConnected(deviceId), credentials, grant);
|
|
301
|
+
}
|
|
302
|
+
async disconnectWiFi(deviceId) {
|
|
303
|
+
return this.client.wifi.disconnect(this.requireConnected(deviceId));
|
|
304
|
+
}
|
|
305
|
+
async getWiFiStatus(deviceId) {
|
|
306
|
+
const device = this.requireConnected(deviceId);
|
|
307
|
+
const status = await this.client.wifi.readStatus(device);
|
|
308
|
+
this.updateCachedDeviceState(device.serialNumber, {
|
|
309
|
+
wifiStatus: status
|
|
310
|
+
});
|
|
311
|
+
return status;
|
|
312
|
+
}
|
|
313
|
+
subscribeToWiFiStatus(deviceId, callback) {
|
|
314
|
+
const device = this.requireConnected(deviceId);
|
|
315
|
+
const promise = this.replaceOwned(this.wifiStatusSubscriptions, device.id, this.client.wifi.subscribeToStatus(device, status => {
|
|
316
|
+
this.updateCachedDeviceState(device.serialNumber, {
|
|
317
|
+
wifiStatus: status
|
|
318
|
+
});
|
|
319
|
+
callback(status);
|
|
320
|
+
}));
|
|
321
|
+
return {
|
|
322
|
+
remove: idempotentRemoval(() => {
|
|
323
|
+
void this.removeExpected(this.wifiStatusSubscriptions, device.id, promise);
|
|
324
|
+
})
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
async scanWiFiNetworks(device) {
|
|
328
|
+
this.requireConnected(device.id);
|
|
329
|
+
return this.client.wifi.scanNetworks(device);
|
|
330
|
+
}
|
|
331
|
+
getCachedDeviceState(serialNumber) {
|
|
332
|
+
return this.cache.get(serialNumber) ?? null;
|
|
333
|
+
}
|
|
334
|
+
getCachedWiFiStatus(serialNumber) {
|
|
335
|
+
return this.cache.get(serialNumber)?.wifiStatus ?? null;
|
|
336
|
+
}
|
|
337
|
+
updateCachedDeviceState(serialNumber, patch) {
|
|
338
|
+
const previous = this.cache.get(serialNumber);
|
|
339
|
+
const wifiStatus = mergeWiFiStatus(previous?.wifiStatus, patch.wifiStatus);
|
|
340
|
+
const state = {
|
|
341
|
+
...(wifiStatus === undefined ? {} : {
|
|
342
|
+
wifiStatus
|
|
343
|
+
}),
|
|
344
|
+
updatedAt: Date.now()
|
|
345
|
+
};
|
|
346
|
+
this.cache.set(serialNumber, state);
|
|
347
|
+
for (const listener of this.cacheListeners) listener(serialNumber, patch, state);
|
|
348
|
+
}
|
|
349
|
+
clearCachedDeviceState(serialNumber) {
|
|
350
|
+
this.cache.delete(serialNumber);
|
|
351
|
+
}
|
|
352
|
+
clearAllCachedDeviceStates() {
|
|
353
|
+
this.cache.clear();
|
|
354
|
+
}
|
|
355
|
+
enableAutoReconnect(serialNumber) {
|
|
356
|
+
this.assertAlive();
|
|
357
|
+
this.autoReconnectSerial = serialNumber;
|
|
358
|
+
this.autoReconnectEnabled = true;
|
|
359
|
+
this.userDisconnected = false;
|
|
360
|
+
const connected = this.findConnectedBySerial(serialNumber);
|
|
361
|
+
if (connected) {
|
|
362
|
+
this.ensureStatusWatchdog(connected);
|
|
363
|
+
} else {
|
|
364
|
+
this.startAutoReconnectLoop();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
disableAutoReconnect() {
|
|
368
|
+
const serialNumber = this.autoReconnectSerial;
|
|
369
|
+
this.autoReconnectEnabled = false;
|
|
370
|
+
this.autoReconnectSerial = null;
|
|
371
|
+
this.userDisconnected = false;
|
|
372
|
+
this.stopAutoReconnectLoop();
|
|
373
|
+
const connected = serialNumber ? this.findConnectedBySerial(serialNumber) : null;
|
|
374
|
+
if (connected && !this.statusCallbacks.has(connected.id)) {
|
|
375
|
+
void this.removeOwned(this.statusSubscriptions, connected.id).catch(() => undefined);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
onCachedDeviceStateChanged(listener) {
|
|
379
|
+
this.cacheListeners.add(listener);
|
|
380
|
+
return {
|
|
381
|
+
remove: () => this.cacheListeners.delete(listener)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
destroy() {
|
|
385
|
+
if (this.destroyed) return;
|
|
386
|
+
this.destroyed = true;
|
|
387
|
+
this.disconnectionSubscription.remove();
|
|
388
|
+
this.disableAutoReconnect();
|
|
389
|
+
this.stopScan();
|
|
390
|
+
for (const [deviceId] of this.statusSubscriptions) {
|
|
391
|
+
void this.removeOwned(this.statusSubscriptions, deviceId);
|
|
392
|
+
}
|
|
393
|
+
for (const [deviceId] of this.wifiStatusSubscriptions) {
|
|
394
|
+
void this.removeOwned(this.wifiStatusSubscriptions, deviceId);
|
|
395
|
+
}
|
|
396
|
+
for (const [deviceId] of this.logSubscriptions) {
|
|
397
|
+
void this.removeOwned(this.logSubscriptions, deviceId);
|
|
398
|
+
}
|
|
399
|
+
for (const [deviceId] of this.recordingStateSubscriptions) {
|
|
400
|
+
void this.removeOwned(this.recordingStateSubscriptions, deviceId);
|
|
401
|
+
}
|
|
402
|
+
this.connectedDevices.clear();
|
|
403
|
+
this.recordingStateCache.clear();
|
|
404
|
+
this.recordingStatePending.clear();
|
|
405
|
+
this.statusCallbacks.clear();
|
|
406
|
+
this.cache.clear();
|
|
407
|
+
this.cacheListeners.clear();
|
|
408
|
+
this.removeAllListeners();
|
|
409
|
+
}
|
|
410
|
+
rememberConnected(device) {
|
|
411
|
+
this.connectedDevices.set(device.id, device);
|
|
412
|
+
this.knownBleIds.set(device.serialNumber, device.id);
|
|
413
|
+
}
|
|
414
|
+
ensureStatusWatchdog(device) {
|
|
415
|
+
if (this.statusSubscriptions.has(device.id)) return;
|
|
416
|
+
const subscription = this.client.devices.subscribeToStatus(status => {
|
|
417
|
+
for (const callback of this.statusCallbacks.get(device.id) ?? []) {
|
|
418
|
+
callback(status);
|
|
419
|
+
}
|
|
420
|
+
this.emit('deviceStatusUpdated', device.id, status);
|
|
421
|
+
});
|
|
422
|
+
this.statusSubscriptions.set(device.id, subscription);
|
|
423
|
+
void subscription.catch(() => {
|
|
424
|
+
if (this.statusSubscriptions.get(device.id) === subscription) {
|
|
425
|
+
this.statusSubscriptions.delete(device.id);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
handleNativeDisconnection(error) {
|
|
430
|
+
if (this.destroyed || this.userDisconnected) return;
|
|
431
|
+
for (const device of this.connectedDevices.values()) {
|
|
432
|
+
this.connectedDevices.delete(device.id);
|
|
433
|
+
this.recordingStateCache.delete(device.id);
|
|
434
|
+
this.recordingStatePending.delete(device.id);
|
|
435
|
+
this.statusCallbacks.delete(device.id);
|
|
436
|
+
void this.removeOwned(this.statusSubscriptions, device.id).catch(() => undefined);
|
|
437
|
+
void this.removeOwned(this.wifiStatusSubscriptions, device.id).catch(() => undefined);
|
|
438
|
+
void this.removeOwned(this.logSubscriptions, device.id).catch(() => undefined);
|
|
439
|
+
void this.removeOwned(this.recordingStateSubscriptions, device.id).catch(() => undefined);
|
|
440
|
+
this.emit('connectionStateChanged', device.id, 'disconnected');
|
|
441
|
+
this.emit('deviceDisconnected', device.id, error);
|
|
442
|
+
if (this.autoReconnectEnabled && this.autoReconnectSerial === device.serialNumber) {
|
|
443
|
+
this.startAutoReconnectLoop();
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
startAutoReconnectLoop() {
|
|
448
|
+
if (this.autoReconnectTimer || !this.autoReconnectSerial) return;
|
|
449
|
+
const attempt = async () => {
|
|
450
|
+
const serialNumber = this.autoReconnectSerial;
|
|
451
|
+
if (!this.autoReconnectEnabled || !serialNumber || this.userDisconnected || this.destroyed) {
|
|
452
|
+
this.stopAutoReconnectLoop();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (this.findConnectedBySerial(serialNumber)) {
|
|
456
|
+
this.stopAutoReconnectLoop();
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (this.autoReconnectAttempting) return;
|
|
460
|
+
this.autoReconnectAttempting = true;
|
|
461
|
+
try {
|
|
462
|
+
await this.reconnect(serialNumber, {
|
|
463
|
+
scanTimeout: 5_000
|
|
464
|
+
});
|
|
465
|
+
this.stopAutoReconnectLoop();
|
|
466
|
+
} catch {
|
|
467
|
+
// The timer owns retries; callers still receive errors from reconnect().
|
|
468
|
+
} finally {
|
|
469
|
+
this.autoReconnectAttempting = false;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
void attempt();
|
|
473
|
+
this.autoReconnectTimer = setInterval(() => void attempt(), 3_000);
|
|
474
|
+
}
|
|
475
|
+
stopAutoReconnectLoop() {
|
|
476
|
+
if (!this.autoReconnectTimer) return;
|
|
477
|
+
clearInterval(this.autoReconnectTimer);
|
|
478
|
+
this.autoReconnectTimer = null;
|
|
479
|
+
}
|
|
480
|
+
findConnectedBySerial(serialNumber) {
|
|
481
|
+
for (const device of this.connectedDevices.values()) {
|
|
482
|
+
if (device.serialNumber === serialNumber && device.connectionState === 'connected') {
|
|
483
|
+
return device;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
requireConnected(deviceId) {
|
|
489
|
+
const device = this.connectedDevices.get(deviceId);
|
|
490
|
+
if (!device || device.connectionState !== 'connected') {
|
|
491
|
+
throw DeviceError.notConnected(deviceId);
|
|
492
|
+
}
|
|
493
|
+
return device;
|
|
494
|
+
}
|
|
495
|
+
async fetchRecordingGrant(device, fetcher) {
|
|
496
|
+
const nonce = await this.readAuthNonce(device).catch(() => null);
|
|
497
|
+
return fetcher(nonce);
|
|
498
|
+
}
|
|
499
|
+
async runRecordingControl(device, grantBlob, command) {
|
|
500
|
+
const operation = command === 'start' ? this.client.controls.requestStartRecording(device, grantBlob) : this.client.controls.requestStopRecording(device, grantBlob);
|
|
501
|
+
const pendingState = operation.then(() => this.readAndCacheRecordingState(device), () => this.cachedRecordingState(device.id));
|
|
502
|
+
this.recordingStatePending.set(device.id, pendingState);
|
|
503
|
+
try {
|
|
504
|
+
const result = await operation;
|
|
505
|
+
await pendingState;
|
|
506
|
+
return result;
|
|
507
|
+
} catch (error) {
|
|
508
|
+
await pendingState;
|
|
509
|
+
throw error;
|
|
510
|
+
} finally {
|
|
511
|
+
if (this.recordingStatePending.get(device.id) === pendingState) {
|
|
512
|
+
this.recordingStatePending.delete(device.id);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
async readAndCacheRecordingState(device) {
|
|
517
|
+
const state = await this.client.controls.readRecordingState(device);
|
|
518
|
+
this.recordingStateCache.set(device.id, state);
|
|
519
|
+
return state;
|
|
520
|
+
}
|
|
521
|
+
cachedRecordingState(deviceId) {
|
|
522
|
+
return this.recordingStateCache.get(deviceId) ?? {
|
|
523
|
+
active: false,
|
|
524
|
+
initiatedBy: 'local'
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
replaceOwned(owner, deviceId, next) {
|
|
528
|
+
void this.removeOwned(owner, deviceId);
|
|
529
|
+
owner.set(deviceId, next);
|
|
530
|
+
void next.catch(() => {
|
|
531
|
+
if (owner.get(deviceId) === next) owner.delete(deviceId);
|
|
532
|
+
});
|
|
533
|
+
return next;
|
|
534
|
+
}
|
|
535
|
+
async removeExpected(owner, deviceId, expected) {
|
|
536
|
+
if (owner.get(deviceId) !== expected) return;
|
|
537
|
+
owner.delete(deviceId);
|
|
538
|
+
await (await expected).remove();
|
|
539
|
+
}
|
|
540
|
+
async removeOwned(owner, deviceId) {
|
|
541
|
+
const subscription = owner.get(deviceId);
|
|
542
|
+
if (!subscription) return;
|
|
543
|
+
owner.delete(deviceId);
|
|
544
|
+
await (await subscription).remove();
|
|
545
|
+
}
|
|
546
|
+
assertAlive() {
|
|
547
|
+
if (this.destroyed) throw new Error('DeviceManager has been destroyed');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const idempotentRemoval = remove => {
|
|
551
|
+
let removed = false;
|
|
552
|
+
return () => {
|
|
553
|
+
if (removed) return;
|
|
554
|
+
removed = true;
|
|
555
|
+
remove();
|
|
556
|
+
};
|
|
557
|
+
};
|
|
558
|
+
const mergeWiFiStatus = (previous, patch) => {
|
|
559
|
+
if (patch === undefined) return previous;
|
|
560
|
+
if (patch === null) return undefined;
|
|
561
|
+
return {
|
|
562
|
+
status: patch.status ?? previous?.status ?? 'idle',
|
|
563
|
+
...((patch.signalStrength ?? previous?.signalStrength) === undefined ? {} : {
|
|
564
|
+
signalStrength: patch.signalStrength ?? previous?.signalStrength
|
|
565
|
+
}),
|
|
566
|
+
...((patch.ssid ?? previous?.ssid) === undefined ? {} : {
|
|
567
|
+
ssid: patch.ssid ?? previous?.ssid
|
|
568
|
+
}),
|
|
569
|
+
...((patch.lastError ?? previous?.lastError) === undefined ? {} : {
|
|
570
|
+
lastError: patch.lastError ?? previous?.lastError
|
|
571
|
+
})
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
const asError = error => error instanceof Error ? error : new Error(String(error));
|
|
575
|
+
const environmentCode = environment => {
|
|
576
|
+
switch (environment) {
|
|
577
|
+
case 'development':
|
|
578
|
+
return 0;
|
|
579
|
+
case 'production':
|
|
580
|
+
return 1;
|
|
581
|
+
case 'gamma':
|
|
582
|
+
return 2;
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const nativeProtocolStatus = error => {
|
|
586
|
+
if (!error || typeof error !== 'object') return undefined;
|
|
587
|
+
const direct = Reflect.get(error, 'protocolStatus');
|
|
588
|
+
if (typeof direct === 'number') return direct;
|
|
589
|
+
const userInfo = Reflect.get(error, 'userInfo');
|
|
590
|
+
if (!userInfo || typeof userInfo !== 'object') return undefined;
|
|
591
|
+
const nested = Reflect.get(userInfo, 'protocolStatus');
|
|
592
|
+
return typeof nested === 'number' ? nested : undefined;
|
|
593
|
+
};
|
|
594
|
+
const isAlreadyPaired = error => error instanceof ProvisioningError && error.code === 'ALREADY_PAIRED' || nativeProtocolStatus(error) === 4;
|
|
595
|
+
const provisioningError = (error, deviceId) => {
|
|
596
|
+
if (error instanceof ProvisioningError) return error;
|
|
597
|
+
switch (nativeProtocolStatus(error)) {
|
|
598
|
+
case 1:
|
|
599
|
+
return ProvisioningError.invalidToken(deviceId);
|
|
600
|
+
case 2:
|
|
601
|
+
return ProvisioningError.storageError(deviceId);
|
|
602
|
+
case 3:
|
|
603
|
+
return ProvisioningError.chunkError(deviceId);
|
|
604
|
+
case 4:
|
|
605
|
+
return ProvisioningError.alreadyPaired(deviceId);
|
|
606
|
+
default:
|
|
607
|
+
return asError(error);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
let compatibilityResetSequence = 0;
|
|
611
|
+
const nextCompatibilityResetCommandId = () => {
|
|
612
|
+
compatibilityResetSequence += 1;
|
|
613
|
+
return `rn-reset-${Date.now().toString(36)}-${compatibilityResetSequence.toString(36)}`;
|
|
614
|
+
};
|
|
615
|
+
const factoryResetRejection = error => {
|
|
616
|
+
switch (nativeProtocolStatus(error)) {
|
|
617
|
+
case 1:
|
|
618
|
+
return {
|
|
619
|
+
success: false,
|
|
620
|
+
error: 'invalid_token'
|
|
621
|
+
};
|
|
622
|
+
case 2:
|
|
623
|
+
return {
|
|
624
|
+
success: false,
|
|
625
|
+
error: 'storage_error'
|
|
626
|
+
};
|
|
627
|
+
case 3:
|
|
628
|
+
return {
|
|
629
|
+
success: false,
|
|
630
|
+
error: 'chunk_error'
|
|
631
|
+
};
|
|
632
|
+
case 4:
|
|
633
|
+
return {
|
|
634
|
+
success: false,
|
|
635
|
+
error: 'already_paired'
|
|
636
|
+
};
|
|
637
|
+
default:
|
|
638
|
+
return undefined;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
//# sourceMappingURL=DeviceManager.js.map
|