Merge branch 'pr/fix/indeehub-signout-uat-20261009(8648b736)'
Demo images / Build & push demo images (push) Failing after 38s
Demo images / Build & push demo images (push) Failing after 38s
This commit is contained in:
@@ -1,27 +1,3 @@
|
||||
# Blocking incident: Framework LND startup and false zero balance
|
||||
|
||||
Before starting or resuming work in this repository, read
|
||||
`docs/incident-framework-lnd-startup.md` and check its status.
|
||||
|
||||
The user explicitly required this incident to take priority over later work.
|
||||
While its status is OPEN:
|
||||
|
||||
- Surface this blocker at session start, including when the user asks for unrelated work.
|
||||
- Prioritize investigation and repair on the actual Framework node. If access is
|
||||
unavailable, ask for it and continue useful offline investigation; defer unrelated
|
||||
work unless the user explicitly overrides this priority.
|
||||
- A manual LND restart, a source patch, passing local tests, or publishing an OTA
|
||||
does not resolve the incident. Do not mark it fixed until the Framework's startup,
|
||||
Receive flow, and balance behavior are verified on the node, including a controlled
|
||||
reboot with access and recovery arrangements in place.
|
||||
- Preserve wallet identity, wallet/channel databases, credentials, and backups.
|
||||
Never run wallet wipe/recreation as an automatic investigation or recovery step.
|
||||
- Record evidence, changes, validation, and remaining work in the incident document.
|
||||
|
||||
This priority comes from the user's explicit instruction on 2026-09-15. It remains
|
||||
in effect across sessions until the documented acceptance criteria are met or the
|
||||
user explicitly changes it.
|
||||
|
||||
## Unit tests on a live node
|
||||
|
||||
Run backend unit tests through `scripts/test-backend-isolated.sh`. Do not run
|
||||
|
||||
@@ -194,6 +194,15 @@
|
||||
try {
|
||||
['nostr_token', 'nostr_pubkey', 'refresh_token'].forEach(function (key) { sessionStorage.removeItem(key); });
|
||||
} catch (_) {}
|
||||
// Signing out is also an explicit request to stop the dashboard from
|
||||
// restoring this app's remembered identity on its next load. The broker
|
||||
// forgets only the app-to-identity selection; node identities and keys are
|
||||
// preserved and remain available for a later explicit sign-in.
|
||||
postToSigner({
|
||||
type: embedded
|
||||
? 'archipelago:identity:clear'
|
||||
: 'archipelago:signer-clear-session',
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleIdentityAuth(pubkey) {
|
||||
|
||||
@@ -312,6 +312,13 @@ function storeIdentity(appUrl: string, identity: SelectedIdentity) {
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function clearStoredIdentity(appUrl: string) {
|
||||
try {
|
||||
const key = IDENTITY_STORAGE_KEY + appUrl.replace(/[^a-z0-9]/gi, '_')
|
||||
localStorage.removeItem(key)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
/** Handle identity selection from the picker */
|
||||
function onIdentitySelected(identity: SelectedIdentity) {
|
||||
showIdentityPicker.value = false
|
||||
@@ -522,6 +529,10 @@ function onMessage(e: MessageEvent) {
|
||||
if (e.data?.force === true) showIdentityPicker.value = true
|
||||
else sendIdentityIfSupported()
|
||||
}
|
||||
if (e.data?.type === 'archipelago:identity:clear' && store.isOpen && e.source === iframeRef.value?.contentWindow) {
|
||||
if (store.url) clearStoredIdentity(store.url)
|
||||
showIdentityPicker.value = false
|
||||
}
|
||||
// Wallet connect — app requests a payment
|
||||
if (e.data?.type === 'archipelago:payment-request' && store.isOpen) {
|
||||
handlePaymentRequest(e)
|
||||
|
||||
@@ -569,6 +569,7 @@ function onMessage(e: MessageEvent) {
|
||||
void registrationBridge.handle(e); void rentalBridge.handle(e)
|
||||
if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e)
|
||||
if (e.data?.type === 'archipelago:identity:request') identity.handleIdentityRequest(e.data?.force === true)
|
||||
if (e.data?.type === 'archipelago:identity:clear') identity.clearRememberedIdentity()
|
||||
if (e.data?.type === 'archipelago:media:playing') screensaverStore.suppress(screensaverReason.value)
|
||||
if (e.data?.type === 'archipelago:media:idle') screensaverStore.resume(screensaverReason.value)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ function storeIdentity(identity: SelectedIdentity) {
|
||||
try { localStorage.setItem(`archipelago_app_identity_${appId.value}`, JSON.stringify(identity)) } catch {}
|
||||
}
|
||||
|
||||
function clearStoredIdentity() {
|
||||
if (!appId.value) return
|
||||
try { localStorage.removeItem(`archipelago_app_identity_${appId.value}`) } catch {}
|
||||
}
|
||||
|
||||
function parentPost(message: Record<string, unknown>) {
|
||||
window.parent.postMessage(message, appOrigin.value || '*')
|
||||
}
|
||||
@@ -204,6 +209,17 @@ function onMessage(event: MessageEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (data.type === 'archipelago:signer-clear-session' && appId.value && event.origin === appOrigin.value) {
|
||||
clearStoredIdentity()
|
||||
queuedRequests.splice(0)
|
||||
showIdentityPicker.value = false
|
||||
bridge.cancelPending()
|
||||
registrationBridge.cancel()
|
||||
rentalBridge.cancel()
|
||||
hideSigner()
|
||||
return
|
||||
}
|
||||
|
||||
if (data.type === 'archipelago-rental-request' && appId.value && event.origin === appOrigin.value) {
|
||||
void rentalBridge.handle(event); return
|
||||
}
|
||||
|
||||
@@ -66,6 +66,25 @@ describe('NostrTabSigner visibility', () => {
|
||||
expect(document.body.classList.contains('nostr-signer-route')).toBe(false)
|
||||
})
|
||||
|
||||
it('forgets the remembered app selection when the app signs out', async () => {
|
||||
localStorage.setItem('archipelago_app_identity_indeedhub', JSON.stringify({
|
||||
id: 'identity-a', name: 'Alice', nostr_pubkey: 'a'.repeat(64),
|
||||
}))
|
||||
localStorage.setItem('unrelated-node-identity', 'preserved')
|
||||
const wrapper = shallowMount(NostrTabSigner)
|
||||
try {
|
||||
parentMessage({ type: 'archipelago:signer-init', appId: 'indeedhub', appName: 'IndeeHub' })
|
||||
parentMessage({ type: 'archipelago:signer-clear-session' })
|
||||
await flushPromises()
|
||||
|
||||
expect(localStorage.getItem('archipelago_app_identity_indeedhub')).toBeNull()
|
||||
expect(localStorage.getItem('unrelated-node-identity')).toBe('preserved')
|
||||
expect(wrapper.findComponent(NostrIdentityPicker).props('show')).toBe(false)
|
||||
} finally {
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
it('hands off a reactive picker identity as cloneable public fields and releases the overlay', async () => {
|
||||
vi.useFakeTimers()
|
||||
const sent: Array<Record<string, unknown>> = []
|
||||
|
||||
@@ -482,10 +482,15 @@ describe('nostr-provider identity selection', () => {
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
const { frame, signerOrigin, postMessage } = loadProvider(false)
|
||||
choose(frame, signerOrigin, key)
|
||||
postMessage.mockClear()
|
||||
providerWindow.archipelagoNostr!.clearSession()
|
||||
await vi.advanceTimersByTimeAsync(1600)
|
||||
expect(fetchMock).not.toHaveBeenCalled()
|
||||
expect(providerWindow.archipelagoNostr!.getSelectedIdentity()).toBeNull()
|
||||
expect(postMessage).toHaveBeenCalledWith(
|
||||
{ type: 'archipelago:signer-clear-session' },
|
||||
signerOrigin,
|
||||
)
|
||||
const selection = providerWindow.archipelagoNostr!.selectIdentity()
|
||||
expect(postMessage).toHaveBeenCalledWith(expect.objectContaining({ type: 'archipelago:signer-select-identity', force: true }), signerOrigin)
|
||||
choose(frame, signerOrigin, key)
|
||||
|
||||
@@ -49,4 +49,21 @@ describe('useAppIdentity explicit identity selection', () => {
|
||||
expect(showPicker.value).toBe(false)
|
||||
expect(postMessage).toHaveBeenCalledWith({ type: 'archipelago:identity-cancelled' }, '*')
|
||||
})
|
||||
|
||||
it('forgets automatic app selection without deleting the node identity', () => {
|
||||
localStorage.setItem('archipelago_app_identity_archipelago-source', JSON.stringify(alice))
|
||||
localStorage.setItem('unrelated-node-identity', 'preserved')
|
||||
const showPicker = ref(true)
|
||||
const identity = useAppIdentity(
|
||||
ref('archipelago-source'),
|
||||
ref(null),
|
||||
showPicker,
|
||||
)
|
||||
|
||||
identity.clearRememberedIdentity()
|
||||
|
||||
expect(identity.getStoredIdentity()).toBeNull()
|
||||
expect(localStorage.getItem('unrelated-node-identity')).toBe('preserved')
|
||||
expect(showPicker.value).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,6 +35,13 @@ export function useAppIdentity(
|
||||
try { localStorage.setItem(IDENTITY_KEY + appId.value, JSON.stringify(identity)) } catch {}
|
||||
}
|
||||
|
||||
/** Forget only this app's automatic identity choice. Saved node identities
|
||||
* and their keys remain intact for a later explicit sign-in. */
|
||||
function clearRememberedIdentity() {
|
||||
try { localStorage.removeItem(IDENTITY_KEY + appId.value) } catch {}
|
||||
showIdentityPicker.value = false
|
||||
}
|
||||
|
||||
async function sendIdentity(identity: SelectedIdentity) {
|
||||
try {
|
||||
const challenge = `archipelago-identity:${Date.now()}`
|
||||
@@ -86,6 +93,7 @@ export function useAppIdentity(
|
||||
onIdentitySelected,
|
||||
onIframeLoadIdentity,
|
||||
handleIdentityRequest,
|
||||
clearRememberedIdentity,
|
||||
cancelIdentitySelection,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user