esp32tool 1.6.5 → 1.6.7

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.
package/electron/main.cjs CHANGED
@@ -1,12 +1,19 @@
1
- const { app, BrowserWindow, Menu, session, ipcMain, dialog } = require('electron');
2
- const path = require('path');
3
- const fs = require('fs');
1
+ const {
2
+ app,
3
+ BrowserWindow,
4
+ Menu,
5
+ session,
6
+ ipcMain,
7
+ dialog,
8
+ } = require("electron");
9
+ const path = require("path");
10
+ const fs = require("fs");
4
11
 
5
12
  // Handle creating/removing shortcuts on Windows when installing/uninstalling.
6
13
  // Only required for Windows Squirrel installer
7
- if (process.platform === 'win32') {
14
+ if (process.platform === "win32") {
8
15
  try {
9
- if (require('electron-squirrel-startup')) {
16
+ if (require("electron-squirrel-startup")) {
10
17
  app.quit();
11
18
  }
12
19
  } catch (e) {
@@ -28,22 +35,22 @@ function createWindow() {
28
35
  webPreferences: {
29
36
  nodeIntegration: false,
30
37
  contextIsolation: true,
31
- preload: path.join(__dirname, 'preload.js'),
38
+ preload: path.join(__dirname, "preload.js"),
32
39
  },
33
- icon: path.join(__dirname, 'icons', 'icon.png'),
34
- title: 'ESP32Tool',
40
+ icon: path.join(__dirname, "icons", "icon.png"),
41
+ title: "ESP32Tool",
35
42
  autoHideMenuBar: false,
36
43
  });
37
44
 
38
45
  // Load the index.html of the app
39
- mainWindow.loadFile(path.join(__dirname, '..', 'index.html'));
46
+ mainWindow.loadFile(path.join(__dirname, "..", "index.html"));
40
47
 
41
48
  // Open DevTools in development
42
- if (process.env.NODE_ENV === 'development') {
49
+ if (process.env.NODE_ENV === "development") {
43
50
  mainWindow.webContents.openDevTools();
44
51
  }
45
52
 
46
- mainWindow.on('closed', () => {
53
+ mainWindow.on("closed", () => {
47
54
  mainWindow = null;
48
55
  });
49
56
 
@@ -53,51 +60,56 @@ function createWindow() {
53
60
 
54
61
  function setupSerialPortHandlers(ses) {
55
62
  const isLikelyEspPort = (port) => {
56
- const name = `${port?.displayName || ''} ${port?.portName || ''}`.toLowerCase();
63
+ const name =
64
+ `${port?.displayName || ""} ${port?.portName || ""}`.toLowerCase();
57
65
  return (
58
- name.includes('cp2102') ||
59
- name.includes('cp2103') ||
60
- name.includes('cp2104') ||
61
- name.includes('cp2105') ||
62
- name.includes('cp2108') ||
63
- name.includes('ch9102') ||
64
- name.includes('ch9104') ||
65
- name.includes('ch340') ||
66
- name.includes('ch341') ||
67
- name.includes('ch343') ||
68
- name.includes('ftdi') ||
69
- name.includes('ft232') ||
70
- name.includes('usb') ||
71
- name.includes('uart') ||
72
- name.includes('silicon labs') ||
73
- name.includes('esp32') ||
74
- name.includes('esp8266') ||
75
- name.includes('esp')
66
+ name.includes("cp2102") ||
67
+ name.includes("cp2103") ||
68
+ name.includes("cp2104") ||
69
+ name.includes("cp2105") ||
70
+ name.includes("cp2108") ||
71
+ name.includes("ch9102") ||
72
+ name.includes("ch9104") ||
73
+ name.includes("ch340") ||
74
+ name.includes("ch341") ||
75
+ name.includes("ch343") ||
76
+ name.includes("ftdi") ||
77
+ name.includes("ft232") ||
78
+ name.includes("usb") ||
79
+ name.includes("uart") ||
80
+ name.includes("silicon labs") ||
81
+ name.includes("esp32") ||
82
+ name.includes("esp8266") ||
83
+ name.includes("esp")
76
84
  );
77
85
  };
78
86
 
79
- const getPortLabel = (port) => port?.displayName || port?.portName || port?.portId || 'Unknown port';
87
+ const getPortLabel = (port) =>
88
+ port?.displayName || port?.portName || port?.portId || "Unknown port";
80
89
 
81
90
  const getPortButtonLabel = (port, isRecommended = false) => {
82
- const shortId = port?.portName || port?.displayName || port?.portId || 'Unknown';
91
+ const shortId =
92
+ port?.portName || port?.displayName || port?.portId || "Unknown";
83
93
  return isRecommended ? `${shortId} (Recommended)` : shortId;
84
94
  };
85
95
 
86
96
  const toHexId = (value) =>
87
- typeof value === 'number' ? `0x${value.toString(16).toUpperCase().padStart(4, '0')}` : null;
97
+ typeof value === "number"
98
+ ? `0x${value.toString(16).toUpperCase().padStart(4, "0")}`
99
+ : null;
88
100
 
89
101
  // Guard against Electron re-firing select-serial-port while a dialog is open
90
102
  let activeSelectionId = 0;
91
103
 
92
104
  // Handle serial port selection - shows when navigator.serial.requestPort() is called
93
- ses.on('select-serial-port', (event, portList, webContents, callback) => {
105
+ ses.on("select-serial-port", (event, portList, webContents, callback) => {
94
106
  event.preventDefault();
95
107
 
96
108
  // Filter to only show ESP-compatible ports
97
109
  const espPorts = (portList || []).filter(isLikelyEspPort);
98
110
 
99
111
  if (espPorts.length === 0) {
100
- callback('');
112
+ callback("");
101
113
  return;
102
114
  }
103
115
 
@@ -113,35 +125,38 @@ function setupSerialPortHandlers(ses) {
113
125
  const buttonLabels = espPorts.map((port) => getPortButtonLabel(port));
114
126
  const cancelIndex = buttonLabels.length;
115
127
 
116
- const ownerWindow = BrowserWindow.fromWebContents(webContents) || mainWindow;
117
- dialog.showMessageBox(ownerWindow, {
118
- type: 'question',
119
- title: 'ESP32Tool',
120
- message: 'Select the serial port for your ESP device.',
121
- buttons: [...buttonLabels, 'Cancel'],
122
- defaultId: 0,
123
- cancelId: cancelIndex,
124
- noLink: true,
125
- }).then((result) => {
126
- // Ignore if a newer select-serial-port event superseded this one
127
- if (mySelectionId !== activeSelectionId) return;
128
- const selectedPort = espPorts[result.response];
129
- callback(selectedPort ? selectedPort.portId : '');
130
- });
128
+ const ownerWindow =
129
+ BrowserWindow.fromWebContents(webContents) || mainWindow;
130
+ dialog
131
+ .showMessageBox(ownerWindow, {
132
+ type: "question",
133
+ title: "ESP32Tool",
134
+ message: "Select the serial port for your ESP device.",
135
+ buttons: [...buttonLabels, "Cancel"],
136
+ defaultId: 0,
137
+ cancelId: cancelIndex,
138
+ noLink: true,
139
+ })
140
+ .then((result) => {
141
+ // Ignore if a newer select-serial-port event superseded this one
142
+ if (mySelectionId !== activeSelectionId) return;
143
+ const selectedPort = espPorts[result.response];
144
+ callback(selectedPort ? selectedPort.portId : "");
145
+ });
131
146
  });
132
147
 
133
- ses.on('serial-port-added', (event, port) => {
134
- console.log('Serial port added:', port);
148
+ ses.on("serial-port-added", (event, port) => {
149
+ console.log("Serial port added:", port);
135
150
  });
136
151
 
137
- ses.on('serial-port-removed', (event, port) => {
138
- console.log('Serial port removed:', port);
152
+ ses.on("serial-port-removed", (event, port) => {
153
+ console.log("Serial port removed:", port);
139
154
  });
140
155
 
141
156
  ses.setPermissionCheckHandler(() => true);
142
157
 
143
158
  ses.setDevicePermissionHandler((details) => {
144
- if (details.deviceType === 'serial' && details.device) {
159
+ if (details.deviceType === "serial" && details.device) {
145
160
  grantedDevices.set(details.device.deviceId, details.device);
146
161
  }
147
162
  return true;
@@ -152,73 +167,71 @@ function setupSerialPortHandlers(ses) {
152
167
  function createMenu() {
153
168
  const template = [
154
169
  {
155
- label: 'File',
156
- submenu: [
157
- { role: 'quit' }
158
- ]
170
+ label: "File",
171
+ submenu: [{ role: "quit" }],
159
172
  },
160
173
  {
161
- label: 'Edit',
174
+ label: "Edit",
162
175
  submenu: [
163
- { role: 'undo' },
164
- { role: 'redo' },
165
- { type: 'separator' },
166
- { role: 'cut' },
167
- { role: 'copy' },
168
- { role: 'paste' },
169
- { role: 'selectAll' }
170
- ]
176
+ { role: "undo" },
177
+ { role: "redo" },
178
+ { type: "separator" },
179
+ { role: "cut" },
180
+ { role: "copy" },
181
+ { role: "paste" },
182
+ { role: "selectAll" },
183
+ ],
171
184
  },
172
185
  {
173
- label: 'View',
186
+ label: "View",
174
187
  submenu: [
175
- { role: 'reload' },
176
- { role: 'forceReload' },
177
- { role: 'toggleDevTools' },
178
- { type: 'separator' },
179
- { role: 'resetZoom' },
180
- { role: 'zoomIn' },
181
- { role: 'zoomOut' },
182
- { type: 'separator' },
183
- { role: 'togglefullscreen' }
184
- ]
188
+ { role: "reload" },
189
+ { role: "forceReload" },
190
+ { role: "toggleDevTools" },
191
+ { type: "separator" },
192
+ { role: "resetZoom" },
193
+ { role: "zoomIn" },
194
+ { role: "zoomOut" },
195
+ { type: "separator" },
196
+ { role: "togglefullscreen" },
197
+ ],
185
198
  },
186
199
  {
187
- label: 'Help',
200
+ label: "Help",
188
201
  submenu: [
189
202
  {
190
- label: 'About ESP32Tool',
203
+ label: "About ESP32Tool",
191
204
  click: async () => {
192
- const { shell } = require('electron');
193
- await shell.openExternal('https://github.com/Jason2866/esp32tool');
194
- }
205
+ const { shell } = require("electron");
206
+ await shell.openExternal("https://github.com/Jason2866/esp32tool");
207
+ },
195
208
  },
196
209
  {
197
- label: 'ESP32 Documentation',
210
+ label: "ESP32 Documentation",
198
211
  click: async () => {
199
- const { shell } = require('electron');
200
- await shell.openExternal('https://docs.espressif.com/');
201
- }
202
- }
203
- ]
204
- }
212
+ const { shell } = require("electron");
213
+ await shell.openExternal("https://docs.espressif.com/");
214
+ },
215
+ },
216
+ ],
217
+ },
205
218
  ];
206
219
 
207
220
  // macOS specific menu adjustments
208
- if (process.platform === 'darwin') {
221
+ if (process.platform === "darwin") {
209
222
  template.unshift({
210
223
  label: app.getName(),
211
224
  submenu: [
212
- { role: 'about' },
213
- { type: 'separator' },
214
- { role: 'services' },
215
- { type: 'separator' },
216
- { role: 'hide' },
217
- { role: 'hideOthers' },
218
- { role: 'unhide' },
219
- { type: 'separator' },
220
- { role: 'quit' }
221
- ]
225
+ { role: "about" },
226
+ { type: "separator" },
227
+ { role: "services" },
228
+ { type: "separator" },
229
+ { role: "hide" },
230
+ { role: "hideOthers" },
231
+ { role: "unhide" },
232
+ { type: "separator" },
233
+ { role: "quit" },
234
+ ],
222
235
  });
223
236
  }
224
237
 
@@ -231,7 +244,7 @@ app.whenReady().then(() => {
231
244
  createMenu();
232
245
  createWindow();
233
246
 
234
- app.on('activate', () => {
247
+ app.on("activate", () => {
235
248
  // On macOS re-create a window when dock icon is clicked and no windows are open
236
249
  if (BrowserWindow.getAllWindows().length === 0) {
237
250
  createWindow();
@@ -240,8 +253,8 @@ app.whenReady().then(() => {
240
253
  });
241
254
 
242
255
  // Quit when all windows are closed, except on macOS
243
- app.on('window-all-closed', () => {
244
- if (process.platform !== 'darwin') {
256
+ app.on("window-all-closed", () => {
257
+ if (process.platform !== "darwin") {
245
258
  app.quit();
246
259
  }
247
260
  });
@@ -251,37 +264,40 @@ app.on('window-all-closed', () => {
251
264
  // ============================================
252
265
 
253
266
  // Save file dialog and write data
254
- ipcMain.handle('save-file', async (event, { data, defaultFilename, filters }) => {
255
- const result = await dialog.showSaveDialog(mainWindow, {
256
- defaultPath: defaultFilename,
257
- filters: filters || [
258
- { name: 'Binary Files', extensions: ['bin'] },
259
- { name: 'All Files', extensions: ['*'] }
260
- ]
261
- });
267
+ ipcMain.handle(
268
+ "save-file",
269
+ async (event, { data, defaultFilename, filters }) => {
270
+ const result = await dialog.showSaveDialog(mainWindow, {
271
+ defaultPath: defaultFilename,
272
+ filters: filters || [
273
+ { name: "Binary Files", extensions: ["bin"] },
274
+ { name: "All Files", extensions: ["*"] },
275
+ ],
276
+ });
262
277
 
263
- if (result.canceled || !result.filePath) {
264
- return { success: false, canceled: true };
265
- }
278
+ if (result.canceled || !result.filePath) {
279
+ return { success: false, canceled: true };
280
+ }
266
281
 
267
- try {
268
- // Convert data to Buffer if it's a Uint8Array
269
- const buffer = Buffer.from(data);
270
- fs.writeFileSync(result.filePath, buffer);
271
- return { success: true, filePath: result.filePath };
272
- } catch (error) {
273
- return { success: false, error: error.message };
274
- }
275
- });
282
+ try {
283
+ // Convert data to Buffer if it's a Uint8Array
284
+ const buffer = Buffer.from(data);
285
+ fs.writeFileSync(result.filePath, buffer);
286
+ return { success: true, filePath: result.filePath };
287
+ } catch (error) {
288
+ return { success: false, error: error.message };
289
+ }
290
+ },
291
+ );
276
292
 
277
293
  // Open file dialog and read data
278
- ipcMain.handle('open-file', async (event, { filters }) => {
294
+ ipcMain.handle("open-file", async (event, { filters }) => {
279
295
  const result = await dialog.showOpenDialog(mainWindow, {
280
- properties: ['openFile'],
296
+ properties: ["openFile"],
281
297
  filters: filters || [
282
- { name: 'Binary Files', extensions: ['bin'] },
283
- { name: 'All Files', extensions: ['*'] }
284
- ]
298
+ { name: "Binary Files", extensions: ["bin"] },
299
+ { name: "All Files", extensions: ["*"] },
300
+ ],
285
301
  });
286
302
 
287
303
  if (result.canceled || result.filePaths.length === 0) {
@@ -292,11 +308,11 @@ ipcMain.handle('open-file', async (event, { filters }) => {
292
308
  const filePath = result.filePaths[0];
293
309
  const data = fs.readFileSync(filePath);
294
310
  const filename = path.basename(filePath);
295
- return {
296
- success: true,
297
- filePath,
311
+ return {
312
+ success: true,
313
+ filePath,
298
314
  filename,
299
- data: Array.from(data) // Convert Buffer to array for IPC transfer
315
+ data: Array.from(data), // Convert Buffer to array for IPC transfer
300
316
  };
301
317
  } catch (error) {
302
318
  return { success: false, error: error.message };
@@ -304,30 +320,33 @@ ipcMain.handle('open-file', async (event, { filters }) => {
304
320
  });
305
321
 
306
322
  // Show input dialog (replacement for prompt())
307
- ipcMain.handle('show-prompt', async (event, { message, defaultValue }) => {
323
+ ipcMain.handle("show-prompt", async (event, { message, defaultValue }) => {
308
324
  // Use a simple approach - return the default value
309
325
  // In Electron, we use the save dialog instead of prompt
310
326
  return defaultValue;
311
327
  });
312
328
 
313
329
  // Show message box
314
- ipcMain.handle('show-message', async (event, { type, title, message, buttons }) => {
315
- const result = await dialog.showMessageBox(mainWindow, {
316
- type: type || 'info',
317
- title: title || 'ESP32Tool',
318
- message: message,
319
- buttons: buttons || ['OK']
320
- });
321
- return result.response;
322
- });
330
+ ipcMain.handle(
331
+ "show-message",
332
+ async (event, { type, title, message, buttons }) => {
333
+ const result = await dialog.showMessageBox(mainWindow, {
334
+ type: type || "info",
335
+ title: title || "ESP32Tool",
336
+ message: message,
337
+ buttons: buttons || ["OK"],
338
+ });
339
+ return result.response;
340
+ },
341
+ );
323
342
 
324
343
  // Show confirm dialog
325
- ipcMain.handle('show-confirm', async (event, { message }) => {
344
+ ipcMain.handle("show-confirm", async (event, { message }) => {
326
345
  const result = await dialog.showMessageBox(mainWindow, {
327
- type: 'question',
328
- title: 'Confirm',
346
+ type: "question",
347
+ title: "Confirm",
329
348
  message: message,
330
- buttons: ['OK', 'Cancel']
349
+ buttons: ["OK", "Cancel"],
331
350
  });
332
351
  return result.response === 0; // true if OK clicked
333
352
  });
@@ -1,10 +1,10 @@
1
1
  // Preload script for Electron
2
2
  // This runs in a sandboxed environment with access to some Node.js APIs
3
3
 
4
- const { contextBridge, ipcRenderer } = require('electron');
4
+ const { contextBridge, ipcRenderer } = require("electron");
5
5
 
6
6
  // Expose platform info and file APIs to renderer
7
- contextBridge.exposeInMainWorld('electronAPI', {
7
+ contextBridge.exposeInMainWorld("electronAPI", {
8
8
  platform: process.platform,
9
9
  isElectron: true,
10
10
  versions: {
@@ -12,26 +12,24 @@ contextBridge.exposeInMainWorld('electronAPI', {
12
12
  chrome: process.versions.chrome,
13
13
  electron: process.versions.electron,
14
14
  },
15
-
15
+
16
16
  // File operations
17
- saveFile: (data, defaultFilename, filters) =>
18
- ipcRenderer.invoke('save-file', { data, defaultFilename, filters }),
19
-
20
- openFile: (filters) =>
21
- ipcRenderer.invoke('open-file', { filters }),
22
-
17
+ saveFile: (data, defaultFilename, filters) =>
18
+ ipcRenderer.invoke("save-file", { data, defaultFilename, filters }),
19
+
20
+ openFile: (filters) => ipcRenderer.invoke("open-file", { filters }),
21
+
23
22
  // Dialog operations
24
23
  showPrompt: (message, defaultValue) =>
25
- ipcRenderer.invoke('show-prompt', { message, defaultValue }),
26
-
24
+ ipcRenderer.invoke("show-prompt", { message, defaultValue }),
25
+
27
26
  showMessage: (type, title, message, buttons) =>
28
- ipcRenderer.invoke('show-message', { type, title, message, buttons }),
29
-
30
- showConfirm: (message) =>
31
- ipcRenderer.invoke('show-confirm', { message })
27
+ ipcRenderer.invoke("show-message", { type, title, message, buttons }),
28
+
29
+ showConfirm: (message) => ipcRenderer.invoke("show-confirm", { message }),
32
30
  });
33
31
 
34
32
  // Log when preload script runs
35
- console.log('Electron preload script loaded');
36
- console.log('Platform:', process.platform);
37
- console.log('Electron version:', process.versions.electron);
33
+ console.log("Electron preload script loaded");
34
+ console.log("Platform:", process.platform);
35
+ console.log("Electron version:", process.versions.electron);
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/icons/icon-72.png CHANGED
Binary file
package/icons/icon-96.png CHANGED
Binary file
package/js/console.js CHANGED
@@ -133,7 +133,7 @@ export class ESP32ToolConsole {
133
133
  `;
134
134
 
135
135
  this.console = new ColoredConsole(
136
- this.containerElement.querySelector(".log")
136
+ this.containerElement.querySelector(".log"),
137
137
  );
138
138
 
139
139
  // Setup event listeners
@@ -152,18 +152,22 @@ export class ESP32ToolConsole {
152
152
  closeBtn.addEventListener("click", () => {
153
153
  // Dispatch close event to parent
154
154
  this.containerElement.dispatchEvent(
155
- new CustomEvent("console-close", { bubbles: true })
155
+ new CustomEvent("console-close", { bubbles: true }),
156
156
  );
157
157
  });
158
158
  }
159
159
 
160
- const improvBtn = this.containerElement.querySelector("#console-improv-btn");
160
+ const improvBtn = this.containerElement.querySelector(
161
+ "#console-improv-btn",
162
+ );
161
163
  if (improvBtn) {
162
164
  improvBtn.addEventListener("click", () => this._openImprov());
163
165
  }
164
166
 
165
167
  if (this.allowInput) {
166
- const input = this.containerElement.querySelector(".esp32tool-console-input");
168
+ const input = this.containerElement.querySelector(
169
+ ".esp32tool-console-input",
170
+ );
167
171
 
168
172
  this.containerElement.addEventListener("click", () => {
169
173
  // Only focus input if user didn't select some text
@@ -217,10 +221,10 @@ export class ESP32ToolConsole {
217
221
  this.console.addLine("");
218
222
  this.console.addLine("");
219
223
  this.console.addLine(
220
- `Terminal disconnected: Port readable stream not available`
224
+ `Terminal disconnected: Port readable stream not available`,
221
225
  );
222
226
  console.error(
223
- "Port readable stream not available - port may need to be reopened at correct baudrate"
227
+ "Port readable stream not available - port may need to be reopened at correct baudrate",
224
228
  );
225
229
  return;
226
230
  }
@@ -248,14 +252,14 @@ export class ESP32ToolConsole {
248
252
  if (pat.test(chunk)) {
249
253
  bootloaderDetected = true;
250
254
  this.containerElement.dispatchEvent(
251
- new CustomEvent("console-bootloader", { bubbles: true })
255
+ new CustomEvent("console-bootloader", { bubbles: true }),
252
256
  );
253
257
  break;
254
258
  }
255
259
  }
256
260
  }
257
261
  },
258
- })
262
+ }),
259
263
  );
260
264
  if (!abortSignal.aborted) {
261
265
  this.console.addLine("");
@@ -264,7 +268,10 @@ export class ESP32ToolConsole {
264
268
  }
265
269
  } catch (e) {
266
270
  // Only log disconnect errors if the abort was NOT intentional
267
- if (!abortSignal.aborted && !(e instanceof DOMException && e.name === 'AbortError')) {
271
+ if (
272
+ !abortSignal.aborted &&
273
+ !(e instanceof DOMException && e.name === "AbortError")
274
+ ) {
268
275
  this.console.addLine("");
269
276
  this.console.addLine("");
270
277
  this.console.addLine(`Terminal disconnected: ${e}`);
@@ -297,7 +304,9 @@ export class ESP32ToolConsole {
297
304
  }
298
305
 
299
306
  async _sendCommand() {
300
- const input = this.containerElement.querySelector(".esp32tool-console-input");
307
+ const input = this.containerElement.querySelector(
308
+ ".esp32tool-console-input",
309
+ );
301
310
  const command = input.value;
302
311
 
303
312
  if (command.trim() !== "") {
@@ -347,8 +356,8 @@ export class ESP32ToolConsole {
347
356
 
348
357
  // Only check the first 30 lines (same as _connect) to avoid false positives
349
358
  // from bootloader patterns appearing later in normal firmware output
350
- const lines = text.split('\n');
351
- const firstLines = lines.slice(0, 30).join('\n');
359
+ const lines = text.split("\n");
360
+ const firstLines = lines.slice(0, 30).join("\n");
352
361
 
353
362
  for (const pat of BOOTLOADER_PATTERNS) {
354
363
  if (pat.test(firstLines)) {