mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 08:38:50 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24f5068fdb | ||
|
|
5733f9c4e4 | ||
|
|
6b73bbf8a3 | ||
|
|
d244b62c7a | ||
|
|
b00af9a30a | ||
|
|
be7c981c14 | ||
|
|
5539e5cf89 | ||
|
|
73decbc8e0 | ||
|
|
b3d95cecdd | ||
|
|
82228036ef | ||
|
|
01435ab9f5 | ||
|
|
63cbc4133a | ||
|
|
049f183d27 | ||
|
|
f9e3119ab4 |
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
@@ -7,6 +7,7 @@ export type Filter = {
|
||||
since?: number
|
||||
until?: number
|
||||
limit?: number
|
||||
search?: string
|
||||
[key: `#${string}`]: string[]
|
||||
}
|
||||
|
||||
|
||||
1
index.ts
1
index.ts
@@ -9,6 +9,7 @@ export * as nip05 from './nip05'
|
||||
export * as nip06 from './nip06'
|
||||
export * as nip19 from './nip19'
|
||||
export * as nip26 from './nip26'
|
||||
export * as nip39 from './nip39'
|
||||
export * as nip57 from './nip57'
|
||||
|
||||
export * as fj from './fakejson'
|
||||
|
||||
1
nip05.ts
1
nip05.ts
@@ -37,6 +37,7 @@ export async function queryProfile(
|
||||
}
|
||||
|
||||
if (!name.match(/^[A-Za-z0-9-_]+$/)) return null
|
||||
if (!domain.includes('.')) return null
|
||||
|
||||
let res
|
||||
try {
|
||||
|
||||
@@ -35,6 +35,21 @@ test('encode and decode nprofile', () => {
|
||||
expect(data.relays).toContain(relays[1])
|
||||
})
|
||||
|
||||
test('decode nprofile without relays', () => {
|
||||
expect(
|
||||
nip19.decode(
|
||||
nip19.nprofileEncode({
|
||||
pubkey:
|
||||
'97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322',
|
||||
relays: []
|
||||
})
|
||||
).data
|
||||
).toHaveProperty(
|
||||
'pubkey',
|
||||
'97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'
|
||||
)
|
||||
})
|
||||
|
||||
test('encode and decode naddr', () => {
|
||||
let pk = getPublicKey(generatePrivateKey())
|
||||
let relays = [
|
||||
|
||||
15
nip39.test.js
Normal file
15
nip39.test.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
const fetch = require('node-fetch')
|
||||
const {nip39} = require('./lib/nostr.cjs.js')
|
||||
|
||||
test('validate github claim', async () => {
|
||||
nip39.useFetchImplementation(fetch)
|
||||
|
||||
let result = await nip39.validateGithub(
|
||||
'npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z',
|
||||
'vitorpamplona',
|
||||
'cf19e2d1d7f8dac6348ad37b35ec8421'
|
||||
)
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
27
nip39.ts
Normal file
27
nip39.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
var _fetch: any
|
||||
|
||||
try {
|
||||
_fetch = fetch
|
||||
} catch {}
|
||||
|
||||
export function useFetchImplementation(fetchImplementation: any) {
|
||||
_fetch = fetchImplementation
|
||||
}
|
||||
|
||||
export async function validateGithub(
|
||||
pubkey: string,
|
||||
username: string,
|
||||
proof: string
|
||||
): Promise<boolean> {
|
||||
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}`
|
||||
)
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nostr-tools",
|
||||
"version": "1.7.4",
|
||||
"version": "1.7.5",
|
||||
"description": "Tools for making a Nostr client.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,6 +16,7 @@
|
||||
"import": "./lib/esm/nostr.mjs",
|
||||
"require": "./lib/nostr.cjs.js"
|
||||
},
|
||||
"license": "Public domain",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.0.0",
|
||||
"@noble/secp256k1": "^1.7.1",
|
||||
|
||||
47
pool.ts
47
pool.ts
@@ -27,7 +27,12 @@ export class SimplePool {
|
||||
async ensureRelay(url: string): Promise<Relay> {
|
||||
const nm = normalizeURL(url)
|
||||
const existing = this._conn[nm]
|
||||
if (existing) return existing
|
||||
if (existing && existing.status === 1) return existing
|
||||
|
||||
if (existing) {
|
||||
await existing.connect()
|
||||
return existing
|
||||
}
|
||||
|
||||
const relay = relayInit(nm, {
|
||||
getTimeout: this.getTimeout * 0.9,
|
||||
@@ -42,8 +47,11 @@ export class SimplePool {
|
||||
|
||||
sub(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Sub {
|
||||
let _knownIds: Set<string> = new Set()
|
||||
let modifiedOpts = opts || {}
|
||||
let modifiedOpts = {...(opts || {})}
|
||||
modifiedOpts.alreadyHaveEvent = (id, url) => {
|
||||
if (opts?.alreadyHaveEvent?.(id, url)) {
|
||||
return true
|
||||
}
|
||||
let set = this._seenOn[id] || new Set()
|
||||
set.add(url)
|
||||
this._seenOn[id] = set
|
||||
@@ -99,19 +107,17 @@ export class SimplePool {
|
||||
subs.forEach(sub => sub.unsub())
|
||||
},
|
||||
on(type, cb) {
|
||||
switch (type) {
|
||||
case 'event':
|
||||
eventListeners.add(cb)
|
||||
break
|
||||
case 'eose':
|
||||
eoseListeners.add(cb)
|
||||
break
|
||||
if (type === 'event') {
|
||||
eventListeners.add(cb)
|
||||
} else if (type === 'eose') {
|
||||
eoseListeners.add(cb as () => void | Promise<void>)
|
||||
}
|
||||
},
|
||||
off(type, cb) {
|
||||
if (type === 'event') {
|
||||
eventListeners.delete(cb)
|
||||
} else if (type === 'eose') eoseListeners.delete(cb)
|
||||
} else if (type === 'eose')
|
||||
eoseListeners.delete(cb as () => void | Promise<void>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,12 +165,14 @@ export class SimplePool {
|
||||
}
|
||||
|
||||
publish(relays: string[], event: Event): Pub {
|
||||
let pubs = relays.map(relay => {
|
||||
let r = this._conn[normalizeURL(relay)]
|
||||
if (!r) return badPub(relay)
|
||||
return r.publish(event)
|
||||
const pubs: Pub[] = []
|
||||
relays.forEach(async relay => {
|
||||
let r
|
||||
try {
|
||||
r = await this.ensureRelay(relay)
|
||||
pubs.push(r.publish(event))
|
||||
} catch (_) {}
|
||||
})
|
||||
|
||||
return {
|
||||
on(type, cb) {
|
||||
pubs.forEach((pub, i) => {
|
||||
@@ -181,12 +189,3 @@ export class SimplePool {
|
||||
return Array.from(this._seenOn[id]?.values?.() || [])
|
||||
}
|
||||
}
|
||||
|
||||
function badPub(relay: string): Pub {
|
||||
return {
|
||||
on(typ, cb) {
|
||||
if (typ === 'failed') cb(`relay ${relay} not connected`)
|
||||
},
|
||||
off() {}
|
||||
}
|
||||
}
|
||||
|
||||
78
relay.ts
78
relay.ts
@@ -4,8 +4,16 @@ import {Event, verifySignature, validateEvent} from './event'
|
||||
import {Filter, matchFilters} from './filter'
|
||||
import {getHex64, getSubscriptionId} from './fakejson'
|
||||
|
||||
type RelayEvent = 'connect' | 'disconnect' | 'error' | 'notice'
|
||||
|
||||
type RelayEvent = {
|
||||
connect: () => void | Promise<void>
|
||||
disconnect: () => void | Promise<void>
|
||||
error: () => void | Promise<void>
|
||||
notice: (msg: string) => void | Promise<void>
|
||||
}
|
||||
type SubEvent = {
|
||||
event: (event: Event) => void | Promise<void>
|
||||
eose: () => void | Promise<void>
|
||||
}
|
||||
export type Relay = {
|
||||
url: string
|
||||
status: number
|
||||
@@ -15,8 +23,14 @@ export type Relay = {
|
||||
list: (filters: Filter[], opts?: SubscriptionOptions) => Promise<Event[]>
|
||||
get: (filter: Filter, opts?: SubscriptionOptions) => Promise<Event | null>
|
||||
publish: (event: Event) => Pub
|
||||
on: (type: RelayEvent, cb: any) => void
|
||||
off: (type: RelayEvent, cb: any) => void
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
|
||||
event: T,
|
||||
listener: U
|
||||
) => void
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
|
||||
event: T,
|
||||
listener: U
|
||||
) => void
|
||||
}
|
||||
export type Pub = {
|
||||
on: (type: 'ok' | 'failed', cb: any) => void
|
||||
@@ -25,8 +39,14 @@ export type Pub = {
|
||||
export type Sub = {
|
||||
sub: (filters: Filter[], opts: SubscriptionOptions) => Sub
|
||||
unsub: () => void
|
||||
on: (type: 'event' | 'eose', cb: any) => void
|
||||
off: (type: 'event' | 'eose', cb: any) => void
|
||||
on: <T extends keyof SubEvent, U extends SubEvent[T]>(
|
||||
event: T,
|
||||
listener: U
|
||||
) => void
|
||||
off: <T extends keyof SubEvent, U extends SubEvent[T]>(
|
||||
event: T,
|
||||
listener: U
|
||||
) => void
|
||||
}
|
||||
|
||||
export type SubscriptionOptions = {
|
||||
@@ -46,22 +66,14 @@ export function relayInit(
|
||||
|
||||
var ws: WebSocket
|
||||
var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {}
|
||||
var listeners: {
|
||||
connect: Array<() => void>
|
||||
disconnect: Array<() => void>
|
||||
error: Array<() => void>
|
||||
notice: Array<(msg: string) => void>
|
||||
} = {
|
||||
var listeners: {[TK in keyof RelayEvent]: RelayEvent[TK][]} = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: []
|
||||
}
|
||||
var subListeners: {
|
||||
[subid: string]: {
|
||||
event: Array<(event: Event) => void>
|
||||
eose: Array<() => void>
|
||||
}
|
||||
[subid: string]: {[TK in keyof SubEvent]: SubEvent[TK][]}
|
||||
} = {}
|
||||
var pubListeners: {
|
||||
[eventid: string]: {
|
||||
@@ -73,7 +85,11 @@ export function relayInit(
|
||||
|
||||
async function connectRelay(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
ws = new WebSocket(url)
|
||||
try {
|
||||
ws = new WebSocket(url)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
listeners.connect.forEach(cb => cb())
|
||||
@@ -225,14 +241,20 @@ export function relayInit(
|
||||
delete subListeners[subid]
|
||||
trySend(['CLOSE', subid])
|
||||
},
|
||||
on: (type: 'event' | 'eose', cb: any): void => {
|
||||
on: <T extends keyof SubEvent, U extends SubEvent[T]>(
|
||||
type: T,
|
||||
cb: U
|
||||
): void => {
|
||||
subListeners[subid] = subListeners[subid] || {
|
||||
event: [],
|
||||
eose: []
|
||||
}
|
||||
subListeners[subid][type].push(cb)
|
||||
},
|
||||
off: (type: 'event' | 'eose', cb: any): void => {
|
||||
off: <T extends keyof SubEvent, U extends SubEvent[T]>(
|
||||
type: T,
|
||||
cb: U
|
||||
): void => {
|
||||
let listeners = subListeners[subid]
|
||||
let idx = listeners[type].indexOf(cb)
|
||||
if (idx >= 0) listeners[type].splice(idx, 1)
|
||||
@@ -243,13 +265,20 @@ export function relayInit(
|
||||
return {
|
||||
url,
|
||||
sub,
|
||||
on: (type: RelayEvent, cb: any): void => {
|
||||
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
|
||||
type: T,
|
||||
cb: U
|
||||
): void => {
|
||||
listeners[type].push(cb)
|
||||
if (type === 'connect' && ws?.readyState === 1) {
|
||||
cb()
|
||||
// i would love to know why we need this
|
||||
;(cb as () => void)()
|
||||
}
|
||||
},
|
||||
off: (type: RelayEvent, cb: any): void => {
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
|
||||
type: T,
|
||||
cb: U
|
||||
): void => {
|
||||
let index = listeners[type].indexOf(cb)
|
||||
if (index !== -1) listeners[type].splice(index, 1)
|
||||
},
|
||||
@@ -310,8 +339,9 @@ export function relayInit(
|
||||
listeners = {connect: [], disconnect: [], error: [], notice: []}
|
||||
subListeners = {}
|
||||
pubListeners = {}
|
||||
|
||||
ws?.close()
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws?.close()
|
||||
}
|
||||
},
|
||||
get status() {
|
||||
return ws?.readyState ?? 3
|
||||
|
||||
Reference in New Issue
Block a user