| 1 | import assert from "node:assert/strict" ; |
| 2 | import {readFile} from "node:fs/promises" ; |
| 3 | import test from "node:test" ; |
| 4 | import vm from "node:vm" ; |
| 5 | |
| 6 | const shared = await readFile(new URL("../tinyclient/tiny.js" , import.meta.url), "utf8" ); |
| 7 | const source = await readFile(new URL("../tinyclient/private-services.js" , import.meta.url), "utf8" ); |
| 8 | class Element {} |
| 9 | const definitions = new Map(); |
| 10 | const sandbox = { |
| 11 | URL, TextEncoder, HTMLElement: Element, |
| 12 | document: {}, Response, |
| 13 | customElements: {define(name, value) { if (definitions.has(name)) throw Error("duplicate definition" ); definitions.set(name, value); }, get(name) { return definitions.get(name); }}, |
| 14 | tinySigner: { |
| 15 | getPublicKey: async () => "a" .repeat(64), |
| 16 | nip44Encrypt: async (_pubkey, value) => "enc:" + value, |
| 17 | nip44Decrypt: async (_pubkey, value) => value.replace(/^enc:/, "" ) |
| 18 | }, |
| 19 | nostr: {signEvent: async event => event}, |
| 20 | NostrSigner: {verifyEvent: () => true} |
| 21 | }; |
| 22 | sandbox.window = sandbox; |
| 23 | sandbox.tiny = {files: {}}; |
| 24 | vm.runInNewContext(shared + source, sandbox); |
| 25 | const api = sandbox.tiny.files.privateServices; |
| 26 | |
| 27 | test("can be evaluated again after in-place navigation" , async () => { |
| 28 | assert.doesNotThrow(() => vm.runInNewContext(source, sandbox)); |
| 29 | assert.equal(definitions.size, 1); |
| 30 | }); |
| 31 | |
| 32 | test("accepts tenant paths while rejecting unsafe relay URLs" , () => { |
| 33 | assert.equal(api.validURL("wss://Relay.Example/r/Private" ), true); |
| 34 | assert.equal(api.canonicalURL("wss://Relay.Example/r/Private/" ), "wss://relay.example/r/Private" ); |
| 35 | assert.equal(api.validURL("wss://relay.example/?token=secret" ), false); |
| 36 | assert.equal(api.validURL("wss://user:pass@relay.example" ), false); |
| 37 | assert.equal(api.validURL("https://relay.example" ), false); |
| 38 | }); |
| 39 | |
| 40 | test("NIP-44 helpers round-trip and retain unrelated private entries" , async () => { |
| 41 | const clear = JSON.stringify([["name" , "work" ], ["g" , "wss://old.example" ], ["p" , "b" .repeat(64)]]); |
| 42 | const encrypted = await api.encrypt("a" .repeat(64), clear); |
| 43 | assert.equal(await api.decrypt("a" .repeat(64), encrypted), clear); |
| 44 | assert.deepEqual(JSON.parse(JSON.stringify(api.mergeRows([["name" , "work" ], ["g" , "wss://old.example" ], ["p" , "b" .repeat(64)]], ["wss://new.example/r/tenant" ]))), [["name" , "work" ], ["p" , "b" .repeat(64)], ["g" , "wss://new.example/r/tenant" ]]); |
| 45 | }); |
| 46 | |
| 47 | test("NIP-07 nip44 methods are accepted alongside bunker methods" , async () => { |
| 48 | sandbox.tinySigner = null; |
| 49 | sandbox.nostr = {nip44: { |
| 50 | encrypt: async (_pubkey, value) => "nip07:" + value, |
| 51 | decrypt: async (_pubkey, value) => value.replace(/^nip07:/, "" ) |
| 52 | }}; |
| 53 | const encrypted = await api.encrypt("a" .repeat(64), "private" ); |
| 54 | assert.equal(encrypted, "nip07:private" ); |
| 55 | assert.equal(await api.decrypt("a" .repeat(64), encrypted), "private" ); |
| 56 | }); |
| 57 | |
| 58 | test("kind 10318 wire has no public tags" , () => { |
| 59 | const event = api.wireEvent("ciphertext" ); |
| 60 | assert.equal(event.kind, 10318); |
| 61 | assert.equal(JSON.stringify(event.tags), "[]" ); |
| 62 | assert.equal(event.content, "ciphertext" ); |
| 63 | }); |
| 64 | |
| 65 | test("reads only a verified kind 10318 event for the requested author" , async () => { |
| 66 | const pubkey = "a" .repeat(64); |
| 67 | const event = {kind: 10318, pubkey, tags: [], content: "ciphertext" , id: "1" , sig: "2" }; |
| 68 | const requests = []; |
| 69 | sandbox.tiny = {signedFetch: async (_path, _method, body) => { |
| 70 | requests.push(JSON.parse(body)); |
| 71 | return new Response(JSON.stringify([event]), {status: 200}); |
| 72 | }}; |
| 73 | sandbox.NostrSigner.verifyEvent = () => true; |
| 74 | assert.deepEqual(JSON.parse(JSON.stringify(await api.readList(pubkey))), event); |
| 75 | assert.deepEqual(JSON.parse(JSON.stringify(requests[0])), [{kinds: [10318], authors: [pubkey], limit: 1}]); |
| 76 | sandbox.tiny.signedFetch = async () => new Response(JSON.stringify([{...event, pubkey: "b" .repeat(64)}]), {status: 200}); |
| 77 | await assert.rejects(api.readList(pubkey), /invalid/); |
| 78 | sandbox.tiny.signedFetch = async () => new Response(JSON.stringify({items: []}), {status: 200}); |
| 79 | await assert.rejects(api.readList(pubkey), /malformed/); |
| 80 | }); |
| 81 | |
| 82 | test("rejects a failed event signature" , () => { |
| 83 | sandbox.NostrSigner.verifyEvent = () => false; |
| 84 | assert.equal(api.acceptEvent({kind: 10318, pubkey: "a" .repeat(64), tags: [], content: "" }, "a" .repeat(64)), false); |
| 85 | }); |