aboutsummaryrefslogtreecommitdiff
path: root/webext/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'webext/chrome')
-rw-r--r--webext/chrome/background.js83
-rw-r--r--webext/chrome/manifest.json30
-rw-r--r--webext/chrome/popup.html56
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>