Allow explicit browser search fill
ci / lint-test (pull_request) Successful in 6m13s
ci / build (pull_request) Successful in 6m8s

This commit is contained in:
Joe Julian
2026-04-28 21:15:15 -07:00
parent e171f49287
commit 72006aa4b1
5 changed files with 116 additions and 10 deletions
+42 -8
View File
@@ -700,7 +700,34 @@ async function statusForPage(options = {}) {
return refreshPageState(page.tabId, page.url, options);
}
async function fillLogin(tabId, entryId) {
function matchedLoginCredentialRequest(settings, entryId, pageUrl) {
return {
action: "get-login",
bearerToken: settings.bearerToken,
entryId,
url: pageUrl
};
}
function selectedLoginCredentialRequest(settings, entryId) {
return {
action: "get-login",
bearerToken: settings.bearerToken,
entryId
};
}
async function fillMatchedLogin(tabId, entryId) {
const page = await loginFillPage(tabId);
return fillLoginOnPage(tabId, entryId, page.url, matchedLoginCredentialRequest);
}
async function fillSelectedLogin(tabId, entryId) {
const page = await loginFillPage(tabId);
return fillLoginOnPage(tabId, entryId, page.url, selectedLoginCredentialRequest);
}
async function loginFillPage(tabId) {
if (!Number.isInteger(tabId)) {
throw new Error("No active tab is available.");
}
@@ -709,7 +736,10 @@ async function fillLogin(tabId, entryId) {
if (!supportsPageStateURL(pageUrl)) {
throw new Error("This page cannot be filled.");
}
return { url: pageUrl };
}
async function fillLoginOnPage(tabId, entryId, pageUrl, credentialRequest) {
let state = await getPageState(tabId, pageUrl);
state = await setPageState(tabId, {
...state,
@@ -729,12 +759,7 @@ async function fillLogin(tabId, entryId) {
throw new Error("API token is not configured.");
}
const response = await connectNative({
action: "get-login",
bearerToken: settings.bearerToken,
entryId,
url: pageUrl
});
const response = await connectNative(credentialRequest(settings, entryId, pageUrl));
if (!response?.success || !response.credential) {
throw new Error(response?.error || "KeePassGO did not return a credential.");
}
@@ -846,6 +871,8 @@ const backgroundTestExports = {
shouldContinueWatchingState,
tokenPendingApprovalCount,
savePlanForObservedLogin,
matchedLoginCredentialRequest,
selectedLoginCredentialRequest,
defaultSettings
};
@@ -872,7 +899,14 @@ if (isNodeTestEnv) {
focusTarget: cloneTarget(message.target)
});
}
sendResponse({ success: true, ...(await fillLogin(targetTabID, message.entryId)) });
sendResponse({ success: true, ...(await fillMatchedLogin(targetTabID, message.entryId)) });
return;
}
case "keepassgo-fill-selected-entry": {
const targetTabID = Number.isInteger(message?.tabId)
? message.tabId
: (Number.isInteger(sender?.tab?.id) ? sender.tab.id : (await activePageContext()).tabId);
sendResponse({ success: true, ...(await fillSelectedLogin(targetTabID, message.entryId)) });
return;
}
case "keepassgo-load-settings":
+21
View File
@@ -149,3 +149,24 @@ test("applyBestMatchOnly preserves all matches when disabled", () => {
assert.deepEqual(filtered.map((match) => match.id), ["livingston", "rusty"]);
});
test("matched login credential requests include the page URL for URL validation", () => {
assert.deepEqual(background.matchedLoginCredentialRequest({
bearerToken: "token-1"
}, "vault-console", "https://bellagio.example.invalid/login"), {
action: "get-login",
bearerToken: "token-1",
entryId: "vault-console",
url: "https://bellagio.example.invalid/login"
});
});
test("explicit selected credential requests omit the page URL", () => {
assert.deepEqual(background.selectedLoginCredentialRequest({
bearerToken: "token-1"
}, "no-url-entry"), {
action: "get-login",
bearerToken: "token-1",
entryId: "no-url-entry"
});
});
+15 -2
View File
@@ -97,7 +97,7 @@ function renderMatchList(root, matches, options = {}) {
setStatus("Filled", `${match.title} was sent to the current page.`, "ready");
}
} catch (error) {
setStatus(options.onSelect ? "Save failed" : "Fill failed", error instanceof Error ? error.message : String(error), "error");
setStatus(options.errorTitle || (options.onSelect ? "Save failed" : "Fill failed"), error instanceof Error ? error.message : String(error), "error");
} finally {
row.disabled = false;
}
@@ -147,7 +147,20 @@ function renderSearchResults(results, query) {
return;
}
renderMatchList(root, results, {
emptyMessage: `No entries matched "${query}".`
emptyMessage: `No entries matched "${query}".`,
errorTitle: "Fill failed",
onSelect: async (match, targetTabID) => {
setStatus("Approval may be required", "KeePassGO will prompt if this token needs approval before fill.", "warning");
const result = await runtimeSend({
type: "keepassgo-fill-selected-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");
}
});
}