ota-hub-reactjs 0.1.2 → 0.1.3

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.
@@ -6,7 +6,6 @@ export type ESP32ConnectionState = DeviceConnectionState & {
6
6
  transport?: Transport;
7
7
  esp?: ESPLoader;
8
8
  debugLogging?: false;
9
- reader?: AsyncGenerator<Uint8Array>;
10
9
  slipReadWrite?: boolean;
11
10
  isFlashing: boolean;
12
11
  flashProgress: number;
@@ -36,7 +36,7 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
36
36
  let inSlipFrame = false; // are we inside a SLIP frame?
37
37
  let escapeNext = false;
38
38
  const reader = transport.rawRead();
39
- base.updateConnection(uuid, (c) => ({ ...c, reader }));
39
+ base.updateConnection(uuid, (c) => ({ ...c }));
40
40
  try {
41
41
  while (true) {
42
42
  const conn = base.getConnection(uuid);
@@ -44,6 +44,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
44
44
  console.log("Kack!");
45
45
  return;
46
46
  }
47
+ if (conn.isFlashing || !conn.isConnected)
48
+ break;
47
49
  const { value, done } = await reader.next();
48
50
  if (done || !value)
49
51
  break;
@@ -114,6 +116,7 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
114
116
  }
115
117
  }
116
118
  }
119
+ await new Promise((resolve) => setTimeout(resolve, 100)); // Little breather before attempt any disconnects
117
120
  }
118
121
  catch (e) {
119
122
  base.appendLog(uuid, {
@@ -122,13 +125,11 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
122
125
  });
123
126
  }
124
127
  finally {
125
- // Clear reader state so it's fresh for next connect
126
- base.updateConnection(uuid, (c) => ({ ...c, reader: undefined }));
127
- base.appendLog(uuid, {
128
- level: 0,
129
- message: "[!] Serial disconnected",
130
- });
131
- await disconnect(uuid);
128
+ base.updateConnection(uuid, (c) => ({ ...c }));
129
+ const conn = base.getConnection(uuid);
130
+ if (conn?.isConnected) {
131
+ await disconnect(uuid);
132
+ }
132
133
  }
133
134
  };
134
135
  const restartDevice = async (uuid, default_transport) => {
@@ -177,7 +178,7 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
177
178
  const esploader = new ESPLoader({
178
179
  transport,
179
180
  baudrate: use_baudrate,
180
- enableTracing: false,
181
+ enableTracing: !!conn.debugLogging,
181
182
  debugLogging: !!conn.debugLogging,
182
183
  });
183
184
  try {
@@ -230,29 +231,9 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
230
231
  };
231
232
  const disconnect = async (uuid, timeout = 2000) => {
232
233
  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
- }
243
234
  if (conn?.transport) {
244
235
  try {
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
236
+ // Attempt disconnect, but don’t hang if the port is crashed
256
237
  await Promise.race([
257
238
  conn.transport.disconnect(),
258
239
  new Promise((resolve) => setTimeout(resolve, timeout)),
@@ -262,32 +243,20 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
262
243
  console.warn(`[${uuid}] Serial Disconnect error:`, e);
263
244
  }
264
245
  }
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
246
+ // Always clear the transport and reset connection state
282
247
  base.updateConnection(uuid, (c) => ({
283
248
  ...c,
284
- port: releasePortByDefault ? undefined : c.port,
285
- transport: releasePortByDefault ? undefined : c.transport,
286
- reader: undefined, // Guarantee reader is cleared out
249
+ port: releasePortByDefault ? null : c.port,
250
+ transport: releasePortByDefault ? null : c.transport,
251
+ esp: releasePortByDefault ? null : c.transport,
287
252
  isConnected: false,
288
253
  isConnecting: false,
289
254
  autoConnect: false,
290
255
  }));
256
+ base.appendLog(uuid, {
257
+ level: 0,
258
+ message: "[!] Serial disconnected",
259
+ });
291
260
  await conn?.onDisconnect?.();
292
261
  };
293
262
  const addConnection = async ({ uuid, propCreator, }) => {
@@ -362,27 +331,26 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
362
331
  // 1. Bail if we don't have the required state
363
332
  if (!conn || !conn.port || assetsToFlash.length === 0)
364
333
  return;
365
- // Gracefully cancel reader and completely drop the transport natively
366
- await disconnect(uuid);
367
- await new Promise((resolve) => setTimeout(resolve, 400));
334
+ const prevAutoConnect = conn.autoConnect;
368
335
  base.updateConnection(uuid, (c) => ({
369
336
  ...c,
370
337
  port: conn.port, // Restore the port into state in case disconnect wiped it
371
338
  isFlashing: true,
372
339
  flashProgress: 0,
373
340
  flashError: undefined,
341
+ autoConnect: false,
374
342
  }));
375
- // Declare outside the try block (Fixes variable shadowing)
376
- let transport;
377
- let esploader;
343
+ if (conn.isConnected)
344
+ await disconnect(uuid);
345
+ await new Promise((resolve) => setTimeout(resolve, 400));
378
346
  try {
379
347
  // --- Connect ONCE ---
380
- transport = new Transport(conn.port, true);
381
- esploader = new ESPLoader({
348
+ const transport = new Transport(conn.port, true);
349
+ const esploader = new ESPLoader({
382
350
  transport,
383
351
  baudrate: 921600,
384
- enableTracing: false,
385
- // debugLogging: !!conn.debugLogging,
352
+ enableTracing: !!conn.debugLogging,
353
+ debugLogging: !!conn.debugLogging,
386
354
  });
387
355
  // This may fail, that'll bubble the failure up to the upper try/catch
388
356
  await esploader.main();
@@ -436,9 +404,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
436
404
  ...c,
437
405
  isFlashing: false,
438
406
  flashProgress: 100,
407
+ autoConnect: prevAutoConnect,
439
408
  }));
440
- // --- Reconnect the standard monitor ---
441
- await connect(uuid);
442
409
  }
443
410
  catch (e) {
444
411
  console.error(`[${uuid}] Flashing failed:`, e);
@@ -448,6 +415,8 @@ export function useESP32MultiDeviceWhisperer({ releasePortByDefault, ...props }
448
415
  flashError: e?.message ?? "Unknown error",
449
416
  }));
450
417
  // Attempt recovery
418
+ }
419
+ finally {
451
420
  await connect(uuid);
452
421
  }
453
422
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ota-hub-reactjs",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",