brilliantsole 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 (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/build/brilliantsole.cjs +5957 -0
  4. package/build/brilliantsole.cjs.map +1 -0
  5. package/build/brilliantsole.js +5448 -0
  6. package/build/brilliantsole.js.map +1 -0
  7. package/build/brilliantsole.ls.js +4872 -0
  8. package/build/brilliantsole.ls.js.map +1 -0
  9. package/build/brilliantsole.min.js +6 -0
  10. package/build/brilliantsole.min.js.map +1 -0
  11. package/build/brilliantsole.module.d.ts +908 -0
  12. package/build/brilliantsole.module.js +5412 -0
  13. package/build/brilliantsole.module.js.map +1 -0
  14. package/build/brilliantsole.module.min.d.ts +908 -0
  15. package/build/brilliantsole.module.min.js +6 -0
  16. package/build/brilliantsole.module.min.js.map +1 -0
  17. package/build/brilliantsole.node.module.d.ts +908 -0
  18. package/build/brilliantsole.node.module.js +5906 -0
  19. package/build/brilliantsole.node.module.js.map +1 -0
  20. package/build/dts/BS.d.ts +25 -0
  21. package/build/dts/Device.d.ts +136 -0
  22. package/build/dts/DeviceInformationManager.d.ts +56 -0
  23. package/build/dts/DeviceManager.d.ts +67 -0
  24. package/build/dts/FileTransferManager.d.ts +84 -0
  25. package/build/dts/FirmwareManager.d.ts +71 -0
  26. package/build/dts/InformationManager.d.ts +66 -0
  27. package/build/dts/TfliteManager.d.ts +92 -0
  28. package/build/dts/connection/BaseConnectionManager.d.ts +59 -0
  29. package/build/dts/connection/ClientConnectionManager.d.ts +23 -0
  30. package/build/dts/connection/WebSocketClientConnectionManager.d.ts +23 -0
  31. package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +10 -0
  32. package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +42 -0
  33. package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +20 -0
  34. package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +14 -0
  35. package/build/dts/connection/webSocket/ClientConnectionManager.d.ts +23 -0
  36. package/build/dts/connection/webSocket/WebSocketClientConnectionManager.d.ts +23 -0
  37. package/build/dts/devicePair/DevicePair.d.ts +60 -0
  38. package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +25 -0
  39. package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +33 -0
  40. package/build/dts/scanner/BaseScanner.d.ts +66 -0
  41. package/build/dts/scanner/NobleScanner.d.ts +17 -0
  42. package/build/dts/scanner/Scanner.d.ts +3 -0
  43. package/build/dts/sensor/BarometerSensorDataManager.d.ts +16 -0
  44. package/build/dts/sensor/MotionSensorDataManager.d.ts +69 -0
  45. package/build/dts/sensor/PressureSensorDataManager.d.ts +36 -0
  46. package/build/dts/sensor/SensorConfigurationManager.d.ts +44 -0
  47. package/build/dts/sensor/SensorDataManager.d.ts +40 -0
  48. package/build/dts/server/BaseClient.d.ts +85 -0
  49. package/build/dts/server/BaseServer.d.ts +48 -0
  50. package/build/dts/server/ServerUtils.d.ts +23 -0
  51. package/build/dts/server/udp/UDPServer.d.ts +11 -0
  52. package/build/dts/server/udp/UDPUtils.d.ts +9 -0
  53. package/build/dts/server/websocket/WebSocketClient.d.ts +17 -0
  54. package/build/dts/server/websocket/WebSocketServer.d.ts +13 -0
  55. package/build/dts/server/websocket/WebSocketUtils.d.ts +9 -0
  56. package/build/dts/utils/ArrayBufferUtils.d.ts +7 -0
  57. package/build/dts/utils/ArrayUtils.d.ts +2 -0
  58. package/build/dts/utils/CenterOfPressureHelper.d.ts +15 -0
  59. package/build/dts/utils/Console.d.ts +34 -0
  60. package/build/dts/utils/EventDispatcher.d.ts +50 -0
  61. package/build/dts/utils/EventUtils.d.ts +6 -0
  62. package/build/dts/utils/MathUtils.d.ts +21 -0
  63. package/build/dts/utils/ParseUtils.d.ts +5 -0
  64. package/build/dts/utils/RangeHelper.d.ts +8 -0
  65. package/build/dts/utils/Text.d.ts +6 -0
  66. package/build/dts/utils/Timer.d.ts +14 -0
  67. package/build/dts/utils/TypeScriptUtils.d.ts +19 -0
  68. package/build/dts/utils/cbor.d.ts +6 -0
  69. package/build/dts/utils/checksum.d.ts +3 -0
  70. package/build/dts/utils/environment.d.ts +13 -0
  71. package/build/dts/utils/mcumgr.d.ts +88 -0
  72. package/build/dts/utils/stringUtils.d.ts +2 -0
  73. package/build/dts/vibration/VibrationManager.d.ts +45 -0
  74. package/build/dts/vibration/VibrationWaveformEffects.d.ts +2 -0
  75. package/build/index.d.ts +908 -0
  76. package/build/index.node.d.ts +908 -0
  77. package/examples/3d/index.html +109 -0
  78. package/examples/3d/scene.html +57 -0
  79. package/examples/3d/script.js +419 -0
  80. package/examples/balance/index.html +138 -0
  81. package/examples/balance/script.js +243 -0
  82. package/examples/basic/index.html +327 -0
  83. package/examples/basic/script.js +1093 -0
  84. package/examples/center-of-pressure/index.html +132 -0
  85. package/examples/center-of-pressure/script.js +207 -0
  86. package/examples/device-pair/index.html +72 -0
  87. package/examples/device-pair/script.js +187 -0
  88. package/examples/edge-impulse/index.html +94 -0
  89. package/examples/edge-impulse/script.js +1033 -0
  90. package/examples/graph/index.html +83 -0
  91. package/examples/graph/script.js +469 -0
  92. package/examples/machine-learning/index.html +366 -0
  93. package/examples/machine-learning/script.js +1774 -0
  94. package/examples/pressure/index.html +145 -0
  95. package/examples/pressure/script.js +201 -0
  96. package/examples/recording/index.html +187 -0
  97. package/examples/recording/script.js +736 -0
  98. package/examples/server/index.html +266 -0
  99. package/examples/server/script.js +925 -0
  100. package/examples/utils/aframe/fingertip-button-component.js +201 -0
  101. package/examples/utils/aframe/fingertip-collider-target-component.js +102 -0
  102. package/examples/utils/aframe/fingertip-colliders-component.js +147 -0
  103. package/examples/utils/three/three.module.min.js +24846 -0
  104. package/examples/webxr/index.html +221 -0
  105. package/examples/webxr/script.js +1127 -0
  106. package/package.json +83 -0
  107. package/src/BS.ts +68 -0
  108. package/src/Device.ts +734 -0
  109. package/src/DeviceInformationManager.ts +146 -0
  110. package/src/DeviceManager.ts +354 -0
  111. package/src/FileTransferManager.ts +452 -0
  112. package/src/FirmwareManager.ts +357 -0
  113. package/src/InformationManager.ts +283 -0
  114. package/src/TfliteManager.ts +450 -0
  115. package/src/connection/BaseConnectionManager.ts +255 -0
  116. package/src/connection/ClientConnectionManager.ts +120 -0
  117. package/src/connection/bluetooth/BluetoothConnectionManager.ts +34 -0
  118. package/src/connection/bluetooth/NobleConnectionManager.ts +302 -0
  119. package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +269 -0
  120. package/src/connection/bluetooth/bluetoothUUIDs.ts +218 -0
  121. package/src/devicePair/DevicePair.ts +253 -0
  122. package/src/devicePair/DevicePairPressureSensorDataManager.ts +82 -0
  123. package/src/devicePair/DevicePairSensorDataManager.ts +90 -0
  124. package/src/scanner/BaseScanner.ts +189 -0
  125. package/src/scanner/NobleScanner.ts +195 -0
  126. package/src/scanner/Scanner.ts +16 -0
  127. package/src/sensor/BarometerSensorDataManager.ts +41 -0
  128. package/src/sensor/MotionSensorDataManager.ts +151 -0
  129. package/src/sensor/PressureSensorDataManager.ts +112 -0
  130. package/src/sensor/SensorConfigurationManager.ts +177 -0
  131. package/src/sensor/SensorDataManager.ts +166 -0
  132. package/src/server/BaseClient.ts +368 -0
  133. package/src/server/BaseServer.ts +344 -0
  134. package/src/server/ServerUtils.ts +93 -0
  135. package/src/server/udp/UDPServer.ts +229 -0
  136. package/src/server/udp/UDPUtils.ts +20 -0
  137. package/src/server/websocket/WebSocketClient.ts +179 -0
  138. package/src/server/websocket/WebSocketServer.ts +184 -0
  139. package/src/server/websocket/WebSocketUtils.ts +20 -0
  140. package/src/utils/ArrayBufferUtils.ts +88 -0
  141. package/src/utils/ArrayUtils.ts +15 -0
  142. package/src/utils/CenterOfPressureHelper.ts +39 -0
  143. package/src/utils/Console.ts +156 -0
  144. package/src/utils/EventDispatcher.ts +153 -0
  145. package/src/utils/EventUtils.ts +41 -0
  146. package/src/utils/MathUtils.ts +53 -0
  147. package/src/utils/ParseUtils.ts +46 -0
  148. package/src/utils/RangeHelper.ts +38 -0
  149. package/src/utils/Text.ts +30 -0
  150. package/src/utils/Timer.ts +72 -0
  151. package/src/utils/TypeScriptUtils.ts +22 -0
  152. package/src/utils/cbor.js +429 -0
  153. package/src/utils/checksum.ts +41 -0
  154. package/src/utils/environment.ts +46 -0
  155. package/src/utils/mcumgr.js +444 -0
  156. package/src/utils/stringUtils.ts +11 -0
  157. package/src/vibration/VibrationManager.ts +308 -0
  158. package/src/vibration/VibrationWaveformEffects.ts +128 -0
@@ -0,0 +1,1033 @@
1
+ import * as BS from "../../build/brilliantsole.module.js";
2
+ //import BS.Device from "../../src/BS.Device.js";
3
+ window.BS = BS;
4
+ console.log(BS);
5
+
6
+ BS.setAllConsoleLevelFlags({ log: false });
7
+
8
+ // DEVICE
9
+
10
+ const device = new BS.Device();
11
+ console.log({ device });
12
+ window.device = device;
13
+
14
+ // GET DEVICES
15
+
16
+ /** @type {HTMLTemplateElement} */
17
+ const availableDeviceTemplate = document.getElementById("availableDeviceTemplate");
18
+ const availableDevicesContainer = document.getElementById("availableDevices");
19
+ /** @param {BS.Device[]} availableDevices */
20
+ function onAvailableDevices(availableDevices) {
21
+ availableDevicesContainer.innerHTML = "";
22
+ if (availableDevices.length == 0) {
23
+ availableDevicesContainer.innerText = "no devices available";
24
+ } else {
25
+ availableDevices.forEach((availableDevice) => {
26
+ const availableDeviceContainer = availableDeviceTemplate.content
27
+ .cloneNode(true)
28
+ .querySelector(".availableDevice");
29
+ availableDeviceContainer.querySelector(".name").innerText = availableDevice.name;
30
+ availableDeviceContainer.querySelector(".type").innerText = availableDevice.type;
31
+
32
+ /** @type {HTMLButtonElement} */
33
+ const toggleConnectionButton = availableDeviceContainer.querySelector(".toggleConnection");
34
+ toggleConnectionButton.addEventListener("click", () => {
35
+ device.connectionManager = availableDevice.connectionManager;
36
+ device.reconnect();
37
+ });
38
+ device.addEventListener("connectionStatus", () => {
39
+ toggleConnectionButton.disabled = device.connectionStatus != "notConnected";
40
+ });
41
+ toggleConnectionButton.disabled = device.connectionStatus != "notConnected";
42
+
43
+ availableDevicesContainer.appendChild(availableDeviceContainer);
44
+ });
45
+ }
46
+ }
47
+ async function getDevices() {
48
+ const availableDevices = await BS.DeviceManager.GetDevices();
49
+ if (!availableDevices) {
50
+ return;
51
+ }
52
+ onAvailableDevices(availableDevices);
53
+ }
54
+
55
+ BS.DeviceManager.AddEventListener("availableDevices", (event) => {
56
+ const devices = event.message.availableDevices;
57
+ onAvailableDevices(devices);
58
+ });
59
+ getDevices();
60
+
61
+ // CONNECTION
62
+
63
+ /** @type {HTMLButtonElement} */
64
+ const toggleConnectionButton = document.getElementById("toggleConnection");
65
+ toggleConnectionButton.addEventListener("click", () => {
66
+ switch (device.connectionStatus) {
67
+ case "notConnected":
68
+ device.connect();
69
+ break;
70
+ case "connected":
71
+ device.disconnect();
72
+ break;
73
+ }
74
+ });
75
+
76
+ /** @type {HTMLButtonElement} */
77
+ const reconnectButton = document.getElementById("reconnect");
78
+ reconnectButton.addEventListener("click", () => {
79
+ device.reconnect();
80
+ });
81
+ device.addEventListener("connectionStatus", () => {
82
+ reconnectButton.disabled = !device.canReconnect;
83
+ });
84
+
85
+ device.addEventListener("connectionStatus", () => {
86
+ switch (device.connectionStatus) {
87
+ case "connected":
88
+ case "notConnected":
89
+ toggleConnectionButton.disabled = false;
90
+ toggleConnectionButton.innerText = device.isConnected ? "disconnect" : "connect";
91
+ break;
92
+ case "connecting":
93
+ case "disconnecting":
94
+ toggleConnectionButton.disabled = true;
95
+ toggleConnectionButton.innerText = device.connectionStatus;
96
+ break;
97
+ }
98
+ });
99
+
100
+ /** @type {HTMLInputElement} */
101
+ const reconnectOnDisconnectionCheckbox = document.getElementById("reconnectOnDisconnection");
102
+ reconnectOnDisconnectionCheckbox.addEventListener("input", () => {
103
+ device.reconnectOnDisconnection = reconnectOnDisconnectionCheckbox.checked;
104
+ });
105
+
106
+ // DEVICE INFORMATION
107
+
108
+ /** @type {HTMLPreElement} */
109
+ const deviceInformationPre = document.getElementById("deviceInformationPre");
110
+ device.addEventListener("deviceInformation", () => {
111
+ deviceInformationPre.textContent = JSON.stringify(device.deviceInformation, null, 2);
112
+ });
113
+
114
+ /** @type {HTMLPreElement} */
115
+ const sensorConfigurationPre = document.getElementById("sensorConfigurationPre");
116
+ device.addEventListener("getSensorConfiguration", () => {
117
+ sensorConfigurationPre.textContent = JSON.stringify(device.sensorConfiguration, null, 2);
118
+ });
119
+
120
+ /** @type {HTMLSpanElement} */
121
+ const nameSpan = document.getElementById("name");
122
+ device.addEventListener("getName", () => {
123
+ nameSpan.innerText = device.name;
124
+ });
125
+
126
+ /** @type {HTMLSpanElement} */
127
+ const typeSpan = document.getElementById("type");
128
+ device.addEventListener("getType", () => {
129
+ typeSpan.innerText = device.type;
130
+ });
131
+
132
+ /** @type {HTMLSpanElement} */
133
+ const batteryLevelSpan = document.getElementById("batteryLevel");
134
+ device.addEventListener("batteryLevel", () => {
135
+ batteryLevelSpan.innerText = device.batteryLevel;
136
+ });
137
+
138
+ // SEARCH PARAMS
139
+
140
+ const url = new URL(location);
141
+ console.log({ url });
142
+ function setUrlParam(key, value) {
143
+ if (history.pushState) {
144
+ let searchParams = new URLSearchParams(window.location.search);
145
+ if (value) {
146
+ searchParams.set(key, value);
147
+ } else {
148
+ searchParams.delete(key);
149
+ }
150
+ let newUrl =
151
+ window.location.protocol + "//" + window.location.host + window.location.pathname + "?" + searchParams.toString();
152
+ window.history.pushState({ path: newUrl }, "", newUrl);
153
+ }
154
+ }
155
+
156
+ // PROJECT ID
157
+
158
+ /** @type {string?} */
159
+ let projectId;
160
+
161
+ /** @type {HTMLInputElement} */
162
+ const projectIdInput = document.getElementById("projectId");
163
+ projectIdInput.addEventListener("input", (event) => {
164
+ setProjectId(event.target.value);
165
+ setHmacKey();
166
+ });
167
+ /** @param {string} newProjectId */
168
+ function setProjectId(newProjectId) {
169
+ projectId = newProjectId;
170
+ console.log({ projectId });
171
+ window.dispatchEvent(new Event("projectId"));
172
+ projectIdInput.value = projectId;
173
+ setUrlParam("projectId", projectId);
174
+ }
175
+ window.addEventListener("loadConfig", () => {
176
+ if (config.projectId) {
177
+ projectIdInput.value = config.projectId;
178
+ }
179
+ });
180
+ window.addEventListener("load", () => {
181
+ if (url.searchParams.has("projectId")) {
182
+ setProjectId(url.searchParams.get("projectId"));
183
+ }
184
+ });
185
+
186
+ // EDGE IMPULSE KEYS
187
+
188
+ /** @type {string?} */
189
+ let apiKey;
190
+
191
+ const apiKeyInput = document.getElementById("apiKey");
192
+ apiKeyInput.addEventListener("input", (event) => {
193
+ setApiKey(event.target.value);
194
+ setHmacKey();
195
+ });
196
+ function setApiKey(newApiKey) {
197
+ apiKey = newApiKey;
198
+ apiKeyInput.value = apiKey;
199
+ console.log({ apiKey });
200
+ window.dispatchEvent(new Event("apiKey"));
201
+ setUrlParam("apiKey", apiKey);
202
+ }
203
+ window.addEventListener("loadConfig", () => {
204
+ if (config.apiKey) {
205
+ setApiKey(config.apiKey);
206
+ }
207
+ });
208
+ window.addEventListener("load", () => {
209
+ if (url.searchParams.has("apiKey")) {
210
+ setApiKey(url.searchParams.get("apiKey"));
211
+ }
212
+ });
213
+
214
+ // HmacKey
215
+
216
+ /** @type {string?} */
217
+ let hmacKey;
218
+
219
+ function setHmacKey(newHmacKey) {
220
+ hmacKey = newHmacKey;
221
+ console.log({ hmacKey });
222
+ window.dispatchEvent(new Event("hmacKey"));
223
+ updateHmacKeyButton();
224
+ setUrlParam("hmacKey", hmacKey);
225
+ }
226
+
227
+ /** @type {HTMLButtonElement} */
228
+ const getHmacKeyButton = document.getElementById("getHmacKey");
229
+ getHmacKeyButton.addEventListener("click", async () => {
230
+ getHmacKeyButton.innerText = "getting hmacKey...";
231
+ getHmacKeyButton.disabled = true;
232
+ await getHmacKey();
233
+ updateHmacKeyButton();
234
+ });
235
+
236
+ function updateHmacKeyButton() {
237
+ getHmacKeyButton.disabled = Boolean(hmacKey);
238
+ getHmacKeyButton.innerText = Boolean(hmacKey) ? "got hmacKey" : "get hmacKey";
239
+ }
240
+
241
+ ["projectId", "apiKey", "hmacKey"].forEach((eventType) => {
242
+ window.addEventListener(eventType, () => {
243
+ updateHmacKeyButton();
244
+ });
245
+ });
246
+
247
+ window.addEventListener("load", () => {
248
+ if (url.searchParams.has("hmacKey")) {
249
+ setHmacKey(url.searchParams.get("hmacKey"));
250
+ }
251
+ });
252
+
253
+ // SENSOR TYPES
254
+
255
+ /** @type {BS.ContinuousSensorType[]} */
256
+ let sensorTypes = [];
257
+ /** @param {BS.ContinuousSensorType[]} newSensorTypes */
258
+ function setSensorTypes(newSensorTypes) {
259
+ sensorTypes = newSensorTypes;
260
+ console.log("sensorTypes", sensorTypes);
261
+ window.dispatchEvent(new Event("sensorTypes"));
262
+ }
263
+
264
+ const sensorTypesContainer = document.getElementById("sensorTypes");
265
+ /** @type {HTMLTemplateElement} */
266
+ const sensorTypeTemplate = document.getElementById("sensorTypeTemplate");
267
+ /** @type {Object.<string, HTMLElement>} */
268
+ const sensorTypeContainers = {};
269
+
270
+ BS.TfliteSensorTypes.forEach((sensorType) => {
271
+ const sensorTypeContainer = sensorTypeTemplate.content.cloneNode(true).querySelector(".sensorType");
272
+ sensorTypeContainer.querySelector(".name").innerText = sensorType;
273
+
274
+ /** @type {HTMLInputElement} */
275
+ const isSensorEnabledInput = sensorTypeContainer.querySelector(".enabled");
276
+ isSensorEnabledInput.addEventListener("input", () => {
277
+ onSensorTypesInput();
278
+ });
279
+
280
+ window.addEventListener("sensorTypes", () => {
281
+ isSensorEnabledInput.checked = sensorTypes.includes(sensorType);
282
+ });
283
+
284
+ sensorTypeContainers[sensorType] = sensorTypeContainer;
285
+
286
+ sensorTypesContainer.appendChild(sensorTypeContainer);
287
+ });
288
+
289
+ function onSensorTypesInput() {
290
+ const sensorTypes = BS.TfliteSensorTypes.filter((sensorType) => {
291
+ /** @type {HTMLInputElement} */
292
+ const input = sensorTypeContainers[sensorType].querySelector(".enabled");
293
+ return input.checked;
294
+ });
295
+ setSensorTypes(sensorTypes);
296
+ }
297
+
298
+ // SAMPLING
299
+
300
+ /** @type {number} */
301
+ let samplingInterval;
302
+ /** @param {number} newSamplingInterval */
303
+ function setSamplingInterval(newSamplingInterval) {
304
+ samplingInterval = newSamplingInterval;
305
+ console.log({ samplingInterval });
306
+ samplingIntervalInput.value = samplingInterval;
307
+ window.dispatchEvent(new Event("samplingInterval"));
308
+ }
309
+ /** @type {HTMLInputElement} */
310
+ const samplingIntervalInput = document.getElementById("samplingInterval");
311
+ samplingIntervalInput.addEventListener("input", (event) => {
312
+ setSamplingInterval(Number(event.target.value));
313
+ });
314
+ setSamplingInterval(20);
315
+
316
+ /** @type {number} */
317
+ let numberOfSamples;
318
+ /** @param {number} newNumberOfSamples */
319
+ function setNumberOfSamples(newNumberOfSamples) {
320
+ numberOfSamples = newNumberOfSamples;
321
+ console.log({ numberOfSamples });
322
+ numberOfSamplesInput.value = numberOfSamples;
323
+ window.dispatchEvent(new Event("numberOfSamples"));
324
+ }
325
+ /** @type {HTMLInputElement} */
326
+ const numberOfSamplesInput = document.getElementById("numberOfSamples");
327
+ numberOfSamplesInput.addEventListener("input", (event) => {
328
+ setNumberOfSamples(Number(event.target.value));
329
+ });
330
+ setNumberOfSamples(10);
331
+
332
+ /** @type {string} */
333
+ let label;
334
+ /** @type {HTMLInputElement} */
335
+ const labelInput = document.getElementById("label");
336
+ labelInput.addEventListener("input", (event) => {
337
+ setLabel(event.target.value);
338
+ });
339
+ /** @param {string} newLabel */
340
+ function setLabel(newLabel) {
341
+ label = newLabel;
342
+ console.log({ label });
343
+ labelInput.value = label;
344
+ window.dispatchEvent(new Event("label"));
345
+ }
346
+ setLabel("idle");
347
+
348
+ /** @type {string} */
349
+ let path;
350
+ /** @type {HTMLInputElement} */
351
+ const pathInput = document.getElementById("path");
352
+ pathInput.addEventListener("input", (event) => {
353
+ setPath(event.target.value);
354
+ });
355
+ /** @param {string} newPath */
356
+ function setPath(newPath) {
357
+ path = newPath;
358
+ console.log({ path });
359
+ pathInput.value = path;
360
+ window.dispatchEvent(new Event("path"));
361
+ }
362
+ setPath("/api/training/data");
363
+
364
+ /** @type {number} */
365
+ let samplingLength;
366
+ /** @param {number} newSamplingLength */
367
+ function setSamplingLength(newSamplingLength) {
368
+ samplingLength = newSamplingLength;
369
+ console.log({ samplingLength });
370
+ samplingLengthInput.value = samplingLength;
371
+ window.dispatchEvent(new Event("samplingLength"));
372
+ }
373
+ /** @type {HTMLInputElement} */
374
+ const samplingLengthInput = document.getElementById("samplingLength");
375
+
376
+ function updateSamplingLength() {
377
+ const newSamplingLength = numberOfSamples * samplingInterval;
378
+ setSamplingLength(newSamplingLength);
379
+ }
380
+ updateSamplingLength();
381
+
382
+ ["samplingInterval", "numberOfSamples"].forEach((eventType) => {
383
+ window.addEventListener(eventType, () => {
384
+ updateSamplingLength();
385
+ });
386
+ });
387
+
388
+ // SAMPLING
389
+
390
+ let isSampling = false;
391
+ /** @param {boolean} newIsSampling */
392
+ function setIsSampling(newIsSampling) {
393
+ isSampling = newIsSampling;
394
+ console.log({ isSampling });
395
+ window.dispatchEvent(new Event("isSampling"));
396
+ }
397
+
398
+ /** @type {HTMLButtonElement} */
399
+ const toggleSamplingButton = document.getElementById("toggleSampling");
400
+ toggleSamplingButton.addEventListener("click", () => {
401
+ sampleAndUpload();
402
+ });
403
+
404
+ function updateToggleSamplingButton() {
405
+ const enabled =
406
+ device.isConnected && isRemoteManagementConnected() && sensorTypes.length > 0 && label.length > 0 && !isSampling;
407
+ toggleSamplingButton.disabled = !enabled;
408
+ toggleSamplingButton.innerText = isSampling ? "sampling..." : "start sampling";
409
+ }
410
+
411
+ ["isSampling", "remoteManagementConnection", "sensortypes", "label"].forEach((eventType) => {
412
+ window.addEventListener(eventType, () => {
413
+ updateToggleSamplingButton();
414
+ });
415
+ });
416
+ device.addEventListener("isConnected", () => {
417
+ updateToggleSamplingButton();
418
+ });
419
+
420
+ async function sampleAndUpload() {
421
+ /** @type {SensorConfiguration} */
422
+ const sensorConfiguration = {};
423
+ sensorTypes.forEach((sensorType) => {
424
+ sensorConfiguration[sensorType] = samplingInterval;
425
+ });
426
+ console.log("sensorConfiguration", sensorConfiguration);
427
+ device.setSensorConfiguration(sensorConfiguration);
428
+
429
+ setIsSampling(true);
430
+
431
+ const deviceData = await collectData(sensorTypes, numberOfSamples);
432
+ await device.clearSensorConfiguration();
433
+ console.log("deviceData", deviceData);
434
+
435
+ sendRemoteManagementMessage?.({ sampleFinished: true });
436
+ setIsSampling(false);
437
+
438
+ sendRemoteManagementMessage?.({ sampleUploading: true });
439
+ await uploadData(sensorTypes, deviceData);
440
+ }
441
+
442
+ // EDGE IMPULSE API
443
+
444
+ const ingestionApi = "https://ingestion.edgeimpulse.com";
445
+ const remoteManagementEndpoint = "wss://remote-mgmt.edgeimpulse.com";
446
+ const studioEndpoint = "https://studio.edgeimpulse.com";
447
+
448
+ async function getProjects() {
449
+ return new Promise((resolve, reject) => {
450
+ const x = new XMLHttpRequest();
451
+ x.open("GET", `${studioEndpoint}/v1/api/projects`);
452
+ x.onload = () => {
453
+ if (x.status !== 200) {
454
+ reject("No projects found: " + x.status + " - " + JSON.stringify(x.response));
455
+ } else {
456
+ if (!x.response.success) {
457
+ reject(x.response.error);
458
+ } else {
459
+ const projects = x.response.projects;
460
+ console.log("projects", projects);
461
+ resolve(projects);
462
+ window.dispatchEvent(new CustomEvent("edgeImpulseProjects", { detail: { projects } }));
463
+ }
464
+ }
465
+ };
466
+ x.onerror = (err) => reject(err);
467
+ x.responseType = "json";
468
+ x.setRequestHeader("x-api-key", apiKey);
469
+ x.send();
470
+ });
471
+ }
472
+
473
+ async function getProject() {
474
+ return new Promise((resolve, reject) => {
475
+ const x = new XMLHttpRequest();
476
+ x.open("GET", `${studioEndpoint}/v1/api/${projectId}/public-info`);
477
+ x.onload = () => {
478
+ if (x.status !== 200) {
479
+ reject("No projects found: " + x.status + " - " + JSON.stringify(x.response));
480
+ } else {
481
+ if (!x.response.success) {
482
+ reject(x.response.error);
483
+ } else {
484
+ const project = x.response;
485
+ console.log("project", project);
486
+ resolve(project);
487
+ window.dispatchEvent(new CustomEvent("edgeImpulseProject", { detail: { project } }));
488
+ }
489
+ }
490
+ };
491
+ if (apiKey) {
492
+ x.setRequestHeader("x-api-key", apiKey);
493
+ }
494
+ x.onerror = (err) => reject(err);
495
+ x.responseType = "json";
496
+ x.send();
497
+ });
498
+ }
499
+
500
+ async function getHmacKey() {
501
+ return new Promise((resolve, reject) => {
502
+ const x = new XMLHttpRequest();
503
+ x.open("GET", `${studioEndpoint}/v1/api/${projectId}/devkeys`);
504
+ x.onload = () => {
505
+ if (x.status !== 200) {
506
+ reject("No development keys found: " + x.status + " - " + JSON.stringify(x.response));
507
+ } else {
508
+ if (!x.response.success) {
509
+ reject(x.response.error);
510
+ } else {
511
+ const { apiKey, hmacKey } = x.response;
512
+ console.log({ apiKey, hmacKey });
513
+ //setApiKey(apiKey);
514
+ setHmacKey(hmacKey);
515
+ resolve({
516
+ apiKey,
517
+ hmacKey,
518
+ });
519
+ }
520
+ }
521
+ };
522
+ x.onerror = (err) => reject(err);
523
+ x.responseType = "json";
524
+ if (apiKey) {
525
+ x.setRequestHeader("x-api-key", apiKey);
526
+ }
527
+ x.send();
528
+ });
529
+ }
530
+
531
+ // REMOTE MANAGEMENT
532
+
533
+ /**
534
+ * @typedef SamplingDetails
535
+ * @type {Object}
536
+ * @property {string} path
537
+ * @property {string} label
538
+ * @property {number} length ms
539
+ * @property {number} interval ms
540
+ * @property {string} hmacKey
541
+ * @property {string} sensor
542
+ */
543
+
544
+ /** @type {WebSocket?} */
545
+ let remoteManagementWebSocket;
546
+ /** @type {(message: object)=>{}?} */
547
+ let sendRemoteManagementMessage;
548
+ async function connectToRemoteManagement() {
549
+ remoteManagementWebSocket?.close();
550
+
551
+ /** @type {number?} */
552
+ let intervalId;
553
+
554
+ const ws = new WebSocket(remoteManagementEndpoint);
555
+ remoteManagementWebSocket = ws;
556
+
557
+ sendRemoteManagementMessage = (message) => {
558
+ console.log("sending message", message);
559
+ ws.send(JSON.stringify(message));
560
+ };
561
+ ws.addEventListener("open", () => {
562
+ console.log("remoteManagementWebSocket.open");
563
+ window.dispatchEvent(new Event("remoteManagementConnection"));
564
+ sendRemoteManagementMessage(remoteManagementHelloMessage());
565
+ intervalId = setInterval(() => {
566
+ console.log("ping");
567
+ ws.send("ping");
568
+ }, 3000);
569
+ });
570
+ ws.addEventListener("close", () => {
571
+ console.log("remoteManagementWebSocket.close");
572
+ window.dispatchEvent(new Event("remoteManagementConnection"));
573
+ clearInterval(intervalId);
574
+ if (reconnectRemoteManagementOnDisconnection && !ws.dontReconnect) {
575
+ window.setTimeout(() => {
576
+ if (!reconnectRemoteManagementOnDisconnection) {
577
+ return;
578
+ }
579
+ connectToRemoteManagement();
580
+ }, 2000);
581
+ }
582
+ });
583
+ ws.addEventListener("error", (event) => {
584
+ console.log("remoteManagementWebSocket.error", event);
585
+ window.dispatchEvent(new Event("remoteManagementConnection"));
586
+ });
587
+ ws.addEventListener("message", async (event) => {
588
+ console.log("remoteManagementWebSocket.message", event.data);
589
+
590
+ const data = await parseRemoteManagementMessage(event);
591
+ if (!data) {
592
+ return;
593
+ }
594
+
595
+ console.log({ data });
596
+
597
+ if ("hello" in data) {
598
+ const isConnected = data.hello;
599
+ console.log({ isConnected });
600
+ if (isConnected) {
601
+ ws._isConnected = true;
602
+ window.dispatchEvent(new Event("remoteManagementConnection"));
603
+ }
604
+ }
605
+
606
+ if ("sample" in data) {
607
+ /** @type {SamplingDetails} */
608
+ const samplingDetails = data.sample;
609
+ console.log("samplingDetails", samplingDetails);
610
+
611
+ /** @type {BS.ContinuousSensorType[]} */
612
+ const sensorTypes = samplingDetails.sensor.split(sensorCombinationSeparator);
613
+ console.log("sensorTypes", sensorTypes);
614
+
615
+ const invalidSensors = sensorTypes.filter((sensorType) => !device.allowedTfliteSensorTypes.includes(sensorType));
616
+ if (invalidSensors.length > 0) {
617
+ console.error("invalid sensorTypes", invalidSensors);
618
+ return;
619
+ }
620
+
621
+ const numberOfSamples = samplingDetails.length / samplingDetails.interval;
622
+ setNumberOfSamples(numberOfSamples);
623
+ setSensorTypes(sensorTypes);
624
+ setSamplingInterval(samplingDetails.interval);
625
+ setSamplingLength(samplingDetails.length);
626
+ setLabel(samplingDetails.label);
627
+ setHmacKey(samplingDetails.hmacKey);
628
+ setPath(samplingDetails.path);
629
+
630
+ sampleAndUpload();
631
+
632
+ // /** @type {SensorConfiguration} */
633
+ // const sensorConfiguration = {};
634
+ // sensorTypes.forEach((sensorType) => {
635
+ // sensorConfiguration[sensorType] = samplingDetails.interval;
636
+ // });
637
+ // console.log("sensorConfiguration", sensorConfiguration);
638
+ // device.setSensorConfiguration(sensorConfiguration);
639
+
640
+ // setIsSampling(true);
641
+
642
+ // const deviceData = await collectData(sensorTypes, numberOfSamples);
643
+ // await device.clearSensorConfiguration();
644
+ // console.log("deviceData", deviceData);
645
+
646
+ // sendRemoteManagementMessage?.({ sampleFinished: true });
647
+ // setIsSampling(false);
648
+
649
+ // sendRemoteManagementMessage?.({ sampleUploading: true });
650
+ // await uploadData(samplingDetails, sensorTypes, deviceData);
651
+ }
652
+ });
653
+ }
654
+
655
+ async function parseRemoteManagementMessage(event) {
656
+ if (event.data instanceof Blob) {
657
+ return await readFile(event.data);
658
+ } else if (typeof event.data === "string") {
659
+ if (event.data === "pong") return null;
660
+ return JSON.parse(event.data);
661
+ }
662
+ return null;
663
+ }
664
+
665
+ const sensorCombinationSeparator = " + ";
666
+
667
+ // ChatGPT
668
+ /** @param {string[]} array */
669
+ function generateSubarrays(array) {
670
+ /** @type {string[][]} */
671
+ const results = [];
672
+
673
+ /**
674
+ * @param {number} start
675
+ * @param {string[]} subArray
676
+ */
677
+ function helper(start, subArray) {
678
+ results.push(subArray);
679
+
680
+ for (let i = start; i < array.length; i++) {
681
+ helper(i + 1, subArray.concat(array[i]));
682
+ }
683
+ }
684
+
685
+ helper(0, []);
686
+
687
+ return results
688
+ .sort((a, b) => a.length - b.length)
689
+ .filter((subArray) => subArray.length > 0)
690
+ .map((subArray) => subArray.join(sensorCombinationSeparator));
691
+ }
692
+
693
+ const getDeviceId = () => `${device.name}.${device.type}.${device.id}`;
694
+
695
+ function remoteManagementHelloMessage() {
696
+ const sensors = device.allowedTfliteSensorTypes;
697
+ const sensorCombinations = generateSubarrays(sensors);
698
+ console.log("sensorCombinations", sensorCombinations);
699
+ return {
700
+ hello: {
701
+ version: 3,
702
+ apiKey: apiKey,
703
+ deviceId: getDeviceId(),
704
+ deviceType: "BrilliantSole",
705
+ connection: "ip",
706
+ sensors: sensorCombinations.map((sensorCombination) => {
707
+ return {
708
+ name: sensorCombination,
709
+ maxSampleLengthS: 1 * 60,
710
+ frequencies: [50.0, 25.0, 12.5], // 20ms, 40ms, 80ms
711
+ };
712
+ }),
713
+ supportsSnapshotStreaming: false,
714
+ },
715
+ };
716
+ }
717
+
718
+ function isRemoteManagementConnected() {
719
+ return remoteManagementWebSocket?.readyState == WebSocket.OPEN && remoteManagementWebSocket?._isConnected;
720
+ }
721
+
722
+ /** @type {HTMLButtonElement} */
723
+ const toggleRemoteManagementConnectionButton = document.getElementById("toggleRemoteManagementConnection");
724
+ toggleRemoteManagementConnectionButton.addEventListener("click", () => {
725
+ if (isRemoteManagementConnected()) {
726
+ remoteManagementWebSocket.dontReconnect = true;
727
+ remoteManagementWebSocket.close();
728
+ toggleRemoteManagementConnectionButton.innerText = "disconnecting...";
729
+ toggleRemoteManagementConnectionButton.disabled = true;
730
+ } else {
731
+ connectToRemoteManagement();
732
+ toggleRemoteManagementConnectionButton.innerText = "connecting...";
733
+ }
734
+ });
735
+
736
+ window.addEventListener("remoteManagementConnection", () => {
737
+ if (isRemoteManagementConnected()) {
738
+ toggleRemoteManagementConnectionButton.innerText = "disconnect";
739
+ toggleRemoteManagementConnectionButton.disabled = false;
740
+ } else {
741
+ toggleRemoteManagementConnectionButton.innerText = "connect";
742
+ toggleRemoteManagementConnectionButton.disabled = false;
743
+ }
744
+ });
745
+
746
+ function updateToggleRemoteManagementConnectionButton() {
747
+ const enabled = device.isConnected;
748
+ toggleRemoteManagementConnectionButton.disabled = !enabled;
749
+ }
750
+ device.addEventListener("isConnected", () => {
751
+ updateToggleRemoteManagementConnectionButton();
752
+ });
753
+
754
+ let reconnectRemoteManagementOnDisconnection = false;
755
+ /** @type {HTMLInputElement} */
756
+ const reconnectRemoteManagementOnDisconnectionInput = document.getElementById(
757
+ "reconnectRemoteManagementOnDisconnection"
758
+ );
759
+ reconnectRemoteManagementOnDisconnectionInput.addEventListener("input", (event) => {
760
+ setReconnectRemoteManagementOnDisconnection(event.target.checked);
761
+ });
762
+ reconnectRemoteManagementOnDisconnectionInput.checked = reconnectRemoteManagementOnDisconnection;
763
+ /** @param {boolean} newReconnectRemoteManagementOnDisconnection */
764
+ function setReconnectRemoteManagementOnDisconnection(newReconnectRemoteManagementOnDisconnection) {
765
+ reconnectRemoteManagementOnDisconnection = newReconnectRemoteManagementOnDisconnection;
766
+ console.log({ reconnectRemoteManagementOnDisconnection });
767
+ dispatchEvent(new Event("reconnectRemoteManagementOnDisconnection"));
768
+ }
769
+
770
+ // DATA COLLECTION
771
+
772
+ const scalars = {
773
+ pressure: 1 / (2 ** 16 - 1),
774
+ linearAcceleration: 1 / 4,
775
+ gyroscope: 1 / 720,
776
+ magnetometer: 1 / 2500,
777
+ };
778
+
779
+ /** @typedef {Object.<string, number>} SensorData */
780
+ /** @typedef {Object.<string, SensorData[]>} DeviceData */
781
+
782
+ /**
783
+ * @param {BS.ContinuousSensorType[]} sensorTypes
784
+ * @param {number} numberOfSamples
785
+ * @returns {Promise<DeviceData>}
786
+ */
787
+ async function collectData(sensorTypes, numberOfSamples) {
788
+ return new Promise((resolve) => {
789
+ /** @type {DeviceData} */
790
+ const deviceData = {};
791
+
792
+ sensorTypes.forEach((sensorType) => {
793
+ deviceData[sensorType] = [];
794
+ });
795
+
796
+ console.log("deviceData", deviceData);
797
+
798
+ const onDeviceSensorData = (event) => {
799
+ /** @type {SensorType} */
800
+ const sensorType = event.message.sensorType;
801
+
802
+ if (!(sensorType in deviceData)) {
803
+ return;
804
+ }
805
+
806
+ const didFinishCollectingDeviceData = Object.values(deviceData).every(
807
+ (sensorData) => sensorData.length >= numberOfSamples
808
+ );
809
+
810
+ if (didFinishCollectingDeviceData) {
811
+ console.log("finished collecting data for device", deviceData);
812
+ device.removeEventListener("sensorData", onDeviceSensorData);
813
+ resolve(deviceData);
814
+ return;
815
+ }
816
+
817
+ if (deviceData[sensorType].length == numberOfSamples) {
818
+ console.log(`finished collecting ${sensorType} data for device`);
819
+ return;
820
+ }
821
+
822
+ const { [sensorType]: data } = event.message;
823
+ deviceData[sensorType].push(data);
824
+ };
825
+
826
+ device.addEventListener("sensorData", onDeviceSensorData);
827
+ });
828
+ }
829
+
830
+ // DATA UPLOAD
831
+
832
+ const emptySignature = Array(64).fill("0").join("");
833
+
834
+ /**
835
+ * @param {BS.ContinuousSensorType[]} sensorTypes
836
+ * @param {DeviceData} deviceData
837
+ */
838
+ async function uploadData(sensorTypes, deviceData) {
839
+ const sensors = sensorTypes.flatMap((sensorType) => {
840
+ let names = [];
841
+ let units;
842
+ switch (sensorType) {
843
+ case "linearAcceleration":
844
+ case "gyroscope":
845
+ case "magnetometer":
846
+ names = ["x", "y", "z"].map((component) => `${sensorType}.${component}`);
847
+ switch (sensorType) {
848
+ case "linearAcceleration":
849
+ units = "g/s";
850
+ break;
851
+ case "gyroscope":
852
+ units = "deg/s";
853
+ break;
854
+ case "magnetometer":
855
+ units = "uT";
856
+ break;
857
+ }
858
+ break;
859
+ case "pressure":
860
+ for (let index = 0; index < device.numberOfPressureSensors; index++) {
861
+ names.push(`${sensorType}.${index}`);
862
+ }
863
+ units = "pressure";
864
+ break;
865
+ default:
866
+ throw `uncaught sensorType ${sensorType}`;
867
+ }
868
+
869
+ return names.map((name) => ({
870
+ name,
871
+ units,
872
+ }));
873
+ });
874
+
875
+ console.log("sensors", sensors);
876
+
877
+ const values = [];
878
+ for (let sampleIndex = 0; sampleIndex < numberOfSamples; sampleIndex++) {
879
+ const value = [];
880
+
881
+ sensorTypes.forEach((sensorType) => {
882
+ const scalar = scalars[sensorType];
883
+ const sensorSamples = deviceData[sensorType];
884
+
885
+ switch (sensorType) {
886
+ case "linearAcceleration":
887
+ case "gyroscope":
888
+ case "magnetometer":
889
+ ["x", "y", "z"].forEach((component) => {
890
+ value.push(sensorSamples[sampleIndex][component] * scalar);
891
+ });
892
+ break;
893
+ case "pressure":
894
+ for (let pressureIndex = 0; pressureIndex < device.numberOfPressureSensors; pressureIndex++) {
895
+ value.push(sensorSamples[sampleIndex].sensors[pressureIndex].rawValue * scalar);
896
+ }
897
+ break;
898
+ default:
899
+ throw `uncaught sensorType ${sensorType}`;
900
+ }
901
+ });
902
+
903
+ values.push(value);
904
+ }
905
+
906
+ console.log("values", values);
907
+
908
+ const data = {
909
+ protected: {
910
+ ver: "v1",
911
+ alg: "HS256",
912
+ iat: Math.floor(Date.now() / 1000),
913
+ },
914
+ signature: emptySignature,
915
+ payload: {
916
+ device_name: getDeviceId(),
917
+ device_type: "BrilliantSole",
918
+ interval_ms: samplingInterval,
919
+ sensors,
920
+ values,
921
+ },
922
+ };
923
+
924
+ console.log("data", data);
925
+
926
+ data.signature = await createSignature(hmacKey, data);
927
+
928
+ console.log("signature", data.signature);
929
+
930
+ const formData = new FormData();
931
+ formData.append("message", new Blob([JSON.stringify(data)], { type: "application/json" }), "message.json");
932
+
933
+ return new Promise((resolve, reject) => {
934
+ let xml = new XMLHttpRequest();
935
+ xml.onload = () => {
936
+ if (xml.status === 200) {
937
+ resolve(xml.responseText);
938
+ } else {
939
+ reject("Failed to upload (status code " + xml.status + "): " + xml.responseText);
940
+ }
941
+ };
942
+ xml.onerror = () => reject(undefined);
943
+ xml.open("post", ingestionApi + path);
944
+ xml.setRequestHeader("x-api-key", apiKey);
945
+ xml.setRequestHeader("x-file-name", encodeLabel(label));
946
+ xml.send(formData);
947
+ });
948
+ }
949
+
950
+ function encodeLabel(header) {
951
+ let encodedHeader;
952
+ try {
953
+ encodedHeader = encodeURIComponent(header);
954
+ } catch (ex) {
955
+ encodedHeader = header;
956
+ }
957
+
958
+ return encodedHeader;
959
+ }
960
+
961
+ const textEncoder = new TextEncoder();
962
+
963
+ /**
964
+ * @param {string} hmacKey
965
+ * @param {object} data
966
+ */
967
+ async function createSignature(hmacKey, data) {
968
+ // encoder to convert string to Uint8Array
969
+ const key = await crypto.subtle.importKey(
970
+ "raw", // raw format of the key - should be Uint8Array
971
+ textEncoder.encode(hmacKey),
972
+ {
973
+ // algorithm details
974
+ name: "HMAC",
975
+ hash: {
976
+ name: "SHA-256",
977
+ },
978
+ },
979
+ false, // export = false
980
+ ["sign", "verify"] // what this key can do
981
+ );
982
+ // Create signature for encoded input data
983
+ const signature = await crypto.subtle.sign("HMAC", key, textEncoder.encode(JSON.stringify(data)));
984
+ // Convert back to Hex
985
+ const b = new Uint8Array(signature);
986
+ return Array.prototype.map.call(b, (x) => ("00" + x.toString(16)).slice(-2)).join("");
987
+ }
988
+
989
+ // EDGE IMPULSE CONFIG
990
+
991
+ const configLocalStorageKey = "EdgeImpulse";
992
+
993
+ let config = {
994
+ projectId,
995
+ apiKey,
996
+ hmacKey,
997
+ };
998
+ /** @type {object?} */
999
+ let loadedConfig;
1000
+ console.log("config", config);
1001
+ function loadConfigFromLocalStorage() {
1002
+ const configString = localStorage.getItem(configLocalStorageKey);
1003
+ if (!configString) {
1004
+ return;
1005
+ }
1006
+ loadedConfig = JSON.parse(configString);
1007
+ console.log("loaded config", loadedConfig);
1008
+ Object.assign(config, loadedConfig);
1009
+ console.log("updated config", config);
1010
+ window.dispatchEvent(new Event("loadConfig"));
1011
+ }
1012
+ loadConfigFromLocalStorage();
1013
+
1014
+ function saveConfigToLocalStorage() {
1015
+ const isConfigDifferent = !loadedConfig || Object.entries(loadedConfig).some(([key, value]) => config[key] != value);
1016
+ if (!isConfigDifferent) {
1017
+ return;
1018
+ }
1019
+ console.log("saving config", config);
1020
+ localStorage.setItem(configLocalStorageKey, JSON.stringify(config));
1021
+ loadedConfig = config;
1022
+ }
1023
+
1024
+ Object.keys(config).forEach((type) => {
1025
+ window.addEventListener(type, () => {
1026
+ config = {
1027
+ projectId,
1028
+ apiKey,
1029
+ hmacKey,
1030
+ };
1031
+ saveConfigToLocalStorage();
1032
+ });
1033
+ });