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.
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/bin/nappup/helpers.js +5 -1
- package/bin/nappup/index.js +2 -2
- package/package.json +4 -2
- package/src/helpers/app-metadata.js +54 -0
- package/src/helpers/app.js +3 -3
- package/src/helpers/base36.js +6 -0
- package/src/helpers/base62.js +5 -5
- package/src/helpers/nip01.js +32 -0
- package/src/helpers/nip19.js +5 -3
- package/src/helpers/stream.js +13 -0
- package/src/index.js +278 -21
- package/src/services/base93-decoder.js +107 -0
- package/src/services/base93-encoder.js +96 -0
- package/src/services/nostr-relays.js +63 -31
- package/src/services/nostr-signer.js +3 -2
- package/lib/GEMINI.md +0 -4
- package/lib/base122.js +0 -171
- package/src/services/base122-decoder.js +0 -56
- package/src/services/base122-encoder.js +0 -19
|
@@ -10,10 +10,10 @@ export const seedRelays = [
|
|
|
10
10
|
'wss://indexer.coracle.social'
|
|
11
11
|
]
|
|
12
12
|
export const freeRelays = [
|
|
13
|
-
'wss://relay.
|
|
14
|
-
'wss://relay.nostr.band',
|
|
13
|
+
'wss://relay.primal.net',
|
|
15
14
|
'wss://nos.lol',
|
|
16
|
-
'wss://relay.
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
}
|
|
94
|
+
p.resolve()
|
|
95
|
+
}
|
|
85
96
|
})
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
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
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
|
-
}
|