Add wrap manager for tracking gift wraps
This commit is contained in:
@@ -28,7 +28,6 @@ import {
|
||||
PINS,
|
||||
} from "@welshman/util"
|
||||
import type {RoomMeta, Profile} from "@welshman/util"
|
||||
import {Nip59, stamp, hash, own} from "@welshman/signer"
|
||||
import {Router, addMaximalFallbacks} from "@welshman/router"
|
||||
import {
|
||||
userRelaySelections,
|
||||
@@ -211,12 +210,11 @@ export type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & {
|
||||
|
||||
export const sendWrapped = async ({event, recipients, ...options}: SendWrappedOptions) =>
|
||||
new MergedThunk(
|
||||
uniq(recipients)
|
||||
.map(recipient => {
|
||||
const relays = Router.get().PubkeyInbox(recipient).getUrls()
|
||||
uniq(recipients).map(recipient => {
|
||||
const relays = Router.get().PubkeyInbox(recipient).getUrls()
|
||||
|
||||
return publishThunk({event, relays, recipient, ...options})
|
||||
})
|
||||
return publishThunk({event, relays, recipient, ...options})
|
||||
}),
|
||||
)
|
||||
|
||||
// NIP 86
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import {throttle} from "@welshman/lib"
|
||||
import {Repository, LocalRelay} from "@welshman/relay"
|
||||
import {Repository, LocalRelay, Tracker} from "@welshman/relay"
|
||||
import {custom} from "@welshman/store"
|
||||
import {Tracker} from "@welshman/net"
|
||||
|
||||
export const tracker = new Tracker()
|
||||
|
||||
export const repository = Repository.get()
|
||||
|
||||
export const relay = new LocalRelay(repository)
|
||||
|
||||
export const tracker = new Tracker()
|
||||
|
||||
// Adapt objects to stores
|
||||
|
||||
export const makeRepositoryStore = ({throttle: t = 300}: {throttle?: number} = {}) =>
|
||||
|
||||
@@ -24,10 +24,10 @@ export * from "./zappers.js"
|
||||
|
||||
import {derived} from "svelte/store"
|
||||
import {sortBy, throttleWithValue, tryCatch} from "@welshman/lib"
|
||||
import {isEphemeralKind, isDVMKind, RelayMode, getRelaysFromList} from "@welshman/util"
|
||||
import {isEphemeralKind, isDVMKind, WRAP, RelayMode, getRelaysFromList} from "@welshman/util"
|
||||
import {routerContext} from "@welshman/router"
|
||||
import {Pool, SocketEvent, isRelayEvent, netContext} from "@welshman/net"
|
||||
import {pubkey} from "./session.js"
|
||||
import {pubkey, unwrapAndStore} from "./session.js"
|
||||
import {repository, tracker} from "./core.js"
|
||||
import {Relay, relays, loadRelay, trackRelayStats, getRelayQuality} from "./relays.js"
|
||||
import {relaySelectionsByPubkey} from "./relaySelections.js"
|
||||
@@ -44,12 +44,17 @@ Pool.get().subscribe(socket => {
|
||||
const event = message[2]
|
||||
|
||||
if (
|
||||
!isEphemeralKind(event.kind) &&
|
||||
!isDVMKind(event.kind) &&
|
||||
!isEphemeralKind(event.kind) &&
|
||||
netContext.isEventValid(event, socket.url)
|
||||
) {
|
||||
tracker.track(event.id, socket.url)
|
||||
repository.publish(event)
|
||||
|
||||
if (event.kind === WRAP) {
|
||||
unwrapAndStore(event)
|
||||
} else {
|
||||
repository.publish(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import {derived, writable} from "svelte/store"
|
||||
import {cached, randomId, append, omit, equals, assoc} from "@welshman/lib"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import {Wallet} from "@welshman/util"
|
||||
import {
|
||||
Wallet,
|
||||
WRAP,
|
||||
isHashedEvent,
|
||||
getPubkeyTagValues,
|
||||
HashedEvent,
|
||||
SignedEvent,
|
||||
} from "@welshman/util"
|
||||
import {
|
||||
Nip59,
|
||||
WrappedSigner,
|
||||
Nip46Broker,
|
||||
Nip46Signer,
|
||||
@@ -12,6 +20,8 @@ import {
|
||||
getPubkey,
|
||||
ISigner,
|
||||
} from "@welshman/signer"
|
||||
import {WrapManager} from "@welshman/relay"
|
||||
import {relay, tracker} from "./core.js"
|
||||
|
||||
export enum SessionMethod {
|
||||
Nip01 = "nip01",
|
||||
@@ -253,6 +263,14 @@ export const getSigner = cached({
|
||||
},
|
||||
})
|
||||
|
||||
export const getSignerFromPubkey = (pubkey: string) => {
|
||||
const session = getSession(pubkey)
|
||||
|
||||
if (session) {
|
||||
return getSigner(session)
|
||||
}
|
||||
}
|
||||
|
||||
export const signer = withGetter(derived(session, getSigner))
|
||||
|
||||
export const nip44EncryptToSelf = (payload: string) => {
|
||||
@@ -265,3 +283,40 @@ export const nip44EncryptToSelf = (payload: string) => {
|
||||
|
||||
return $signer.nip44.encrypt($pubkey!, payload)
|
||||
}
|
||||
|
||||
export const wrapManager = new WrapManager({relay, tracker})
|
||||
|
||||
export const unwrapAndStore = async (wrap: SignedEvent) => {
|
||||
if (wrap.kind !== WRAP) throw new Error("Tried to unwrap an invalid event")
|
||||
|
||||
// First, check index and repository
|
||||
const cached = wrapManager.getRumor(wrap.id)
|
||||
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
let rumor: HashedEvent | undefined
|
||||
let recipient: string | undefined
|
||||
|
||||
// Next, try to decrypt as the recipient
|
||||
for (const pubkey of getPubkeyTagValues(wrap.tags)) {
|
||||
const signer = getSignerFromPubkey(pubkey)
|
||||
|
||||
if (signer) {
|
||||
try {
|
||||
rumor = await Nip59.fromSigner(signer).unwrap(wrap)
|
||||
recipient = pubkey
|
||||
break
|
||||
} catch (e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rumor && recipient && isHashedEvent(rumor)) {
|
||||
wrapManager.add({wrap, rumor, recipient})
|
||||
}
|
||||
|
||||
return rumor
|
||||
}
|
||||
|
||||
+41
-44
@@ -1,10 +1,8 @@
|
||||
import type {Subscriber} from "svelte/store"
|
||||
import {writable, get} from "svelte/store"
|
||||
import type {Override} from '@welshman/lib'
|
||||
import {writable} from "svelte/store"
|
||||
import type {Override} from "@welshman/lib"
|
||||
import {
|
||||
append,
|
||||
reject,
|
||||
spec,
|
||||
TaskQueue,
|
||||
ifLet,
|
||||
ensurePlural,
|
||||
@@ -14,20 +12,7 @@ import {
|
||||
nth,
|
||||
without,
|
||||
} from "@welshman/lib"
|
||||
import {
|
||||
TrustedEvent,
|
||||
HashedEvent,
|
||||
EventTemplate,
|
||||
SignedEvent,
|
||||
StampedEvent,
|
||||
OwnedEvent,
|
||||
isStampedEvent,
|
||||
isOwnedEvent,
|
||||
isHashedEvent,
|
||||
isUnwrappedEvent,
|
||||
isSignedEvent,
|
||||
WRAPPED_KINDS,
|
||||
} from "@welshman/util"
|
||||
import {HashedEvent, EventTemplate, SignedEvent, isSignedEvent, WRAPPED_KINDS} from "@welshman/util"
|
||||
import {
|
||||
publish,
|
||||
PublishStatus,
|
||||
@@ -35,15 +20,18 @@ import {
|
||||
PublishOptions,
|
||||
PublishResultsByRelay,
|
||||
} from "@welshman/net"
|
||||
import {ISigner, Nip59, prep} from '@welshman/signer'
|
||||
import {ISigner, Nip59, prep} from "@welshman/signer"
|
||||
import {repository, tracker} from "./core.js"
|
||||
import {pubkey, signer} from "./session.js"
|
||||
import {pubkey, signer, wrapManager} from "./session.js"
|
||||
|
||||
export type ThunkOptions = Override<PublishOptions, {
|
||||
event: EventTemplate
|
||||
recipient?: string
|
||||
delay?: number
|
||||
}>
|
||||
export type ThunkOptions = Override<
|
||||
PublishOptions,
|
||||
{
|
||||
event: EventTemplate
|
||||
recipient?: string
|
||||
delay?: number
|
||||
}
|
||||
>
|
||||
|
||||
export class Thunk {
|
||||
_subs: Subscriber<Thunk>[] = []
|
||||
@@ -54,6 +42,7 @@ export class Thunk {
|
||||
results: PublishResultsByRelay = {}
|
||||
complete = defer<void>()
|
||||
controller = new AbortController()
|
||||
wrap?: SignedEvent
|
||||
|
||||
constructor(readonly options: ThunkOptions) {
|
||||
if (!options.recipient && WRAPPED_KINDS.includes(options.event.kind)) {
|
||||
@@ -132,11 +121,6 @@ export class Thunk {
|
||||
}
|
||||
|
||||
async _publish(event: SignedEvent) {
|
||||
// Copy the signature over since we may have deferred signing
|
||||
ifLet(repository.getEvent(event.id), savedEvent => {
|
||||
savedEvent.sig = event.sig
|
||||
})
|
||||
|
||||
// Wait if the thunk is to be delayed
|
||||
if (this.options.delay) {
|
||||
await sleep(this.options.delay)
|
||||
@@ -179,17 +163,17 @@ export class Thunk {
|
||||
// Handle abort immediately if possible
|
||||
if (this.controller.signal.aborted) return
|
||||
|
||||
// If we were given an event with wraps, reject it (this used to be allowed)
|
||||
if (isUnwrappedEvent(this.event)) {
|
||||
throw new Error("Attempted to publish an unwrapped event")
|
||||
}
|
||||
const {recipient} = this.options
|
||||
|
||||
// If we're sending it privately, wrap the event using nip 59
|
||||
if (this.options.recipient) {
|
||||
if (recipient) {
|
||||
const nip59 = Nip59.fromSigner(this.signer)
|
||||
const event = await nip59.wrap(this.options.recipient, this.event)
|
||||
|
||||
return this._publish(event)
|
||||
this.wrap = await nip59.wrap(recipient, this.event)
|
||||
|
||||
wrapManager.add({recipient, wrap: this.wrap, rumor: this.event})
|
||||
|
||||
return this._publish(this.wrap)
|
||||
}
|
||||
|
||||
// If the event has been signed, we're good to go
|
||||
@@ -200,11 +184,16 @@ export class Thunk {
|
||||
// Allow for lazily signing events in order to decrease apparent latency in the UI
|
||||
// that results from waiting for remote signers
|
||||
try {
|
||||
return this._publish(
|
||||
await this.signer.sign(this.event, {
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
})
|
||||
)
|
||||
const signedEvent = await this.signer.sign(this.event, {
|
||||
signal: AbortSignal.timeout(15_000),
|
||||
})
|
||||
|
||||
// Copy the signature over since we deferred signing
|
||||
ifLet(repository.getEvent(signedEvent.id), savedEvent => {
|
||||
savedEvent.sig = signedEvent.sig
|
||||
})
|
||||
|
||||
return this._publish(signedEvent)
|
||||
} catch (e: any) {
|
||||
return this._fail(String(e || "Failed to sign event"))
|
||||
}
|
||||
@@ -214,6 +203,16 @@ export class Thunk {
|
||||
thunkQueue.push(this)
|
||||
repository.publish(this.event)
|
||||
thunks.update($thunks => append(this, $thunks))
|
||||
|
||||
this.controller.signal.addEventListener("abort", () => {
|
||||
if (this.wrap) {
|
||||
wrapManager.remove(this.wrap.id)
|
||||
} else {
|
||||
repository.removeEvent(this.event.id)
|
||||
}
|
||||
|
||||
thunks.update($thunks => remove(this, $thunks))
|
||||
})
|
||||
}
|
||||
|
||||
subscribe(subscriber: Subscriber<Thunk>) {
|
||||
@@ -389,8 +388,6 @@ export const publishThunk = (options: ThunkOptions) => {
|
||||
export const abortThunk = (thunk: AbstractThunk) => {
|
||||
for (const child of flattenThunks([thunk])) {
|
||||
child.controller.abort()
|
||||
repository.removeEvent(child.event.id)
|
||||
thunks.update($thunks => reject(spec({id: child.event.id}), $thunks))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user