79 lines
2.5 KiB
TypeScript
79 lines
2.5 KiB
TypeScript
import {get, writable} from "svelte/store"
|
|
import type {Unsubscriber} from "svelte/store"
|
|
import {on, TaskQueue} from "@welshman/lib"
|
|
import {WRAP, getPubkeyTagValues} from "@welshman/util"
|
|
import type {TrustedEvent, SignedEvent} from "@welshman/util"
|
|
import {Nip59} from "@welshman/signer"
|
|
import type {ClientContext} from "./client.js"
|
|
|
|
export type GiftWrapsOptions = {
|
|
shouldUnwrap?: boolean
|
|
}
|
|
|
|
/**
|
|
* Per-client gift-wrap (NIP-59) ingestion. Watches the client's repository for
|
|
* kind-1059 wraps and unwraps the ones addressed to THIS client's user, storing
|
|
* the resulting rumors via the wrap manager.
|
|
*
|
|
* In the old global model a single queue tried every logged-in account's signer
|
|
* against every wrap, depositing all rumors into one shared repository — which
|
|
* is exactly how DM history got merged across accounts. Here a client only ever
|
|
* unwraps its own user's messages into its own repository.
|
|
*/
|
|
export class GiftWraps {
|
|
shouldUnwrap = writable(false)
|
|
failedUnwraps = new Set<string>()
|
|
queue: TaskQueue<TrustedEvent>
|
|
cleanup: Unsubscriber
|
|
|
|
constructor(
|
|
readonly ctx: ClientContext,
|
|
options: GiftWrapsOptions = {},
|
|
) {
|
|
this.shouldUnwrap.set(options.shouldUnwrap ?? false)
|
|
|
|
this.queue = new TaskQueue<TrustedEvent>({
|
|
batchSize: 5,
|
|
batchDelay: 30,
|
|
processItem: async (wrap: TrustedEvent) => {
|
|
const signer = this.ctx.user?.signer
|
|
const recipient = this.ctx.user?.pubkey
|
|
|
|
// Only unwrap messages addressed to our user
|
|
if (!signer || !recipient || !getPubkeyTagValues(wrap.tags).includes(recipient)) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
const rumor = await Nip59.fromSigner(signer).unwrap(wrap as SignedEvent)
|
|
|
|
this.ctx.wrapManager.add({wrap: wrap as SignedEvent, rumor, recipient})
|
|
} catch (e) {
|
|
this.failedUnwraps.add(wrap.id)
|
|
}
|
|
},
|
|
})
|
|
|
|
// Process wraps already in the repository, then any that arrive later
|
|
for (const wrap of this.ctx.repository.query([{kinds: [WRAP]}])) {
|
|
this.enqueue(wrap)
|
|
}
|
|
|
|
this.cleanup = on(this.ctx.repository, "update", ({added}: {added: TrustedEvent[]}) => {
|
|
for (const event of added) {
|
|
if (event.kind === WRAP) {
|
|
this.enqueue(event)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
enqueue = (wrap: TrustedEvent) => {
|
|
if (!get(this.shouldUnwrap)) return
|
|
if (this.failedUnwraps.has(wrap.id)) return
|
|
if (this.ctx.wrapManager.getRumor(wrap.id)) return
|
|
|
|
this.queue.push(wrap)
|
|
}
|
|
}
|