From 088a5c797d60d25f5799af0d1fef5cb212a2fdcd Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:22:40 -0400 Subject: [PATCH] diag(nip46): add temporary lifecycle diagnostics for #33 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code-level audit of the NIP-46/Amber login+profile+persistence bug points at a single shared SignerManager.nip46Client + per-profile restore/switch not being multi-account-aware. Added TEMP [nip46-diag] userLogger traces (visible on unstable; devLogger is gated off there) at: - scheduleStoredRemoteSignerRestore (restore-on-load → "unknown user") - disconnectRemoteSigner (the shared-client teardown → "logs out everyone") - SignerManager.logout (scope) - authService.switchProfile (does the switch resolve the expected pubkey) These let a single Amber reproduction pinpoint the exact failure before the fix. Findings + plan recorded under TODO #33. Remove diagnostics with the fix. Co-Authored-By: Claude Opus 4.8 --- js/nostr/managers/SignerManager.js | 31 +++++++++++++++++++++++++++++- js/services/authService.js | 17 ++++++++++++++++ todo/TODO_2026-06-20_pre-launch.md | 29 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/js/nostr/managers/SignerManager.js b/js/nostr/managers/SignerManager.js index 6d42f920..84da75f4 100644 --- a/js/nostr/managers/SignerManager.js +++ b/js/nostr/managers/SignerManager.js @@ -883,6 +883,13 @@ export class SignerManager { async scheduleStoredRemoteSignerRestore() { const stored = readStoredNip46Session(); + // [nip46-diag] TEMP (todo #33): trace whether a NIP-46 session is restored on + // load — the "unknown user after refresh" symptom. Remove with the fix. + userLogger.info("[nip46-diag] restore: scheduleStoredRemoteSignerRestore", { + hasStored: !!stored, + storedPubkey: stored?.pubkey || stored?.userPubkey || null, + storedKeys: stored ? Object.keys(stored) : [], + }); if (!stored) { return; } @@ -896,8 +903,15 @@ export class SignerManager { silent: true, validator: this.buildAccessControlValidator(), }); + userLogger.info("[nip46-diag] restore: succeeded", { + resolvedPubkey: this.pubkey || null, + }); } catch (err) { - devLogger.warn("Failed to restore remote signer", err); + // Surfaced via userLogger so it's visible on unstable (devLogger is gated). + userLogger.warn("[nip46-diag] restore: FAILED", { + message: err?.message || String(err), + code: err?.code || null, + }); } }; @@ -905,6 +919,14 @@ export class SignerManager { } async disconnectRemoteSigner({ keepStored = false } = {}) { + // [nip46-diag] TEMP (todo #33): the single shared nip46Client is torn down here; + // logout/switch calling this is the suspected "logs out everyone" cause. + userLogger.info("[nip46-diag] disconnectRemoteSigner", { + keepStored, + hadClient: !!this.nip46Client, + pubkey: this.pubkey || null, + stack: new Error().stack?.split("\n").slice(2, 5).join(" <- "), + }); // Abort an in-flight nostrconnect:// handshake wait, if any. if (typeof this.pendingHandshakeCancel === "function") { try { @@ -1016,6 +1038,13 @@ export class SignerManager { logout() { const previousPubkey = this.pubkey; + // [nip46-diag] TEMP (todo #33): trace logout scope — which pubkey + whether a + // shared remote-signer client is being torn down (affects other NIP-46 profiles). + userLogger.info("[nip46-diag] logout", { + previousPubkey: previousPubkey || null, + hadRemoteClient: !!this.nip46Client, + sessionActorSource: this.sessionActor?.source || null, + }); this.pubkey = null; logoutSignerFromRegistry(previousPubkey); const previousSessionActor = this.sessionActor; diff --git a/js/services/authService.js b/js/services/authService.js index dec5c086..1715f2ef 100644 --- a/js/services/authService.js +++ b/js/services/authService.js @@ -977,6 +977,12 @@ export default class AuthService { const providerId = normalizeProviderId(normalizedOptions.providerId); const normalizedActive = this.normalizeHexPubkey(getActiveProfilePubkey()); + // [nip46-diag] TEMP (todo #33): trace profile switching — from/to + provider. + userLogger.info("[nip46-diag] switchProfile: begin", { + from: normalizedActive || null, + to: normalizedTarget, + providerId: providerId || null, + }); if (normalizedActive && normalizedActive === normalizedTarget) { return { switched: false, reason: "already-active" }; } @@ -993,6 +999,10 @@ export default class AuthService { try { requestResult = await this.requestLogin(requestOptions); } catch (error) { + userLogger.warn("[nip46-diag] switchProfile: requestLogin FAILED", { + to: normalizedTarget, + message: error?.message || String(error), + }); throw error; } @@ -1001,6 +1011,13 @@ export default class AuthService { (typeof requestResult?.pubkey === "string" ? requestResult.pubkey.trim() : ""); + // [nip46-diag] TEMP (todo #33): did the switch resolve the EXPECTED pubkey? + userLogger.info("[nip46-diag] switchProfile: requestLogin returned", { + to: normalizedTarget, + requestedPubkey: requestedPubkey || null, + matchesTarget: requestedPubkey === normalizedTarget, + hasDetail: !!requestResult?.detail, + }); let detail = requestResult?.detail || null; diff --git a/todo/TODO_2026-06-20_pre-launch.md b/todo/TODO_2026-06-20_pre-launch.md index 783f6e9c..e4eb3c4d 100644 --- a/todo/TODO_2026-06-20_pre-launch.md +++ b/todo/TODO_2026-06-20_pre-launch.md @@ -48,6 +48,35 @@ Likely a shared root cause in session/identity persistence + multi-account handl connection are stored/restored, how multiple accounts are tracked, and how logout scopes to one account. Produce a plan (likely `docs/`), then fix. +- [x] **AUDIT done 2026-06-25 (code-level).** Architecture: + `js/state/cache.js` persists a list of **saved profiles** (`bitvid:savedProfiles:v1`) + + a single **active profile pubkey**. There is ONE active session at a time. + `SignerManager` (`js/nostr/managers/SignerManager.js`) holds a SINGLE shared + `this.nip46Client` (one remote-signer connection). Likely root cause for ALL three + symptoms: the single-shared `nip46Client` + per-profile restore/switch not being + multi-account-aware. Specifics: + - **Logout-logs-out-everyone:** `authService.logout()` IS registry-scoped + (`logoutSignerFromRegistry(previousPubkey)`), but `SignerManager.logout()` + calls `disconnectRemoteSigner()` which destroys the SHARED `nip46Client` — so + for NIP-46 every profile that depends on it loses its signer. + - **Switch-doesn't-work:** `authService.switchProfile` re-runs `requestLogin` + with `expectPubkey`; for NIP-46 the shared client must be re-established for the + target profile — if it doesn't reconnect, the new profile has no working signer. + - **Unknown-user-after-refresh:** `scheduleStoredRemoteSignerRestore` → + `useStoredRemoteSigner` silently fails (the catch logged via the gated + `devLogger`), leaving `this.pubkey` unresolved → UI shows "unknown user". +- [x] **Diagnostics SHIPPED (2026-06-25, TEMP).** Added `[nip46-diag]` `userLogger` + traces (visible on unstable, where `devLogger` is gated off) at: + `scheduleStoredRemoteSignerRestore` (hasStored/storedPubkey + succeeded/FAILED), + `disconnectRemoteSigner` (keepStored/hadClient + caller stack), + `SignerManager.logout` (previousPubkey/hadRemoteClient), and + `authService.switchProfile` (from/to + whether requestLogin resolved the expected + pubkey). **Remove these once the fix lands.** +- [ ] **NEXT: reproduce on the phone with Amber on unstable, paste the `[nip46-diag]` + console output**, then ship the targeted fix (likely: per-profile NIP-46 session + keyed by pubkey, don't tear the shared client on a scoped logout, and reconnect on + switch/restore). + ### 1. Delete is not fully working — tombstoned videos still show in the UI - [x] **Root cause found + fixed** (`afb6200b`): deletes published only to the CAPPED read set (<=8) while videos publish to the full write set, so relays outside