Files

77 lines
3.7 KiB
TypeScript

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<string, string>();
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();
}
});