ota-hub-reactjs 0.0.19 → 0.1.2

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.
@@ -31,8 +31,9 @@ export type DeviceConnectionState = {
31
31
  export declare function createDefaultInitialDeviceState<T extends DeviceConnectionState>(uuid: string, props?: any): T;
32
32
  export type DeviceWhispererProps<T extends DeviceConnectionState> = {
33
33
  createInitialConnectionState?: (uuid: string) => Partial<T>;
34
+ setFocussedConnectionOnAddConnection?: boolean;
34
35
  };
35
- export declare function MultiDeviceWhisperer<T extends DeviceConnectionState>({ createInitialConnectionState, }?: DeviceWhispererProps<T>): {
36
+ export declare function useMultiDeviceWhisperer<T extends DeviceConnectionState>({ createInitialConnectionState, setFocussedConnectionOnAddConnection, }?: DeviceWhispererProps<T>): {
36
37
  focussedConnection: string | undefined;
37
38
  setFocussedConnection: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
38
39
  connections: T[];
@@ -16,7 +16,7 @@ export function createDefaultInitialDeviceState(uuid, props) {
16
16
  /* One Device Whisperer is used for all like-devices, such as all Serial with Protobuf.
17
17
  Any e.g. Serial without Protobuf, or LoRaWAN etc. devices would be handled via a separate device whisperer
18
18
  */
19
- export function MultiDeviceWhisperer({ createInitialConnectionState = createDefaultInitialDeviceState, } = {}) {
19
+ export function useMultiDeviceWhisperer({ createInitialConnectionState = createDefaultInitialDeviceState, setFocussedConnectionOnAddConnection, } = {}) {
20
20
  const [connections, setConnections] = useState([]);
21
21
  const connectionsRef = useRef(new Map());
22
22
  const [isReady, setIsReady] = useState(false);
@@ -54,6 +54,9 @@ export function MultiDeviceWhisperer({ createInitialConnectionState = createDefa
54
54
  };
55
55
  connectionsRef.current.set(uuid, newConnection);
56
56
  setConnections(Array.from(connectionsRef.current.values()));
57
+ // Automatically set to the latest connection
58
+ if (!!setFocussedConnectionOnAddConnection)
59
+ setFocussedConnection(uuid);
57
60
  return uuid;
58
61
  };
59
62
  // ---------- REMOVE ----------
@@ -1,13 +1,13 @@
1
- import { AddConnectionProps, DeviceConnectionState, MultiDeviceWhisperer } from "../base/device-whisperer.js";
1
+ import { AddConnectionProps, DeviceConnectionState, useMultiDeviceWhisperer } from "../base/device-whisperer.js";
2
2
  export type TopicHandlerContext<AppLayer extends DeviceConnectionState> = {
3
- base: ReturnType<typeof MultiDeviceWhisperer<AppLayer>>;
3
+ base: ReturnType<typeof useMultiDeviceWhisperer<AppLayer>>;
4
4
  uuid: string;
5
5
  };
6
6
  export type TopicHandlerMap<AppLayer extends DeviceConnectionState, Topic extends string | number, Message = any> = {
7
7
  [K in Topic]?: (message: Message, context: TopicHandlerContext<AppLayer>) => void;
8
8
  };
9
9
  export type ProtobufDeviceWhispererProps<AppLayer extends DeviceConnectionState, Topic extends string | number, MessageRX = any, MessageTX = any> = {
10
- transportLayer: ReturnType<typeof MultiDeviceWhisperer<AppLayer>>;
10
+ transportLayer: ReturnType<typeof useMultiDeviceWhisperer<AppLayer>>;
11
11
  encodeRX: (message: MessageTX) => Uint8Array;
12
12
  decodeTX: (bytes: Uint8Array) => MessageRX;
13
13
  messageTypeField: keyof MessageRX;
@@ -15,7 +15,7 @@ export type ProtobufDeviceWhispererProps<AppLayer extends DeviceConnectionState,
15
15
  HEADER?: Uint8Array;
16
16
  expectLength?: boolean;
17
17
  };
18
- export declare function ProtobufMultiDeviceWhisperer<AppLayer extends DeviceConnectionState, Topic extends string | number, MessageRX = any, MessageTX = any>({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER, }: ProtobufDeviceWhispererProps<AppLayer, Topic, MessageRX, MessageTX>): {
18
+ export declare function useProtobufMultiDeviceWhisperer<AppLayer extends DeviceConnectionState, Topic extends string | number, MessageRX = any, MessageTX = any>({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER, }: ProtobufDeviceWhispererProps<AppLayer, Topic, MessageRX, MessageTX>): {
19
19
  addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppLayer>) => Promise<string>;
20
20
  sendProtobuf: (uuid: string, message: MessageTX) => void;
21
21
  protoBufOnReceiveHandler: (uuid: string, data: string | Uint8Array) => void;
@@ -1,4 +1,4 @@
1
- export function ProtobufMultiDeviceWhisperer({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER = new Uint8Array([]), }) {
1
+ export function useProtobufMultiDeviceWhisperer({ transportLayer, encodeRX, decodeTX, messageTypeField, rxTopicHandlerMap, HEADER = new Uint8Array([]), }) {
2
2
  // --- Utils ---
3
3
  const concatUint8Arrays = (a, b) => {
4
4
  const result = new Uint8Array(a.length + b.length);
@@ -5,6 +5,8 @@ export type ESP32ConnectionState = DeviceConnectionState & {
5
5
  baudrate?: number;
6
6
  transport?: Transport;
7
7
  esp?: ESPLoader;
8
+ debugLogging?: false;
9
+ reader?: AsyncGenerator<Uint8Array>;
8
10
  slipReadWrite?: boolean;
9
11
  isFlashing: boolean;
10
12
  flashProgress: number;
@@ -15,7 +17,7 @@ export type FlashFirmwareProps = {
15
17
  firmwareBlob?: Blob;
16
18
  fileArray?: FlashOptions["fileArray"];
17
19
  };
18
- export declare function ESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP32ConnectionState>({ releasePortByDefault, ...props }?: {
20
+ export declare function useESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP32ConnectionState>({ releasePortByDefault, ...props }?: {
19
21
  releasePortByDefault: boolean;
20
22
  } & DeviceWhispererProps<AppOrMessageLayer>): {
21
23
  addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppOrMessageLayer>) => Promise<string>;
@@ -1,8 +1,8 @@
1
1
  import { useEffect } from "react";
2
2
  import { ESPLoader, Transport } from "esptool-js";
3
- import { MultiDeviceWhisperer, } from "../base/device-whisperer.js";
4
- export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = { releasePortByDefault: true }) {
5
- const base = MultiDeviceWhisperer(props);
3
+ import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
4
+ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = { releasePortByDefault: true }) {
5
+ const base = useMultiDeviceWhisperer(props);
6
6
  const defaultOnReceive = (uuid, data) => {
7
7
  const text = typeof data === "string" ? data : new TextDecoder().decode(data);
8
8
  const trimmed = text.trim();
@@ -35,8 +35,9 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
35
35
  let slipBuffer = []; // accumulate SLIP frames
36
36
  let inSlipFrame = false; // are we inside a SLIP frame?
37
37
  let escapeNext = false;
38
+ const reader = transport.rawRead();
39
+ base.updateConnection(uuid, (c) => ({ ...c, reader }));
38
40
  try {
39
- const reader = transport.rawRead();
40
41
  while (true) {
41
42
  const conn = base.getConnection(uuid);
42
43
  if (!conn) {
@@ -121,6 +122,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
121
122
  });
122
123
  }
123
124
  finally {
125
+ // Clear reader state so it's fresh for next connect
126
+ base.updateConnection(uuid, (c) => ({ ...c, reader: undefined }));
124
127
  base.appendLog(uuid, {
125
128
  level: 0,
126
129
  message: "[!] Serial disconnected",
@@ -152,6 +155,21 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
152
155
  });
153
156
  base.updateConnection(uuid, (c) => ({ ...c, port }));
154
157
  }
158
+ else {
159
+ try {
160
+ const oldInfo = port.getInfo();
161
+ const authorizedPorts = await navigator.serial.getPorts();
162
+ const freshPort = authorizedPorts.find((p) => p.getInfo().usbVendorId === oldInfo.usbVendorId &&
163
+ p.getInfo().usbProductId === oldInfo.usbProductId);
164
+ if (freshPort) {
165
+ port = freshPort; // Swap out the dead pointer
166
+ base.updateConnection(uuid, (c) => ({ ...c, port }));
167
+ }
168
+ }
169
+ catch (e) {
170
+ console.warn(`[${uuid}] Failed to silently heal port reference:`, e);
171
+ }
172
+ }
155
173
  base.updateConnection(uuid, (c) => ({ ...c, isConnecting: true }));
156
174
  const use_baudrate = baudrate ?? conn.baudrate ?? 115200;
157
175
  const transport = new Transport(port, false, false);
@@ -160,6 +178,7 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
160
178
  transport,
161
179
  baudrate: use_baudrate,
162
180
  enableTracing: false,
181
+ debugLogging: !!conn.debugLogging,
163
182
  });
164
183
  try {
165
184
  await esploader.main();
@@ -182,6 +201,7 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
182
201
  base.updateConnection(uuid, (c) => ({
183
202
  ...c,
184
203
  transport,
204
+ esp: esploader,
185
205
  baudrate: use_baudrate,
186
206
  isConnected: true,
187
207
  isConnecting: false,
@@ -191,7 +211,9 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
191
211
  message: "[✓] Serial connected",
192
212
  });
193
213
  await conn.onConnect?.();
194
- await readLoop(uuid, transport);
214
+ // FIRE AND FORGET
215
+ // We initiate the read loop but DO NOT await it, returning control immediately.
216
+ readLoop(uuid, transport).catch((e) => console.error(`[${uuid}] Background read loop failed:`, e));
195
217
  }
196
218
  catch (err) {
197
219
  base.updateConnection(uuid, (c) => ({
@@ -208,9 +230,29 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
208
230
  };
209
231
  const disconnect = async (uuid, timeout = 2000) => {
210
232
  const conn = base.getConnection(uuid);
233
+ // 1. Politely kill our high-level generator
234
+ if (conn?.reader && typeof conn.reader.return === "function") {
235
+ try {
236
+ // @ts-ignore
237
+ await conn.reader.return();
238
+ }
239
+ catch (e) {
240
+ console.warn(`[${uuid}] Error returning generator lock:`, e);
241
+ }
242
+ }
211
243
  if (conn?.transport) {
212
244
  try {
213
- // Attempt disconnect, but don’t hang if the port is crashed
245
+ // 2. AGGRESSIVE LOCK REMOVAL (The Fix)
246
+ // Dig into esptool-js's internal state and force the native reader to release.
247
+ // If we don't do this, transport.disconnect() will hang and fail to close the port.
248
+ const internalReader = conn.transport.reader;
249
+ if (internalReader) {
250
+ await internalReader.cancel().catch(() => { });
251
+ if (typeof internalReader.releaseLock === "function") {
252
+ internalReader.releaseLock();
253
+ }
254
+ }
255
+ // 3. Now that locks are cleared, attempt standard disconnect
214
256
  await Promise.race([
215
257
  conn.transport.disconnect(),
216
258
  new Promise((resolve) => setTimeout(resolve, timeout)),
@@ -220,11 +262,28 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
220
262
  console.warn(`[${uuid}] Serial Disconnect error:`, e);
221
263
  }
222
264
  }
223
- // Always clear the transport and reset connection state
265
+ // 4. NATIVE SAFETY NET
266
+ // Guarantee the port is closed natively just in case the transport failed
267
+ if (conn?.port) {
268
+ try {
269
+ if (conn.port.readable?.locked) {
270
+ await conn.port.readable.cancel().catch(() => { });
271
+ }
272
+ if (conn.port.writable?.locked) {
273
+ await conn.port.writable.abort().catch(() => { });
274
+ }
275
+ await conn.port.close().catch(() => { });
276
+ }
277
+ catch (e) {
278
+ // It will throw if already closed, which is perfectly fine.
279
+ }
280
+ }
281
+ // 5. Always clear the transport and reset connection state
224
282
  base.updateConnection(uuid, (c) => ({
225
283
  ...c,
226
- port: releasePortByDefault ? null : c.port,
227
- transport: releasePortByDefault ? null : c.transport,
284
+ port: releasePortByDefault ? undefined : c.port,
285
+ transport: releasePortByDefault ? undefined : c.transport,
286
+ reader: undefined, // Guarantee reader is cleared out
228
287
  isConnected: false,
229
288
  isConnecting: false,
230
289
  autoConnect: false,
@@ -298,31 +357,35 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
298
357
  };
299
358
  // This function now handles an entire device flashing session
300
359
  const handleFlashFirmware = async (uuid, assetsToFlash) => {
360
+ // Capture the original connection and port reference before disconnect
301
361
  const conn = base.getConnection(uuid);
362
+ // 1. Bail if we don't have the required state
302
363
  if (!conn || !conn.port || assetsToFlash.length === 0)
303
364
  return;
365
+ // Gracefully cancel reader and completely drop the transport natively
304
366
  await disconnect(uuid);
367
+ await new Promise((resolve) => setTimeout(resolve, 400));
305
368
  base.updateConnection(uuid, (c) => ({
306
369
  ...c,
370
+ port: conn.port, // Restore the port into state in case disconnect wiped it
307
371
  isFlashing: true,
308
372
  flashProgress: 0,
309
373
  flashError: undefined,
310
374
  }));
375
+ // Declare outside the try block (Fixes variable shadowing)
376
+ let transport;
377
+ let esploader;
311
378
  try {
312
379
  // --- Connect ONCE ---
313
- const transport = new Transport(conn.port, true);
314
- const esploader = new ESPLoader({
380
+ transport = new Transport(conn.port, true);
381
+ esploader = new ESPLoader({
315
382
  transport,
316
383
  baudrate: 921600,
317
384
  enableTracing: false,
385
+ // debugLogging: !!conn.debugLogging,
318
386
  });
319
- try {
320
- await esploader.main();
321
- }
322
- catch (e) {
323
- console.log("failed to esploader.main()", e);
324
- return;
325
- }
387
+ // This may fail, that'll bubble the failure up to the upper try/catch
388
+ await esploader.main();
326
389
  // --- Prepare an ARRAY of files for the library ---
327
390
  const fileArray = await Promise.all(assetsToFlash.map(async ({ blob, address }) => {
328
391
  const arrayBuffer = await blob.arrayBuffer();
@@ -331,32 +394,34 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
331
394
  .join("");
332
395
  return { data: binaryString, address };
333
396
  }));
397
+ let lastRenderTime = 0;
334
398
  const flashOptions = {
335
- fileArray, // Pass the whole array here
399
+ fileArray,
336
400
  flashSize: "keep",
337
401
  flashMode: "qio",
338
402
  flashFreq: "80m",
339
403
  eraseAll: fileArray.length > 1, // Writing more than 1 thing, so likely writing partitions.
340
404
  compress: true,
341
405
  reportProgress: (fileIndex, written, total) => {
342
- // You can enhance progress reporting to show which file is being flashed
343
406
  const progress = (written / total) * 100;
344
- console.log(`Flashing file ${fileIndex + 1}/${fileArray.length}: ${progress.toFixed(1)}%`);
345
- base.updateConnection(uuid, (c) => ({
346
- ...c,
347
- flashProgress: progress,
348
- }));
407
+ const now = performance.now();
408
+ // Throttle UI updates to at most once every 100ms (~10 FPS),
409
+ // but ALWAYS ensure the final 100% tick renders.
410
+ if (now - lastRenderTime > 100 || written === total) {
411
+ lastRenderTime = now;
412
+ // Optional: Only log every 100ms as well to prevent console spam
413
+ console.log(`Flashing file ${fileIndex + 1}/${fileArray.length}: ${progress.toFixed(1)}%`);
414
+ base.updateConnection(uuid, (c) => ({
415
+ ...c,
416
+ flashProgress: progress,
417
+ }));
418
+ }
349
419
  },
350
420
  };
351
421
  // --- Call writeFlash ONCE with all files ---
352
- try {
353
- base.updateConnection(uuid, (c) => ({ ...c, flashProgress: -1 }));
354
- await esploader.writeFlash(flashOptions);
355
- }
356
- catch (e) {
357
- console.log("failed to esploader.writeFlash", e);
358
- }
359
- // --- Disconnect ---
422
+ base.updateConnection(uuid, (c) => ({ ...c, flashProgress: -1 }));
423
+ await esploader.writeFlash(flashOptions);
424
+ // --- Disconnect cleanly ---
360
425
  await esploader.after();
361
426
  try {
362
427
  await transport.disconnect();
@@ -372,6 +437,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
372
437
  isFlashing: false,
373
438
  flashProgress: 100,
374
439
  }));
440
+ // --- Reconnect the standard monitor ---
441
+ await connect(uuid);
375
442
  }
376
443
  catch (e) {
377
444
  console.error(`[${uuid}] Flashing failed:`, e);
@@ -380,6 +447,8 @@ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = {
380
447
  isFlashing: false,
381
448
  flashError: e?.message ?? "Unknown error",
382
449
  }));
450
+ // Attempt recovery
451
+ await connect(uuid);
383
452
  }
384
453
  };
385
454
  useEffect(() => {
@@ -13,7 +13,7 @@ export type MQTTConnectionState = DeviceConnectionState & {
13
13
  pingFunction?: (props?: any) => void;
14
14
  touchHeartbeat?: () => void;
15
15
  };
16
- export declare function MQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTConnectionState>({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid, pubTopicFromUuid, serverPort, clientId, username, password, serverAutoConnect, serverConnectOn, enableWatchdog, ...props }: {
16
+ export declare function useMQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTConnectionState>({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid, pubTopicFromUuid, serverPort, clientId, username, password, serverAutoConnect, serverConnectOn, enableWatchdog, ...props }: {
17
17
  serverUrl?: string;
18
18
  serverUrlFnc?: () => Promise<string> | string;
19
19
  uuidFromMessage: (topic: string, payload: Buffer<ArrayBufferLike>) => string;
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useRef } from "react";
2
- import { MultiDeviceWhisperer, } from "../base/device-whisperer.js";
2
+ import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
3
3
  import mqtt from "mqtt";
4
- export function MQTTMultiDeviceWhisperer({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid = undefined, pubTopicFromUuid = undefined, serverPort = 443, clientId = undefined, username = undefined, password = undefined, serverAutoConnect = true, serverConnectOn = false, enableWatchdog = true, ...props }) {
5
- const base = MultiDeviceWhisperer(props);
4
+ export function useMQTTMultiDeviceWhisperer({ serverUrl, serverUrlFnc, uuidFromMessage, subTopicFromUuid = undefined, pubTopicFromUuid = undefined, serverPort = 443, clientId = undefined, username = undefined, password = undefined, serverAutoConnect = true, serverConnectOn = false, enableWatchdog = true, ...props }) {
5
+ const base = useMultiDeviceWhisperer(props);
6
6
  const clientRef = useRef(null);
7
7
  const isUnmountedRef = useRef(false);
8
8
  // ✨ NEW: Manage our own reconnect timer
@@ -88,7 +88,7 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, serverUrlFnc, uuidFromMess
88
88
  };
89
89
  const connectToMQTTServer = async () => {
90
90
  if (!serverUrl && !serverUrlFnc) {
91
- console.error("MQTT MultiDeviceWhisperer requires either serverUrl or serverUrlFnc");
91
+ console.error("MQTT useMultiDeviceWhisperer requires either serverUrl or serverUrlFnc");
92
92
  return;
93
93
  }
94
94
  if (reconnectTimeoutRef.current)
@@ -38,7 +38,7 @@ export type SerialConnectionState = DeviceConnectionState & {
38
38
  baudrate?: number;
39
39
  slipReadWrite?: boolean;
40
40
  };
41
- export declare function SerialMultiDeviceWhisperer<AppOrMessageLayer extends SerialConnectionState>({ ...props }?: DeviceWhispererProps<AppOrMessageLayer>): {
41
+ export declare function useSerialMultiDeviceWhisperer<AppOrMessageLayer extends SerialConnectionState>({ ...props }?: DeviceWhispererProps<AppOrMessageLayer>): {
42
42
  addConnection: ({ uuid, propCreator, }: AddConnectionProps<AppOrMessageLayer>) => Promise<string | undefined>;
43
43
  removeConnection: (uuid: string) => Promise<void>;
44
44
  connect: (uuid: string, baudrate?: number) => Promise<void>;
@@ -1,4 +1,4 @@
1
- import { MultiDeviceWhisperer, } from "../base/device-whisperer.js";
1
+ import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
2
2
  import { useEffect, useRef, useState } from "react";
3
3
  /*
4
4
  ┌────────────────────────────────┐
@@ -151,8 +151,8 @@ export class UsbTransport {
151
151
  // Standard CDC-ACM requests
152
152
  UsbTransport.SET_LINE_CODING = 0x20;
153
153
  UsbTransport.SET_CONTROL_LINE_STATE = 0x22;
154
- export function SerialMultiDeviceWhisperer({ ...props } = {}) {
155
- const base = MultiDeviceWhisperer(props);
154
+ export function useSerialMultiDeviceWhisperer({ ...props } = {}) {
155
+ const base = useMultiDeviceWhisperer(props);
156
156
  const releasePortByDefaultRef = useRef(false);
157
157
  const [releasePortByDefaultState, setReleasePortByDefaultState] = useState(false);
158
158
  useEffect(() => {
@@ -9,7 +9,7 @@ export type DeviceObjectResponse = {
9
9
  deviceConnectedTime: Date | null;
10
10
  deviceLastCommTime: Date | null;
11
11
  };
12
- export declare function WebsocketMultiDeviceWhisperer<AppOrMessageLayer extends WebsocketConnectionState>({ server_url, server_port, ...props }: {
12
+ export declare function useWebsocketMultiDeviceWhisperer<AppOrMessageLayer extends WebsocketConnectionState>({ server_url, server_port, ...props }: {
13
13
  server_url: string;
14
14
  server_port: number;
15
15
  }): {
@@ -1,7 +1,7 @@
1
1
  import { useEffect } from "react";
2
- import { MultiDeviceWhisperer, } from "../base/device-whisperer.js";
3
- export function WebsocketMultiDeviceWhisperer({ server_url, server_port, ...props }) {
4
- const base = MultiDeviceWhisperer(props);
2
+ import { useMultiDeviceWhisperer, } from "../base/device-whisperer.js";
3
+ export function useWebsocketMultiDeviceWhisperer({ server_url, server_port, ...props }) {
4
+ const base = useMultiDeviceWhisperer(props);
5
5
  const defaultOnReceive = (uuid, data) => {
6
6
  const conn = base.getConnection(uuid);
7
7
  if (!conn)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ota-hub-reactjs",
3
- "version": "0.0.19",
3
+ "version": "0.1.2",
4
4
  "description": "ReactJS tools for building web apps to flash MCU devices such as esp32, brought to you by OTA Hub.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",