fix(theseus): closing a still-connecting relay socket must not crash the main process
Node's ws aborts the handshake when close() is called on a CONNECTING socket and emits an error on the next tick; with no listener that is an uncaught exception, and Electron answers with the modal "A JavaScript error occurred in the main process". nostr-tools drops its onerror handler right before closing, which is what the WizardConnect relay teardown in Aegis runs on every wallet disconnect while a relay is still connecting. Browser WebSockets ignore the same sequence, which is why the library gets away with it elsewhere. Every consumer in the main process shares the one ws module, so close() now adds a no-op error listener to a connecting socket before aborting it. Anything else that still escapes to the top of the process is logged to <userData>/main-errors.log instead of raising the modal; Electron continued after that dialog anyway, so only the interruption goes.
This commit is contained in:
parent
d3787f8a29
commit
729930f0b5
1 changed files with 29 additions and 0 deletions
29
main.js
29
main.js
|
|
@ -23,6 +23,35 @@ const webapps = require("./webapps");
|
|||
// them, so a write that cannot land is not an error worth stopping for.
|
||||
for (const stream of [process.stdout, process.stderr]) stream.on("error", () => {});
|
||||
|
||||
// Node's ws turns "closed while still connecting" into a crash: close() on a
|
||||
// CONNECTING socket aborts the handshake and emits an error on the next tick,
|
||||
// and an error event with no listener is an uncaught exception. Browser
|
||||
// WebSockets shrug the same thing off, so libraries written for both do it —
|
||||
// nostr-tools drops its onerror handler and then closes the socket, which is
|
||||
// what the WizardConnect relay teardown in Aegis runs on every wallet
|
||||
// disconnect. Seen 2026-09-27 as the "JavaScript error occurred in the main
|
||||
// process" dialog. Every consumer in this process (messenger, add-ons,
|
||||
// WizardConnect via isomorphic-ws) shares this one ws module, so guard it here.
|
||||
{
|
||||
const close = WebSocket.prototype.close;
|
||||
WebSocket.prototype.close = function (...args) {
|
||||
if (this.readyState === WebSocket.CONNECTING && this.listenerCount("error") === 0) this.once("error", () => {});
|
||||
return close.apply(this, args);
|
||||
};
|
||||
}
|
||||
// Anything else that escapes to the top of the main process: Electron would
|
||||
// show a modal "JavaScript error occurred in the main process" and carry on.
|
||||
// Carry on without the modal, and keep the report where it can be found.
|
||||
process.on("uncaughtException", (err) => {
|
||||
const line = `[${new Date().toISOString()}] uncaught: ${(err && err.stack) || err}\n`;
|
||||
try { console.error(line); } catch {}
|
||||
try {
|
||||
const f = path.join(app.getPath("userData"), "main-errors.log");
|
||||
try { if (fs.statSync(f).size > 512 * 1024) fs.truncateSync(f, 0); } catch {}
|
||||
fs.appendFileSync(f, line);
|
||||
} catch {}
|
||||
});
|
||||
|
||||
// Packaged builds ship the resolver and tor/ as unpacked resources (they can't
|
||||
// run from inside app.asar); dev runs read them from the repo.
|
||||
const RES_DIR = app.isPackaged ? process.resourcesPath : __dirname;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue