From 718032022c66c947704d1b338d1966d1874ed572 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 31 Aug 2023 13:42:15 -0500 Subject: [PATCH] just format --- .eslintrc.json | 39 ++--- .github/workflows/test.yml | 2 +- README.md | 82 ++++------ build.js | 12 +- event.test.ts | 92 +++++------ event.ts | 41 ++--- fakejson.test.ts | 26 ++- filter.test.ts | 113 +++++++------ filter.ts | 36 +---- keys.test.ts | 2 +- keys.ts | 4 +- kinds.test.ts | 2 +- nip04.test.ts | 8 +- nip04.ts | 48 ++---- nip05.test.ts | 14 +- nip05.ts | 11 +- nip06.test.ts | 10 +- nip06.ts | 17 +- nip10.test.ts | 315 ++++++++++++------------------------- nip10.ts | 17 +- nip13.test.ts | 2 +- nip13.ts | 4 +- nip18.test.ts | 39 ++--- nip18.ts | 36 +++-- nip19.test.ts | 60 +++---- nip19.ts | 40 +++-- nip21.test.ts | 34 +--- nip21.ts | 9 +- nip25.test.ts | 23 ++- nip25.ts | 30 ++-- nip26.test.ts | 52 +++--- nip26.ts | 51 ++---- nip27.test.ts | 24 +-- nip27.ts | 15 +- nip28.test.ts | 48 ++---- nip28.ts | 39 +++-- nip39.test.ts | 4 +- nip39.ts | 15 +- nip42.test.ts | 14 +- nip42.ts | 14 +- nip44.test.ts | 6 +- nip44.ts | 12 +- nip57.test.ts | 144 +++++++---------- nip57.ts | 47 ++---- nip98.test.ts | 67 ++------ nip98.ts | 57 +++---- pool.test.ts | 84 +++++----- pool.ts | 52 +++--- references.test.ts | 47 ++---- references.ts | 32 ++-- relay.test.ts | 57 +++---- relay.ts | 101 ++++-------- test-helpers.ts | 4 +- utils.test.ts | 234 ++++++++++++++------------- utils.ts | 30 +--- 55 files changed, 930 insertions(+), 1488 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 60544c4..420ac63 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,11 +35,11 @@ "rules": { "accessor-pairs": 2, - "arrow-spacing": [2, {"before": true, "after": true}], + "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", {"allowSingleLine": true}], + "brace-style": [2, "1tbs", { "allowSingleLine": true }], "comma-dangle": 0, - "comma-spacing": [2, {"before": false, "after": true}], + "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, "curly": [0, "multi-line"], @@ -49,8 +49,8 @@ "handle-callback-err": [2, "^(err|error)$"], "indent": 0, "jsx-quotes": [2, "prefer-double"], - "key-spacing": [2, {"beforeColon": false, "afterColon": true}], - "keyword-spacing": [2, {"before": true, "after": true}], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "keyword-spacing": [2, { "before": true, "after": true }], "new-cap": 0, "new-parens": 0, "no-array-constructor": 2, @@ -82,12 +82,12 @@ "no-irregular-whitespace": 2, "no-iterator": 2, "no-label-var": 2, - "no-labels": [2, {"allowLoop": false, "allowSwitch": false}], + "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], "no-lone-blocks": 2, "no-mixed-spaces-and-tabs": 2, "no-multi-spaces": 2, "no-multi-str": 2, - "no-multiple-empty-lines": [2, {"max": 2}], + "no-multiple-empty-lines": [2, { "max": 2 }], "no-native-reassign": 2, "no-negated-in-lhs": 2, "no-new": 0, @@ -115,34 +115,23 @@ "no-undef": 2, "no-undef-init": 2, "no-unexpected-multiline": 2, - "no-unneeded-ternary": [2, {"defaultAssignment": false}], + "no-unneeded-ternary": [2, { "defaultAssignment": false }], "no-unreachable": 2, - "no-unused-vars": [ - 2, - {"vars": "local", "args": "none", "varsIgnorePattern": "^_"} - ], + "no-unused-vars": [2, { "vars": "local", "args": "none", "varsIgnorePattern": "^_" }], "no-useless-call": 2, "no-useless-constructor": 2, "no-with": 2, - "one-var": [0, {"initialized": "never"}], - "operator-linebreak": [ - 2, - "after", - {"overrides": {"?": "before", ":": "before"}} - ], + "one-var": [0, { "initialized": "never" }], + "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], "padded-blocks": [2, "never"], - "quotes": [ - 2, - "single", - {"avoidEscape": true, "allowTemplateLiterals": true} - ], + "quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }], "semi": [2, "never"], - "semi-spacing": [2, {"before": false, "after": true}], + "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [2, "always"], "space-before-function-paren": 0, "space-in-parens": [2, "never"], "space-infix-ops": 2, - "space-unary-ops": [2, {"words": true, "nonwords": false}], + "space-unary-ops": [2, { "words": true, "nonwords": false }], "spaced-comment": 0, "template-curly-spacing": [2, "never"], "use-isnan": 2, diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5e8d8ce..0ccc2e7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,4 +24,4 @@ jobs: with: node-version: 18 - run: just install-dependencies - - run: just lint \ No newline at end of file + - run: just lint diff --git a/README.md b/README.md index 15e921d..c463175 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This package is only providing lower-level functionality. If you want an easy-to ### Generating a private key and a public key ```js -import {generatePrivateKey, getPublicKey} from 'nostr-tools' +import { generatePrivateKey, getPublicKey } from 'nostr-tools' let sk = generatePrivateKey() // `sk` is a hex string let pk = getPublicKey(sk) // `pk` is a hex string @@ -26,20 +26,14 @@ let pk = getPublicKey(sk) // `pk` is a hex string ### Creating, signing and verifying events ```js -import { - validateEvent, - verifySignature, - getSignature, - getEventHash, - getPublicKey -} from 'nostr-tools' +import { validateEvent, verifySignature, getSignature, getEventHash, getPublicKey } from 'nostr-tools' let event = { kind: 1, created_at: Math.floor(Date.now() / 1000), tags: [], content: 'hello', - pubkey: getPublicKey(privateKey) + pubkey: getPublicKey(privateKey), } event.id = getEventHash(event) @@ -52,12 +46,7 @@ let veryOk = verifySignature(event) ### Interacting with a relay ```js -import { - relayInit, - finishEvent, - generatePrivateKey, - getPublicKey, -} from 'nostr-tools' +import { relayInit, finishEvent, generatePrivateKey, getPublicKey } from 'nostr-tools' const relay = relayInit('wss://relay.example.com') relay.on('connect', () => { @@ -72,8 +61,8 @@ await relay.connect() // let's query for an event that exists let sub = relay.sub([ { - ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] - } + ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'], + }, ]) sub.on('event', event => { console.log('we got the event we wanted:', event) @@ -89,8 +78,8 @@ let pk = getPublicKey(sk) let sub = relay.sub([ { kinds: [1], - authors: [pk] - } + authors: [pk], + }, ]) sub.on('event', event => { @@ -102,16 +91,16 @@ let event = { pubkey: pk, created_at: Math.floor(Date.now() / 1000), tags: [], - content: 'hello world' + content: 'hello world', } // this calculates the event id and signs the event in a single step const signedEvent = finishEvent(event, sk) await relay.publish(signedEvent) -let events = await relay.list([{kinds: [0, 1]}]) +let events = await relay.list([{ kinds: [0, 1] }]) let event = await relay.get({ - ids: ['44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'] + ids: ['44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], }) relay.close() @@ -126,7 +115,7 @@ import 'websocket-polyfill' ### Interacting with multiple relays ```js -import {SimplePool} from 'nostr-tools' +import { SimplePool } from 'nostr-tools' const pool = new SimplePool() @@ -136,11 +125,9 @@ let sub = pool.sub( [...relays, 'wss://relay.example3.com'], [ { - authors: [ - '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' - ] - } - ] + authors: ['32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], + }, + ], ) sub.on('event', event => { @@ -151,14 +138,12 @@ sub.on('event', event => { let pubs = pool.publish(relays, newEvent) await Promise.all(pubs) -let events = await pool.list(relays, [{kinds: [0, 1]}]) +let events = await pool.list(relays, [{ kinds: [0, 1] }]) let event = await pool.get(relays, { - ids: ['44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'] + ids: ['44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], }) -let relaysForEvent = pool.seenOn( - '44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' -) +let relaysForEvent = pool.seenOn('44e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245') // relaysForEvent will be an array of URLs from relays a given event was seen on pool.close() @@ -167,12 +152,12 @@ pool.close() ### Parsing references (mentions) from a content using NIP-10 and NIP-27 ```js -import {parseReferences} from 'nostr-tools' +import { parseReferences } from 'nostr-tools' let references = parseReferences(event) let simpleAugmentedContent = event.content for (let i = 0; i < references.length; i++) { - let {text, profile, event, address} = references[i] + let { text, profile, event, address } = references[i] let augmentedReference = profile ? `@${profilesCache[profile.pubkey].name}` : event @@ -187,7 +172,7 @@ for (let i = 0; i < references.length; i++) { ### Querying profile data from a NIP-05 address ```js -import {nip05} from 'nostr-tools' +import { nip05 } from 'nostr-tools' let profile = await nip05.queryProfile('jb55.com') console.log(profile.pubkey) @@ -205,27 +190,24 @@ nip05.useFetchImplementation(require('node-fetch')) ### Encoding and decoding NIP-19 codes ```js -import {nip19, generatePrivateKey, getPublicKey} from 'nostr-tools' +import { nip19, generatePrivateKey, getPublicKey } from 'nostr-tools' let sk = generatePrivateKey() let nsec = nip19.nsecEncode(sk) -let {type, data} = nip19.decode(nsec) +let { type, data } = nip19.decode(nsec) assert(type === 'nsec') assert(data === sk) let pk = getPublicKey(generatePrivateKey()) let npub = nip19.npubEncode(pk) -let {type, data} = nip19.decode(npub) +let { type, data } = nip19.decode(npub) assert(type === 'npub') assert(data === pk) let pk = getPublicKey(generatePrivateKey()) -let relays = [ - 'wss://relay.nostr.example.mydomain.example.com', - 'wss://nostr.banana.com' -] -let nprofile = nip19.nprofileEncode({pubkey: pk, relays}) -let {type, data} = nip19.decode(nprofile) +let relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com'] +let nprofile = nip19.nprofileEncode({ pubkey: pk, relays }) +let { type, data } = nip19.decode(nprofile) assert(type === 'nprofile') assert(data.pubkey === pk) assert(data.relays.length === 2) @@ -234,7 +216,7 @@ assert(data.relays.length === 2) ### Encrypting and decrypting direct messages ```js -import {nip04, getPublicKey, generatePrivateKey} from 'nostr-tools' +import { nip04, getPublicKey, generatePrivateKey } from 'nostr-tools' // sender let sk1 = generatePrivateKey() @@ -253,7 +235,7 @@ let event = { pubkey: pk1, tags: [['p', pk2]], content: ciphertext, - ...otherProperties + ...otherProperties, } sendEvent(event) @@ -269,7 +251,7 @@ sub.on('event', async event => { ### Performing and checking for delegation ```js -import {nip26, getPublicKey, generatePrivateKey} from 'nostr-tools' +import { nip26, getPublicKey, generatePrivateKey } from 'nostr-tools' // delegator let sk1 = generatePrivateKey() @@ -284,7 +266,7 @@ let delegation = nip26.createDelegation(sk1, { pubkey: pk2, kind: 1, since: Math.round(Date.now() / 1000), - until: Math.round(Date.now() / 1000) + 60 * 60 * 24 * 30 /* 30 days */ + until: Math.round(Date.now() / 1000) + 60 * 60 * 24 * 30 /* 30 days */, }) // the delegatee uses the delegation when building an event @@ -293,7 +275,7 @@ let event = { kind: 1, created_at: Math.round(Date.now() / 1000), content: 'hello from a delegated key', - tags: [['delegation', delegation.from, delegation.cond, delegation.sig]] + tags: [['delegation', delegation.from, delegation.cond, delegation.sig]], } // finally any receiver of this event can check for the presence of a valid delegation tag diff --git a/build.js b/build.js index 1b2dce6..b91634f 100755 --- a/build.js +++ b/build.js @@ -6,7 +6,7 @@ const esbuild = require('esbuild') let common = { entryPoints: ['index.ts'], bundle: true, - sourcemap: 'external' + sourcemap: 'external', } esbuild @@ -14,10 +14,10 @@ esbuild ...common, outfile: 'lib/esm/nostr.mjs', format: 'esm', - packages: 'external' + packages: 'external', }) .then(() => { - const packageJson = JSON.stringify({type: 'module'}) + const packageJson = JSON.stringify({ type: 'module' }) fs.writeFileSync(`${__dirname}/lib/esm/package.json`, packageJson, 'utf8') console.log('esm build success.') @@ -28,7 +28,7 @@ esbuild ...common, outfile: 'lib/nostr.cjs.js', format: 'cjs', - packages: 'external' + packages: 'external', }) .then(() => console.log('cjs build success.')) @@ -41,7 +41,7 @@ esbuild define: { window: 'self', global: 'self', - process: '{"env": {}}' - } + process: '{"env": {}}', + }, }) .then(() => console.log('standalone build success.')) diff --git a/event.test.ts b/event.test.ts index 6c2cae4..625447a 100644 --- a/event.test.ts +++ b/event.test.ts @@ -8,7 +8,7 @@ import { getSignature, Kind, } from './event.ts' -import {getPublicKey} from './keys.ts' +import { getPublicKey } from './keys.ts' describe('Event', () => { describe('getBlankEvent', () => { @@ -17,7 +17,7 @@ describe('Event', () => { kind: 255, content: '', tags: [], - created_at: 0 + created_at: 0, }) }) @@ -26,22 +26,21 @@ describe('Event', () => { kind: 1, content: '', tags: [], - created_at: 0 + created_at: 0, }) }) }) describe('finishEvent', () => { it('should create a signed event from a template', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const template = { kind: Kind.Text, tags: [], content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, } const event = finishEvent(template, privateKey) @@ -58,8 +57,7 @@ describe('Event', () => { describe('serializeEvent', () => { it('should serialize a valid event object', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const unsignedEvent = { @@ -67,7 +65,7 @@ describe('Event', () => { created_at: 1617932115, kind: Kind.Text, tags: [], - content: 'Hello, world!' + content: 'Hello, world!', } const serializedEvent = serializeEvent(unsignedEvent) @@ -79,21 +77,20 @@ describe('Event', () => { unsignedEvent.created_at, unsignedEvent.kind, unsignedEvent.tags, - unsignedEvent.content - ]) + unsignedEvent.content, + ]), ) }) it('should throw an error for an invalid event object', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const invalidEvent = { kind: Kind.Text, tags: [], created_at: 1617932115, - pubkey: publicKey // missing content + pubkey: publicKey, // missing content } expect(() => { @@ -105,8 +102,7 @@ describe('Event', () => { describe('getEventHash', () => { it('should return the correct event hash', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const unsignedEvent = { @@ -114,7 +110,7 @@ describe('Event', () => { tags: [], content: 'Hello, world!', created_at: 1617932115, - pubkey: publicKey + pubkey: publicKey, } const eventHash = getEventHash(unsignedEvent) @@ -126,8 +122,7 @@ describe('Event', () => { describe('validateEvent', () => { it('should return true for a valid event object', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const unsignedEvent = { @@ -135,7 +130,7 @@ describe('Event', () => { tags: [], content: 'Hello, world!', created_at: 1617932115, - pubkey: publicKey + pubkey: publicKey, } const isValid = validateEvent(unsignedEvent) @@ -155,7 +150,7 @@ describe('Event', () => { const invalidEvent = { kind: Kind.Text, tags: [], - created_at: 1617932115 // missing content and pubkey + created_at: 1617932115, // missing content and pubkey } const isValid = validateEvent(invalidEvent) @@ -172,15 +167,14 @@ describe('Event', () => { }) it('should return false for an object with invalid properties', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const invalidEvent = { kind: 1, tags: [], created_at: '1617932115', // should be a number - pubkey: publicKey + pubkey: publicKey, } const isValid = validateEvent(invalidEvent) @@ -194,7 +188,7 @@ describe('Event', () => { tags: [], content: 'Hello, world!', created_at: 1617932115, - pubkey: 'invalid_pubkey' + pubkey: 'invalid_pubkey', } const isValid = validateEvent(invalidEvent) @@ -203,8 +197,7 @@ describe('Event', () => { }) it('should return false for an object with invalid tags', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const invalidEvent = { @@ -212,7 +205,7 @@ describe('Event', () => { tags: {}, // should be an array content: 'Hello, world!', created_at: 1617932115, - pubkey: publicKey + pubkey: publicKey, } const isValid = validateEvent(invalidEvent) @@ -223,17 +216,16 @@ describe('Event', () => { describe('verifySignature', () => { it('should return true for a valid event signature', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const event = finishEvent( { kind: Kind.Text, tags: [], content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, }, - privateKey + privateKey, ) const isValid = verifySignature(event) @@ -242,17 +234,16 @@ describe('Event', () => { }) it('should return false for an invalid event signature', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const event = finishEvent( { kind: Kind.Text, tags: [], content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, }, - privateKey + privateKey, ) // tamper with the signature @@ -264,11 +255,9 @@ describe('Event', () => { }) it('should return false when verifying an event with a different private key', () => { - const privateKey1 = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey1 = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' - const privateKey2 = - '5b4a34f4e4b23c63ad55a35e3f84a3b53d96dbf266edf521a8358f71d19cbf67' + const privateKey2 = '5b4a34f4e4b23c63ad55a35e3f84a3b53d96dbf266edf521a8358f71d19cbf67' const publicKey2 = getPublicKey(privateKey2) const event = finishEvent( @@ -276,15 +265,15 @@ describe('Event', () => { kind: Kind.Text, tags: [], content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, }, - privateKey1 + privateKey1, ) // verify with different private key const isValid = verifySignature({ ...event, - pubkey: publicKey2 + pubkey: publicKey2, }) expect(isValid).toEqual(false) @@ -293,8 +282,7 @@ describe('Event', () => { describe('getSignature', () => { it('should produce the correct signature for an event object', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) const unsignedEvent = { @@ -302,7 +290,7 @@ describe('Event', () => { tags: [], content: 'Hello, world!', created_at: 1617932115, - pubkey: publicKey + pubkey: publicKey, } const sig = getSignature(unsignedEvent, privateKey) @@ -311,7 +299,7 @@ describe('Event', () => { // @ts-expect-error const isValid = verifySignature({ ...unsignedEvent, - sig + sig, }) expect(typeof sig).toEqual('string') @@ -320,19 +308,17 @@ describe('Event', () => { }) it('should not sign an event with different private key', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) - const wrongPrivateKey = - 'a91e2a9d9e0f70f0877bea0dbf034e8f95d7392a27a7f07da0d14b9e9d456be7' + const wrongPrivateKey = 'a91e2a9d9e0f70f0877bea0dbf034e8f95d7392a27a7f07da0d14b9e9d456be7' const unsignedEvent = { kind: Kind.Text, tags: [], content: 'Hello, world!', created_at: 1617932115, - pubkey: publicKey + pubkey: publicKey, } const sig = getSignature(unsignedEvent, wrongPrivateKey) @@ -341,7 +327,7 @@ describe('Event', () => { // @ts-expect-error const isValid = verifySignature({ ...unsignedEvent, - sig + sig, }) expect(typeof sig).toEqual('string') diff --git a/event.ts b/event.ts index 4358541..ca2039c 100644 --- a/event.ts +++ b/event.ts @@ -1,9 +1,9 @@ -import {schnorr} from '@noble/curves/secp256k1' -import {sha256} from '@noble/hashes/sha256' -import {bytesToHex} from '@noble/hashes/utils' +import { schnorr } from '@noble/curves/secp256k1' +import { sha256 } from '@noble/hashes/sha256' +import { bytesToHex } from '@noble/hashes/utils' -import {getPublicKey} from './keys.ts' -import {utf8Encoder} from './utils.ts' +import { getPublicKey } from './keys.ts' +import { utf8Encoder } from './utils.ts' /** @deprecated Use numbers instead. */ /* eslint-disable no-unused-vars */ @@ -32,7 +32,7 @@ export enum Kind { ProfileBadge = 30008, BadgeDefinition = 30009, Article = 30023, - FileMetadata = 1063 + FileMetadata = 1063, } export type EventTemplate = { @@ -58,14 +58,11 @@ export function getBlankEvent(kind: K | Kind.Blank = Kind.Blank) { kind, content: '', tags: [], - created_at: 0 + created_at: 0, } } -export function finishEvent( - t: EventTemplate, - privateKey: string -): Event { +export function finishEvent(t: EventTemplate, privateKey: string): Event { let event = t as Event event.pubkey = getPublicKey(privateKey) event.id = getEventHash(event) @@ -74,17 +71,9 @@ export function finishEvent( } export function serializeEvent(evt: UnsignedEvent): string { - if (!validateEvent(evt)) - throw new Error("can't serialize event with wrong or missing properties") + if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties") - return JSON.stringify([ - 0, - evt.pubkey, - evt.created_at, - evt.kind, - evt.tags, - evt.content - ]) + return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]) } export function getEventHash(event: UnsignedEvent): string { @@ -92,8 +81,7 @@ export function getEventHash(event: UnsignedEvent): string { return bytesToHex(eventHash) } -const isRecord = (obj: unknown): obj is Record => - obj instanceof Object +const isRecord = (obj: unknown): obj is Record => obj instanceof Object export function validateEvent(event: T): event is T & UnsignedEvent { if (!isRecord(event)) return false @@ -126,15 +114,12 @@ export function verifySignature(event: Event): boolean { /** @deprecated Use `getSignature` instead. */ export function signEvent(event: UnsignedEvent, key: string): string { console.warn( - 'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.' + 'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.', ) return getSignature(event, key) } /** Calculate the signature for an event. */ -export function getSignature( - event: UnsignedEvent, - key: string -): string { +export function getSignature(event: UnsignedEvent, key: string): string { return bytesToHex(schnorr.sign(getEventHash(event), key)) } diff --git a/fakejson.test.ts b/fakejson.test.ts index fa4f50b..b60c298 100644 --- a/fakejson.test.ts +++ b/fakejson.test.ts @@ -1,18 +1,18 @@ -import {matchEventId, matchEventKind, getSubscriptionId} from './fakejson.ts' +import { matchEventId, matchEventKind, getSubscriptionId } from './fakejson.ts' test('match id', () => { expect( matchEventId( `["EVENT","nostril-query",{"tags":[],"content":"so did we cut all corners and p2p stuff in order to make a decentralized social network that was fast and worked, but in the end what we got was a lot of very slow clients that can't handle the traffic of one jack dorsey tweet?","sig":"ca62629d189edebb8f0811cfa0ac53015013df5f305dcba3f411ba15cfc4074d8c2d517ee7d9e81c9eb72a7328bfbe31c9122156397565ac55e740404e2b1fe7","id":"fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1671150419}]`, - 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146' - ) + 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146', + ), ).toBeTruthy() expect( matchEventId( `["EVENT","nostril-query",{"content":"a bunch of mfs interacted with my post using what I assume were \"likes\": https://nostr.build/i/964.png","created_at":1672506879,"id":"f40bdd0905137ad60482537e260890ab50b0863bf16e67cf9383f203bd26c96f","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","sig":"8b825d2d4096f0643b18ca39da59ec07a682cd8a3e717f119c845037573d98099f5bea94ec7ddedd5600c8020144a255ed52882a911f7f7ada6d6abb3c0a1eb4","tags":[]}]`, - 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146' - ) + 'fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146', + ), ).toBeFalsy() }) @@ -20,15 +20,15 @@ test('match kind', () => { expect( matchEventKind( `["EVENT","nostril-query",{"tags":[],"content":"so did we cut all corners and p2p stuff in order to make a decentralized social network that was fast and worked, but in the end what we got was a lot of very slow clients that can't handle the traffic of one jack dorsey tweet?","sig":"ca62629d189edebb8f0811cfa0ac53015013df5f305dcba3f411ba15cfc4074d8c2d517ee7d9e81c9eb72a7328bfbe31c9122156397565ac55e740404e2b1fe7","id":"fef2a50f7d9d3d5a5f38ee761bc087ec16198d3f0140df6d1e8193abf7c2b146","kind":1,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1671150419}]`, - 1 - ) + 1, + ), ).toBeTruthy() expect( matchEventKind( `["EVENT","nostril-query",{"content":"{\"name\":\"fiatjaf\",\"about\":\"buy my merch at fiatjaf store\",\"picture\":\"https://fiatjaf.com/static/favicon.jpg\",\"nip05\":\"_@fiatjaf.com\"}","created_at":1671217411,"id":"b52f93f6dfecf9d81f59062827cd941412a0e8398dda60baf960b17499b88900","kind":12720,"pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","sig":"fc1ea5d45fa5ed0526faed06e8fc7a558e60d1b213e9714f440828584ee999b93407092f9b04deea7e504fa034fc0428f31f7f0f95417b3280ebe6004b80b470","tags":[]}]`, - 12720 - ) + 12720, + ), ).toBeTruthy() }) @@ -36,12 +36,8 @@ test('match subscription id', () => { expect(getSubscriptionId('["EVENT","",{}]')).toEqual('') expect(getSubscriptionId('["EVENT","_",{}]')).toEqual('_') expect(getSubscriptionId('["EVENT","subname",{}]')).toEqual('subname') - expect(getSubscriptionId('["EVENT", "kasjbdjkav", {}]')).toEqual( - 'kasjbdjkav' - ) + expect(getSubscriptionId('["EVENT", "kasjbdjkav", {}]')).toEqual('kasjbdjkav') expect( - getSubscriptionId( - ' [ \n\n "EVENT" , \n\n "y4d5ow45gfwoiudfÇA VSADLKAN KLDASB[12312535]SFMZSNJKLH" , {}]' - ) + getSubscriptionId(' [ \n\n "EVENT" , \n\n "y4d5ow45gfwoiudfÇA VSADLKAN KLDASB[12312535]SFMZSNJKLH" , {}]'), ).toEqual('y4d5ow45gfwoiudfÇA VSADLKAN KLDASB[12312535]SFMZSNJKLH') }) diff --git a/filter.test.ts b/filter.test.ts index 3ca3f10..8120ed0 100644 --- a/filter.test.ts +++ b/filter.test.ts @@ -1,5 +1,5 @@ -import {matchFilter, matchFilters, mergeFilters} from './filter.ts' -import {buildEvent} from './test-helpers.ts' +import { matchFilter, matchFilters, mergeFilters } from './filter.ts' +import { buildEvent } from './test-helpers.ts' describe('Filter', () => { describe('matchFilter', () => { @@ -10,7 +10,7 @@ describe('Filter', () => { authors: ['abc'], since: 100, until: 200, - '#tag': ['value'] + '#tag': ['value'], } const event = buildEvent({ @@ -18,7 +18,7 @@ describe('Filter', () => { kind: 1, pubkey: 'abc', created_at: 150, - tags: [['tag', 'value']] + tags: [['tag', 'value']], }) const result = matchFilter(filter, event) @@ -27,9 +27,9 @@ describe('Filter', () => { }) it('should return false when the event id is not in the filter', () => { - const filter = {ids: ['123', '456']} + const filter = { ids: ['123', '456'] } - const event = buildEvent({id: '789'}) + const event = buildEvent({ id: '789' }) const result = matchFilter(filter, event) @@ -37,9 +37,9 @@ describe('Filter', () => { }) it('should return true when the event id starts with a prefix', () => { - const filter = {ids: ['22', '00']} + const filter = { ids: ['22', '00'] } - const event = buildEvent({id: '001'}) + const event = buildEvent({ id: '001' }) const result = matchFilter(filter, event) @@ -47,9 +47,9 @@ describe('Filter', () => { }) it('should return false when the event kind is not in the filter', () => { - const filter = {kinds: [1, 2, 3]} + const filter = { kinds: [1, 2, 3] } - const event = buildEvent({kind: 4}) + const event = buildEvent({ kind: 4 }) const result = matchFilter(filter, event) @@ -57,9 +57,9 @@ describe('Filter', () => { }) it('should return false when the event author is not in the filter', () => { - const filter = {authors: ['abc', 'def']} + const filter = { authors: ['abc', 'def'] } - const event = buildEvent({pubkey: 'ghi'}) + const event = buildEvent({ pubkey: 'ghi' }) const result = matchFilter(filter, event) @@ -67,9 +67,9 @@ describe('Filter', () => { }) it('should return false when a tag is not present in the event', () => { - const filter = {'#tag': ['value1', 'value2']} + const filter = { '#tag': ['value1', 'value2'] } - const event = buildEvent({tags: [['not_tag', 'value1']]}) + const event = buildEvent({ tags: [['not_tag', 'value1']] }) const result = matchFilter(filter, event) @@ -77,9 +77,9 @@ describe('Filter', () => { }) it('should return false when a tag value is not present in the event', () => { - const filter = {'#tag': ['value1', 'value2']} + const filter = { '#tag': ['value1', 'value2'] } - const event = buildEvent({tags: [['tag', 'value3']]}) + const event = buildEvent({ tags: [['tag', 'value3']] }) const result = matchFilter(filter, event) @@ -87,7 +87,7 @@ describe('Filter', () => { }) it('should return true when filter has tags that is present in the event', () => { - const filter = {'#tag1': ['foo']} + const filter = { '#tag1': ['foo'] } const event = buildEvent({ id: '123', @@ -96,8 +96,8 @@ describe('Filter', () => { created_at: 150, tags: [ ['tag1', 'foo'], - ['tag2', 'bar'] - ] + ['tag2', 'bar'], + ], }) const result = matchFilter(filter, event) @@ -106,9 +106,9 @@ describe('Filter', () => { }) it('should return false when the event is before the filter since value', () => { - const filter = {since: 100} + const filter = { since: 100 } - const event = buildEvent({created_at: 50}) + const event = buildEvent({ created_at: 50 }) const result = matchFilter(filter, event) @@ -116,9 +116,9 @@ describe('Filter', () => { }) it('should return true when the timestamp of event is equal to the filter since value', () => { - const filter = {since: 100} + const filter = { since: 100 } - const event = buildEvent({created_at: 100}) + const event = buildEvent({ created_at: 100 }) const result = matchFilter(filter, event) @@ -126,9 +126,9 @@ describe('Filter', () => { }) it('should return false when the event is after the filter until value', () => { - const filter = {until: 100} + const filter = { until: 100 } - const event = buildEvent({created_at: 150}) + const event = buildEvent({ created_at: 150 }) const result = matchFilter(filter, event) @@ -136,9 +136,9 @@ describe('Filter', () => { }) it('should return true when the timestamp of event is equal to the filter until value', () => { - const filter = {until: 100} + const filter = { until: 100 } - const event = buildEvent({created_at: 100}) + const event = buildEvent({ created_at: 100 }) const result = matchFilter(filter, event) @@ -149,12 +149,12 @@ describe('Filter', () => { describe('matchFilters', () => { it('should return true when at least one filter matches the event', () => { const filters = [ - {ids: ['123'], kinds: [1], authors: ['abc']}, - {ids: ['456'], kinds: [2], authors: ['def']}, - {ids: ['789'], kinds: [3], authors: ['ghi']} + { ids: ['123'], kinds: [1], authors: ['abc'] }, + { ids: ['456'], kinds: [2], authors: ['def'] }, + { ids: ['789'], kinds: [3], authors: ['ghi'] }, ] - const event = buildEvent({id: '789', kind: 3, pubkey: 'ghi'}) + const event = buildEvent({ id: '789', kind: 3, pubkey: 'ghi' }) const result = matchFilters(filters, event) @@ -163,12 +163,12 @@ describe('Filter', () => { it('should return true when at least one prefix matches the event', () => { const filters = [ - {ids: ['1'], kinds: [1], authors: ['a']}, - {ids: ['4'], kinds: [2], authors: ['d']}, - {ids: ['9'], kinds: [3], authors: ['g']} + { ids: ['1'], kinds: [1], authors: ['a'] }, + { ids: ['4'], kinds: [2], authors: ['d'] }, + { ids: ['9'], kinds: [3], authors: ['g'] }, ] - const event = buildEvent({id: '987', kind: 3, pubkey: 'ghi'}) + const event = buildEvent({ id: '987', kind: 3, pubkey: 'ghi' }) const result = matchFilters(filters, event) @@ -177,16 +177,16 @@ describe('Filter', () => { it('should return true when event matches one or more filters and some have limit set', () => { const filters = [ - {ids: ['123'], limit: 1}, - {kinds: [1], limit: 2}, - {authors: ['abc'], limit: 3} + { ids: ['123'], limit: 1 }, + { kinds: [1], limit: 2 }, + { authors: ['abc'], limit: 3 }, ] const event = buildEvent({ id: '123', kind: 1, pubkey: 'abc', - created_at: 150 + created_at: 150, }) const result = matchFilters(filters, event) @@ -196,12 +196,12 @@ describe('Filter', () => { it('should return false when no filters match the event', () => { const filters = [ - {ids: ['123'], kinds: [1], authors: ['abc']}, - {ids: ['456'], kinds: [2], authors: ['def']}, - {ids: ['789'], kinds: [3], authors: ['ghi']} + { ids: ['123'], kinds: [1], authors: ['abc'] }, + { ids: ['456'], kinds: [2], authors: ['def'] }, + { ids: ['789'], kinds: [3], authors: ['ghi'] }, ] - const event = buildEvent({id: '100', kind: 4, pubkey: 'jkl'}) + const event = buildEvent({ id: '100', kind: 4, pubkey: 'jkl' }) const result = matchFilters(filters, event) @@ -210,15 +210,15 @@ describe('Filter', () => { it('should return false when event matches none of the filters and some have limit set', () => { const filters = [ - {ids: ['123'], limit: 1}, - {kinds: [1], limit: 2}, - {authors: ['abc'], limit: 3} + { ids: ['123'], limit: 1 }, + { kinds: [1], limit: 2 }, + { authors: ['abc'], limit: 3 }, ] const event = buildEvent({ id: '456', kind: 2, pubkey: 'def', - created_at: 200 + created_at: 200, }) const result = matchFilters(filters, event) @@ -229,20 +229,15 @@ describe('Filter', () => { describe('mergeFilters', () => { it('should merge filters', () => { - expect( - mergeFilters( - {ids: ['a', 'b'], limit: 3}, - {authors: ['x'], ids: ['b', 'c']} - ) - ).toEqual({ids: ['a', 'b', 'c'], limit: 3, authors: ['x']}) + expect(mergeFilters({ ids: ['a', 'b'], limit: 3 }, { authors: ['x'], ids: ['b', 'c'] })).toEqual({ + ids: ['a', 'b', 'c'], + limit: 3, + authors: ['x'], + }) expect( - mergeFilters( - {kinds: [1], since: 15, until: 30}, - {since: 10, kinds: [7], until: 15}, - {kinds: [9, 10]} - ) - ).toEqual({kinds: [1, 7, 9, 10], since: 10, until: 30}) + mergeFilters({ kinds: [1], since: 15, until: 30 }, { since: 10, kinds: [7], until: 15 }, { kinds: [9, 10] }), + ).toEqual({ kinds: [1, 7, 9, 10], since: 10, until: 30 }) }) }) }) diff --git a/filter.ts b/filter.ts index 811e31f..deea4af 100644 --- a/filter.ts +++ b/filter.ts @@ -1,4 +1,4 @@ -import {Event} from './event.ts' +import { Event } from './event.ts' export type Filter = { ids?: string[] @@ -11,10 +11,7 @@ export type Filter = { [key: `#${string}`]: string[] | undefined } -export function matchFilter( - filter: Filter, - event: Event -): boolean { +export function matchFilter(filter: Filter, event: Event): boolean { if (filter.ids && filter.ids.indexOf(event.id) === -1) { if (!filter.ids.some(prefix => event.id.startsWith(prefix))) { return false @@ -31,13 +28,7 @@ export function matchFilter( if (f[0] === '#') { let tagName = f.slice(1) let values = filter[`#${tagName}`] - if ( - values && - !event.tags.find( - ([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1 - ) - ) - return false + if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1)) return false } } @@ -47,10 +38,7 @@ export function matchFilter( return true } -export function matchFilters( - filters: Filter[], - event: Event -): boolean { +export function matchFilters(filters: Filter[], event: Event): boolean { for (let i = 0; i < filters.length; i++) { if (matchFilter(filters[i], event)) return true } @@ -62,12 +50,7 @@ export function mergeFilters(...filters: Filter[]): Filter { for (let i = 0; i < filters.length; i++) { let filter = filters[i] Object.entries(filter).forEach(([property, values]) => { - if ( - property === 'kinds' || - property === 'ids' || - property === 'authors' || - property[0] === '#' - ) { + if (property === 'kinds' || property === 'ids' || property === 'authors' || property[0] === '#') { // @ts-ignore result[property] = result[property] || [] // @ts-ignore @@ -80,12 +63,9 @@ export function mergeFilters(...filters: Filter[]): Filter { } }) - if (filter.limit && (!result.limit || filter.limit > result.limit)) - result.limit = filter.limit - if (filter.until && (!result.until || filter.until > result.until)) - result.until = filter.until - if (filter.since && (!result.since || filter.since < result.since)) - result.since = filter.since + if (filter.limit && (!result.limit || filter.limit > result.limit)) result.limit = filter.limit + if (filter.until && (!result.until || filter.until > result.until)) result.until = filter.until + if (filter.since && (!result.since || filter.since < result.since)) result.since = filter.since } return result diff --git a/keys.test.ts b/keys.test.ts index ff6c26d..aa32cf1 100644 --- a/keys.test.ts +++ b/keys.test.ts @@ -1,4 +1,4 @@ -import {generatePrivateKey, getPublicKey} from './keys.ts' +import { generatePrivateKey, getPublicKey } from './keys.ts' test('private key generation', () => { expect(generatePrivateKey()).toMatch(/[a-f0-9]{64}/) diff --git a/keys.ts b/keys.ts index 9661a8b..03edd1c 100644 --- a/keys.ts +++ b/keys.ts @@ -1,5 +1,5 @@ -import {schnorr} from '@noble/curves/secp256k1' -import {bytesToHex} from '@noble/hashes/utils' +import { schnorr } from '@noble/curves/secp256k1' +import { bytesToHex } from '@noble/hashes/utils' export function generatePrivateKey(): string { return bytesToHex(schnorr.utils.randomPrivateKey()) diff --git a/kinds.test.ts b/kinds.test.ts index a4f3b72..d1775a3 100644 --- a/kinds.test.ts +++ b/kinds.test.ts @@ -1,4 +1,4 @@ -import {classifyKind} from './kinds.ts' +import { classifyKind } from './kinds.ts' test('kind classification', () => { expect(classifyKind(1)).toBe('regular') diff --git a/nip04.test.ts b/nip04.test.ts index 93fe01c..ffa29a4 100644 --- a/nip04.test.ts +++ b/nip04.test.ts @@ -1,7 +1,7 @@ import crypto from 'node:crypto' -import {encrypt, decrypt} from './nip04.ts' -import {getPublicKey, generatePrivateKey} from './keys.ts' +import { encrypt, decrypt } from './nip04.ts' +import { getPublicKey, generatePrivateKey } from './keys.ts' // @ts-ignore // eslint-disable-next-line no-undef @@ -13,7 +13,5 @@ test('encrypt and decrypt message', async () => { let pk1 = getPublicKey(sk1) let pk2 = getPublicKey(sk2) - expect( - await decrypt(sk2, pk1, await encrypt(sk1, pk2, 'hello')) - ).toEqual('hello') + expect(await decrypt(sk2, pk1, await encrypt(sk1, pk2, 'hello'))).toEqual('hello') }) diff --git a/nip04.ts b/nip04.ts index 86438af..334f3a3 100644 --- a/nip04.ts +++ b/nip04.ts @@ -1,8 +1,8 @@ -import {randomBytes} from '@noble/hashes/utils' -import {secp256k1} from '@noble/curves/secp256k1' -import {base64} from '@scure/base' +import { randomBytes } from '@noble/hashes/utils' +import { secp256k1 } from '@noble/curves/secp256k1' +import { base64 } from '@scure/base' -import {utf8Decoder, utf8Encoder} from './utils.ts' +import { utf8Decoder, utf8Encoder } from './utils.ts' // @ts-ignore if (typeof crypto !== 'undefined' && !crypto.subtle && crypto.webcrypto) { @@ -10,58 +10,30 @@ if (typeof crypto !== 'undefined' && !crypto.subtle && crypto.webcrypto) { crypto.subtle = crypto.webcrypto.subtle } -export async function encrypt( - privkey: string, - pubkey: string, - text: string -): Promise { +export async function encrypt(privkey: string, pubkey: string, text: string): Promise { const key = secp256k1.getSharedSecret(privkey, '02' + pubkey) const normalizedKey = getNormalizedX(key) let iv = Uint8Array.from(randomBytes(16)) let plaintext = utf8Encoder.encode(text) - let cryptoKey = await crypto.subtle.importKey( - 'raw', - normalizedKey, - {name: 'AES-CBC'}, - false, - ['encrypt'] - ) - let ciphertext = await crypto.subtle.encrypt( - {name: 'AES-CBC', iv}, - cryptoKey, - plaintext - ) + let cryptoKey = await crypto.subtle.importKey('raw', normalizedKey, { name: 'AES-CBC' }, false, ['encrypt']) + let ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, plaintext) let ctb64 = base64.encode(new Uint8Array(ciphertext)) let ivb64 = base64.encode(new Uint8Array(iv.buffer)) return `${ctb64}?iv=${ivb64}` } -export async function decrypt( - privkey: string, - pubkey: string, - data: string -): Promise { +export async function decrypt(privkey: string, pubkey: string, data: string): Promise { let [ctb64, ivb64] = data.split('?iv=') let key = secp256k1.getSharedSecret(privkey, '02' + pubkey) let normalizedKey = getNormalizedX(key) - let cryptoKey = await crypto.subtle.importKey( - 'raw', - normalizedKey, - {name: 'AES-CBC'}, - false, - ['decrypt'] - ) + let cryptoKey = await crypto.subtle.importKey('raw', normalizedKey, { name: 'AES-CBC' }, false, ['decrypt']) let ciphertext = base64.decode(ctb64) let iv = base64.decode(ivb64) - let plaintext = await crypto.subtle.decrypt( - {name: 'AES-CBC', iv}, - cryptoKey, - ciphertext - ) + let plaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, ciphertext) let text = utf8Decoder.decode(plaintext) return text diff --git a/nip05.test.ts b/nip05.test.ts index 388f03a..848b8a7 100644 --- a/nip05.test.ts +++ b/nip05.test.ts @@ -1,26 +1,20 @@ import fetch from 'node-fetch' -import {useFetchImplementation, queryProfile} from './nip05.ts' +import { useFetchImplementation, queryProfile } from './nip05.ts' test('fetch nip05 profiles', async () => { useFetchImplementation(fetch) let p1 = await queryProfile('jb55.com') - expect(p1!.pubkey).toEqual( - '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' - ) + expect(p1!.pubkey).toEqual('32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245') expect(p1!.relays).toEqual(['wss://relay.damus.io']) let p2 = await queryProfile('jb55@jb55.com') - expect(p2!.pubkey).toEqual( - '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245' - ) + expect(p2!.pubkey).toEqual('32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245') expect(p2!.relays).toEqual(['wss://relay.damus.io']) let p3 = await queryProfile('_@fiatjaf.com') - expect(p3!.pubkey).toEqual( - '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' - ) + expect(p3!.pubkey).toEqual('3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d') expect(p3!.relays).toEqual([ 'wss://relay.nostr.bg', 'wss://nos.lol', diff --git a/nip05.ts b/nip05.ts index 53dedc3..b95c9c0 100644 --- a/nip05.ts +++ b/nip05.ts @@ -1,4 +1,4 @@ -import {ProfilePointer} from './nip19.ts' +import { ProfilePointer } from './nip19.ts' /** * NIP-05 regex. The localpart is optional, and should be assumed to be `_` otherwise. @@ -19,14 +19,9 @@ export function useFetchImplementation(fetchImplementation: any) { _fetch = fetchImplementation } -export async function searchDomain( - domain: string, - query = '' -): Promise<{[name: string]: string}> { +export async function searchDomain(domain: string, query = ''): Promise<{ [name: string]: string }> { try { - let res = await ( - await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`) - ).json() + let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${query}`)).json() return res.names } catch (_) { diff --git a/nip06.test.ts b/nip06.test.ts index b3e31b7..0cbf1a0 100644 --- a/nip06.test.ts +++ b/nip06.test.ts @@ -1,18 +1,14 @@ -import {privateKeyFromSeedWords} from './nip06.ts' +import { privateKeyFromSeedWords } from './nip06.ts' test('generate private key from a mnemonic', async () => { const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const privateKey = privateKeyFromSeedWords(mnemonic) - expect(privateKey).toEqual( - 'c26cf31d8ba425b555ca27d00ca71b5008004f2f662470f8c8131822ec129fe2' - ) + expect(privateKey).toEqual('c26cf31d8ba425b555ca27d00ca71b5008004f2f662470f8c8131822ec129fe2') }) test('generate private key from a mnemonic and passphrase', async () => { const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const passphrase = '123' const privateKey = privateKeyFromSeedWords(mnemonic, passphrase) - expect(privateKey).toEqual( - '55a22b8203273d0aaf24c22c8fbe99608e70c524b17265641074281c8b978ae4' - ) + expect(privateKey).toEqual('55a22b8203273d0aaf24c22c8fbe99608e70c524b17265641074281c8b978ae4') }) diff --git a/nip06.ts b/nip06.ts index 0c14790..ac12761 100644 --- a/nip06.ts +++ b/nip06.ts @@ -1,16 +1,9 @@ -import {bytesToHex} from '@noble/hashes/utils' -import {wordlist} from '@scure/bip39/wordlists/english' -import { - generateMnemonic, - mnemonicToSeedSync, - validateMnemonic -} from '@scure/bip39' -import {HDKey} from '@scure/bip32' +import { bytesToHex } from '@noble/hashes/utils' +import { wordlist } from '@scure/bip39/wordlists/english' +import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39' +import { HDKey } from '@scure/bip32' -export function privateKeyFromSeedWords( - mnemonic: string, - passphrase?: string -): string { +export function privateKeyFromSeedWords(mnemonic: string, passphrase?: string): string { let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) let privateKey = root.derive(`m/44'/1237'/0'/0/0`).privateKey if (!privateKey) throw new Error('could not derive private key') diff --git a/nip10.test.ts b/nip10.test.ts index 93ac7d5..29c4eb0 100644 --- a/nip10.test.ts +++ b/nip10.test.ts @@ -1,252 +1,173 @@ -import {parse} from './nip10.ts' +import { parse } from './nip10.ts' describe('parse NIP10-referenced events', () => { test('legacy + a lot of events', () => { let event = { tags: [ - [ - 'e', - 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c' - ], - [ - 'e', - 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631' - ], - [ - 'e', - '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64' - ], - [ - 'e', - '49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4' - ], - [ - 'e', - '567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976' - ], - [ - 'e', - '090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051' - ], - [ - 'e', - '89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d' - ], - [ - 'p', - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7' - ], - [ - 'p', - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec' - ], - [ - 'p', - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0' - ] - ] + ['e', 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'], + ['e', 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'], + ['e', '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64'], + ['e', '49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4'], + ['e', '567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976'], + ['e', '090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051'], + ['e', '89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d'], + ['p', '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'], + ['p', '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'], + ['p', '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'], + ], } expect(parse(event)).toEqual({ mentions: [ { id: 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631', - relays: [] + relays: [], }, { id: '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64', - relays: [] + relays: [], }, { id: '49aff7ae6daeaaa2777931b90f9bb29f6cb01c5a3d7d88c8ba82d890f264afb4', - relays: [] + relays: [], }, { id: '567b7c11f0fe582361e3cea6fcc7609a8942dfe196ee1b98d5604c93fbeea976', - relays: [] + relays: [], }, { id: '090c037b2e399ee74d9f134758928948dd9154413ca1a1acb37155046e03a051', - relays: [] - } + relays: [], + }, ], profiles: [ { - pubkey: - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', - relays: [] + pubkey: '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', + relays: [], }, { - pubkey: - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', - relays: [] + pubkey: '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', + relays: [], }, { - pubkey: - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', - relays: [] - } + pubkey: '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', + relays: [], + }, ], reply: { id: '89f220b63465c93542b1a78caa3a952cf4f196e91a50596493c8093c533ebc4d', - relays: [] + relays: [], }, root: { id: 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c', - relays: [] - } + relays: [], + }, }) }) test('legacy + 3 events', () => { let event = { tags: [ - [ - 'e', - 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c' - ], - [ - 'e', - 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631' - ], - [ - 'e', - '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64' - ], - [ - 'p', - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7' - ], - [ - 'p', - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec' - ], - [ - 'p', - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0' - ] - ] + ['e', 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'], + ['e', 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'], + ['e', '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64'], + ['p', '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'], + ['p', '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'], + ['p', '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'], + ], } expect(parse(event)).toEqual({ mentions: [ { id: 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631', - relays: [] - } + relays: [], + }, ], profiles: [ { - pubkey: - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', - relays: [] + pubkey: '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', + relays: [], }, { - pubkey: - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', - relays: [] + pubkey: '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', + relays: [], }, { - pubkey: - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', - relays: [] - } + pubkey: '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', + relays: [], + }, ], reply: { id: '5e081ebb19153357d7c31e8a10b9ceeef29313f58dc8d701f66727fab02aef64', - relays: [] + relays: [], }, root: { id: 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c', - relays: [] - } + relays: [], + }, }) }) test('legacy + 2 events', () => { let event = { tags: [ - [ - 'e', - 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c' - ], - [ - 'e', - 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631' - ], - [ - 'p', - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7' - ], - [ - 'p', - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec' - ], - [ - 'p', - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0' - ] - ] + ['e', 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c'], + ['e', 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631'], + ['p', '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7'], + ['p', '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'], + ['p', '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0'], + ], } expect(parse(event)).toEqual({ mentions: [], profiles: [ { - pubkey: - '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', - relays: [] + pubkey: '77ce56f89d1228f7ff3743ce1ad1b254857b9008564727ebd5a1f317362f6ca7', + relays: [], }, { - pubkey: - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', - relays: [] + pubkey: '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', + relays: [], }, { - pubkey: - '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', - relays: [] - } + pubkey: '4ca4f5533e40da5e0508796d409e6bb35a50b26fc304345617ab017183d83ac0', + relays: [], + }, ], reply: { id: 'bbd72f0ae14374aa8fb166b483cfcf99b57d7f4cf1600ccbf17c350040834631', - relays: [] + relays: [], }, root: { id: 'b857504288c18a15950dd05b9e8772c62ca6289d5aac373c0a8ee5b132e94e7c', - relays: [] - } + relays: [], + }, }) }) test('legacy + 1 event', () => { let event = { tags: [ - [ - 'e', - '9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590' - ], - [ - 'p', - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec' - ] - ] + ['e', '9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590'], + ['p', '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec'], + ], } expect(parse(event)).toEqual({ mentions: [], profiles: [ { - pubkey: - '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', - relays: [] - } + pubkey: '534780e44da7b494485e85cd4cca6af4f6caa1627472432b6f2a4ece0e9e54ec', + relays: [], + }, ], reply: undefined, root: { id: '9abbfd9b9ac5ecdab45d14b8bf8d746139ea039e931a1b376d19a239f1946590', - relays: [] - } + relays: [], + }, }) }) @@ -257,95 +178,55 @@ describe('parse NIP10-referenced events', () => { test('recommended + 1 event', () => { let event = { tags: [ - [ - 'p', - 'a8c21fcd8aa1f4befba14d72fc7a012397732d30d8b3131af912642f3c726f52', - 'wss://relay.mostr.pub' - ], - [ - 'p', - '003d7fd21fd09ff7f6f63a75daf194dd99feefbe6919cc376b7359d5090aa9a6', - 'wss://relay.mostr.pub' - ], - [ - 'p', - '2f6fbe452edd3987d3c67f3b034c03ec5bcf4d054c521c3a954686f89f03212e', - 'wss://relay.mostr.pub' - ], - [ - 'p', - '44c7c74668ff222b0e0b30579c49fc6e22dafcdeaad091036c947f9856590f1e', - 'wss://relay.mostr.pub' - ], - [ - 'p', - 'c5cf39149caebda4cdd61771c51f6ba91ef5645919004e5c4998a4ea69f00512', - 'wss://relay.mostr.pub' - ], - [ - 'p', - '094d44bb1e812696c57f57ad1c0c707812dedbe72c07e538b80639032c236a9e', - 'wss://relay.mostr.pub' - ], - [ - 'p', - 'a1ba0ac9b6ec098f726a3c11ec654df4a32cbb84b5377e8788395e9c27d9ecda', - 'wss://relay.mostr.pub' - ], - [ - 'e', - 'f9472913904ab7e9da008dcb2d85fd4af2d2993ada483d00c646d0c4481d031d', - 'wss://relay.mostr.pub', - 'reply' - ], - ['mostr', 'https://poa.st/objects/dc50684b-6364-4264-ab16-49f4622f05ea'] - ] + ['p', 'a8c21fcd8aa1f4befba14d72fc7a012397732d30d8b3131af912642f3c726f52', 'wss://relay.mostr.pub'], + ['p', '003d7fd21fd09ff7f6f63a75daf194dd99feefbe6919cc376b7359d5090aa9a6', 'wss://relay.mostr.pub'], + ['p', '2f6fbe452edd3987d3c67f3b034c03ec5bcf4d054c521c3a954686f89f03212e', 'wss://relay.mostr.pub'], + ['p', '44c7c74668ff222b0e0b30579c49fc6e22dafcdeaad091036c947f9856590f1e', 'wss://relay.mostr.pub'], + ['p', 'c5cf39149caebda4cdd61771c51f6ba91ef5645919004e5c4998a4ea69f00512', 'wss://relay.mostr.pub'], + ['p', '094d44bb1e812696c57f57ad1c0c707812dedbe72c07e538b80639032c236a9e', 'wss://relay.mostr.pub'], + ['p', 'a1ba0ac9b6ec098f726a3c11ec654df4a32cbb84b5377e8788395e9c27d9ecda', 'wss://relay.mostr.pub'], + ['e', 'f9472913904ab7e9da008dcb2d85fd4af2d2993ada483d00c646d0c4481d031d', 'wss://relay.mostr.pub', 'reply'], + ['mostr', 'https://poa.st/objects/dc50684b-6364-4264-ab16-49f4622f05ea'], + ], } expect(parse(event)).toEqual({ mentions: [], profiles: [ { - pubkey: - 'a8c21fcd8aa1f4befba14d72fc7a012397732d30d8b3131af912642f3c726f52', - relays: ['wss://relay.mostr.pub'] + pubkey: 'a8c21fcd8aa1f4befba14d72fc7a012397732d30d8b3131af912642f3c726f52', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - '003d7fd21fd09ff7f6f63a75daf194dd99feefbe6919cc376b7359d5090aa9a6', - relays: ['wss://relay.mostr.pub'] + pubkey: '003d7fd21fd09ff7f6f63a75daf194dd99feefbe6919cc376b7359d5090aa9a6', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - '2f6fbe452edd3987d3c67f3b034c03ec5bcf4d054c521c3a954686f89f03212e', - relays: ['wss://relay.mostr.pub'] + pubkey: '2f6fbe452edd3987d3c67f3b034c03ec5bcf4d054c521c3a954686f89f03212e', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - '44c7c74668ff222b0e0b30579c49fc6e22dafcdeaad091036c947f9856590f1e', - relays: ['wss://relay.mostr.pub'] + pubkey: '44c7c74668ff222b0e0b30579c49fc6e22dafcdeaad091036c947f9856590f1e', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - 'c5cf39149caebda4cdd61771c51f6ba91ef5645919004e5c4998a4ea69f00512', - relays: ['wss://relay.mostr.pub'] + pubkey: 'c5cf39149caebda4cdd61771c51f6ba91ef5645919004e5c4998a4ea69f00512', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - '094d44bb1e812696c57f57ad1c0c707812dedbe72c07e538b80639032c236a9e', - relays: ['wss://relay.mostr.pub'] + pubkey: '094d44bb1e812696c57f57ad1c0c707812dedbe72c07e538b80639032c236a9e', + relays: ['wss://relay.mostr.pub'], }, { - pubkey: - 'a1ba0ac9b6ec098f726a3c11ec654df4a32cbb84b5377e8788395e9c27d9ecda', - relays: ['wss://relay.mostr.pub'] - } + pubkey: 'a1ba0ac9b6ec098f726a3c11ec654df4a32cbb84b5377e8788395e9c27d9ecda', + relays: ['wss://relay.mostr.pub'], + }, ], reply: { id: 'f9472913904ab7e9da008dcb2d85fd4af2d2993ada483d00c646d0c4481d031d', - relays: ['wss://relay.mostr.pub'] + relays: ['wss://relay.mostr.pub'], }, - root: undefined + root: undefined, }) }) }) diff --git a/nip10.ts b/nip10.ts index e9fd21e..87c4cb4 100644 --- a/nip10.ts +++ b/nip10.ts @@ -1,5 +1,5 @@ -import type {Event} from './event.ts' -import type {EventPointer, ProfilePointer} from './nip19.ts' +import type { Event } from './event.ts' +import type { EventPointer, ProfilePointer } from './nip19.ts' export type NIP10Result = { /** @@ -28,7 +28,7 @@ export function parse(event: Pick): NIP10Result { reply: undefined, root: undefined, mentions: [], - profiles: [] + profiles: [], } const eTags: string[][] = [] @@ -41,7 +41,7 @@ export function parse(event: Pick): NIP10Result { if (tag[0] === 'p' && tag[1]) { result.profiles.push({ pubkey: tag[1], - relays: tag[2] ? [tag[2]] : [] + relays: tag[2] ? [tag[2]] : [], }) } } @@ -49,16 +49,11 @@ export function parse(event: Pick): NIP10Result { for (let eTagIndex = 0; eTagIndex < eTags.length; eTagIndex++) { const eTag = eTags[eTagIndex] - const [_, eTagEventId, eTagRelayUrl, eTagMarker] = eTag as [ - string, - string, - undefined | string, - undefined | string - ] + const [_, eTagEventId, eTagRelayUrl, eTagMarker] = eTag as [string, string, undefined | string, undefined | string] const eventPointer: EventPointer = { id: eTagEventId, - relays: eTagRelayUrl ? [eTagRelayUrl] : [] + relays: eTagRelayUrl ? [eTagRelayUrl] : [], } const isFirstETag = eTagIndex === 0 diff --git a/nip13.test.ts b/nip13.test.ts index 85b1ccb..e10e2ff 100644 --- a/nip13.test.ts +++ b/nip13.test.ts @@ -1,4 +1,4 @@ -import {getPow} from './nip13.ts' +import { getPow } from './nip13.ts' test('identifies proof-of-work difficulty', async () => { const id = '000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358' diff --git a/nip13.ts b/nip13.ts index 6adf1d2..654df22 100644 --- a/nip13.ts +++ b/nip13.ts @@ -1,4 +1,4 @@ -import {hexToBytes} from '@noble/hashes/utils' +import { hexToBytes } from '@noble/hashes/utils' /** Get POW difficulty from a Nostr hex ID. */ export function getPow(id: string): number { @@ -34,7 +34,7 @@ function msb(b: number) { } // eslint-disable-next-line no-cond-assign - while (b >>= 1) { + while ((b >>= 1)) { n++ } diff --git a/nip18.test.ts b/nip18.test.ts index 3a01c0f..e813abd 100644 --- a/nip18.test.ts +++ b/nip18.test.ts @@ -1,13 +1,12 @@ -import {finishEvent, Kind} from './event.ts' -import {getPublicKey} from './keys.ts' -import {finishRepostEvent, getRepostedEventPointer, getRepostedEvent} from './nip18.ts' -import {buildEvent} from './test-helpers.ts' +import { finishEvent, Kind } from './event.ts' +import { getPublicKey } from './keys.ts' +import { finishRepostEvent, getRepostedEventPointer, getRepostedEvent } from './nip18.ts' +import { buildEvent } from './test-helpers.ts' const relayUrl = 'https://relay.example.com' describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) @@ -16,30 +15,25 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () => kind: Kind.Text, tags: [ ['e', 'replied event id'], - ['p', 'replied event pubkey'] + ['p', 'replied event pubkey'], ], content: 'Replied to a post', - created_at: 1617932115 + created_at: 1617932115, }, - privateKey + privateKey, ) it('should create a signed event from a minimal template', () => { const template = { - created_at: 1617932115 + created_at: 1617932115, } - const event = finishRepostEvent( - template, - repostedEvent, - relayUrl, - privateKey - ) + const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey) expect(event.kind).toEqual(Kind.Repost) expect(event.tags).toEqual([ ['e', repostedEvent.id, relayUrl], - ['p', repostedEvent.pubkey] + ['p', repostedEvent.pubkey], ]) expect(event.content).toEqual(JSON.stringify(repostedEvent)) expect(event.created_at).toEqual(template.created_at) @@ -62,21 +56,16 @@ describe('finishRepostEvent + getRepostedEventPointer + getRepostedEvent', () => const template = { tags: [['nonstandard', 'tag']], content: '' as const, - created_at: 1617932115 + created_at: 1617932115, } - const event = finishRepostEvent( - template, - repostedEvent, - relayUrl, - privateKey - ) + const event = finishRepostEvent(template, repostedEvent, relayUrl, privateKey) expect(event.kind).toEqual(Kind.Repost) expect(event.tags).toEqual([ ['nonstandard', 'tag'], ['e', repostedEvent.id, relayUrl], - ['p', repostedEvent.pubkey] + ['p', repostedEvent.pubkey], ]) expect(event.content).toEqual('') expect(event.created_at).toEqual(template.created_at) diff --git a/nip18.ts b/nip18.ts index 8b63bb6..3a7e6da 100644 --- a/nip18.ts +++ b/nip18.ts @@ -1,5 +1,5 @@ -import {Event, finishEvent, Kind, verifySignature} from './event.ts' -import {EventPointer} from './nip19.ts' +import { Event, finishEvent, Kind, verifySignature } from './event.ts' +import { EventPointer } from './nip19.ts' export type RepostEventTemplate = { /** @@ -13,7 +13,7 @@ export type RepostEventTemplate = { * Any other content will be ignored and replaced with the stringified JSON of the reposted event. * @default Stringified JSON of the reposted event */ - content?: ''; + content?: '' created_at: number } @@ -24,16 +24,15 @@ export function finishRepostEvent( relayUrl: string, privateKey: string, ): Event { - return finishEvent({ - kind: Kind.Repost, - tags: [ - ...(t.tags ?? []), - [ 'e', reposted.id, relayUrl ], - [ 'p', reposted.pubkey ], - ], - content: t.content === '' ? '' : JSON.stringify(reposted), - created_at: t.created_at, - }, privateKey) + return finishEvent( + { + kind: Kind.Repost, + tags: [...(t.tags ?? []), ['e', reposted.id, relayUrl], ['p', reposted.pubkey]], + content: t.content === '' ? '' : JSON.stringify(reposted), + created_at: t.created_at, + }, + privateKey, + ) } export function getRepostedEventPointer(event: Event): undefined | EventPointer { @@ -61,16 +60,19 @@ export function getRepostedEventPointer(event: Event): undefined | Event return { id: lastETag[1], - relays: [ lastETag[2], lastPTag?.[2] ].filter((x): x is string => typeof x === 'string'), + relays: [lastETag[2], lastPTag?.[2]].filter((x): x is string => typeof x === 'string'), author: lastPTag?.[1], } } export type GetRepostedEventOptions = { - skipVerification?: boolean, -}; + skipVerification?: boolean +} -export function getRepostedEvent(event: Event, { skipVerification }: GetRepostedEventOptions = {}): undefined | Event { +export function getRepostedEvent( + event: Event, + { skipVerification }: GetRepostedEventOptions = {}, +): undefined | Event { const pointer = getRepostedEventPointer(event) if (pointer === undefined || event.content === '') { diff --git a/nip19.test.ts b/nip19.test.ts index 73420b3..472ebb9 100644 --- a/nip19.test.ts +++ b/nip19.test.ts @@ -1,4 +1,4 @@ -import {generatePrivateKey, getPublicKey} from './keys.ts' +import { generatePrivateKey, getPublicKey } from './keys.ts' import { decode, naddrEncode, @@ -14,7 +14,7 @@ test('encode and decode nsec', () => { let sk = generatePrivateKey() let nsec = nsecEncode(sk) expect(nsec).toMatch(/nsec1\w+/) - let {type, data} = decode(nsec) + let { type, data } = decode(nsec) expect(type).toEqual('nsec') expect(data).toEqual(sk) }) @@ -23,20 +23,17 @@ test('encode and decode npub', () => { let pk = getPublicKey(generatePrivateKey()) let npub = npubEncode(pk) expect(npub).toMatch(/npub1\w+/) - let {type, data} = decode(npub) + let { type, data } = decode(npub) expect(type).toEqual('npub') expect(data).toEqual(pk) }) test('encode and decode nprofile', () => { let pk = getPublicKey(generatePrivateKey()) - let relays = [ - 'wss://relay.nostr.example.mydomain.example.com', - 'wss://nostr.banana.com' - ] - let nprofile = nprofileEncode({pubkey: pk, relays}) + let relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com'] + let nprofile = nprofileEncode({ pubkey: pk, relays }) expect(nprofile).toMatch(/nprofile1\w+/) - let {type, data} = decode(nprofile) + let { type, data } = decode(nprofile) expect(type).toEqual('nprofile') const pointer = data as ProfilePointer expect(pointer.pubkey).toEqual(pk) @@ -48,31 +45,24 @@ test('decode nprofile without relays', () => { expect( decode( nprofileEncode({ - pubkey: - '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322', - relays: [] - }) - ).data - ).toHaveProperty( - 'pubkey', - '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322' - ) + pubkey: '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322', + relays: [], + }), + ).data, + ).toHaveProperty('pubkey', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322') }) test('encode and decode naddr', () => { let pk = getPublicKey(generatePrivateKey()) - let relays = [ - 'wss://relay.nostr.example.mydomain.example.com', - 'wss://nostr.banana.com' - ] + let relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com'] let naddr = naddrEncode({ pubkey: pk, relays, kind: 30023, - identifier: 'banana' + identifier: 'banana', }) expect(naddr).toMatch(/naddr1\w+/) - let {type, data} = decode(naddr) + let { type, data } = decode(naddr) expect(type).toEqual('naddr') const pointer = data as AddressPointer expect(pointer.pubkey).toEqual(pk) @@ -83,31 +73,25 @@ test('encode and decode naddr', () => { }) test('decode naddr from habla.news', () => { - let {type, data} = decode( - 'naddr1qq98yetxv4ex2mnrv4esygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2xkpjspsgqqqw4rsf34vl5' + let { type, data } = decode( + 'naddr1qq98yetxv4ex2mnrv4esygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2xkpjspsgqqqw4rsf34vl5', ) expect(type).toEqual('naddr') const pointer = data as AddressPointer - expect(pointer.pubkey).toEqual( - '7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194' - ) + expect(pointer.pubkey).toEqual('7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194') expect(pointer.kind).toEqual(30023) expect(pointer.identifier).toEqual('references') }) test('decode naddr from go-nostr with different TLV ordering', () => { - let {type, data} = decode( - 'naddr1qqrxyctwv9hxzq3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqp65wqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmd0zfmwx' + let { type, data } = decode( + 'naddr1qqrxyctwv9hxzq3q80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsxpqqqp65wqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmd0zfmwx', ) expect(type).toEqual('naddr') const pointer = data as AddressPointer - expect(pointer.pubkey).toEqual( - '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' - ) - expect(pointer.relays).toContain( - 'wss://relay.nostr.example.mydomain.example.com' - ) + expect(pointer.pubkey).toEqual('3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d') + expect(pointer.relays).toContain('wss://relay.nostr.example.mydomain.example.com') expect(pointer.relays).toContain('wss://nostr.banana.com') expect(pointer.kind).toEqual(30023) expect(pointer.identifier).toEqual('banana') @@ -117,7 +101,7 @@ test('encode and decode nrelay', () => { let url = 'wss://relay.nostr.example' let nrelay = nrelayEncode(url) expect(nrelay).toMatch(/nrelay1\w+/) - let {type, data} = decode(nrelay) + let { type, data } = decode(nrelay) expect(type).toEqual('nrelay') expect(data).toEqual(url) }) diff --git a/nip19.ts b/nip19.ts index 8f402f4..fa83ff0 100644 --- a/nip19.ts +++ b/nip19.ts @@ -1,7 +1,7 @@ -import {bytesToHex, concatBytes, hexToBytes} from '@noble/hashes/utils' -import {bech32} from '@scure/base' +import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils' +import { bech32 } from '@scure/base' -import {utf8Decoder, utf8Encoder} from './utils.ts' +import { utf8Decoder, utf8Encoder } from './utils.ts' const Bech32MaxSize = 5000 @@ -9,8 +9,7 @@ const Bech32MaxSize = 5000 * Bech32 regex. * @see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 */ -export const BECH32_REGEX = - /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/ +export const BECH32_REGEX = /[\x21-\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/ export type ProfilePointer = { pubkey: string // hex @@ -52,7 +51,7 @@ export type DecodeResult = { export function decode(nip19: `${Prefix}1${string}`): DecodeValue export function decode(nip19: string): DecodeResult export function decode(nip19: string): DecodeResult { - let {prefix, words} = bech32.decode(nip19, Bech32MaxSize) + let { prefix, words } = bech32.decode(nip19, Bech32MaxSize) let data = new Uint8Array(bech32.fromWords(words)) switch (prefix) { @@ -65,24 +64,23 @@ export function decode(nip19: string): DecodeResult { type: 'nprofile', data: { pubkey: bytesToHex(tlv[0][0]), - relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [] - } + relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [], + }, } } case 'nevent': { let tlv = parseTLV(data) if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nevent') if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes') - if (tlv[2] && tlv[2][0].length !== 32) - throw new Error('TLV 2 should be 32 bytes') + if (tlv[2] && tlv[2][0].length !== 32) throw new Error('TLV 2 should be 32 bytes') return { type: 'nevent', data: { id: bytesToHex(tlv[0][0]), relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [], - author: tlv[2]?.[0] ? bytesToHex(tlv[2][0]) : undefined - } + author: tlv[2]?.[0] ? bytesToHex(tlv[2][0]) : undefined, + }, } } @@ -100,8 +98,8 @@ export function decode(nip19: string): DecodeResult { identifier: utf8Decoder.decode(tlv[0][0]), pubkey: bytesToHex(tlv[2][0]), kind: parseInt(bytesToHex(tlv[3][0]), 16), - relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [] - } + relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [], + }, } } @@ -111,21 +109,21 @@ export function decode(nip19: string): DecodeResult { return { type: 'nrelay', - data: utf8Decoder.decode(tlv[0][0]) + data: utf8Decoder.decode(tlv[0][0]), } } case 'nsec': case 'npub': case 'note': - return {type: prefix, data: bytesToHex(data)} + return { type: prefix, data: bytesToHex(data) } default: throw new Error(`unknown prefix ${prefix}`) } } -type TLV = {[t: number]: Uint8Array[]} +type TLV = { [t: number]: Uint8Array[] } function parseTLV(data: Uint8Array): TLV { let result: TLV = {} @@ -168,7 +166,7 @@ function encodeBytes(prefix: Prefix, hex: string): `${Pre export function nprofileEncode(profile: ProfilePointer): `nprofile1${string}` { let data = encodeTLV({ 0: [hexToBytes(profile.pubkey)], - 1: (profile.relays || []).map(url => utf8Encoder.encode(url)) + 1: (profile.relays || []).map(url => utf8Encoder.encode(url)), }) return encodeBech32('nprofile', data) } @@ -177,7 +175,7 @@ export function neventEncode(event: EventPointer): `nevent1${string}` { let data = encodeTLV({ 0: [hexToBytes(event.id)], 1: (event.relays || []).map(url => utf8Encoder.encode(url)), - 2: event.author ? [hexToBytes(event.author)] : [] + 2: event.author ? [hexToBytes(event.author)] : [], }) return encodeBech32('nevent', data) } @@ -190,14 +188,14 @@ export function naddrEncode(addr: AddressPointer): `naddr1${string}` { 0: [utf8Encoder.encode(addr.identifier)], 1: (addr.relays || []).map(url => utf8Encoder.encode(url)), 2: [hexToBytes(addr.pubkey)], - 3: [new Uint8Array(kind)] + 3: [new Uint8Array(kind)], }) return encodeBech32('naddr', data) } export function nrelayEncode(url: string): `nrelay1${string}` { let data = encodeTLV({ - 0: [utf8Encoder.encode(url)] + 0: [utf8Encoder.encode(url)], }) return encodeBech32('nrelay', data) } diff --git a/nip21.test.ts b/nip21.test.ts index a985170..50be537 100644 --- a/nip21.test.ts +++ b/nip21.test.ts @@ -1,41 +1,23 @@ -import {test as testRegex, parse} from './nip21.ts' +import { test as testRegex, parse } from './nip21.ts' test('test()', () => { - expect( - testRegex( - 'nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6' - ) - ).toBe(true) - expect( - testRegex( - 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' - ) - ).toBe(true) - expect( - testRegex( - ' nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6' - ) - ).toBe(false) + expect(testRegex('nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6')).toBe(true) + expect(testRegex('nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky')).toBe(true) + expect(testRegex(' nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6')).toBe(false) expect(testRegex('nostr:')).toBe(false) - expect( - testRegex( - 'nostr:npub108pv4cg5ag52nQq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6' - ) - ).toBe(false) + expect(testRegex('nostr:npub108pv4cg5ag52nQq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6')).toBe(false) expect(testRegex('gggggg')).toBe(false) }) test('parse', () => { - const result = parse( - 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' - ) + const result = parse('nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky') expect(result).toEqual({ uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', decoded: { type: 'note', - data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b' - } + data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b', + }, }) }) diff --git a/nip21.ts b/nip21.ts index 2ff80f2..3bb0280 100644 --- a/nip21.ts +++ b/nip21.ts @@ -1,14 +1,11 @@ -import {BECH32_REGEX, decode, type DecodeResult} from './nip19.ts' +import { BECH32_REGEX, decode, type DecodeResult } from './nip19.ts' /** Nostr URI regex, eg `nostr:npub1...` */ export const NOSTR_URI_REGEX = new RegExp(`nostr:(${BECH32_REGEX.source})`) /** Test whether the value is a Nostr URI. */ export function test(value: unknown): value is `nostr:${string}` { - return ( - typeof value === 'string' && - new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value) - ) + return typeof value === 'string' && new RegExp(`^${NOSTR_URI_REGEX.source}$`).test(value) } /** Parsed Nostr URI data. */ @@ -28,6 +25,6 @@ export function parse(uri: string): NostrURI { return { uri: match[0] as `nostr:${string}`, value: match[1], - decoded: decode(match[1]) + decoded: decode(match[1]), } } diff --git a/nip25.test.ts b/nip25.test.ts index 7a43ba6..d397b82 100644 --- a/nip25.test.ts +++ b/nip25.test.ts @@ -1,10 +1,9 @@ -import {finishEvent, Kind} from './event.ts' -import {getPublicKey} from './keys.ts' -import {finishReactionEvent, getReactedEventPointer} from './nip25.ts' +import { finishEvent, Kind } from './event.ts' +import { getPublicKey } from './keys.ts' +import { finishReactionEvent, getReactedEventPointer } from './nip25.ts' describe('finishReactionEvent + getReactedEventPointer', () => { - const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' + const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) @@ -13,17 +12,17 @@ describe('finishReactionEvent + getReactedEventPointer', () => { kind: Kind.Text, tags: [ ['e', 'replied event id'], - ['p', 'replied event pubkey'] + ['p', 'replied event pubkey'], ], content: 'Replied to a post', - created_at: 1617932115 + created_at: 1617932115, }, - privateKey + privateKey, ) it('should create a signed event from a minimal template', () => { const template = { - created_at: 1617932115 + created_at: 1617932115, } const event = finishReactionEvent(template, reactedEvent, privateKey) @@ -33,7 +32,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => { ['e', 'replied event id'], ['p', 'replied event pubkey'], ['e', '0ecdbd4dba0652afb19e5f638257a41552a37995a4438ef63de658443f8d16b1'], - ['p', '6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f'] + ['p', '6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f'], ]) expect(event.content).toEqual('+') expect(event.created_at).toEqual(template.created_at) @@ -51,7 +50,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => { const template = { tags: [['nonstandard', 'tag']], content: '👍', - created_at: 1617932115 + created_at: 1617932115, } const event = finishReactionEvent(template, reactedEvent, privateKey) @@ -62,7 +61,7 @@ describe('finishReactionEvent + getReactedEventPointer', () => { ['e', 'replied event id'], ['p', 'replied event pubkey'], ['e', '0ecdbd4dba0652afb19e5f638257a41552a37995a4438ef63de658443f8d16b1'], - ['p', '6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f'] + ['p', '6af0f9de588f2c53cedcba26c5e2402e0d0aa64ec7b47c9f8d97b5bc562bab5f'], ]) expect(event.content).toEqual('👍') expect(event.created_at).toEqual(template.created_at) diff --git a/nip25.ts b/nip25.ts index 2fa3925..5e10c47 100644 --- a/nip25.ts +++ b/nip25.ts @@ -1,6 +1,6 @@ -import {Event, finishEvent, Kind} from './event.ts' +import { Event, finishEvent, Kind } from './event.ts' -import type {EventPointer} from './nip19.ts' +import type { EventPointer } from './nip19.ts' export type ReactionEventTemplate = { /** @@ -21,21 +21,17 @@ export function finishReactionEvent( reacted: Event, privateKey: string, ): Event { - const inheritedTags = reacted.tags.filter( - (tag) => tag.length >= 2 && (tag[0] === 'e' || tag[0] === 'p'), - ) + const inheritedTags = reacted.tags.filter(tag => tag.length >= 2 && (tag[0] === 'e' || tag[0] === 'p')) - return finishEvent({ - ...t, - kind: Kind.Reaction, - tags: [ - ...(t.tags ?? []), - ...inheritedTags, - ['e', reacted.id], - ['p', reacted.pubkey], - ], - content: t.content ?? '+', - }, privateKey) + return finishEvent( + { + ...t, + kind: Kind.Reaction, + tags: [...(t.tags ?? []), ...inheritedTags, ['e', reacted.id], ['p', reacted.pubkey]], + content: t.content ?? '+', + }, + privateKey, + ) } export function getReactedEventPointer(event: Event): undefined | EventPointer { @@ -63,7 +59,7 @@ export function getReactedEventPointer(event: Event): undefined | EventP return { id: lastETag[1], - relays: [ lastETag[2], lastPTag[2] ].filter((x) => x !== undefined), + relays: [lastETag[2], lastPTag[2]].filter(x => x !== undefined), author: lastPTag[1], } } diff --git a/nip26.test.ts b/nip26.test.ts index 23d8644..9bc7020 100644 --- a/nip26.test.ts +++ b/nip26.test.ts @@ -1,13 +1,12 @@ -import {getPublicKey, generatePrivateKey} from './keys.ts' -import {getDelegator, createDelegation} from './nip26.ts' -import {buildEvent} from './test-helpers.ts' +import { getPublicKey, generatePrivateKey } from './keys.ts' +import { getDelegator, createDelegation } from './nip26.ts' +import { buildEvent } from './test-helpers.ts' test('parse good delegation from NIP', async () => { expect( getDelegator({ id: 'a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc', - pubkey: - '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', + pubkey: '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', created_at: 1660896109, kind: 1, tags: [ @@ -15,12 +14,12 @@ test('parse good delegation from NIP', async () => { 'delegation', '86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e', 'kind=1&created_at>1640995200', - 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1' - ] + 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1', + ], ], content: 'Hello world', - sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6' - }) + sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6', + }), ).toEqual('86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e') }) @@ -28,8 +27,7 @@ test('parse bad delegations', async () => { expect( getDelegator({ id: 'a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc', - pubkey: - '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', + pubkey: '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', created_at: 1660896109, kind: 1, tags: [ @@ -37,19 +35,18 @@ test('parse bad delegations', async () => { 'delegation', '86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42f', 'kind=1&created_at>1640995200', - 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1' - ] + 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1', + ], ], content: 'Hello world', - sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6' - }) + sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6', + }), ).toEqual(null) expect( getDelegator({ id: 'a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc', - pubkey: - '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', + pubkey: '62903b1ff41559daf9ee98ef1ae67cc52f301bb5ce26d14baba3052f649c3f49', created_at: 1660896109, kind: 1, tags: [ @@ -57,19 +54,18 @@ test('parse bad delegations', async () => { 'delegation', '86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e', 'kind=1&created_at>1740995200', - 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1' - ] + 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1', + ], ], content: 'Hello world', - sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6' - }) + sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6', + }), ).toEqual(null) expect( getDelegator({ id: 'a080fd288b60ac2225ff2e2d815291bd730911e583e177302cc949a15dc2b2dc', - pubkey: - '62903b1ff41559daf9ee98ef1ae67c152f301bb5ce26d14baba3052f649c3f49', + pubkey: '62903b1ff41559daf9ee98ef1ae67c152f301bb5ce26d14baba3052f649c3f49', created_at: 1660896109, kind: 1, tags: [ @@ -77,12 +73,12 @@ test('parse bad delegations', async () => { 'delegation', '86f0689bd48dcd19c67a19d994f938ee34f251d8c39976290955ff585f2db42e', 'kind=1&created_at>1640995200', - 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1' - ] + 'c33c88ba78ec3c760e49db591ac5f7b129e3887c8af7729795e85a0588007e5ac89b46549232d8f918eefd73e726cb450135314bfda419c030d0b6affe401ec1', + ], ], content: 'Hello world', - sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6' - }) + sig: 'cd4a3cd20dc61dcbc98324de561a07fd23b3d9702115920c0814b5fb822cc5b7c5bcdaf3fa326d24ed50c5b9c8214d66c75bae34e3a84c25e4d122afccb66eb6', + }), ).toEqual(null) }) @@ -91,7 +87,7 @@ test('create and verify delegation', async () => { let pk1 = getPublicKey(sk1) let sk2 = generatePrivateKey() let pk2 = getPublicKey(sk2) - let delegation = createDelegation(sk1, {pubkey: pk2, kind: 1}) + let delegation = createDelegation(sk1, { pubkey: pk2, kind: 1 }) expect(delegation).toHaveProperty('from', pk1) expect(delegation).toHaveProperty('to', pk2) expect(delegation).toHaveProperty('cond', 'kind=1') diff --git a/nip26.ts b/nip26.ts index b9fdfcc..4ba46db 100644 --- a/nip26.ts +++ b/nip26.ts @@ -1,11 +1,11 @@ -import {schnorr} from '@noble/curves/secp256k1' -import {bytesToHex} from '@noble/hashes/utils' -import {sha256} from '@noble/hashes/sha256' +import { schnorr } from '@noble/curves/secp256k1' +import { bytesToHex } from '@noble/hashes/utils' +import { sha256 } from '@noble/hashes/sha256' -import {utf8Encoder} from './utils.ts' -import {getPublicKey} from './keys.ts' +import { utf8Encoder } from './utils.ts' +import { getPublicKey } from './keys.ts' -import type {Event} from './event.ts' +import type { Event } from './event.ts' export type Parameters = { pubkey: string // the key to whom the delegation will be given @@ -21,32 +21,24 @@ export type Delegation = { sig: string } -export function createDelegation( - privateKey: string, - parameters: Parameters -): Delegation { +export function createDelegation(privateKey: string, parameters: Parameters): Delegation { let conditions = [] if ((parameters.kind || -1) >= 0) conditions.push(`kind=${parameters.kind}`) if (parameters.until) conditions.push(`created_at<${parameters.until}`) if (parameters.since) conditions.push(`created_at>${parameters.since}`) let cond = conditions.join('&') - if (cond === '') - throw new Error('refusing to create a delegation without any conditions') + if (cond === '') throw new Error('refusing to create a delegation without any conditions') - let sighash = sha256( - utf8Encoder.encode(`nostr:delegation:${parameters.pubkey}:${cond}`) - ) + let sighash = sha256(utf8Encoder.encode(`nostr:delegation:${parameters.pubkey}:${cond}`)) - let sig = bytesToHex( - schnorr.sign(sighash, privateKey) - ) + let sig = bytesToHex(schnorr.sign(sighash, privateKey)) return { from: getPublicKey(privateKey), to: parameters.pubkey, cond, - sig + sig, } } @@ -65,27 +57,14 @@ export function getDelegator(event: Event): string | null { let [key, operator, value] = conditions[i].split(/\b/) // the supported conditions are just 'kind' and 'created_at' for now - if (key === 'kind' && operator === '=' && event.kind === parseInt(value)) - continue - else if ( - key === 'created_at' && - operator === '<' && - event.created_at < parseInt(value) - ) - continue - else if ( - key === 'created_at' && - operator === '>' && - event.created_at > parseInt(value) - ) - continue + if (key === 'kind' && operator === '=' && event.kind === parseInt(value)) continue + else if (key === 'created_at' && operator === '<' && event.created_at < parseInt(value)) continue + else if (key === 'created_at' && operator === '>' && event.created_at > parseInt(value)) continue else return null // invalid condition } // check signature - let sighash = sha256( - utf8Encoder.encode(`nostr:delegation:${event.pubkey}:${cond}`) - ) + let sighash = sha256(utf8Encoder.encode(`nostr:delegation:${event.pubkey}:${cond}`)) if (!schnorr.verify(sig, sighash, pubkey)) return null return pubkey diff --git a/nip27.test.ts b/nip27.test.ts index 134d23a..18e61aa 100644 --- a/nip27.test.ts +++ b/nip27.test.ts @@ -1,8 +1,8 @@ -import {matchAll, replaceAll} from './nip27.ts' +import { matchAll, replaceAll } from './nip27.ts' test('matchAll', () => { const result = matchAll( - 'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' + 'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', ) expect([...result]).toEqual([ @@ -11,40 +11,40 @@ test('matchAll', () => { value: 'npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6', decoded: { type: 'npub', - data: '79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6' + data: '79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6', }, start: 6, - end: 75 + end: 75, }, { uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', decoded: { type: 'note', - data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b' + data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b', }, start: 78, - end: 147 - } + end: 147, + }, ]) }) test('matchAll with an invalid nip19', () => { const result = matchAll( - 'Hello nostr:npub129tvj896hqqkljerxkccpj9flshwnw999v9uwn9lfmwlj8vnzwgq9y5llnpub1rujdpkd8mwezrvpqd2rx2zphfaztqrtsfg6w3vdnlj!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' + 'Hello nostr:npub129tvj896hqqkljerxkccpj9flshwnw999v9uwn9lfmwlj8vnzwgq9y5llnpub1rujdpkd8mwezrvpqd2rx2zphfaztqrtsfg6w3vdnlj!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', ) expect([...result]).toEqual([ { decoded: { data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b', - type: 'note' + type: 'note', }, end: 193, start: 124, uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', - value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' - } + value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky', + }, ]) }) @@ -52,7 +52,7 @@ test('replaceAll', () => { const content = 'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky' - const result = replaceAll(content, ({decoded, value}) => { + const result = replaceAll(content, ({ decoded, value }) => { switch (decoded.type) { case 'npub': return '@alex' diff --git a/nip27.ts b/nip27.ts index fac7d59..c63804b 100644 --- a/nip27.ts +++ b/nip27.ts @@ -1,5 +1,5 @@ -import {decode} from './nip19.ts' -import {NOSTR_URI_REGEX, type NostrURI} from './nip21.ts' +import { decode } from './nip19.ts' +import { NOSTR_URI_REGEX, type NostrURI } from './nip21.ts' /** Regex to find NIP-21 URIs inside event content. */ export const regex = () => new RegExp(`\\b${NOSTR_URI_REGEX.source}\\b`, 'g') @@ -13,7 +13,7 @@ export interface NostrURIMatch extends NostrURI { } /** Find and decode all NIP-21 URIs. */ -export function * matchAll(content: string): Iterable { +export function* matchAll(content: string): Iterable { const matches = content.matchAll(regex()) for (const match of matches) { @@ -25,7 +25,7 @@ export function * matchAll(content: string): Iterable { value, decoded: decode(value), start: match.index!, - end: match.index! + uri.length + end: match.index! + uri.length, } } catch (_e) { // do nothing @@ -52,15 +52,12 @@ export function * matchAll(content: string): Iterable { * }) * ``` */ -export function replaceAll( - content: string, - replacer: (match: NostrURI) => string -): string { +export function replaceAll(content: string, replacer: (match: NostrURI) => string): string { return content.replaceAll(regex(), (uri, value: string) => { return replacer({ uri: uri as `nostr:${string}`, value, - decoded: decode(value) + decoded: decode(value), }) }) } diff --git a/nip28.test.ts b/nip28.test.ts index a850371..cee94f5 100644 --- a/nip28.test.ts +++ b/nip28.test.ts @@ -1,5 +1,5 @@ -import {Kind} from './event.ts' -import {getPublicKey} from './keys.ts' +import { Kind } from './event.ts' +import { getPublicKey } from './keys.ts' import { channelCreateEvent, channelMetadataEvent, @@ -7,24 +7,23 @@ import { channelHideMessageEvent, channelMuteUserEvent, ChannelMetadata, - ChannelMessageEventTemplate + ChannelMessageEventTemplate, } from './nip28.ts' -const privateKey = - 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' +const privateKey = 'd217c1ff2f8a65c3e3a1740db3b9f58b8c848bb45e26d00ed4714e4a0f4ceecf' const publicKey = getPublicKey(privateKey) describe('NIP-28 Functions', () => { const channelMetadata: ChannelMetadata = { name: 'Test Channel', about: 'This is a test channel', - picture: 'https://example.com/picture.jpg' + picture: 'https://example.com/picture.jpg', } it('channelCreateEvent should create an event with given template', () => { const template = { content: channelMetadata, - created_at: 1617932115 + created_at: 1617932115, } const event = channelCreateEvent(template, privateKey) @@ -37,7 +36,7 @@ describe('NIP-28 Functions', () => { const template = { channel_create_event_id: 'channel creation event id', content: channelMetadata, - created_at: 1617932115 + created_at: 1617932115, } const event = channelMetadataEvent(template, privateKey) @@ -54,17 +53,12 @@ describe('NIP-28 Functions', () => { channel_create_event_id: 'channel creation event id', relay_url: 'https://relay.example.com', content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, } const event = channelMessageEvent(template, privateKey) expect(event.kind).toEqual(Kind.ChannelMessage) - expect(event.tags[0]).toEqual([ - 'e', - template.channel_create_event_id, - template.relay_url, - 'root' - ]) + expect(event.tags[0]).toEqual(['e', template.channel_create_event_id, template.relay_url, 'root']) expect(event.content).toEqual(template.content) expect(event.pubkey).toEqual(publicKey) expect(typeof event.id).toEqual('string') @@ -77,23 +71,13 @@ describe('NIP-28 Functions', () => { reply_to_channel_message_event_id: 'channel message event id', relay_url: 'https://relay.example.com', content: 'Hello, world!', - created_at: 1617932115 + created_at: 1617932115, } const event = channelMessageEvent(template, privateKey) expect(event.kind).toEqual(Kind.ChannelMessage) - expect(event.tags).toContainEqual([ - 'e', - template.channel_create_event_id, - template.relay_url, - 'root' - ]) - expect(event.tags).toContainEqual([ - 'e', - template.reply_to_channel_message_event_id, - template.relay_url, - 'reply' - ]) + expect(event.tags).toContainEqual(['e', template.channel_create_event_id, template.relay_url, 'root']) + expect(event.tags).toContainEqual(['e', template.reply_to_channel_message_event_id, template.relay_url, 'reply']) expect(event.content).toEqual(template.content) expect(event.pubkey).toEqual(publicKey) expect(typeof event.id).toEqual('string') @@ -103,8 +87,8 @@ describe('NIP-28 Functions', () => { it('channelHideMessageEvent should create a signed event with given template', () => { const template = { channel_message_event_id: 'channel message event id', - content: {reason: 'Inappropriate content'}, - created_at: 1617932115 + content: { reason: 'Inappropriate content' }, + created_at: 1617932115, } const event = channelHideMessageEvent(template, privateKey) @@ -118,9 +102,9 @@ describe('NIP-28 Functions', () => { it('channelMuteUserEvent should create a signed event with given template', () => { const template = { - content: {reason: 'Spamming'}, + content: { reason: 'Spamming' }, created_at: 1617932115, - pubkey_to_mute: 'pubkey to mute' + pubkey_to_mute: 'pubkey to mute', } const event = channelMuteUserEvent(template, privateKey) diff --git a/nip28.ts b/nip28.ts index 2d53bc2..fba812f 100644 --- a/nip28.ts +++ b/nip28.ts @@ -1,4 +1,4 @@ -import {Event, finishEvent, Kind} from './event.ts' +import { Event, finishEvent, Kind } from './event.ts' export interface ChannelMetadata { name: string @@ -32,13 +32,13 @@ export interface ChannelMessageEventTemplate { export interface ChannelHideMessageEventTemplate { channel_message_event_id: string - content: string | {reason: string} + content: string | { reason: string } created_at: number tags?: string[][] } export interface ChannelMuteUserEventTemplate { - content: string | {reason: string} + content: string | { reason: string } created_at: number pubkey_to_mute: string tags?: string[][] @@ -46,7 +46,7 @@ export interface ChannelMuteUserEventTemplate { export const channelCreateEvent = ( t: ChannelCreateEventTemplate, - privateKey: string + privateKey: string, ): Event | undefined => { let content: string if (typeof t.content === 'object') { @@ -62,15 +62,15 @@ export const channelCreateEvent = ( kind: Kind.ChannelCreation, tags: [...(t.tags ?? [])], content: content, - created_at: t.created_at + created_at: t.created_at, }, - privateKey + privateKey, ) } export const channelMetadataEvent = ( t: ChannelMetadataEventTemplate, - privateKey: string + privateKey: string, ): Event | undefined => { let content: string if (typeof t.content === 'object') { @@ -86,16 +86,13 @@ export const channelMetadataEvent = ( kind: Kind.ChannelMetadata, tags: [['e', t.channel_create_event_id], ...(t.tags ?? [])], content: content, - created_at: t.created_at + created_at: t.created_at, }, - privateKey + privateKey, ) } -export const channelMessageEvent = ( - t: ChannelMessageEventTemplate, - privateKey: string -): Event => { +export const channelMessageEvent = (t: ChannelMessageEventTemplate, privateKey: string): Event => { const tags = [['e', t.channel_create_event_id, t.relay_url, 'root']] if (t.reply_to_channel_message_event_id) { @@ -107,16 +104,16 @@ export const channelMessageEvent = ( kind: Kind.ChannelMessage, tags: [...tags, ...(t.tags ?? [])], content: t.content, - created_at: t.created_at + created_at: t.created_at, }, - privateKey + privateKey, ) } /* "e" tag should be the kind 42 event to hide */ export const channelHideMessageEvent = ( t: ChannelHideMessageEventTemplate, - privateKey: string + privateKey: string, ): Event | undefined => { let content: string if (typeof t.content === 'object') { @@ -132,15 +129,15 @@ export const channelHideMessageEvent = ( kind: Kind.ChannelHideMessage, tags: [['e', t.channel_message_event_id], ...(t.tags ?? [])], content: content, - created_at: t.created_at + created_at: t.created_at, }, - privateKey + privateKey, ) } export const channelMuteUserEvent = ( t: ChannelMuteUserEventTemplate, - privateKey: string + privateKey: string, ): Event | undefined => { let content: string if (typeof t.content === 'object') { @@ -156,8 +153,8 @@ export const channelMuteUserEvent = ( kind: Kind.ChannelMuteUser, tags: [['p', t.pubkey_to_mute], ...(t.tags ?? [])], content: content, - created_at: t.created_at + created_at: t.created_at, }, - privateKey + privateKey, ) } diff --git a/nip39.test.ts b/nip39.test.ts index ac7142e..ac31a69 100644 --- a/nip39.test.ts +++ b/nip39.test.ts @@ -1,6 +1,6 @@ import fetch from 'node-fetch' -import {useFetchImplementation, validateGithub} from './nip39.ts' +import { useFetchImplementation, validateGithub } from './nip39.ts' test('validate github claim', async () => { useFetchImplementation(fetch) @@ -8,7 +8,7 @@ test('validate github claim', async () => { let result = await validateGithub( 'npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z', 'vitorpamplona', - 'cf19e2d1d7f8dac6348ad37b35ec8421' + 'cf19e2d1d7f8dac6348ad37b35ec8421', ) expect(result).toBe(true) }) diff --git a/nip39.ts b/nip39.ts index 9a94af7..73237f8 100644 --- a/nip39.ts +++ b/nip39.ts @@ -8,19 +8,10 @@ export function useFetchImplementation(fetchImplementation: any) { _fetch = fetchImplementation } -export async function validateGithub( - pubkey: string, - username: string, - proof: string -): Promise { +export async function validateGithub(pubkey: string, username: string, proof: string): Promise { try { - let res = await ( - await _fetch(`https://gist.github.com/${username}/${proof}/raw`) - ).text() - return ( - res === - `Verifying that I control the following Nostr public key: ${pubkey}` - ) + let res = await (await _fetch(`https://gist.github.com/${username}/${proof}/raw`)).text() + return res === `Verifying that I control the following Nostr public key: ${pubkey}` } catch (_) { return false } diff --git a/nip42.test.ts b/nip42.test.ts index 9ee6bee..53adb2b 100644 --- a/nip42.test.ts +++ b/nip42.test.ts @@ -1,23 +1,23 @@ import 'websocket-polyfill' -import {finishEvent} from './event.ts' -import {generatePrivateKey} from './keys.ts' -import {authenticate} from './nip42.ts' -import {relayInit} from './relay.ts' +import { finishEvent } from './event.ts' +import { generatePrivateKey } from './keys.ts' +import { authenticate } from './nip42.ts' +import { relayInit } from './relay.ts' test('auth flow', () => { const relay = relayInit('wss://nostr.kollider.xyz') relay.connect() const sk = generatePrivateKey() - return new Promise((resolve) => { + return new Promise(resolve => { relay.on('auth', async challenge => { await expect( authenticate({ challenge, relay, - sign: (e) => finishEvent(e, sk) - }) + sign: e => finishEvent(e, sk), + }), ).rejects.toBeTruthy() relay.close() resolve() diff --git a/nip42.ts b/nip42.ts index 369545b..167d6e3 100644 --- a/nip42.ts +++ b/nip42.ts @@ -1,5 +1,5 @@ -import {Kind, type EventTemplate, type Event} from './event.ts' -import {Relay} from './relay.ts' +import { Kind, type EventTemplate, type Event } from './event.ts' +import { Relay } from './relay.ts' /** * Authenticate via NIP-42 flow. @@ -13,22 +13,20 @@ import {Relay} from './relay.ts' export const authenticate = async ({ challenge, relay, - sign + sign, }: { challenge: string relay: Relay - sign: ( - e: EventTemplate - ) => Promise> | Event + sign: (e: EventTemplate) => Promise> | Event }): Promise => { const e: EventTemplate = { kind: Kind.ClientAuth, created_at: Math.floor(Date.now() / 1000), tags: [ ['relay', relay.url], - ['challenge', challenge] + ['challenge', challenge], ], - content: '' + content: '', } return relay.auth(await sign(e)) } diff --git a/nip44.test.ts b/nip44.test.ts index 1bf348b..a9a89e6 100644 --- a/nip44.test.ts +++ b/nip44.test.ts @@ -1,8 +1,8 @@ import crypto from 'node:crypto' -import {hexToBytes} from '@noble/hashes/utils' +import { hexToBytes } from '@noble/hashes/utils' -import {encrypt, decrypt, getSharedSecret} from './nip44.ts' -import {getPublicKey, generatePrivateKey} from './keys.ts' +import { encrypt, decrypt, getSharedSecret } from './nip44.ts' +import { getPublicKey, generatePrivateKey } from './keys.ts' // @ts-ignore // eslint-disable-next-line no-undef diff --git a/nip44.ts b/nip44.ts index d526d2a..dd75f85 100644 --- a/nip44.ts +++ b/nip44.ts @@ -1,10 +1,10 @@ -import {base64} from '@scure/base' -import {randomBytes} from '@noble/hashes/utils' -import {secp256k1} from '@noble/curves/secp256k1' -import {sha256} from '@noble/hashes/sha256' -import {xchacha20} from '@noble/ciphers/chacha' +import { base64 } from '@scure/base' +import { randomBytes } from '@noble/hashes/utils' +import { secp256k1 } from '@noble/curves/secp256k1' +import { sha256 } from '@noble/hashes/sha256' +import { xchacha20 } from '@noble/ciphers/chacha' -import {utf8Decoder, utf8Encoder} from './utils.ts' +import { utf8Decoder, utf8Encoder } from './utils.ts' export const getSharedSecret = (privkey: string, pubkey: string): Uint8Array => sha256(secp256k1.getSharedSecret(privkey, '02' + pubkey).subarray(1, 33)) diff --git a/nip57.test.ts b/nip57.test.ts index 9df9883..4990f07 100644 --- a/nip57.test.ts +++ b/nip57.test.ts @@ -1,17 +1,11 @@ -import {finishEvent} from './event.ts' -import {getPublicKey, generatePrivateKey} from './keys.ts' -import { - getZapEndpoint, - makeZapReceipt, - makeZapRequest, - useFetchImplementation, - validateZapRequest, -} from './nip57.ts' -import {buildEvent} from './test-helpers.ts' +import { finishEvent } from './event.ts' +import { getPublicKey, generatePrivateKey } from './keys.ts' +import { getZapEndpoint, makeZapReceipt, makeZapRequest, useFetchImplementation, validateZapRequest } from './nip57.ts' +import { buildEvent } from './test-helpers.ts' describe('getZapEndpoint', () => { test('returns null if neither lud06 nor lud16 is present', async () => { - const metadata = buildEvent({kind: 0, content: '{}'}) + const metadata = buildEvent({ kind: 0, content: '{}' }) const result = await getZapEndpoint(metadata) expect(result).toBeNull() @@ -21,28 +15,22 @@ describe('getZapEndpoint', () => { const fetchImplementation = jest.fn(() => Promise.reject(new Error())) useFetchImplementation(fetchImplementation) - const metadata = buildEvent({kind: 0, content: '{"lud16": "name@domain"}'}) + const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' }) const result = await getZapEndpoint(metadata) expect(result).toBeNull() - expect(fetchImplementation).toHaveBeenCalledWith( - 'https://domain/.well-known/lnurlp/name' - ) + expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name') }) test('returns null if the response does not allow Nostr payments', async () => { - const fetchImplementation = jest.fn(() => - Promise.resolve({json: () => ({allowsNostr: false})}) - ) + const fetchImplementation = jest.fn(() => Promise.resolve({ json: () => ({ allowsNostr: false }) })) useFetchImplementation(fetchImplementation) - const metadata = buildEvent({kind: 0, content: '{"lud16": "name@domain"}'}) + const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' }) const result = await getZapEndpoint(metadata) expect(result).toBeNull() - expect(fetchImplementation).toHaveBeenCalledWith( - 'https://domain/.well-known/lnurlp/name' - ) + expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name') }) test('returns the callback URL if the response allows Nostr payments', async () => { @@ -51,19 +39,17 @@ describe('getZapEndpoint', () => { json: () => ({ allowsNostr: true, nostrPubkey: 'pubkey', - callback: 'callback' - }) - }) + callback: 'callback', + }), + }), ) useFetchImplementation(fetchImplementation) - const metadata = buildEvent({kind: 0, content: '{"lud16": "name@domain"}'}) + const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' }) const result = await getZapEndpoint(metadata) expect(result).toBe('callback') - expect(fetchImplementation).toHaveBeenCalledWith( - 'https://domain/.well-known/lnurlp/name' - ) + expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name') }) }) @@ -75,8 +61,8 @@ describe('makeZapRequest', () => { profile: 'profile', event: null, relays: [], - comment: '' - }) + comment: '', + }), ).toThrow() }) @@ -87,8 +73,8 @@ describe('makeZapRequest', () => { event: null, amount: 100, relays: [], - comment: '' - }) + comment: '', + }), ).toThrow() }) @@ -98,7 +84,7 @@ describe('makeZapRequest', () => { event: 'event', amount: 100, relays: ['relay1', 'relay2'], - comment: 'comment' + comment: 'comment', }) expect(result.kind).toBe(9734) expect(result.created_at).toBeCloseTo(Date.now() / 1000, 0) @@ -107,8 +93,8 @@ describe('makeZapRequest', () => { expect.arrayContaining([ ['p', 'profile'], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ]) + ['relays', 'relay1', 'relay2'], + ]), ) expect(result.tags).toContainEqual(['e', 'event']) }) @@ -116,9 +102,7 @@ describe('makeZapRequest', () => { describe('validateZapRequest', () => { test('returns an error message for invalid JSON', () => { - expect(validateZapRequest('invalid JSON')).toBe( - 'Invalid zap request JSON.' - ) + expect(validateZapRequest('invalid JSON')).toBe('Invalid zap request JSON.') }) test('returns an error message if the Zap request is not a valid Nostr event', () => { @@ -129,13 +113,11 @@ describe('validateZapRequest', () => { tags: [ ['p', 'profile'], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], } - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - 'Zap request is not a valid Nostr event.' - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe('Zap request is not a valid Nostr event.') }) test('returns an error message if the signature on the Zap request is invalid', () => { @@ -150,13 +132,11 @@ describe('validateZapRequest', () => { tags: [ ['p', publicKey], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], } - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - 'Invalid signature on zap request.' - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe('Invalid signature on zap request.') }) test('returns an error message if the Zap request does not have a "p" tag', () => { @@ -169,15 +149,13 @@ describe('validateZapRequest', () => { content: 'content', tags: [ ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey + privateKey, ) - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - "Zap request doesn't have a 'p' tag." - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe("Zap request doesn't have a 'p' tag.") }) test('returns an error message if the "p" tag on the Zap request is not valid hex', () => { @@ -191,15 +169,13 @@ describe('validateZapRequest', () => { tags: [ ['p', 'invalid hex'], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey + privateKey, ) - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - "Zap request 'p' tag is not valid hex." - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe("Zap request 'p' tag is not valid hex.") }) test('returns an error message if the "e" tag on the Zap request is not valid hex', () => { @@ -215,15 +191,13 @@ describe('validateZapRequest', () => { ['p', publicKey], ['e', 'invalid hex'], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey + privateKey, ) - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - "Zap request 'e' tag is not valid hex." - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe("Zap request 'e' tag is not valid hex.") }) test('returns an error message if the Zap request does not have a relays tag', () => { @@ -237,15 +211,13 @@ describe('validateZapRequest', () => { content: 'content', tags: [ ['p', publicKey], - ['amount', '100'] - ] + ['amount', '100'], + ], }, - privateKey + privateKey, ) - expect(validateZapRequest(JSON.stringify(zapRequest))).toBe( - "Zap request doesn't have a 'relays' tag." - ) + expect(validateZapRequest(JSON.stringify(zapRequest))).toBe("Zap request doesn't have a 'relays' tag.") }) test('returns null for a valid Zap request', () => { @@ -260,10 +232,10 @@ describe('validateZapRequest', () => { tags: [ ['p', publicKey], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey + privateKey, ) expect(validateZapRequest(JSON.stringify(zapRequest))).toBeNull() @@ -284,17 +256,17 @@ describe('makeZapReceipt', () => { tags: [ ['p', publicKey], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey - ) + privateKey, + ), ) const preimage = 'preimage' const bolt11 = 'bolt11' const paidAt = new Date() - const result = makeZapReceipt({zapRequest, preimage, bolt11, paidAt}) + const result = makeZapReceipt({ zapRequest, preimage, bolt11, paidAt }) expect(result.kind).toBe(9735) expect(result.created_at).toBeCloseTo(paidAt.getTime() / 1000, 0) @@ -318,16 +290,16 @@ describe('makeZapReceipt', () => { tags: [ ['p', publicKey], ['amount', '100'], - ['relays', 'relay1', 'relay2'] - ] + ['relays', 'relay1', 'relay2'], + ], }, - privateKey - ) + privateKey, + ), ) const bolt11 = 'bolt11' const paidAt = new Date() - const result = makeZapReceipt({zapRequest, bolt11, paidAt}) + const result = makeZapReceipt({ zapRequest, bolt11, paidAt }) expect(result.kind).toBe(9735) expect(result.created_at).toBeCloseTo(paidAt.getTime() / 1000, 0) diff --git a/nip57.ts b/nip57.ts index 6c424f9..94fd488 100644 --- a/nip57.ts +++ b/nip57.ts @@ -1,13 +1,7 @@ -import {bech32} from '@scure/base' +import { bech32 } from '@scure/base' -import { - Kind, - validateEvent, - verifySignature, - type Event, - type EventTemplate, -} from './event.ts' -import {utf8Decoder} from './utils.ts' +import { Kind, validateEvent, verifySignature, type Event, type EventTemplate } from './event.ts' +import { utf8Decoder } from './utils.ts' var _fetch: any @@ -19,14 +13,12 @@ export function useFetchImplementation(fetchImplementation: any) { _fetch = fetchImplementation } -export async function getZapEndpoint( - metadata: Event -): Promise { +export async function getZapEndpoint(metadata: Event): Promise { try { let lnurl: string = '' - let {lud06, lud16} = JSON.parse(metadata.content) + let { lud06, lud16 } = JSON.parse(metadata.content) if (lud06) { - let {words} = bech32.decode(lud06, 1000) + let { words } = bech32.decode(lud06, 1000) let data = bech32.fromWords(words) lnurl = utf8Decoder.decode(data) } else if (lud16) { @@ -54,7 +46,7 @@ export function makeZapRequest({ event, amount, relays, - comment = '' + comment = '', }: { profile: string event: string | null @@ -72,8 +64,8 @@ export function makeZapRequest({ tags: [ ['p', profile], ['amount', amount.toString()], - ['relays', ...relays] - ] + ['relays', ...relays], + ], } if (event) { @@ -92,19 +84,16 @@ export function validateZapRequest(zapRequestString: string): string | null { return 'Invalid zap request JSON.' } - if (!validateEvent(zapRequest)) - return 'Zap request is not a valid Nostr event.' + if (!validateEvent(zapRequest)) return 'Zap request is not a valid Nostr event.' if (!verifySignature(zapRequest)) return 'Invalid signature on zap request.' let p = zapRequest.tags.find(([t, v]) => t === 'p' && v) if (!p) return "Zap request doesn't have a 'p' tag." - if (!p[1].match(/^[a-f0-9]{64}$/)) - return "Zap request 'p' tag is not valid hex." + if (!p[1].match(/^[a-f0-9]{64}$/)) return "Zap request 'p' tag is not valid hex." let e = zapRequest.tags.find(([t, v]) => t === 'e' && v) - if (e && !e[1].match(/^[a-f0-9]{64}$/)) - return "Zap request 'e' tag is not valid hex." + if (e && !e[1].match(/^[a-f0-9]{64}$/)) return "Zap request 'e' tag is not valid hex." let relays = zapRequest.tags.find(([t, v]) => t === 'relays' && v) if (!relays) return "Zap request doesn't have a 'relays' tag." @@ -116,7 +105,7 @@ export function makeZapReceipt({ zapRequest, preimage, bolt11, - paidAt + paidAt, }: { zapRequest: string preimage?: string @@ -124,19 +113,13 @@ export function makeZapReceipt({ paidAt: Date }): EventTemplate { let zr: Event = JSON.parse(zapRequest) - let tagsFromZapRequest = zr.tags.filter( - ([t]) => t === 'e' || t === 'p' || t === 'a' - ) + let tagsFromZapRequest = zr.tags.filter(([t]) => t === 'e' || t === 'p' || t === 'a') let zap: EventTemplate = { kind: 9735, created_at: Math.round(paidAt.getTime() / 1000), content: '', - tags: [ - ...tagsFromZapRequest, - ['bolt11', bolt11], - ['description', zapRequest] - ] + tags: [...tagsFromZapRequest, ['bolt11', bolt11], ['description', zapRequest]], } if (preimage) { diff --git a/nip98.test.ts b/nip98.test.ts index 034f8f1..5a637a1 100644 --- a/nip98.test.ts +++ b/nip98.test.ts @@ -1,14 +1,12 @@ -import {getToken, unpackEventFromToken, validateEvent, validateToken} from './nip98.ts' -import {Event, Kind, finishEvent} from './event.ts' -import {generatePrivateKey, getPublicKey} from './keys.ts' +import { getToken, unpackEventFromToken, validateEvent, validateToken } from './nip98.ts' +import { Event, Kind, finishEvent } from './event.ts' +import { generatePrivateKey, getPublicKey } from './keys.ts' const sk = generatePrivateKey() describe('getToken', () => { test('getToken GET returns without authorization scheme', async () => { - let result = await getToken('http://test.com', 'get', e => - finishEvent(e, sk) - ) + let result = await getToken('http://test.com', 'get', e => finishEvent(e, sk)) const decodedResult: Event = await unpackEventFromToken(result) @@ -18,14 +16,12 @@ describe('getToken', () => { expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.tags).toStrictEqual([ ['u', 'http://test.com'], - ['method', 'get'] + ['method', 'get'], ]) }) test('getToken POST returns token without authorization scheme', async () => { - let result = await getToken('http://test.com', 'post', e => - finishEvent(e, sk) - ) + let result = await getToken('http://test.com', 'post', e => finishEvent(e, sk)) const decodedResult: Event = await unpackEventFromToken(result) @@ -35,19 +31,14 @@ describe('getToken', () => { expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.tags).toStrictEqual([ ['u', 'http://test.com'], - ['method', 'post'] + ['method', 'post'], ]) }) test('getToken GET returns token WITH authorization scheme', async () => { const authorizationScheme = 'Nostr ' - let result = await getToken( - 'http://test.com', - 'post', - e => finishEvent(e, sk), - true - ) + let result = await getToken('http://test.com', 'post', e => finishEvent(e, sk), true) expect(result.startsWith(authorizationScheme)).toBe(true) @@ -59,7 +50,7 @@ describe('getToken', () => { expect(decodedResult.pubkey).toBe(getPublicKey(sk)) expect(decodedResult.tags).toStrictEqual([ ['u', 'http://test.com'], - ['method', 'post'] + ['method', 'post'], ]) }) @@ -81,21 +72,14 @@ describe('getToken', () => { describe('validateToken', () => { test('validateToken returns true for valid token without authorization scheme', async () => { - const validToken = await getToken('http://test.com', 'get', e => - finishEvent(e, sk) - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk)) const result = await validateToken(validToken, 'http://test.com', 'get') expect(result).toBe(true) }) test('validateToken returns true for valid token with authorization scheme', async () => { - const validToken = await getToken( - 'http://test.com', - 'get', - e => finishEvent(e, sk), - true - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk), true) const result = await validateToken(validToken, 'http://test.com', 'get') expect(result).toBe(true) @@ -112,30 +96,21 @@ describe('validateToken', () => { }) test('validateToken throws an error for a wrong url', async () => { - const validToken = await getToken('http://test.com', 'get', e => - finishEvent(e, sk) - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk)) const result = validateToken(validToken, 'http://wrong-test.com', 'get') await expect(result).rejects.toThrow(Error) }) test('validateToken throws an error for a wrong method', async () => { - const validToken = await getToken('http://test.com', 'get', e => - finishEvent(e, sk) - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk)) const result = validateToken(validToken, 'http://test.com', 'post') await expect(result).rejects.toThrow(Error) }) test('validateEvent returns true for valid decoded token with authorization scheme', async () => { - const validToken = await getToken( - 'http://test.com', - 'get', - e => finishEvent(e, sk), - true - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk), true) const decodedResult: Event = await unpackEventFromToken(validToken) const result = await validateEvent(decodedResult, 'http://test.com', 'get') @@ -143,12 +118,7 @@ describe('validateToken', () => { }) test('validateEvent throws an error for a wrong url', async () => { - const validToken = await getToken( - 'http://test.com', - 'get', - e => finishEvent(e, sk), - true - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk), true) const decodedResult: Event = await unpackEventFromToken(validToken) const result = validateEvent(decodedResult, 'http://wrong-test.com', 'get') @@ -156,12 +126,7 @@ describe('validateToken', () => { }) test('validateEvent throws an error for a wrong method', async () => { - const validToken = await getToken( - 'http://test.com', - 'get', - e => finishEvent(e, sk), - true - ) + const validToken = await getToken('http://test.com', 'get', e => finishEvent(e, sk), true) const decodedResult: Event = await unpackEventFromToken(validToken) const result = validateEvent(decodedResult, 'http://test.com', 'post') diff --git a/nip98.ts b/nip98.ts index f7a79e7..3e78ec6 100644 --- a/nip98.ts +++ b/nip98.ts @@ -1,12 +1,6 @@ -import {base64} from '@scure/base' -import { - Event, - EventTemplate, - Kind, - getBlankEvent, - verifySignature -} from './event' -import {utf8Decoder, utf8Encoder} from './utils' +import { base64 } from '@scure/base' +import { Event, EventTemplate, Kind, getBlankEvent, verifySignature } from './event' +import { utf8Decoder, utf8Encoder } from './utils' const _authorizationScheme = 'Nostr ' @@ -20,31 +14,23 @@ const _authorizationScheme = 'Nostr ' export async function getToken( loginUrl: string, httpMethod: string, - sign: ( - e: EventTemplate - ) => Promise> | Event, - includeAuthorizationScheme: boolean = false + sign: (e: EventTemplate) => Promise> | Event, + includeAuthorizationScheme: boolean = false, ): Promise { - if (!loginUrl || !httpMethod) - throw new Error('Missing loginUrl or httpMethod') + if (!loginUrl || !httpMethod) throw new Error('Missing loginUrl or httpMethod') const event = getBlankEvent(Kind.HttpAuth) event.tags = [ ['u', loginUrl], - ['method', httpMethod] + ['method', httpMethod], ] event.created_at = Math.round(new Date().getTime() / 1000) const signedEvent = await sign(event) - const authorizationScheme = includeAuthorizationScheme - ? _authorizationScheme - : '' - return ( - authorizationScheme + - base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent))) - ) + const authorizationScheme = includeAuthorizationScheme ? _authorizationScheme : '' + return authorizationScheme + base64.encode(utf8Encoder.encode(JSON.stringify(signedEvent))) } /** @@ -53,13 +39,13 @@ export async function getToken( * @example * await nip98.validateToken('Nostr base64token', 'https://example.com/login', 'post') */ -export async function validateToken( - token: string, - url: string, - method: string -): Promise { - const event = await unpackEventFromToken(token).catch((error) => { throw(error) }) - const valid = await validateEvent(event, url, method).catch((error) => { throw(error) }) +export async function validateToken(token: string, url: string, method: string): Promise { + const event = await unpackEventFromToken(token).catch(error => { + throw error + }) + const valid = await validateEvent(event, url, method).catch(error => { + throw error + }) return valid } @@ -80,11 +66,7 @@ export async function unpackEventFromToken(token: string): Promise { return event } -export async function validateEvent( - event: Event, - url: string, - method: string -): Promise { +export async function validateEvent(event: Event, url: string, method: string): Promise { if (!event) { throw new Error('Invalid nostr event') } @@ -110,10 +92,7 @@ export async function validateEvent( } const methodTag = event.tags.find(t => t[0] === 'method') - if ( - methodTag?.length !== 1 && - methodTag?.[1].toLowerCase() !== method.toLowerCase() - ) { + if (methodTag?.length !== 1 && methodTag?.[1].toLowerCase() !== method.toLowerCase()) { throw new Error('Invalid nostr event, method tag invalid') } diff --git a/pool.test.ts b/pool.test.ts index c6de68e..6c1d59f 100644 --- a/pool.test.ts +++ b/pool.test.ts @@ -1,8 +1,8 @@ import 'websocket-polyfill' -import {finishEvent, type Event} from './event.ts' -import {generatePrivateKey, getPublicKey} from './keys.ts' -import {SimplePool} from './pool.ts' +import { finishEvent, type Event } from './event.ts' +import { generatePrivateKey, getPublicKey } from './keys.ts' +import { SimplePool } from './pool.ts' let pool = new SimplePool() @@ -11,23 +11,18 @@ let relays = [ 'wss://relay.nostr.bg/', 'wss://nostr.fmt.wiz.biz/', 'wss://relay.nostr.band/', - 'wss://nos.lol/' + 'wss://nos.lol/', ] afterAll(() => { - pool.close([ - ...relays, - 'wss://nostr.wine', - 'wss://offchain.pub', - 'wss://eden.nostr.land' - ]) + pool.close([...relays, 'wss://nostr.wine', 'wss://offchain.pub', 'wss://eden.nostr.land']) }) test('removing duplicates when querying', async () => { let priv = generatePrivateKey() let pub = getPublicKey(priv) - let sub = pool.sub(relays, [{authors: [pub]}]) + let sub = pool.sub(relays, [{ authors: [pub] }]) let received: Event[] = [] sub.on('event', event => { @@ -37,12 +32,15 @@ test('removing duplicates when querying', async () => { received.push(event) }) - let event = finishEvent({ - created_at: Math.round(Date.now() / 1000), - content: 'test', - kind: 22345, - tags: [] - }, priv) + let event = finishEvent( + { + created_at: Math.round(Date.now() / 1000), + content: 'test', + kind: 22345, + tags: [], + }, + priv, + ) pool.publish(relays, event) @@ -55,8 +53,8 @@ test('same with double querying', async () => { let priv = generatePrivateKey() let pub = getPublicKey(priv) - let sub1 = pool.sub(relays, [{authors: [pub]}]) - let sub2 = pool.sub(relays, [{authors: [pub]}]) + let sub1 = pool.sub(relays, [{ authors: [pub] }]) + let sub2 = pool.sub(relays, [{ authors: [pub] }]) let received: Event[] = [] @@ -68,12 +66,15 @@ test('same with double querying', async () => { received.push(event) }) - let event = finishEvent({ - created_at: Math.round(Date.now() / 1000), - content: 'test2', - kind: 22346, - tags: [] - }, priv) + let event = finishEvent( + { + created_at: Math.round(Date.now() / 1000), + content: 'test2', + kind: 22346, + tags: [], + }, + priv, + ) pool.publish(relays, event) @@ -84,13 +85,10 @@ test('same with double querying', async () => { test('get()', async () => { let event = await pool.get(relays, { - ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] + ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'], }) - expect(event).toHaveProperty( - 'id', - 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027' - ) + expect(event).toHaveProperty('id', 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027') }) test('list()', async () => { @@ -98,13 +96,11 @@ test('list()', async () => { [...relays, 'wss://offchain.pub', 'wss://eden.nostr.land'], [ { - authors: [ - '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' - ], + authors: ['3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'], kinds: [1], - limit: 2 - } - ] + limit: 2, + }, + ], ) // the actual received number will be greater than 2, but there will be no duplicates @@ -112,27 +108,21 @@ test('list()', async () => { events .map(evt => evt.id) // @ts-ignore ??? - .reduce((acc, n) => (acc.indexOf(n) !== -1 ? acc : [...acc, n]), []) - .length + .reduce((acc, n) => (acc.indexOf(n) !== -1 ? acc : [...acc, n]), []).length, ) - let relaysForAllEvents = events - .map(event => pool.seenOn(event.id)) - .reduce((acc, n) => acc.concat(n), []) + let relaysForAllEvents = events.map(event => pool.seenOn(event.id)).reduce((acc, n) => acc.concat(n), []) expect(relaysForAllEvents.length).toBeGreaterThanOrEqual(events.length) }) test('seenOnEnabled: false', async () => { - const poolWithoutSeenOn = new SimplePool({seenOnEnabled: false}) + const poolWithoutSeenOn = new SimplePool({ seenOnEnabled: false }) const event = await poolWithoutSeenOn.get(relays, { - ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] + ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'], }) - expect(event).toHaveProperty( - 'id', - 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027' - ) + expect(event).toHaveProperty('id', 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027') const relaysForEvent = poolWithoutSeenOn.seenOn(event!.id) diff --git a/pool.ts b/pool.ts index 990b9e0..7076360 100644 --- a/pool.ts +++ b/pool.ts @@ -1,13 +1,8 @@ -import { - relayInit, - type Relay, - type Sub, - type SubscriptionOptions -} from './relay.ts' -import {normalizeURL} from './utils.ts' +import { relayInit, type Relay, type Sub, type SubscriptionOptions } from './relay.ts' +import { normalizeURL } from './utils.ts' -import type {Event} from './event.ts' -import {matchFilters, type Filter} from './filter.ts' +import type { Event } from './event.ts' +import { matchFilters, type Filter } from './filter.ts' type BatchedRequest = { filters: Filter[] @@ -17,9 +12,9 @@ type BatchedRequest = { } export class SimplePool { - private _conn: {[url: string]: Relay} - private _seenOn: {[id: string]: Set} = {} // a map of all events we've seen in each relay - private batchedByKey: {[batchKey: string]: BatchedRequest[]} = {} + private _conn: { [url: string]: Relay } + private _seenOn: { [id: string]: Set } = {} // a map of all events we've seen in each relay + private batchedByKey: { [batchKey: string]: BatchedRequest[] } = {} private eoseSubTimeout: number private getTimeout: number @@ -32,7 +27,7 @@ export class SimplePool { getTimeout?: number seenOnEnabled?: boolean batchInterval?: number - } = {} + } = {}, ) { this._conn = {} this.eoseSubTimeout = options.eoseSubTimeout || 3400 @@ -54,7 +49,7 @@ export class SimplePool { if (!this._conn[nm]) { this._conn[nm] = relayInit(nm, { getTimeout: this.getTimeout * 0.9, - listTimeout: this.getTimeout * 0.9 + listTimeout: this.getTimeout * 0.9, }) } @@ -63,13 +58,9 @@ export class SimplePool { return relay } - sub( - relays: string[], - filters: Filter[], - opts?: SubscriptionOptions - ): Sub { + sub(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Sub { let _knownIds: Set = new Set() - let modifiedOpts = {...(opts || {})} + let modifiedOpts = { ...(opts || {}) } modifiedOpts.alreadyHaveEvent = (id, url) => { if (opts?.alreadyHaveEvent?.(id, url)) { return true @@ -142,9 +133,8 @@ export class SimplePool { off(type, cb) { if (type === 'event') { eventListeners.delete(cb) - } else if (type === 'eose') - eoseListeners.delete(cb as () => void | Promise) - } + } else if (type === 'eose') eoseListeners.delete(cb as () => void | Promise) + }, } return greaterSub @@ -153,7 +143,7 @@ export class SimplePool { get( relays: string[], filter: Filter, - opts?: SubscriptionOptions + opts?: SubscriptionOptions, ): Promise | null> { return new Promise(resolve => { let sub = this.sub(relays, [filter], opts) @@ -172,7 +162,7 @@ export class SimplePool { list( relays: string[], filters: Filter[], - opts?: SubscriptionOptions + opts?: SubscriptionOptions, ): Promise[]> { return new Promise(resolve => { let events: Event[] = [] @@ -193,7 +183,7 @@ export class SimplePool { batchedList( batchKey: string, relays: string[], - filters: Filter[] + filters: Filter[], ): Promise[]> { return new Promise(resolve => { if (!this.batchedByKey[batchKey]) { @@ -202,8 +192,8 @@ export class SimplePool { filters, relays, resolve, - events: [] - } + events: [], + }, ] setTimeout(() => { @@ -219,9 +209,7 @@ export class SimplePool { const sub = this.sub(relays, filters) sub.on('event', event => { - batchedRequests.forEach( - br => matchFilters(br.filters, event) && br.events.push(event) - ) + batchedRequests.forEach(br => matchFilters(br.filters, event) && br.events.push(event)) }) sub.on('eose', () => { sub.unsub() @@ -236,7 +224,7 @@ export class SimplePool { filters, relays, resolve, - events: [] + events: [], }) } }) diff --git a/references.test.ts b/references.test.ts index 038acdc..1b2a0b8 100644 --- a/references.test.ts +++ b/references.test.ts @@ -1,25 +1,12 @@ -import {parseReferences} from './references.ts' -import {buildEvent} from './test-helpers.ts' +import { parseReferences } from './references.ts' +import { buildEvent } from './test-helpers.ts' test('parse mentions', () => { let evt = buildEvent({ tags: [ - [ - 'p', - 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', - 'wss://nostr.com' - ], - [ - 'e', - 'a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33', - 'wss://other.com', - 'reply' - ], - [ - 'e', - '31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8', - '' - ] + ['p', 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', 'wss://nostr.com'], + ['e', 'a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33', 'wss://other.com', 'reply'], + ['e', '31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8', ''], ], content: 'hello #[0], have you seen #[2]? it was made by nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg on nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4! broken #[3]', @@ -29,33 +16,31 @@ test('parse mentions', () => { { text: '#[0]', profile: { - pubkey: - 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', - relays: ['wss://nostr.com'] - } + pubkey: 'c9d556c6d3978d112d30616d0d20aaa81410e3653911dd67787b5aaf9b36ade8', + relays: ['wss://nostr.com'], + }, }, { text: '#[2]', event: { id: '31d7c2875b5fc8e6f9c8f9dc1f84de1b6b91d1947ea4c59225e55c325d330fa8', - relays: [] - } + relays: [], + }, }, { text: 'nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg', profile: { - pubkey: - 'cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393', - relays: [] - } + pubkey: 'cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393', + relays: [], + }, }, { text: 'nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4', event: { id: 'cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393', relays: [], - author: undefined - } - } + author: undefined, + }, + }, ]) }) diff --git a/references.ts b/references.ts index b548ee2..2ceb511 100644 --- a/references.ts +++ b/references.ts @@ -1,11 +1,6 @@ -import { - decode, - type AddressPointer, - type ProfilePointer, - type EventPointer, -} from './nip19.ts' +import { decode, type AddressPointer, type ProfilePointer, type EventPointer } from './nip19.ts' -import type {Event} from './event.ts' +import type { Event } from './event.ts' type Reference = { text: string @@ -14,8 +9,7 @@ type Reference = { address?: AddressPointer } -const mentionRegex = - /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g +const mentionRegex = /\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b|#\[(\d+)\]/g export function parseReferences(evt: Event): Reference[] { let references: Reference[] = [] @@ -23,40 +17,40 @@ export function parseReferences(evt: Event): Reference[] { if (ref[2]) { // it's a NIP-27 mention try { - let {type, data} = decode(ref[1]) + let { type, data } = decode(ref[1]) switch (type) { case 'npub': { references.push({ text: ref[0], - profile: {pubkey: data as string, relays: []} + profile: { pubkey: data as string, relays: [] }, }) break } case 'nprofile': { references.push({ text: ref[0], - profile: data as ProfilePointer + profile: data as ProfilePointer, }) break } case 'note': { references.push({ text: ref[0], - event: {id: data as string, relays: []} + event: { id: data as string, relays: [] }, }) break } case 'nevent': { references.push({ text: ref[0], - event: data as EventPointer + event: data as EventPointer, }) break } case 'naddr': { references.push({ text: ref[0], - address: data as AddressPointer + address: data as AddressPointer, }) break } @@ -74,14 +68,14 @@ export function parseReferences(evt: Event): Reference[] { case 'p': { references.push({ text: ref[0], - profile: {pubkey: tag[1], relays: tag[2] ? [tag[2]] : []} + profile: { pubkey: tag[1], relays: tag[2] ? [tag[2]] : [] }, }) break } case 'e': { references.push({ text: ref[0], - event: {id: tag[1], relays: tag[2] ? [tag[2]] : []} + event: { id: tag[1], relays: tag[2] ? [tag[2]] : [] }, }) break } @@ -94,8 +88,8 @@ export function parseReferences(evt: Event): Reference[] { identifier, pubkey, kind: parseInt(kind, 10), - relays: tag[2] ? [tag[2]] : [] - } + relays: tag[2] ? [tag[2]] : [], + }, }) } catch (err) { /***/ diff --git a/relay.test.ts b/relay.test.ts index 96d088a..27f3218 100644 --- a/relay.test.ts +++ b/relay.test.ts @@ -1,8 +1,8 @@ import 'websocket-polyfill' -import {finishEvent} from './event.ts' -import {generatePrivateKey, getPublicKey} from './keys.ts' -import {relayInit} from './relay.ts' +import { finishEvent } from './event.ts' +import { generatePrivateKey, getPublicKey } from './keys.ts' +import { relayInit } from './relay.ts' let relay = relayInit('wss://relay.damus.io/') @@ -23,7 +23,7 @@ test('connectivity', () => { relay.on('error', () => { resolve(false) }) - }) + }), ).resolves.toBe(true) }) @@ -33,14 +33,11 @@ test('querying', async () => { let sub = relay.sub([ { - ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] - } + ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'], + }, ]) sub.on('event', event => { - expect(event).toHaveProperty( - 'id', - 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027' - ) + expect(event).toHaveProperty('id', 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027') resolve1(true) }) sub.on('eose', () => { @@ -53,7 +50,7 @@ test('querying', async () => { }), new Promise(resolve => { resolve2 = resolve - }) + }), ]) expect(t1).toEqual(true) @@ -62,24 +59,19 @@ test('querying', async () => { test('get()', async () => { let event = await relay.get({ - ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] + ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'], }) - expect(event).toHaveProperty( - 'id', - 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027' - ) + expect(event).toHaveProperty('id', 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027') }) test('list()', async () => { let events = await relay.list([ { - authors: [ - '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' - ], + authors: ['3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'], kinds: [1], - limit: 2 - } + limit: 2, + }, ]) expect(events.length).toEqual(2) @@ -94,8 +86,8 @@ test('listening (twice) and publishing', async () => { let sub = relay.sub([ { kinds: [27572], - authors: [pk] - } + authors: [pk], + }, ]) sub.on('event', event => { @@ -111,12 +103,15 @@ test('listening (twice) and publishing', async () => { resolve2(true) }) - let event = finishEvent({ - kind: 27572, - created_at: Math.floor(Date.now() / 1000), - tags: [], - content: 'nostr-tools test suite' - }, sk) + let event = finishEvent( + { + kind: 27572, + created_at: Math.floor(Date.now() / 1000), + tags: [], + content: 'nostr-tools test suite', + }, + sk, + ) relay.publish(event) return expect( @@ -126,7 +121,7 @@ test('listening (twice) and publishing', async () => { }), new Promise(resolve => { resolve2 = resolve - }) - ]) + }), + ]), ).resolves.toEqual([true, true]) }) diff --git a/relay.ts b/relay.ts index 018ca10..1c160c7 100644 --- a/relay.ts +++ b/relay.ts @@ -1,9 +1,9 @@ /* global WebSocket */ -import {verifySignature, validateEvent, type Event} from './event.ts' -import {matchFilters, type Filter} from './filter.ts' -import {getHex64, getSubscriptionId} from './fakejson.ts' -import {MessageQueue} from './utils.ts' +import { verifySignature, validateEvent, type Event } from './event.ts' +import { matchFilters, type Filter } from './filter.ts' +import { getHex64, getSubscriptionId } from './fakejson.ts' +import { MessageQueue } from './utils.ts' type RelayEvent = { connect: () => void | Promise @@ -25,47 +25,20 @@ export type Relay = { status: number connect: () => Promise close: () => void - sub: ( - filters: Filter[], - opts?: SubscriptionOptions - ) => Sub - list: ( - filters: Filter[], - opts?: SubscriptionOptions - ) => Promise[]> - get: ( - filter: Filter, - opts?: SubscriptionOptions - ) => Promise | null> - count: ( - filters: Filter[], - opts?: SubscriptionOptions - ) => Promise + sub: (filters: Filter[], opts?: SubscriptionOptions) => Sub + list: (filters: Filter[], opts?: SubscriptionOptions) => Promise[]> + get: (filter: Filter, opts?: SubscriptionOptions) => Promise | null> + count: (filters: Filter[], opts?: SubscriptionOptions) => Promise publish: (event: Event) => Promise auth: (event: Event) => Promise - off: ( - event: T, - listener: U - ) => void - on: ( - event: T, - listener: U - ) => void + off: (event: T, listener: U) => void + on: (event: T, listener: U) => void } export type Sub = { - sub: ( - filters: Filter[], - opts: SubscriptionOptions - ) => Sub + sub: (filters: Filter[], opts: SubscriptionOptions) => Sub unsub: () => void - on: , U extends SubEvent[T]>( - event: T, - listener: U - ) => void - off: , U extends SubEvent[T]>( - event: T, - listener: U - ) => void + on: , U extends SubEvent[T]>(event: T, listener: U) => void + off: , U extends SubEvent[T]>(event: T, listener: U) => void } export type SubscriptionOptions = { @@ -75,12 +48,12 @@ export type SubscriptionOptions = { alreadyHaveEvent?: null | ((id: string, relay: string) => boolean) } -const newListeners = (): {[TK in keyof RelayEvent]: RelayEvent[TK][]} => ({ +const newListeners = (): { [TK in keyof RelayEvent]: RelayEvent[TK][] } => ({ connect: [], disconnect: [], error: [], notice: [], - auth: [] + auth: [], }) export function relayInit( @@ -89,15 +62,15 @@ export function relayInit( getTimeout?: number listTimeout?: number countTimeout?: number - } = {} + } = {}, ): Relay { - let {listTimeout = 3000, getTimeout = 3000, countTimeout = 3000} = options + let { listTimeout = 3000, getTimeout = 3000, countTimeout = 3000 } = options var ws: WebSocket - var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {} + var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {} var listeners = newListeners() var subListeners: { - [subid: string]: {[TK in keyof SubEvent]: SubEvent[TK][]} + [subid: string]: { [TK in keyof SubEvent]: SubEvent[TK][] } } = {} var pubListeners: { [eventid: string]: { @@ -153,11 +126,7 @@ export function relayInit( let subid = getSubscriptionId(json) if (subid) { let so = openSubs[subid] - if ( - so && - so.alreadyHaveEvent && - so.alreadyHaveEvent(getHex64(json, 'id'), url) - ) { + if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, 'id'), url)) { return } } @@ -203,7 +172,7 @@ export function relayInit( let ok: boolean = data[2] let reason: string = data[3] || '' if (id in pubListeners) { - let {resolve, reject} = pubListeners[id] + let { resolve, reject } = pubListeners[id] if (ok) resolve(null) else reject(new Error(reason)) } @@ -258,8 +227,8 @@ export function relayInit( verb = 'REQ', skipVerification = false, alreadyHaveEvent = null, - id = Math.random().toString().slice(2) - }: SubscriptionOptions = {} + id = Math.random().toString().slice(2), + }: SubscriptionOptions = {}, ): Sub => { let subid = id @@ -267,7 +236,7 @@ export function relayInit( id: subid, filters, skipVerification, - alreadyHaveEvent + alreadyHaveEvent, } trySend([verb, subid, ...filters]) @@ -276,7 +245,7 @@ export function relayInit( sub(newFilters || filters, { skipVerification: newOpts.skipVerification || skipVerification, alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent, - id: subid + id: subid, }), unsub: () => { delete openSubs[subid] @@ -287,7 +256,7 @@ export function relayInit( subListeners[subid] = subListeners[subid] || { event: [], count: [], - eose: [] + eose: [], } subListeners[subid][type].push(cb) }, @@ -295,7 +264,7 @@ export function relayInit( let listeners = subListeners[subid] let idx = listeners[type].indexOf(cb) if (idx >= 0) listeners[type].splice(idx, 1) - } + }, } } @@ -308,27 +277,21 @@ export function relayInit( let id = event.id trySend([type, event]) - pubListeners[id] = {resolve, reject} + pubListeners[id] = { resolve, reject } }) } return { url, sub, - on: ( - type: T, - cb: U - ): void => { + on: (type: T, cb: U): void => { listeners[type].push(cb) if (type === 'connect' && ws?.readyState === 1) { // i would love to know why we need this ;(cb as () => void)() } }, - off: ( - type: T, - cb: U - ): void => { + off: (type: T, cb: U): void => { let index = listeners[type].indexOf(cb) if (index !== -1) listeners[type].splice(index, 1) }, @@ -364,7 +327,7 @@ export function relayInit( }), count: (filters: Filter[]): Promise => new Promise(resolve => { - let s = sub(filters, {...sub, verb: 'COUNT'}) + let s = sub(filters, { ...sub, verb: 'COUNT' }) let timeout = setTimeout(() => { s.unsub() resolve(null) @@ -392,6 +355,6 @@ export function relayInit( }, get status() { return ws?.readyState ?? 3 - } + }, } } diff --git a/test-helpers.ts b/test-helpers.ts index 04e7045..98a8bab 100644 --- a/test-helpers.ts +++ b/test-helpers.ts @@ -1,4 +1,4 @@ -import type {Event} from './event.ts' +import type { Event } from './event.ts' type EventParams = Partial> @@ -12,6 +12,6 @@ export function buildEvent(params: EventParams): Event< content: '', tags: [], sig: '', - ...params + ...params, } } diff --git a/utils.test.ts b/utils.test.ts index 7ad3d3b..c14bcdf 100644 --- a/utils.test.ts +++ b/utils.test.ts @@ -1,101 +1,109 @@ -import {buildEvent} from './test-helpers.ts' -import { - MessageQueue, - insertEventIntoAscendingList, - insertEventIntoDescendingList, -} from './utils.ts' +import { buildEvent } from './test-helpers.ts' +import { MessageQueue, insertEventIntoAscendingList, insertEventIntoDescendingList } from './utils.ts' -import type {Event} from './event.ts' +import type { Event } from './event.ts' describe('inserting into a desc sorted list of events', () => { test('insert into an empty list', async () => { const list0: Event[] = [] - expect( - insertEventIntoDescendingList(list0, buildEvent({id: 'abc', created_at: 10})) - ).toHaveLength(1) + expect(insertEventIntoDescendingList(list0, buildEvent({ id: 'abc', created_at: 10 }))).toHaveLength(1) }) test('insert in the beginning of a list', async () => { - const list0 = [buildEvent({created_at: 20}), buildEvent({created_at: 10})] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 30 - })) + const list0 = [buildEvent({ created_at: 20 }), buildEvent({ created_at: 10 })] + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 30, + }), + ) expect(list1).toHaveLength(3) expect(list1[0].id).toBe('abc') }) test('insert in the beginning of a list with same created_at', async () => { - const list0 = [ - buildEvent({created_at: 30}), - buildEvent({created_at: 20}), - buildEvent({created_at: 10}), - ] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 30 - })) + const list0 = [buildEvent({ created_at: 30 }), buildEvent({ created_at: 20 }), buildEvent({ created_at: 10 })] + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 30, + }), + ) expect(list1).toHaveLength(4) expect(list1[0].id).toBe('abc') }) test('insert in the middle of a list', async () => { const list0 = [ - buildEvent({created_at: 30}), - buildEvent({created_at: 20}), - buildEvent({created_at: 10}), - buildEvent({created_at: 1}), + buildEvent({ created_at: 30 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 10 }), + buildEvent({ created_at: 1 }), ] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 15 - })) + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 15, + }), + ) expect(list1).toHaveLength(5) expect(list1[2].id).toBe('abc') }) test('insert in the end of a list', async () => { const list0 = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 10}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 10 }), ] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 5 - })) + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 5, + }), + ) expect(list1).toHaveLength(6) expect(list1.slice(-1)[0].id).toBe('abc') }) test('insert in the last-to-end of a list with same created_at', async () => { const list0: Event[] = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 10}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 10 }), ] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 10 - })) + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 10, + }), + ) expect(list1).toHaveLength(6) expect(list1.slice(-2)[0].id).toBe('abc') }) test('do not insert duplicates', async () => { const list0 = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 10, id: 'abc'}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 10, id: 'abc' }), ] - const list1 = insertEventIntoDescendingList(list0, buildEvent({ - id: 'abc', - created_at: 10 - })) + const list1 = insertEventIntoDescendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 10, + }), + ) expect(list1).toHaveLength(3) }) }) @@ -103,92 +111,104 @@ describe('inserting into a desc sorted list of events', () => { describe('inserting into a asc sorted list of events', () => { test('insert into an empty list', async () => { const list0: Event[] = [] - expect( - insertEventIntoAscendingList(list0, buildEvent({id: 'abc', created_at: 10})) - ).toHaveLength(1) + expect(insertEventIntoAscendingList(list0, buildEvent({ id: 'abc', created_at: 10 }))).toHaveLength(1) }) test('insert in the beginning of a list', async () => { - const list0 = [buildEvent({created_at: 10}), buildEvent({created_at: 20})] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 1 - })) + const list0 = [buildEvent({ created_at: 10 }), buildEvent({ created_at: 20 })] + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 1, + }), + ) expect(list1).toHaveLength(3) expect(list1[0].id).toBe('abc') }) test('insert in the beginning of a list with same created_at', async () => { - const list0 = [ - buildEvent({created_at: 10}), - buildEvent({created_at: 20}), - buildEvent({created_at: 30}), - ] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 10 - })) + const list0 = [buildEvent({ created_at: 10 }), buildEvent({ created_at: 20 }), buildEvent({ created_at: 30 })] + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 10, + }), + ) expect(list1).toHaveLength(4) expect(list1[0].id).toBe('abc') }) test('insert in the middle of a list', async () => { const list0 = [ - buildEvent({created_at: 10}), - buildEvent({created_at: 20}), - buildEvent({created_at: 30}), - buildEvent({created_at: 40}), + buildEvent({ created_at: 10 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 30 }), + buildEvent({ created_at: 40 }), ] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 25 - })) + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 25, + }), + ) expect(list1).toHaveLength(5) expect(list1[2].id).toBe('abc') }) test('insert in the end of a list', async () => { const list0 = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 40}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 40 }), ] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 50 - })) + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 50, + }), + ) expect(list1).toHaveLength(6) expect(list1.slice(-1)[0].id).toBe('abc') }) test('insert in the last-to-end of a list with same created_at', async () => { const list0 = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 30}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 30 }), ] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 30 - })) + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 30, + }), + ) expect(list1).toHaveLength(6) expect(list1.slice(-2)[0].id).toBe('abc') }) test('do not insert duplicates', async () => { const list0 = [ - buildEvent({created_at: 20}), - buildEvent({created_at: 20}), - buildEvent({created_at: 30, id: 'abc'}), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 20 }), + buildEvent({ created_at: 30, id: 'abc' }), ] - const list1 = insertEventIntoAscendingList(list0, buildEvent({ - id: 'abc', - created_at: 30 - })) + const list1 = insertEventIntoAscendingList( + list0, + buildEvent({ + id: 'abc', + created_at: 30, + }), + ) expect(list1).toHaveLength(3) }) }) diff --git a/utils.ts b/utils.ts index 86e9eb2..cae4d5f 100644 --- a/utils.ts +++ b/utils.ts @@ -1,4 +1,4 @@ -import type {Event} from './event.ts' +import type { Event } from './event.ts' export const utf8Decoder = new TextDecoder('utf-8') export const utf8Encoder = new TextEncoder() @@ -7,11 +7,7 @@ export function normalizeURL(url: string): string { let p = new URL(url) p.pathname = p.pathname.replace(/\/+/g, '/') if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1) - if ( - (p.port === '80' && p.protocol === 'ws:') || - (p.port === '443' && p.protocol === 'wss:') - ) - p.port = '' + if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = '' p.searchParams.sort() p.hash = '' return p.toString() @@ -20,10 +16,7 @@ export function normalizeURL(url: string): string { // // fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array // -export function insertEventIntoDescendingList( - sortedArray: Event[], - event: Event -) { +export function insertEventIntoDescendingList(sortedArray: Event[], event: Event) { let start = 0 let end = sortedArray.length - 1 let midPoint @@ -55,20 +48,13 @@ export function insertEventIntoDescendingList( // insert when num is NOT already in (no duplicates) if (sortedArray[position]?.id !== event.id) { - return [ - ...sortedArray.slice(0, position), - event, - ...sortedArray.slice(position) - ] + return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)] } return sortedArray } -export function insertEventIntoAscendingList( - sortedArray: Event[], - event: Event -) { +export function insertEventIntoAscendingList(sortedArray: Event[], event: Event) { let start = 0 let end = sortedArray.length - 1 let midPoint @@ -100,11 +86,7 @@ export function insertEventIntoAscendingList( // insert when num is NOT already in (no duplicates) if (sortedArray[position]?.id !== event.id) { - return [ - ...sortedArray.slice(0, position), - event, - ...sortedArray.slice(position) - ] + return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)] } return sortedArray