libp2r2p 0.8.0 → 0.10.0
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/README.md +72 -45
- package/base16/index.js +6 -3
- package/base36/index.js +12 -8
- package/base62/index.js +15 -11
- package/base64/index.js +18 -2
- package/base93/index.js +19 -7
- package/content-key/event/index.js +40 -12
- package/double-dh/index.js +21 -6
- package/ecdh/index.js +12 -1
- package/error/index.js +32 -0
- package/event/helpers/serialize.js +31 -0
- package/event/index.js +116 -0
- package/idb/index.js +5 -3
- package/idb-queue/index.js +21 -20
- package/index.js +10 -1
- package/key/index.js +31 -18
- package/kind/index.js +244 -0
- package/nip04/index.js +47 -0
- package/nip05/index.js +61 -0
- package/nip19/index.js +176 -43
- package/nip44/helpers.js +95 -0
- package/nip44/index.js +14 -0
- package/nip44-v3/index.js +35 -16
- package/nip46/helpers/frame.js +6 -6
- package/nip46/helpers/url.js +8 -6
- package/nip46/services/bunker-signer.js +7 -6
- package/nip46/services/client.js +8 -7
- package/nip46/services/server-session.js +8 -7
- package/nip46/services/transport.js +9 -8
- package/nip96/index.js +285 -0
- package/nip98/index.js +56 -0
- package/nwt/index.js +241 -0
- package/package.json +22 -5
- package/private-channel/helpers/chunks.js +7 -6
- package/private-channel/helpers/event.js +5 -4
- package/private-channel/index.js +63 -61
- package/private-channel/services/received-chunks.js +4 -3
- package/private-message/index.js +32 -31
- package/private-messenger/index.js +23 -22
- package/private-messenger/recovery/index.js +15 -14
- package/private-messenger/services/channel-state.js +2 -1
- package/relay/helpers/hll.js +3 -1
- package/relay/services/query.js +3 -3
- package/relay/services/relay-connection.js +222 -121
- package/relay/services/relay-pool.js +13 -10
- package/temporary-storage/index.js +3 -1
- package/url/index.js +131 -0
- package/web-storage-queue/index.js +8 -6
- package/i18n/index.js +0 -235
package/nip44-v3/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { concatBytes, utf8ToBytes } from '@noble/hashes/utils.js'
|
|
|
5
5
|
import { chacha20 } from '@noble/ciphers/chacha.js'
|
|
6
6
|
import { bytesToBase64, base64ToBytes } from '../base64/index.js'
|
|
7
7
|
import { sharedXOnlySecret } from '../ecdh/index.js'
|
|
8
|
+
import { ValidationError } from '../error/index.js'
|
|
8
9
|
|
|
9
10
|
// NIP-44 v3 — local implementation (spec.nostr.land/nip44v3)
|
|
10
11
|
// Copied from the bunker testbench and verified against the vendored
|
|
@@ -34,7 +35,7 @@ function readU32be (b, off) {
|
|
|
34
35
|
return new DataView(b.buffer, b.byteOffset, b.byteLength).getUint32(off, false)
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
function
|
|
38
|
+
function areBytesEqual (a, b) {
|
|
38
39
|
if (a.length !== b.length) return false
|
|
39
40
|
let diff = 0
|
|
40
41
|
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i]
|
|
@@ -47,7 +48,15 @@ function randomBytes32 () {
|
|
|
47
48
|
return bytes
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function assertBytes (value, code, length) {
|
|
52
|
+
if (!(value instanceof Uint8Array) || (length !== undefined && value.length !== length)) {
|
|
53
|
+
throw new ValidationError(code)
|
|
54
|
+
}
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
export function deriveKeys (seckey, pubkey, nonce) {
|
|
59
|
+
assertBytes(nonce, 'INVALID_NONCE', 32)
|
|
51
60
|
const shared = sharedXOnlySecret(seckey, pubkey)
|
|
52
61
|
const salt = concatBytes(utf8ToBytes('nip44-v3\x00'), nonce)
|
|
53
62
|
const prk = hkdfExtract(sha256, shared, salt)
|
|
@@ -71,6 +80,8 @@ export function payloadByteLength (plaintextByteLength, scopeByteLength = 0) {
|
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
export function deriveKeysFromConversationKey (conversationKey, nonce) {
|
|
83
|
+
assertBytes(conversationKey, 'INVALID_CONVERSATION_KEY', 32)
|
|
84
|
+
assertBytes(nonce, 'INVALID_NONCE', 32)
|
|
74
85
|
const salt = concatBytes(utf8ToBytes('nip44-v3\x00'), nonce)
|
|
75
86
|
const prk = hkdfExtract(sha256, conversationKey, salt)
|
|
76
87
|
return {
|
|
@@ -87,6 +98,11 @@ export function encryptBytes (seckey, pubkey, kind, scope, plaintext, nonce) {
|
|
|
87
98
|
|
|
88
99
|
export function encryptWithConversationKeyBytes (conversationKey, kind, scope, plaintext, nonce) {
|
|
89
100
|
nonce ??= randomBytes32()
|
|
101
|
+
assertBytes(conversationKey, 'INVALID_CONVERSATION_KEY', 32)
|
|
102
|
+
kind = normalizeKind(kind)
|
|
103
|
+
assertBytes(scope, 'INVALID_SCOPE')
|
|
104
|
+
assertBytes(plaintext, 'INVALID_PLAINTEXT')
|
|
105
|
+
assertBytes(nonce, 'INVALID_NONCE', 32)
|
|
90
106
|
const { encryption_key: encryptionKey, mac_key: macKey } = deriveKeysFromConversationKey(conversationKey, nonce)
|
|
91
107
|
const prefixed = concatBytes(u32be(plaintext.length), plaintext)
|
|
92
108
|
const padded = new Uint8Array(targetSize(prefixed.length))
|
|
@@ -102,34 +118,37 @@ export function decryptBytes (seckey, pubkey, expectedKind, expectedScope, ciphe
|
|
|
102
118
|
}
|
|
103
119
|
|
|
104
120
|
export function decryptWithConversationKeyBytes (conversationKey, expectedKind, expectedScope, ciphertext) {
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
assertBytes(conversationKey, 'INVALID_CONVERSATION_KEY', 32)
|
|
122
|
+
expectedKind = normalizeKind(expectedKind)
|
|
123
|
+
assertBytes(expectedScope, 'INVALID_SCOPE')
|
|
124
|
+
if (typeof ciphertext !== 'string' || ciphertext.length === 0) throw new ValidationError('EMPTY_CIPHERTEXT', { message: 'empty ciphertext' })
|
|
125
|
+
if (ciphertext[0] === '#') throw new ValidationError('UNSUPPORTED_NIP44_VERSION', { message: 'unsupported future version' })
|
|
107
126
|
let decoded
|
|
108
|
-
try { decoded = base64ToBytes(ciphertext) } catch { throw new
|
|
109
|
-
if (decoded.length < 77) throw new
|
|
110
|
-
if (decoded[0] !== VERSION) throw new
|
|
127
|
+
try { decoded = base64ToBytes(ciphertext) } catch (cause) { throw new ValidationError('INVALID_BASE64', { message: 'invalid base64', cause }) }
|
|
128
|
+
if (decoded.length < 77) throw new ValidationError('NIP44_CIPHERTEXT_TOO_SHORT', { message: 'ciphertext too short' })
|
|
129
|
+
if (decoded[0] !== VERSION) throw new ValidationError('UNSUPPORTED_NIP44_VERSION', { message: `unsupported version ${decoded[0]}` })
|
|
111
130
|
const nonce = decoded.subarray(1, 33)
|
|
112
131
|
const mac = decoded.subarray(33, 65)
|
|
113
132
|
const kind = readU32be(decoded, 65)
|
|
114
133
|
const scopeLength = readU32be(decoded, 69)
|
|
115
|
-
if (scopeLength > decoded.length - 73) throw new
|
|
134
|
+
if (scopeLength > decoded.length - 73) throw new ValidationError('INVALID_NIP44_SCOPE_LENGTH', { message: 'invalid scope length' })
|
|
116
135
|
const scope = decoded.subarray(73, 73 + scopeLength)
|
|
117
|
-
try { fatalTextDecoder.decode(scope) } catch { throw new
|
|
136
|
+
try { fatalTextDecoder.decode(scope) } catch (cause) { throw new ValidationError('INVALID_NIP44_SCOPE_UTF8', { message: 'scope is not valid UTF-8', cause }) }
|
|
118
137
|
const ct = decoded.subarray(73 + scopeLength)
|
|
119
|
-
if (ct.length < 4) throw new
|
|
120
|
-
if (kind !== expectedKind) throw new
|
|
121
|
-
if (!
|
|
138
|
+
if (ct.length < 4) throw new ValidationError('NIP44_CIPHERTEXT_TOO_SHORT', { message: 'ciphertext too short' })
|
|
139
|
+
if (kind !== expectedKind) throw new ValidationError('NIP44_KIND_MISMATCH', { message: `kind mismatch: got ${kind}, expected ${expectedKind}` })
|
|
140
|
+
if (!areBytesEqual(scope, expectedScope)) throw new ValidationError('NIP44_SCOPE_MISMATCH', { message: 'scope mismatch' })
|
|
122
141
|
const { encryption_key: encryptionKey, mac_key: macKey } = deriveKeysFromConversationKey(conversationKey, nonce)
|
|
123
142
|
const authData = concatBytes(nonce, u32be(kind), u32be(scope.length), scope, ct)
|
|
124
|
-
if (!
|
|
143
|
+
if (!areBytesEqual(mac, hmac(sha256, macKey, authData))) throw new ValidationError('INVALID_MAC', { message: 'invalid MAC' })
|
|
125
144
|
const padded = chacha(encryptionKey, ct)
|
|
126
145
|
const plaintextLength = readU32be(padded, 0)
|
|
127
|
-
if (plaintextLength + 4 > padded.length) throw new
|
|
128
|
-
if (plaintextLength > 2 ** 31 - 1) throw new
|
|
146
|
+
if (plaintextLength + 4 > padded.length) throw new ValidationError('INVALID_PLAINTEXT_LENGTH', { message: 'invalid plaintext length' })
|
|
147
|
+
if (plaintextLength > 2 ** 31 - 1) throw new ValidationError('PLAINTEXT_TOO_LONG', { message: 'plaintext too long' })
|
|
129
148
|
// Only verify the padding is all-zeroes. Per spec, implementations MUST NOT do any
|
|
130
149
|
// other check on the padding length — non-standard zero-padding must decrypt.
|
|
131
150
|
const padding = padded.subarray(4 + plaintextLength)
|
|
132
|
-
if (!
|
|
151
|
+
if (!areBytesEqual(padding, new Uint8Array(padding.length))) throw new ValidationError('INVALID_PADDING', { message: 'invalid padding' })
|
|
133
152
|
return padded.subarray(4, 4 + plaintextLength)
|
|
134
153
|
}
|
|
135
154
|
|
|
@@ -139,7 +158,7 @@ function deriveSharedConversationKey (seckey, pubkey) {
|
|
|
139
158
|
|
|
140
159
|
export function normalizeKind (kind) {
|
|
141
160
|
const n = typeof kind === 'string' && kind.trim() !== '' ? Number(kind) : kind
|
|
142
|
-
if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) throw new
|
|
161
|
+
if (!Number.isInteger(n) || n < 0 || n > 0xffffffff) throw new ValidationError('INVALID_KIND')
|
|
143
162
|
return n
|
|
144
163
|
}
|
|
145
164
|
|
package/nip46/helpers/frame.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { finalizeEvent,
|
|
2
|
-
import { decrypt, encrypt, getConversationKey } from '
|
|
1
|
+
import { finalizeEvent, isValidEvent } from '../../event/index.js'
|
|
2
|
+
import { decrypt, encrypt, getConversationKey } from '../../nip44/index.js'
|
|
3
3
|
import { NIP46_KIND } from '../constants/index.js'
|
|
4
4
|
|
|
5
5
|
const PUBKEY = /^[0-9a-f]{64}$/
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function isValidPubkey (value) {
|
|
8
8
|
return typeof value === 'string' && PUBKEY.test(value)
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -14,9 +14,9 @@ export function hasPTag (event, pubkey) {
|
|
|
14
14
|
|
|
15
15
|
export function isNip46EventFor (event, pubkey) {
|
|
16
16
|
return event?.kind === NIP46_KIND &&
|
|
17
|
-
|
|
17
|
+
isValidPubkey(event.pubkey) &&
|
|
18
18
|
hasPTag(event, pubkey) &&
|
|
19
|
-
|
|
19
|
+
isValidEvent(event)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function decodeNip46Frame (event, secretKey) {
|
|
@@ -38,7 +38,7 @@ export function createNip46Event ({ secretKey, recipientPubkey, payload }) {
|
|
|
38
38
|
}, secretKey)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export function
|
|
41
|
+
export function isValidRequestFrame (frame) {
|
|
42
42
|
return typeof frame?.id === 'string' && frame.id &&
|
|
43
43
|
typeof frame.method === 'string' && frame.method &&
|
|
44
44
|
Array.isArray(frame.params) && frame.params.every(param => typeof param === 'string')
|
package/nip46/helpers/url.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
2
|
+
|
|
1
3
|
const PUBKEY = /^[0-9a-f]{64}$/
|
|
2
4
|
|
|
3
5
|
function uniqueStrings (values) {
|
|
@@ -5,7 +7,7 @@ function uniqueStrings (values) {
|
|
|
5
7
|
return [...new Set(values.filter(value => typeof value === 'string' && value))]
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
function
|
|
10
|
+
function isValidPubkey (value) {
|
|
9
11
|
return typeof value === 'string' && PUBKEY.test(value)
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -13,7 +15,7 @@ export function normalizeBunkerPointer (pointer) {
|
|
|
13
15
|
if (!pointer || typeof pointer !== 'object') return null
|
|
14
16
|
const remoteSignerPubkey = String(pointer.remoteSignerPubkey || '').toLowerCase()
|
|
15
17
|
const relays = uniqueStrings(pointer.relays)
|
|
16
|
-
if (!
|
|
18
|
+
if (!isValidPubkey(remoteSignerPubkey) || !relays.length) return null
|
|
17
19
|
return {
|
|
18
20
|
remoteSignerPubkey,
|
|
19
21
|
relays,
|
|
@@ -39,7 +41,7 @@ export function parseBunkerUrl (input) {
|
|
|
39
41
|
// Serializes a direct NIP-46 bunker pointer without any network lookup.
|
|
40
42
|
export function toBunkerUrl (pointer) {
|
|
41
43
|
const normalized = normalizeBunkerPointer(pointer)
|
|
42
|
-
if (!normalized) throw new
|
|
44
|
+
if (!normalized) throw new ValidationError('INVALID_BUNKER_POINTER')
|
|
43
45
|
const url = new URL(`bunker://${normalized.remoteSignerPubkey}`)
|
|
44
46
|
for (const relay of normalized.relays) url.searchParams.append('relay', relay)
|
|
45
47
|
if (normalized.secret) url.searchParams.set('secret', normalized.secret)
|
|
@@ -58,8 +60,8 @@ export function createNostrConnectURI ({
|
|
|
58
60
|
} = {}) {
|
|
59
61
|
const normalizedPubkey = String(clientPubkey || '').toLowerCase()
|
|
60
62
|
const normalizedRelays = uniqueStrings(relays)
|
|
61
|
-
if (!
|
|
62
|
-
throw new
|
|
63
|
+
if (!isValidPubkey(normalizedPubkey) || !normalizedRelays.length || typeof secret !== 'string' || !secret) {
|
|
64
|
+
throw new ValidationError('INVALID_NOSTRCONNECT_URI')
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
const uri = new URL(`nostrconnect://${normalizedPubkey}`)
|
|
@@ -78,7 +80,7 @@ export function parseNostrConnectURI (input) {
|
|
|
78
80
|
const clientPubkey = url.hostname.toLowerCase()
|
|
79
81
|
const relays = uniqueStrings(url.searchParams.getAll('relay'))
|
|
80
82
|
const secret = url.searchParams.get('secret') || ''
|
|
81
|
-
if (url.protocol !== 'nostrconnect:' || !
|
|
83
|
+
if (url.protocol !== 'nostrconnect:' || !isValidPubkey(clientPubkey) || !relays.length || !secret) return null
|
|
82
84
|
return {
|
|
83
85
|
clientPubkey,
|
|
84
86
|
relays,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
2
|
+
import { isValidEvent } from '../../event/index.js'
|
|
3
|
+
import { isValidPubkey } from '../helpers/frame.js'
|
|
3
4
|
import { Nip46Client } from './client.js'
|
|
4
5
|
|
|
5
6
|
// A NIP-46 remote signer with the standard Nostr signing commands.
|
|
@@ -14,7 +15,7 @@ export class BunkerSigner extends Nip46Client {
|
|
|
14
15
|
async getPublicKey (options) {
|
|
15
16
|
if (!options?.extension && this.#cachedPubkey) return this.#cachedPubkey
|
|
16
17
|
const pubkey = await this.sendRequest('get_public_key', [], options)
|
|
17
|
-
if (!
|
|
18
|
+
if (!isValidPubkey(pubkey)) throw new ValidationError('NIP46_INVALID_PUBLIC_KEY')
|
|
18
19
|
if (!options?.extension) this.#cachedPubkey = pubkey
|
|
19
20
|
return pubkey
|
|
20
21
|
}
|
|
@@ -24,10 +25,10 @@ export class BunkerSigner extends Nip46Client {
|
|
|
24
25
|
let signed
|
|
25
26
|
try {
|
|
26
27
|
signed = JSON.parse(response)
|
|
27
|
-
} catch {
|
|
28
|
-
throw new
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
throw new ValidationError('NIP46_INVALID_SIGNED_EVENT', { cause })
|
|
29
30
|
}
|
|
30
|
-
if (!
|
|
31
|
+
if (!isValidEvent(signed)) throw new ValidationError('NIP46_INVALID_SIGNED_EVENT')
|
|
31
32
|
return signed
|
|
32
33
|
}
|
|
33
34
|
|
package/nip46/services/client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
2
|
+
import { getPublicKey } from '../../key/index.js'
|
|
2
3
|
import { relayPool as defaultRelayPool } from '../../relay/index.js'
|
|
3
4
|
import {
|
|
4
5
|
DEFAULT_TIMEOUT,
|
|
@@ -10,10 +11,10 @@ import {
|
|
|
10
11
|
decodeNip46Frame,
|
|
11
12
|
isNip46EventFor,
|
|
12
13
|
requestError,
|
|
13
|
-
|
|
14
|
+
isValidRequestFrame
|
|
14
15
|
} from '../helpers/frame.js'
|
|
15
16
|
import { normalizeBunkerPointer, parseNostrConnectURI } from '../helpers/url.js'
|
|
16
|
-
import {
|
|
17
|
+
import { areRelaySetsEqual, Nip46Transport, waitForNip46 } from './transport.js'
|
|
17
18
|
|
|
18
19
|
function cleanClientMetadata (value) {
|
|
19
20
|
if (!value || typeof value !== 'object') return null
|
|
@@ -41,7 +42,7 @@ export class Nip46Client {
|
|
|
41
42
|
timeoutAfterFirstEose = DEFAULT_TIMEOUT_AFTER_FIRST_EOSE
|
|
42
43
|
} = {}) {
|
|
43
44
|
const normalized = normalizeBunkerPointer(pointer)
|
|
44
|
-
if (!normalized) throw new
|
|
45
|
+
if (!normalized) throw new ValidationError('INVALID_BUNKER_POINTER')
|
|
45
46
|
this.#secretKey = clientSecretKey
|
|
46
47
|
this.#pointer = normalized
|
|
47
48
|
this.#onAuthUrl = onAuthUrl
|
|
@@ -64,7 +65,7 @@ export class Nip46Client {
|
|
|
64
65
|
static async fromURI (clientSecretKey, uri, options = {}) {
|
|
65
66
|
const parsed = parseNostrConnectURI(uri)
|
|
66
67
|
const clientPubkey = getPublicKey(clientSecretKey)
|
|
67
|
-
if (!parsed || clientPubkey !== parsed.clientPubkey) throw new
|
|
68
|
+
if (!parsed || clientPubkey !== parsed.clientPubkey) throw new ValidationError('INVALID_NOSTRCONNECT_URI')
|
|
68
69
|
|
|
69
70
|
const relayPool = options.relayPool || defaultRelayPool
|
|
70
71
|
const controller = new AbortController()
|
|
@@ -169,7 +170,7 @@ export class Nip46Client {
|
|
|
169
170
|
if (relays === null) return false
|
|
170
171
|
|
|
171
172
|
const nextPointer = normalizeBunkerPointer({ ...this.#pointer, relays })
|
|
172
|
-
if (!nextPointer ||
|
|
173
|
+
if (!nextPointer || areRelaySetsEqual(nextPointer.relays, this.#pointer.relays)) return false
|
|
173
174
|
|
|
174
175
|
const nextContext = this.#openResponseContext(nextPointer)
|
|
175
176
|
try {
|
|
@@ -229,7 +230,7 @@ export class Nip46Client {
|
|
|
229
230
|
}
|
|
230
231
|
|
|
231
232
|
async #handleRequest (peerPubkey, request, context) {
|
|
232
|
-
if (!
|
|
233
|
+
if (!isValidRequestFrame(request)) {
|
|
233
234
|
if (typeof request?.id === 'string') {
|
|
234
235
|
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_INVALID_REQUEST', { context })
|
|
235
236
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
1
2
|
import { relayPool as defaultRelayPool } from '../../relay/index.js'
|
|
2
3
|
import { DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_AFTER_FIRST_EOSE, NIP46_KIND } from '../constants/index.js'
|
|
3
4
|
import {
|
|
4
5
|
decodeNip46Frame,
|
|
5
6
|
isNip46EventFor,
|
|
6
|
-
|
|
7
|
+
isValidRequestFrame
|
|
7
8
|
} from '../helpers/frame.js'
|
|
8
|
-
import {
|
|
9
|
+
import { areRelaySetsEqual, Nip46Transport } from './transport.js'
|
|
9
10
|
|
|
10
11
|
function cleanRelays (relays) {
|
|
11
12
|
return [...new Set((Array.isArray(relays) ? relays : [])
|
|
@@ -48,8 +49,8 @@ export class Nip46ServerSession {
|
|
|
48
49
|
timeoutAfterFirstEose = DEFAULT_TIMEOUT_AFTER_FIRST_EOSE
|
|
49
50
|
} = {}) {
|
|
50
51
|
this.#relays = cleanRelays(relays)
|
|
51
|
-
if (!this.#relays.length) throw new
|
|
52
|
-
if (typeof secret !== 'string') throw new
|
|
52
|
+
if (!this.#relays.length) throw new ValidationError('NIP46_RELAYS_REQUIRED')
|
|
53
|
+
if (typeof secret !== 'string') throw new ValidationError('NIP46_SECRET_REQUIRED')
|
|
53
54
|
this.#secretKey = serverSecretKey
|
|
54
55
|
this.#secret = secret
|
|
55
56
|
this.#onConnect = onConnect
|
|
@@ -85,8 +86,8 @@ export class Nip46ServerSession {
|
|
|
85
86
|
// Opens replacements now and switches only after the client requests them.
|
|
86
87
|
async updateRelays (relays) {
|
|
87
88
|
const nextRelays = cleanRelays(relays)
|
|
88
|
-
if (!nextRelays.length) throw new
|
|
89
|
-
if (
|
|
89
|
+
if (!nextRelays.length) throw new ValidationError('NIP46_RELAYS_REQUIRED')
|
|
90
|
+
if (areRelaySetsEqual(nextRelays, this.#relays)) return false
|
|
90
91
|
if (!this.#transport.activeContext) {
|
|
91
92
|
this.#relays = nextRelays
|
|
92
93
|
return true
|
|
@@ -137,7 +138,7 @@ export class Nip46ServerSession {
|
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
async #handleRequest (peerPubkey, request, context) {
|
|
140
|
-
if (!
|
|
141
|
+
if (!isValidRequestFrame(request)) {
|
|
141
142
|
if (typeof request?.id === 'string') {
|
|
142
143
|
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_INVALID_REQUEST', { context })
|
|
143
144
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
2
|
+
import { getPublicKey } from '../../key/index.js'
|
|
2
3
|
import { relayPool as defaultRelayPool } from '../../relay/index.js'
|
|
3
4
|
import { DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_AFTER_FIRST_EOSE } from '../constants/index.js'
|
|
4
5
|
import { createNip46Event, requestError } from '../helpers/frame.js'
|
|
@@ -14,9 +15,9 @@ function requestId () {
|
|
|
14
15
|
|
|
15
16
|
function requestExtension (value) {
|
|
16
17
|
if (value === undefined || value === null) return null
|
|
17
|
-
if (typeof value !== 'object' || Array.isArray(value)) throw new
|
|
18
|
+
if (typeof value !== 'object' || Array.isArray(value)) throw new ValidationError('NIP46_REQUEST_EXTENSION_REQUIRED')
|
|
18
19
|
for (const key of ['id', 'method', 'params']) {
|
|
19
|
-
if (Object.hasOwn(value, key)) throw new
|
|
20
|
+
if (Object.hasOwn(value, key)) throw new ValidationError(`NIP46_REQUEST_EXTENSION_CANNOT_SET_${key.toUpperCase()}`)
|
|
20
21
|
}
|
|
21
22
|
return value
|
|
22
23
|
}
|
|
@@ -46,7 +47,7 @@ export function waitForNip46 (promise, { timeout = null, signal, label = 'NIP46_
|
|
|
46
47
|
})
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export function
|
|
50
|
+
export function areRelaySetsEqual (left, right) {
|
|
50
51
|
if (left.length !== right.length) return false
|
|
51
52
|
const values = new Set(left)
|
|
52
53
|
return right.every(value => values.has(value))
|
|
@@ -74,8 +75,8 @@ export class Nip46Transport {
|
|
|
74
75
|
timeoutAfterFirstEose = DEFAULT_TIMEOUT_AFTER_FIRST_EOSE,
|
|
75
76
|
onError
|
|
76
77
|
} = {}) {
|
|
77
|
-
if (!(secretKey instanceof Uint8Array)) throw new
|
|
78
|
-
if (!relayPool?.getLiveEventsGenerator || !relayPool?.sendEvent) throw new
|
|
78
|
+
if (!(secretKey instanceof Uint8Array)) throw new ValidationError('NIP46_SECRET_KEY_REQUIRED')
|
|
79
|
+
if (!relayPool?.getLiveEventsGenerator || !relayPool?.sendEvent) throw new ValidationError('RELAY_POOL_REQUIRED')
|
|
79
80
|
this.#secretKey = secretKey
|
|
80
81
|
this.#pubkey = getPublicKey(secretKey)
|
|
81
82
|
this.#relayPool = relayPool
|
|
@@ -164,9 +165,9 @@ export class Nip46Transport {
|
|
|
164
165
|
extension
|
|
165
166
|
} = {}) {
|
|
166
167
|
if (this.#closed) throw new Error('NIP46_CLOSED')
|
|
167
|
-
if (typeof method !== 'string' || !method) throw new
|
|
168
|
+
if (typeof method !== 'string' || !method) throw new ValidationError('NIP46_METHOD_REQUIRED')
|
|
168
169
|
if (!Array.isArray(params) || !params.every(param => typeof param === 'string')) {
|
|
169
|
-
throw new
|
|
170
|
+
throw new ValidationError('NIP46_PARAMS_REQUIRED')
|
|
170
171
|
}
|
|
171
172
|
if (signal?.aborted) throw new Error('Aborted')
|
|
172
173
|
const context = this.#activeContext
|