Skip to content

fix: survive orphaned extension context + add reset-to-init to main screen - #26

Merged
jjohare merged 3 commits into
mainfrom
fix/nip98-context-resilience-and-reset-to-init
Aug 13, 2026
Merged

fix: survive orphaned extension context + add reset-to-init to main screen#26
jjohare merged 3 commits into
mainfrom
fix/nip98-context-resilience-and-reset-to-init

Conversation

@jjohare

@jjohare jjohare commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Two robustness fixes surfaced while live-testing the 0.0.8 passkey identity flow. Source-only — CI rebuilds the bundles.

1. Content-script resilience to an invalidated extension context

src/injected.js, src/nip98-interceptor.js

When the extension is reloaded/updated/disabled while a consuming page stays open, the content script is orphaned: every chrome.runtime.* call throws Extension context invalidated. A high-frequency caller (observed on Proton, whose eventManager polls continuously) then floods the console with an identical multi-line stack on every request.

Fix: the bridge latches the dead context on first sight, console.warns once, dispatches podkey-nip98-disable so the page interceptor restores native fetch/XHR (stops round-tripping to a dead extension), and answers all later requests with a silent null. A tab reload re-injects fresh scripts against the live extension.

  • Reliable detection via chrome.runtime?.id (goes undefined when orphaned) plus an error-message match (Extension context invalidated / message port closed / receiving end does not exist).
  • Functionally harmless before/after (each failure already degraded to an unauthenticated fetch) — this removes the console-spam and the per-request round-trip.

2. Reset-to-init reachable from the main screen

popup/popup.html, popup/popup.js, popup/popup.css

handleForgetKey (wipe vault + public key + passkey config → return to setup) was only wired to the unlock screen's link. An unlocked user had no way back to the setup screen — which is the sole entry point for passkey-derived identity creation. So creating a passkey identity when a key already existed meant locking first, then "Forget key".

Fix: a "Start over" footer link on the main screen, wired to the existing handleForgetKey, styled with the existing --danger token. Same confirm-guarded wipe; no new backend surface.

Verification

  • npm run buildnpm test169/169 passnpm run lint (eslint) → clean ✓
  • Content scripts are shipped raw (manifest points at src/…); bundles are gitignored and rebuilt by CI, so this PR is source-only by design.

🤖 Generated by Claude Code

…init

Two robustness fixes surfaced while live-testing the passkey identity flow.

Content-script resilience (injected.js, nip98-interceptor.js):
When the extension is reloaded/updated while a consuming page stays open,
the content script is orphaned and every chrome.runtime call throws
"Extension context invalidated." High-frequency callers (e.g. Proton's
event-manager poll) flooded the console with an identical stack forever.
Now the bridge latches the dead context on first sight, warns once, tells
the page interceptor to restore native fetch/XHR, and answers all later
requests with a silent null. A tab reload re-injects fresh scripts.

Reset-to-init (popup):
The wipe-and-return-to-setup action (handleForgetKey) was only reachable
from the unlock screen, so an unlocked user had no way back to the setup
screen — the sole entry point for passkey-derived identity creation. Add a
"Start over" footer link on the main screen wired to the same handler, so
existing users can reset to init (and reach passkey creation) without
locking first. Styled with the existing --danger token.

Co-Authored-By: jjohare <github@thedreamlab.uk>
… errors

Requesting residentKey:'preferred' asks the authenticator to create a
discoverable credential, which Podkey never uses — it stores the
credentialId and always passes it via allowCredentials at unlock. On some
TPM/security-key authenticators (e.g. tpm-fido) the resident-credential
makeCredential path fails after a successful fingerprint/UV, surfacing as a
generic NotAllowedError ("timed out or not allowed"). Switch to
residentKey:'discouraged'; hmac-secret/PRF works fine on non-resident creds.

Also surface actionable errors instead of the raw NotAllowedError:
- After create(), check getClientExtensionResults().prf.enabled — the
  definitive signal that the authenticator provisioned hmac-secret — and fail
  early with a clear "no PRF" message rather than persisting a credential that
  can never unlock.
- Translate NotAllowedError/AbortError from either ceremony into a message that
  names the likely causes (cancel/timeout/focus) and the two-prompt shape.

Co-Authored-By: jjohare <github@thedreamlab.uk>
Bring the user-facing docs up to date with the FIDO2 passkey feature and the
PR #26 fixes.

- CHANGELOG: populate [Unreleased] with the passkey master identity (derived +
  wrapped, PRF requirement, nsec backup), "Start over" reset, the invalidated-
  context resilience fix, and the passkey ceremony compatibility/error fixes.
- USAGE: add a "Create a passkey-derived identity (advanced)" walkthrough (two
  prompts, PRF-capable authenticator, nsec backup), "Passkey unlock" for an
  existing key, "Start over (reset to setup)", and a passkey troubleshooting
  entry (NotAllowedError / PRF / fingerprint verify-no-match).
- README: add a "Passkey identity (advanced)" section linking the specs, add
  passkey.js/keyformat.js/auth-header-utils.js to the source tree, correct the
  test count to 169, and add a passkey troubleshooting note.

Co-Authored-By: jjohare <github@thedreamlab.uk>
@jjohare
jjohare merged commit 3bc0ba3 into main Aug 13, 2026
2 checks passed
@jjohare
jjohare deleted the fix/nip98-context-resilience-and-reset-to-init branch August 13, 2026 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant