re-work thunks
This commit is contained in:
+224
-194
@@ -4,6 +4,7 @@ import {
|
||||
Deferred,
|
||||
fromPairs,
|
||||
TaskQueue,
|
||||
ifLet,
|
||||
dissoc,
|
||||
remove,
|
||||
identity,
|
||||
@@ -11,6 +12,9 @@ import {
|
||||
defer,
|
||||
sleep,
|
||||
assoc,
|
||||
spec,
|
||||
nthEq,
|
||||
nth,
|
||||
} from "@welshman/lib"
|
||||
import {stamp, own, hash} from "@welshman/signer"
|
||||
import {
|
||||
@@ -26,37 +30,12 @@ import {
|
||||
isUnwrappedEvent,
|
||||
isSignedEvent,
|
||||
} from "@welshman/util"
|
||||
import {publish, AdapterContext, PublishStatus} from "@welshman/net"
|
||||
import {publish, AdapterContext, PublishStatus, PublishOptions, PublishStatusByRelay} from "@welshman/net"
|
||||
import {repository, tracker} from "./core.js"
|
||||
import {pubkey, getSession, getSigner} from "./session.js"
|
||||
|
||||
const {Pending, Success, Failure, Timeout, Aborted} = PublishStatus
|
||||
|
||||
export type ThunkEvent = EventTemplate | StampedEvent | OwnedEvent | TrustedEvent
|
||||
|
||||
export type ThunkRequest = {
|
||||
event: ThunkEvent
|
||||
relays: string[]
|
||||
delay?: number
|
||||
timeout?: number
|
||||
context?: AdapterContext
|
||||
}
|
||||
|
||||
export type ThunkStatus = {
|
||||
message: string
|
||||
status: PublishStatus
|
||||
}
|
||||
|
||||
export type ThunkStatusByRelay = Record<string, ThunkStatus>
|
||||
|
||||
export type Thunk = {
|
||||
event: TrustedEvent
|
||||
request: ThunkRequest
|
||||
controller: AbortController
|
||||
result: Deferred<ThunkStatusByRelay>
|
||||
status: Writable<ThunkStatusByRelay>
|
||||
}
|
||||
|
||||
export const prepEvent = (event: ThunkEvent) => {
|
||||
if (!isStampedEvent(event as StampedEvent)) {
|
||||
event = stamp(event)
|
||||
@@ -73,63 +52,229 @@ export const prepEvent = (event: ThunkEvent) => {
|
||||
return event as TrustedEvent
|
||||
}
|
||||
|
||||
export const makeThunk = (request: ThunkRequest) => {
|
||||
const event = prepEvent(request.event)
|
||||
const controller = new AbortController()
|
||||
const result: Thunk["result"] = defer()
|
||||
const status: Thunk["status"] = writable({})
|
||||
|
||||
return {event, request, controller, result, status}
|
||||
export type ThunkOptions = Omit<PublishOptions, 'event'> & {
|
||||
event: ThunkEvent
|
||||
delay?: number
|
||||
}
|
||||
|
||||
export type MergedThunk = {
|
||||
thunks: Thunk[]
|
||||
controller: AbortController
|
||||
result: Promise<ThunkStatusByRelay[]>
|
||||
status: Readable<ThunkStatusByRelay>
|
||||
}
|
||||
export class Thunk {
|
||||
_subs: Subscriber<Thunk>[] = []
|
||||
|
||||
export const isMergedThunk = (thunk: Thunk | MergedThunk): thunk is MergedThunk =>
|
||||
Boolean((thunk as any).thunks)
|
||||
event: TrustedEvent
|
||||
result = defer<PublishStatusByRelay>()
|
||||
status: PublishStatusByRelay = {}
|
||||
details: Record<string, string> = {}
|
||||
controller = new AbortController()
|
||||
|
||||
export const mergeThunks = (thunks: Thunk[]) => {
|
||||
const controller = new AbortController()
|
||||
constructor(readonly options: ThunkOptions) {
|
||||
this.event = prepEvent(options.event)
|
||||
|
||||
controller.signal.addEventListener("abort", () => {
|
||||
for (const thunk of thunks) {
|
||||
thunk.controller.abort()
|
||||
for (const relay of options.relays) {
|
||||
this.status[relay] = PublishStatus.Sending
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
thunks,
|
||||
controller,
|
||||
result: Promise.all(thunks.map(thunk => thunk.result)),
|
||||
status: derived(
|
||||
thunks.map(thunk => thunk.status),
|
||||
statuses => {
|
||||
const mergedStatus: ThunkStatusByRelay = {}
|
||||
_notify() {
|
||||
for (const subscriber of this._subs) {
|
||||
subscriber(this)
|
||||
}
|
||||
}
|
||||
|
||||
for (const url of uniq(statuses.flatMap(s => Object.keys(s)))) {
|
||||
const urlStatuses = statuses.map(s => s[url])
|
||||
const thunkStatus = [Aborted, Failure, Timeout, Pending, Success]
|
||||
.map(status => urlStatuses.find(s => s?.status === status))
|
||||
.find(identity)
|
||||
_fail(message: string) {
|
||||
for (const relay of this.options.relays) {
|
||||
this.status[relay] = PublishStatus.Failure
|
||||
this.details[relay] = message
|
||||
}
|
||||
|
||||
if (thunkStatus) {
|
||||
mergedStatus[url] = thunkStatus
|
||||
}
|
||||
}
|
||||
this._notify()
|
||||
}
|
||||
|
||||
return mergedStatus
|
||||
},
|
||||
),
|
||||
async publish() {
|
||||
let event = this.event
|
||||
|
||||
// Handle abort immediately if possible
|
||||
if (this.controller.signal.aborted) return
|
||||
|
||||
// If we were given a wrapped event, make sure to publish the wrapper, not the rumor
|
||||
if (isUnwrappedEvent(event)) {
|
||||
event = event.wrap
|
||||
}
|
||||
|
||||
// If the event was already signed, leave it alone. Otherwise, sign it now. This is to
|
||||
// decrease apparent latency in the UI that results from waiting for remote signers
|
||||
if (!isSignedEvent(event)) {
|
||||
const signer = getSigner(getSession(event.pubkey))
|
||||
|
||||
if (!signer) {
|
||||
return this._fail(`No signer found for ${event.pubkey}`)
|
||||
}
|
||||
|
||||
try {
|
||||
event = await signer.sign(event)
|
||||
} catch (e: any) {
|
||||
return this._fail(String(e.error || e))
|
||||
}
|
||||
}
|
||||
|
||||
// We're guaranteed to have a signed event at this point
|
||||
const signedEvent = event as SignedEvent
|
||||
|
||||
// Copy the signature over since we had deferred signing
|
||||
ifLet(repository.getEvent(signedEvent.id), savedEvent => {
|
||||
savedEvent.sig = signedEvent.sig
|
||||
})
|
||||
|
||||
// Wait if the thunk is to be delayed
|
||||
if (this.options.delay) {
|
||||
await sleep(this.options.delay)
|
||||
}
|
||||
|
||||
// Skip publishing if aborted
|
||||
if (this.controller.signal.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
// Send it off
|
||||
this.result.resolve(
|
||||
await publish({
|
||||
...this.options,
|
||||
event: signedEvent,
|
||||
onSuccess: (message: string, relay: string) => {
|
||||
tracker.track(signedEvent.id, relay)
|
||||
this.options.onSuccess?.(message, relay)
|
||||
this.status[relay] = PublishStatus.Success
|
||||
this.details[relay] = message
|
||||
this._notify()
|
||||
},
|
||||
onFailure: (message: string, relay: string) => {
|
||||
this.options.onFailure?.(message, relay)
|
||||
this.status[relay] = PublishStatus.Failure
|
||||
this.details[relay] = message
|
||||
this._notify()
|
||||
},
|
||||
onPending: (relay: string) => {
|
||||
this.options.onPending?.(relay)
|
||||
this.status[relay] = PublishStatus.Pending
|
||||
this._notify()
|
||||
},
|
||||
onTimeout: (relay: string) => {
|
||||
this.options.onTimeout?.(relay)
|
||||
this.status[relay] = PublishStatus.Timeout
|
||||
this.details[relay] = "Publish timed out"
|
||||
this._notify()
|
||||
},
|
||||
onAborted: (relay: string) => {
|
||||
this.options.onAborted?.(relay)
|
||||
this.status[relay] = PublishStatus.Aborted
|
||||
this.details[relay] = "Publish was aborted"
|
||||
this._notify()
|
||||
},
|
||||
onComplete: () => {
|
||||
this.options.onComplete?.()
|
||||
this._subs = []
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
subscribe(subscriber: Subscriber<Thunk>) {
|
||||
this._subs.push(subscriber)
|
||||
|
||||
subscriber(this)
|
||||
|
||||
return () => {
|
||||
this._subs = remove(subscriber, this._subs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* walkThunks(thunks: (Thunk | MergedThunk)[]): Iterable<Thunk> {
|
||||
export class MergedThunk {
|
||||
_subs: Subscriber<MergedThunk>[] = []
|
||||
|
||||
controller = new AbortController()
|
||||
status: PublishStatusByRelay = {}
|
||||
details: Record<string, string> = {}
|
||||
|
||||
constructor(readonly thunks: Thunk[]) {
|
||||
const {Aborted, Failure, Timeout, Pending, Success} = PublishStatus
|
||||
const relays = new Set(thunks.flatMap(thunk => Object.keys(thunk.options.relays)))
|
||||
const statusMaps = thunks.map(thunk => thunk.status)
|
||||
|
||||
for (const thunk of thunks) {
|
||||
this.controller.signal.addEventListener("abort", () => thunk.controller.abort())
|
||||
|
||||
thunk.subscribe($thunk => {
|
||||
this.status = {}
|
||||
this.details = {}
|
||||
|
||||
for (const relay of relays) {
|
||||
for (const status of [Aborted, Failure, Timeout, Pending, Success]) {
|
||||
const thunk = thunks.find(spec({[relay]: status}))
|
||||
|
||||
if (thunk) {
|
||||
this.status[relay] = thunk.status[relay]!
|
||||
this.details[relay] = thunk.details[relay]!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._notify()
|
||||
|
||||
if (thunks.filter(thunkIsComplete).length === thunks.length) {
|
||||
this._subs = []
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_notify() {
|
||||
for (const subscriber of this._subs) {
|
||||
subscriber(this)
|
||||
}
|
||||
}
|
||||
|
||||
subscribe(subscriber: Subscriber<MergedThunk>) {
|
||||
this._subs.push(subscriber)
|
||||
|
||||
subscriber(this)
|
||||
|
||||
return () => {
|
||||
this._subs = remove(subscriber, this._subs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type AbstractThunk = Thunk | MergedThunk
|
||||
|
||||
export const isThunk = (thunk: AbstractThunk): thunk is Thunk =>
|
||||
thunk instanceof Thunk
|
||||
|
||||
export const isMergedThunk = (thunk: AbstractThunk): thunk is MergedThunk =>
|
||||
thunk instanceof MergedThunk
|
||||
|
||||
export const thunkHasStatus = (thunk: AbstractThunk, status: PublishStatus) =>
|
||||
Object.entries(thunk.status).some(nthEq(1, status))
|
||||
|
||||
export const thunkUrlsWithStatus = (thunk: AbstractThunk, status: PublishStatus) =>
|
||||
Object.entries(thunk.status).filter(nthEq(1, status)).map(nth(0))
|
||||
|
||||
export const thunkCompleteUrls = (thunk: AbstractThunk) => {
|
||||
const incompleteStatuses = [PublishStatus.Sending, PublishStatus.Pending]
|
||||
|
||||
return Object.entries(thunk.status).filter(([_, s]) => !incompleteStatuses.includes(s)).map(nth(1))
|
||||
}
|
||||
|
||||
export const thunkIncompleteUrls = (thunk: AbstractThunk) => {
|
||||
const incompleteStatuses = [PublishStatus.Sending, PublishStatus.Pending]
|
||||
|
||||
return Object.entries(thunk.status).filter(([_, s]) => incompleteStatuses.includes(s)).map(nth(1))
|
||||
}
|
||||
|
||||
export const thunkIsComplete = (thunk: AbstractThunk) => thunkCompleteUrls(thunk).length > 0
|
||||
|
||||
export function* walkThunks(thunks: (AbstractThunk)[]): Iterable<Thunk> {
|
||||
for (const thunk of thunks) {
|
||||
if (isMergedThunk(thunk)) {
|
||||
if (thunk instanceof MergedThunk) {
|
||||
yield* walkThunks(thunk.thunks)
|
||||
} else {
|
||||
yield thunk
|
||||
@@ -137,10 +282,17 @@ export function* walkThunks(thunks: (Thunk | MergedThunk)[]): Iterable<Thunk> {
|
||||
}
|
||||
}
|
||||
|
||||
export const thunks = writable<Record<string, Thunk | MergedThunk>>({})
|
||||
export const thunks = writable<Record<string, AbstractThunk>>({})
|
||||
|
||||
export const publishThunk = (request: ThunkRequest) => {
|
||||
const thunk = makeThunk(request)
|
||||
export const thunkQueue = new TaskQueue<Thunk>({
|
||||
batchSize: 50,
|
||||
processItem: (thunk: Thunk) => {
|
||||
thunk.publish()
|
||||
},
|
||||
})
|
||||
|
||||
export const publishThunk = (options: ThunkOptions) => {
|
||||
const thunk = new Thunk(options)
|
||||
|
||||
thunkQueue.push(thunk)
|
||||
|
||||
@@ -155,130 +307,8 @@ export const publishThunk = (request: ThunkRequest) => {
|
||||
return thunk
|
||||
}
|
||||
|
||||
export const publishThunks = (requests: ThunkRequest[]) => {
|
||||
const newThunks = requests.map(makeThunk)
|
||||
const mergedThunk = mergeThunks(newThunks)
|
||||
|
||||
for (const thunk of newThunks) {
|
||||
thunkQueue.push(thunk)
|
||||
|
||||
repository.publish(thunk.event)
|
||||
|
||||
thunks.update(assoc(thunk.event.id, mergedThunk))
|
||||
|
||||
thunk.controller.signal.addEventListener("abort", () => {
|
||||
repository.removeEvent(thunk.event.id)
|
||||
})
|
||||
}
|
||||
|
||||
return mergedThunk
|
||||
}
|
||||
|
||||
export const abortThunk = (thunk: Thunk) => {
|
||||
thunk.controller.abort()
|
||||
thunks.update(dissoc(thunk.event.id))
|
||||
repository.removeEvent(thunk.event.id)
|
||||
}
|
||||
|
||||
export const thunkQueue = new TaskQueue<Thunk>({
|
||||
batchSize: 50,
|
||||
processItem: (thunk: Thunk) => {
|
||||
let event = thunk.event
|
||||
|
||||
// Handle abort immediately if possible
|
||||
if (thunk.controller.signal.aborted) return
|
||||
|
||||
// If we were given a wrapped event, make sure to publish the wrapper, not the rumor
|
||||
if (isUnwrappedEvent(event)) {
|
||||
event = event.wrap
|
||||
}
|
||||
|
||||
// Avoid making this function async so multiple publishes can run concurrently
|
||||
Promise.resolve().then(async () => {
|
||||
const fail = (message: string) => {
|
||||
const status: ThunkStatusByRelay = {}
|
||||
|
||||
for (const url of thunk.request.relays) {
|
||||
status[url] = {status: PublishStatus.Failure, message}
|
||||
}
|
||||
|
||||
thunk.status.set(status)
|
||||
}
|
||||
|
||||
// If the event was already signed, leave it alone. Otherwise, sign it now. This is to
|
||||
// decrease apparent latency in the UI that results from waiting for remote signers
|
||||
if (!isSignedEvent(event)) {
|
||||
const signer = getSigner(getSession(event.pubkey))
|
||||
|
||||
if (!signer) {
|
||||
return fail(`No signer found for ${event.pubkey}`)
|
||||
}
|
||||
|
||||
try {
|
||||
event = await signer.sign(event)
|
||||
} catch (e: any) {
|
||||
return fail(String(e.error || e))
|
||||
}
|
||||
}
|
||||
|
||||
// We're guaranteed to have a signed event at this point
|
||||
const signedEvent = event as SignedEvent
|
||||
|
||||
// Wait if the thunk is to be delayed
|
||||
if (thunk.request.delay) {
|
||||
await sleep(thunk.request.delay)
|
||||
}
|
||||
|
||||
// Skip publishing if aborted
|
||||
if (thunk.controller.signal.aborted) {
|
||||
return
|
||||
}
|
||||
|
||||
// Update status to pending
|
||||
thunk.status.set(
|
||||
fromPairs(
|
||||
thunk.request.relays.map(url => [
|
||||
url,
|
||||
{status: Pending, message: "Sending your message..."},
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
// Send it off
|
||||
publish({
|
||||
event: signedEvent,
|
||||
relays: thunk.request.relays,
|
||||
context: thunk.request.context,
|
||||
timeout: thunk.request.timeout,
|
||||
onSuccess: (message: string, url: string) => {
|
||||
tracker.track(signedEvent.id, url)
|
||||
thunk.status.update(assoc(url, {status: PublishStatus.Success, message}))
|
||||
},
|
||||
onFailure: (message: string, url: string) => {
|
||||
thunk.status.update(assoc(url, {status: PublishStatus.Failure, message}))
|
||||
},
|
||||
onTimeout: (url: string) => {
|
||||
const message = "Publish timed out"
|
||||
|
||||
thunk.status.update(assoc(url, {status: PublishStatus.Timeout, message}))
|
||||
},
|
||||
onAborted: (url: string) => {
|
||||
const message = "Publish was aborted"
|
||||
|
||||
thunk.status.update(assoc(url, {status: PublishStatus.Aborted, message}))
|
||||
},
|
||||
onComplete: () => {
|
||||
thunk.result.resolve(get(thunk.status))
|
||||
},
|
||||
})
|
||||
|
||||
// Copy the signature over since we had deferred it
|
||||
const savedEvent = repository.getEvent(signedEvent.id) as SignedEvent
|
||||
|
||||
// The event may already be replaced or deleted
|
||||
if (savedEvent) {
|
||||
savedEvent.sig = signedEvent.sig
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1283,13 +1283,13 @@ export const spec =
|
||||
/** Returns a function that checks equality with value */
|
||||
export const eq =
|
||||
<T>(v: T) =>
|
||||
(x: T) =>
|
||||
(x: T, ...args: unknown[]) =>
|
||||
x === v
|
||||
|
||||
/** Returns a function that checks inequality with value */
|
||||
export const ne =
|
||||
<T>(v: T) =>
|
||||
(x: T) =>
|
||||
(x: T, ...args: unknown[]) =>
|
||||
x !== v
|
||||
|
||||
/** Returns a function that gets property value from object */
|
||||
@@ -1310,6 +1310,12 @@ export const dissoc =
|
||||
(o: T) =>
|
||||
omit([k], o)
|
||||
|
||||
/** Returns a function that checks whether a value is in the given sequence */
|
||||
export const member =
|
||||
<T>(xs: Iterable<T>) =>
|
||||
(x: T) =>
|
||||
Array.from(xs).includes(x)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Sets
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
+50
-26
@@ -5,6 +5,7 @@ import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
|
||||
import {AbstractAdapter, AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
|
||||
|
||||
export enum PublishStatus {
|
||||
Sending = "publish:status:sending",
|
||||
Pending = "publish:status:pending",
|
||||
Success = "publish:status:success",
|
||||
Failure = "publish:status:failure",
|
||||
@@ -23,11 +24,11 @@ export type PublishOneOptions = {
|
||||
signal?: AbortSignal
|
||||
timeout?: number
|
||||
context?: AdapterContext
|
||||
onStatus?: (status: PublishStatus, relay: string) => void
|
||||
onSuccess?: (detail: string, relay: string) => void
|
||||
onFailure?: (detail: string, relay: string) => void
|
||||
onTimeout?: (relay: string) => void
|
||||
onAborted?: (relay: string) => void
|
||||
onSuccess?: (detail: string) => void
|
||||
onFailure?: (detail: string) => void
|
||||
onPending?: () => void
|
||||
onTimeout?: () => void
|
||||
onAborted?: () => void
|
||||
onComplete?: () => void
|
||||
}
|
||||
|
||||
@@ -37,10 +38,7 @@ export const publishOne = (options: PublishOneOptions) =>
|
||||
|
||||
let status = PublishStatus.Pending
|
||||
|
||||
const setStatus = (_status: PublishStatus) => {
|
||||
status = _status
|
||||
options.onStatus?.(status, options.relay)
|
||||
}
|
||||
options.onPending?.()
|
||||
|
||||
const cleanup = () => {
|
||||
options.onComplete?.()
|
||||
@@ -57,11 +55,11 @@ export const publishOne = (options: PublishOneOptions) =>
|
||||
if (id !== options.event.id) return
|
||||
|
||||
if (ok) {
|
||||
setStatus(PublishStatus.Success)
|
||||
options.onSuccess?.(detail, options.relay)
|
||||
status = PublishStatus.Success
|
||||
options.onSuccess?.(detail)
|
||||
} else {
|
||||
setStatus(PublishStatus.Failure)
|
||||
options.onFailure?.(detail, options.relay)
|
||||
status = PublishStatus.Failure
|
||||
options.onFailure?.(detail)
|
||||
}
|
||||
|
||||
cleanup()
|
||||
@@ -71,8 +69,8 @@ export const publishOne = (options: PublishOneOptions) =>
|
||||
|
||||
options.signal?.addEventListener('abort', () => {
|
||||
if (status === PublishStatus.Pending) {
|
||||
setStatus(PublishStatus.Aborted)
|
||||
options.onAborted?.(options.relay)
|
||||
status = PublishStatus.Aborted
|
||||
options.onAborted?.()
|
||||
}
|
||||
|
||||
cleanup()
|
||||
@@ -80,26 +78,34 @@ export const publishOne = (options: PublishOneOptions) =>
|
||||
|
||||
setTimeout(() => {
|
||||
if (status === PublishStatus.Pending) {
|
||||
setStatus(PublishStatus.Timeout)
|
||||
options.onTimeout?.(options.relay)
|
||||
status = PublishStatus.Timeout
|
||||
options.onTimeout?.()
|
||||
}
|
||||
|
||||
cleanup()
|
||||
}, options.timeout || 10_000)
|
||||
|
||||
adapter.send([ClientMessageType.Event, options.event])
|
||||
|
||||
setStatus(PublishStatus.Pending)
|
||||
})
|
||||
|
||||
export type PublishStatusByRelay = Record<string, PublishStatus>
|
||||
|
||||
export type PublishOptions = Omit<PublishOneOptions, "relay"> & {
|
||||
export type PublishOptions = {
|
||||
event: SignedEvent
|
||||
relays: string[]
|
||||
onUpdate?: (status: PublishStatusByRelay) => void
|
||||
signal?: AbortSignal
|
||||
timeout?: number
|
||||
context?: AdapterContext
|
||||
onSuccess?: (detail: string, relay: string) => void
|
||||
onFailure?: (detail: string, relay: string) => void
|
||||
onPending?: (relay: string) => void
|
||||
onTimeout?: (relay: string) => void
|
||||
onAborted?: (relay: string) => void
|
||||
onComplete?: () => void
|
||||
}
|
||||
|
||||
export const publish = async (options: PublishOptions) => {
|
||||
const {event, timeout, signal, context} = options
|
||||
const status: PublishStatusByRelay = {}
|
||||
const completed = new Set<string>()
|
||||
const relays = new Set(options.relays)
|
||||
@@ -111,12 +117,30 @@ export const publish = async (options: PublishOptions) => {
|
||||
await Promise.all(
|
||||
options.relays.map(relay =>
|
||||
publishOne({
|
||||
event,
|
||||
relay,
|
||||
...options,
|
||||
onStatus: (_status: PublishStatus, relay: string) => {
|
||||
status[relay] = _status
|
||||
options.onStatus?.(_status, relay)
|
||||
options.onUpdate?.(status)
|
||||
signal,
|
||||
timeout,
|
||||
context,
|
||||
onSuccess: (detail: string) => {
|
||||
status[relay] = PublishStatus.Success
|
||||
options.onSuccess?.(detail, relay)
|
||||
},
|
||||
onFailure: (detail: string) => {
|
||||
status[relay] = PublishStatus.Failure
|
||||
options.onFailure?.(detail, relay)
|
||||
},
|
||||
onPending: () => {
|
||||
status[relay] = PublishStatus.Pending
|
||||
options.onPending?.(relay)
|
||||
},
|
||||
onTimeout: () => {
|
||||
status[relay] = PublishStatus.Timeout
|
||||
options.onTimeout?.(relay)
|
||||
},
|
||||
onAborted: () => {
|
||||
status[relay] = PublishStatus.Aborted
|
||||
options.onAborted?.(relay)
|
||||
},
|
||||
onComplete: () => {
|
||||
completed.add(relay)
|
||||
|
||||
Reference in New Issue
Block a user