-
Notifications
You must be signed in to change notification settings - Fork 10
feat: add browser extension integration to main branch #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,13 +2,14 @@ | |||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * Soterios Native Messaging Host | ||||||||||||||||||||||||||||||||||||||||||
| * Bridges browser extension <-> desktop Electron app via stdin/stdout JSON messages | ||||||||||||||||||||||||||||||||||||||||||
| * First attempts to connect via named pipe (if app is running), falls back to launching app | ||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const { spawn } = require('child_process'); | ||||||||||||||||||||||||||||||||||||||||||
| const readline = require('readline'); | ||||||||||||||||||||||||||||||||||||||||||
| const fs = require('fs'); | ||||||||||||||||||||||||||||||||||||||||||
| const path = require('path'); | ||||||||||||||||||||||||||||||||||||||||||
| const net = require('net'); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const DESKTOP_APP = process.env.SOTERIOS_APP_PATH || 'soterios://'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function log(...args) { | ||||||||||||||||||||||||||||||||||||||||||
| console.error('[Soterios Native Host]', new Date().toISOString(), ...args); | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,126 +24,67 @@ | |||||||||||||||||||||||||||||||||||||||||
| process.stdout.write(buf); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Persistent stream parser to avoid listener accumulation | ||||||||||||||||||||||||||||||||||||||||||
| let messageBuffer = Buffer.alloc(0); | ||||||||||||||||||||||||||||||||||||||||||
| let messageResolver = null; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function readMessage() { | ||||||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver = { resolve, reject }; | ||||||||||||||||||||||||||||||||||||||||||
| tryParseBuffer(); | ||||||||||||||||||||||||||||||||||||||||||
| function readMessages() { | ||||||||||||||||||||||||||||||||||||||||||
| const rl = readline.createInterface({ | ||||||||||||||||||||||||||||||||||||||||||
| input: process.stdin, | ||||||||||||||||||||||||||||||||||||||||||
| terminal: false | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function tryParseBuffer() { | ||||||||||||||||||||||||||||||||||||||||||
| if (!messageResolver) return; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| while (messageBuffer.length >= 4) { | ||||||||||||||||||||||||||||||||||||||||||
| const len = messageBuffer.readUInt32LE(0); | ||||||||||||||||||||||||||||||||||||||||||
| if (messageBuffer.length < 4 + len) break; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const msgBuf = messageBuffer.subarray(4, 4 + len); | ||||||||||||||||||||||||||||||||||||||||||
| messageBuffer = messageBuffer.subarray(4 + len); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const msg = JSON.parse(msgBuf.toString('utf8')); | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver.resolve(msg); | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver = null; | ||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver.reject(new Error(`Failed to parse message: ${e.message}`)); | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver = null; | ||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Set up persistent stdin listener once | ||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('data', (chunk) => { | ||||||||||||||||||||||||||||||||||||||||||
| messageBuffer = Buffer.concat([messageBuffer, chunk]); | ||||||||||||||||||||||||||||||||||||||||||
| tryParseBuffer(); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| let buffer = Buffer.alloc(0); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('error', (err) => { | ||||||||||||||||||||||||||||||||||||||||||
| if (messageResolver) { | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver.reject(err); | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver = null; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('data', chunk => { | ||||||||||||||||||||||||||||||||||||||||||
| buffer = Buffer.concat([buffer, chunk]); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| process.stdin.on('end', () => { | ||||||||||||||||||||||||||||||||||||||||||
| if (messageResolver) { | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver.reject(new Error('Stream ended')); | ||||||||||||||||||||||||||||||||||||||||||
| messageResolver = null; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| while (buffer.length >= 4) { | ||||||||||||||||||||||||||||||||||||||||||
| const len = buffer.readUInt32LE(0); | ||||||||||||||||||||||||||||||||||||||||||
| if (buffer.length < 4 + len) break; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| let desktopClient = null; | ||||||||||||||||||||||||||||||||||||||||||
| let desktopProc = null; | ||||||||||||||||||||||||||||||||||||||||||
| const json = buffer.subarray(4, 4 + len).toString(); | ||||||||||||||||||||||||||||||||||||||||||
| buffer = buffer.subarray(4 + len); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async function connectToDesktopApp() { | ||||||||||||||||||||||||||||||||||||||||||
| const pipeName = process.platform === 'win32' ? '\\\\.\\pipe\\soterios-credential-safety' : '/tmp/soterios-credential-safety.sock'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||
| const client = net.createConnection(pipeName, () => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Connected to desktop app via named pipe'); | ||||||||||||||||||||||||||||||||||||||||||
| resolve(client); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| client.on('error', (err) => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Named pipe connection failed:', err.message); | ||||||||||||||||||||||||||||||||||||||||||
| reject(err); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const msg = JSON.parse(json); | ||||||||||||||||||||||||||||||||||||||||||
| handleMessage(msg); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||
| log('Parse error:', e.message); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win Handle rejections from
Catch the rejection and report it over the protocol. 🛡️ Proposed fix to report failures instead of exiting try {
const msg = JSON.parse(json);
- handleMessage(msg);
+ Promise.resolve(handleMessage(msg)).catch(e => {
+ log('Handler error:', e.message);
+ send({ type: 'ERROR', error: e.message, original: msg });
+ });
} catch (e) {
log('Parse error:', e.message);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| let desktopProc = null; | ||||||||||||||||||||||||||||||||||||||||||
| const pending = new Map(); | ||||||||||||||||||||||||||||||||||||||||||
| let msgId = 0; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| function launchDesktopApp() { | ||||||||||||||||||||||||||||||||||||||||||
| if (desktopProc) return Promise.resolve(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return new Promise((resolve, reject) => { | ||||||||||||||||||||||||||||||||||||||||||
| const appPath = process.env.DESKTOP_APP || 'soterios://'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Check if it's a protocol URL or an executable path | ||||||||||||||||||||||||||||||||||||||||||
| const isProtocolUrl = appPath.startsWith('soterios://') || appPath.startsWith('http://') || appPath.startsWith('https://'); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (isProtocolUrl) { | ||||||||||||||||||||||||||||||||||||||||||
| // Launch using OS-appropriate protocol handler | ||||||||||||||||||||||||||||||||||||||||||
| const isWin = process.platform === 'win32'; | ||||||||||||||||||||||||||||||||||||||||||
| const args = isWin ? ['/c', 'start', '', appPath] : ['open', appPath]; | ||||||||||||||||||||||||||||||||||||||||||
| const cmd = isWin ? 'cmd' : (process.platform === 'darwin' ? 'open' : 'xdg-open'); | ||||||||||||||||||||||||||||||||||||||||||
| const options = { shell: false, detached: true }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| desktopProc = spawn(cmd, args, options); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc.unref(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| desktopProc.on('error', e => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Desktop app launch error:', e.message); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc = null; | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| setTimeout(resolve, 1500); | ||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||
| // Launch as executable path | ||||||||||||||||||||||||||||||||||||||||||
| const resolvedPath = path.resolve(appPath); | ||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(resolvedPath)) { | ||||||||||||||||||||||||||||||||||||||||||
| return reject(new Error('Desktop app not found at: ' + resolvedPath)); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| const appPath = process.env.DESKTOP_APP; | ||||||||||||||||||||||||||||||||||||||||||
| if (!appPath) { | ||||||||||||||||||||||||||||||||||||||||||
| return reject(new Error('DESKTOP_APP environment variable not set')); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
59
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Line 12 defines The result is that A native-messaging host inherits only the browser process environment, so 🐛 Proposed fix to use the resolved constant return new Promise((resolve, reject) => {
- const appPath = process.env.DESKTOP_APP;
+ const appPath = DESKTOP_APP;
if (!appPath) {
- return reject(new Error('DESKTOP_APP environment variable not set'));
+ return reject(new Error('SOTERIOS_APP_PATH environment variable not set'));
}
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Resolve and validate path - prevent command injection | ||||||||||||||||||||||||||||||||||||||||||
| const resolvedPath = path.resolve(appPath); | ||||||||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(resolvedPath)) { | ||||||||||||||||||||||||||||||||||||||||||
| return reject(new Error('Desktop app not found at: ' + resolvedPath)); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const isWin = process.platform === 'win32'; | ||||||||||||||||||||||||||||||||||||||||||
| const args = isWin ? ['/c', 'start', '""', resolvedPath] : [resolvedPath]; | ||||||||||||||||||||||||||||||||||||||||||
| const cmd = isWin ? 'cmd' : resolvedPath; | ||||||||||||||||||||||||||||||||||||||||||
| const options = { shell: false, detached: true }; | ||||||||||||||||||||||||||||||||||||||||||
| const isWin = process.platform === 'win32'; | ||||||||||||||||||||||||||||||||||||||||||
| const args = isWin ? ['/c', 'start', '""', resolvedPath] : [resolvedPath]; | ||||||||||||||||||||||||||||||||||||||||||
| const cmd = isWin ? 'cmd' : resolvedPath; | ||||||||||||||||||||||||||||||||||||||||||
| const options = { shell: false, detached: true }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| desktopProc = spawn(cmd, args, options); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc.unref(); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc = spawn(cmd, args, options); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| desktopProc.unref(); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Launch the executable directly instead of through The comment at line 68 states the goal is to prevent command injection.
🔒 Proposed fix to drop the `cmd` indirection- const isWin = process.platform === 'win32';
- const args = isWin ? ['/c', 'start', '""', resolvedPath] : [resolvedPath];
- const cmd = isWin ? 'cmd' : resolvedPath;
- const options = { shell: false, detached: true };
-
- desktopProc = spawn(cmd, args, options);
+ desktopProc = spawn(resolvedPath, [], { shell: false, detached: true, stdio: 'ignore' });
desktopProc.unref();
+ let settled = false;
desktopProc.on('error', e => {
log('Desktop app launch error:', e.message);
desktopProc = null;
+ if (!settled) { settled = true; reject(e); }
});
- setTimeout(resolve, 1500);
+ setTimeout(() => { if (!settled) { settled = true; resolve(); } }, 1500);📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: CodeQL[warning] 79-79: Shell command built from environment values 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| desktopProc.on('error', e => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Desktop app launch error:', e.message); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc = null; | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc.on('error', e => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Desktop app launch error:', e.message); | ||||||||||||||||||||||||||||||||||||||||||
| desktopProc = null; | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| setTimeout(resolve, 1500); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| setTimeout(resolve, 1500); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -151,20 +93,8 @@ | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| switch (msg.type) { | ||||||||||||||||||||||||||||||||||||||||||
| case 'CREDENTIAL_LEAK': { | ||||||||||||||||||||||||||||||||||||||||||
| // Try to connect via named pipe first | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| if (!desktopClient) { | ||||||||||||||||||||||||||||||||||||||||||
| desktopClient = await connectToDesktopApp(); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| if (desktopClient) { | ||||||||||||||||||||||||||||||||||||||||||
| desktopClient.write(JSON.stringify({ type: 'CREDENTIAL_LEAK', ...msg.payload }) + '\n'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| send({ type: 'LEAK_NOTIFIED', ok: true, original: msg }); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (pipeErr) { | ||||||||||||||||||||||||||||||||||||||||||
| log('Pipe connection failed, launching desktop app:', pipeErr.message); | ||||||||||||||||||||||||||||||||||||||||||
| await launchDesktopApp(); | ||||||||||||||||||||||||||||||||||||||||||
| send({ type: 'LEAK_NOTIFIED', ok: true, original: msg }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| await launchDesktopApp(); | ||||||||||||||||||||||||||||||||||||||||||
| send({ type: 'LEAK_NOTIFIED', ok: true, original: msg }); | ||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| case 'PING': { | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -182,29 +112,6 @@ | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async function main() { | ||||||||||||||||||||||||||||||||||||||||||
| log('Starting native messaging host'); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Try to connect to desktop app on startup | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| desktopClient = await connectToDesktopApp(); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||
| log('Desktop app not running on startup, will launch when needed'); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| while (true) { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const msg = await readMessage(); | ||||||||||||||||||||||||||||||||||||||||||
| await handleMessage(msg); | ||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||
| if (e.message.includes('Stream ended') || e.message.includes('Unexpected end of JSON')) { | ||||||||||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| log('Error processing message:', e.message); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| process.on('uncaughtException', e => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Uncaught:', e); | ||||||||||||||||||||||||||||||||||||||||||
| send({ type: 'ERROR', error: e.message }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -214,7 +121,5 @@ | |||||||||||||||||||||||||||||||||||||||||
| log('Unhandled rejection:', e); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| main().catch(e => { | ||||||||||||||||||||||||||||||||||||||||||
| log('Fatal:', e); | ||||||||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| log('Starting native messaging host'); | ||||||||||||||||||||||||||||||||||||||||||
| readMessages(); | ||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Icon teardown is incomplete at both cleanup sites, so fields permanently lose their icon.
addIconToFieldregisters aupdatePoslistener onwindowforscrollandresize(lines 95-96), stores an entry inpasswordFields(line 99), and setsinput.dataset.soteriosId(line 87). Both cleanup paths now call onlyicon.remove(), so none of those three are reversed. One missing teardown routine causes all of the following:updatePosclosure keepsiconandinputreachable, so the detached icon never gets collected, and thewindowlistener list grows with every password field.passwordFieldsentry retains both nodes for the page lifetime.input.dataset.soteriosIdstays set.addIconToFieldreturns early at line 84 on that dataset value, andscanForPasswordFieldsexcludes[data-soterios-id]at line 103. After the first blur, the field never gets an icon again, even when the user focuses it again.Add one
removeIcon(input, icon)helper that removes the listeners, deletes the map entry, deletesdataset.soteriosId, and removes the icon. Call it from both sites.browser-extension/content.js#L97: call the helper from theblurhandler instead oficon.remove().browser-extension/content.js#L136: call the helper for each entry instead oficon.remove(), then clear the map.🐛 Proposed shared teardown helper
function addIconToField(input) { @@ const updatePos = () => positionIcon(icon, input); window.addEventListener('scroll', updatePos, true); window.addEventListener('resize', updatePos); - input.addEventListener('blur', () => setTimeout(() => icon.remove(), 200), { once: true }); + icon._cleanup = () => { + window.removeEventListener('scroll', updatePos, true); + window.removeEventListener('resize', updatePos); + delete input.dataset.soteriosId; + passwordFields.delete(input); + icon.remove(); + }; + input.addEventListener('blur', () => setTimeout(() => icon._cleanup(), 200), { once: true }); passwordFields.set(input, icon); }📝 Committable suggestion
📍 Affects 1 file
browser-extension/content.js#L97-L97(this comment)browser-extension/content.js#L136-L136🤖 Prompt for AI Agents