ota-hub-reactjs 0.0.14 → 0.0.16

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.
@@ -2,7 +2,7 @@ export type LogMessage = string | number | boolean | Record<string, any> | any[]
2
2
  export type LogLine = {
3
3
  level: number;
4
4
  message: LogMessage;
5
- timestamp?: string | Date;
5
+ timestamp?: Date;
6
6
  };
7
7
  type PropCreatorProps<T> = (uuid: string) => Partial<T> | undefined;
8
8
  export interface AddConnectionProps<T> {
@@ -30,7 +30,6 @@ export type DeviceWhispererProps<T extends DeviceConnectionState> = {
30
30
  };
31
31
  export declare function MultiDeviceWhisperer<T extends DeviceConnectionState>({ createInitialConnectionState, }?: DeviceWhispererProps<T>): {
32
32
  connections: T[];
33
- connectionsRef: import("react").RefObject<T[]>;
34
33
  addConnection: ({ uuid, propCreator }: AddConnectionProps<T>) => Promise<string>;
35
34
  removeConnection: (uuid: string) => void;
36
35
  connect: (_uuid: string) => void;
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { useEffect, useRef, useState } from "react";
3
- import { uniqueNamesGenerator, animals } from "unique-names-generator";
4
3
  ;
5
4
  // Initial, generic state for the generic device
6
5
  export function createDefaultInitialDeviceState(uuid, props) {
@@ -20,53 +19,55 @@ export function createDefaultInitialDeviceState(uuid, props) {
20
19
  */
21
20
  export function MultiDeviceWhisperer({ createInitialConnectionState = createDefaultInitialDeviceState, } = {}) {
22
21
  const [connections, setConnections] = useState([]);
23
- const connectionsRef = useRef(connections);
22
+ const connectionsRef = useRef(new Map());
24
23
  const [isReady, setIsReady] = useState(false);
25
- const getConnection = (uuid) => connectionsRef.current.find(c => c.uuid === uuid);
24
+ // ---------- GET ----------
25
+ const getConnection = (uuid) => connectionsRef.current.get(uuid);
26
+ // ---------- UPDATE ----------
26
27
  const updateConnection = (uuid, updater) => {
27
- setConnections(prev => {
28
- const updated = prev.map((c) => c.uuid === uuid ? updater(c) : c);
29
- connectionsRef.current = updated;
30
- return updated;
31
- });
28
+ const current = connectionsRef.current.get(uuid);
29
+ if (!current)
30
+ return;
31
+ const next = updater(current);
32
+ connectionsRef.current.set(uuid, next);
33
+ setConnections(Array.from(connectionsRef.current.values()));
32
34
  };
33
35
  const updateConnectionName = (uuid, name) => {
34
- setConnections((prev) => prev.map((c) => (c.uuid === uuid ? { ...c, name } : c)));
36
+ updateConnection(uuid, (c) => ({ ...c, name }));
35
37
  };
36
38
  const appendLog = (uuid, log) => {
37
- if (!log.timestamp) {
39
+ if (!log.timestamp)
38
40
  log.timestamp = new Date();
39
- }
40
41
  updateConnection(uuid, (c) => ({
41
42
  ...c,
42
43
  logs: [...c.logs.slice(-199), log],
43
44
  }));
44
45
  };
46
+ // ---------- ADD ----------
45
47
  const addConnection = async ({ uuid, propCreator }) => {
46
- uuid = uuid ?? uniqueNamesGenerator({ dictionaries: [animals] });
48
+ uuid = uuid ?? `unnamed_device_${connectionsRef.current.size}`;
47
49
  const props = propCreator?.(uuid);
48
50
  const newConnection = {
49
51
  ...createDefaultInitialDeviceState(uuid),
50
52
  ...createInitialConnectionState(uuid),
51
53
  ...props
52
54
  };
53
- connectionsRef.current = [...connectionsRef.current, newConnection];
54
- setConnections(prev => [...prev, newConnection]);
55
- const anyUpdatedConnection = getConnection(uuid);
56
- if (!anyUpdatedConnection) {
57
- return "";
58
- }
55
+ connectionsRef.current.set(uuid, newConnection);
56
+ setConnections(Array.from(connectionsRef.current.values()));
59
57
  return uuid;
60
58
  };
59
+ // ---------- REMOVE ----------
61
60
  const removeConnection = (uuid) => {
62
- setConnections((prev) => prev.filter((c) => c.uuid !== uuid));
61
+ connectionsRef.current.delete(uuid);
62
+ setConnections(Array.from(connectionsRef.current.values()));
63
63
  };
64
+ // ---------- EFFECT ----------
64
65
  useEffect(() => {
65
- connectionsRef.current = connections;
66
- }, [connections]);
66
+ // initial snapshot
67
+ setConnections(Array.from(connectionsRef.current.values()));
68
+ }, []);
67
69
  return {
68
70
  connections,
69
- connectionsRef,
70
71
  addConnection,
71
72
  removeConnection,
72
73
  connect: (_uuid) => { },
@@ -20,7 +20,6 @@ export declare function ProtobufMultiDeviceWhisperer<AppLayer extends DeviceConn
20
20
  sendProtobuf: (uuid: string, message: MessageTX) => void;
21
21
  protoBufOnReceiveHandler: (uuid: string, data: string | Uint8Array) => void;
22
22
  connections: AppLayer[];
23
- connectionsRef: import("react").RefObject<AppLayer[]>;
24
23
  removeConnection: (uuid: string) => void;
25
24
  connect: (_uuid: string) => void;
26
25
  disconnect: (_uuid: string) => void;
@@ -1,5 +1,5 @@
1
1
  import { ESPLoader, FlashOptions, Transport } from "esptool-js";
2
- import { AddConnectionProps, DeviceConnectionState } from "../base/device-whisperer.js";
2
+ import { AddConnectionProps, DeviceConnectionState, DeviceWhispererProps } from "../base/device-whisperer.js";
3
3
  export type ESP32ConnectionState = DeviceConnectionState & {
4
4
  port?: SerialPort;
5
5
  baudrate?: number;
@@ -15,7 +15,9 @@ export type FlashFirmwareProps = {
15
15
  firmwareBlob?: Blob;
16
16
  fileArray?: FlashOptions["fileArray"];
17
17
  };
18
- export declare function ESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP32ConnectionState>({ ...props }?: {}): {
18
+ export declare function ESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP32ConnectionState>({ releasePortByDefault, ...props }?: {
19
+ releasePortByDefault: boolean;
20
+ } & DeviceWhispererProps<AppOrMessageLayer>): {
19
21
  addConnection: ({ uuid, propCreator }: AddConnectionProps<AppOrMessageLayer>) => Promise<string>;
20
22
  removeConnection: (uuid: string) => Promise<void>;
21
23
  connect: (uuid: string, baudrate?: number, restart_on_connect?: boolean) => Promise<void>;
@@ -26,7 +28,6 @@ export declare function ESP32MultiDeviceWhisperer<AppOrMessageLayer extends ESP3
26
28
  address: number;
27
29
  }[]) => Promise<void>;
28
30
  connections: AppOrMessageLayer[];
29
- connectionsRef: import("react").RefObject<AppOrMessageLayer[]>;
30
31
  updateConnection: (uuid: string, updater: (c: AppOrMessageLayer) => AppOrMessageLayer) => void;
31
32
  updateConnectionName: (uuid: string, name: string) => void;
32
33
  getConnection: (uuid: string) => AppOrMessageLayer | undefined;
@@ -1,39 +1,19 @@
1
1
  import { useEffect } from "react";
2
2
  import { ESPLoader, Transport } from "esptool-js";
3
3
  import { MultiDeviceWhisperer } from "../base/device-whisperer.js";
4
- export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
4
+ export function ESP32MultiDeviceWhisperer({ releasePortByDefault, ...props } = { releasePortByDefault: true }) {
5
5
  const base = MultiDeviceWhisperer(props);
6
6
  const defaultOnReceive = (uuid, data) => {
7
- const conn = base.getConnection(uuid);
8
- if (!conn)
7
+ const text = typeof data === "string"
8
+ ? data
9
+ : new TextDecoder().decode(data);
10
+ const trimmed = text.trim();
11
+ if (!trimmed)
9
12
  return;
10
- const decoder = new TextDecoder();
11
- let bytes;
12
- if (typeof data === "string") {
13
- bytes = new TextEncoder().encode(data);
14
- }
15
- else if (data instanceof ArrayBuffer) {
16
- bytes = new Uint8Array(data);
17
- }
18
- else {
19
- bytes = data;
20
- }
21
- const asText = decoder.decode(bytes);
22
- const combined = conn.readBufferLeftover + asText;
23
- const lines = combined.split("\r\n");
24
- base.updateConnection(uuid, (c) => ({
25
- ...c,
26
- readBufferLeftover: lines.pop() || ""
27
- }));
28
- for (const line of lines) {
29
- const trimmed = line.trim();
30
- if (trimmed) {
31
- base.appendLog(uuid, {
32
- level: 2,
33
- message: trimmed,
34
- });
35
- }
36
- }
13
+ base.appendLog(uuid, {
14
+ level: 2,
15
+ message: trimmed,
16
+ });
37
17
  };
38
18
  const defaultSend = async (uuid, data) => {
39
19
  const conn = base.getConnection(uuid);
@@ -52,9 +32,7 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
52
32
  return;
53
33
  };
54
34
  const readLoop = async (uuid, transport) => {
55
- const conn = base.getConnection(uuid);
56
- if (!conn)
57
- return;
35
+ const textDecoder = new TextDecoder();
58
36
  let readBuffer = ""; // accumulate ASCII/lines
59
37
  let slipBuffer = []; // accumulate SLIP frames
60
38
  let inSlipFrame = false; // are we inside a SLIP frame?
@@ -62,6 +40,12 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
62
40
  try {
63
41
  const reader = transport.rawRead();
64
42
  while (true) {
43
+ const conn = base.getConnection(uuid);
44
+ if (!conn) {
45
+ console.log("Kack!");
46
+ return;
47
+ }
48
+ ;
65
49
  const { value, done } = await reader.next();
66
50
  if (done || !value)
67
51
  break;
@@ -117,9 +101,8 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
117
101
  escapeNext = false;
118
102
  continue;
119
103
  }
120
- // treat as normal ASCII text
121
- const char = String.fromCharCode(b);
122
- readBuffer += char;
104
+ // treat as normal text (correctly)
105
+ readBuffer += textDecoder.decode(new Uint8Array([b]), { stream: true });
123
106
  // check for newline
124
107
  let newlineIndex;
125
108
  while ((newlineIndex = readBuffer.indexOf("\n")) >= 0) {
@@ -244,8 +227,8 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
244
227
  // Always clear the transport and reset connection state
245
228
  base.updateConnection(uuid, (c) => ({
246
229
  ...c,
247
- port: null,
248
- transport: null,
230
+ port: releasePortByDefault ? null : c.port,
231
+ transport: releasePortByDefault ? null : c.transport,
249
232
  isConnected: false,
250
233
  isConnecting: false,
251
234
  autoConnect: false,
@@ -285,8 +268,11 @@ export function ESP32MultiDeviceWhisperer({ ...props } = {}) {
285
268
  base.removeConnection(uuid);
286
269
  };
287
270
  const reconnectAll = async (...connectionProps) => {
288
- const connections = [...base.connectionsRef.current]; // snapshot first
289
- await Promise.all(connections.map(async (c) => {
271
+ const connectionIds = base.connections.map(c => c.uuid);
272
+ await Promise.all(connectionIds.map(async (id) => {
273
+ const c = base.getConnection(id);
274
+ if (!c)
275
+ return;
290
276
  await disconnect(c.uuid);
291
277
  await new Promise((res) => setTimeout(res, 250));
292
278
  return connect(c.uuid, ...connectionProps);
@@ -22,7 +22,6 @@ export declare function MQTTMultiDeviceWhisperer<AppOrMessageLayer extends MQTTC
22
22
  reconnectAll: () => Promise<void>;
23
23
  connectToMQTTServer: () => (() => void) | undefined;
24
24
  connections: AppOrMessageLayer[];
25
- connectionsRef: import("react").RefObject<AppOrMessageLayer[]>;
26
25
  updateConnection: (uuid: string, updater: (c: AppOrMessageLayer) => AppOrMessageLayer) => void;
27
26
  updateConnectionName: (uuid: string, name: string) => void;
28
27
  getConnection: (uuid: string) => AppOrMessageLayer | undefined;
@@ -189,7 +189,7 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicF
189
189
  Error("In MQTT you MUST define a UUID otherwise we don't know what device we're connecting to!");
190
190
  return;
191
191
  }
192
- if (base.connectionsRef.current.some(c => c.uuid === uuid) || addingConnections.current.has(uuid)) {
192
+ if (base.connections.some(c => c.uuid === uuid) || addingConnections.current.has(uuid)) {
193
193
  return;
194
194
  }
195
195
  await base.addConnection({
@@ -221,13 +221,15 @@ export function MQTTMultiDeviceWhisperer({ serverUrl, uuidFromMessage, subTopicF
221
221
  base.removeConnection(uuid);
222
222
  };
223
223
  const reconnectAll = async () => {
224
- for (const c of base.connectionsRef.current) {
224
+ const connectionIds = base.connections.map(c => c.uuid);
225
+ await Promise.all(connectionIds.map(async (id) => {
226
+ const c = base.getConnection(id);
227
+ if (!c)
228
+ return;
225
229
  await disconnect(c.uuid);
226
230
  await new Promise((res) => setTimeout(res, 250));
227
- }
228
- for (const c of base.connectionsRef.current) {
229
- await connect(c.uuid);
230
- }
231
+ return connect(c.uuid);
232
+ }));
231
233
  };
232
234
  useEffect(() => {
233
235
  if (!(serverAutoConnect || serverConnectOn))
@@ -1,7 +1,8 @@
1
- import { DeviceConnectionState, AddConnectionProps } from "../base/device-whisperer.js";
1
+ import { DeviceConnectionState, AddConnectionProps, DeviceWhispererProps } from "../base/device-whisperer.js";
2
2
  export declare class UsbTransport {
3
3
  device: USBDevice;
4
- interfaceNumber: number;
4
+ controlInterface: number;
5
+ dataInterface: number;
5
6
  endpointIn: number;
6
7
  endpointOut: number;
7
8
  private static readonly SET_LINE_CODING;
@@ -37,14 +38,15 @@ export type SerialConnectionState = DeviceConnectionState & {
37
38
  baudrate?: number;
38
39
  slipReadWrite?: boolean;
39
40
  };
40
- export declare function SerialMultiDeviceWhisperer<AppOrMessageLayer extends SerialConnectionState>({ ...props }?: {}): {
41
+ export declare function SerialMultiDeviceWhisperer<AppOrMessageLayer extends SerialConnectionState>({ ...props }?: DeviceWhispererProps<AppOrMessageLayer>): {
41
42
  addConnection: ({ uuid, propCreator }: AddConnectionProps<AppOrMessageLayer>) => Promise<string | undefined>;
42
43
  removeConnection: (uuid: string) => Promise<void>;
43
44
  connect: (uuid: string, baudrate?: number) => Promise<void>;
44
45
  disconnect: (uuid: string) => Promise<void>;
45
- reconnectAll: () => Promise<void>;
46
+ reconnectAll: (...connectionProps: any) => Promise<void>;
47
+ releasePortByDefault: boolean;
48
+ setReleasePortByDefault: import("react").Dispatch<import("react").SetStateAction<boolean>>;
46
49
  connections: AppOrMessageLayer[];
47
- connectionsRef: import("react").RefObject<AppOrMessageLayer[]>;
48
50
  updateConnection: (uuid: string, updater: (c: AppOrMessageLayer) => AppOrMessageLayer) => void;
49
51
  updateConnectionName: (uuid: string, name: string) => void;
50
52
  getConnection: (uuid: string) => AppOrMessageLayer | undefined;
@@ -1,5 +1,5 @@
1
1
  import { MultiDeviceWhisperer } from "../base/device-whisperer.js";
2
- import { useEffect } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  /*
4
4
  ┌────────────────────────────────┐
5
5
  │ Device Whisperer (Generic) │ (connect/disconnect, state mgmt) - "Abstraction Layer"
@@ -14,7 +14,8 @@ import { useEffect } from "react";
14
14
  // usb-transport.ts
15
15
  export class UsbTransport {
16
16
  constructor(device) {
17
- this.interfaceNumber = 0;
17
+ this.controlInterface = 0;
18
+ this.dataInterface = 1;
18
19
  this.endpointIn = 0;
19
20
  this.endpointOut = 0;
20
21
  this.device = device;
@@ -27,25 +28,40 @@ export class UsbTransport {
27
28
  if (this.device.configuration === null) {
28
29
  await this.device.selectConfiguration(1);
29
30
  }
30
- // Find the CDC Data interface (usually class 10) or just the first one with Bulk endpoints
31
31
  const interfaces = this.device.configuration?.interfaces || [];
32
- let targetInterface;
33
- for (const iface of interfaces) {
32
+ let ctrlIface;
33
+ let dataIface;
34
+ dataIface = interfaces.find(iface => {
34
35
  const endpoints = iface.alternate.endpoints;
35
- const hasIn = endpoints.some(e => e.direction === 'in' && e.type === 'bulk');
36
- const hasOut = endpoints.some(e => e.direction === 'out' && e.type === 'bulk');
37
- if (hasIn && hasOut) {
38
- targetInterface = iface;
39
- break;
36
+ return endpoints.some(e => e.direction === 'in' && e.type === 'bulk') &&
37
+ endpoints.some(e => e.direction === 'out' && e.type === 'bulk');
38
+ });
39
+ if (dataIface) {
40
+ this.dataInterface = dataIface.interfaceNumber;
41
+ ctrlIface = interfaces.find(i => i.interfaceNumber === this.dataInterface - 1)
42
+ || interfaces.find(i => i.alternate.interfaceClass === 2);
43
+ this.controlInterface = ctrlIface ? ctrlIface.interfaceNumber : this.dataInterface;
44
+ }
45
+ if (!dataIface) {
46
+ throw new Error("No serial-compatible Bulk interface found.");
47
+ }
48
+ // Note: On Android, if the OS has claimed Interface 0, this first call will fail.
49
+ try {
50
+ if (this.controlInterface !== this.dataInterface) {
51
+ await this.device.claimInterface(this.controlInterface);
40
52
  }
41
53
  }
42
- if (!targetInterface) {
43
- throw new Error("No serial-compatible interface found on device.");
54
+ catch (e) {
55
+ console.warn("Could not claim Control Interface (OS locked?). Proceeding to Data...", e);
56
+ // We continue, but setSignals might fail later.
57
+ }
58
+ try {
59
+ await this.device.claimInterface(this.dataInterface);
60
+ }
61
+ catch (e) {
62
+ throw new Error(`Failed to claim Data Interface. Android OS has locked the device driver. Try using a 'Vendor Specific' USB Class device or a native Serial App workaround.`);
44
63
  }
45
- this.interfaceNumber = targetInterface.interfaceNumber;
46
- await this.device.claimInterface(this.interfaceNumber);
47
- // Map endpoints
48
- const endpoints = targetInterface.alternate.endpoints;
64
+ const endpoints = dataIface.alternate.endpoints;
49
65
  this.endpointIn = endpoints.find(e => e.direction === 'in' && e.type === 'bulk').endpointNumber;
50
66
  this.endpointOut = endpoints.find(e => e.direction === 'out' && e.type === 'bulk').endpointNumber;
51
67
  await this.setBaudRate(baudRate);
@@ -57,7 +73,6 @@ export class UsbTransport {
57
73
  async write(data) {
58
74
  if (!this.device.opened)
59
75
  return;
60
- // Cast to 'unknown' then 'BufferSource' to satisfy the strict type definition
61
76
  await this.device.transferOut(this.endpointOut, data);
62
77
  }
63
78
  /**
@@ -85,14 +100,13 @@ export class UsbTransport {
85
100
  async setSignals({ dtr, rts }) {
86
101
  if (!this.device.opened)
87
102
  return;
88
- // CDC-ACM: DTR is bit 0, RTS is bit 1
89
103
  const value = (Number(dtr) | (Number(rts) << 1));
90
104
  await this.device.controlTransferOut({
91
105
  requestType: 'class',
92
106
  recipient: 'interface',
93
107
  request: UsbTransport.SET_CONTROL_LINE_STATE,
94
108
  value: value,
95
- index: this.interfaceNumber
109
+ index: this.controlInterface
96
110
  });
97
111
  }
98
112
  async setBaudRate(baud) {
@@ -102,20 +116,31 @@ export class UsbTransport {
102
116
  // 4 bytes: baud (LE), 1 byte: stop bits, 1 byte: parity, 1 byte: data bits
103
117
  const buffer = new ArrayBuffer(7);
104
118
  const view = new DataView(buffer);
105
- view.setUint32(0, baud, true); // Little Endian
106
- view.setUint8(4, 0); // 1 stop bit
107
- view.setUint8(5, 0); // No parity
108
- view.setUint8(6, 8); // 8 data bits
119
+ view.setUint32(0, baud, true);
120
+ view.setUint8(4, 0);
121
+ view.setUint8(5, 0);
122
+ view.setUint8(6, 8);
109
123
  await this.device.controlTransferOut({
110
124
  requestType: 'class',
111
125
  recipient: 'interface',
112
126
  request: UsbTransport.SET_LINE_CODING,
113
127
  value: 0,
114
- index: this.interfaceNumber
128
+ index: this.controlInterface
115
129
  }, buffer);
116
130
  }
117
131
  async disconnect() {
118
132
  if (this.device.opened) {
133
+ // Release both
134
+ try {
135
+ await this.device.releaseInterface(this.dataInterface);
136
+ }
137
+ catch (e) { }
138
+ if (this.controlInterface !== this.dataInterface) {
139
+ try {
140
+ await this.device.releaseInterface(this.controlInterface);
141
+ }
142
+ catch (e) { }
143
+ }
119
144
  await this.device.close();
120
145
  }
121
146
  }
@@ -125,6 +150,11 @@ UsbTransport.SET_LINE_CODING = 0x20;
125
150
  UsbTransport.SET_CONTROL_LINE_STATE = 0x22;
126
151
  export function SerialMultiDeviceWhisperer({ ...props } = {}) {
127
152
  const base = MultiDeviceWhisperer(props);
153
+ const releasePortByDefaultRef = useRef(false);
154
+ const [releasePortByDefaultState, setReleasePortByDefaultState] = useState(false);
155
+ useEffect(() => {
156
+ releasePortByDefaultRef.current = releasePortByDefaultState;
157
+ }, [releasePortByDefaultState]);
128
158
  // --- Message Processing (Identical logic, just copied over) ---
129
159
  const defaultOnReceive = (uuid, data) => {
130
160
  const conn = base.getConnection(uuid);
@@ -140,7 +170,7 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
140
170
  bytes = data;
141
171
  const asText = decoder.decode(bytes);
142
172
  const combined = conn.readBufferLeftover + asText;
143
- const lines = combined.split("\r\n");
173
+ const lines = combined.split("\n");
144
174
  base.updateConnection(uuid, (c) => ({ ...c, readBufferLeftover: lines.pop() || "" }));
145
175
  for (const line of lines) {
146
176
  if (line.trim()) {
@@ -281,7 +311,7 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
281
311
  }
282
312
  base.updateConnection(uuid, c => ({
283
313
  ...c,
284
- transport: null,
314
+ transport: releasePortByDefaultRef.current ? null : c.transport,
285
315
  isConnected: false,
286
316
  isConnecting: false
287
317
  }));
@@ -321,7 +351,17 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
321
351
  await disconnect(uuid);
322
352
  base.removeConnection(uuid);
323
353
  };
324
- const reconnectAll = async () => { };
354
+ const reconnectAll = async (...connectionProps) => {
355
+ const connectionIds = base.connections.map(c => c.uuid);
356
+ await Promise.all(connectionIds.map(async (id) => {
357
+ const c = base.getConnection(id);
358
+ if (!c)
359
+ return;
360
+ await disconnect(c.uuid);
361
+ await new Promise((res) => setTimeout(res, 250));
362
+ return connect(c.uuid, ...connectionProps);
363
+ }));
364
+ };
325
365
  useEffect(() => { base.setIsReady(true); }, []);
326
366
  return {
327
367
  ...base,
@@ -329,6 +369,8 @@ export function SerialMultiDeviceWhisperer({ ...props } = {}) {
329
369
  removeConnection,
330
370
  connect,
331
371
  disconnect,
332
- reconnectAll
372
+ reconnectAll,
373
+ releasePortByDefault: releasePortByDefaultState,
374
+ setReleasePortByDefault: setReleasePortByDefaultState
333
375
  };
334
376
  }
@@ -18,9 +18,8 @@ export declare function WebsocketMultiDeviceWhisperer<AppOrMessageLayer extends
18
18
  connect: (uuid: string, attempt?: number) => Promise<void>;
19
19
  disconnect: (uuid: string) => Promise<void>;
20
20
  checkForNewDevices: () => Promise<DeviceObjectResponse[]>;
21
- reconnectAll: () => Promise<void>;
21
+ reconnectAll: (...connectionProps: any) => Promise<void>;
22
22
  connections: AppOrMessageLayer[];
23
- connectionsRef: import("react").RefObject<AppOrMessageLayer[]>;
24
23
  updateConnection: (uuid: string, updater: (c: AppOrMessageLayer) => AppOrMessageLayer) => void;
25
24
  updateConnectionName: (uuid: string, name: string) => void;
26
25
  getConnection: (uuid: string) => AppOrMessageLayer | undefined;
@@ -19,7 +19,7 @@ export function WebsocketMultiDeviceWhisperer({ server_url, server_port, ...prop
19
19
  }
20
20
  const asText = decoder.decode(bytes);
21
21
  const combined = conn.readBufferLeftover + asText;
22
- const lines = combined.split("\n");
22
+ const lines = combined.split(/\r?\n/);
23
23
  base.updateConnection(uuid, (c) => ({
24
24
  ...c,
25
25
  readBufferLeftover: lines.pop() || ""
@@ -173,14 +173,16 @@ export function WebsocketMultiDeviceWhisperer({ server_url, server_port, ...prop
173
173
  return [];
174
174
  }
175
175
  };
176
- const reconnectAll = async () => {
177
- for (const c of base.connectionsRef.current) {
176
+ const reconnectAll = async (...connectionProps) => {
177
+ const connectionIds = base.connections.map(c => c.uuid);
178
+ await Promise.all(connectionIds.map(async (id) => {
179
+ const c = base.getConnection(id);
180
+ if (!c)
181
+ return;
178
182
  await disconnect(c.uuid);
179
183
  await new Promise((res) => setTimeout(res, 250));
180
- }
181
- for (const c of base.connectionsRef.current) {
182
- await connect(c.uuid);
183
- }
184
+ return connect(c.uuid, ...connectionProps);
185
+ }));
184
186
  };
185
187
  useEffect(() => {
186
188
  base.setIsReady(true); // Ready on page load by default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ota-hub-reactjs",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
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",
@@ -33,7 +33,6 @@
33
33
  "dependencies": {
34
34
  "esptool-js": "^0.5.5",
35
35
  "mqtt": "^5.14.1",
36
- "unique-names-generator": "^4.7.1",
37
36
  "uuid": "^11.1.0"
38
37
  },
39
38
  "peerDependencies": {