import { test } from "node:test"; import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import vm from "node:vm"; import { NostrConnectSigner } from "applesauce-signers"; import { loginWithBunker, loginWithRemoteApp, clearSigner, getNativeIdentityPicker } from "../src/services/signer.ts"; function storage() { const values = new Map(); return { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); }, removeItem: (key: string) => { values.delete(key); } }; } function browser() { Object.defineProperty(globalThis, "localStorage", { configurable: true, value: storage() }); Object.defineProperty(globalThis, "sessionStorage", { configurable: true, value: storage() }); Object.defineProperty(globalThis, "navigator", { configurable: true, value: { userAgent: "Android" } }); const window = { location: { origin: "https://miners.example", href: "" }, setTimeout, clearTimeout }; Object.defineProperty(globalThis, "window", { configurable: true, value: window }); return window; } test("provider does not overwrite an externally installed NIP-07 extension", () => { const extension = { getPublicKey() {}, signEvent() {} }; const window = { nostr: extension }; const source = readFileSync(new URL("../../../nostr-provider.js", import.meta.url), "utf8"); vm.runInNewContext(source, { window }); assert.equal(window.nostr, extension); assert.equal((window as any).archipelagoNostr, undefined); }); test("native picker remains available locally but is not used for a late extension", () => { const window = browser() as any; const bridge = { getPublicKey() {}, signEvent() {} }; window.nostr = bridge; window.archipelagoNostr = { provider: bridge, selectIdentity() {}, getSelectedIdentity() {} }; assert.equal(getNativeIdentityPicker(), window.archipelagoNostr); window.nostr = { getPublicKey() {}, signEvent() {} }; assert.equal(getNativeIdentityPicker(), undefined); }); test("bunker URI gets explicit relay transport without contacting any relay", async () => { browser(); const original = NostrConnectSigner.fromBunkerURI; const pubkey = "a".repeat(64); (NostrConnectSigner as any).fromBunkerURI = async (_uri: string, options: any) => { assert.equal(typeof options.subscriptionMethod, "function"); assert.equal(typeof options.publishMethod, "function"); return { getPublicKey: async () => pubkey, remote: pubkey, signer: { key: new Uint8Array(32).fill(1) }, secret: "synthetic", relays: [] }; }; try { assert.equal(await loginWithBunker(`bunker://${pubkey}`), pubkey); } finally { NostrConnectSigner.fromBunkerURI = original; clearSigner(); } }); test("mobile signer URI opens synchronously on tap and keeps the external callback origin", async () => { const window = browser(); const originalWait = NostrConnectSigner.prototype.waitForSigner; const originalKey = NostrConnectSigner.prototype.getPublicKey; NostrConnectSigner.prototype.waitForSigner = function () { this.remote = "a".repeat(64); this.isConnected = true; return Promise.resolve(); }; NostrConnectSigner.prototype.getPublicKey = async () => "a".repeat(64); try { const pending = loginWithRemoteApp(); // Deliberately before an await/microtask: preserve mobile user activation. assert.match(window.location.href, /^nostrconnect:\/\//); assert.equal(new URL(window.location.href).searchParams.get("callback"), "https://miners.example/auth/nostr-callback"); assert.equal(await pending, "a".repeat(64)); } finally { NostrConnectSigner.prototype.waitForSigner = originalWait; NostrConnectSigner.prototype.getPublicKey = originalKey; clearSigner(); } });