libp2r2p 0.10.19 → 0.10.21
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 +174 -10
- package/idb-queue/index.js +66 -2
- package/package.json +1 -1
- package/private-channel/helpers/rumor.js +26 -0
- package/private-channel/index.js +24 -12
- package/private-channel/services/received-chunks.js +3 -2
- package/private-message/index.js +333 -281
- package/private-messenger/index.js +328 -91
- package/private-messenger/services/channel-state.js +1 -1
- package/relay/helpers/read-admission.js +92 -0
- package/relay/services/relay-pool.js +210 -58
package/private-message/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isValidEvent } from '../event/index.js'
|
|
2
2
|
import { ValidationError } from '../error/index.js'
|
|
3
3
|
import { generateKeypair } from '../key/index.js'
|
|
4
4
|
import * as privateChannel from '../private-channel/index.js'
|
|
5
|
+
import { normalizeRumor, deliveryInfo } from '../private-channel/helpers/rumor.js'
|
|
5
6
|
|
|
6
7
|
export const ASK_KIND = 7329
|
|
7
8
|
export const REPLY_KIND = 7330
|
|
@@ -11,10 +12,6 @@ const RESUBSCRIBE_GRACE_MS = 500
|
|
|
11
12
|
const PRIVATE_MESSAGE_KINDS = [ASK_KIND, REPLY_KIND, TELL_KIND]
|
|
12
13
|
const HEX_PUBKEY = /^[0-9a-f]{64}$/i
|
|
13
14
|
|
|
14
|
-
const watchesByChannel = new Map()
|
|
15
|
-
const subsByRelay = new Map()
|
|
16
|
-
let nextWatchRevision = 1
|
|
17
|
-
|
|
18
15
|
function nowSeconds () {
|
|
19
16
|
return Math.floor(Date.now() / 1000)
|
|
20
17
|
}
|
|
@@ -120,25 +117,15 @@ function cloneTags (tags) {
|
|
|
120
117
|
async function makeOutgoingRumor ({ senderSigner, rumor }) {
|
|
121
118
|
if (!senderSigner?.getPublicKey) throw new ValidationError('SENDER_SIGNER_REQUIRED')
|
|
122
119
|
const senderPubkey = await senderSigner.getPublicKey()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
kind: rumor.kind,
|
|
126
|
-
tags: cloneTags(rumor.tags),
|
|
127
|
-
content: rumor.content,
|
|
128
|
-
created_at: rumor.created_at !== undefined
|
|
129
|
-
? rumor.created_at
|
|
130
|
-
: nowSeconds()
|
|
120
|
+
if (rumor.pubkey !== undefined && rumor.pubkey !== senderPubkey && rumor.created_at === undefined) {
|
|
121
|
+
throw new ValidationError('FORWARDED_RUMOR_TIMESTAMP_REQUIRED')
|
|
131
122
|
}
|
|
132
|
-
const event = normalizeRumor(
|
|
123
|
+
const event = normalizeRumor({ ...rumor, tags: cloneTags(rumor.tags), created_at: rumor.created_at === undefined ? nowSeconds() : rumor.created_at }, senderPubkey)
|
|
124
|
+
const { id: _, pubkey, ...template } = event
|
|
125
|
+
const wireEvent = pubkey === senderPubkey ? template : { ...template, pubkey }
|
|
133
126
|
return { event, wireEvent }
|
|
134
127
|
}
|
|
135
128
|
|
|
136
|
-
function normalizeRumor (event, pubkey) {
|
|
137
|
-
const normalized = { ...event, pubkey }
|
|
138
|
-
if (!isSerializableEvent(normalized)) throw new ValidationError('INVALID_RUMOR')
|
|
139
|
-
return { ...normalized, id: getEventHash(normalized) }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
129
|
function assertValidSignedEvent (event) {
|
|
143
130
|
if (!isValidEvent(event)) {
|
|
144
131
|
throw new ValidationError('INVALID_SIGNED_EVENT')
|
|
@@ -155,308 +142,364 @@ async function ownPrivateChannelPubkey (signer) {
|
|
|
155
142
|
return signer.getPublicKey()
|
|
156
143
|
}
|
|
157
144
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
145
|
+
// A session owns its callbacks and relay reads. Other consumers, including
|
|
146
|
+
// the other side of the same shared channel, cannot overwrite or stop them.
|
|
147
|
+
export function createPrivateMessageSession () {
|
|
148
|
+
const watchesByChannel = new Map()
|
|
149
|
+
const subsByRelay = new Map()
|
|
150
|
+
let nextWatchRevision = 1
|
|
151
|
+
let requestSequence = 0
|
|
152
|
+
const watchRequests = new Map()
|
|
153
|
+
let identity
|
|
154
|
+
const receivedChunkScope = globalThis.crypto.randomUUID()
|
|
155
|
+
let subscribe = privateChannel.subscribe
|
|
156
|
+
function assertWatching (channelPubkey) {
|
|
157
|
+
if (!watchesByChannel.has(channelPubkey)) throw new Error('PRIVATE_MESSAGE_NOT_WATCHING')
|
|
158
|
+
}
|
|
161
159
|
|
|
162
|
-
function watchCallbacks (channelPubkey) {
|
|
163
|
-
|
|
164
|
-
}
|
|
160
|
+
function watchCallbacks (channelPubkey) {
|
|
161
|
+
return watchesByChannel.get(channelPubkey)?.callbacks || {}
|
|
162
|
+
}
|
|
165
163
|
|
|
166
|
-
function dispatchWatchedEvent (event, outer, meta) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
async function dispatchWatchedEvent (event, outer, meta) {
|
|
165
|
+
const callbacks = watchCallbacks(meta.channelPubkey)
|
|
166
|
+
const payload = parseRumorContent(event)
|
|
167
|
+
const info = deliveryInfo(event, meta.senderPubkey ?? meta.router?.tags?.find(tag => tag[0] === 'f')?.[1])
|
|
168
|
+
const message = { event, outer, meta, payload, ...info }
|
|
169
|
+
if (info.provenance === 'hearsay' || (info.senderPubkey && info.senderPubkey !== event.pubkey)) {
|
|
170
|
+
await callbacks.onMessage?.(message)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
170
173
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
if (event.kind === ASK_KIND) {
|
|
175
|
+
await callbacks.onAsk?.({ ...message, question: event })
|
|
176
|
+
} else if (event.kind === REPLY_KIND) {
|
|
177
|
+
const questionId = readTag(event, 'q')
|
|
178
|
+
await callbacks.onReply?.({ ...message, questionId, reply: event })
|
|
179
|
+
} else if (event.kind === TELL_KIND) {
|
|
180
|
+
const receiverTag = readTag(event, 'r')
|
|
181
|
+
if (receiverTag) await callbacks.onTell?.({ ...message, tell: event })
|
|
182
|
+
else await callbacks.onYell?.({ ...message, yell: event })
|
|
183
|
+
}
|
|
181
184
|
|
|
182
|
-
|
|
183
|
-
}
|
|
185
|
+
await callbacks.onMessage?.(message)
|
|
186
|
+
}
|
|
184
187
|
|
|
185
|
-
function dispatchWatchedNymEvent (event, outer, meta) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
188
|
+
async function dispatchWatchedNymEvent (event, outer, meta) {
|
|
189
|
+
const callbacks = watchCallbacks(meta.channelPubkey)
|
|
190
|
+
await callbacks.onNym?.({
|
|
191
|
+
event,
|
|
192
|
+
outer,
|
|
193
|
+
meta,
|
|
194
|
+
payload: parseRumorContent(event),
|
|
195
|
+
...deliveryInfo(event, meta.senderPubkey ?? meta.carrier?.pubkey ?? event.pubkey),
|
|
196
|
+
nym: event
|
|
197
|
+
})
|
|
198
|
+
}
|
|
195
199
|
|
|
196
|
-
function dispatchSeedEvent (seed) {
|
|
197
|
-
|
|
198
|
-
}
|
|
200
|
+
function dispatchSeedEvent (seed) {
|
|
201
|
+
return watchCallbacks(seed.channelPubkey).onSeed?.(seed)
|
|
202
|
+
}
|
|
199
203
|
|
|
200
|
-
function dispatchContentKeyUsage (usage) {
|
|
201
|
-
|
|
202
|
-
}
|
|
204
|
+
function dispatchContentKeyUsage (usage) {
|
|
205
|
+
watchCallbacks(usage.channelPubkey).onContentKeyUsage?.(usage)
|
|
206
|
+
}
|
|
203
207
|
|
|
204
|
-
function handleChunk (chunk) {
|
|
205
|
-
|
|
206
|
-
}
|
|
208
|
+
function handleChunk (chunk) {
|
|
209
|
+
watchCallbacks(chunk.channelPubkey).onChunk?.(chunk)
|
|
210
|
+
}
|
|
207
211
|
|
|
208
|
-
function desiredRelayState () {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
function desiredRelayState () {
|
|
213
|
+
const relayToChannels = new Map()
|
|
214
|
+
for (const [channelPubkey, watch] of watchesByChannel) {
|
|
215
|
+
for (const relay of watch.relays) {
|
|
216
|
+
if (!relayToChannels.has(relay)) relayToChannels.set(relay, new Set())
|
|
217
|
+
relayToChannels.get(relay).add(channelPubkey)
|
|
218
|
+
}
|
|
214
219
|
}
|
|
220
|
+
return relayToChannels
|
|
215
221
|
}
|
|
216
|
-
return relayToChannels
|
|
217
|
-
}
|
|
218
222
|
|
|
219
|
-
function signersForChannels (channels) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
function signersForChannels (channels) {
|
|
224
|
+
const out = {}
|
|
225
|
+
for (const channel of channels) {
|
|
226
|
+
const signer = watchesByChannel.get(channel)?.privateChannelSigner
|
|
227
|
+
if (signer) out[channel] = signer
|
|
228
|
+
}
|
|
229
|
+
return out
|
|
224
230
|
}
|
|
225
|
-
return out
|
|
226
|
-
}
|
|
227
231
|
|
|
228
|
-
function readerSignersForChannels (channels) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
232
|
+
function readerSignersForChannels (channels) {
|
|
233
|
+
const out = {}
|
|
234
|
+
for (const channel of channels) {
|
|
235
|
+
const signer = watchesByChannel.get(channel)?.privateChannelReaderSigner
|
|
236
|
+
if (signer) out[channel] = signer
|
|
237
|
+
}
|
|
238
|
+
return out
|
|
233
239
|
}
|
|
234
|
-
return out
|
|
235
|
-
}
|
|
236
240
|
|
|
237
|
-
function readerPubkeysForChannels (channels) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
241
|
+
function readerPubkeysForChannels (channels) {
|
|
242
|
+
const out = {}
|
|
243
|
+
for (const channel of channels) {
|
|
244
|
+
const pubkey = watchesByChannel.get(channel)?.privateChannelReaderPubkey
|
|
245
|
+
if (pubkey) out[channel] = pubkey
|
|
246
|
+
}
|
|
247
|
+
return out
|
|
242
248
|
}
|
|
243
|
-
return out
|
|
244
|
-
}
|
|
245
249
|
|
|
246
|
-
function modesForChannels (channels) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function maxWatchNumber (channels, field) {
|
|
253
|
-
const values = channels
|
|
254
|
-
.map(channel => watchesByChannel.get(channel)?.[field])
|
|
255
|
-
.filter(value => Number.isFinite(value))
|
|
256
|
-
return values.length ? Math.max(...values) : undefined
|
|
257
|
-
}
|
|
250
|
+
function modesForChannels (channels) {
|
|
251
|
+
const out = {}
|
|
252
|
+
for (const channel of channels) out[channel] = watchesByChannel.get(channel)?.mode || 'leecher'
|
|
253
|
+
return out
|
|
254
|
+
}
|
|
258
255
|
|
|
259
|
-
function
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
256
|
+
function maxWatchNumber (channels, field) {
|
|
257
|
+
const values = channels
|
|
258
|
+
.map(channel => watchesByChannel.get(channel)?.[field])
|
|
259
|
+
.filter(value => Number.isFinite(value))
|
|
260
|
+
return values.length ? Math.max(...values) : undefined
|
|
264
261
|
}
|
|
265
|
-
return out
|
|
266
|
-
}
|
|
267
262
|
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
263
|
+
function watchNumbersForChannels (channels, field) {
|
|
264
|
+
const out = {}
|
|
265
|
+
for (const channel of channels) {
|
|
266
|
+
const value = watchesByChannel.get(channel)?.[field]
|
|
267
|
+
if (Number.isFinite(value)) out[channel] = value
|
|
268
|
+
}
|
|
269
|
+
return out
|
|
270
|
+
}
|
|
271
271
|
|
|
272
|
-
function
|
|
273
|
-
|
|
274
|
-
for (const channel of channels) {
|
|
275
|
-
if (current.revisions?.[channel] !== watchesByChannel.get(channel)?.revision) return false
|
|
272
|
+
function watchRevisionsForChannels (channels) {
|
|
273
|
+
return Object.fromEntries(channels.map(channel => [channel, watchesByChannel.get(channel)?.revision || 0]))
|
|
276
274
|
}
|
|
277
|
-
return true
|
|
278
|
-
}
|
|
279
275
|
|
|
280
|
-
function
|
|
281
|
-
|
|
282
|
-
const
|
|
283
|
-
|
|
276
|
+
function doesSubscriptionMatch (current, channels) {
|
|
277
|
+
if (!current || !areSetsEqual(current.channels, channels)) return false
|
|
278
|
+
for (const channel of channels) {
|
|
279
|
+
if (current.revisions?.[channel] !== watchesByChannel.get(channel)?.revision) return false
|
|
280
|
+
}
|
|
281
|
+
return true
|
|
284
282
|
}
|
|
285
|
-
return undefined
|
|
286
|
-
}
|
|
287
283
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
284
|
+
function firstWatchValue (channels, field) {
|
|
285
|
+
for (const channel of channels) {
|
|
286
|
+
const value = watchesByChannel.get(channel)?.[field]
|
|
287
|
+
if (value !== undefined) return value
|
|
288
|
+
}
|
|
289
|
+
return undefined
|
|
292
290
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
|
|
292
|
+
function closeSubscription (sub, gracefulClose) {
|
|
293
|
+
if (gracefulClose) {
|
|
294
|
+
setTimeout(() => Promise.resolve().then(() => sub.close()).catch(() => {}), RESUBSCRIBE_GRACE_MS)
|
|
295
|
+
return null
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
return Promise.resolve(sub.close())
|
|
299
|
+
} catch (err) {
|
|
300
|
+
return Promise.reject(err)
|
|
301
|
+
}
|
|
297
302
|
}
|
|
298
|
-
}
|
|
299
303
|
|
|
300
|
-
function rebuildSubscriptions ({ _subscribe = privateChannel.subscribe, gracefulClose = true } = {}) {
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
function rebuildSubscriptions ({ _subscribe = privateChannel.subscribe, gracefulClose = true } = {}) {
|
|
305
|
+
subscribe = _subscribe === privateChannel.subscribe ? subscribe : _subscribe
|
|
306
|
+
_subscribe = subscribe
|
|
307
|
+
const desired = desiredRelayState()
|
|
308
|
+
const closing = []
|
|
309
|
+
|
|
310
|
+
for (const [relay, current] of subsByRelay) {
|
|
311
|
+
const nextChannels = desired.get(relay)
|
|
312
|
+
if (nextChannels && doesSubscriptionMatch(current, nextChannels)) continue
|
|
313
|
+
if (!nextChannels) {
|
|
314
|
+
const close = closeSubscription(current.sub, gracefulClose)
|
|
315
|
+
if (close) closing.push(close)
|
|
316
|
+
subsByRelay.delete(relay)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
303
319
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
320
|
+
for (const [relay, channels] of desired) {
|
|
321
|
+
const current = subsByRelay.get(relay)
|
|
322
|
+
if (doesSubscriptionMatch(current, channels)) continue
|
|
323
|
+
|
|
324
|
+
const channelList = [...channels]
|
|
325
|
+
const firstWatch = watchesByChannel.get(channelList[0])
|
|
326
|
+
const sub = _subscribe({
|
|
327
|
+
receiverSigner: firstWatch.receiverSigner,
|
|
328
|
+
iykcSigner: firstWatch.iykcSigner,
|
|
329
|
+
privateChannelSigner: firstWatch.privateChannelSigner,
|
|
330
|
+
privateChannelSignersByPubkey: signersForChannels(channelList),
|
|
331
|
+
privateChannelReaderSigner: firstWatch.privateChannelReaderSigner,
|
|
332
|
+
privateChannelReaderSignersByPubkey: readerSignersForChannels(channelList),
|
|
333
|
+
privateChannelReaderPubkey: firstWatch.privateChannelReaderPubkey,
|
|
334
|
+
privateChannelReaderPubkeysByPubkey: readerPubkeysForChannels(channelList),
|
|
335
|
+
privateChannelPubkeys: channelList,
|
|
336
|
+
receiverPubkey: firstWatch.receiverPubkey,
|
|
337
|
+
relays: [relay],
|
|
338
|
+
mode: firstWatch.mode,
|
|
339
|
+
modeByPubkey: modesForChannels(channelList),
|
|
340
|
+
receivedChunkScope,
|
|
341
|
+
receivedChunkTtlMs: maxWatchNumber(channelList, 'receivedChunkTtlMs'),
|
|
342
|
+
receivedChunkTtlMsByPubkey: watchNumbersForChannels(channelList, 'receivedChunkTtlMs'),
|
|
343
|
+
receivedChunkMaxBytes: maxWatchNumber(channelList, 'receivedChunkMaxBytes'),
|
|
344
|
+
receivedChunkIndexedDB: firstWatchValue(channelList, 'receivedChunkIndexedDB'),
|
|
345
|
+
ignoredGroupTtlMs: maxWatchNumber(channelList, 'ignoredGroupTtlMs'),
|
|
346
|
+
ignoredGroupMaxEntries: maxWatchNumber(channelList, 'ignoredGroupMaxEntries'),
|
|
347
|
+
limit: 0,
|
|
348
|
+
since: nowSeconds(),
|
|
349
|
+
liveOnly: true,
|
|
350
|
+
onChunk: handleChunk,
|
|
351
|
+
onEvent: (event, outer, meta) => {
|
|
352
|
+
return dispatchWatchedEvent(event, outer, meta)
|
|
353
|
+
},
|
|
354
|
+
onNymEvent: (event, outer, meta) => {
|
|
355
|
+
return dispatchWatchedNymEvent(event, outer, meta)
|
|
356
|
+
},
|
|
357
|
+
onSeedEvent: (seed) => {
|
|
358
|
+
return dispatchSeedEvent(seed)
|
|
359
|
+
},
|
|
360
|
+
onContentKeyUsage: dispatchContentKeyUsage,
|
|
361
|
+
onError: err => firstWatch.callbacks.onError?.(err)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
subsByRelay.set(relay, {
|
|
365
|
+
channels: new Set(channels),
|
|
366
|
+
revisions: watchRevisionsForChannels(channelList),
|
|
367
|
+
sub
|
|
368
|
+
})
|
|
369
|
+
if (current) {
|
|
370
|
+
const close = closeSubscription(current.sub, gracefulClose)
|
|
371
|
+
if (close) closing.push(close)
|
|
372
|
+
}
|
|
311
373
|
}
|
|
374
|
+
return Promise.allSettled(closing)
|
|
312
375
|
}
|
|
313
376
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
377
|
+
async function watch ({
|
|
378
|
+
channels,
|
|
379
|
+
relays,
|
|
380
|
+
receiverSigner,
|
|
381
|
+
iykcSigner,
|
|
382
|
+
privateChannelSigner = receiverSigner,
|
|
383
|
+
privateChannelReaderSigner = privateChannelSigner,
|
|
384
|
+
privateChannelReaderPubkey,
|
|
385
|
+
receiverPubkey,
|
|
386
|
+
mode = 'leecher',
|
|
387
|
+
onAsk,
|
|
388
|
+
onReply,
|
|
389
|
+
onTell,
|
|
390
|
+
onYell,
|
|
391
|
+
onNym,
|
|
392
|
+
onMessage,
|
|
393
|
+
onSeed,
|
|
394
|
+
onChunk,
|
|
395
|
+
onContentKeyUsage,
|
|
396
|
+
onError,
|
|
397
|
+
receivedChunkTtlMs,
|
|
398
|
+
receivedChunkMaxBytes,
|
|
399
|
+
receivedChunkIndexedDB,
|
|
400
|
+
ignoredGroupTtlMs,
|
|
401
|
+
ignoredGroupMaxEntries,
|
|
402
|
+
since = nowSeconds(),
|
|
403
|
+
_subscribe = privateChannel.subscribe
|
|
404
|
+
}) {
|
|
405
|
+
if (!relays?.length) throw new ValidationError('NO_RELAYS')
|
|
406
|
+
const request = ++requestSequence
|
|
407
|
+
const channelList = uniq(channels?.length ? channels : [await ownPrivateChannelPubkey(privateChannelSigner)])
|
|
408
|
+
for (const channel of channelList) {
|
|
409
|
+
if ((watchRequests.get(channel) || 0) < request) watchRequests.set(channel, request)
|
|
410
|
+
}
|
|
411
|
+
const ownPubkey = receiverPubkey || await receiverSigner?.getPublicKey?.()
|
|
412
|
+
if (identity !== undefined && identity !== ownPubkey) throw new ValidationError('PRIVATE_MESSAGE_IDENTITY_MISMATCH')
|
|
413
|
+
identity = ownPubkey
|
|
414
|
+
const callbacks = { onAsk, onReply, onTell, onYell, onNym, onMessage, onSeed, onChunk, onContentKeyUsage, onError }
|
|
415
|
+
|
|
416
|
+
let changed = false
|
|
417
|
+
for (const channel of channelList) {
|
|
418
|
+
if (watchRequests.get(channel) !== request) continue
|
|
419
|
+
const next = {
|
|
420
|
+
request,
|
|
421
|
+
relays: uniq(relays),
|
|
422
|
+
receiverSigner,
|
|
423
|
+
iykcSigner,
|
|
424
|
+
privateChannelSigner,
|
|
425
|
+
privateChannelReaderSigner: privateChannelReaderSigner || privateChannelSigner,
|
|
426
|
+
privateChannelReaderPubkey,
|
|
427
|
+
receiverPubkey: ownPubkey,
|
|
428
|
+
mode,
|
|
429
|
+
receivedChunkTtlMs,
|
|
430
|
+
receivedChunkMaxBytes,
|
|
431
|
+
receivedChunkIndexedDB,
|
|
432
|
+
ignoredGroupTtlMs,
|
|
433
|
+
ignoredGroupMaxEntries,
|
|
434
|
+
callbacks,
|
|
435
|
+
since
|
|
436
|
+
}
|
|
437
|
+
const current = watchesByChannel.get(channel)
|
|
438
|
+
const areSettingsEqual = Boolean(
|
|
439
|
+
current &&
|
|
440
|
+
current.receiverSigner === next.receiverSigner &&
|
|
441
|
+
current.iykcSigner === next.iykcSigner &&
|
|
442
|
+
current.privateChannelSigner === next.privateChannelSigner &&
|
|
443
|
+
current.privateChannelReaderSigner === next.privateChannelReaderSigner &&
|
|
444
|
+
current.privateChannelReaderPubkey === next.privateChannelReaderPubkey &&
|
|
445
|
+
current.receiverPubkey === next.receiverPubkey &&
|
|
446
|
+
current.mode === next.mode &&
|
|
447
|
+
current.receivedChunkTtlMs === next.receivedChunkTtlMs &&
|
|
448
|
+
current.receivedChunkMaxBytes === next.receivedChunkMaxBytes &&
|
|
449
|
+
current.receivedChunkIndexedDB === next.receivedChunkIndexedDB &&
|
|
450
|
+
current.ignoredGroupTtlMs === next.ignoredGroupTtlMs &&
|
|
451
|
+
current.ignoredGroupMaxEntries === next.ignoredGroupMaxEntries
|
|
452
|
+
)
|
|
453
|
+
next.revision = areSettingsEqual ? current.revision : nextWatchRevision++
|
|
454
|
+
if (areSettingsEqual && areSetsEqual(new Set(current.relays), new Set(next.relays))) {
|
|
455
|
+
current.callbacks = callbacks
|
|
456
|
+
current.request = request
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
watchesByChannel.set(channel, next)
|
|
460
|
+
changed = true
|
|
461
|
+
}
|
|
356
462
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
})
|
|
362
|
-
if (current) {
|
|
363
|
-
const close = closeSubscription(current.sub, gracefulClose)
|
|
364
|
-
if (close) closing.push(close)
|
|
463
|
+
if (changed) await rebuildSubscriptions({ _subscribe })
|
|
464
|
+
return () => {
|
|
465
|
+
const owned = channelList.filter(channel => watchRequests.get(channel) === request)
|
|
466
|
+
return unwatch(owned)
|
|
365
467
|
}
|
|
366
468
|
}
|
|
367
|
-
return Promise.allSettled(closing)
|
|
368
|
-
}
|
|
369
469
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
privateChannelSigner = receiverSigner,
|
|
376
|
-
privateChannelReaderSigner = privateChannelSigner,
|
|
377
|
-
privateChannelReaderPubkey,
|
|
378
|
-
receiverPubkey,
|
|
379
|
-
mode = 'leecher',
|
|
380
|
-
onAsk,
|
|
381
|
-
onReply,
|
|
382
|
-
onTell,
|
|
383
|
-
onYell,
|
|
384
|
-
onNym,
|
|
385
|
-
onMessage,
|
|
386
|
-
onSeed,
|
|
387
|
-
onChunk,
|
|
388
|
-
onContentKeyUsage,
|
|
389
|
-
onError,
|
|
390
|
-
receivedChunkTtlMs,
|
|
391
|
-
receivedChunkMaxBytes,
|
|
392
|
-
receivedChunkIndexedDB,
|
|
393
|
-
ignoredGroupTtlMs,
|
|
394
|
-
ignoredGroupMaxEntries,
|
|
395
|
-
since = nowSeconds(),
|
|
396
|
-
_subscribe = privateChannel.subscribe
|
|
397
|
-
}) {
|
|
398
|
-
if (!relays?.length) throw new ValidationError('NO_RELAYS')
|
|
399
|
-
const channelList = uniq(channels?.length ? channels : [await ownPrivateChannelPubkey(privateChannelSigner)])
|
|
400
|
-
const ownPubkey = receiverPubkey || await receiverSigner?.getPublicKey?.()
|
|
401
|
-
const callbacks = { onAsk, onReply, onTell, onYell, onNym, onMessage, onSeed, onChunk, onContentKeyUsage, onError }
|
|
402
|
-
|
|
403
|
-
let changed = false
|
|
404
|
-
for (const channel of channelList) {
|
|
405
|
-
const next = {
|
|
406
|
-
relays: uniq(relays),
|
|
407
|
-
receiverSigner,
|
|
408
|
-
iykcSigner,
|
|
409
|
-
privateChannelSigner,
|
|
410
|
-
privateChannelReaderSigner: privateChannelReaderSigner || privateChannelSigner,
|
|
411
|
-
privateChannelReaderPubkey,
|
|
412
|
-
receiverPubkey: ownPubkey,
|
|
413
|
-
mode,
|
|
414
|
-
receivedChunkTtlMs,
|
|
415
|
-
receivedChunkMaxBytes,
|
|
416
|
-
receivedChunkIndexedDB,
|
|
417
|
-
ignoredGroupTtlMs,
|
|
418
|
-
ignoredGroupMaxEntries,
|
|
419
|
-
callbacks,
|
|
420
|
-
since
|
|
470
|
+
function unwatch (channels) {
|
|
471
|
+
const channelList = channels ? uniq(Array.isArray(channels) ? channels : [channels]) : [...watchRequests.keys()]
|
|
472
|
+
for (const channel of channelList) {
|
|
473
|
+
watchRequests.set(channel, ++requestSequence)
|
|
474
|
+
watchesByChannel.delete(channel)
|
|
421
475
|
}
|
|
422
|
-
|
|
423
|
-
const areSettingsEqual = Boolean(
|
|
424
|
-
current &&
|
|
425
|
-
current.receiverSigner === next.receiverSigner &&
|
|
426
|
-
current.iykcSigner === next.iykcSigner &&
|
|
427
|
-
current.privateChannelSigner === next.privateChannelSigner &&
|
|
428
|
-
current.privateChannelReaderSigner === next.privateChannelReaderSigner &&
|
|
429
|
-
current.privateChannelReaderPubkey === next.privateChannelReaderPubkey &&
|
|
430
|
-
current.receiverPubkey === next.receiverPubkey &&
|
|
431
|
-
current.mode === next.mode &&
|
|
432
|
-
current.receivedChunkTtlMs === next.receivedChunkTtlMs &&
|
|
433
|
-
current.receivedChunkMaxBytes === next.receivedChunkMaxBytes &&
|
|
434
|
-
current.receivedChunkIndexedDB === next.receivedChunkIndexedDB &&
|
|
435
|
-
current.ignoredGroupTtlMs === next.ignoredGroupTtlMs &&
|
|
436
|
-
current.ignoredGroupMaxEntries === next.ignoredGroupMaxEntries
|
|
437
|
-
)
|
|
438
|
-
next.revision = areSettingsEqual ? current.revision : nextWatchRevision++
|
|
439
|
-
if (areSettingsEqual && areSetsEqual(new Set(current.relays), new Set(next.relays))) {
|
|
440
|
-
current.callbacks = callbacks
|
|
441
|
-
continue
|
|
442
|
-
}
|
|
443
|
-
watchesByChannel.set(channel, next)
|
|
444
|
-
changed = true
|
|
476
|
+
return rebuildSubscriptions({ gracefulClose: false })
|
|
445
477
|
}
|
|
446
478
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
479
|
+
function clearChannelState (channelPubkey) {
|
|
480
|
+
if (watchesByChannel.has(channelPubkey)) return unwatch(channelPubkey)
|
|
481
|
+
return Promise.resolve([])
|
|
482
|
+
}
|
|
450
483
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
484
|
+
return {
|
|
485
|
+
watch, unwatch, clearChannelState,
|
|
486
|
+
ask: options => ask({ ...options, _assertWatching: assertWatching }),
|
|
487
|
+
reply, tell, yell, broadcastRumor, broadcastEvent, broadcastNymRumor, broadcastNymEvent
|
|
488
|
+
}
|
|
455
489
|
}
|
|
456
490
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
491
|
+
const defaultSessions = new Map()
|
|
492
|
+
export async function watch (options) {
|
|
493
|
+
const identity = options.receiverPubkey || await options.receiverSigner?.getPublicKey?.() || ''
|
|
494
|
+
let session = defaultSessions.get(identity)
|
|
495
|
+
if (!session) defaultSessions.set(identity, (session = createPrivateMessageSession()))
|
|
496
|
+
return session.watch(options)
|
|
497
|
+
}
|
|
498
|
+
export async function unwatch (channels) {
|
|
499
|
+
await Promise.all([...defaultSessions.values()].map(session => session.unwatch(channels)))
|
|
500
|
+
}
|
|
501
|
+
export async function clearChannelState (channelPubkey) {
|
|
502
|
+
await Promise.all([...defaultSessions.values()].map(session => session.clearChannelState(channelPubkey)))
|
|
460
503
|
}
|
|
461
504
|
|
|
462
505
|
async function sendPrivateMessage ({
|
|
@@ -517,12 +560,19 @@ export async function ask ({
|
|
|
517
560
|
deletionPubkey,
|
|
518
561
|
deletionSeckey,
|
|
519
562
|
autoDeletionCapability = true,
|
|
520
|
-
_publish = privateChannel.publish
|
|
563
|
+
_publish = privateChannel.publish,
|
|
564
|
+
_assertWatching
|
|
521
565
|
}) {
|
|
522
566
|
if (!receiverPubkey) throw new ValidationError('RECEIVER_PUBKEY_REQUIRED')
|
|
523
567
|
if (!privateChannelSigner?.getPublicKey) throw new ValidationError('PRIVATE_CHANNEL_WRITER_REQUIRED')
|
|
524
568
|
const privateChannelPubkey = await ownPrivateChannelPubkey(privateChannelSigner)
|
|
525
|
-
|
|
569
|
+
if (_assertWatching) _assertWatching(privateChannelPubkey)
|
|
570
|
+
else {
|
|
571
|
+
const identity = await senderSigner?.getPublicKey?.()
|
|
572
|
+
const session = defaultSessions.get(identity)
|
|
573
|
+
if (!session) throw new Error('PRIVATE_MESSAGE_NOT_WATCHING')
|
|
574
|
+
return session.ask({ senderSigner, imkcSigner, privateChannelSigner, privateChannelReaderPubkey, receiverPubkey, relays, relayToReceivers, recoveryRelays, message, code, payload, error, content, expirationSeconds, temporaryStorageArea, _getIykcProofs, deletionPubkey, deletionSeckey, autoDeletionCapability, _publish })
|
|
575
|
+
}
|
|
526
576
|
|
|
527
577
|
const { event: question, wireEvent } = await makeOutgoingRumor({
|
|
528
578
|
senderSigner,
|
|
@@ -716,6 +766,8 @@ export async function broadcastNymRumor ({
|
|
|
716
766
|
_publish = privateChannel.publishNymEvent
|
|
717
767
|
}) {
|
|
718
768
|
if (!nymSigner?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
|
|
769
|
+
if (!nymSigner?.getPublicKey) throw new ValidationError('NYM_SIGNER_REQUIRED')
|
|
770
|
+
if (rumor.pubkey !== undefined && rumor.pubkey !== await nymSigner.getPublicKey()) throw new ValidationError('NYM_RUMOR_AUTHOR_MISMATCH')
|
|
719
771
|
const { event, wireEvent } = await makeOutgoingRumor({ senderSigner: nymSigner, rumor })
|
|
720
772
|
const deletion = resolveDeletionCapability({ deletionPubkey, deletionSeckey, autoDeletionCapability })
|
|
721
773
|
const reports = await sendNymMessage({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey: deletion.deletionPubkey, event: wireEvent, relays, relayToReceivers, recoveryRelays, expirationSeconds, _publish })
|