nappup 1.0.9 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,10 +10,10 @@ export const seedRelays = [
10
10
  'wss://indexer.coracle.social'
11
11
  ]
12
12
  export const freeRelays = [
13
- 'wss://relay.damus.io',
14
- 'wss://relay.nostr.band',
13
+ 'wss://relay.primal.net',
15
14
  'wss://nos.lol',
16
- 'wss://relay.primal.net'
15
+ 'wss://relay.damus.io',
16
+ 'wss://relay.nostr.band'
17
17
  ]
18
18
 
19
19
  // Interacts with Nostr relays.
@@ -27,7 +27,10 @@ export class NostrRelays {
27
27
  if (this.#relays.has(url)) {
28
28
  clearTimeout(this.#relayTimeouts.get(url))
29
29
  this.#relayTimeouts.set(url, maybeUnref(setTimeout(() => this.disconnect(url), this.#timeout)))
30
- return this.#relays.get(url)
30
+ const relay = this.#relays.get(url)
31
+ // reconnect if needed to avoid SendingOnClosedConnection errors
32
+ await relay.connect()
33
+ return relay
31
34
  }
32
35
 
33
36
  const relay = new Relay(url)
@@ -61,55 +64,84 @@ export class NostrRelays {
61
64
  // Get events from a list of relays
62
65
  async getEvents (filter, relays, timeout = 5000) {
63
66
  const events = []
67
+ const resolveOrReject = (resolve, reject, err) => {
68
+ err ? reject(err) : resolve()
69
+ }
64
70
  const promises = relays.map(async (url) => {
71
+ let sub
72
+ let isClosed = false
73
+ const p = Promise.withResolvers()
74
+ const timer = maybeUnref(setTimeout(() => {
75
+ sub?.close()
76
+ isClosed = true
77
+ resolveOrReject(p.resolve, p.reject, new Error(`timeout: ${url}`))
78
+ }, timeout))
65
79
  try {
66
80
  const relay = await this.#getRelay(url)
67
- return new Promise((resolve) => {
68
- const sub = relay.subscribe([filter], {
69
- onevent: (event) => {
70
- events.push(event)
71
- },
72
- onclose: () => {
73
- clearTimeout(timer)
74
- resolve()
75
- },
76
- oneose: () => {
77
- clearTimeout(timer)
78
- resolve()
79
- }
80
- })
81
- const timer = maybeUnref(setTimeout(() => {
81
+ sub = relay.subscribe([filter], {
82
+ onevent: (event) => {
83
+ events.push(event)
84
+ },
85
+ onclose: err => {
86
+ clearTimeout(timer)
87
+ if (isClosed) return
88
+ resolveOrReject(p.resolve, p.reject, err /* may be empty (closed normally) */)
89
+ },
90
+ oneose: () => {
91
+ clearTimeout(timer)
92
+ isClosed = true
82
93
  sub.close()
83
- resolve()
84
- }, timeout))
94
+ p.resolve()
95
+ }
85
96
  })
86
- } catch (error) {
87
- console.error(`Failed to get events from ${url}`, error)
97
+
98
+ await p.promise
99
+ } catch (err) {
100
+ clearTimeout(timer)
101
+ p.reject(err)
88
102
  }
89
103
  })
90
104
 
91
105
  const results = await Promise.allSettled(promises)
92
- if (results.some(v => v.status === 'rejected')) throw new Error(results[0].reason)
93
- return events
106
+ const rejectedResults = results.filter(v => v.status === 'rejected')
107
+
108
+ return {
109
+ result: events,
110
+ errors: rejectedResults.map(v => ({ reason: v.reason, relay: relays[results.indexOf(v)] })),
111
+ success: events.length > 0 || results.length !== rejectedResults.length
112
+ }
94
113
  }
95
114
 
96
115
  // Send an event to a list of relays.
97
116
  async sendEvent (event, relays, timeout = 3000) {
98
117
  const promises = relays.map(async (url) => {
118
+ let timer
99
119
  try {
100
- const relay = await this.#getRelay(url)
101
- const timer = maybeUnref(setTimeout(() => {
102
- throw new Error(`Timeout sending event to ${url}`)
120
+ timer = maybeUnref(setTimeout(() => {
121
+ throw new Error(`timeout: ${url}`)
103
122
  }, timeout))
123
+ const relay = await this.#getRelay(url)
104
124
  await relay.publish(event)
125
+ } catch (err) {
126
+ if (err.message?.startsWith('duplicate:')) return
127
+ if (err.message?.startsWith('mute:')) {
128
+ console.info(`${url} - ${err.message}`)
129
+ return
130
+ }
131
+ throw err
132
+ } finally {
105
133
  clearTimeout(timer)
106
- } catch (error) {
107
- console.error(`Failed to send event to ${url}`, error)
108
134
  }
109
135
  })
110
136
 
111
137
  const results = await Promise.allSettled(promises)
112
- if (results.some(v => v.status === 'rejected')) throw new Error(results[0].reason)
138
+ const rejectedResults = results.filter(v => v.status === 'rejected')
139
+
140
+ return {
141
+ result: null,
142
+ errors: rejectedResults.map(v => ({ reason: v.reason, relay: relays[results.indexOf(v)] })),
143
+ success: results.length !== rejectedResults.length
144
+ }
113
145
  }
114
146
  }
115
147
  // Share same connection.
@@ -2,10 +2,11 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import * as dotenv from 'dotenv'
5
- import { getPublicKey, finalizeEvent } from 'nostr-tools/pure'
5
+ import { getPublicKey } from 'nostr-tools/pure'
6
6
  import { getConversationKey, encrypt, decrypt } from 'nostr-tools/nip44'
7
7
  import nostrRelays, { seedRelays, freeRelays } from '#services/nostr-relays.js'
8
8
  import { bytesToBase16, base16ToBytes } from '#helpers/base16.js'
9
+ import { finalizeEvent } from '#helpers/nip01.js'
9
10
  const __dirname = fileURLToPath(new URL('.', import.meta.url))
10
11
 
11
12
  const dotenvPath = process.env.DOTENV_CONFIG_PATH ?? `${__dirname}/../../.env`
@@ -54,7 +55,7 @@ export default class NostrSigner {
54
55
  async getRelays () {
55
56
  if (this.relays) return this.relays
56
57
 
57
- const relayLists = await nostrRelays.getEvents({ authors: [await this.getPublicKey()], kinds: [10002], limit: 1 }, seedRelays)
58
+ const relayLists = (await nostrRelays.getEvents({ authors: [await this.getPublicKey()], kinds: [10002], limit: 1 }, seedRelays)).result
58
59
  const relayList = relayLists.sort((a, b) => b.created_at - a.created_at)[0]
59
60
  const rTags = (relayList?.tags ?? []).filter(v => v[0] === 'r' && /^wss?:\/\//.test(v[1]))
60
61
  if (rTags.length === 0) return (this.relays = await this.#initRelays())
package/lib/GEMINI.md DELETED
@@ -1,4 +0,0 @@
1
- # lib folder
2
-
3
- This is the place to add third-party libraries that don't have a
4
- published npm package.
package/lib/base122.js DELETED
@@ -1,171 +0,0 @@
1
- // https://github.com/kevinAlbs/Base122/commit/b62945c2733fa4da8792a1071e40a8b326e8dd1b
2
- // Provides functions for encoding/decoding data to and from base-122.
3
-
4
- const kString = 0
5
- const kUint8Array = 1
6
- const kDebug = false
7
- const kIllegals = [
8
- 0, // null
9
- 10, // newline
10
- 13, // carriage return
11
- 34, // double quote
12
- 38, // ampersand
13
- 92 // backslash
14
- ]
15
- const kShortened = 0b111 // Uses the illegal index to signify the last two-byte char encodes <= 7 bits.
16
-
17
- /**
18
- * Encodes raw data into base-122.
19
- * @param {Uint8Array|Buffer|Array|String} rawData - The data to be encoded. This can be an array
20
- * or Buffer with raw data bytes or a string of bytes (i.e. the type of argument to btoa())
21
- * @returns {Array} The base-122 encoded data as a regular array of UTF-8 character byte values.
22
- */
23
- function encode (rawData) {
24
- const dataType = typeof (rawData) === 'string' ? kString : kUint8Array
25
- let curIndex = 0
26
- let curBit = 0 // Points to current bit needed
27
- // const curMask = 0b10000000
28
- const outData = []
29
- let getByte = i => rawData[i]
30
-
31
- if (dataType === kString) {
32
- getByte = (i) => {
33
- const val = rawData.codePointAt(i)
34
- if (val > 255) {
35
- throw new Error('Unexpected code point at position: ' + i + '. Expected value [0,255]. Got: ' + val)
36
- }
37
- return val
38
- }
39
- }
40
-
41
- // Get seven bits of input data. Returns false if there is no input left.
42
- function get7 () {
43
- if (curIndex >= rawData.length) return false
44
- // Shift, mask, unshift to get first part.
45
- const firstByte = getByte(curIndex)
46
- let firstPart = ((0b11111110 >>> curBit) & firstByte) << curBit
47
- // Align it to a seven bit chunk.
48
- firstPart >>= 1
49
- // Check if we need to go to the next byte for more bits.
50
- curBit += 7
51
- if (curBit < 8) return firstPart // Do not need next byte.
52
- curBit -= 8
53
- curIndex++
54
- // Now we want bits [0..curBit] of the next byte if it exists.
55
- if (curIndex >= rawData.length) return firstPart
56
- const secondByte = getByte(curIndex)
57
- let secondPart = ((0xFF00 >>> curBit) & secondByte) & 0xFF
58
- // Align it.
59
- secondPart >>= 8 - curBit
60
- return firstPart | secondPart
61
- }
62
-
63
- while (true) {
64
- // Grab 7 bits.
65
- const bits = get7()
66
- if (bits === false) break
67
- debugLog('Seven input bits', print7Bits(bits), bits)
68
-
69
- const illegalIndex = kIllegals.indexOf(bits)
70
- if (illegalIndex !== -1) {
71
- // Since this will be a two-byte character, get the next chunk of seven bits.
72
- let nextBits = get7()
73
- debugLog('Handle illegal sequence', print7Bits(bits), print7Bits(nextBits))
74
-
75
- let b1 = 0b11000010; let b2 = 0b10000000
76
- if (nextBits === false) {
77
- debugLog('Last seven bits are an illegal sequence.')
78
- b1 |= (0b111 & kShortened) << 2
79
- nextBits = bits // Encode these bits after the shortened signifier.
80
- } else {
81
- b1 |= (0b111 & illegalIndex) << 2
82
- }
83
-
84
- // Push first bit onto first byte, remaining 6 onto second.
85
- const firstBit = (nextBits & 0b01000000) > 0 ? 1 : 0
86
- b1 |= firstBit
87
- b2 |= nextBits & 0b00111111
88
- outData.push(b1)
89
- outData.push(b2)
90
- } else {
91
- outData.push(bits)
92
- }
93
- }
94
- return outData
95
- }
96
-
97
- /**
98
- * Decodes base-122 encoded data back to the original data.
99
- * @param {Uint8Array|Buffer|String} rawData - The data to be decoded. This can be a Uint8Array
100
- * or Buffer with raw data bytes or a string of bytes (i.e. the type of argument to btoa())
101
- * @returns {Array} The data in a regular array representing byte values.
102
- */
103
- function decode (base122Data) {
104
- const strData = typeof (base122Data) === 'string' ? base122Data : utf8DataToString(base122Data)
105
- const decoded = []
106
- // const decodedIndex = 0
107
- let curByte = 0
108
- let bitOfByte = 0
109
-
110
- function push7 (byte) {
111
- byte <<= 1
112
- // Align this byte to offset for current byte.
113
- curByte |= (byte >>> bitOfByte)
114
- bitOfByte += 7
115
- if (bitOfByte >= 8) {
116
- decoded.push(curByte)
117
- bitOfByte -= 8
118
- // Now, take the remainder, left shift by what has been taken.
119
- curByte = (byte << (7 - bitOfByte)) & 255
120
- }
121
- }
122
-
123
- for (let i = 0; i < strData.length; i++) {
124
- const c = strData.charCodeAt(i)
125
- // Check if this is a two-byte character.
126
- if (c > 127) {
127
- // Note, the charCodeAt will give the codePoint, thus
128
- // 0b110xxxxx 0b10yyyyyy will give => xxxxxyyyyyy
129
- const illegalIndex = (c >>> 8) & 7 // 7 = 0b111.
130
- // We have to first check if this is a shortened two-byte character, i.e. if it only
131
- // encodes <= 7 bits.
132
- if (illegalIndex !== kShortened) push7(kIllegals[illegalIndex])
133
- // Always push the rest.
134
- push7(c & 127)
135
- } else {
136
- // One byte characters can be pushed directly.
137
- push7(c)
138
- }
139
- }
140
- return decoded
141
- }
142
-
143
- /**
144
- * Converts a sequence of UTF-8 bytes to a string.
145
- * @param {Uint8Array|Buffer} data - The UTF-8 data.
146
- * @returns {String} A string with each character representing a code point.
147
- */
148
- function utf8DataToString (data) {
149
- return Buffer.from(data).toString('utf-8')
150
- }
151
-
152
- // For debugging.
153
- function debugLog () {
154
- if (kDebug) console.log(...arguments)
155
- }
156
-
157
- // For debugging.
158
- function print7Bits (num) {
159
- return '0000000'.substring(num.toString(2).length) + num.toString(2)
160
- }
161
-
162
- // For debugging.
163
- // eslint-disable-next-line no-unused-vars
164
- function print8Bits (num) {
165
- return '00000000'.substring(num.toString(2).length) + num.toString(2)
166
- }
167
-
168
- export {
169
- encode,
170
- decode
171
- }
@@ -1,56 +0,0 @@
1
- import { decode } from '#lib/base122.js'
2
-
3
- // Decodes data from base122.
4
- export default class Base122Decoder {
5
- textEncoder = new TextEncoder()
6
-
7
- constructor (source, { mimeType = '' } = {}) {
8
- this.sourceIterator = source?.[Symbol.iterator]?.() || source?.[Symbol.asyncIterator]?.() || source()
9
- this.isText = mimeType.startsWith('text/')
10
- if (this.isText) this.textDecoder = new TextDecoder()
11
- }
12
-
13
- // decoder generator
14
- * [Symbol.iterator] (base122String) {
15
- let bytes
16
- if (this.isText) {
17
- while (base122String) {
18
- bytes = this.textEncoder.encode(base122String) // from string to UInt8Array
19
- // stream=true avoids cutting a multi-byte character
20
- base122String = yield this.textDecoder.decode(new Uint8Array(decode(bytes)), { stream: true })
21
- }
22
- } else {
23
- while (base122String) {
24
- bytes = this.textEncoder.encode(base122String)
25
- base122String = yield new Uint8Array(decode(bytes))
26
- }
27
- }
28
- }
29
-
30
- // Gets the decoded data.
31
- getDecoded () { return iteratorToStream(this, this.sourceIterator) }
32
- }
33
-
34
- function iteratorToStream (decoder, sourceIterator) {
35
- return new ReadableStream({
36
- decoderIterator: null,
37
- async start (controller) {
38
- const { value: chunk, done } = await sourceIterator.next()
39
- if (done) return controller.close()
40
-
41
- // Pass first chunk when instantiating the decoder generator
42
- this.decoderIterator = decoder[Symbol.iterator](chunk)
43
- const { value } = this.decoderIterator.next()
44
- if (value) controller.enqueue(value)
45
- },
46
- async pull (controller) {
47
- if (!this.decoderIterator) return
48
-
49
- const { value: chunk, done: sourceDone } = await sourceIterator.next()
50
- const { value, done } = this.decoderIterator.next(chunk)
51
-
52
- if (value) controller.enqueue(value)
53
- if (done || sourceDone) controller.close()
54
- }
55
- })
56
- }
@@ -1,19 +0,0 @@
1
- import { encode } from '#lib/base122.js'
2
-
3
- // Encodes data using base122.
4
- export default class Base122Encoder {
5
- textDecoder = new TextDecoder()
6
- // The encoded data.
7
- encoded = ''
8
-
9
- // Updates the encoded data with the given bytes.
10
- update (bytes) {
11
- this.encoded += this.textDecoder.decode(new Uint8Array(encode(bytes)))
12
- return this
13
- }
14
-
15
- // Gets the encoded data.
16
- getEncoded () {
17
- return this.encoded
18
- }
19
- }