mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 16:48:50 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb29d62033 | ||
|
|
0d237405d9 | ||
|
|
659ad36b62 | ||
|
|
d062ab8afd | ||
|
|
94f841f347 | ||
|
|
c1d03cf00b | ||
|
|
29ecdfc5ec | ||
|
|
d3fc4734b4 | ||
|
|
66d0b8a4e1 |
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||
#  nostr-tools
|
#  [](https://jsr.io/@nostr/tools) nostr-tools
|
||||||
|
|
||||||
Tools for developing [Nostr](https://github.com/fiatjaf/nostr) clients.
|
Tools for developing [Nostr](https://github.com/fiatjaf/nostr) clients.
|
||||||
|
|
||||||
@@ -9,11 +9,19 @@ This package is only providing lower-level functionality. If you want more highe
|
|||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install nostr-tools # or yarn add nostr-tools
|
# npm
|
||||||
|
npm install --save nostr-tools
|
||||||
|
|
||||||
|
# jsr
|
||||||
|
npx jsr add @nostr/tools
|
||||||
```
|
```
|
||||||
|
|
||||||
If using TypeScript, this package requires TypeScript >= 5.0.
|
If using TypeScript, this package requires TypeScript >= 5.0.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
https://jsr.io/@nostr/tools/doc
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Generating a private key and a public key
|
### Generating a private key and a public key
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export type SubscribeManyParams = Omit<SubscriptionParams, 'onclose' | 'id'> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AbstractSimplePool {
|
export class AbstractSimplePool {
|
||||||
protected relays = new Map<string, AbstractRelay>()
|
protected relays: Map<string, AbstractRelay> = new Map()
|
||||||
public seenOn: Map<string, Set<AbstractRelay>> = new Map()
|
public seenOn: Map<string, Set<AbstractRelay>> = new Map()
|
||||||
public trackRelays: boolean = false
|
public trackRelays: boolean = false
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export class AbstractRelay {
|
|||||||
|
|
||||||
public baseEoseTimeout: number = 4400
|
public baseEoseTimeout: number = 4400
|
||||||
public connectionTimeout: number = 4400
|
public connectionTimeout: number = 4400
|
||||||
|
public publishTimeout: number = 4400
|
||||||
public openSubs: Map<string, Subscription> = new Map()
|
public openSubs: Map<string, Subscription> = new Map()
|
||||||
private connectionTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
private connectionTimeoutHandle: ReturnType<typeof setTimeout> | undefined
|
||||||
|
|
||||||
@@ -198,9 +199,11 @@ export class AbstractRelay {
|
|||||||
const ok: boolean = data[2]
|
const ok: boolean = data[2]
|
||||||
const reason: string = data[3]
|
const reason: string = data[3]
|
||||||
const ep = this.openEventPublishes.get(id) as EventPublishResolver
|
const ep = this.openEventPublishes.get(id) as EventPublishResolver
|
||||||
if (ok) ep.resolve(reason)
|
if (ep) {
|
||||||
else ep.reject(new Error(reason))
|
if (ok) ep.resolve(reason)
|
||||||
this.openEventPublishes.delete(id)
|
else ep.reject(new Error(reason))
|
||||||
|
this.openEventPublishes.delete(id)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case 'CLOSED': {
|
case 'CLOSED': {
|
||||||
@@ -248,6 +251,13 @@ export class AbstractRelay {
|
|||||||
this.openEventPublishes.set(event.id, { resolve, reject })
|
this.openEventPublishes.set(event.id, { resolve, reject })
|
||||||
})
|
})
|
||||||
this.send('["EVENT",' + JSON.stringify(event) + ']')
|
this.send('["EVENT",' + JSON.stringify(event) + ']')
|
||||||
|
setTimeout(() => {
|
||||||
|
const ep = this.openEventPublishes.get(event.id) as EventPublishResolver
|
||||||
|
if (ep) {
|
||||||
|
ep.reject(new Error('publish timed out'))
|
||||||
|
this.openEventPublishes.delete(event.id)
|
||||||
|
}
|
||||||
|
}, this.publishTimeout)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
jsr.json
7
jsr.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nostr/tools",
|
"name": "@nostr/tools",
|
||||||
"version": "2.3.2",
|
"version": "2.10.0",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./core": "./core.ts",
|
"./core": "./core.ts",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"./nip10": "./nip10.ts",
|
"./nip10": "./nip10.ts",
|
||||||
"./nip11": "./nip11.ts",
|
"./nip11": "./nip11.ts",
|
||||||
"./nip13": "./nip13.ts",
|
"./nip13": "./nip13.ts",
|
||||||
|
"./nip17": "./nip17.ts",
|
||||||
"./nip18": "./nip18.ts",
|
"./nip18": "./nip18.ts",
|
||||||
"./nip19": "./nip19.ts",
|
"./nip19": "./nip19.ts",
|
||||||
"./nip21": "./nip21.ts",
|
"./nip21": "./nip21.ts",
|
||||||
@@ -34,6 +35,8 @@
|
|||||||
"./nip46": "./nip46.ts",
|
"./nip46": "./nip46.ts",
|
||||||
"./nip49": "./nip49.ts",
|
"./nip49": "./nip49.ts",
|
||||||
"./nip57": "./nip57.ts",
|
"./nip57": "./nip57.ts",
|
||||||
|
"./nip58": "./nip58.ts",
|
||||||
|
"./nip59": "./nip59.ts",
|
||||||
"./nip75": "./nip75.ts",
|
"./nip75": "./nip75.ts",
|
||||||
"./nip94": "./nip94.ts",
|
"./nip94": "./nip94.ts",
|
||||||
"./nip96": "./nip96.ts",
|
"./nip96": "./nip96.ts",
|
||||||
@@ -42,4 +45,4 @@
|
|||||||
"./fakejson": "./fakejson.ts",
|
"./fakejson": "./fakejson.ts",
|
||||||
"./utils": "./utils.ts"
|
"./utils": "./utils.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { test, expect } from 'bun:test'
|
import { test, expect } from 'bun:test'
|
||||||
import fetch from 'node-fetch'
|
|
||||||
|
|
||||||
import { useFetchImplementation, queryProfile, NIP05_REGEX, isNip05 } from './nip05.ts'
|
import { useFetchImplementation, queryProfile, NIP05_REGEX, isNip05 } from './nip05.ts'
|
||||||
|
|
||||||
@@ -16,7 +15,27 @@ test('validate NIP05_REGEX', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('fetch nip05 profiles', async () => {
|
test('fetch nip05 profiles', async () => {
|
||||||
useFetchImplementation(fetch)
|
const fetchStub = async (url: string) => ({
|
||||||
|
status: 200,
|
||||||
|
async json() {
|
||||||
|
return {
|
||||||
|
'https://compile-error.net/.well-known/nostr.json?name=_': {
|
||||||
|
names: { _: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc' },
|
||||||
|
},
|
||||||
|
'https://fiatjaf.com/.well-known/nostr.json?name=_': {
|
||||||
|
names: { _: '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d' },
|
||||||
|
relays: {
|
||||||
|
'3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d': [
|
||||||
|
'wss://pyramid.fiatjaf.com',
|
||||||
|
'wss://nos.lol',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}[url]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
useFetchImplementation(fetchStub)
|
||||||
|
|
||||||
let p2 = await queryProfile('compile-error.net')
|
let p2 = await queryProfile('compile-error.net')
|
||||||
expect(p2!.pubkey).toEqual('2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc')
|
expect(p2!.pubkey).toEqual('2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc')
|
||||||
|
|||||||
28
nip05.ts
28
nip05.ts
@@ -12,20 +12,26 @@ export type Nip05 = `${string}@${string}`
|
|||||||
export const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/
|
export const NIP05_REGEX = /^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$/
|
||||||
export const isNip05 = (value?: string | null): value is Nip05 => NIP05_REGEX.test(value || '')
|
export const isNip05 = (value?: string | null): value is Nip05 => NIP05_REGEX.test(value || '')
|
||||||
|
|
||||||
var _fetch: any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
let _fetch: any
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_fetch = fetch
|
_fetch = fetch
|
||||||
} catch {}
|
} catch (_) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
export function useFetchImplementation(fetchImplementation: any) {
|
export function useFetchImplementation(fetchImplementation: unknown) {
|
||||||
_fetch = fetchImplementation
|
_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 {
|
try {
|
||||||
const url = `https://${domain}/.well-known/nostr.json?name=${query}`
|
const url = `https://${domain}/.well-known/nostr.json?name=${query}`
|
||||||
const res = await _fetch(url, { redirect: 'error' })
|
const res = await _fetch(url, { redirect: 'manual' })
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw Error('Wrong response code')
|
||||||
|
}
|
||||||
const json = await res.json()
|
const json = await res.json()
|
||||||
return json.names
|
return json.names
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -37,20 +43,24 @@ export async function queryProfile(fullname: string): Promise<ProfilePointer | n
|
|||||||
const match = fullname.match(NIP05_REGEX)
|
const match = fullname.match(NIP05_REGEX)
|
||||||
if (!match) return null
|
if (!match) return null
|
||||||
|
|
||||||
const [_, name = '_', domain] = match
|
const [, name = '_', domain] = match
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = `https://${domain}/.well-known/nostr.json?name=${name}`
|
const url = `https://${domain}/.well-known/nostr.json?name=${name}`
|
||||||
const res = await (await _fetch(url, { redirect: 'error' })).json()
|
const res = await _fetch(url, { redirect: 'manual' })
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw Error('Wrong response code')
|
||||||
|
}
|
||||||
|
const json = await res.json()
|
||||||
|
|
||||||
let pubkey = res.names[name]
|
const pubkey = json.names[name]
|
||||||
return pubkey ? { pubkey, relays: res.relays?.[pubkey] } : null
|
return pubkey ? { pubkey, relays: json.relays?.[pubkey] } : null
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isValid(pubkey: string, nip05: Nip05): Promise<boolean> {
|
export async function isValid(pubkey: string, nip05: Nip05): Promise<boolean> {
|
||||||
let res = await queryProfile(nip05)
|
const res = await queryProfile(nip05)
|
||||||
return res ? res.pubkey === pubkey : false
|
return res ? res.pubkey === pubkey : false
|
||||||
}
|
}
|
||||||
|
|||||||
19
nip17.ts
19
nip17.ts
@@ -1,5 +1,5 @@
|
|||||||
import { PrivateDirectMessage } from './kinds.ts'
|
import { PrivateDirectMessage } from './kinds.ts'
|
||||||
import { EventTemplate, getPublicKey } from './pure.ts'
|
import { EventTemplate, NostrEvent, getPublicKey } from './pure.ts'
|
||||||
import * as nip59 from './nip59.ts'
|
import * as nip59 from './nip59.ts'
|
||||||
|
|
||||||
type Recipient = {
|
type Recipient = {
|
||||||
@@ -48,7 +48,7 @@ export function wrapEvent(
|
|||||||
message: string,
|
message: string,
|
||||||
conversationTitle?: string,
|
conversationTitle?: string,
|
||||||
replyTo?: ReplyTo,
|
replyTo?: ReplyTo,
|
||||||
) {
|
): NostrEvent {
|
||||||
const event = createEvent(recipient, message, conversationTitle, replyTo)
|
const event = createEvent(recipient, message, conversationTitle, replyTo)
|
||||||
return nip59.wrapEvent(event, senderPrivateKey, recipient.publicKey)
|
return nip59.wrapEvent(event, senderPrivateKey, recipient.publicKey)
|
||||||
}
|
}
|
||||||
@@ -59,22 +59,17 @@ export function wrapManyEvents(
|
|||||||
message: string,
|
message: string,
|
||||||
conversationTitle?: string,
|
conversationTitle?: string,
|
||||||
replyTo?: ReplyTo,
|
replyTo?: ReplyTo,
|
||||||
) {
|
): NostrEvent[] {
|
||||||
if (!recipients || recipients.length === 0) {
|
if (!recipients || recipients.length === 0) {
|
||||||
throw new Error('At least one recipient is required.')
|
throw new Error('At least one recipient is required.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderPublicKey = getPublicKey(senderPrivateKey)
|
const senderPublicKey = getPublicKey(senderPrivateKey)
|
||||||
|
|
||||||
// Initialize the wrappeds array with the sender's own wrapped event
|
// wrap the event for the sender and then for each recipient
|
||||||
const wrappeds = [wrapEvent(senderPrivateKey, { publicKey: senderPublicKey }, message, conversationTitle, replyTo)]
|
return [{ publicKey: senderPublicKey }, ...recipients].map(recipient =>
|
||||||
|
wrapEvent(senderPrivateKey, recipient, message, conversationTitle, replyTo),
|
||||||
// Wrap the event for each recipient
|
)
|
||||||
recipients.forEach(recipient => {
|
|
||||||
wrappeds.push(wrapEvent(senderPrivateKey, recipient, message, conversationTitle, replyTo))
|
|
||||||
})
|
|
||||||
|
|
||||||
return wrappeds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unwrapEvent = nip59.unwrapEvent
|
export const unwrapEvent = nip59.unwrapEvent
|
||||||
|
|||||||
21
nip46.ts
21
nip46.ts
@@ -1,8 +1,8 @@
|
|||||||
import { NostrEvent, UnsignedEvent, VerifiedEvent } from './core.ts'
|
import { NostrEvent, UnsignedEvent, VerifiedEvent } from './core.ts'
|
||||||
import { generateSecretKey, finalizeEvent, getPublicKey, verifyEvent } from './pure.ts'
|
import { generateSecretKey, finalizeEvent, getPublicKey, verifyEvent } from './pure.ts'
|
||||||
import { AbstractSimplePool, SubCloser } from './abstract-pool.ts'
|
import { AbstractSimplePool, SubCloser } from './abstract-pool.ts'
|
||||||
import { decrypt, encrypt } from './nip04.ts'
|
import { decrypt as legacyDecrypt } from './nip04.ts'
|
||||||
import { getConversationKey, decrypt as nip44decrypt } from './nip44.ts'
|
import { getConversationKey, decrypt, encrypt } from './nip44.ts'
|
||||||
import { NIP05_REGEX } from './nip05.ts'
|
import { NIP05_REGEX } from './nip05.ts'
|
||||||
import { SimplePool } from './pool.ts'
|
import { SimplePool } from './pool.ts'
|
||||||
import { Handlerinformation, NostrConnect } from './kinds.ts'
|
import { Handlerinformation, NostrConnect } from './kinds.ts'
|
||||||
@@ -48,7 +48,7 @@ export async function parseBunkerInput(input: string): Promise<BunkerPointer | n
|
|||||||
return queryBunkerProfile(input)
|
return queryBunkerProfile(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queryBunkerProfile(nip05: string): Promise<BunkerPointer | null> {
|
export async function queryBunkerProfile(nip05: string): Promise<BunkerPointer | null> {
|
||||||
const match = nip05.match(NIP05_REGEX)
|
const match = nip05.match(NIP05_REGEX)
|
||||||
if (!match) return null
|
if (!match) return null
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ export class BunkerSigner {
|
|||||||
}
|
}
|
||||||
private waitingForAuth: { [id: string]: boolean }
|
private waitingForAuth: { [id: string]: boolean }
|
||||||
private secretKey: Uint8Array
|
private secretKey: Uint8Array
|
||||||
|
private conversationKey: Uint8Array
|
||||||
public bp: BunkerPointer
|
public bp: BunkerPointer
|
||||||
|
|
||||||
private cachedPubKey: string | undefined
|
private cachedPubKey: string | undefined
|
||||||
@@ -103,6 +104,7 @@ export class BunkerSigner {
|
|||||||
|
|
||||||
this.pool = params.pool || new SimplePool()
|
this.pool = params.pool || new SimplePool()
|
||||||
this.secretKey = clientSecretKey
|
this.secretKey = clientSecretKey
|
||||||
|
this.conversationKey = getConversationKey(clientSecretKey, bp.pubkey)
|
||||||
this.bp = bp
|
this.bp = bp
|
||||||
this.isOpen = false
|
this.isOpen = false
|
||||||
this.idPrefix = Math.random().toString(36).substring(7)
|
this.idPrefix = Math.random().toString(36).substring(7)
|
||||||
@@ -112,18 +114,18 @@ export class BunkerSigner {
|
|||||||
|
|
||||||
const listeners = this.listeners
|
const listeners = this.listeners
|
||||||
const waitingForAuth = this.waitingForAuth
|
const waitingForAuth = this.waitingForAuth
|
||||||
const skBytes = this.secretKey
|
const convKey = this.conversationKey
|
||||||
|
|
||||||
this.subCloser = this.pool.subscribeMany(
|
this.subCloser = this.pool.subscribeMany(
|
||||||
this.bp.relays,
|
this.bp.relays,
|
||||||
[{ kinds: [NostrConnect], '#p': [getPublicKey(this.secretKey)] }],
|
[{ kinds: [NostrConnect], authors: [bp.pubkey], '#p': [getPublicKey(this.secretKey)] }],
|
||||||
{
|
{
|
||||||
async onevent(event: NostrEvent) {
|
async onevent(event: NostrEvent) {
|
||||||
let o
|
let o
|
||||||
try {
|
try {
|
||||||
o = JSON.parse(await decrypt(clientSecretKey, event.pubkey, event.content))
|
o = JSON.parse(decrypt(event.content, convKey))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
o = JSON.parse(nip44decrypt(event.content, getConversationKey(skBytes, event.pubkey)))
|
o = JSON.parse(await legacyDecrypt(event.content, event.pubkey, event.content))
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, result, error } = o
|
const { id, result, error } = o
|
||||||
@@ -166,7 +168,7 @@ export class BunkerSigner {
|
|||||||
this.serial++
|
this.serial++
|
||||||
const id = `${this.idPrefix}-${this.serial}`
|
const id = `${this.idPrefix}-${this.serial}`
|
||||||
|
|
||||||
const encryptedContent = await encrypt(this.secretKey, this.bp.pubkey, JSON.stringify({ id, method, params }))
|
const encryptedContent = encrypt(JSON.stringify({ id, method, params }), this.conversationKey)
|
||||||
|
|
||||||
// the request event
|
// the request event
|
||||||
const verifiedEvent: VerifiedEvent = finalizeEvent(
|
const verifiedEvent: VerifiedEvent = finalizeEvent(
|
||||||
@@ -292,9 +294,6 @@ export async function createAccount(
|
|||||||
return rpc
|
return rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
// @deprecated use fetchBunkerProviders instead
|
|
||||||
export const fetchCustodialBunkers = fetchBunkerProviders
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches info on available providers that announce themselves using NIP-89 events.
|
* Fetches info on available providers that announce themselves using NIP-89 events.
|
||||||
* @returns A promise that resolves to an array of available bunker objects.
|
* @returns A promise that resolves to an array of available bunker objects.
|
||||||
|
|||||||
26
nip59.ts
26
nip59.ts
@@ -1,4 +1,4 @@
|
|||||||
import { EventTemplate, UnsignedEvent, Event } from './core.ts'
|
import { EventTemplate, UnsignedEvent, NostrEvent } from './core.ts'
|
||||||
import { getConversationKey, decrypt, encrypt } from './nip44.ts'
|
import { getConversationKey, decrypt, encrypt } from './nip44.ts'
|
||||||
import { getEventHash, generateSecretKey, finalizeEvent, getPublicKey } from './pure.ts'
|
import { getEventHash, generateSecretKey, finalizeEvent, getPublicKey } from './pure.ts'
|
||||||
import { Seal, GiftWrap } from './kinds.ts'
|
import { Seal, GiftWrap } from './kinds.ts'
|
||||||
@@ -15,10 +15,10 @@ const nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) => getC
|
|||||||
const nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) =>
|
const nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) =>
|
||||||
encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey))
|
encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey))
|
||||||
|
|
||||||
const nip44Decrypt = (data: Event, privateKey: Uint8Array) =>
|
const nip44Decrypt = (data: NostrEvent, privateKey: Uint8Array) =>
|
||||||
JSON.parse(decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey)))
|
JSON.parse(decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey)))
|
||||||
|
|
||||||
export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array) {
|
export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array): Rumor {
|
||||||
const rumor = {
|
const rumor = {
|
||||||
created_at: now(),
|
created_at: now(),
|
||||||
content: '',
|
content: '',
|
||||||
@@ -32,7 +32,7 @@ export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Arra
|
|||||||
return rumor as Rumor
|
return rumor as Rumor
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string) {
|
export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string): NostrEvent {
|
||||||
return finalizeEvent(
|
return finalizeEvent(
|
||||||
{
|
{
|
||||||
kind: Seal,
|
kind: Seal,
|
||||||
@@ -41,10 +41,10 @@ export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublic
|
|||||||
tags: [],
|
tags: [],
|
||||||
},
|
},
|
||||||
privateKey,
|
privateKey,
|
||||||
) as Event
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWrap(seal: Event, recipientPublicKey: string) {
|
export function createWrap(seal: NostrEvent, recipientPublicKey: string): NostrEvent {
|
||||||
const randomKey = generateSecretKey()
|
const randomKey = generateSecretKey()
|
||||||
|
|
||||||
return finalizeEvent(
|
return finalizeEvent(
|
||||||
@@ -55,10 +55,14 @@ export function createWrap(seal: Event, recipientPublicKey: string) {
|
|||||||
tags: [['p', recipientPublicKey]],
|
tags: [['p', recipientPublicKey]],
|
||||||
},
|
},
|
||||||
randomKey,
|
randomKey,
|
||||||
) as Event
|
) as NostrEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapEvent(event: Partial<UnsignedEvent>, senderPrivateKey: Uint8Array, recipientPublicKey: string) {
|
export function wrapEvent(
|
||||||
|
event: Partial<UnsignedEvent>,
|
||||||
|
senderPrivateKey: Uint8Array,
|
||||||
|
recipientPublicKey: string,
|
||||||
|
): NostrEvent {
|
||||||
const rumor = createRumor(event, senderPrivateKey)
|
const rumor = createRumor(event, senderPrivateKey)
|
||||||
|
|
||||||
const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)
|
const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)
|
||||||
@@ -69,7 +73,7 @@ export function wrapManyEvents(
|
|||||||
event: Partial<UnsignedEvent>,
|
event: Partial<UnsignedEvent>,
|
||||||
senderPrivateKey: Uint8Array,
|
senderPrivateKey: Uint8Array,
|
||||||
recipientsPublicKeys: string[],
|
recipientsPublicKeys: string[],
|
||||||
) {
|
): NostrEvent[] {
|
||||||
if (!recipientsPublicKeys || recipientsPublicKeys.length === 0) {
|
if (!recipientsPublicKeys || recipientsPublicKeys.length === 0) {
|
||||||
throw new Error('At least one recipient is required.')
|
throw new Error('At least one recipient is required.')
|
||||||
}
|
}
|
||||||
@@ -85,12 +89,12 @@ export function wrapManyEvents(
|
|||||||
return wrappeds
|
return wrappeds
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unwrapEvent(wrap: Event, recipientPrivateKey: Uint8Array): Rumor {
|
export function unwrapEvent(wrap: NostrEvent, recipientPrivateKey: Uint8Array): Rumor {
|
||||||
const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)
|
const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)
|
||||||
return nip44Decrypt(unwrappedSeal, recipientPrivateKey)
|
return nip44Decrypt(unwrappedSeal, recipientPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unwrapManyEvents(wrappedEvents: Event[], recipientPrivateKey: Uint8Array): Rumor[] {
|
export function unwrapManyEvents(wrappedEvents: NostrEvent[], recipientPrivateKey: Uint8Array): Rumor[] {
|
||||||
let unwrappedEvents: Rumor[] = []
|
let unwrappedEvents: Rumor[] = []
|
||||||
|
|
||||||
wrappedEvents.forEach(e => {
|
wrappedEvents.forEach(e => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "2.9.2",
|
"version": "2.10.0",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -103,6 +103,11 @@
|
|||||||
"require": "./lib/cjs/nip13.js",
|
"require": "./lib/cjs/nip13.js",
|
||||||
"types": "./lib/types/nip13.d.ts"
|
"types": "./lib/types/nip13.d.ts"
|
||||||
},
|
},
|
||||||
|
"./nip17": {
|
||||||
|
"import": "./lib/esm/nip17.js",
|
||||||
|
"require": "./lib/cjs/nip17.js",
|
||||||
|
"types": "./lib/types/nip17.d.ts"
|
||||||
|
},
|
||||||
"./nip18": {
|
"./nip18": {
|
||||||
"import": "./lib/esm/nip18.js",
|
"import": "./lib/esm/nip18.js",
|
||||||
"require": "./lib/cjs/nip18.js",
|
"require": "./lib/cjs/nip18.js",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, test } from 'bun:test'
|
import { expect, test } from 'bun:test'
|
||||||
|
import { Server } from 'mock-socket'
|
||||||
import { finalizeEvent, generateSecretKey, getPublicKey } from './pure.ts'
|
import { finalizeEvent, generateSecretKey, getPublicKey } from './pure.ts'
|
||||||
import { Relay, useWebSocketImplementation } from './relay.ts'
|
import { Relay, useWebSocketImplementation } from './relay.ts'
|
||||||
import { MockRelay, MockWebSocketClient } from './test-helpers.ts'
|
import { MockRelay, MockWebSocketClient } from './test-helpers.ts'
|
||||||
@@ -92,3 +92,28 @@ test('listening and publishing and closing', async done => {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('publish timeout', async () => {
|
||||||
|
const url = 'wss://relay.example.com'
|
||||||
|
new Server(url)
|
||||||
|
|
||||||
|
const relay = new Relay(url)
|
||||||
|
relay.publishTimeout = 100
|
||||||
|
await relay.connect()
|
||||||
|
|
||||||
|
setTimeout(() => relay.close(), 20000) // close the relay to fail the test on timeout
|
||||||
|
|
||||||
|
expect(
|
||||||
|
relay.publish(
|
||||||
|
finalizeEvent(
|
||||||
|
{
|
||||||
|
kind: 1,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [],
|
||||||
|
content: 'hello',
|
||||||
|
},
|
||||||
|
generateSecretKey(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).rejects.toThrow('publish timed out')
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user