diff options
Diffstat (limited to 'webext/chrome')
| -rw-r--r-- | webext/chrome/background.js | 83 | ||||
| -rw-r--r-- | webext/chrome/manifest.json | 30 | ||||
| -rw-r--r-- | webext/chrome/popup.html | 56 |
3 files changed, 169 insertions, 0 deletions
diff --git a/webext/chrome/background.js b/webext/chrome/background.js new file mode 100644 index 0000000..faa201f --- /dev/null +++ b/webext/chrome/background.js @@ -0,0 +1,83 @@ +// Background service worker for Cerberus Chrome/Edge extension (MV3) + +function connectNative() { + try { + return chrome.runtime.connectNative('com.cerberus.pm'); + } catch (e) { + console.error('Native host connection failed', e); + return null; + } +} + +function nativeRequest(payload) { + return new Promise((resolve, reject) => { + const port = connectNative(); + if (!port) { + reject(new Error('no_native_host')); + return; + } + const onMessage = (resp) => { + port.onMessage.removeListener(onMessage); + try { port.disconnect(); } catch {} + resolve(resp); + }; + const onDisconnect = () => { + port.onMessage.removeListener(onMessage); + reject(new Error('disconnected')); + }; + port.onMessage.addListener(onMessage); + port.onDisconnect.addListener(onDisconnect); + port.postMessage(payload); + }); +} + +async function getActiveTab() { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + return tabs[0]; +} + +async function getOriginForActiveTab() { + const tab = await getActiveTab(); + try { + const url = new URL(tab.url); + return url.origin; + } catch (e) { + return tab.url; + } +} + +chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + (async () => { + if (!message || !message.type) return; + if (message.type === 'GET_PAGE_FORMS') { + const tab = await getActiveTab(); + chrome.tabs.sendMessage(tab.id, { type: 'SCAN_FORMS' }, sendResponse); + return true; + } + if (message.type === 'FILL_CREDENTIALS') { + const tab = await getActiveTab(); + chrome.tabs.sendMessage(tab.id, { type: 'FILL_CREDENTIALS', payload: message.payload }, sendResponse); + return true; + } + if (message.type === 'GET_CREDENTIALS_FOR_TAB') { + const origin = await getOriginForActiveTab(); + try { + const resp = await nativeRequest({ type: 'get_for_origin', origin, include_password: true }); + sendResponse(resp); + } catch (e) { + sendResponse({ ok: false, error: String(e) }); + } + return true; + } + if (message.type === 'PING_NATIVE') { + try { + const resp = await nativeRequest({ type: 'ping' }); + sendResponse(resp); + } catch (e) { + sendResponse({ ok: false, error: String(e) }); + } + return true; + } + })(); + return true; // keep channel open for async +}); diff --git a/webext/chrome/manifest.json b/webext/chrome/manifest.json new file mode 100644 index 0000000..977b83c --- /dev/null +++ b/webext/chrome/manifest.json @@ -0,0 +1,30 @@ +{ + "manifest_version": 3, + "name": "Cerberus Password Manager", + "version": "0.1.0", + "description": "Auto-fill and manage passwords with Cerberus.", + "permissions": [ + "activeTab", + "storage", + "tabs", + "scripting", + "nativeMessaging" + ], + "host_permissions": [ + "<all_urls>" + ], + "action": { + "default_title": "Cerberus", + "default_popup": "popup.html" + }, + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": ["<all_urls>"], + "js": ["content.js"], + "run_at": "document_end" + } + ] +} diff --git a/webext/chrome/popup.html b/webext/chrome/popup.html new file mode 100644 index 0000000..b3482ed --- /dev/null +++ b/webext/chrome/popup.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8" /> + <title>Cerberus</title> + <style> + body { font-family: sans-serif; width: 280px; margin: 10px; } + button { width: 100%; padding: 8px; margin: 6px 0; } + input { width: 100%; padding: 6px; margin: 6px 0; } + </style> +</head> +<body> + <h3>Cerberus</h3> + <input id="username" placeholder="Username" /> + <input id="password" placeholder="Password" type="password" /> + <button id="fetch">Fetch from Vault</button> + <button id="fill">Fill on Page</button> + <div id="status" style="font-size: 12px; color: #666; margin-top: 6px;"></div> + <script> + async function getActiveTab() { + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + return tabs[0]; + } + function sendToBackground(msg) { + return new Promise((resolve) => { + chrome.runtime.sendMessage(msg, resolve); + }); + } + async function fill(username, password) { + await sendToBackground({ type: 'FILL_CREDENTIALS', payload: { username, password } }); + } + document.getElementById('fill').addEventListener('click', async () => { + const username = document.getElementById('username').value; + const password = document.getElementById('password').value; + try { + await fill(username, password); + window.close(); + } catch (e) { console.error(e); } + }); + document.getElementById('fetch').addEventListener('click', async () => { + const status = document.getElementById('status'); + status.textContent = 'Fetching credentials from vault...'; + try { + const resp = await sendToBackground({ type: 'GET_CREDENTIALS_FOR_TAB' }); + if (!resp || !resp.ok) { status.textContent = 'Failed to fetch (native host?)'; return; } + const results = resp.result || []; + if (results.length === 0) { status.textContent = 'No matching entries'; return; } + const { username, password } = results[0]; + if (username) document.getElementById('username').value = username; + if (password) document.getElementById('password').value = password; + status.textContent = 'Fetched from vault'; + } catch (e) { console.error(e); status.textContent = 'Error'; } + }); + </script> +</body> +</html> |
