Improve locked vault browser workflow
This commit is contained in:
@@ -292,6 +292,16 @@ function approvalHintForState(state) {
|
|||||||
return state.pendingMessage || "Approve or deny the fill request in KeePassGO.";
|
return state.pendingMessage || "Approve or deny the fill request in KeePassGO.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldContinueWatchingState(state) {
|
||||||
|
if (!state?.pageHasLoginForm) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (state?.pendingFill) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Boolean(state?.status?.locked);
|
||||||
|
}
|
||||||
|
|
||||||
function schedulePendingPoll(tabId, pageUrl) {
|
function schedulePendingPoll(tabId, pageUrl) {
|
||||||
if (!Number.isInteger(tabId)) {
|
if (!Number.isInteger(tabId)) {
|
||||||
return;
|
return;
|
||||||
@@ -492,7 +502,7 @@ async function refreshPageState(tabId, pageUrl, options = {}) {
|
|||||||
state.matches = [];
|
state.matches = [];
|
||||||
state.updatedAt = Date.now();
|
state.updatedAt = Date.now();
|
||||||
const saved = await setPageState(tabId, state);
|
const saved = await setPageState(tabId, state);
|
||||||
if (saved.pendingFill) {
|
if (shouldContinueWatchingState(saved)) {
|
||||||
schedulePendingPoll(tabId, resolvedURL);
|
schedulePendingPoll(tabId, resolvedURL);
|
||||||
} else {
|
} else {
|
||||||
clearPendingPoll(tabId);
|
clearPendingPoll(tabId);
|
||||||
@@ -502,7 +512,7 @@ async function refreshPageState(tabId, pageUrl, options = {}) {
|
|||||||
|
|
||||||
if (shouldReuseMatches(state, force)) {
|
if (shouldReuseMatches(state, force)) {
|
||||||
const saved = await setPageState(tabId, state);
|
const saved = await setPageState(tabId, state);
|
||||||
if (saved.pendingFill) {
|
if (shouldContinueWatchingState(saved)) {
|
||||||
schedulePendingPoll(tabId, resolvedURL);
|
schedulePendingPoll(tabId, resolvedURL);
|
||||||
} else {
|
} else {
|
||||||
clearPendingPoll(tabId);
|
clearPendingPoll(tabId);
|
||||||
@@ -529,7 +539,7 @@ async function refreshPageState(tabId, pageUrl, options = {}) {
|
|||||||
updatedAt: Date.now()
|
updatedAt: Date.now()
|
||||||
};
|
};
|
||||||
const saved = await setPageState(tabId, state);
|
const saved = await setPageState(tabId, state);
|
||||||
if (saved.pendingFill) {
|
if (shouldContinueWatchingState(saved)) {
|
||||||
schedulePendingPoll(tabId, resolvedURL);
|
schedulePendingPoll(tabId, resolvedURL);
|
||||||
} else {
|
} else {
|
||||||
clearPendingPoll(tabId);
|
clearPendingPoll(tabId);
|
||||||
@@ -657,6 +667,7 @@ const backgroundTestExports = {
|
|||||||
normalizePageState,
|
normalizePageState,
|
||||||
actionPresentationForState,
|
actionPresentationForState,
|
||||||
shouldReuseMatches,
|
shouldReuseMatches,
|
||||||
|
shouldContinueWatchingState,
|
||||||
tokenPendingApprovalCount,
|
tokenPendingApprovalCount,
|
||||||
defaultSettings
|
defaultSettings
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,24 @@ test("tokenPendingApprovalCount reads token-scoped approval state", () => {
|
|||||||
assert.equal(background.tokenPendingApprovalCount({}), 0);
|
assert.equal(background.tokenPendingApprovalCount({}), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("shouldContinueWatchingState keeps polling locked login pages", () => {
|
||||||
|
assert.equal(background.shouldContinueWatchingState({
|
||||||
|
pageHasLoginForm: true,
|
||||||
|
pendingFill: false,
|
||||||
|
status: { locked: true }
|
||||||
|
}), true);
|
||||||
|
assert.equal(background.shouldContinueWatchingState({
|
||||||
|
pageHasLoginForm: true,
|
||||||
|
pendingFill: true,
|
||||||
|
status: { locked: false }
|
||||||
|
}), true);
|
||||||
|
assert.equal(background.shouldContinueWatchingState({
|
||||||
|
pageHasLoginForm: true,
|
||||||
|
pendingFill: false,
|
||||||
|
status: { locked: false }
|
||||||
|
}), false);
|
||||||
|
});
|
||||||
|
|
||||||
test("default settings include a blank bearer token that can be overridden by harness patching", () => {
|
test("default settings include a blank bearer token that can be overridden by harness patching", () => {
|
||||||
assert.equal(background.defaultSettings.bearerToken, "");
|
assert.equal(background.defaultSettings.bearerToken, "");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -429,6 +429,7 @@ function shouldShowInlineOverlay(state, hasTarget, suppressed, idleHidden) {
|
|||||||
state?.pageHasLoginForm &&
|
state?.pageHasLoginForm &&
|
||||||
(
|
(
|
||||||
state?.pendingFill ||
|
state?.pendingFill ||
|
||||||
|
(state?.configured && state?.success && state?.status?.locked) ||
|
||||||
(state?.configured && state?.success && !state?.status?.locked && Array.isArray(state?.matches) && state.matches.length > 0)
|
(state?.configured && state?.success && !state?.status?.locked && Array.isArray(state?.matches) && state.matches.length > 0)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -727,10 +728,13 @@ if (isNodeTestEnv) {
|
|||||||
|
|
||||||
ensureRootMounted();
|
ensureRootMounted();
|
||||||
dock.style.display = "block";
|
dock.style.display = "block";
|
||||||
trigger.dataset.tone = pageState.pendingFill ? "warning" : (pageState.error ? "error" : "ready");
|
trigger.dataset.tone = pageState.pendingFill || pageState.status?.locked ? "warning" : (pageState.error ? "error" : "ready");
|
||||||
if (pageState.pendingFill) {
|
if (pageState.pendingFill) {
|
||||||
meta.textContent = "Approval needed in KeePassGO";
|
meta.textContent = "Approval needed in KeePassGO";
|
||||||
panelCopy.textContent = pageState.pendingMessage || "Approve or deny the fill request in KeePassGO.";
|
panelCopy.textContent = pageState.pendingMessage || "Approve or deny the fill request in KeePassGO.";
|
||||||
|
} else if (pageState.status?.locked) {
|
||||||
|
meta.textContent = "Unlock KeePassGO";
|
||||||
|
panelCopy.textContent = "Unlock KeePassGO to turn this field back into live login suggestions.";
|
||||||
} else {
|
} else {
|
||||||
const count = Array.isArray(pageState.matches) ? pageState.matches.length : 0;
|
const count = Array.isArray(pageState.matches) ? pageState.matches.length : 0;
|
||||||
meta.textContent = count === 1 ? "1 login ready" : `${count} logins ready`;
|
meta.textContent = count === 1 ? "1 login ready" : `${count} logins ready`;
|
||||||
|
|||||||
@@ -94,6 +94,19 @@ test("shouldShowInlineOverlay hides the page overlay after it is suppressed", ()
|
|||||||
assert.equal(content.shouldShowInlineOverlay(state, true, true, false), false);
|
assert.equal(content.shouldShowInlineOverlay(state, true, true, false), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("shouldShowInlineOverlay stays visible for locked login pages", () => {
|
||||||
|
const state = {
|
||||||
|
pageHasLoginForm: true,
|
||||||
|
configured: true,
|
||||||
|
success: true,
|
||||||
|
status: { locked: true },
|
||||||
|
matches: [],
|
||||||
|
pendingFill: false
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.equal(content.shouldShowInlineOverlay(state, true, false, false), true);
|
||||||
|
});
|
||||||
|
|
||||||
test("shouldShowInlineOverlay hides the page overlay after idle expiry", () => {
|
test("shouldShowInlineOverlay hides the page overlay after idle expiry", () => {
|
||||||
const state = {
|
const state = {
|
||||||
pageHasLoginForm: true,
|
pageHasLoginForm: true,
|
||||||
|
|||||||
@@ -115,6 +115,25 @@ Expected behavior:
|
|||||||
- custom URL fields such as `URL1`, `URL2`, and similar KeePass-style URL
|
- custom URL fields such as `URL1`, `URL2`, and similar KeePass-style URL
|
||||||
slots
|
slots
|
||||||
|
|
||||||
|
## Locked Vault Workflow
|
||||||
|
|
||||||
|
User story:
|
||||||
|
|
||||||
|
- When the current page has a login form but KeePassGO is locked, the browser
|
||||||
|
must still make that state visible on the page and in the popup.
|
||||||
|
- Unlocking KeePassGO should not require the user to reopen the popup multiple
|
||||||
|
times or reload the page before the extension becomes usable again.
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
|
||||||
|
- The popup shows a locked-state message instead of silently falling back to
|
||||||
|
"no matches."
|
||||||
|
- The inline page affordance stays visible on login forms while KeePassGO is
|
||||||
|
locked and tells the user to unlock the vault.
|
||||||
|
- After the vault is unlocked, the extension rechecks the page automatically
|
||||||
|
and turns the locked affordance back into live matches without requiring a
|
||||||
|
page reload.
|
||||||
|
|
||||||
For extension-side regression checks, run:
|
For extension-side regression checks, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user