mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-08 16:28:49 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55ff796b9f | ||
|
|
3ef2ad5bc4 | ||
|
|
45c07a5f45 | ||
|
|
6a037d1658 | ||
|
|
dcf101c6c2 | ||
|
|
eb97dbd9ef | ||
|
|
92988051c6 | ||
|
|
bf7e00d32a | ||
|
|
9241089997 | ||
|
|
32c47e9bd8 | ||
|
|
6e58fe371c | ||
|
|
26e35d50e0 | ||
|
|
ef3184a6e0 | ||
|
|
56fe3dd5dd | ||
|
|
f1bb5030c8 | ||
|
|
ac212cb5c8 | ||
|
|
204ae0eff1 | ||
|
|
f17ab41d72 |
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
@@ -325,4 +325,4 @@ Please consult the tests or [the source code](https://github.com/fiatjaf/nostr-t
|
||||
|
||||
## License
|
||||
|
||||
Public domain.
|
||||
This is free and unencumbered software released into the public domain. By submitting patches to this project, you agree to dedicate any and all copyright interest in this software to the public domain.
|
||||
|
||||
3
index.ts
3
index.ts
@@ -11,8 +11,11 @@ export * as nip06 from './nip06'
|
||||
export * as nip10 from './nip10'
|
||||
export * as nip13 from './nip13'
|
||||
export * as nip19 from './nip19'
|
||||
export * as nip21 from './nip21'
|
||||
export * as nip26 from './nip26'
|
||||
export * as nip27 from './nip27'
|
||||
export * as nip39 from './nip39'
|
||||
export * as nip42 from './nip42'
|
||||
export * as nip57 from './nip57'
|
||||
|
||||
export * as fj from './fakejson'
|
||||
|
||||
@@ -100,3 +100,12 @@ test('decode naddr from go-nostr with different TLV ordering', () => {
|
||||
expect(data.kind).toEqual(30023)
|
||||
expect(data.identifier).toEqual('banana')
|
||||
})
|
||||
|
||||
test('encode and decode nrelay', () => {
|
||||
let url = "wss://relay.nostr.example"
|
||||
let nrelay = nip19.nrelayEncode(url)
|
||||
expect(nrelay).toMatch(/nrelay1\w+/)
|
||||
let {type, data} = nip19.decode(nrelay)
|
||||
expect(type).toEqual('nrelay')
|
||||
expect(data).toEqual(url)
|
||||
})
|
||||
|
||||
32
nip19.ts
32
nip19.ts
@@ -23,10 +23,16 @@ export type AddressPointer = {
|
||||
relays?: string[]
|
||||
}
|
||||
|
||||
export function decode(nip19: string): {
|
||||
type: string
|
||||
data: ProfilePointer | EventPointer | AddressPointer | string
|
||||
} {
|
||||
export type DecodeResult =
|
||||
| {type: 'nprofile'; data: ProfilePointer}
|
||||
| {type: 'nrelay'; data: string}
|
||||
| {type: 'nevent'; data: EventPointer}
|
||||
| {type: 'naddr'; data: AddressPointer}
|
||||
| {type: 'nsec'; data: string}
|
||||
| {type: 'npub'; data: string}
|
||||
| {type: 'note'; data: string}
|
||||
|
||||
export function decode(nip19: string): DecodeResult {
|
||||
let {prefix, words} = bech32.decode(nip19, Bech32MaxSize)
|
||||
let data = new Uint8Array(bech32.fromWords(words))
|
||||
|
||||
@@ -82,6 +88,16 @@ export function decode(nip19: string): {
|
||||
}
|
||||
}
|
||||
|
||||
case 'nrelay': {
|
||||
let tlv = parseTLV(data)
|
||||
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nrelay')
|
||||
|
||||
return {
|
||||
type: 'nrelay',
|
||||
data: utf8Decoder.decode(tlv[0][0])
|
||||
}
|
||||
}
|
||||
|
||||
case 'nsec':
|
||||
case 'npub':
|
||||
case 'note':
|
||||
@@ -160,6 +176,14 @@ export function naddrEncode(addr: AddressPointer): string {
|
||||
return bech32.encode('naddr', words, Bech32MaxSize)
|
||||
}
|
||||
|
||||
export function nrelayEncode(url: string): string {
|
||||
let data = encodeTLV({
|
||||
0: [utf8Encoder.encode(url)]
|
||||
})
|
||||
let words = bech32.toWords(data)
|
||||
return bech32.encode('nrelay', words, Bech32MaxSize)
|
||||
}
|
||||
|
||||
function encodeTLV(tlv: TLV): Uint8Array {
|
||||
let entries: Uint8Array[] = []
|
||||
|
||||
|
||||
42
nip21.test.js
Normal file
42
nip21.test.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/* eslint-env jest */
|
||||
const {nip21} = require('./lib/nostr.cjs')
|
||||
|
||||
test('test', () => {
|
||||
expect(
|
||||
nip21.test(
|
||||
'nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
nip21.test(
|
||||
'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||
)
|
||||
).toBe(true)
|
||||
expect(
|
||||
nip21.test(
|
||||
' nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||
)
|
||||
).toBe(false)
|
||||
expect(nip21.test('nostr:')).toBe(false)
|
||||
expect(
|
||||
nip21.test(
|
||||
'nostr:npub108pv4cg5ag52nQq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6'
|
||||
)
|
||||
).toBe(false)
|
||||
expect(nip21.test('gggggg')).toBe(false)
|
||||
})
|
||||
|
||||
test('parse', () => {
|
||||
const result = nip21.parse(
|
||||
'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||
value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||
decoded: {
|
||||
type: 'note',
|
||||
data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b'
|
||||
}
|
||||
})
|
||||
})
|
||||
41
nip21.ts
Normal file
41
nip21.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as nip19 from './nip19'
|
||||
import * as nip21 from './nip21'
|
||||
|
||||
/**
|
||||
* 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,}/
|
||||
|
||||
/** 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)
|
||||
)
|
||||
}
|
||||
|
||||
/** Parsed Nostr URI data. */
|
||||
export interface NostrURI {
|
||||
/** Full URI including the `nostr:` protocol. */
|
||||
uri: `nostr:${string}`
|
||||
/** The bech32-encoded data (eg `npub1...`). */
|
||||
value: string
|
||||
/** Decoded bech32 string, according to NIP-19. */
|
||||
decoded: nip19.DecodeResult
|
||||
}
|
||||
|
||||
/** Parse and decode a Nostr URI. */
|
||||
export function parse(uri: string): NostrURI {
|
||||
const match = uri.match(new RegExp(`^${nip21.NOSTR_URI_REGEX.source}$`))
|
||||
if (!match) throw new Error(`Invalid Nostr URI: ${uri}`)
|
||||
return {
|
||||
uri: match[0] as `nostr:${string}`,
|
||||
value: match[1],
|
||||
decoded: nip19.decode(match[1])
|
||||
}
|
||||
}
|
||||
49
nip27.test.js
Normal file
49
nip27.test.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* eslint-env jest */
|
||||
const {nip27} = require('./lib/nostr.cjs')
|
||||
|
||||
test('matchAll', () => {
|
||||
const result = nip27.matchAll(
|
||||
'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||
)
|
||||
|
||||
expect([...result]).toEqual([
|
||||
{
|
||||
uri: 'nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6',
|
||||
value: 'npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6',
|
||||
decoded: {
|
||||
type: 'npub',
|
||||
data: '79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6'
|
||||
},
|
||||
start: 6,
|
||||
end: 75
|
||||
},
|
||||
{
|
||||
uri: 'nostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||
value: 'note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky',
|
||||
decoded: {
|
||||
type: 'note',
|
||||
data: '46d731680add2990efe1cc619dc9b8014feeb23261ab9dee50e9d11814de5a2b'
|
||||
},
|
||||
start: 78,
|
||||
end: 147
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
test('replaceAll', () => {
|
||||
const content =
|
||||
'Hello nostr:npub108pv4cg5ag52nq082kd5leu9ffrn2gdg6g4xdwatn73y36uzplmq9uyev6!\n\nnostr:note1gmtnz6q2m55epmlpe3semjdcq987av3jvx4emmjsa8g3s9x7tg4sclreky'
|
||||
|
||||
const result = nip27.replaceAll(content, ({decoded, value}) => {
|
||||
switch (decoded.type) {
|
||||
case 'npub':
|
||||
return '@alex'
|
||||
case 'note':
|
||||
return '!1234'
|
||||
default:
|
||||
return value
|
||||
}
|
||||
})
|
||||
|
||||
expect(result).toEqual('Hello @alex!\n\n!1234')
|
||||
})
|
||||
63
nip27.ts
Normal file
63
nip27.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as nip19 from './nip19'
|
||||
import * as nip21 from './nip21'
|
||||
|
||||
/** Regex to find NIP-21 URIs inside event content. */
|
||||
export const regex = () =>
|
||||
new RegExp(`\\b${nip21.NOSTR_URI_REGEX.source}\\b`, 'g')
|
||||
|
||||
/** Match result for a Nostr URI in event content. */
|
||||
export interface NostrURIMatch extends nip21.NostrURI {
|
||||
/** Index where the URI begins in the event content. */
|
||||
start: number
|
||||
/** Index where the URI ends in the event content. */
|
||||
end: number
|
||||
}
|
||||
|
||||
/** Find and decode all NIP-21 URIs. */
|
||||
export function * matchAll(content: string): Iterable<NostrURIMatch> {
|
||||
const matches = content.matchAll(regex())
|
||||
|
||||
for (const match of matches) {
|
||||
const [uri, value] = match
|
||||
|
||||
yield {
|
||||
uri: uri as `nostr:${string}`,
|
||||
value,
|
||||
decoded: nip19.decode(value),
|
||||
start: match.index!,
|
||||
end: match.index! + uri.length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all occurrences of Nostr URIs in the text.
|
||||
*
|
||||
* WARNING: using this on an HTML string is potentially unsafe!
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* nip27.replaceAll(event.content, ({ decoded, value }) => {
|
||||
* switch(decoded.type) {
|
||||
* case 'npub':
|
||||
* return renderMention(decoded)
|
||||
* case 'note':
|
||||
* return renderNote(decoded)
|
||||
* default:
|
||||
* return value
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function replaceAll(
|
||||
content: string,
|
||||
replacer: (match: nip21.NostrURI) => string
|
||||
): string {
|
||||
return content.replaceAll(regex(), (uri, value) => {
|
||||
return replacer({
|
||||
uri: uri as `nostr:${string}`,
|
||||
value,
|
||||
decoded: nip19.decode(value)
|
||||
})
|
||||
})
|
||||
}
|
||||
27
nip42.test.js
Normal file
27
nip42.test.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
require('websocket-polyfill')
|
||||
const {
|
||||
relayInit,
|
||||
generatePrivateKey,
|
||||
finishEvent,
|
||||
nip42
|
||||
} = require('./lib/nostr.cjs')
|
||||
|
||||
test('auth flow', done => {
|
||||
const relay = relayInit('wss://nostr.kollider.xyz')
|
||||
relay.connect()
|
||||
const sk = generatePrivateKey()
|
||||
|
||||
relay.on('auth', async challenge => {
|
||||
await expect(
|
||||
nip42.authenticate({
|
||||
challenge,
|
||||
relay,
|
||||
sign: e => finishEvent(e, sk)
|
||||
})
|
||||
).rejects.toBeTruthy()
|
||||
relay.close()
|
||||
done()
|
||||
})
|
||||
})
|
||||
42
nip42.ts
Normal file
42
nip42.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import {EventTemplate, Event, Kind} from './event'
|
||||
import {Relay} from './relay'
|
||||
|
||||
/**
|
||||
* Authenticate via NIP-42 flow.
|
||||
*
|
||||
* @example
|
||||
* const sign = window.nostr.signEvent
|
||||
* relay.on('auth', challenge =>
|
||||
* authenticate({ relay, sign, challenge })
|
||||
* )
|
||||
*/
|
||||
export const authenticate = async ({
|
||||
challenge,
|
||||
relay,
|
||||
sign
|
||||
}: {
|
||||
challenge: string
|
||||
relay: Relay
|
||||
sign: (e: EventTemplate) => Promise<Event>
|
||||
}): Promise<void> => {
|
||||
const e: EventTemplate = {
|
||||
kind: Kind.ClientAuth,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [
|
||||
['relay', relay.url],
|
||||
['challenge', challenge]
|
||||
],
|
||||
content: ''
|
||||
}
|
||||
const pub = relay.auth(await sign(e))
|
||||
return new Promise((resolve, reject) => {
|
||||
pub.on('ok', function ok() {
|
||||
pub.off('ok', ok)
|
||||
resolve()
|
||||
})
|
||||
pub.on('failed', function fail(reason: string) {
|
||||
pub.off('failed', fail)
|
||||
reject(reason)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nostr-tools",
|
||||
"version": "1.8.4",
|
||||
"version": "1.10.1",
|
||||
"description": "Tools for making a Nostr client.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,10 +16,10 @@
|
||||
"import": "./lib/esm/nostr.mjs",
|
||||
"require": "./lib/nostr.cjs.js"
|
||||
},
|
||||
"license": "Public domain",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.2.0",
|
||||
"@noble/secp256k1": "1.7.0",
|
||||
"@noble/secp256k1": "1.7.1",
|
||||
"@scure/base": "1.1.1",
|
||||
"@scure/bip32": "1.1.4",
|
||||
"@scure/bip39": "1.1.1"
|
||||
|
||||
4
pool.ts
4
pool.ts
@@ -2,7 +2,7 @@ import {Relay, relayInit} from './relay'
|
||||
import {normalizeURL} from './utils'
|
||||
import {Filter} from './filter'
|
||||
import {Event} from './event'
|
||||
import {SubscriptionOptions, Sub, Pub} from './relay'
|
||||
import {SubscriptionOptions, Sub, Pub, CountPayload} from './relay'
|
||||
|
||||
export class SimplePool {
|
||||
private _conn: {[url: string]: Relay}
|
||||
@@ -53,7 +53,7 @@ export class SimplePool {
|
||||
}
|
||||
|
||||
let subs: Sub[] = []
|
||||
let eventListeners: Set<(event: Event) => void> = new Set()
|
||||
let eventListeners: Set<any> = new Set()
|
||||
let eoseListeners: Set<() => void> = new Set()
|
||||
let eosesMissing = relays.length
|
||||
|
||||
|
||||
110
relay.ts
110
relay.ts
@@ -9,9 +9,14 @@ type RelayEvent = {
|
||||
disconnect: () => void | Promise<void>
|
||||
error: () => void | Promise<void>
|
||||
notice: (msg: string) => void | Promise<void>
|
||||
auth: (challenge: string) => void | Promise<void>
|
||||
}
|
||||
export type CountPayload = {
|
||||
count: number
|
||||
}
|
||||
type SubEvent = {
|
||||
event: (event: Event) => void | Promise<void>
|
||||
count: (payload: CountPayload) => void | Promise<void>
|
||||
eose: () => void | Promise<void>
|
||||
}
|
||||
export type Relay = {
|
||||
@@ -22,7 +27,12 @@ export type Relay = {
|
||||
sub: (filters: Filter[], opts?: SubscriptionOptions) => Sub
|
||||
list: (filters: Filter[], opts?: SubscriptionOptions) => Promise<Event[]>
|
||||
get: (filter: Filter, opts?: SubscriptionOptions) => Promise<Event | null>
|
||||
count: (
|
||||
filters: Filter[],
|
||||
opts?: SubscriptionOptions
|
||||
) => Promise<CountPayload | null>
|
||||
publish: (event: Event) => Pub
|
||||
auth: (event: Event) => Pub
|
||||
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(
|
||||
event: T,
|
||||
listener: U
|
||||
@@ -51,27 +61,32 @@ export type Sub = {
|
||||
|
||||
export type SubscriptionOptions = {
|
||||
id?: string
|
||||
verb?: 'REQ' | 'COUNT'
|
||||
skipVerification?: boolean
|
||||
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
|
||||
}
|
||||
|
||||
const newListeners = (): {[TK in keyof RelayEvent]: RelayEvent[TK][]} => ({
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: [],
|
||||
auth: []
|
||||
})
|
||||
|
||||
export function relayInit(
|
||||
url: string,
|
||||
options: {
|
||||
getTimeout?: number
|
||||
listTimeout?: number
|
||||
countTimeout?: number
|
||||
} = {}
|
||||
): Relay {
|
||||
let {listTimeout = 3000, getTimeout = 3000} = options
|
||||
let {listTimeout = 3000, getTimeout = 3000, countTimeout = 3000} = options
|
||||
|
||||
var ws: WebSocket
|
||||
var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {}
|
||||
var listeners: {[TK in keyof RelayEvent]: RelayEvent[TK][]} = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
error: [],
|
||||
notice: []
|
||||
}
|
||||
var listeners = newListeners()
|
||||
var subListeners: {
|
||||
[subid: string]: {[TK in keyof SubEvent]: SubEvent[TK][]}
|
||||
} = {}
|
||||
@@ -146,7 +161,7 @@ export function relayInit(
|
||||
// will naturally be caught by the encompassing try..catch block
|
||||
|
||||
switch (data[0]) {
|
||||
case 'EVENT':
|
||||
case 'EVENT': {
|
||||
let id = data[1]
|
||||
let event = data[2]
|
||||
if (
|
||||
@@ -159,6 +174,14 @@ export function relayInit(
|
||||
;(subListeners[id]?.event || []).forEach(cb => cb(event))
|
||||
}
|
||||
return
|
||||
}
|
||||
case 'COUNT':
|
||||
let id = data[1]
|
||||
let payload = data[2]
|
||||
if (openSubs[id]) {
|
||||
;(subListeners[id]?.count || []).forEach(cb => cb(payload))
|
||||
}
|
||||
return
|
||||
case 'EOSE': {
|
||||
let id = data[1]
|
||||
if (id in subListeners) {
|
||||
@@ -183,6 +206,11 @@ export function relayInit(
|
||||
let notice = data[1]
|
||||
listeners.notice.forEach(cb => cb(notice))
|
||||
return
|
||||
case 'AUTH': {
|
||||
let challenge = data[1]
|
||||
listeners.auth?.forEach(cb => cb(challenge))
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return
|
||||
@@ -220,6 +248,7 @@ export function relayInit(
|
||||
const sub = (
|
||||
filters: Filter[],
|
||||
{
|
||||
verb = 'REQ',
|
||||
skipVerification = false,
|
||||
alreadyHaveEvent = null,
|
||||
id = Math.random().toString().slice(2)
|
||||
@@ -233,7 +262,7 @@ export function relayInit(
|
||||
skipVerification,
|
||||
alreadyHaveEvent
|
||||
}
|
||||
trySend(['REQ', subid, ...filters])
|
||||
trySend([verb, subid, ...filters])
|
||||
|
||||
return {
|
||||
sub: (newFilters, newOpts = {}) =>
|
||||
@@ -253,6 +282,7 @@ export function relayInit(
|
||||
): void => {
|
||||
subListeners[subid] = subListeners[subid] || {
|
||||
event: [],
|
||||
count: [],
|
||||
eose: []
|
||||
}
|
||||
subListeners[subid][type].push(cb)
|
||||
@@ -268,6 +298,29 @@ export function relayInit(
|
||||
}
|
||||
}
|
||||
|
||||
function _publishEvent(event: Event, type: string) {
|
||||
if (!event.id) throw new Error(`event ${event} has no id`)
|
||||
let id = event.id
|
||||
|
||||
trySend([type, event])
|
||||
|
||||
return {
|
||||
on: (type: 'ok' | 'failed', cb: any) => {
|
||||
pubListeners[id] = pubListeners[id] || {
|
||||
ok: [],
|
||||
failed: []
|
||||
}
|
||||
pubListeners[id][type].push(cb)
|
||||
},
|
||||
off: (type: 'ok' | 'failed', cb: any) => {
|
||||
let listeners = pubListeners[id]
|
||||
if (!listeners) return
|
||||
let idx = listeners[type].indexOf(cb)
|
||||
if (idx >= 0) listeners[type].splice(idx, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
url,
|
||||
sub,
|
||||
@@ -318,31 +371,28 @@ export function relayInit(
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
publish(event: Event): Pub {
|
||||
if (!event.id) throw new Error(`event ${event} has no id`)
|
||||
let id = event.id
|
||||
|
||||
trySend(['EVENT', event])
|
||||
|
||||
return {
|
||||
on: (type: 'ok' | 'failed', cb: any) => {
|
||||
pubListeners[id] = pubListeners[id] || {
|
||||
ok: [],
|
||||
failed: []
|
||||
}
|
||||
pubListeners[id][type].push(cb)
|
||||
count: (filters: Filter[]): Promise<CountPayload | null> =>
|
||||
new Promise(resolve => {
|
||||
let s = sub(filters, {...sub, verb: 'COUNT'})
|
||||
let timeout = setTimeout(() => {
|
||||
s.unsub()
|
||||
resolve(null)
|
||||
}, countTimeout)
|
||||
s.on('count', (event: CountPayload) => {
|
||||
s.unsub()
|
||||
clearTimeout(timeout)
|
||||
resolve(event)
|
||||
})
|
||||
}),
|
||||
publish(event): Pub {
|
||||
return _publishEvent(event, 'EVENT')
|
||||
},
|
||||
off: (type: 'ok' | 'failed', cb: any) => {
|
||||
let listeners = pubListeners[id]
|
||||
if (!listeners) return
|
||||
let idx = listeners[type].indexOf(cb)
|
||||
if (idx >= 0) listeners[type].splice(idx, 1)
|
||||
}
|
||||
}
|
||||
auth(event): Pub {
|
||||
return _publishEvent(event, 'AUTH')
|
||||
},
|
||||
connect,
|
||||
close(): void {
|
||||
listeners = {connect: [], disconnect: [], error: [], notice: []}
|
||||
listeners = newListeners()
|
||||
subListeners = {}
|
||||
pubListeners = {}
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
|
||||
Reference in New Issue
Block a user