Add browser extension gRPC bridge
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
const extPopup = globalThis.browser ?? globalThis.chrome;
|
||||
|
||||
function runtimeSend(message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
extPopup.runtime.sendMessage(message, (response) => {
|
||||
const error = extPopup.runtime.lastError;
|
||||
if (error) {
|
||||
reject(new Error(error.message));
|
||||
return;
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hostFromURL(rawURL) {
|
||||
try {
|
||||
return new URL(rawURL).host || rawURL;
|
||||
} catch (_error) {
|
||||
return rawURL || "Current page";
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(title, message, tone) {
|
||||
const card = document.getElementById("status-card");
|
||||
card.dataset.tone = tone || "neutral";
|
||||
document.getElementById("status-title").textContent = title;
|
||||
document.getElementById("status-message").textContent = message;
|
||||
}
|
||||
|
||||
function renderMatches(state) {
|
||||
const root = document.getElementById("matches");
|
||||
root.textContent = "";
|
||||
if (!Array.isArray(state.matches) || state.matches.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "subtle";
|
||||
empty.textContent = "No matching entries for this page.";
|
||||
root.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const match of state.matches) {
|
||||
const row = document.createElement("button");
|
||||
row.type = "button";
|
||||
row.className = "match-row";
|
||||
row.innerHTML = `
|
||||
<span class="match-main">
|
||||
<strong>${match.title}</strong>
|
||||
<span class="subtle">${match.username || "No username"}</span>
|
||||
</span>
|
||||
<span class="quality">${match.quality || ""}</span>
|
||||
`;
|
||||
row.addEventListener("click", async () => {
|
||||
row.disabled = true;
|
||||
try {
|
||||
const result = await runtimeSend({ type: "keepassgo-fill-entry", entryId: match.id });
|
||||
if (!result?.success) {
|
||||
throw new Error(result?.error || "Fill failed.");
|
||||
}
|
||||
setStatus("Filled", `${match.title} was sent to the current page.`, "ready");
|
||||
} catch (error) {
|
||||
setStatus("Fill failed", error instanceof Error ? error.message : String(error), "error");
|
||||
} finally {
|
||||
row.disabled = false;
|
||||
}
|
||||
});
|
||||
root.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const state = await runtimeSend({ type: "keepassgo-popup-state" });
|
||||
document.getElementById("page-host").textContent = hostFromURL(state.pageUrl || "");
|
||||
|
||||
if (!state.configured) {
|
||||
setStatus("Configure access", state.error || "Set the API token in extension settings.", "warning");
|
||||
renderMatches({ matches: [] });
|
||||
return;
|
||||
}
|
||||
if (!state.success) {
|
||||
setStatus("KeePassGO unavailable", state.error || "The native host could not reach KeePassGO.", "error");
|
||||
renderMatches({ matches: [] });
|
||||
return;
|
||||
}
|
||||
if (state.status?.locked) {
|
||||
setStatus("Vault locked", "Unlock KeePassGO, then open the popup again.", "warning");
|
||||
renderMatches({ matches: [] });
|
||||
return;
|
||||
}
|
||||
|
||||
const count = Array.isArray(state.matches) ? state.matches.length : 0;
|
||||
setStatus("Ready", count === 0 ? "KeePassGO is connected." : `${count} matching entr${count === 1 ? "y" : "ies"} found.`, "ready");
|
||||
renderMatches(state);
|
||||
} catch (error) {
|
||||
setStatus("Error", error instanceof Error ? error.message : String(error), "error");
|
||||
renderMatches({ matches: [] });
|
||||
}
|
||||
}
|
||||
|
||||
void main();
|
||||
Reference in New Issue
Block a user