Skip to content

Commit 0cd1638

Browse files
committed
Improve events
1 parent 8c10a46 commit 0cd1638

File tree

12 files changed

+1074
-137
lines changed

12 files changed

+1074
-137
lines changed

src/js/logger.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Small test-friendly logger wrapper. Tests can mock/spy on these methods
2+
// if they want to suppress or capture output.
3+
const logger = {
4+
info: (...args) => console.log(...args),
5+
warn: (...args) => console.warn(...args),
6+
error: (...args) => console.error(...args),
7+
};
8+
9+
export default logger;

src/js/main.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,6 @@ function startProcess() {
188188
if (GUI.connected_to || GUI.connecting_to) {
189189
$("a.connection_button__link").click();
190190
}
191-
// this line is required but it triggers opening the firmware flasher tab again
192-
$("a.firmware_flasher_button__link").click();
193191
} else if (GUI.allowedTabs.indexOf(tab) < 0) {
194192
gui_log(i18n.getMessage("tabSwitchUpgradeRequired", [tabName]));
195193
return;

src/js/msp/MSPHelper.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,13 +1779,17 @@ MspHelper.prototype.process_data = function (dataHandler) {
17791779

17801780
// remove object from array
17811781
dataHandler.callbacks.splice(i, 1);
1782-
if (!crcError) {
1783-
// fire callback
1784-
if (callback) {
1785-
callback({ command: code, data: data, length: data.byteLength, crcError: crcError });
1782+
// Always invoke callback and pass crcError flag. Callbacks expect to
1783+
// receive the original DataView so they can choose how to handle CRC
1784+
// errors; don't replace the data with null here to avoid breaking
1785+
// existing consumers that dereference response.data before checking
1786+
// crcError.
1787+
if (callback) {
1788+
try {
1789+
callback({ command: code, data: data, length: data ? data.byteLength : 0, crcError: crcError });
1790+
} catch (e) {
1791+
console.error(`callback for code ${code} threw:`, e);
17861792
}
1787-
} else {
1788-
console.warn(`code: ${code} - crc failed. No callback`);
17891793
}
17901794
}
17911795
}

src/js/protocols/WebSerial.js

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { webSerialDevices, vendorIdNames } from "./devices";
22
import GUI from "../gui";
3+
import logger from "../logger";
34

45
const logHead = "[WEBSERIAL]";
56

@@ -57,7 +58,7 @@ class WebSerial extends EventTarget {
5758
this.reading = false;
5859

5960
if (!navigator?.serial) {
60-
console.error(`${logHead} Web Serial API not supported`);
61+
logger.error(`${logHead} Web Serial API not supported`);
6162
return;
6263
}
6364

@@ -92,7 +93,7 @@ class WebSerial extends EventTarget {
9293
}
9394

9495
handleDisconnect() {
95-
console.log(`${logHead} Device disconnected externally`);
96+
logger.info(`${logHead} Device disconnected externally`);
9697
this.disconnect();
9798
}
9899

@@ -119,7 +120,7 @@ class WebSerial extends EventTarget {
119120
const ports = await navigator.serial.getPorts();
120121
this.ports = ports.map((port) => this.createPort(port));
121122
} catch (error) {
122-
console.error(`${logHead} Error loading devices:`, error);
123+
logger.error(`${logHead} Error loading devices:`, error);
123124
}
124125
}
125126

@@ -135,9 +136,9 @@ class WebSerial extends EventTarget {
135136
if (!newPermissionPort) {
136137
newPermissionPort = this.handleNewDevice(userSelectedPort);
137138
}
138-
console.info(`${logHead} User selected SERIAL device from permissions:`, newPermissionPort.path);
139+
logger.info(`${logHead} User selected SERIAL device from permissions:`, newPermissionPort.path);
139140
} catch (error) {
140-
console.error(`${logHead} User didn't select any SERIAL device when requesting permission:`, error);
141+
logger.error(`${logHead} User didn't select any SERIAL device when requesting permission:`, error);
141142
}
142143
return newPermissionPort;
143144
}
@@ -150,7 +151,7 @@ class WebSerial extends EventTarget {
150151
async connect(path, options = { baudRate: 115200 }) {
151152
// Prevent double connections
152153
if (this.connected) {
153-
console.log(`${logHead} Already connected, not connecting again`);
154+
logger.info(`${logHead} Already connected, not connecting again`);
154155
return true;
155156
}
156157

@@ -160,7 +161,7 @@ class WebSerial extends EventTarget {
160161
try {
161162
const device = this.ports.find((device) => device.path === path);
162163
if (!device) {
163-
console.error(`${logHead} Device not found:`, path);
164+
logger.error(`${logHead} Device not found:`, path);
164165
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
165166
return false;
166167
}
@@ -173,7 +174,7 @@ class WebSerial extends EventTarget {
173174
this.connectionInfo = connectionInfo;
174175
this.isNeedBatchWrite = this.checkIsNeedBatchWrite();
175176
if (this.isNeedBatchWrite) {
176-
console.log(`${logHead} Enabling batch write mode for AT32 on macOS`);
177+
logger.info(`${logHead} Enabling batch write mode for AT32 on macOS`);
177178
}
178179
this.writer = this.port.writable.getWriter();
179180
this.reader = this.port.readable.getReader();
@@ -190,7 +191,7 @@ class WebSerial extends EventTarget {
190191
this.port.addEventListener("disconnect", this.handleDisconnect);
191192
this.addEventListener("receive", this.handleReceiveBytes);
192193

193-
console.log(`${logHead} Connection opened with ID: ${this.connectionId}, Baud: ${options.baudRate}`);
194+
logger.info(`${logHead} Connection opened with ID: ${this.connectionId}, Baud: ${options.baudRate}`);
194195

195196
this.dispatchEvent(new CustomEvent("connect", { detail: connectionInfo }));
196197

@@ -202,7 +203,7 @@ class WebSerial extends EventTarget {
202203
} else if (connectionInfo && this.openCanceled) {
203204
this.connectionId = path;
204205

205-
console.log(`${logHead} Connection opened with ID: ${path}, but request was canceled, disconnecting`);
206+
logger.info(`${logHead} Connection opened with ID: ${path}, but request was canceled, disconnecting`);
206207
// some bluetooth dongles/dongle drivers really doesn't like to be closed instantly, adding a small delay
207208
setTimeout(() => {
208209
this.openRequested = false;
@@ -214,12 +215,102 @@ class WebSerial extends EventTarget {
214215
return false;
215216
} else {
216217
this.openRequested = false;
217-
console.log(`${logHead} Failed to open serial port`);
218+
logger.warn(`${logHead} Failed to open serial port`);
218219
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
219220
return false;
220221
}
221222
} catch (error) {
222-
console.error(`${logHead} Error connecting:`, error);
223+
logger.error(`${logHead} Error connecting:`, error);
224+
225+
// If the port is already open (InvalidStateError) we can attempt to
226+
// recover by attaching to the already-open port. Some browser
227+
// implementations throw when open() is called concurrently but the
228+
// underlying port is already usable. Try to initialize reader/writer
229+
// and proceed as a successful connection.
230+
if (
231+
error &&
232+
(error.name === "InvalidStateError" ||
233+
(typeof error.message === "string" && error.message.includes("already open")))
234+
) {
235+
// NOTE: Some browser implementations throw InvalidStateError when
236+
// open() is called concurrently while the underlying port is
237+
// already open. In those cases, we try to attach to the already-
238+
// open port by obtaining the reader/writer objects. If we can
239+
// obtain both reader and writer we treat the port as successfully
240+
// recovered; otherwise we perform a graceful failure path below.
241+
try {
242+
const connectionInfo = this.port?.getInfo ? this.port.getInfo() : null;
243+
this.connectionInfo = connectionInfo;
244+
this.isNeedBatchWrite = connectionInfo ? this.checkIsNeedBatchWrite() : false;
245+
// Attempt to obtain writer/reader if available
246+
try {
247+
if (this.port?.writable && !this.writer) {
248+
this.writer = this.port.writable.getWriter();
249+
}
250+
} catch (e) {
251+
logger.warn(`${logHead} Could not get writer from already-open port:`, e);
252+
}
253+
254+
try {
255+
if (this.port?.readable && !this.reader) {
256+
this.reader = this.port.readable.getReader();
257+
}
258+
} catch (e) {
259+
logger.warn(`${logHead} Could not get reader from already-open port:`, e);
260+
}
261+
262+
if (connectionInfo) {
263+
if (!this.writer || !this.reader) {
264+
logger.warn(`${logHead} Recovered port missing reader/writer`);
265+
// Release partial writer/reader locks before aborting recovery
266+
if (this.writer) {
267+
try {
268+
this.writer.releaseLock();
269+
} catch (releaseErr) {
270+
logger.warn(`${logHead} Failed to release partial writer lock:`, releaseErr);
271+
}
272+
this.writer = null;
273+
}
274+
if (this.reader) {
275+
try {
276+
this.reader.releaseLock?.();
277+
} catch (releaseErr) {
278+
logger.warn(`${logHead} Failed to release partial reader lock:`, releaseErr);
279+
}
280+
this.reader = null;
281+
}
282+
// Reset request state and signal failure to callers instead of throwing
283+
this.openRequested = false;
284+
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
285+
return false;
286+
}
287+
this.connected = true;
288+
this.connectionId = path;
289+
this.bitrate = options.baudRate;
290+
this.bytesReceived = 0;
291+
this.bytesSent = 0;
292+
this.failed = 0;
293+
this.openRequested = false;
294+
295+
this.port.addEventListener("disconnect", this.handleDisconnect);
296+
this.addEventListener("receive", this.handleReceiveBytes);
297+
298+
logger.info(`${logHead} Recovered already-open serial port, ID: ${this.connectionId}`);
299+
this.dispatchEvent(new CustomEvent("connect", { detail: connectionInfo }));
300+
301+
// Start read loop if not already running
302+
if (!this.reading) {
303+
this.reading = true;
304+
this.readLoop();
305+
}
306+
307+
return true;
308+
}
309+
} catch (e) {
310+
logger.warn(`${logHead} Failed to recover already-open port:`, e);
311+
}
312+
}
313+
223314
this.openRequested = false;
224315
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
225316
return false;
@@ -232,7 +323,7 @@ class WebSerial extends EventTarget {
232323
this.dispatchEvent(new CustomEvent("receive", { detail: value }));
233324
}
234325
} catch (error) {
235-
console.error(`${logHead} Error reading:`, error);
326+
logger.error(`${logHead} Error reading:`, error);
236327
if (this.connected) {
237328
this.disconnect();
238329
}
@@ -272,7 +363,7 @@ class WebSerial extends EventTarget {
272363
try {
273364
await this.reader.cancel();
274365
} catch (e) {
275-
console.warn(`${logHead} Reader cancel error (can be ignored):`, e);
366+
logger.warn(`${logHead} Reader cancel error (can be ignored):`, e);
276367
}
277368
}
278369

@@ -284,7 +375,7 @@ class WebSerial extends EventTarget {
284375
try {
285376
this.writer.releaseLock();
286377
} catch (e) {
287-
console.warn(`${logHead} Writer release error (can be ignored):`, e);
378+
logger.warn(`${logHead} Writer release error (can be ignored):`, e);
288379
}
289380
this.writer = null;
290381
}
@@ -295,12 +386,12 @@ class WebSerial extends EventTarget {
295386
try {
296387
await this.port.close();
297388
} catch (e) {
298-
console.warn(`${logHead} Port already closed or error during close:`, e);
389+
logger.warn(`${logHead} Port already closed or error during close:`, e);
299390
}
300391
this.port = null;
301392
}
302393

303-
console.log(
394+
logger.info(
304395
`${logHead} Connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`,
305396
);
306397

@@ -312,7 +403,7 @@ class WebSerial extends EventTarget {
312403
this.dispatchEvent(new CustomEvent("disconnect", { detail: true }));
313404
return true;
314405
} catch (error) {
315-
console.error(`${logHead} Error disconnecting:`, error);
406+
logger.error(`${logHead} Error disconnecting:`, error);
316407
this.closeRequested = false;
317408
// Ensure connectionInfo is reset even on error if port was potentially open
318409
this.connectionInfo = null;
@@ -341,7 +432,7 @@ class WebSerial extends EventTarget {
341432
try {
342433
await this.writer.write(sliceData);
343434
} catch (error) {
344-
console.error(`${logHead} Error writing batch chunk:`, error);
435+
logger.error(`${logHead} Error writing batch chunk:`, error);
345436
throw error; // Re-throw to be caught by the send method
346437
}
347438
}
@@ -350,7 +441,7 @@ class WebSerial extends EventTarget {
350441

351442
async send(data, callback) {
352443
if (!this.connected || !this.writer) {
353-
console.error(`${logHead} Failed to send data, serial port not open`);
444+
logger.error(`${logHead} Failed to send data, serial port not open`);
354445
if (callback) {
355446
callback({ bytesSent: 0 });
356447
}
@@ -371,7 +462,7 @@ class WebSerial extends EventTarget {
371462
}
372463
return result;
373464
} catch (error) {
374-
console.error(`${logHead} Error sending data:`, error);
465+
logger.error(`${logHead} Error sending data:`, error);
375466
if (callback) {
376467
callback({ bytesSent: 0 });
377468
}

0 commit comments

Comments
 (0)