Merge pull request 'Add browser best match option' (#10) from feature/browser-best-match-only into main
ci / lint-test (push) Successful in 4m24s
ci / build (push) Failing after 6m18s

Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
2026-04-25 21:03:43 +00:00
6 changed files with 96 additions and 7 deletions
+34 -6
View File
@@ -3,7 +3,8 @@ const nativeHost = "com.keepassgo.browser";
const isNodeTestEnv = typeof module !== "undefined" && module.exports;
const usePromiseAPI = typeof globalThis.browser !== "undefined";
const defaultSettings = {
bearerToken: ""
bearerToken: "",
bestMatchOnly: false
};
const pageStatePrefix = "keepassgo-page-state:";
const matchCacheTTL = 30 * 1000;
@@ -173,9 +174,10 @@ function connectNative(message) {
}
async function loadSettings() {
const stored = await storageGet(["bearerToken"]);
const stored = await storageGet(["bearerToken", "bestMatchOnly"]);
return {
bearerToken: (stored.bearerToken || defaultSettings.bearerToken).trim()
bearerToken: (stored.bearerToken || defaultSettings.bearerToken).trim(),
bestMatchOnly: Boolean(stored.bestMatchOnly ?? defaultSettings.bestMatchOnly)
};
}
@@ -238,6 +240,30 @@ function matchHost(rawURL) {
}
}
function qualityRank(quality) {
switch (String(quality || "").trim().toLowerCase()) {
case "exact":
return 0;
case "scheme":
return 1;
case "host":
return 2;
default:
return 3;
}
}
function applyBestMatchOnly(matches, enabled) {
if (!Array.isArray(matches) || !enabled) {
return Array.isArray(matches) ? [...matches] : [];
}
if (matches.length === 0) {
return [];
}
const bestRank = Math.min(...matches.map((match) => qualityRank(match?.quality)));
return matches.filter((match) => qualityRank(match?.quality) === bestRank);
}
function defaultObservedTitle(observed) {
if (observed?.title) {
return observed.title;
@@ -632,7 +658,7 @@ async function refreshPageState(tabId, pageUrl, options = {}) {
pendingMessage: tokenPendingApprovalCount(matches?.status ?? state.status) > 0
? approvalHintForState(state) || "Approve or deny the browser fill request in KeePassGO."
: "",
matches: Array.isArray(matches?.matches) ? matches.matches : [],
matches: applyBestMatchOnly(matches?.matches, settings.bestMatchOnly),
error: matches?.error ?? "",
updatedAt: Date.now()
};
@@ -813,6 +839,7 @@ async function saveObservedLogin(tabId, selectedMatch = null) {
}
const backgroundTestExports = {
applyBestMatchOnly,
normalizePageState,
actionPresentationForState,
shouldReuseMatches,
@@ -853,7 +880,8 @@ if (isNodeTestEnv) {
return;
case "keepassgo-save-settings":
await storageSet({
bearerToken: String(message.settings?.bearerToken || "").trim()
bearerToken: String(message.settings?.bearerToken || "").trim(),
bestMatchOnly: Boolean(message.settings?.bestMatchOnly)
});
await refreshActivePage({ force: true }).catch(() => null);
sendResponse({ success: true });
@@ -868,7 +896,7 @@ if (isNodeTestEnv) {
sendResponse({
success: Boolean(response?.success),
error: response?.error || "",
results: Array.isArray(response?.searchResults) ? response.searchResults : [],
results: applyBestMatchOnly(response?.searchResults, settings.bestMatchOnly),
status: response?.status ?? null
});
return;
+20
View File
@@ -69,6 +69,7 @@ test("shouldContinueWatchingState keeps polling locked login pages", () => {
test("default settings include a blank bearer token that can be overridden by harness patching", () => {
assert.equal(background.defaultSettings.bearerToken, "");
assert.equal(background.defaultSettings.bestMatchOnly, false);
});
test("savePlanForObservedLogin prefers updating an exact username match", () => {
@@ -129,3 +130,22 @@ test("savePlanForObservedLogin falls back to saving into the current page group"
url: "https://vault.example.invalid/login"
});
});
test("applyBestMatchOnly keeps only the strongest quality band when enabled", () => {
const filtered = background.applyBestMatchOnly([
{ id: "livingston", title: "Livingston Dell", quality: "exact" },
{ id: "rusty", title: "Rusty Ryan", quality: "host" },
{ id: "linus", title: "Linus Caldwell", quality: "scheme" }
], true);
assert.deepEqual(filtered.map((match) => match.id), ["livingston"]);
});
test("applyBestMatchOnly preserves all matches when disabled", () => {
const filtered = background.applyBestMatchOnly([
{ id: "livingston", title: "Livingston Dell", quality: "exact" },
{ id: "rusty", title: "Rusty Ryan", quality: "host" }
], false);
assert.deepEqual(filtered.map((match) => match.id), ["livingston", "rusty"]);
});
+7
View File
@@ -19,6 +19,13 @@
<span>API token</span>
<textarea id="bearer-token" name="bearer-token" rows="6" spellcheck="false"></textarea>
</label>
<fieldset>
<legend>Browser Matching</legend>
<label class="checkbox-row">
<input id="best-match-only" name="best-match-only" type="checkbox">
<span>Best match only</span>
</label>
</fieldset>
<div class="actions">
<button type="submit">Save</button>
</div>
+3 -1
View File
@@ -23,6 +23,7 @@ async function loadSettings() {
throw new Error(response?.error || "Could not load settings.");
}
document.getElementById("bearer-token").value = response.settings.bearerToken || "";
document.getElementById("best-match-only").checked = Boolean(response.settings.bestMatchOnly);
}
async function saveSettings(event) {
@@ -33,7 +34,8 @@ async function saveSettings(event) {
const response = await runtimeSend({
type: "keepassgo-save-settings",
settings: {
bearerToken: document.getElementById("bearer-token").value
bearerToken: document.getElementById("bearer-token").value,
bestMatchOnly: document.getElementById("best-match-only").checked
}
});
if (!response?.success) {
+27
View File
@@ -187,6 +187,33 @@ textarea {
font: inherit;
}
fieldset {
margin: 0;
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
display: grid;
gap: 12px;
}
legend {
padding: 0 6px;
color: var(--ink-soft);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.checkbox-row {
display: flex;
align-items: center;
gap: 10px;
}
.checkbox-row input {
width: auto;
}
button,
.link-button {
padding: 10px 14px;
+5
View File
@@ -103,6 +103,8 @@ User story:
access.
- Browser matching must treat common KeePass data conventions as real browser
targets, not just the primary `URL` field.
- Users who prefer narrow suggestions can ask the extension to show only the
strongest match quality returned by KeePassGO.
Expected behavior:
@@ -114,6 +116,9 @@ Expected behavior:
- scheme-less host values such as `gitlab.com`
- custom URL fields such as `URL1`, `URL2`, and similar KeePass-style URL
slots
- The extension settings page exposes `Best match only`.
- When `Best match only` is enabled, page suggestions and popup search results
only show the strongest quality band returned by KeePassGO.
## Locked Vault Workflow