fix: retain native signer session through repeated public-key lookups

This commit is contained in:
archipelago
2026-10-05 21:18:27 -04:00
parent 6f098cd9c2
commit 3d0c67eb9b
4 changed files with 80 additions and 7 deletions
+10 -1
View File
@@ -211,7 +211,16 @@
selectedPublicKeyTimer = null;
return Promise.resolve(publicKey);
}
if (navigator.userActivation && navigator.userActivation.isActive) {
// The picker click itself leaves transient user activation active. A second
// public-key lookup in the same login must not open another picker.
var restoringSession = false;
try {
var sessionHint = sessionStorage.getItem('nostr_token');
restoringSession = !!sessionHint && sessionHint.indexOf('mock-') !== 0;
} catch (_) {}
// This is only a UI hint: the broker still verifies the node session and
// signing permissions. A restored app token never authorizes a signature.
if (!restoringSession && !selectedIdentity && navigator.userActivation && navigator.userActivation.isActive) {
return selectIdentity().then(function () {
return getPublicKey();
});
@@ -26,6 +26,7 @@ describe('nostr-provider identity selection', () => {
let providerWindow: ProviderWindow
beforeEach(() => {
sessionStorage.clear()
providerWindow = window as ProviderWindow
delete providerWindow.__archipelagoNostr
delete providerWindow.nostr
@@ -122,6 +123,46 @@ describe('nostr-provider identity selection', () => {
)
})
it('does not reopen after a selected identity when activation survives account restoration', async () => {
const { frame, postMessage, signerOrigin } = loadProvider(true)
const selected = new MessageEvent('message', {
data: { type: 'archipelago:signer-identity', identity: { nostr_pubkey: 'selected-key' } },
origin: signerOrigin,
})
Object.defineProperty(selected, 'source', { value: frame.contentWindow })
window.dispatchEvent(selected)
await expect(providerWindow.nostr!.getPublicKey()).resolves.toBe('selected-key')
postMessage.mockClear()
const next = providerWindow.nostr!.getPublicKey()
expect(postMessage).not.toHaveBeenCalledWith(
expect.objectContaining({ type: 'archipelago:signer-select-identity' }), expect.anything(),
)
const request = postMessage.mock.calls[0]![0] as { id: number }
const response = new MessageEvent('message', {
data: { type: 'nostr-response', id: request.id, result: 'selected-key' }, origin: signerOrigin,
})
Object.defineProperty(response, 'source', { value: frame.contentWindow })
window.dispatchEvent(response)
await expect(next).resolves.toBe('selected-key')
// Explicit account switching remains available after a successful login.
void providerWindow.archipelagoNostr!.selectIdentity()
expect(postMessage).toHaveBeenCalledWith(
expect.objectContaining({ type: 'archipelago:signer-select-identity', force: true }), signerOrigin,
)
})
it('does not mistake reload activation for a new login while restoring a session', () => {
sessionStorage.setItem('nostr_token', 'test-session-hint')
const { postMessage, signerOrigin } = loadProvider(true)
void providerWindow.nostr!.getPublicKey()
expect(postMessage).toHaveBeenCalledWith(
expect.objectContaining({ type: 'nostr-request', method: 'getPublicKey' }), signerOrigin,
)
expect(postMessage).not.toHaveBeenCalledWith(
expect.objectContaining({ type: 'archipelago:signer-select-identity' }), expect.anything(),
)
})
it('parks the hidden broker off-screen and reuses it for the next request', async () => {
const surface = {
expectPageTransition: vi.fn(),
@@ -15,12 +15,13 @@ describe('fleet unavailable readings', () => {
global: {mocks: {$ver: (v: string) => v}},
})
const cards = wrapper.findAll('.fleet-node-card')
expect(cards[0].text()).toContain('0%')
expect(cards[0].text()).toContain('—')
expect(cards[0].text()).toContain('Offline · last seen 2d ago')
expect(cards[1].text()).toContain('Status unknown')
expect(cards[1].text()).not.toContain('0%')
expect(cards[1].text()).toContain('Uptime unavailable')
expect(cards).toHaveLength(2)
expect(cards[0]!.text()).toContain('0%')
expect(cards[0]!.text()).toContain('—')
expect(cards[0]!.text()).toContain('Offline · last seen 2d ago')
expect(cards[1]!.text()).toContain('Status unknown')
expect(cards[1]!.text()).not.toContain('0%')
expect(cards[1]!.text()).toContain('Uptime unavailable')
expect(wrapper.text()).not.toContain('NaN')
wrapper.unmount()
})