Re-work publish/thunk status
This commit is contained in:
@@ -32,10 +32,10 @@ const publish = async (content: string) => {
|
|||||||
delay: 3000, // 3s window for abort
|
delay: 3000, // 3s window for abort
|
||||||
})
|
})
|
||||||
|
|
||||||
// Track publish status
|
// Track publish results
|
||||||
thunk.status.subscribe(statuses => {
|
thunk.subscribe($thunk => {
|
||||||
for (const [url, {status, message}] of Object.entries(statuses)) {
|
for (const [url, result] of Object.entries($thunk.results)) {
|
||||||
console.log(`${url}: ${status} ${message}`)
|
console.log(`${url}: ${result.status} - ${result.detail}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ const publish = async (content: string) => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
// Wait for completion
|
// Wait for completion
|
||||||
await thunk.result
|
await thunk.complete
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+24
-7
@@ -14,11 +14,24 @@ Status values for publish operations:
|
|||||||
- `Timeout` - Request timed out
|
- `Timeout` - Request timed out
|
||||||
- `Aborted` - Request was aborted
|
- `Aborted` - Request was aborted
|
||||||
|
|
||||||
|
## Types
|
||||||
|
|
||||||
|
### PublishResult
|
||||||
|
|
||||||
|
Result object for publish operations:
|
||||||
|
- `relay` - The relay URL
|
||||||
|
- `status` - PublishStatus enum value
|
||||||
|
- `detail` - Human-readable status message
|
||||||
|
|
||||||
|
### PublishResultsByRelay
|
||||||
|
|
||||||
|
Type alias for `Record<string, PublishResult>` - maps relay URLs to their publish results.
|
||||||
|
|
||||||
## Functions
|
## Functions
|
||||||
|
|
||||||
### publishOne(options)
|
### publishOne(options)
|
||||||
|
|
||||||
Publishes an event to a single relay and returns a promise that resolves with the publish status.
|
Publishes an event to a single relay and returns a promise that resolves with a `PublishResult`.
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
- `event` - The signed event to publish
|
- `event` - The signed event to publish
|
||||||
@@ -26,11 +39,11 @@ Publishes an event to a single relay and returns a promise that resolves with th
|
|||||||
- `signal?` - AbortSignal for cancellation
|
- `signal?` - AbortSignal for cancellation
|
||||||
- `timeout?` - Timeout in milliseconds (default: 10000)
|
- `timeout?` - Timeout in milliseconds (default: 10000)
|
||||||
- `context?` - Adapter context
|
- `context?` - Adapter context
|
||||||
- Callback functions: `onSuccess`, `onFailure`, `onPending`, `onTimeout`, `onAborted`, `onComplete`
|
- Callback functions (all receive `PublishResult`): `onSuccess`, `onFailure`, `onPending`, `onTimeout`, `onAborted`, `onComplete`
|
||||||
|
|
||||||
### publish(options)
|
### publish(options)
|
||||||
|
|
||||||
Publishes an event to multiple relays in parallel and returns a status object mapping relay URLs to their publish status.
|
Publishes an event to multiple relays in parallel and returns a `PublishResultsByRelay` object mapping relay URLs to their publish results.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -41,13 +54,17 @@ const event = {
|
|||||||
// ... signed event
|
// ... signed event
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusByRelay = await publish({
|
const resultsByRelay = await publish({
|
||||||
event,
|
event,
|
||||||
relays: ["wss://relay1.com", "wss://relay2.com"],
|
relays: ["wss://relay1.com", "wss://relay2.com"],
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
onSuccess: (detail, relay) => console.log(`Published to ${relay}`),
|
onSuccess: (result) => console.log(`Published to ${result.relay}: ${result.detail}`),
|
||||||
onFailure: (detail, relay) => console.log(`Failed on ${relay}: ${detail}`)
|
onFailure: (result) => console.log(`Failed on ${result.relay}: ${result.detail}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(statusByRelay) // { "wss://relay1.com": "success", "wss://relay2.com": "failure" }
|
console.log(resultsByRelay)
|
||||||
|
// {
|
||||||
|
// "wss://relay1.com": {relay: "wss://relay1.com", status: PublishStatus.Success, detail: "ok"},
|
||||||
|
// "wss://relay2.com": {relay: "wss://relay2.com", status: PublishStatus.Failure, detail: "invalid: ..."}
|
||||||
|
// }
|
||||||
```
|
```
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman",
|
"name": "@welshman",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ describe("thunk", () => {
|
|||||||
// Wait for initial async operations
|
// Wait for initial async operations
|
||||||
await vi.runAllTimersAsync()
|
await vi.runAllTimersAsync()
|
||||||
|
|
||||||
expect(thunk.status[LOCAL_RELAY_URL]).toEqual(PublishStatus.Success)
|
expect(thunk.results[LOCAL_RELAY_URL].status).toEqual(PublishStatus.Success)
|
||||||
|
|
||||||
// Verify tracker was called on success
|
// Verify tracker was called on success
|
||||||
expect(track).toHaveBeenCalledWith(thunk.event.id, LOCAL_RELAY_URL)
|
expect(track).toHaveBeenCalledWith(thunk.event.id, LOCAL_RELAY_URL)
|
||||||
|
|
||||||
await vi.runAllTimersAsync()
|
await vi.runAllTimersAsync()
|
||||||
|
await thunk.complete
|
||||||
|
|
||||||
const finalStatus = await thunk.result
|
expect(thunk.results[LOCAL_RELAY_URL].status).toEqual(PublishStatus.Success)
|
||||||
expect(finalStatus).toEqual({[LOCAL_RELAY_URL]: PublishStatus.Success})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/app",
|
"name": "@welshman/app",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of svelte stores for use in building nostr client applications.",
|
"description": "A collection of svelte stores for use in building nostr client applications.",
|
||||||
|
|||||||
@@ -85,17 +85,30 @@ export const relaysByPubkey = derived(relays, $relays =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const fetchRelayProfiles = async (urls: string[]) => {
|
export const fetchRelayProfiles = async (urls: string[]) => {
|
||||||
const base = appContext.dufflepudUrl
|
|
||||||
|
|
||||||
if (!base) {
|
|
||||||
throw new Error("ctx.app.dufflepudUrl is required to fetch relay metadata")
|
|
||||||
}
|
|
||||||
|
|
||||||
const res: any = await postJson(`${base}/relay/info`, {urls})
|
|
||||||
const profilesByUrl = new Map<string, RelayProfile>()
|
const profilesByUrl = new Map<string, RelayProfile>()
|
||||||
|
|
||||||
for (const {url, info} of res?.data || []) {
|
if (appContext.dufflepudUrl) {
|
||||||
profilesByUrl.set(url, info)
|
const res: any = await postJson(`${appContext.dufflepudUrl}/relay/info`, {urls})
|
||||||
|
|
||||||
|
for (const {url, info} of res?.data || []) {
|
||||||
|
profilesByUrl.set(url, info)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await Promise.all(
|
||||||
|
urls.map(async url => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(url.replace(/^ws/, "http"), {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/nostr+json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
profilesByUrl.set(url, await res.json())
|
||||||
|
} catch (e) {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return profilesByUrl
|
return profilesByUrl
|
||||||
|
|||||||
+67
-64
@@ -26,7 +26,13 @@ import {
|
|||||||
isUnwrappedEvent,
|
isUnwrappedEvent,
|
||||||
isSignedEvent,
|
isSignedEvent,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {publish, PublishStatus, PublishOptions, PublishStatusByRelay} from "@welshman/net"
|
import {
|
||||||
|
publish,
|
||||||
|
PublishStatus,
|
||||||
|
PublishResult,
|
||||||
|
PublishOptions,
|
||||||
|
PublishResultsByRelay,
|
||||||
|
} from "@welshman/net"
|
||||||
import {repository, tracker} from "./core.js"
|
import {repository, tracker} from "./core.js"
|
||||||
import {pubkey, getSession, getSigner} from "./session.js"
|
import {pubkey, getSession, getSigner} from "./session.js"
|
||||||
|
|
||||||
@@ -57,21 +63,28 @@ export class Thunk {
|
|||||||
_subs: Subscriber<Thunk>[] = []
|
_subs: Subscriber<Thunk>[] = []
|
||||||
|
|
||||||
event: TrustedEvent
|
event: TrustedEvent
|
||||||
result = defer<PublishStatusByRelay>()
|
results: PublishResultsByRelay = {}
|
||||||
status: PublishStatusByRelay = {}
|
complete = defer<void>()
|
||||||
details: Record<string, string> = {}
|
|
||||||
controller = new AbortController()
|
controller = new AbortController()
|
||||||
|
|
||||||
constructor(readonly options: ThunkOptions) {
|
constructor(readonly options: ThunkOptions) {
|
||||||
this.event = prepEvent(options.event)
|
this.event = prepEvent(options.event)
|
||||||
|
|
||||||
for (const relay of options.relays) {
|
for (const relay of options.relays) {
|
||||||
this.status[relay] = PublishStatus.Sending
|
this.results[relay] = {
|
||||||
|
relay,
|
||||||
|
status: PublishStatus.Sending,
|
||||||
|
detail: "sending...",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controller.signal.addEventListener("abort", () => {
|
this.controller.signal.addEventListener("abort", () => {
|
||||||
for (const relay of options.relays) {
|
for (const relay of options.relays) {
|
||||||
this._setAborted(relay)
|
this._setAborted({
|
||||||
|
relay,
|
||||||
|
status: PublishStatus.Aborted,
|
||||||
|
detail: "aborted",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -82,32 +95,33 @@ export class Thunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fail(message: string) {
|
_fail(detail: string) {
|
||||||
for (const relay of this.options.relays) {
|
for (const relay of this.options.relays) {
|
||||||
this.status[relay] = PublishStatus.Failure
|
this.results[relay] = {
|
||||||
this.details[relay] = message
|
relay,
|
||||||
|
status: PublishStatus.Failure,
|
||||||
|
detail: detail,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._notify()
|
this._notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
_setPending(relay: string) {
|
_setPending = (result: PublishResult) => {
|
||||||
this.options.onPending?.(relay)
|
this.options.onPending?.(result)
|
||||||
this.status[relay] = PublishStatus.Pending
|
this.results[result.relay] = result
|
||||||
this._notify()
|
this._notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
_setTimeout(relay: string) {
|
_setTimeout = (result: PublishResult) => {
|
||||||
this.options.onTimeout?.(relay)
|
this.options.onTimeout?.(result)
|
||||||
this.status[relay] = PublishStatus.Timeout
|
this.results[result.relay] = result
|
||||||
this.details[relay] = "Publish timed out"
|
|
||||||
this._notify()
|
this._notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
_setAborted(relay: string) {
|
_setAborted = (result: PublishResult) => {
|
||||||
this.options.onAborted?.(relay)
|
this.options.onAborted?.(result)
|
||||||
this.status[relay] = PublishStatus.Aborted
|
this.results[result.relay] = result
|
||||||
this.details[relay] = "Publish was aborted"
|
|
||||||
this._notify()
|
this._notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,38 +173,30 @@ export class Thunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send it off
|
// Send it off
|
||||||
this.result.resolve(
|
await publish({
|
||||||
await publish({
|
...this.options,
|
||||||
...this.options,
|
event: signedEvent,
|
||||||
event: signedEvent,
|
onSuccess: (result: PublishResult) => {
|
||||||
onSuccess: (message: string, relay: string) => {
|
tracker.track(signedEvent.id, result.relay)
|
||||||
tracker.track(signedEvent.id, relay)
|
this.options.onSuccess?.(result)
|
||||||
this.options.onSuccess?.(message, relay)
|
this.results[result.relay] = result
|
||||||
this.status[relay] = PublishStatus.Success
|
this._notify()
|
||||||
this.details[relay] = message
|
},
|
||||||
this._notify()
|
onFailure: (result: PublishResult) => {
|
||||||
},
|
this.options.onFailure?.(result)
|
||||||
onFailure: (message: string, relay: string) => {
|
this.results[result.relay] = result
|
||||||
this.options.onFailure?.(message, relay)
|
this._notify()
|
||||||
this.status[relay] = PublishStatus.Failure
|
},
|
||||||
this.details[relay] = message
|
onPending: this._setPending,
|
||||||
this._notify()
|
onTimeout: this._setTimeout,
|
||||||
},
|
onAborted: this._setAborted,
|
||||||
onPending: (relay: string) => {
|
onComplete: (result: PublishResult) => {
|
||||||
this._setPending(relay)
|
this.options.onComplete?.(result)
|
||||||
},
|
this._subs = []
|
||||||
onTimeout: (relay: string) => {
|
},
|
||||||
this._setTimeout(relay)
|
})
|
||||||
},
|
|
||||||
onAborted: (relay: string) => {
|
this.complete.resolve()
|
||||||
this._setAborted(relay)
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
this.options.onComplete?.()
|
|
||||||
this._subs = []
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe(subscriber: Subscriber<Thunk>) {
|
subscribe(subscriber: Subscriber<Thunk>) {
|
||||||
@@ -207,8 +213,7 @@ export class Thunk {
|
|||||||
export class MergedThunk {
|
export class MergedThunk {
|
||||||
_subs: Subscriber<MergedThunk>[] = []
|
_subs: Subscriber<MergedThunk>[] = []
|
||||||
|
|
||||||
status: PublishStatusByRelay = {}
|
results: PublishResultsByRelay = {}
|
||||||
details: Record<string, string> = {}
|
|
||||||
|
|
||||||
constructor(readonly thunks: Thunk[]) {
|
constructor(readonly thunks: Thunk[]) {
|
||||||
const {Aborted, Failure, Timeout, Pending, Sending, Success} = PublishStatus
|
const {Aborted, Failure, Timeout, Pending, Sending, Success} = PublishStatus
|
||||||
@@ -216,16 +221,14 @@ export class MergedThunk {
|
|||||||
|
|
||||||
for (const thunk of thunks) {
|
for (const thunk of thunks) {
|
||||||
thunk.subscribe($thunk => {
|
thunk.subscribe($thunk => {
|
||||||
this.status = {}
|
this.results = {}
|
||||||
this.details = {}
|
|
||||||
|
|
||||||
for (const relay of relays) {
|
for (const relay of relays) {
|
||||||
for (const status of [Aborted, Failure, Timeout, Pending, Sending, Success]) {
|
for (const status of [Aborted, Failure, Timeout, Pending, Sending, Success]) {
|
||||||
const thunk = thunks.find(t => t.status[relay] === status)
|
const thunk = thunks.find(t => t.results[relay]?.status === status)
|
||||||
|
|
||||||
if (thunk) {
|
if (thunk) {
|
||||||
this.status[relay] = thunk.status[relay]!
|
this.results[relay] = thunk.results[relay]!
|
||||||
this.details[relay] = thunk.details[relay]!
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,9 +274,9 @@ export const getThunkUrlsWithStatus = (
|
|||||||
) => {
|
) => {
|
||||||
statuses = ensurePlural(statuses)
|
statuses = ensurePlural(statuses)
|
||||||
|
|
||||||
return Object.entries(thunk.status)
|
return Object.entries(thunk.results)
|
||||||
.filter(([_, status]) => statuses.includes(status))
|
.filter(([_, {status}]) => statuses.includes(status))
|
||||||
.map(nth(0))
|
.map(nth(0)) as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCompleteThunkUrls = (thunk: AbstractThunk) =>
|
export const getCompleteThunkUrls = (thunk: AbstractThunk) =>
|
||||||
@@ -299,9 +302,9 @@ export const thunkIsComplete = (thunk: AbstractThunk) =>
|
|||||||
// Thunk errors
|
// Thunk errors
|
||||||
|
|
||||||
export const getThunkError = (thunk: Thunk) => {
|
export const getThunkError = (thunk: Thunk) => {
|
||||||
for (const [relay, status] of Object.entries(thunk.status)) {
|
for (const [_, {status, detail}] of Object.entries(thunk.results)) {
|
||||||
if (status === PublishStatus.Failure) {
|
if (status === PublishStatus.Failure) {
|
||||||
return thunk.details[relay]
|
return detail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/content",
|
"name": "@welshman/content",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of utilities for parsing nostr note content.",
|
"description": "A collection of utilities for parsing nostr note content.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/editor",
|
"name": "@welshman/editor",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A batteries-included nostr editor.",
|
"description": "A batteries-included nostr editor.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/feeds",
|
"name": "@welshman/feeds",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Utilities for building dynamic nostr feeds.",
|
"description": "Utilities for building dynamic nostr feeds.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/lib",
|
"name": "@welshman/lib",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of utilities.",
|
"description": "A collection of utilities.",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import {publishOne, publish} from "../src/publish"
|
import {publishOne, publish, PublishStatus} from "../src/publish"
|
||||||
import {MockAdapter} from "../src/adapter"
|
import {MockAdapter} from "../src/adapter"
|
||||||
import {ClientMessageType} from "../src/message"
|
import {ClientMessageType} from "../src/message"
|
||||||
import {makeEvent} from "@welshman/util"
|
import {makeEvent} from "@welshman/util"
|
||||||
@@ -40,7 +40,11 @@ describe("publishOne", () => {
|
|||||||
|
|
||||||
await vi.runAllTimers()
|
await vi.runAllTimers()
|
||||||
|
|
||||||
expect(successSpy).toHaveBeenCalledWith("hi")
|
expect(successSpy).toHaveBeenCalledWith({
|
||||||
|
relay: "1",
|
||||||
|
detail: "hi",
|
||||||
|
status: PublishStatus.Success,
|
||||||
|
})
|
||||||
expect(failureSpy).not.toHaveBeenCalled()
|
expect(failureSpy).not.toHaveBeenCalled()
|
||||||
expect(completeSpy).toHaveBeenCalled()
|
expect(completeSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
@@ -72,7 +76,11 @@ describe("publishOne", () => {
|
|||||||
await vi.runAllTimers()
|
await vi.runAllTimers()
|
||||||
|
|
||||||
expect(successSpy).not.toHaveBeenCalled()
|
expect(successSpy).not.toHaveBeenCalled()
|
||||||
expect(failureSpy).toHaveBeenCalledWith("hi")
|
expect(failureSpy).toHaveBeenCalledWith({
|
||||||
|
relay: "1",
|
||||||
|
detail: "hi",
|
||||||
|
status: PublishStatus.Failure,
|
||||||
|
})
|
||||||
expect(completeSpy).toHaveBeenCalled()
|
expect(completeSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -196,9 +204,21 @@ describe("publish", () => {
|
|||||||
|
|
||||||
await vi.runAllTimersAsync()
|
await vi.runAllTimersAsync()
|
||||||
|
|
||||||
expect(successSpy).toHaveBeenCalledWith("hi", "1")
|
expect(successSpy).toHaveBeenCalledWith({
|
||||||
expect(failureSpy).toHaveBeenCalledWith("hi", "2")
|
relay: "1",
|
||||||
|
status: PublishStatus.Success,
|
||||||
|
detail: "hi",
|
||||||
|
})
|
||||||
|
expect(failureSpy).toHaveBeenCalledWith({
|
||||||
|
relay: "2",
|
||||||
|
status: PublishStatus.Failure,
|
||||||
|
detail: "hi",
|
||||||
|
})
|
||||||
expect(completeSpy).toHaveBeenCalledTimes(1)
|
expect(completeSpy).toHaveBeenCalledTimes(1)
|
||||||
expect(timeoutSpy).toHaveBeenCalledWith("3")
|
expect(timeoutSpy).toHaveBeenCalledWith({
|
||||||
|
relay: "3",
|
||||||
|
status: PublishStatus.Timeout,
|
||||||
|
detail: "timed out",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/net",
|
"name": "@welshman/net",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Utilities for connecting with nostr relays.",
|
"description": "Utilities for connecting with nostr relays.",
|
||||||
|
|||||||
+66
-66
@@ -1,3 +1,4 @@
|
|||||||
|
import {fromPairs} from "@welshman/lib"
|
||||||
import {SignedEvent} from "@welshman/util"
|
import {SignedEvent} from "@welshman/util"
|
||||||
import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
|
import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
|
||||||
import {AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
|
import {AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
|
||||||
@@ -14,6 +15,7 @@ export enum PublishStatus {
|
|||||||
export type PublishResult = {
|
export type PublishResult = {
|
||||||
status: PublishStatus
|
status: PublishStatus
|
||||||
detail: string
|
detail: string
|
||||||
|
relay: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PublishOneOptions = {
|
export type PublishOneOptions = {
|
||||||
@@ -22,26 +24,30 @@ export type PublishOneOptions = {
|
|||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
timeout?: number
|
timeout?: number
|
||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
onSuccess?: (detail: string) => void
|
onSuccess?: (result: PublishResult) => void
|
||||||
onFailure?: (detail: string) => void
|
onFailure?: (result: PublishResult) => void
|
||||||
onPending?: () => void
|
onPending?: (result: PublishResult) => void
|
||||||
onTimeout?: () => void
|
onTimeout?: (result: PublishResult) => void
|
||||||
onAborted?: () => void
|
onAborted?: (result: PublishResult) => void
|
||||||
onComplete?: () => void
|
onComplete?: (result: PublishResult) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publishOne = (options: PublishOneOptions) =>
|
export const publishOne = (options: PublishOneOptions) =>
|
||||||
new Promise(resolve => {
|
new Promise<PublishResult>(resolve => {
|
||||||
const adapter = getAdapter(options.relay, options.context)
|
const adapter = getAdapter(options.relay, options.context)
|
||||||
|
|
||||||
let status = PublishStatus.Pending
|
const result = {
|
||||||
|
relay: options.relay,
|
||||||
|
status: PublishStatus.Pending,
|
||||||
|
detail: "",
|
||||||
|
}
|
||||||
|
|
||||||
options.onPending?.()
|
options.onPending?.(result)
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
options.onComplete?.()
|
options.onComplete?.(result)
|
||||||
adapter.cleanup()
|
adapter.cleanup()
|
||||||
resolve(status)
|
resolve(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter.on(AdapterEvent.Receive, (message: RelayMessage, url: string) => {
|
adapter.on(AdapterEvent.Receive, (message: RelayMessage, url: string) => {
|
||||||
@@ -51,11 +57,15 @@ export const publishOne = (options: PublishOneOptions) =>
|
|||||||
if (id !== options.event.id) return
|
if (id !== options.event.id) return
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
status = PublishStatus.Success
|
result.status = PublishStatus.Success
|
||||||
options.onSuccess?.(detail)
|
result.detail = detail
|
||||||
|
|
||||||
|
options.onSuccess?.(result)
|
||||||
} else {
|
} else {
|
||||||
status = PublishStatus.Failure
|
result.status = PublishStatus.Failure
|
||||||
options.onFailure?.(detail)
|
result.detail = detail
|
||||||
|
|
||||||
|
options.onFailure?.(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
@@ -63,18 +73,22 @@ export const publishOne = (options: PublishOneOptions) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
options.signal?.addEventListener("abort", () => {
|
options.signal?.addEventListener("abort", () => {
|
||||||
if (status === PublishStatus.Pending) {
|
if (result.status === PublishStatus.Pending) {
|
||||||
status = PublishStatus.Aborted
|
result.status = PublishStatus.Aborted
|
||||||
options.onAborted?.()
|
result.detail = "aborted"
|
||||||
|
|
||||||
|
options.onAborted?.(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (status === PublishStatus.Pending) {
|
if (result.status === PublishStatus.Pending) {
|
||||||
status = PublishStatus.Timeout
|
result.status = PublishStatus.Timeout
|
||||||
options.onTimeout?.()
|
result.detail = "timed out"
|
||||||
|
|
||||||
|
options.onTimeout?.(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup()
|
cleanup()
|
||||||
@@ -83,7 +97,7 @@ export const publishOne = (options: PublishOneOptions) =>
|
|||||||
adapter.send([ClientMessageType.Event, options.event])
|
adapter.send([ClientMessageType.Event, options.event])
|
||||||
})
|
})
|
||||||
|
|
||||||
export type PublishStatusByRelay = Record<string, PublishStatus>
|
export type PublishResultsByRelay = Record<string, PublishResult>
|
||||||
|
|
||||||
export type PublishOptions = {
|
export type PublishOptions = {
|
||||||
event: SignedEvent
|
event: SignedEvent
|
||||||
@@ -91,17 +105,16 @@ export type PublishOptions = {
|
|||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
timeout?: number
|
timeout?: number
|
||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
onSuccess?: (detail: string, relay: string) => void
|
onSuccess?: (result: PublishResult) => void
|
||||||
onFailure?: (detail: string, relay: string) => void
|
onFailure?: (result: PublishResult) => void
|
||||||
onPending?: (relay: string) => void
|
onPending?: (result: PublishResult) => void
|
||||||
onTimeout?: (relay: string) => void
|
onTimeout?: (result: PublishResult) => void
|
||||||
onAborted?: (relay: string) => void
|
onAborted?: (result: PublishResult) => void
|
||||||
onComplete?: () => void
|
onComplete?: (result: PublishResult) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publish = async (options: PublishOptions) => {
|
export const publish = async (options: PublishOptions): Promise<PublishResultsByRelay> => {
|
||||||
const {event, timeout, signal, context} = options
|
const {event, timeout, signal, context} = options
|
||||||
const status: PublishStatusByRelay = {}
|
|
||||||
const completed = new Set<string>()
|
const completed = new Set<string>()
|
||||||
const relays = new Set(options.relays)
|
const relays = new Set(options.relays)
|
||||||
|
|
||||||
@@ -109,44 +122,31 @@ export const publish = async (options: PublishOptions) => {
|
|||||||
console.warn("Non-unique relays passed to publish")
|
console.warn("Non-unique relays passed to publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
return fromPairs(
|
||||||
options.relays.map(relay =>
|
await Promise.all(
|
||||||
publishOne({
|
options.relays.map(async relay => {
|
||||||
event,
|
const result = await publishOne({
|
||||||
relay,
|
event,
|
||||||
signal,
|
relay,
|
||||||
timeout,
|
signal,
|
||||||
context,
|
timeout,
|
||||||
onSuccess: (detail: string) => {
|
context,
|
||||||
status[relay] = PublishStatus.Success
|
onSuccess: options.onSuccess,
|
||||||
options.onSuccess?.(detail, relay)
|
onFailure: options.onFailure,
|
||||||
},
|
onPending: options.onPending,
|
||||||
onFailure: (detail: string) => {
|
onTimeout: options.onTimeout,
|
||||||
status[relay] = PublishStatus.Failure
|
onAborted: options.onAborted,
|
||||||
options.onFailure?.(detail, relay)
|
onComplete: (result: PublishResult) => {
|
||||||
},
|
completed.add(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)
|
|
||||||
|
|
||||||
if (completed.size === relays.size) {
|
if (completed.size === relays.size) {
|
||||||
options.onComplete?.()
|
options.onComplete?.(result)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return [relay, result]
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return status
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/relay",
|
"name": "@welshman/relay",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "An in-memory nostr relay implementation.",
|
"description": "An in-memory nostr relay implementation.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/router",
|
"name": "@welshman/router",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of utilities for nostr relay selection.",
|
"description": "A collection of utilities for nostr relay selection.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/signer",
|
"name": "@welshman/signer",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A nostr signer implemenation supporting several login methods.",
|
"description": "A nostr signer implemenation supporting several login methods.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/store",
|
"name": "@welshman/store",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of utilities based on svelte/store for use with welshman",
|
"description": "A collection of utilities based on svelte/store for use with welshman",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@welshman/util",
|
"name": "@welshman/util",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"author": "hodlbod",
|
"author": "hodlbod",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A collection of nostr-related utilities.",
|
"description": "A collection of nostr-related utilities.",
|
||||||
|
|||||||
Generated
-17
@@ -20,9 +20,6 @@ importers:
|
|||||||
eslint-plugin-prettier:
|
eslint-plugin-prettier:
|
||||||
specifier: ~5.2.5
|
specifier: ~5.2.5
|
||||||
version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.23.0))(eslint@9.23.0)(prettier@3.5.3)
|
version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.23.0))(eslint@9.23.0)(prettier@3.5.3)
|
||||||
fake-indexeddb:
|
|
||||||
specifier: ^6.0.0
|
|
||||||
version: 6.0.0
|
|
||||||
globals:
|
globals:
|
||||||
specifier: ~16.0.0
|
specifier: ~16.0.0
|
||||||
version: 16.0.0
|
version: 16.0.0
|
||||||
@@ -95,9 +92,6 @@ importers:
|
|||||||
fuse.js:
|
fuse.js:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
idb:
|
|
||||||
specifier: ^8.0.0
|
|
||||||
version: 8.0.2
|
|
||||||
svelte:
|
svelte:
|
||||||
specifier: ^4.2.18
|
specifier: ^4.2.18
|
||||||
version: 4.2.19
|
version: 4.2.19
|
||||||
@@ -1698,10 +1692,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
|
resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
fake-indexeddb@6.0.0:
|
|
||||||
resolution: {integrity: sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
|
|
||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
@@ -1805,9 +1795,6 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
idb@8.0.2:
|
|
||||||
resolution: {integrity: sha512-CX70rYhx7GDDQzwwQMDwF6kDRQi5vVs6khHUumDrMecBylKkwvZ8HWvKV08AGb7VbpoGCWUQ4aHzNDgoUiOIUg==}
|
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -3901,8 +3888,6 @@ snapshots:
|
|||||||
|
|
||||||
expect-type@1.2.1: {}
|
expect-type@1.2.1: {}
|
||||||
|
|
||||||
fake-indexeddb@6.0.0: {}
|
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-diff@1.3.0: {}
|
fast-diff@1.3.0: {}
|
||||||
@@ -4011,8 +3996,6 @@ snapshots:
|
|||||||
|
|
||||||
husky@9.1.7: {}
|
husky@9.1.7: {}
|
||||||
|
|
||||||
idb@8.0.2: {}
|
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {defineConfig} from "vitest/config"
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
environment: "happy-dom",
|
environment: "happy-dom",
|
||||||
setupFiles: "./vitest.setup.ts",
|
|
||||||
include: ["packages/**/*.test.ts"],
|
include: ["packages/**/*.test.ts"],
|
||||||
coverage: {
|
coverage: {
|
||||||
provider: "v8",
|
provider: "v8",
|
||||||
|
|||||||
Reference in New Issue
Block a user