174 lines
5.6 KiB
JavaScript
174 lines
5.6 KiB
JavaScript
const extPopup = globalThis.browser ?? globalThis.chrome;
|
|
const usePromiseAPI = typeof globalThis.browser !== "undefined";
|
|
|
|
function runtimeSend(message) {
|
|
if (usePromiseAPI) {
|
|
return extPopup.runtime.sendMessage(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 matchSubtitle(match) {
|
|
const parts = [];
|
|
if (match.username) {
|
|
parts.push(match.username);
|
|
}
|
|
if (Array.isArray(match.path) && match.path.length !== 0) {
|
|
parts.push(match.path.join(" / "));
|
|
}
|
|
return parts.join(" · ") || "No username";
|
|
}
|
|
|
|
function renderMatches(state) {
|
|
const root = document.getElementById("matches");
|
|
const targetTabID = popupTabID();
|
|
root.textContent = "";
|
|
if (!Array.isArray(state.matches) || state.matches.length === 0) {
|
|
const empty = document.createElement("p");
|
|
empty.className = "subtle";
|
|
empty.textContent = state.pageHasLoginForm
|
|
? "No matching entries for this page."
|
|
: "No login fields detected on this page.";
|
|
root.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
for (const match of state.matches) {
|
|
const row = document.createElement("button");
|
|
row.type = "button";
|
|
row.className = "match-row";
|
|
const main = document.createElement("span");
|
|
main.className = "match-main";
|
|
const title = document.createElement("strong");
|
|
title.textContent = match.title;
|
|
const subtitle = document.createElement("span");
|
|
subtitle.className = "subtle";
|
|
subtitle.textContent = matchSubtitle(match);
|
|
const quality = document.createElement("span");
|
|
quality.className = "quality";
|
|
quality.textContent = match.quality || "";
|
|
main.appendChild(title);
|
|
main.appendChild(subtitle);
|
|
row.appendChild(main);
|
|
row.appendChild(quality);
|
|
row.addEventListener("click", async () => {
|
|
row.disabled = true;
|
|
setStatus("Approval may be required", "KeePassGO will prompt if this token needs approval before fill.", "warning");
|
|
try {
|
|
const result = await runtimeSend({
|
|
type: "keepassgo-fill-entry",
|
|
entryId: match.id,
|
|
tabId: targetTabID
|
|
});
|
|
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);
|
|
}
|
|
}
|
|
|
|
function renderPageHint(state) {
|
|
const hint = document.getElementById("page-hint");
|
|
if (state.pendingFill) {
|
|
hint.textContent = "Approval is pending in KeePassGO.";
|
|
return;
|
|
}
|
|
if (state.pageHasLoginForm && Array.isArray(state.matches) && state.matches.length > 0) {
|
|
hint.textContent = "Inline KeePassGO suggestions are available on the page.";
|
|
return;
|
|
}
|
|
if (state.pageHasLoginForm) {
|
|
hint.textContent = "KeePassGO checked this login form already.";
|
|
return;
|
|
}
|
|
hint.textContent = "Open a sign-in page to see KeePassGO suggestions here.";
|
|
}
|
|
|
|
function popupTabID() {
|
|
const rawValue = new URLSearchParams(window.location.search).get("tabId");
|
|
if (rawValue === null) {
|
|
return null;
|
|
}
|
|
const parsed = Number.parseInt(rawValue, 10);
|
|
return Number.isInteger(parsed) ? parsed : null;
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const state = await runtimeSend({
|
|
type: "keepassgo-popup-state",
|
|
force: true,
|
|
tabId: popupTabID()
|
|
});
|
|
document.getElementById("page-host").textContent = hostFromURL(state.pageUrl || "");
|
|
renderPageHint(state);
|
|
|
|
if (!state.configured) {
|
|
setStatus("Configure access", state.error || "Set the API token in extension settings.", "warning");
|
|
renderMatches({ matches: [] });
|
|
return;
|
|
}
|
|
if (state.pendingFill) {
|
|
setStatus("Approval needed", state.pendingMessage || "Approve or deny the fill request in KeePassGO.", "warning");
|
|
renderMatches(state);
|
|
return;
|
|
}
|
|
if (!state.success) {
|
|
setStatus("KeePassGO unavailable", state.error || "The native host could not reach KeePassGO.", "error");
|
|
renderMatches(state);
|
|
return;
|
|
}
|
|
if (state.status?.locked) {
|
|
setStatus("Vault locked", "Unlock KeePassGO, then try the page again.", "warning");
|
|
renderMatches(state);
|
|
return;
|
|
}
|
|
|
|
const count = Array.isArray(state.matches) ? state.matches.length : 0;
|
|
if (!state.pageHasLoginForm) {
|
|
setStatus("Ready", "KeePassGO is connected. Open a login form to check for matches.", "ready");
|
|
} else if (count === 0) {
|
|
setStatus("Checked this page", "KeePassGO did not find a matching login for this form.", "ready");
|
|
} else {
|
|
setStatus("Page suggestions ready", count === 1 ? "1 matching entry is ready on this page." : `${count} matching entries are ready on this page.`, "ready");
|
|
}
|
|
renderMatches(state);
|
|
} catch (error) {
|
|
setStatus("Error", error instanceof Error ? error.message : String(error), "error");
|
|
renderMatches({ matches: [] });
|
|
}
|
|
}
|
|
|
|
void main();
|