libp2r2p 0.0.1
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/AGENTS.md +20 -0
- package/README.md +129 -0
- package/base16/index.js +14 -0
- package/base64/index.js +29 -0
- package/content-key/event/index.js +85 -0
- package/content-key/index.js +1 -0
- package/content-key/services/iykc-proof.js +129 -0
- package/double-dh/index.js +166 -0
- package/ecdh/index.js +8 -0
- package/idb/index.js +64 -0
- package/idb-queue/index.js +793 -0
- package/index.js +21 -0
- package/key/index.js +139 -0
- package/network/index.js +68 -0
- package/nip44-v3/index.js +175 -0
- package/nip46/constants/index.js +5 -0
- package/nip46/helpers/frame.js +51 -0
- package/nip46/helpers/url.js +96 -0
- package/nip46/index.js +5 -0
- package/nip46/services/bunker-signer.js +49 -0
- package/nip46/services/client.js +252 -0
- package/nip46/services/server-session.js +225 -0
- package/nip46/services/transport.js +265 -0
- package/package.json +49 -0
- package/private-channel/constants/index.js +5 -0
- package/private-channel/helpers/chunk-size.js +61 -0
- package/private-channel/helpers/chunks.js +282 -0
- package/private-channel/helpers/event.js +68 -0
- package/private-channel/index.js +942 -0
- package/private-channel/services/received-chunks.js +398 -0
- package/private-message/index.js +672 -0
- package/private-messenger/constants/index.js +1 -0
- package/private-messenger/index.js +1431 -0
- package/private-messenger/recovery/index.js +316 -0
- package/relay/constants/index.js +18 -0
- package/relay/helpers/hll.js +62 -0
- package/relay/helpers/publish.js +100 -0
- package/relay/helpers/routing.js +36 -0
- package/relay/helpers/timer.js +4 -0
- package/relay/index.js +4 -0
- package/relay/services/query.js +224 -0
- package/relay/services/relay-connection.js +156 -0
- package/relay/services/relay-pool.js +908 -0
- package/temporary-storage/index.js +85 -0
- package/tests/base16.test.js +19 -0
- package/tests/base64.test.js +18 -0
- package/tests/content-key-event.test.js +48 -0
- package/tests/fixtures/nip44v3-vectors.json +1 -0
- package/tests/helpers/test-signer.js +185 -0
- package/tests/idb-queue.test.js +153 -0
- package/tests/idb.test.js +91 -0
- package/tests/nip44-v3.test.js +73 -0
- package/tests/nip46.test.js +668 -0
- package/tests/private-channel.test.js +1899 -0
- package/tests/private-message.test.js +460 -0
- package/tests/private-messenger.test.js +1715 -0
- package/tests/queries.test.js +101 -0
- package/tests/queue-parity.test.js +105 -0
- package/tests/relay-hll.test.js +32 -0
- package/tests/relay-pool.test.js +2067 -0
- package/tests/temporary-storage.test.js +89 -0
- package/tests/web-storage-queue.test.js +480 -0
- package/web-storage-queue/index.js +652 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { getPublicKey } from 'nostr-tools'
|
|
2
|
+
import { relayPool as defaultRelayPool } from '../../relay/index.js'
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_TIMEOUT,
|
|
5
|
+
DEFAULT_TIMEOUT_AFTER_FIRST_EOSE,
|
|
6
|
+
NIP46_KIND,
|
|
7
|
+
RELAY_SWITCH_WAIT_TIMEOUT
|
|
8
|
+
} from '../constants/index.js'
|
|
9
|
+
import {
|
|
10
|
+
decodeNip46Frame,
|
|
11
|
+
isNip46EventFor,
|
|
12
|
+
requestError,
|
|
13
|
+
validRequestFrame
|
|
14
|
+
} from '../helpers/frame.js'
|
|
15
|
+
import { normalizeBunkerPointer, parseNostrConnectURI } from '../helpers/url.js'
|
|
16
|
+
import { Nip46Transport, sameRelays, waitForNip46 } from './transport.js'
|
|
17
|
+
|
|
18
|
+
function cleanClientMetadata (value) {
|
|
19
|
+
if (!value || typeof value !== 'object') return null
|
|
20
|
+
const metadata = {}
|
|
21
|
+
for (const key of ['name', 'url', 'image']) {
|
|
22
|
+
if (typeof value[key] === 'string' && value[key]) metadata[key] = value[key]
|
|
23
|
+
}
|
|
24
|
+
return Object.keys(metadata).length ? metadata : null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// A reusable NIP-46 client for standard or application-defined commands.
|
|
28
|
+
export class Nip46Client {
|
|
29
|
+
#secretKey
|
|
30
|
+
#transport
|
|
31
|
+
#pointer
|
|
32
|
+
#onAuthUrl
|
|
33
|
+
#onRequest
|
|
34
|
+
|
|
35
|
+
constructor (clientSecretKey, pointer, {
|
|
36
|
+
relayPool = defaultRelayPool,
|
|
37
|
+
onAuthUrl,
|
|
38
|
+
onRequest,
|
|
39
|
+
onError,
|
|
40
|
+
timeout = DEFAULT_TIMEOUT,
|
|
41
|
+
timeoutAfterFirstEose = DEFAULT_TIMEOUT_AFTER_FIRST_EOSE
|
|
42
|
+
} = {}) {
|
|
43
|
+
const normalized = normalizeBunkerPointer(pointer)
|
|
44
|
+
if (!normalized) throw new Error('INVALID_BUNKER_POINTER')
|
|
45
|
+
this.#secretKey = clientSecretKey
|
|
46
|
+
this.#pointer = normalized
|
|
47
|
+
this.#onAuthUrl = onAuthUrl
|
|
48
|
+
this.#onRequest = onRequest
|
|
49
|
+
this.#transport = new Nip46Transport(clientSecretKey, {
|
|
50
|
+
relayPool,
|
|
51
|
+
networkTimeout: timeout,
|
|
52
|
+
timeoutAfterFirstEose,
|
|
53
|
+
onError
|
|
54
|
+
})
|
|
55
|
+
this.#transport.activateContext(this.#openResponseContext(normalized), { retirePrevious: false })
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Creates a client for a parsed direct `bunker://` pointer.
|
|
59
|
+
static fromBunker (clientSecretKey, pointer, options = {}) {
|
|
60
|
+
return new this(clientSecretKey, pointer, options)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Waits for a signer response to a client-created `nostrconnect://` URI.
|
|
64
|
+
static async fromURI (clientSecretKey, uri, options = {}) {
|
|
65
|
+
const parsed = parseNostrConnectURI(uri)
|
|
66
|
+
const clientPubkey = getPublicKey(clientSecretKey)
|
|
67
|
+
if (!parsed || clientPubkey !== parsed.clientPubkey) throw new Error('INVALID_NOSTRCONNECT_URI')
|
|
68
|
+
|
|
69
|
+
const relayPool = options.relayPool || defaultRelayPool
|
|
70
|
+
const controller = new AbortController()
|
|
71
|
+
const stream = relayPool.getLiveEventsGenerator({
|
|
72
|
+
kinds: [NIP46_KIND],
|
|
73
|
+
'#p': [clientPubkey],
|
|
74
|
+
limit: 0
|
|
75
|
+
}, parsed.relays, {
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
timeoutAfterFirstEose: options.timeoutAfterFirstEose ?? DEFAULT_TIMEOUT_AFTER_FIRST_EOSE
|
|
78
|
+
})
|
|
79
|
+
const found = Promise.withResolvers()
|
|
80
|
+
const consume = (async () => {
|
|
81
|
+
try {
|
|
82
|
+
for await (const event of stream) {
|
|
83
|
+
if (!isNip46EventFor(event, clientPubkey)) continue
|
|
84
|
+
const response = decodeNip46Frame(event, clientSecretKey)
|
|
85
|
+
if (response?.result === parsed.secret) {
|
|
86
|
+
found.resolve({
|
|
87
|
+
remoteSignerPubkey: event.pubkey,
|
|
88
|
+
relays: parsed.relays,
|
|
89
|
+
secret: parsed.secret
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error?.message !== 'Aborted') found.reject(error)
|
|
95
|
+
}
|
|
96
|
+
})()
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const timeout = options.timeout ?? DEFAULT_TIMEOUT
|
|
100
|
+
const report = await waitForNip46(stream.ready, {
|
|
101
|
+
timeout,
|
|
102
|
+
signal: options.signal,
|
|
103
|
+
label: 'NIP46_LISTENER_TIMEOUT'
|
|
104
|
+
})
|
|
105
|
+
if (!stream.readyRelays.length) {
|
|
106
|
+
throw requestError(report.errors?.[0]?.reason || 'NIP46_NO_READY_RELAYS')
|
|
107
|
+
}
|
|
108
|
+
const pointer = await waitForNip46(found.promise, {
|
|
109
|
+
timeout,
|
|
110
|
+
signal: options.signal,
|
|
111
|
+
label: 'NIP46_CONNECTION_TIMEOUT'
|
|
112
|
+
})
|
|
113
|
+
controller.abort()
|
|
114
|
+
await consume
|
|
115
|
+
|
|
116
|
+
const client = new this(clientSecretKey, pointer, options)
|
|
117
|
+
await client.#awaitReady({ timeout, signal: options.signal })
|
|
118
|
+
await client.#switchRelaysAfterConnect()
|
|
119
|
+
return client
|
|
120
|
+
} catch (error) {
|
|
121
|
+
controller.abort()
|
|
122
|
+
await consume.catch(() => {})
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get clientPubkey () { return this.#transport.pubkey }
|
|
128
|
+
get pointer () { return { ...this.#pointer, relays: [...this.#pointer.relays] } }
|
|
129
|
+
|
|
130
|
+
// Connects and immediately asks the remote signer for its preferred relays.
|
|
131
|
+
async connect ({ requestedPermissions = [], clientMetadata, timeout = null, signal } = {}) {
|
|
132
|
+
const permissions = Array.isArray(requestedPermissions)
|
|
133
|
+
? requestedPermissions.filter(permission => typeof permission === 'string' && permission).join(',')
|
|
134
|
+
: ''
|
|
135
|
+
const metadata = cleanClientMetadata(clientMetadata)
|
|
136
|
+
const params = [this.#pointer.remoteSignerPubkey, this.#pointer.secret || '']
|
|
137
|
+
if (permissions || metadata) params.push(permissions)
|
|
138
|
+
if (metadata) params.push(JSON.stringify(metadata))
|
|
139
|
+
await this.sendRequest('connect', params, { timeout, signal })
|
|
140
|
+
return this.#switchRelaysAfterConnect()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Sends a positional-string request after the response listener is ready.
|
|
144
|
+
sendRequest (method, params = [], options = {}) {
|
|
145
|
+
return this.#transport.sendRequest(this.#pointer.remoteSignerPubkey, method, params, options)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async ping (options) {
|
|
149
|
+
const response = await this.sendRequest('ping', [], options)
|
|
150
|
+
if (response !== 'pong') throw new Error(`NIP46_PING_FAILED:${response}`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async logout (options) {
|
|
154
|
+
const response = await this.sendRequest('logout', [], options)
|
|
155
|
+
if (response !== 'ack') throw new Error(`NIP46_LOGOUT_FAILED:${response}`)
|
|
156
|
+
await this.close()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Moves to the relay list returned by the remote signer, if it changed.
|
|
160
|
+
async switchRelays (options) {
|
|
161
|
+
const response = await this.sendRequest('switch_relays', [], options)
|
|
162
|
+
if (response === null) return false
|
|
163
|
+
let relays
|
|
164
|
+
try {
|
|
165
|
+
relays = JSON.parse(response)
|
|
166
|
+
} catch {
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
if (relays === null) return false
|
|
170
|
+
|
|
171
|
+
const nextPointer = normalizeBunkerPointer({ ...this.#pointer, relays })
|
|
172
|
+
if (!nextPointer || sameRelays(nextPointer.relays, this.#pointer.relays)) return false
|
|
173
|
+
|
|
174
|
+
const nextContext = this.#openResponseContext(nextPointer)
|
|
175
|
+
try {
|
|
176
|
+
await this.#transport.awaitContextReady(nextContext)
|
|
177
|
+
} catch {
|
|
178
|
+
await this.#transport.closeContext(nextContext)
|
|
179
|
+
return false
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.#pointer = nextPointer
|
|
183
|
+
this.#transport.activateContext(nextContext)
|
|
184
|
+
return true
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
close () {
|
|
188
|
+
return this.#transport.close()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async #switchRelaysAfterConnect () {
|
|
192
|
+
try {
|
|
193
|
+
return await this.switchRelays({ timeout: RELAY_SWITCH_WAIT_TIMEOUT })
|
|
194
|
+
} catch {
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#openResponseContext (pointer) {
|
|
200
|
+
let context
|
|
201
|
+
context = this.#transport.openContext({
|
|
202
|
+
filter: {
|
|
203
|
+
kinds: [NIP46_KIND],
|
|
204
|
+
authors: [pointer.remoteSignerPubkey],
|
|
205
|
+
'#p': [this.#transport.pubkey],
|
|
206
|
+
limit: 0
|
|
207
|
+
},
|
|
208
|
+
relays: pointer.relays,
|
|
209
|
+
onEvent: event => this.#receiveEvent(event, context)
|
|
210
|
+
})
|
|
211
|
+
return context
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#awaitReady (options) {
|
|
215
|
+
return this.#transport.awaitContextReady(this.#transport.activeContext, options)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#receiveEvent (event, context) {
|
|
219
|
+
if (event?.pubkey !== this.#pointer.remoteSignerPubkey ||
|
|
220
|
+
!isNip46EventFor(event, this.#transport.pubkey) ||
|
|
221
|
+
!this.#transport.isNewEvent(event)) return
|
|
222
|
+
const frame = decodeNip46Frame(event, this.#secretKey)
|
|
223
|
+
if (!frame) return
|
|
224
|
+
|
|
225
|
+
if (Object.hasOwn(frame, 'method')) {
|
|
226
|
+
return this.#handleRequest(event.pubkey, frame, context)
|
|
227
|
+
}
|
|
228
|
+
this.#transport.receiveResponse(event.pubkey, frame, { onAuthUrl: this.#onAuthUrl })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async #handleRequest (peerPubkey, request, context) {
|
|
232
|
+
if (!validRequestFrame(request)) {
|
|
233
|
+
if (typeof request?.id === 'string') {
|
|
234
|
+
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_INVALID_REQUEST', { context })
|
|
235
|
+
}
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
if (!this.#onRequest) throw new Error('NIP46_METHOD_NOT_SUPPORTED')
|
|
241
|
+
const result = await this.#onRequest({
|
|
242
|
+
method: request.method,
|
|
243
|
+
params: [...request.params],
|
|
244
|
+
peerPubkey
|
|
245
|
+
})
|
|
246
|
+
if (typeof result !== 'string') throw new Error('NIP46_RESULT_REQUIRED')
|
|
247
|
+
await this.#transport.reply(peerPubkey, request.id, result, null, { context })
|
|
248
|
+
} catch (error) {
|
|
249
|
+
await this.#transport.reply(peerPubkey, request.id, null, error?.message || 'NIP46_REQUEST_REJECTED', { context })
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { relayPool as defaultRelayPool } from '../../relay/index.js'
|
|
2
|
+
import { DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_AFTER_FIRST_EOSE, NIP46_KIND } from '../constants/index.js'
|
|
3
|
+
import {
|
|
4
|
+
decodeNip46Frame,
|
|
5
|
+
isNip46EventFor,
|
|
6
|
+
validRequestFrame
|
|
7
|
+
} from '../helpers/frame.js'
|
|
8
|
+
import { Nip46Transport, sameRelays } from './transport.js'
|
|
9
|
+
|
|
10
|
+
function cleanRelays (relays) {
|
|
11
|
+
return [...new Set((Array.isArray(relays) ? relays : [])
|
|
12
|
+
.filter(relay => typeof relay === 'string' && relay))]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parseClientMetadata (value) {
|
|
16
|
+
if (!value) return null
|
|
17
|
+
try {
|
|
18
|
+
const metadata = JSON.parse(value)
|
|
19
|
+
return metadata && typeof metadata === 'object' && !Array.isArray(metadata) ? metadata : null
|
|
20
|
+
} catch {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A one-secret, single-client NIP-46 server session.
|
|
26
|
+
export class Nip46ServerSession {
|
|
27
|
+
#secretKey
|
|
28
|
+
#secret
|
|
29
|
+
#secretConsumed = false
|
|
30
|
+
#transport
|
|
31
|
+
#relays
|
|
32
|
+
#clientPubkey = null
|
|
33
|
+
#connectingPubkey = null
|
|
34
|
+
#pendingContext = null
|
|
35
|
+
#onConnect
|
|
36
|
+
#onRequest
|
|
37
|
+
#onLogout
|
|
38
|
+
|
|
39
|
+
constructor (serverSecretKey, {
|
|
40
|
+
relays,
|
|
41
|
+
secret = '',
|
|
42
|
+
relayPool = defaultRelayPool,
|
|
43
|
+
onConnect,
|
|
44
|
+
onRequest,
|
|
45
|
+
onLogout,
|
|
46
|
+
onError,
|
|
47
|
+
timeout = DEFAULT_TIMEOUT,
|
|
48
|
+
timeoutAfterFirstEose = DEFAULT_TIMEOUT_AFTER_FIRST_EOSE
|
|
49
|
+
} = {}) {
|
|
50
|
+
this.#relays = cleanRelays(relays)
|
|
51
|
+
if (!this.#relays.length) throw new Error('NIP46_RELAYS_REQUIRED')
|
|
52
|
+
if (typeof secret !== 'string') throw new Error('NIP46_SECRET_REQUIRED')
|
|
53
|
+
this.#secretKey = serverSecretKey
|
|
54
|
+
this.#secret = secret
|
|
55
|
+
this.#onConnect = onConnect
|
|
56
|
+
this.#onRequest = onRequest
|
|
57
|
+
this.#onLogout = onLogout
|
|
58
|
+
this.#transport = new Nip46Transport(serverSecretKey, {
|
|
59
|
+
relayPool,
|
|
60
|
+
networkTimeout: timeout,
|
|
61
|
+
timeoutAfterFirstEose,
|
|
62
|
+
onError
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get serverPubkey () { return this.#transport.pubkey }
|
|
67
|
+
get clientPubkey () { return this.#clientPubkey }
|
|
68
|
+
get relays () { return Object.freeze([...this.#relays]) }
|
|
69
|
+
get readyRelays () { return this.#transport.readyRelays }
|
|
70
|
+
|
|
71
|
+
// Opens the live request listener and waits until at least one relay is ready.
|
|
72
|
+
start (options) {
|
|
73
|
+
if (!this.#transport.activeContext) {
|
|
74
|
+
this.#transport.activateContext(this.#openContext(this.#relays), { retirePrevious: false })
|
|
75
|
+
}
|
|
76
|
+
return this.#transport.awaitContextReady(this.#transport.activeContext, options)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Sends an application-defined request to the connected client.
|
|
80
|
+
sendRequest (method, params = [], options = {}) {
|
|
81
|
+
if (!this.#clientPubkey) return Promise.reject(new Error('NIP46_NOT_CONNECTED'))
|
|
82
|
+
return this.#transport.sendRequest(this.#clientPubkey, method, params, options)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Opens replacements now and switches only after the client requests them.
|
|
86
|
+
async updateRelays (relays) {
|
|
87
|
+
const nextRelays = cleanRelays(relays)
|
|
88
|
+
if (!nextRelays.length) throw new Error('NIP46_RELAYS_REQUIRED')
|
|
89
|
+
if (sameRelays(nextRelays, this.#relays)) return false
|
|
90
|
+
if (!this.#transport.activeContext) {
|
|
91
|
+
this.#relays = nextRelays
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const nextContext = this.#openContext(nextRelays)
|
|
96
|
+
try {
|
|
97
|
+
await this.#transport.awaitContextReady(nextContext)
|
|
98
|
+
} catch (error) {
|
|
99
|
+
await this.#transport.closeContext(nextContext)
|
|
100
|
+
throw error
|
|
101
|
+
}
|
|
102
|
+
if (this.#pendingContext) await this.#transport.closeContext(this.#pendingContext)
|
|
103
|
+
this.#relays = nextRelays
|
|
104
|
+
this.#pendingContext = nextContext
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
close () {
|
|
109
|
+
this.#pendingContext = null
|
|
110
|
+
return this.#transport.close()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#openContext (relays) {
|
|
114
|
+
let context
|
|
115
|
+
context = this.#transport.openContext({
|
|
116
|
+
filter: {
|
|
117
|
+
kinds: [NIP46_KIND],
|
|
118
|
+
'#p': [this.#transport.pubkey],
|
|
119
|
+
limit: 0
|
|
120
|
+
},
|
|
121
|
+
relays,
|
|
122
|
+
onEvent: event => this.#receiveEvent(event, context)
|
|
123
|
+
})
|
|
124
|
+
return context
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#receiveEvent (event, context) {
|
|
128
|
+
if (!isNip46EventFor(event, this.#transport.pubkey) || !this.#transport.isNewEvent(event)) return
|
|
129
|
+
const frame = decodeNip46Frame(event, this.#secretKey)
|
|
130
|
+
if (!frame) return
|
|
131
|
+
|
|
132
|
+
if (!Object.hasOwn(frame, 'method')) {
|
|
133
|
+
if (event.pubkey === this.#clientPubkey) this.#transport.receiveResponse(event.pubkey, frame)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
return this.#handleRequest(event.pubkey, frame, context)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async #handleRequest (peerPubkey, request, context) {
|
|
140
|
+
if (!validRequestFrame(request)) {
|
|
141
|
+
if (typeof request?.id === 'string') {
|
|
142
|
+
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_INVALID_REQUEST', { context })
|
|
143
|
+
}
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (request.method === 'connect') {
|
|
148
|
+
await this.#handleConnect(peerPubkey, request, context)
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
if (!this.#clientPubkey || peerPubkey !== this.#clientPubkey) {
|
|
152
|
+
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_NOT_CONNECTED', { context })
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
if (request.method === 'ping') {
|
|
158
|
+
await this.#transport.reply(peerPubkey, request.id, 'pong', null, { context })
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
if (request.method === 'switch_relays') {
|
|
162
|
+
await this.#transport.reply(peerPubkey, request.id, JSON.stringify(this.#relays), null, { context })
|
|
163
|
+
if (this.#pendingContext) {
|
|
164
|
+
const nextContext = this.#pendingContext
|
|
165
|
+
this.#pendingContext = null
|
|
166
|
+
const previous = this.#transport.activateContext(nextContext, { retirePrevious: false })
|
|
167
|
+
if (previous && previous !== nextContext) this.#transport.retireContext(previous)
|
|
168
|
+
}
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
if (request.method === 'logout') {
|
|
172
|
+
await this.#transport.reply(peerPubkey, request.id, 'ack', null, { context })
|
|
173
|
+
await this.#onLogout?.({ clientPubkey: peerPubkey })
|
|
174
|
+
await this.close()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
if (!this.#onRequest) throw new Error('NIP46_METHOD_NOT_SUPPORTED')
|
|
178
|
+
const result = await this.#onRequest({
|
|
179
|
+
method: request.method,
|
|
180
|
+
params: [...request.params],
|
|
181
|
+
clientPubkey: peerPubkey
|
|
182
|
+
})
|
|
183
|
+
if (typeof result !== 'string') throw new Error('NIP46_RESULT_REQUIRED')
|
|
184
|
+
await this.#transport.reply(peerPubkey, request.id, result, null, { context })
|
|
185
|
+
} catch (error) {
|
|
186
|
+
await this.#transport.reply(peerPubkey, request.id, null, error?.message || 'NIP46_REQUEST_REJECTED', { context })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async #handleConnect (peerPubkey, request, context) {
|
|
191
|
+
if (this.#clientPubkey || this.#connectingPubkey || this.#secretConsumed) {
|
|
192
|
+
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_ALREADY_CONNECTED', { context })
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
if (request.params[0] !== this.#transport.pubkey || request.params[1] !== this.#secret) {
|
|
196
|
+
await this.#transport.reply(peerPubkey, request.id, null, 'NIP46_INVALID_SECRET', { context })
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const requestedPermissions = request.params[2]
|
|
201
|
+
? request.params[2].split(',').filter(Boolean)
|
|
202
|
+
: []
|
|
203
|
+
const clientMetadata = parseClientMetadata(request.params[3])
|
|
204
|
+
this.#connectingPubkey = peerPubkey
|
|
205
|
+
try {
|
|
206
|
+
await this.#onConnect?.({ peerPubkey, requestedPermissions, clientMetadata })
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.#connectingPubkey = null
|
|
209
|
+
await this.#transport.reply(peerPubkey, request.id, null, error?.message || 'NIP46_CONNECT_REJECTED', { context })
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.#clientPubkey = peerPubkey
|
|
214
|
+
this.#secretConsumed = true
|
|
215
|
+
try {
|
|
216
|
+
await this.#transport.reply(peerPubkey, request.id, 'ack', null, { context })
|
|
217
|
+
} catch (error) {
|
|
218
|
+
this.#clientPubkey = null
|
|
219
|
+
this.#secretConsumed = false
|
|
220
|
+
throw error
|
|
221
|
+
} finally {
|
|
222
|
+
this.#connectingPubkey = null
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|