Add browser extension gRPC bridge

This commit is contained in:
Joe Julian
2026-04-11 00:52:01 -07:00
parent 885d599db1
commit c017308aa1
23 changed files with 2437 additions and 280 deletions
+195
View File
@@ -0,0 +1,195 @@
const ext = globalThis.browser ?? globalThis.chrome;
const nativeHost = "org.keepassgo.browser";
const defaultSettings = {
grpcAddress: "127.0.0.1:47777",
bearerToken: ""
};
function storageGet(keys) {
return new Promise((resolve, reject) => {
ext.storage.local.get(keys, (value) => {
const error = ext.runtime.lastError;
if (error) {
reject(new Error(error.message));
return;
}
resolve(value);
});
});
}
function storageSet(value) {
return new Promise((resolve, reject) => {
ext.storage.local.set(value, () => {
const error = ext.runtime.lastError;
if (error) {
reject(new Error(error.message));
return;
}
resolve();
});
});
}
function tabsQuery(query) {
return new Promise((resolve, reject) => {
ext.tabs.query(query, (tabs) => {
const error = ext.runtime.lastError;
if (error) {
reject(new Error(error.message));
return;
}
resolve(tabs);
});
});
}
function tabsSendMessage(tabId, message) {
return new Promise((resolve, reject) => {
ext.tabs.sendMessage(tabId, message, (response) => {
const error = ext.runtime.lastError;
if (error) {
reject(new Error(error.message));
return;
}
resolve(response);
});
});
}
function connectNative(message) {
return new Promise((resolve, reject) => {
ext.runtime.sendNativeMessage(nativeHost, message, (response) => {
const error = ext.runtime.lastError;
if (error) {
reject(new Error(error.message));
return;
}
resolve(response);
});
});
}
async function loadSettings() {
const stored = await storageGet(["grpcAddress", "bearerToken"]);
return {
grpcAddress: (stored.grpcAddress || defaultSettings.grpcAddress).trim(),
bearerToken: (stored.bearerToken || "").trim()
};
}
async function activePageContext() {
const [tab] = await tabsQuery({ active: true, currentWindow: true });
return {
tabId: tab?.id ?? null,
url: typeof tab?.url === "string" ? tab.url : ""
};
}
async function statusForPage() {
const settings = await loadSettings();
const page = await activePageContext();
if (!settings.bearerToken) {
return {
success: false,
configured: false,
status: null,
pageUrl: page.url,
matches: [],
error: "Set an API token in extension settings."
};
}
const status = await connectNative({
action: "status",
grpcAddress: settings.grpcAddress,
bearerToken: settings.bearerToken
});
if (!status.success || status.status?.locked || !page.url.startsWith("http")) {
return {
success: status.success,
configured: true,
status: status.status ?? null,
pageUrl: page.url,
matches: [],
error: status.error ?? ""
};
}
const matches = await connectNative({
action: "find-logins",
grpcAddress: settings.grpcAddress,
bearerToken: settings.bearerToken,
url: page.url
});
return {
success: matches.success,
configured: true,
status: matches.status ?? status.status ?? null,
pageUrl: page.url,
matches: matches.matches ?? [],
error: matches.error ?? ""
};
}
async function fillLogin(entryId) {
const settings = await loadSettings();
const page = await activePageContext();
if (!settings.bearerToken) {
throw new Error("API token is not configured.");
}
if (page.tabId == null) {
throw new Error("No active tab is available.");
}
const response = await connectNative({
action: "get-login",
grpcAddress: settings.grpcAddress,
bearerToken: settings.bearerToken,
entryId,
url: page.url
});
if (!response.success || !response.credential) {
throw new Error(response.error || "KeePassGO did not return a credential.");
}
const fillResponse = await tabsSendMessage(page.tabId, {
type: "keepassgo-fill-credential",
credential: response.credential
});
if (!fillResponse?.ok) {
throw new Error(fillResponse?.error || "The current page could not be filled.");
}
return {
credential: response.credential,
pageUrl: page.url
};
}
ext.runtime.onMessage.addListener((message, _sender, sendResponse) => {
(async () => {
switch (message?.type) {
case "keepassgo-popup-state":
sendResponse(await statusForPage());
return;
case "keepassgo-fill-entry":
sendResponse({ success: true, ...(await fillLogin(message.entryId)) });
return;
case "keepassgo-load-settings":
sendResponse({ success: true, settings: await loadSettings() });
return;
case "keepassgo-save-settings":
await storageSet({
grpcAddress: String(message.settings?.grpcAddress || defaultSettings.grpcAddress).trim(),
bearerToken: String(message.settings?.bearerToken || "").trim()
});
sendResponse({ success: true });
return;
default:
sendResponse({ success: false, error: `Unsupported message ${message?.type || ""}`.trim() });
}
})().catch((error) => {
sendResponse({ success: false, error: error instanceof Error ? error.message : String(error) });
});
return true;
});