Lint
This commit is contained in:
+1
-2
@@ -3,6 +3,5 @@
|
|||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"arrowParens": "avoid",
|
"arrowParens": "avoid",
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false
|
||||||
"pluginSearchDirs": false
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -1,32 +1,32 @@
|
|||||||
import globals from "globals";
|
import globals from "globals"
|
||||||
import js from "@eslint/js";
|
import js from "@eslint/js"
|
||||||
import tsEslint from "typescript-eslint";
|
import tsEslint from "typescript-eslint"
|
||||||
import pluginReact from "eslint-plugin-react";
|
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"
|
||||||
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
|
||||||
|
|
||||||
export default tsEslint.config(
|
export default tsEslint.config(
|
||||||
{
|
{
|
||||||
ignores: [
|
ignores: ["node_modules", "!.*", "**/dist", "**/build", "docs"],
|
||||||
"node_modules",
|
|
||||||
"!.*",
|
|
||||||
"**/dist",
|
|
||||||
"**/build",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tsEslint.configs.recommended],
|
extends: [js.configs.recommended, ...tsEslint.configs.recommended],
|
||||||
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
|
files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
|
||||||
languageOptions: { globals: globals.node },
|
languageOptions: {globals: globals.node},
|
||||||
rules: {
|
rules: {
|
||||||
|
"no-useless-escape": "off",
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", {
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"vars": "all",
|
"error",
|
||||||
"args": "after-used",
|
{
|
||||||
"caughtErrors": "none",
|
vars: "all",
|
||||||
"argsIgnorePattern": "^_",
|
args: "none",
|
||||||
}]
|
caughtErrors: "none",
|
||||||
}
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
eslintPluginPrettierRecommended,
|
eslintPluginPrettierRecommended,
|
||||||
);
|
)
|
||||||
|
|||||||
+1
-1
@@ -8,6 +8,7 @@
|
|||||||
"clean": "pnpm run -r clean",
|
"clean": "pnpm run -r clean",
|
||||||
"build": "pnpm run -r build",
|
"build": "pnpm run -r build",
|
||||||
"link": "for p in $(ls packages); do cd packages/$p; pnpm link --global; cd ../..; done",
|
"link": "for p in $(ls packages); do cd packages/$p; pnpm link --global; cd ../..; done",
|
||||||
|
"format": "eslint --fix .",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"docs:dev": "vitepress dev docs",
|
"docs:dev": "vitepress dev docs",
|
||||||
@@ -19,7 +20,6 @@
|
|||||||
"eslint": "~9.23.0",
|
"eslint": "~9.23.0",
|
||||||
"eslint-config-prettier": "^10.1.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-prettier": "~5.2.5",
|
"eslint-plugin-prettier": "~5.2.5",
|
||||||
"eslint-plugin-react": "~7.37.4",
|
|
||||||
"fake-indexeddb": "^6.0.0",
|
"fake-indexeddb": "^6.0.0",
|
||||||
"globals": "~16.0.0",
|
"globals": "~16.0.0",
|
||||||
"happy-dom": "^17.4.4",
|
"happy-dom": "^17.4.4",
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import {now} from "@welshman/lib"
|
import {PublishStatus} from "@welshman/net"
|
||||||
import {PublishStatus, MockAdapter} from "@welshman/net"
|
import {NOTE, makeEvent} from "@welshman/util"
|
||||||
import {NOTE, makeEvent} from "@welshman/util"
|
|
||||||
import {Nip01Signer} from "@welshman/signer"
|
|
||||||
import {LOCAL_RELAY_URL} from "@welshman/relay"
|
import {LOCAL_RELAY_URL} from "@welshman/relay"
|
||||||
import {getPubkey, makeSecret} from "@welshman/signer"
|
import {getPubkey, makeSecret} from "@welshman/signer"
|
||||||
import {EventEmitter} from "events"
|
|
||||||
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
|
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
|
||||||
import {repository, tracker} from "../src/core"
|
import {repository, tracker} from "../src/core"
|
||||||
import {addSession, dropSession} from "../src/session"
|
import {addSession, dropSession} from "../src/session"
|
||||||
@@ -31,7 +28,7 @@ const mockRequest = {
|
|||||||
describe("thunk", () => {
|
describe("thunk", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers()
|
vi.useFakeTimers()
|
||||||
addSession({method: 'nip01', secret, pubkey})
|
addSession({method: "nip01", secret, pubkey})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
@@ -70,7 +67,7 @@ describe("thunk", () => {
|
|||||||
|
|
||||||
describe("publishThunk", () => {
|
describe("publishThunk", () => {
|
||||||
it("should create and publish a thunk", async () => {
|
it("should create and publish a thunk", async () => {
|
||||||
const publishSpy = vi.spyOn(repository, 'publish')
|
const publishSpy = vi.spyOn(repository, "publish")
|
||||||
const result = publishThunk(mockRequest)
|
const result = publishThunk(mockRequest)
|
||||||
|
|
||||||
expect(publishSpy).toHaveBeenCalled()
|
expect(publishSpy).toHaveBeenCalled()
|
||||||
@@ -79,7 +76,7 @@ describe("thunk", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle abort", () => {
|
it("should handle abort", () => {
|
||||||
const removeEventSpy = vi.spyOn(repository, 'removeEvent')
|
const removeEventSpy = vi.spyOn(repository, "removeEvent")
|
||||||
const thunk = publishThunk(mockRequest)
|
const thunk = publishThunk(mockRequest)
|
||||||
|
|
||||||
thunk.controller.abort()
|
thunk.controller.abort()
|
||||||
@@ -109,8 +106,7 @@ describe("thunk", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should update status during publishing", async () => {
|
it("should update status during publishing", async () => {
|
||||||
const send = vi.fn()
|
const track = vi.spyOn(tracker, "track")
|
||||||
const track = vi.spyOn(tracker, 'track')
|
|
||||||
const thunk = makeThunk(mockRequest)
|
const thunk = makeThunk(mockRequest)
|
||||||
let status: Record<string, any> = {}
|
let status: Record<string, any> = {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,25 @@
|
|||||||
import {get, derived} from 'svelte/store'
|
import {derived} from "svelte/store"
|
||||||
import {batch, fromPairs} from '@welshman/lib'
|
import {batch, fromPairs} from "@welshman/lib"
|
||||||
import {PROFILE, FOLLOWS, MUTES, RELAYS, INBOX_RELAYS, getPubkeyTagValues, getListTags} from '@welshman/util'
|
import {
|
||||||
import {throttled, withGetter} from '@welshman/store'
|
PROFILE,
|
||||||
import {RepositoryUpdate} from '@welshman/relay'
|
FOLLOWS,
|
||||||
import {getAll, bulkPut, bulkDelete} from './storage.js'
|
MUTES,
|
||||||
import {relays} from './relays.js'
|
RELAYS,
|
||||||
import {handles, onHandle} from './handles.js'
|
INBOX_RELAYS,
|
||||||
import {zappers, onZapper} from './zappers.js'
|
getPubkeyTagValues,
|
||||||
import {plaintext} from './plaintext.js'
|
getListTags,
|
||||||
import {freshness} from './freshness.js'
|
} from "@welshman/util"
|
||||||
import {repository} from './core.js'
|
import {throttled, withGetter} from "@welshman/store"
|
||||||
import {sessions} from './session.js'
|
import {RepositoryUpdate} from "@welshman/relay"
|
||||||
import {userFollows} from './user.js'
|
import {getAll, bulkPut, bulkDelete} from "./storage.js"
|
||||||
|
import {relays} from "./relays.js"
|
||||||
|
import {handles, onHandle} from "./handles.js"
|
||||||
|
import {zappers, onZapper} from "./zappers.js"
|
||||||
|
import {plaintext} from "./plaintext.js"
|
||||||
|
import {freshness} from "./freshness.js"
|
||||||
|
import {repository} from "./core.js"
|
||||||
|
import {sessions} from "./session.js"
|
||||||
|
import {userFollows} from "./user.js"
|
||||||
|
|
||||||
export const defaultStorageAdapters = {
|
export const defaultStorageAdapters = {
|
||||||
relays: {
|
relays: {
|
||||||
@@ -70,7 +78,7 @@ export const defaultStorageAdapters = {
|
|||||||
init: async () => repository.load(await getAll("events")),
|
init: async () => repository.load(await getAll("events")),
|
||||||
sync: () => {
|
sync: () => {
|
||||||
const userFollowPubkeys = withGetter(
|
const userFollowPubkeys = withGetter(
|
||||||
derived(userFollows, l => new Set(getPubkeyTagValues(getListTags(l))))
|
derived(userFollows, l => new Set(getPubkeyTagValues(getListTags(l)))),
|
||||||
)
|
)
|
||||||
|
|
||||||
const onUpdate = async ({added, removed}: RepositoryUpdate) => {
|
const onUpdate = async ({added, removed}: RepositoryUpdate) => {
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ export const requestDVM = async ({kind, onEvent, ...request}: DVMOpts) => {
|
|||||||
const tags = request.tags || []
|
const tags = request.tags || []
|
||||||
const $signer = signer.get() || new Nip01Signer(makeSecret())
|
const $signer = signer.get() || new Nip01Signer(makeSecret())
|
||||||
const pubkey = await $signer.getPubkey()
|
const pubkey = await $signer.getPubkey()
|
||||||
const relays = request.relays || Router.get().FromPubkeys(getPubkeyTagValues(tags)).policy(addMinimalFallbacks).getUrls()
|
const relays =
|
||||||
|
request.relays ||
|
||||||
|
Router.get().FromPubkeys(getPubkeyTagValues(tags)).policy(addMinimalFallbacks).getUrls()
|
||||||
|
|
||||||
if (!tags.some(nthEq(0, "expiration"))) {
|
if (!tags.some(nthEq(0, "expiration"))) {
|
||||||
tags.push(["expiration", String(now() + 60)])
|
tags.push(["expiration", String(now() + 60)])
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import {FOLLOWS, asDecryptedEvent, readList} from "@welshman/util"
|
import {FOLLOWS, asDecryptedEvent, readList} from "@welshman/util"
|
||||||
import {TrustedEvent, PublishedList} from "@welshman/util"
|
import {TrustedEvent, PublishedList} from "@welshman/util"
|
||||||
import {MultiRequestOptions, load} from "@welshman/net"
|
|
||||||
import {deriveEventsMapped} from "@welshman/store"
|
import {deriveEventsMapped} from "@welshman/store"
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {Router} from "./router.js"
|
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {writable, derived} from "svelte/store"
|
import {writable, derived} from "svelte/store"
|
||||||
import {MultiRequestOptions} from "@welshman/net"
|
|
||||||
import {tryCatch, fetchJson, uniq, batcher, postJson, last} from "@welshman/lib"
|
import {tryCatch, fetchJson, uniq, batcher, postJson, last} from "@welshman/lib"
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
import {deriveProfile} from "./profiles.js"
|
import {deriveProfile} from "./profiles.js"
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import {MUTES, asDecryptedEvent, readList} from "@welshman/util"
|
import {MUTES, asDecryptedEvent, readList} from "@welshman/util"
|
||||||
import {TrustedEvent, PublishedList} from "@welshman/util"
|
import {TrustedEvent, PublishedList} from "@welshman/util"
|
||||||
import {load, MultiRequestOptions} from "@welshman/net"
|
|
||||||
import {deriveEventsMapped} from "@welshman/store"
|
import {deriveEventsMapped} from "@welshman/store"
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {Router} from "./router.js"
|
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
import {ensurePlaintext} from "./plaintext.js"
|
import {ensurePlaintext} from "./plaintext.js"
|
||||||
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import {PINS, asDecryptedEvent, readList} from "@welshman/util"
|
import {PINS, asDecryptedEvent, readList} from "@welshman/util"
|
||||||
import {TrustedEvent, PublishedList} from "@welshman/util"
|
import {TrustedEvent, PublishedList} from "@welshman/util"
|
||||||
import {load, MultiRequestOptions} from "@welshman/net"
|
|
||||||
import {deriveEventsMapped} from "@welshman/store"
|
import {deriveEventsMapped} from "@welshman/store"
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {Router} from "./router.js"
|
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import {derived, readable} from "svelte/store"
|
import {derived, readable} from "svelte/store"
|
||||||
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
|
import {readProfile, displayProfile, displayPubkey, PROFILE} from "@welshman/util"
|
||||||
import {load, MultiRequestOptions} from "@welshman/net"
|
|
||||||
import {PublishedProfile} from "@welshman/util"
|
import {PublishedProfile} from "@welshman/util"
|
||||||
import {deriveEventsMapped, withGetter} from "@welshman/store"
|
import {deriveEventsMapped, withGetter} from "@welshman/store"
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {Router} from "./router.js"
|
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
import {loadWithAsapMetaRelayUrls} from "./relaySelections.js"
|
||||||
|
|
||||||
@@ -25,7 +23,7 @@ export const {
|
|||||||
store: profiles,
|
store: profiles,
|
||||||
getKey: profile => profile.event.pubkey,
|
getKey: profile => profile.event.pubkey,
|
||||||
load: (pubkey: string, relays: string[]) =>
|
load: (pubkey: string, relays: string[]) =>
|
||||||
loadWithAsapMetaRelayUrls(pubkey, relays, [{kinds: [PROFILE], authors: [pubkey]}])
|
loadWithAsapMetaRelayUrls(pubkey, relays, [{kinds: [PROFILE], authors: [pubkey]}]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const displayProfileByPubkey = (pubkey: string | undefined) =>
|
export const displayProfileByPubkey = (pubkey: string | undefined) =>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import {
|
|||||||
getRelayTagValues,
|
getRelayTagValues,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {TrustedEvent, Filter, PublishedList, List} from "@welshman/util"
|
import {TrustedEvent, Filter, PublishedList, List} from "@welshman/util"
|
||||||
import {load, MultiRequestOptions} from "@welshman/net"
|
import {load} from "@welshman/net"
|
||||||
import {deriveEventsMapped} from "@welshman/store"
|
import {deriveEventsMapped} from "@welshman/store"
|
||||||
import {repository} from "./core.js"
|
import {repository} from "./core.js"
|
||||||
import {Router, addNoFallbacks} from "./router.js"
|
import {Router} from "./router.js"
|
||||||
import {collection} from "./collection.js"
|
import {collection} from "./collection.js"
|
||||||
|
|
||||||
export const getRelayUrls = (list?: List): string[] =>
|
export const getRelayUrls = (list?: List): string[] =>
|
||||||
@@ -51,13 +51,15 @@ export const {
|
|||||||
const router = Router.get()
|
const router = Router.get()
|
||||||
|
|
||||||
await load({
|
await load({
|
||||||
relays: router.merge([router.Index(), router.FromRelays(relays), router.FromPubkey(pubkey)]).getUrls(),
|
relays: router
|
||||||
|
.merge([router.Index(), router.FromRelays(relays), router.FromPubkey(pubkey)])
|
||||||
|
.getUrls(),
|
||||||
filters: [{kinds: [RELAYS], authors: [pubkey]}],
|
filters: [{kinds: [RELAYS], authors: [pubkey]}],
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const loadWithAsapMetaRelayUrls = <T>(pubkey: string, relays: string[], filters: Filter[]) => {
|
export const loadWithAsapMetaRelayUrls = (pubkey: string, relays: string[], filters: Filter[]) => {
|
||||||
const router = Router.get()
|
const router = Router.get()
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@@ -69,8 +71,10 @@ export const loadWithAsapMetaRelayUrls = <T>(pubkey: string, relays: string[], f
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
load({filters, relays: router.merge([router.Index(), router.FromRelays(relays)]).getUrls()})
|
load({
|
||||||
.then(onLoad)
|
filters,
|
||||||
|
relays: router.merge([router.Index(), router.FromRelays(relays)]).getUrls(),
|
||||||
|
}).then(onLoad)
|
||||||
|
|
||||||
loadRelaySelections(pubkey, relays)
|
loadRelaySelections(pubkey, relays)
|
||||||
.then(() => load({filters, relays: router.FromPubkey(pubkey).getUrls()}))
|
.then(() => load({filters, relays: router.FromPubkey(pubkey).getUrls()}))
|
||||||
@@ -93,5 +97,5 @@ export const {
|
|||||||
store: inboxRelaySelections,
|
store: inboxRelaySelections,
|
||||||
getKey: inboxRelaySelections => inboxRelaySelections.event.pubkey,
|
getKey: inboxRelaySelections => inboxRelaySelections.event.pubkey,
|
||||||
load: (pubkey: string, relays: string[]) =>
|
load: (pubkey: string, relays: string[]) =>
|
||||||
loadWithAsapMetaRelayUrls(pubkey, relays, [{kinds: [INBOX_RELAYS], authors: [pubkey]}])
|
loadWithAsapMetaRelayUrls(pubkey, relays, [{kinds: [INBOX_RELAYS], authors: [pubkey]}]),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
MINUTE,
|
MINUTE,
|
||||||
HOUR,
|
HOUR,
|
||||||
DAY,
|
DAY,
|
||||||
WEEK,
|
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
getFilterId,
|
getFilterId,
|
||||||
@@ -242,7 +241,8 @@ export class Router {
|
|||||||
|
|
||||||
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
|
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
|
||||||
|
|
||||||
PubkeyInbox = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Inbox))
|
PubkeyInbox = (pubkey: string) =>
|
||||||
|
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Inbox))
|
||||||
|
|
||||||
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
|
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
|
||||||
|
|
||||||
@@ -493,7 +493,9 @@ export const getFilterSelections = (
|
|||||||
const result = []
|
const result = []
|
||||||
|
|
||||||
for (const [id, filter] of filtersById.entries()) {
|
for (const [id, filter] of filtersById.entries()) {
|
||||||
const scenario = Router.get().merge(scenariosById.get(id) || []).policy(addMinimalFallbacks)
|
const scenario = Router.get()
|
||||||
|
.merge(scenariosById.get(id) || [])
|
||||||
|
.policy(addMinimalFallbacks)
|
||||||
|
|
||||||
result.push({filters: [filter], relays: scenario.getUrls()})
|
result.push({filters: [filter], relays: scenario.getUrls()})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {openDB, deleteDB} from "idb"
|
import {openDB, deleteDB} from "idb"
|
||||||
import {IDBPDatabase} from "idb"
|
import {IDBPDatabase} from "idb"
|
||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import {Unsubscriber, Writable} from "svelte/store"
|
import {Unsubscriber} from "svelte/store"
|
||||||
import {indexBy, call, equals, throttle, fromPairs} from "@welshman/lib"
|
import {call} from "@welshman/lib"
|
||||||
import {TrustedEvent} from "@welshman/util"
|
import {withGetter} from "@welshman/store"
|
||||||
import {Repository} from "@welshman/relay"
|
|
||||||
import {Tracker} from "@welshman/net"
|
|
||||||
import {withGetter, adapter, throttled, custom} from "@welshman/store"
|
|
||||||
|
|
||||||
export type StorageAdapterOptions = {
|
export type StorageAdapterOptions = {
|
||||||
throttle?: number
|
throttle?: number
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import {Writable, Readable, writable, derived, get} from "svelte/store"
|
import {Writable, Readable, writable, derived, get} from "svelte/store"
|
||||||
import {Deferred, fromPairs, TaskQueue, dissoc, identity, uniq, defer, sleep, assoc} from "@welshman/lib"
|
import {
|
||||||
|
Deferred,
|
||||||
|
fromPairs,
|
||||||
|
TaskQueue,
|
||||||
|
dissoc,
|
||||||
|
identity,
|
||||||
|
uniq,
|
||||||
|
defer,
|
||||||
|
sleep,
|
||||||
|
assoc,
|
||||||
|
} from "@welshman/lib"
|
||||||
import {stamp, own, hash} from "@welshman/signer"
|
import {stamp, own, hash} from "@welshman/signer"
|
||||||
import {
|
import {
|
||||||
TrustedEvent,
|
TrustedEvent,
|
||||||
@@ -225,9 +235,11 @@ export const thunkQueue = new TaskQueue<Thunk>({
|
|||||||
// Update status to pending
|
// Update status to pending
|
||||||
thunk.status.set(
|
thunk.status.set(
|
||||||
fromPairs(
|
fromPairs(
|
||||||
thunk.request.relays
|
thunk.request.relays.map(url => [
|
||||||
.map(url => [url, {status: PublishStatus.Pending, message: "Sending your message..."}])
|
url,
|
||||||
)
|
{status: PublishStatus.Pending, message: "Sending your message..."},
|
||||||
|
]),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send it off
|
// Send it off
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {writable, derived} from "svelte/store"
|
import {writable, derived} from "svelte/store"
|
||||||
import {Zapper} from "@welshman/util"
|
import {Zapper} from "@welshman/util"
|
||||||
import {MultiRequestOptions} from "@welshman/net"
|
|
||||||
import {
|
import {
|
||||||
identity,
|
identity,
|
||||||
fetchJson,
|
fetchJson,
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ describe("Content Parsing", () => {
|
|||||||
describe("Basic Parsing", () => {
|
describe("Basic Parsing", () => {
|
||||||
it("should parse plain text", () => {
|
it("should parse plain text", () => {
|
||||||
const result = parse({content: "Hello world"})
|
const result = parse({content: "Hello world"})
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([{type: ParsedType.Text, value: "Hello world", raw: "Hello world"}])
|
||||||
{type: ParsedType.Text, value: "Hello world", raw: "Hello world"},
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should parse newlines", () => {
|
it("should parse newlines", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export * from "./nodeviews/index.js"
|
export * from "./nodeviews/index.js"
|
||||||
export * from "./extensions/index.js"
|
export * from "./extensions/index.js"
|
||||||
export * from "./plugins/index.js"
|
export * from "./plugins/index.js"
|
||||||
export {Editor, NodeViewProps} from '@tiptap/core'
|
export {Editor, NodeViewProps} from "@tiptap/core"
|
||||||
export {UploadTask} from 'nostr-editor'
|
export {UploadTask} from "nostr-editor"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {bech32, utf8} from "@scure/base"
|
import {bech32, utf8} from "@scure/base"
|
||||||
|
|
||||||
type Obj<T = any> = Record<string, T>;
|
type Obj<T = any> = Record<string, T>
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Basic functional programming utilities
|
// Basic functional programming utilities
|
||||||
@@ -134,7 +134,8 @@ export const gt = (x: number | undefined, y: number | undefined) => num(x) > num
|
|||||||
export const gte = (x: number | undefined, y: number | undefined) => num(x) >= num(y)
|
export const gte = (x: number | undefined, y: number | undefined) => num(x) >= num(y)
|
||||||
|
|
||||||
/** Returns maximum value in array, handling undefined values */
|
/** Returns maximum value in array, handling undefined values */
|
||||||
export const max = (xs: (number | undefined)[]) => xs.reduce((a: number, b) => Math.max(num(a), num(b)), 0)
|
export const max = (xs: (number | undefined)[]) =>
|
||||||
|
xs.reduce((a: number, b) => Math.max(num(a), num(b)), 0)
|
||||||
|
|
||||||
/** Returns minimum value in array, handling undefined values */
|
/** Returns minimum value in array, handling undefined values */
|
||||||
export const min = (xs: (number | undefined)[]) => {
|
export const min = (xs: (number | undefined)[]) => {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
// Copied from https://github.com/sindresorhus/normalize-url
|
// Copied from https://github.com/sindresorhus/normalize-url
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
/**
|
/**
|
||||||
@default 'http'
|
@default 'http'
|
||||||
*/
|
*/
|
||||||
readonly defaultProtocol?: 'https' | 'http';
|
readonly defaultProtocol?: "https" | "http"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Prepends `defaultProtocol` to the URL if it's protocol-relative.
|
Prepends `defaultProtocol` to the URL if it's protocol-relative.
|
||||||
|
|
||||||
@default true
|
@default true
|
||||||
@@ -20,9 +21,9 @@ export type Options = {
|
|||||||
//=> '//sindresorhus.com'
|
//=> '//sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly normalizeProtocol?: boolean;
|
readonly normalizeProtocol?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Normalizes HTTPS URLs to HTTP.
|
Normalizes HTTPS URLs to HTTP.
|
||||||
|
|
||||||
@default false
|
@default false
|
||||||
@@ -36,9 +37,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com'
|
//=> 'http://sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly forceHttp?: boolean;
|
readonly forceHttp?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Normalizes HTTP URLs to HTTPS.
|
Normalizes HTTP URLs to HTTPS.
|
||||||
|
|
||||||
This option cannot be used with the `forceHttp` option at the same time.
|
This option cannot be used with the `forceHttp` option at the same time.
|
||||||
@@ -54,9 +55,9 @@ export type Options = {
|
|||||||
//=> 'https://sindresorhus.com'
|
//=> 'https://sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly forceHttps?: boolean;
|
readonly forceHttps?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Strip the [authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) part of a URL.
|
Strip the [authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) part of a URL.
|
||||||
|
|
||||||
@default true
|
@default true
|
||||||
@@ -70,9 +71,9 @@ export type Options = {
|
|||||||
//=> 'https://user:password@sindresorhus.com'
|
//=> 'https://user:password@sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly stripAuthentication?: boolean;
|
readonly stripAuthentication?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes hash from the URL.
|
Removes hash from the URL.
|
||||||
|
|
||||||
@default false
|
@default false
|
||||||
@@ -86,9 +87,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com/about.html'
|
//=> 'http://sindresorhus.com/about.html'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly stripHash?: boolean;
|
readonly stripHash?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Remove the protocol from the URL: `http://sindresorhus.com` → `sindresorhus.com`.
|
Remove the protocol from the URL: `http://sindresorhus.com` → `sindresorhus.com`.
|
||||||
|
|
||||||
It will only remove `https://` and `http://` protocols.
|
It will only remove `https://` and `http://` protocols.
|
||||||
@@ -104,9 +105,9 @@ export type Options = {
|
|||||||
//=> 'sindresorhus.com'
|
//=> 'sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly stripProtocol?: boolean;
|
readonly stripProtocol?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Strip the [text fragment](https://web.dev/text-fragments/) part of the URL
|
Strip the [text fragment](https://web.dev/text-fragments/) part of the URL
|
||||||
|
|
||||||
__Note:__ The text fragment will always be removed if the `stripHash` option is set to `true`, as the hash contains the text fragment.
|
__Note:__ The text fragment will always be removed if the `stripHash` option is set to `true`, as the hash contains the text fragment.
|
||||||
@@ -128,9 +129,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com/about.html#section:~:text=hello'
|
//=> 'http://sindresorhus.com/about.html#section:~:text=hello'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly stripTextFragment?: boolean;
|
readonly stripTextFragment?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes `www.` from the URL.
|
Removes `www.` from the URL.
|
||||||
|
|
||||||
@default true
|
@default true
|
||||||
@@ -144,9 +145,9 @@ export type Options = {
|
|||||||
//=> 'http://www.sindresorhus.com'
|
//=> 'http://www.sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly stripWWW?: boolean;
|
readonly stripWWW?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes query parameters that matches any of the provided strings or regexes.
|
Removes query parameters that matches any of the provided strings or regexes.
|
||||||
|
|
||||||
@default [/^utm_\w+/i]
|
@default [/^utm_\w+/i]
|
||||||
@@ -177,9 +178,9 @@ export type Options = {
|
|||||||
//=> 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test'
|
//=> 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly removeQueryParameters?: ReadonlyArray<RegExp | string> | boolean;
|
readonly removeQueryParameters?: ReadonlyArray<RegExp | string> | boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Keeps only query parameters that matches any of the provided strings or regexes.
|
Keeps only query parameters that matches any of the provided strings or regexes.
|
||||||
|
|
||||||
__Note__: It overrides the `removeQueryParameters` option.
|
__Note__: It overrides the `removeQueryParameters` option.
|
||||||
@@ -194,9 +195,9 @@ export type Options = {
|
|||||||
//=> 'https://sindresorhus.com/?ref=unicorn'
|
//=> 'https://sindresorhus.com/?ref=unicorn'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly keepQueryParameters?: ReadonlyArray<RegExp | string>;
|
readonly keepQueryParameters?: ReadonlyArray<RegExp | string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes trailing slash.
|
Removes trailing slash.
|
||||||
|
|
||||||
__Note__: Trailing slash is always removed if the URL doesn't have a pathname unless the `removeSingleSlash` option is set to `false`.
|
__Note__: Trailing slash is always removed if the URL doesn't have a pathname unless the `removeSingleSlash` option is set to `false`.
|
||||||
@@ -215,9 +216,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com'
|
//=> 'http://sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly removeTrailingSlash?: boolean;
|
readonly removeTrailingSlash?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Remove a sole `/` pathname in the output. This option is independent of `removeTrailingSlash`.
|
Remove a sole `/` pathname in the output. This option is independent of `removeTrailingSlash`.
|
||||||
|
|
||||||
@default true
|
@default true
|
||||||
@@ -231,9 +232,9 @@ export type Options = {
|
|||||||
//=> 'https://sindresorhus.com/'
|
//=> 'https://sindresorhus.com/'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly removeSingleSlash?: boolean;
|
readonly removeSingleSlash?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes the default directory index file from path that matches any of the provided strings or regexes.
|
Removes the default directory index file from path that matches any of the provided strings or regexes.
|
||||||
When `true`, the regex `/^index\.[a-z]+$/` is used.
|
When `true`, the regex `/^index\.[a-z]+$/` is used.
|
||||||
|
|
||||||
@@ -247,9 +248,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com/foo'
|
//=> 'http://sindresorhus.com/foo'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly removeDirectoryIndex?: boolean | ReadonlyArray<RegExp | string>;
|
readonly removeDirectoryIndex?: boolean | ReadonlyArray<RegExp | string>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes an explicit port number from the URL.
|
Removes an explicit port number from the URL.
|
||||||
|
|
||||||
Port 443 is always removed from HTTPS URLs and 80 is always removed from HTTP URLs regardless of this option.
|
Port 443 is always removed from HTTPS URLs and 80 is always removed from HTTP URLs regardless of this option.
|
||||||
@@ -264,9 +265,9 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com'
|
//=> 'http://sindresorhus.com'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly removeExplicitPort?: boolean;
|
readonly removeExplicitPort?: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sorts the query parameters alphabetically by key.
|
Sorts the query parameters alphabetically by key.
|
||||||
|
|
||||||
@default true
|
@default true
|
||||||
@@ -279,79 +280,74 @@ export type Options = {
|
|||||||
//=> 'http://sindresorhus.com/?b=two&a=one&c=three'
|
//=> 'http://sindresorhus.com/?b=two&a=one&c=three'
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
readonly sortQueryParameters?: boolean;
|
readonly sortQueryParameters?: boolean
|
||||||
};
|
}
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
||||||
const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain'
|
const DATA_URL_DEFAULT_MIME_TYPE = "text/plain"
|
||||||
const DATA_URL_DEFAULT_CHARSET = 'us-ascii'
|
const DATA_URL_DEFAULT_CHARSET = "us-ascii"
|
||||||
|
|
||||||
const testParameter = (name: string, filters: any[]) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name)
|
const testParameter = (name: string, filters: any[]) =>
|
||||||
|
filters.some(filter => (filter instanceof RegExp ? filter.test(name) : filter === name))
|
||||||
|
|
||||||
const supportedProtocols = new Set([
|
const supportedProtocols = new Set(["https:", "http:", "file:"])
|
||||||
'https:',
|
|
||||||
'http:',
|
|
||||||
'file:',
|
|
||||||
])
|
|
||||||
|
|
||||||
const hasCustomProtocol = (urlString: string) => {
|
const hasCustomProtocol = (urlString: string) => {
|
||||||
try {
|
try {
|
||||||
const {protocol} = new URL(urlString)
|
const {protocol} = new URL(urlString)
|
||||||
return protocol.endsWith(':') && !supportedProtocols.has(protocol)
|
return protocol.endsWith(":") && !supportedProtocols.has(protocol)
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeDataURL = (urlString: string, {stripHash}: {stripHash: boolean}) => {
|
const normalizeDataURL = (urlString: string, {stripHash}: {stripHash: boolean}) => {
|
||||||
const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString)
|
const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString)
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
throw new Error(`Invalid URL: ${urlString}`)
|
throw new Error(`Invalid URL: ${urlString}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let {type, data, hash} = match.groups as any
|
let {type, data, hash} = match.groups as any
|
||||||
const mediaType = type.split(';')
|
const mediaType = type.split(";")
|
||||||
hash = stripHash ? '' : hash
|
hash = stripHash ? "" : hash
|
||||||
|
|
||||||
let isBase64 = false
|
let isBase64 = false
|
||||||
if (mediaType[mediaType.length - 1] === 'base64') {
|
if (mediaType[mediaType.length - 1] === "base64") {
|
||||||
mediaType.pop()
|
mediaType.pop()
|
||||||
isBase64 = true
|
isBase64 = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lowercase MIME type
|
// Lowercase MIME type
|
||||||
const mimeType = mediaType.shift()?.toLowerCase() ?? ''
|
const mimeType = mediaType.shift()?.toLowerCase() ?? ""
|
||||||
const attributes = mediaType
|
const attributes = mediaType
|
||||||
.map((attribute: string) => {
|
.map((attribute: string) => {
|
||||||
let [key, value = ''] = attribute.split('=').map((s: string) => s.trim())
|
let [key, value = ""] = attribute.split("=").map((s: string) => s.trim())
|
||||||
|
|
||||||
// Lowercase `charset`
|
// Lowercase `charset`
|
||||||
if (key === 'charset') {
|
if (key === "charset") {
|
||||||
value = value.toLowerCase()
|
value = value.toLowerCase()
|
||||||
|
|
||||||
if (value === DATA_URL_DEFAULT_CHARSET) {
|
if (value === DATA_URL_DEFAULT_CHARSET) {
|
||||||
return ''
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${key}${value ? `=${value}` : ''}`
|
return `${key}${value ? `=${value}` : ""}`
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
const normalizedMediaType = [
|
const normalizedMediaType = [...attributes]
|
||||||
...attributes,
|
|
||||||
]
|
|
||||||
|
|
||||||
if (isBase64) {
|
if (isBase64) {
|
||||||
normalizedMediaType.push('base64')
|
normalizedMediaType.push("base64")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {
|
if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) {
|
||||||
normalizedMediaType.unshift(mimeType)
|
normalizedMediaType.unshift(mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`
|
return `data:${normalizedMediaType.join(";")},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ""}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -374,212 +370,224 @@ normalizeUrl('//www.sindresorhus.com:80/../baz?b=bar&a=foo');
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export default function normalizeUrl(urlString: string, opts?: Options): string {
|
export default function normalizeUrl(urlString: string, opts?: Options): string {
|
||||||
const options = {
|
const options = {
|
||||||
defaultProtocol: 'http',
|
defaultProtocol: "http",
|
||||||
normalizeProtocol: true,
|
normalizeProtocol: true,
|
||||||
forceHttp: false,
|
forceHttp: false,
|
||||||
forceHttps: false,
|
forceHttps: false,
|
||||||
stripAuthentication: true,
|
stripAuthentication: true,
|
||||||
stripHash: false,
|
stripHash: false,
|
||||||
stripTextFragment: true,
|
stripTextFragment: true,
|
||||||
stripWWW: true,
|
stripWWW: true,
|
||||||
removeQueryParameters: [/^utm_\w+/i],
|
removeQueryParameters: [/^utm_\w+/i],
|
||||||
removeTrailingSlash: true,
|
removeTrailingSlash: true,
|
||||||
removeSingleSlash: true,
|
removeSingleSlash: true,
|
||||||
removeDirectoryIndex: false,
|
removeDirectoryIndex: false,
|
||||||
removeExplicitPort: false,
|
removeExplicitPort: false,
|
||||||
sortQueryParameters: true,
|
sortQueryParameters: true,
|
||||||
...opts,
|
...opts,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy: Append `:` to the protocol if missing.
|
// Legacy: Append `:` to the protocol if missing.
|
||||||
if (typeof options.defaultProtocol === 'string' && !options.defaultProtocol.endsWith(':')) {
|
if (typeof options.defaultProtocol === "string" && !options.defaultProtocol.endsWith(":")) {
|
||||||
options.defaultProtocol = `${options.defaultProtocol}:`
|
options.defaultProtocol = `${options.defaultProtocol}:`
|
||||||
}
|
}
|
||||||
|
|
||||||
urlString = urlString.trim()
|
urlString = urlString.trim()
|
||||||
|
|
||||||
// Data URL
|
// Data URL
|
||||||
if (/^data:/i.test(urlString)) {
|
if (/^data:/i.test(urlString)) {
|
||||||
return normalizeDataURL(urlString, options)
|
return normalizeDataURL(urlString, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCustomProtocol(urlString)) {
|
if (hasCustomProtocol(urlString)) {
|
||||||
return urlString
|
return urlString
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasRelativeProtocol = urlString.startsWith('//')
|
const hasRelativeProtocol = urlString.startsWith("//")
|
||||||
const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString)
|
const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString)
|
||||||
|
|
||||||
// Prepend protocol
|
// Prepend protocol
|
||||||
if (!isRelativeUrl) {
|
if (!isRelativeUrl) {
|
||||||
urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol)
|
urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol)
|
||||||
}
|
}
|
||||||
|
|
||||||
const urlObject = new URL(urlString)
|
const urlObject = new URL(urlString)
|
||||||
|
|
||||||
if (options.forceHttp && options.forceHttps) {
|
if (options.forceHttp && options.forceHttps) {
|
||||||
throw new Error('The `forceHttp` and `forceHttps` options cannot be used together')
|
throw new Error("The `forceHttp` and `forceHttps` options cannot be used together")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.forceHttp && urlObject.protocol === 'https:') {
|
if (options.forceHttp && urlObject.protocol === "https:") {
|
||||||
urlObject.protocol = 'http:'
|
urlObject.protocol = "http:"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.forceHttps && urlObject.protocol === 'http:') {
|
if (options.forceHttps && urlObject.protocol === "http:") {
|
||||||
urlObject.protocol = 'https:'
|
urlObject.protocol = "https:"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove auth
|
// Remove auth
|
||||||
if (options.stripAuthentication) {
|
if (options.stripAuthentication) {
|
||||||
urlObject.username = ''
|
urlObject.username = ""
|
||||||
urlObject.password = ''
|
urlObject.password = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove hash
|
// Remove hash
|
||||||
if (options.stripHash) {
|
if (options.stripHash) {
|
||||||
urlObject.hash = ''
|
urlObject.hash = ""
|
||||||
} else if (options.stripTextFragment) {
|
} else if (options.stripTextFragment) {
|
||||||
urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, '')
|
urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove duplicate slashes if not preceded by a protocol
|
// Remove duplicate slashes if not preceded by a protocol
|
||||||
// NOTE: This could be implemented using a single negative lookbehind
|
// NOTE: This could be implemented using a single negative lookbehind
|
||||||
// regex, but we avoid that to maintain compatibility with older js engines
|
// regex, but we avoid that to maintain compatibility with older js engines
|
||||||
// which do not have support for that feature.
|
// which do not have support for that feature.
|
||||||
if (urlObject.pathname) {
|
if (urlObject.pathname) {
|
||||||
// TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\b[a-z][a-z\d+\-.]{1,50}:)\/{2,}/g, '/');` when Safari supports negative lookbehind.
|
// TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(?<!\b[a-z][a-z\d+\-.]{1,50}:)\/{2,}/g, '/');` when Safari supports negative lookbehind.
|
||||||
|
|
||||||
// Split the string by occurrences of this protocol regex, and perform
|
// Split the string by occurrences of this protocol regex, and perform
|
||||||
// duplicate-slash replacement on the strings between those occurrences
|
// duplicate-slash replacement on the strings between those occurrences
|
||||||
// (if any).
|
// (if any).
|
||||||
const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g
|
const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g
|
||||||
|
|
||||||
let lastIndex = 0
|
let lastIndex = 0
|
||||||
let result = ''
|
let result = ""
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const match = protocolRegex.exec(urlObject.pathname)
|
const match = protocolRegex.exec(urlObject.pathname)
|
||||||
if (!match) {
|
if (!match) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const protocol = match[0]
|
const protocol = match[0]
|
||||||
const protocolAtIndex = match.index
|
const protocolAtIndex = match.index
|
||||||
const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex)
|
const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex)
|
||||||
|
|
||||||
result += intermediate.replace(/\/{2,}/g, '/')
|
result += intermediate.replace(/\/{2,}/g, "/")
|
||||||
result += protocol
|
result += protocol
|
||||||
lastIndex = protocolAtIndex + protocol.length
|
lastIndex = protocolAtIndex + protocol.length
|
||||||
}
|
}
|
||||||
|
|
||||||
const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length)
|
const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length)
|
||||||
result += remnant.replace(/\/{2,}/g, '/')
|
result += remnant.replace(/\/{2,}/g, "/")
|
||||||
|
|
||||||
urlObject.pathname = result
|
urlObject.pathname = result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode URI octets
|
// Decode URI octets
|
||||||
if (urlObject.pathname) {
|
if (urlObject.pathname) {
|
||||||
try {
|
try {
|
||||||
urlObject.pathname = decodeURI(urlObject.pathname)
|
urlObject.pathname = decodeURI(urlObject.pathname)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove directory index
|
// Remove directory index
|
||||||
if (options.removeDirectoryIndex === true) {
|
if (options.removeDirectoryIndex === true) {
|
||||||
options.removeDirectoryIndex = [/^index\.[a-z]+$/]
|
options.removeDirectoryIndex = [/^index\.[a-z]+$/]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
|
if (Array.isArray(options.removeDirectoryIndex) && options.removeDirectoryIndex.length > 0) {
|
||||||
let pathComponents = urlObject.pathname.split('/')
|
let pathComponents = urlObject.pathname.split("/")
|
||||||
const lastComponent = pathComponents[pathComponents.length - 1]
|
const lastComponent = pathComponents[pathComponents.length - 1]
|
||||||
|
|
||||||
if (testParameter(lastComponent, options.removeDirectoryIndex)) {
|
if (testParameter(lastComponent, options.removeDirectoryIndex)) {
|
||||||
pathComponents = pathComponents.slice(0, -1)
|
pathComponents = pathComponents.slice(0, -1)
|
||||||
urlObject.pathname = pathComponents.slice(1).join('/') + '/'
|
urlObject.pathname = pathComponents.slice(1).join("/") + "/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urlObject.hostname) {
|
if (urlObject.hostname) {
|
||||||
// Remove trailing dot
|
// Remove trailing dot
|
||||||
urlObject.hostname = urlObject.hostname.replace(/\.$/, '')
|
urlObject.hostname = urlObject.hostname.replace(/\.$/, "")
|
||||||
|
|
||||||
// Remove `www.`
|
// Remove `www.`
|
||||||
if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) {
|
if (
|
||||||
// Each label should be max 63 at length (min: 1).
|
options.stripWWW &&
|
||||||
// Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
|
/^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)
|
||||||
// Each TLD should be up to 63 characters long (min: 2).
|
) {
|
||||||
// It is technically possible to have a single character TLD, but none currently exist.
|
// Each label should be max 63 at length (min: 1).
|
||||||
urlObject.hostname = urlObject.hostname.replace(/^www\./, '')
|
// Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
|
||||||
}
|
// Each TLD should be up to 63 characters long (min: 2).
|
||||||
}
|
// It is technically possible to have a single character TLD, but none currently exist.
|
||||||
|
urlObject.hostname = urlObject.hostname.replace(/^www\./, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove query unwanted parameters
|
// Remove query unwanted parameters
|
||||||
if (Array.isArray(options.removeQueryParameters)) {
|
if (Array.isArray(options.removeQueryParameters)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (const key of [...urlObject.searchParams.keys()]) {
|
for (const key of [...urlObject.searchParams.keys()]) {
|
||||||
if (testParameter(key, options.removeQueryParameters)) {
|
if (testParameter(key, options.removeQueryParameters)) {
|
||||||
urlObject.searchParams.delete(key)
|
urlObject.searchParams.delete(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
|
if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) {
|
||||||
urlObject.search = ''
|
urlObject.search = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep wanted query parameters
|
// Keep wanted query parameters
|
||||||
if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
|
if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (const key of [...urlObject.searchParams.keys()]) {
|
for (const key of [...urlObject.searchParams.keys()]) {
|
||||||
if (!testParameter(key, options.keepQueryParameters)) {
|
if (!testParameter(key, options.keepQueryParameters)) {
|
||||||
urlObject.searchParams.delete(key)
|
urlObject.searchParams.delete(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort query parameters
|
// Sort query parameters
|
||||||
if (options.sortQueryParameters) {
|
if (options.sortQueryParameters) {
|
||||||
urlObject.searchParams.sort()
|
urlObject.searchParams.sort()
|
||||||
|
|
||||||
// Calling `.sort()` encodes the search parameters, so we need to decode them again.
|
// Calling `.sort()` encodes the search parameters, so we need to decode them again.
|
||||||
try {
|
try {
|
||||||
urlObject.search = decodeURIComponent(urlObject.search)
|
urlObject.search = decodeURIComponent(urlObject.search)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.removeTrailingSlash) {
|
if (options.removeTrailingSlash) {
|
||||||
urlObject.pathname = urlObject.pathname.replace(/\/$/, '')
|
urlObject.pathname = urlObject.pathname.replace(/\/$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove an explicit port number, excluding a default port number, if applicable
|
// Remove an explicit port number, excluding a default port number, if applicable
|
||||||
if (options.removeExplicitPort && urlObject.port) {
|
if (options.removeExplicitPort && urlObject.port) {
|
||||||
urlObject.port = ''
|
urlObject.port = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldUrlString = urlString
|
const oldUrlString = urlString
|
||||||
|
|
||||||
// Take advantage of many of the Node `url` normalizations
|
// Take advantage of many of the Node `url` normalizations
|
||||||
urlString = urlObject.toString()
|
urlString = urlObject.toString()
|
||||||
|
|
||||||
if (!options.removeSingleSlash && urlObject.pathname === '/' && !oldUrlString.endsWith('/') && urlObject.hash === '') {
|
if (
|
||||||
urlString = urlString.replace(/\/$/, '')
|
!options.removeSingleSlash &&
|
||||||
}
|
urlObject.pathname === "/" &&
|
||||||
|
!oldUrlString.endsWith("/") &&
|
||||||
|
urlObject.hash === ""
|
||||||
|
) {
|
||||||
|
urlString = urlString.replace(/\/$/, "")
|
||||||
|
}
|
||||||
|
|
||||||
// Remove ending `/` unless removeSingleSlash is false
|
// Remove ending `/` unless removeSingleSlash is false
|
||||||
if ((options.removeTrailingSlash || urlObject.pathname === '/') && urlObject.hash === '' && options.removeSingleSlash) {
|
if (
|
||||||
urlString = urlString.replace(/\/$/, '')
|
(options.removeTrailingSlash || urlObject.pathname === "/") &&
|
||||||
}
|
urlObject.hash === "" &&
|
||||||
|
options.removeSingleSlash
|
||||||
|
) {
|
||||||
|
urlString = urlString.replace(/\/$/, "")
|
||||||
|
}
|
||||||
|
|
||||||
// Restore relative protocol, if applicable
|
// Restore relative protocol, if applicable
|
||||||
if (hasRelativeProtocol && !options.normalizeProtocol) {
|
if (hasRelativeProtocol && !options.normalizeProtocol) {
|
||||||
urlString = urlString.replace(/^http:\/\//, '//')
|
urlString = urlString.replace(/^http:\/\//, "//")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove http/https
|
// Remove http/https
|
||||||
if (options.stripProtocol) {
|
if (options.stripProtocol) {
|
||||||
urlString = urlString.replace(/^(?:https?:)?\/\//, '')
|
urlString = urlString.replace(/^(?:https?:)?\/\//, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return urlString
|
return urlString
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import EventEmitter from "events"
|
import EventEmitter from "events"
|
||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { isRelayUrl } from "@welshman/util"
|
import {LocalRelay, Repository, LOCAL_RELAY_URL} from "@welshman/relay"
|
||||||
import { LocalRelay, Repository, LOCAL_RELAY_URL } from "@welshman/relay"
|
import {AdapterEvent, SocketAdapter, LocalAdapter, getAdapter} from "../src/adapter"
|
||||||
import { AdapterEvent, SocketAdapter, LocalAdapter, getAdapter } from "../src/adapter"
|
import {ClientMessage, RelayMessage} from "../src/message"
|
||||||
import { ClientMessage, RelayMessage } from "../src/message"
|
import {Socket, SocketEvent} from "../src/socket"
|
||||||
import { Socket, SocketEvent } from "../src/socket"
|
import {Pool} from "../src/pool"
|
||||||
import { Pool } from "../src/pool"
|
|
||||||
|
|
||||||
vi.mock('isomorphic-ws', () => {
|
vi.mock("isomorphic-ws", () => {
|
||||||
const WebSocket = vi.fn(function (this: any) {
|
const WebSocket = vi.fn(function (this: any) {
|
||||||
setTimeout(() => this.onopen())
|
setTimeout(() => this.onopen())
|
||||||
})
|
})
|
||||||
@@ -18,7 +17,7 @@ vi.mock('isomorphic-ws', () => {
|
|||||||
this.onclose()
|
this.onclose()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { default: WebSocket }
|
return {default: WebSocket}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("SocketAdapter", () => {
|
describe("SocketAdapter", () => {
|
||||||
@@ -27,7 +26,7 @@ describe("SocketAdapter", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.useFakeTimers()
|
vi.useFakeTimers()
|
||||||
socket = new Socket('wss://test.relay')
|
socket = new Socket("wss://test.relay")
|
||||||
adapter = new SocketAdapter(socket)
|
adapter = new SocketAdapter(socket)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -48,15 +47,15 @@ describe("SocketAdapter", () => {
|
|||||||
const receiveSpy = vi.fn()
|
const receiveSpy = vi.fn()
|
||||||
adapter.on(AdapterEvent.Receive, receiveSpy)
|
adapter.on(AdapterEvent.Receive, receiveSpy)
|
||||||
|
|
||||||
const message: RelayMessage = ["EVENT", "123", { id: "123", kind: 1 }]
|
const message: RelayMessage = ["EVENT", "123", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Receive, message, "wss://test.relay")
|
socket.emit(SocketEvent.Receive, message, "wss://test.relay")
|
||||||
|
|
||||||
expect(receiveSpy).toHaveBeenCalledWith(message, "wss://test.relay")
|
expect(receiveSpy).toHaveBeenCalledWith(message, "wss://test.relay")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should send messages to socket", () => {
|
it("should send messages to socket", () => {
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
const message: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const message: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
adapter.send(message)
|
adapter.send(message)
|
||||||
|
|
||||||
expect(sendSpy).toHaveBeenCalledWith(message)
|
expect(sendSpy).toHaveBeenCalledWith(message)
|
||||||
@@ -77,7 +76,7 @@ describe("LocalAdapter", () => {
|
|||||||
const mockRelay = new EventEmitter()
|
const mockRelay = new EventEmitter()
|
||||||
Object.assign(mockRelay, {
|
Object.assign(mockRelay, {
|
||||||
send: vi.fn(),
|
send: vi.fn(),
|
||||||
removeAllListeners: vi.fn()
|
removeAllListeners: vi.fn(),
|
||||||
})
|
})
|
||||||
relay = mockRelay as unknown as LocalRelay & EventEmitter
|
relay = mockRelay as unknown as LocalRelay & EventEmitter
|
||||||
adapter = new LocalAdapter(relay)
|
adapter = new LocalAdapter(relay)
|
||||||
@@ -98,14 +97,14 @@ describe("LocalAdapter", () => {
|
|||||||
const receiveSpy = vi.fn()
|
const receiveSpy = vi.fn()
|
||||||
adapter.on(AdapterEvent.Receive, receiveSpy)
|
adapter.on(AdapterEvent.Receive, receiveSpy)
|
||||||
|
|
||||||
const message: RelayMessage = ["EVENT", "123", { id: "123", kind: 1 }]
|
const message: RelayMessage = ["EVENT", "123", {id: "123", kind: 1}]
|
||||||
relay.emit("*", ...message)
|
relay.emit("*", ...message)
|
||||||
|
|
||||||
expect(receiveSpy).toHaveBeenCalledWith(message, LOCAL_RELAY_URL)
|
expect(receiveSpy).toHaveBeenCalledWith(message, LOCAL_RELAY_URL)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should send messages to relay", () => {
|
it("should send messages to relay", () => {
|
||||||
const message: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const message: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
adapter.send(message)
|
adapter.send(message)
|
||||||
|
|
||||||
expect(relay.send).toHaveBeenCalledWith("EVENT", message[1])
|
expect(relay.send).toHaveBeenCalledWith("EVENT", message[1])
|
||||||
@@ -136,13 +135,13 @@ describe("getAdapter", () => {
|
|||||||
|
|
||||||
it("should return LocalAdapter for local relay URL", () => {
|
it("should return LocalAdapter for local relay URL", () => {
|
||||||
const url = LOCAL_RELAY_URL
|
const url = LOCAL_RELAY_URL
|
||||||
const adapter = getAdapter(url, { repository })
|
const adapter = getAdapter(url, {repository})
|
||||||
expect(adapter).toBeInstanceOf(LocalAdapter)
|
expect(adapter).toBeInstanceOf(LocalAdapter)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return SocketAdapter for remote relay URL", () => {
|
it("should return SocketAdapter for remote relay URL", () => {
|
||||||
const url = "wss://test.relay"
|
const url = "wss://test.relay"
|
||||||
const adapter = getAdapter(url, { pool })
|
const adapter = getAdapter(url, {pool})
|
||||||
expect(adapter).toBeInstanceOf(SocketAdapter)
|
expect(adapter).toBeInstanceOf(SocketAdapter)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -151,9 +150,12 @@ describe("getAdapter", () => {
|
|||||||
const getCustomAdapter = vi.fn().mockReturnValue(customAdapter)
|
const getCustomAdapter = vi.fn().mockReturnValue(customAdapter)
|
||||||
const url = "wss://test.relay"
|
const url = "wss://test.relay"
|
||||||
|
|
||||||
const adapter = getAdapter(url, { getAdapter: getCustomAdapter })
|
const adapter = getAdapter(url, {getAdapter: getCustomAdapter})
|
||||||
|
|
||||||
expect(getCustomAdapter).toHaveBeenCalledWith(url, expect.objectContaining({ getAdapter: getCustomAdapter }))
|
expect(getCustomAdapter).toHaveBeenCalledWith(
|
||||||
|
url,
|
||||||
|
expect.objectContaining({getAdapter: getCustomAdapter}),
|
||||||
|
)
|
||||||
expect(adapter).toBe(customAdapter)
|
expect(adapter).toBe(customAdapter)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { Socket, SocketStatus, SocketEvent } from "../src/socket"
|
import {Socket, SocketStatus, SocketEvent} from "../src/socket"
|
||||||
import { makeEvent, StampedEvent, CLIENT_AUTH } from "@welshman/util"
|
import {StampedEvent, CLIENT_AUTH} from "@welshman/util"
|
||||||
import { Nip01Signer } from "@welshman/signer"
|
import {Nip01Signer} from "@welshman/signer"
|
||||||
import { AuthState, AuthStatus, AuthStateEvent, makeAuthEvent } from "../src/auth"
|
import {AuthStatus, AuthStateEvent} from "../src/auth"
|
||||||
import EventEmitter from "events"
|
import {RelayMessage} from "../src/message"
|
||||||
import { RelayMessage } from "../src/message"
|
|
||||||
|
|
||||||
vi.mock('isomorphic-ws', () => {
|
vi.mock("isomorphic-ws", () => {
|
||||||
const WebSocket = vi.fn(function (this: any) {
|
const WebSocket = vi.fn(function (this: any) {
|
||||||
setTimeout(() => this.onopen())
|
setTimeout(() => this.onopen())
|
||||||
})
|
})
|
||||||
@@ -17,14 +16,14 @@ vi.mock('isomorphic-ws', () => {
|
|||||||
this.onclose()
|
this.onclose()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { default: WebSocket }
|
return {default: WebSocket}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('auth', () => {
|
describe("auth", () => {
|
||||||
let socket: Socket
|
let socket: Socket
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
socket = new Socket('wss://test.relay')
|
socket = new Socket("wss://test.relay")
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -72,7 +71,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle client AUTH message", () => {
|
it("should handle client AUTH message", () => {
|
||||||
const message: RelayMessage = ["AUTH", { id: "123", kind: CLIENT_AUTH }]
|
const message: RelayMessage = ["AUTH", {id: "123", kind: CLIENT_AUTH}]
|
||||||
socket.emit(SocketEvent.Sending, message)
|
socket.emit(SocketEvent.Sending, message)
|
||||||
|
|
||||||
expect(socket.auth.status).toBe(AuthStatus.PendingResponse)
|
expect(socket.auth.status).toBe(AuthStatus.PendingResponse)
|
||||||
@@ -113,7 +112,7 @@ describe('auth', () => {
|
|||||||
const sign = vi.fn()
|
const sign = vi.fn()
|
||||||
|
|
||||||
await expect(socket.auth.authenticate(sign)).rejects.toThrow(
|
await expect(socket.auth.authenticate(sign)).rejects.toThrow(
|
||||||
"Attempted to authenticate with no challenge"
|
"Attempted to authenticate with no challenge",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,7 +123,7 @@ describe('auth', () => {
|
|||||||
socket.auth.status = AuthStatus.PendingResponse
|
socket.auth.status = AuthStatus.PendingResponse
|
||||||
|
|
||||||
await expect(socket.auth.authenticate(sign)).rejects.toThrow(
|
await expect(socket.auth.authenticate(sign)).rejects.toThrow(
|
||||||
"Attempted to authenticate when auth is already auth:status:pending_response"
|
"Attempted to authenticate when auth is already auth:status:pending_response",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,7 +139,7 @@ describe('auth', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should send AUTH message", async () => {
|
it("should send AUTH message", async () => {
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
let event
|
let event
|
||||||
|
|
||||||
socket.auth.challenge = "challenge123"
|
socket.auth.challenge = "challenge123"
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { AUTH_JOIN } from "@welshman/util"
|
import {AUTH_JOIN} from "@welshman/util"
|
||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { Socket, SocketStatus, SocketEvent } from "../src/socket"
|
import {Socket, SocketStatus, SocketEvent} from "../src/socket"
|
||||||
import { AuthStatus, AuthStateEvent } from "../src/auth"
|
import {AuthStatus, AuthStateEvent} from "../src/auth"
|
||||||
import {
|
import {
|
||||||
socketPolicyAuthBuffer,
|
socketPolicyAuthBuffer,
|
||||||
socketPolicyConnectOnSend,
|
socketPolicyConnectOnSend,
|
||||||
socketPolicyCloseOnTimeout,
|
socketPolicyCloseOnTimeout,
|
||||||
socketPolicyReopenActive
|
socketPolicyReopenActive,
|
||||||
} from "../src/policy"
|
} from "../src/policy"
|
||||||
import { ClientMessage, RelayMessage } from "../src/message"
|
import {ClientMessage, RelayMessage} from "../src/message"
|
||||||
|
|
||||||
// Hoist mock definition to top level
|
// Hoist mock definition to top level
|
||||||
const mockWs = vi.hoisted(() => ({
|
const mockWs = vi.hoisted(() => ({
|
||||||
@@ -21,11 +21,11 @@ const mockWs = vi.hoisted(() => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
// Mock the WebSocket module
|
// Mock the WebSocket module
|
||||||
vi.mock('isomorphic-ws', () => ({
|
vi.mock("isomorphic-ws", () => ({
|
||||||
default: mockWs
|
default: mockWs,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('policy', () => {
|
describe("policy", () => {
|
||||||
let socket: Socket
|
let socket: Socket
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -42,22 +42,22 @@ describe('policy', () => {
|
|||||||
describe("socketPolicyAuthBuffer", () => {
|
describe("socketPolicyAuthBuffer", () => {
|
||||||
it("should buffer messages when not authenticated", () => {
|
it("should buffer messages when not authenticated", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
||||||
|
|
||||||
// Regular event should be buffered
|
// Regular event should be buffered
|
||||||
const event: ClientMessage = ["EVENT", { id: "123"}]
|
const event: ClientMessage = ["EVENT", {id: "123"}]
|
||||||
socket.send(event)
|
socket.send(event)
|
||||||
expect(sendSpy).toHaveBeenCalledWith(event)
|
expect(sendSpy).toHaveBeenCalledWith(event)
|
||||||
|
|
||||||
// Auth event should not be buffered
|
// Auth event should not be buffered
|
||||||
const authEvent: ClientMessage = ["AUTH", { id: "456" }]
|
const authEvent: ClientMessage = ["AUTH", {id: "456"}]
|
||||||
socket.send(authEvent)
|
socket.send(authEvent)
|
||||||
expect(sendSpy).toHaveBeenCalledWith(authEvent)
|
expect(sendSpy).toHaveBeenCalledWith(authEvent)
|
||||||
|
|
||||||
// Auth join event should not be buffered
|
// Auth join event should not be buffered
|
||||||
const joinEvent: ClientMessage = ["EVENT", { id: "789", kind: AUTH_JOIN }]
|
const joinEvent: ClientMessage = ["EVENT", {id: "789", kind: AUTH_JOIN}]
|
||||||
socket.send(joinEvent)
|
socket.send(joinEvent)
|
||||||
expect(sendSpy).toHaveBeenCalledWith(joinEvent)
|
expect(sendSpy).toHaveBeenCalledWith(joinEvent)
|
||||||
|
|
||||||
@@ -66,18 +66,18 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should send buffered messages when auth succeeds", () => {
|
it("should send buffered messages when auth succeeds", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
||||||
|
|
||||||
// Buffer some messages
|
// Buffer some messages
|
||||||
const event1: ClientMessage = ["EVENT", { id: "123"}]
|
const event1: ClientMessage = ["EVENT", {id: "123"}]
|
||||||
const event2: ClientMessage = ["EVENT", { id: "456"}]
|
const event2: ClientMessage = ["EVENT", {id: "456"}]
|
||||||
socket.send(event1)
|
socket.send(event1)
|
||||||
socket.send(event2)
|
socket.send(event2)
|
||||||
|
|
||||||
// Auth succeeds
|
// Auth succeeds
|
||||||
socket.send(["AUTH", { id: "auth" }])
|
socket.send(["AUTH", {id: "auth"}])
|
||||||
socket.emit(AuthStateEvent.Status, AuthStatus.Ok)
|
socket.emit(AuthStateEvent.Status, AuthStatus.Ok)
|
||||||
|
|
||||||
expect(sendSpy).toHaveBeenCalledWith(event1)
|
expect(sendSpy).toHaveBeenCalledWith(event1)
|
||||||
@@ -88,12 +88,12 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should handle CLOSE messages properly", () => {
|
it("should handle CLOSE messages properly", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
socket.emit(SocketEvent.Receive, ["AUTH", "challenge"])
|
||||||
|
|
||||||
// Buffer a REQ message
|
// Buffer a REQ message
|
||||||
const req: ClientMessage = ["REQ", "123", { kinds: [1] }]
|
const req: ClientMessage = ["REQ", "123", {kinds: [1]}]
|
||||||
socket.send(req)
|
socket.send(req)
|
||||||
|
|
||||||
// Send CLOSE for buffered REQ
|
// Send CLOSE for buffered REQ
|
||||||
@@ -109,10 +109,13 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should retry events once when auth-required", () => {
|
it("should retry events once when auth-required", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const recvQueueRemoveSpy = vi.spyOn(socket._recvQueue, 'remove')
|
const recvQueueRemoveSpy = vi.spyOn(socket._recvQueue, "remove")
|
||||||
|
|
||||||
// Send an event
|
// Send an event
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1, content: "", tags: [], pubkey: "", sig: "" }]
|
const event: ClientMessage = [
|
||||||
|
"EVENT",
|
||||||
|
{id: "123", kind: 1, content: "", tags: [], pubkey: "", sig: ""},
|
||||||
|
]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Receive auth-required rejection
|
// Receive auth-required rejection
|
||||||
@@ -134,10 +137,10 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should retry REQ once when auth-required", () => {
|
it("should retry REQ once when auth-required", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const recvQueueRemoveSpy = vi.spyOn(socket._recvQueue, 'remove')
|
const recvQueueRemoveSpy = vi.spyOn(socket._recvQueue, "remove")
|
||||||
|
|
||||||
// Send a REQ
|
// Send a REQ
|
||||||
const req: ClientMessage = ["REQ", "123", { kinds: [1] }]
|
const req: ClientMessage = ["REQ", "123", {kinds: [1]}]
|
||||||
socket.emit(SocketEvent.Send, req)
|
socket.emit(SocketEvent.Send, req)
|
||||||
|
|
||||||
// Receive auth-required rejection
|
// Receive auth-required rejection
|
||||||
@@ -159,10 +162,13 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should not retry AUTH_JOIN events", () => {
|
it("should not retry AUTH_JOIN events", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send an AUTH_JOIN event
|
// Send an AUTH_JOIN event
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: AUTH_JOIN, content: "", tags: [], pubkey: "", sig: "" }]
|
const event: ClientMessage = [
|
||||||
|
"EVENT",
|
||||||
|
{id: "123", kind: AUTH_JOIN, content: "", tags: [], pubkey: "", sig: ""},
|
||||||
|
]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Receive auth-required rejection
|
// Receive auth-required rejection
|
||||||
@@ -176,10 +182,13 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should clear pending messages on successful response", () => {
|
it("should clear pending messages on successful response", () => {
|
||||||
const cleanup = socketPolicyAuthBuffer(socket)
|
const cleanup = socketPolicyAuthBuffer(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send an event
|
// Send an event
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1, content: "", tags: [], pubkey: "", sig: "" }]
|
const event: ClientMessage = [
|
||||||
|
"EVENT",
|
||||||
|
{id: "123", kind: 1, content: "", tags: [], pubkey: "", sig: ""},
|
||||||
|
]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Receive successful response
|
// Receive successful response
|
||||||
@@ -198,13 +207,13 @@ describe('policy', () => {
|
|||||||
describe("socketPolicyConnectOnSend", () => {
|
describe("socketPolicyConnectOnSend", () => {
|
||||||
it("should open socket on send when closed", () => {
|
it("should open socket on send when closed", () => {
|
||||||
const cleanup = socketPolicyConnectOnSend(socket)
|
const cleanup = socketPolicyConnectOnSend(socket)
|
||||||
const openSpy = vi.spyOn(socket, 'open')
|
const openSpy = vi.spyOn(socket, "open")
|
||||||
|
|
||||||
// Socket starts closed
|
// Socket starts closed
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Sending, event)
|
socket.emit(SocketEvent.Sending, event)
|
||||||
|
|
||||||
// Should open the socket
|
// Should open the socket
|
||||||
@@ -215,13 +224,13 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should not open socket if already open", () => {
|
it("should not open socket if already open", () => {
|
||||||
const cleanup = socketPolicyConnectOnSend(socket)
|
const cleanup = socketPolicyConnectOnSend(socket)
|
||||||
const openSpy = vi.spyOn(socket, 'open')
|
const openSpy = vi.spyOn(socket, "open")
|
||||||
|
|
||||||
// Socket is open
|
// Socket is open
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Sending, event)
|
socket.emit(SocketEvent.Sending, event)
|
||||||
|
|
||||||
// Should not try to open the socket
|
// Should not try to open the socket
|
||||||
@@ -232,14 +241,14 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should not open socket if there was a recent error", () => {
|
it("should not open socket if there was a recent error", () => {
|
||||||
const cleanup = socketPolicyConnectOnSend(socket)
|
const cleanup = socketPolicyConnectOnSend(socket)
|
||||||
const openSpy = vi.spyOn(socket, 'open')
|
const openSpy = vi.spyOn(socket, "open")
|
||||||
|
|
||||||
// Socket has an error
|
// Socket has an error
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Error)
|
socket.emit(SocketEvent.Status, SocketStatus.Error)
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Sending, event)
|
socket.emit(SocketEvent.Sending, event)
|
||||||
|
|
||||||
// Should not try to open the socket due to recent error
|
// Should not try to open the socket due to recent error
|
||||||
@@ -261,7 +270,7 @@ describe('policy', () => {
|
|||||||
describe("socketPolicyCloseOnTimeout", () => {
|
describe("socketPolicyCloseOnTimeout", () => {
|
||||||
it("should close socket after 30 seconds of inactivity", async () => {
|
it("should close socket after 30 seconds of inactivity", async () => {
|
||||||
const cleanup = socketPolicyCloseOnTimeout(socket)
|
const cleanup = socketPolicyCloseOnTimeout(socket)
|
||||||
const closeSpy = vi.spyOn(socket, 'close')
|
const closeSpy = vi.spyOn(socket, "close")
|
||||||
|
|
||||||
// Set socket as open
|
// Set socket as open
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
||||||
@@ -277,7 +286,7 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should reset timer on send activity", () => {
|
it("should reset timer on send activity", () => {
|
||||||
const cleanup = socketPolicyCloseOnTimeout(socket)
|
const cleanup = socketPolicyCloseOnTimeout(socket)
|
||||||
const closeSpy = vi.spyOn(socket, 'close')
|
const closeSpy = vi.spyOn(socket, "close")
|
||||||
|
|
||||||
// Set socket as open
|
// Set socket as open
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
||||||
@@ -286,7 +295,7 @@ describe('policy', () => {
|
|||||||
vi.advanceTimersByTime(20000)
|
vi.advanceTimersByTime(20000)
|
||||||
|
|
||||||
// Send a message
|
// Send a message
|
||||||
socket.emit(SocketEvent.Send, ["EVENT", { id: "123" }])
|
socket.emit(SocketEvent.Send, ["EVENT", {id: "123"}])
|
||||||
|
|
||||||
// Advance time partially again
|
// Advance time partially again
|
||||||
vi.advanceTimersByTime(20000)
|
vi.advanceTimersByTime(20000)
|
||||||
@@ -305,7 +314,7 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should reset timer on receive activity", () => {
|
it("should reset timer on receive activity", () => {
|
||||||
const cleanup = socketPolicyCloseOnTimeout(socket)
|
const cleanup = socketPolicyCloseOnTimeout(socket)
|
||||||
const closeSpy = vi.spyOn(socket, 'close')
|
const closeSpy = vi.spyOn(socket, "close")
|
||||||
|
|
||||||
// Set socket as open
|
// Set socket as open
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
socket.emit(SocketEvent.Status, SocketStatus.Open)
|
||||||
@@ -314,7 +323,7 @@ describe('policy', () => {
|
|||||||
vi.advanceTimersByTime(20000)
|
vi.advanceTimersByTime(20000)
|
||||||
|
|
||||||
// Receive a message
|
// Receive a message
|
||||||
socket.emit(SocketEvent.Receive, ["EVENT", "123", { id: "123" }])
|
socket.emit(SocketEvent.Receive, ["EVENT", "123", {id: "123"}])
|
||||||
|
|
||||||
// Advance time partially again
|
// Advance time partially again
|
||||||
vi.advanceTimersByTime(20000)
|
vi.advanceTimersByTime(20000)
|
||||||
@@ -333,7 +342,7 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should not close socket if not open", () => {
|
it("should not close socket if not open", () => {
|
||||||
const cleanup = socketPolicyCloseOnTimeout(socket)
|
const cleanup = socketPolicyCloseOnTimeout(socket)
|
||||||
const closeSpy = vi.spyOn(socket, 'close')
|
const closeSpy = vi.spyOn(socket, "close")
|
||||||
|
|
||||||
// Set socket as closed
|
// Set socket as closed
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
||||||
@@ -351,10 +360,10 @@ describe('policy', () => {
|
|||||||
describe("socketPolicyReopenActive", () => {
|
describe("socketPolicyReopenActive", () => {
|
||||||
it("should reopen socket when closed with pending messages", async () => {
|
it("should reopen socket when closed with pending messages", async () => {
|
||||||
const cleanup = socketPolicyReopenActive(socket)
|
const cleanup = socketPolicyReopenActive(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send an event that will be pending
|
// Send an event that will be pending
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Socket closes
|
// Socket closes
|
||||||
@@ -371,10 +380,10 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should reopen socket when closed with pending requests", async () => {
|
it("should reopen socket when closed with pending requests", async () => {
|
||||||
const cleanup = socketPolicyReopenActive(socket)
|
const cleanup = socketPolicyReopenActive(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send a request that will be pending
|
// Send a request that will be pending
|
||||||
const req: ClientMessage = ["REQ", "123", { kinds: [1] }]
|
const req: ClientMessage = ["REQ", "123", {kinds: [1]}]
|
||||||
socket.emit(SocketEvent.Send, req)
|
socket.emit(SocketEvent.Send, req)
|
||||||
|
|
||||||
// Socket closes
|
// Socket closes
|
||||||
@@ -391,10 +400,10 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should not reopen socket immediately after previous open", async () => {
|
it("should not reopen socket immediately after previous open", async () => {
|
||||||
const cleanup = socketPolicyReopenActive(socket)
|
const cleanup = socketPolicyReopenActive(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send an event that will be pending
|
// Send an event that will be pending
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Socket opens then closes quickly
|
// Socket opens then closes quickly
|
||||||
@@ -418,10 +427,10 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should remove pending messages when they complete", () => {
|
it("should remove pending messages when they complete", () => {
|
||||||
const cleanup = socketPolicyReopenActive(socket)
|
const cleanup = socketPolicyReopenActive(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send an event that will be pending
|
// Send an event that will be pending
|
||||||
const event: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const event: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.emit(SocketEvent.Send, event)
|
socket.emit(SocketEvent.Send, event)
|
||||||
|
|
||||||
// Event completes successfully
|
// Event completes successfully
|
||||||
@@ -441,10 +450,10 @@ describe('policy', () => {
|
|||||||
|
|
||||||
it("should remove pending messages when closed", () => {
|
it("should remove pending messages when closed", () => {
|
||||||
const cleanup = socketPolicyReopenActive(socket)
|
const cleanup = socketPolicyReopenActive(socket)
|
||||||
const sendSpy = vi.spyOn(socket, 'send')
|
const sendSpy = vi.spyOn(socket, "send")
|
||||||
|
|
||||||
// Send a request that will be pending
|
// Send a request that will be pending
|
||||||
const req: ClientMessage = ["REQ", "123", { kinds: [1] }]
|
const req: ClientMessage = ["REQ", "123", {kinds: [1]}]
|
||||||
socket.emit(SocketEvent.Send, req)
|
socket.emit(SocketEvent.Send, req)
|
||||||
|
|
||||||
// Send close for the request
|
// Send close for the request
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { Socket } from "../src/socket"
|
import {Socket} from "../src/socket"
|
||||||
import { Pool, makeSocket } from "../src/pool"
|
import {Pool} from "../src/pool"
|
||||||
import { normalizeRelayUrl } from "@welshman/util"
|
|
||||||
|
|
||||||
vi.mock('isomorphic-ws', () => {
|
vi.mock("isomorphic-ws", () => {
|
||||||
const WebSocket = vi.fn(function (this: any) {
|
const WebSocket = vi.fn(function (this: any) {
|
||||||
setTimeout(() => this.onopen())
|
setTimeout(() => this.onopen())
|
||||||
})
|
})
|
||||||
@@ -14,7 +13,7 @@ vi.mock('isomorphic-ws', () => {
|
|||||||
this.onclose()
|
this.onclose()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { default: WebSocket }
|
return {default: WebSocket}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Pool", () => {
|
describe("Pool", () => {
|
||||||
@@ -95,7 +94,7 @@ describe("Pool", () => {
|
|||||||
|
|
||||||
describe("remove", () => {
|
describe("remove", () => {
|
||||||
it("should remove and cleanup existing socket", () => {
|
it("should remove and cleanup existing socket", () => {
|
||||||
const mockSocket = { url: "wss://test.relay", cleanup: vi.fn() }
|
const mockSocket = {url: "wss://test.relay", cleanup: vi.fn()}
|
||||||
|
|
||||||
pool._data.set(mockSocket.url, mockSocket as unknown as Socket)
|
pool._data.set(mockSocket.url, mockSocket as unknown as Socket)
|
||||||
pool.remove(mockSocket.url)
|
pool.remove(mockSocket.url)
|
||||||
@@ -113,7 +112,7 @@ describe("Pool", () => {
|
|||||||
describe("clear", () => {
|
describe("clear", () => {
|
||||||
it("should remove all sockets", () => {
|
it("should remove all sockets", () => {
|
||||||
const urls = ["wss://test1.relay", "wss://test2.relay"]
|
const urls = ["wss://test1.relay", "wss://test2.relay"]
|
||||||
const mockSockets = urls.map(url => ({ url, cleanup: vi.fn() }))
|
const mockSockets = urls.map(url => ({url, cleanup: vi.fn()}))
|
||||||
|
|
||||||
for (const mockSocket of mockSockets) {
|
for (const mockSocket of mockSockets) {
|
||||||
pool._data.set(mockSocket.url, mockSocket as unknown as Socket)
|
pool._data.set(mockSocket.url, mockSocket as unknown as Socket)
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { EventEmitter } from "events"
|
import {SinglePublish, MultiPublish, PublishEvent} from "../src/publish"
|
||||||
import { SinglePublish, MultiPublish, PublishEvent, PublishStatus, } from "../src/publish"
|
import {MockAdapter} from "../src/adapter"
|
||||||
import { AbstractAdapter, AdapterEvent, MockAdapter } from "../src/adapter"
|
import {ClientMessageType} from "../src/message"
|
||||||
import { ClientMessageType, RelayMessage } from "../src/message"
|
import {makeEvent} from "@welshman/util"
|
||||||
import { SignedEvent, makeEvent } from "@welshman/util"
|
import {Nip01Signer} from "@welshman/signer"
|
||||||
import { Nip01Signer } from '@welshman/signer'
|
|
||||||
|
|
||||||
describe("SinglePublish", () => {
|
describe("SinglePublish", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -17,12 +16,12 @@ describe("SinglePublish", () => {
|
|||||||
|
|
||||||
it("success works", async () => {
|
it("success works", async () => {
|
||||||
const sendSpy = vi.fn()
|
const sendSpy = vi.fn()
|
||||||
const adapter = new MockAdapter('1', sendSpy)
|
const adapter = new MockAdapter("1", sendSpy)
|
||||||
const signer = Nip01Signer.ephemeral()
|
const signer = Nip01Signer.ephemeral()
|
||||||
const event = await signer.sign(makeEvent(1))
|
const event = await signer.sign(makeEvent(1))
|
||||||
|
|
||||||
const pub = new SinglePublish({
|
const pub = new SinglePublish({
|
||||||
relay: '1',
|
relay: "1",
|
||||||
context: {getAdapter: () => adapter},
|
context: {getAdapter: () => adapter},
|
||||||
event,
|
event,
|
||||||
})
|
})
|
||||||
@@ -50,12 +49,12 @@ describe("SinglePublish", () => {
|
|||||||
|
|
||||||
it("failure works", async () => {
|
it("failure works", async () => {
|
||||||
const sendSpy = vi.fn()
|
const sendSpy = vi.fn()
|
||||||
const adapter = new MockAdapter('1', sendSpy)
|
const adapter = new MockAdapter("1", sendSpy)
|
||||||
const signer = Nip01Signer.ephemeral()
|
const signer = Nip01Signer.ephemeral()
|
||||||
const event = await signer.sign(makeEvent(1))
|
const event = await signer.sign(makeEvent(1))
|
||||||
|
|
||||||
const pub = new SinglePublish({
|
const pub = new SinglePublish({
|
||||||
relay: '1',
|
relay: "1",
|
||||||
context: {getAdapter: () => adapter},
|
context: {getAdapter: () => adapter},
|
||||||
event,
|
event,
|
||||||
})
|
})
|
||||||
@@ -83,12 +82,12 @@ describe("SinglePublish", () => {
|
|||||||
|
|
||||||
it("timeout works", async () => {
|
it("timeout works", async () => {
|
||||||
const sendSpy = vi.fn()
|
const sendSpy = vi.fn()
|
||||||
const adapter = new MockAdapter('1', sendSpy)
|
const adapter = new MockAdapter("1", sendSpy)
|
||||||
const signer = Nip01Signer.ephemeral()
|
const signer = Nip01Signer.ephemeral()
|
||||||
const event = await signer.sign(makeEvent(1))
|
const event = await signer.sign(makeEvent(1))
|
||||||
|
|
||||||
const pub = new SinglePublish({
|
const pub = new SinglePublish({
|
||||||
relay: '1',
|
relay: "1",
|
||||||
context: {getAdapter: () => adapter},
|
context: {getAdapter: () => adapter},
|
||||||
event,
|
event,
|
||||||
})
|
})
|
||||||
@@ -117,12 +116,12 @@ describe("SinglePublish", () => {
|
|||||||
|
|
||||||
it("abort works", async () => {
|
it("abort works", async () => {
|
||||||
const sendSpy = vi.fn()
|
const sendSpy = vi.fn()
|
||||||
const adapter = new MockAdapter('1', sendSpy)
|
const adapter = new MockAdapter("1", sendSpy)
|
||||||
const signer = Nip01Signer.ephemeral()
|
const signer = Nip01Signer.ephemeral()
|
||||||
const event = await signer.sign(makeEvent(1))
|
const event = await signer.sign(makeEvent(1))
|
||||||
|
|
||||||
const pub = new SinglePublish({
|
const pub = new SinglePublish({
|
||||||
relay: '1',
|
relay: "1",
|
||||||
context: {getAdapter: () => adapter},
|
context: {getAdapter: () => adapter},
|
||||||
event,
|
event,
|
||||||
})
|
})
|
||||||
@@ -163,27 +162,31 @@ describe("MultiPublish", () => {
|
|||||||
|
|
||||||
it("should all basically work", async () => {
|
it("should all basically work", async () => {
|
||||||
const send1Spy = vi.fn()
|
const send1Spy = vi.fn()
|
||||||
const adapter1 = new MockAdapter('1', send1Spy)
|
const adapter1 = new MockAdapter("1", send1Spy)
|
||||||
const send2Spy = vi.fn()
|
const send2Spy = vi.fn()
|
||||||
const adapter2 = new MockAdapter('2', send2Spy)
|
const adapter2 = new MockAdapter("2", send2Spy)
|
||||||
const send3Spy = vi.fn()
|
const send3Spy = vi.fn()
|
||||||
const adapter3 = new MockAdapter('3', send3Spy)
|
const adapter3 = new MockAdapter("3", send3Spy)
|
||||||
const signer = Nip01Signer.ephemeral()
|
const signer = Nip01Signer.ephemeral()
|
||||||
const event = await signer.sign(makeEvent(1))
|
const event = await signer.sign(makeEvent(1))
|
||||||
|
|
||||||
const pub = new MultiPublish({
|
const pub = new MultiPublish({
|
||||||
event,
|
event,
|
||||||
relays: ['1', '2', '3'],
|
relays: ["1", "2", "3"],
|
||||||
context: {
|
context: {
|
||||||
getAdapter: (url: string) => {
|
getAdapter: (url: string) => {
|
||||||
switch(url) {
|
switch (url) {
|
||||||
case '1': return adapter1
|
case "1":
|
||||||
case '2': return adapter2
|
return adapter1
|
||||||
case '3': return adapter3
|
case "2":
|
||||||
default: throw new Error(`Unknown relay: ${url}`)
|
return adapter2
|
||||||
|
case "3":
|
||||||
|
return adapter3
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown relay: ${url}`)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const successSpy = vi.fn()
|
const successSpy = vi.fn()
|
||||||
@@ -199,7 +202,6 @@ describe("MultiPublish", () => {
|
|||||||
adapter1.receive(["OK", event.id, true, "hi"])
|
adapter1.receive(["OK", event.id, true, "hi"])
|
||||||
adapter2.receive(["OK", event.id, false, "hi"])
|
adapter2.receive(["OK", event.id, false, "hi"])
|
||||||
|
|
||||||
|
|
||||||
await vi.runAllTimers()
|
await vi.runAllTimers()
|
||||||
|
|
||||||
expect(successSpy).toHaveBeenCalledWith(event.id, "hi", "1")
|
expect(successSpy).toHaveBeenCalledWith(event.id, "hi", "1")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
|
import {describe, expect, it, vi, beforeEach, afterEach} from "vitest"
|
||||||
import { Nip01Signer } from '@welshman/signer'
|
import {Nip01Signer} from "@welshman/signer"
|
||||||
import { makeEvent } from '@welshman/util'
|
import {makeEvent} from "@welshman/util"
|
||||||
import { ClientMessageType } from "../src/message"
|
import {ClientMessageType} from "../src/message"
|
||||||
import { MockAdapter } from "../src/adapter"
|
import {MockAdapter} from "../src/adapter"
|
||||||
import { SingleRequest, MultiRequest, RequestEvent } from "../src/request"
|
import {SingleRequest, MultiRequest, RequestEvent} from "../src/request"
|
||||||
|
|
||||||
describe("SingleRequest", () => {
|
describe("SingleRequest", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -16,9 +16,9 @@ describe("SingleRequest", () => {
|
|||||||
|
|
||||||
it("everything basically works", async () => {
|
it("everything basically works", async () => {
|
||||||
const sendSpy = vi.fn()
|
const sendSpy = vi.fn()
|
||||||
const adapter = new MockAdapter('1', sendSpy)
|
const adapter = new MockAdapter("1", sendSpy)
|
||||||
const req = new SingleRequest({
|
const req = new SingleRequest({
|
||||||
relay: 'whatever',
|
relay: "whatever",
|
||||||
filters: [{kinds: [1]}],
|
filters: [{kinds: [1]}],
|
||||||
context: {getAdapter: () => adapter},
|
context: {getAdapter: () => adapter},
|
||||||
})
|
})
|
||||||
@@ -82,14 +82,14 @@ describe("MultiRequest", () => {
|
|||||||
|
|
||||||
it("everything basically works", async () => {
|
it("everything basically works", async () => {
|
||||||
const send1Spy = vi.fn()
|
const send1Spy = vi.fn()
|
||||||
const adapter1 = new MockAdapter('1', send1Spy)
|
const adapter1 = new MockAdapter("1", send1Spy)
|
||||||
const send2Spy = vi.fn()
|
const send2Spy = vi.fn()
|
||||||
const adapter2 = new MockAdapter('2', send2Spy)
|
const adapter2 = new MockAdapter("2", send2Spy)
|
||||||
const req = new MultiRequest({
|
const req = new MultiRequest({
|
||||||
relays: ['1', '2'],
|
relays: ["1", "2"],
|
||||||
filters: [{kinds: [1]}],
|
filters: [{kinds: [1]}],
|
||||||
context: {
|
context: {
|
||||||
getAdapter: (url: string) => url === '1' ? adapter1 : adapter2
|
getAdapter: (url: string) => (url === "1" ? adapter1 : adapter2),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -129,10 +129,10 @@ describe("MultiRequest", () => {
|
|||||||
|
|
||||||
await vi.runAllTimersAsync()
|
await vi.runAllTimersAsync()
|
||||||
|
|
||||||
expect(duplicateSpy).toHaveBeenCalledWith(event1, '2')
|
expect(duplicateSpy).toHaveBeenCalledWith(event1, "2")
|
||||||
expect(filteredSpy).toHaveBeenCalledWith(event2, '1')
|
expect(filteredSpy).toHaveBeenCalledWith(event2, "1")
|
||||||
expect(invalidSpy).toHaveBeenCalledWith(event3, '1')
|
expect(invalidSpy).toHaveBeenCalledWith(event3, "1")
|
||||||
expect(eventSpy).toHaveBeenCalledWith(event1, '1')
|
expect(eventSpy).toHaveBeenCalledWith(event1, "1")
|
||||||
expect(eoseSpy).toHaveBeenCalledTimes(0)
|
expect(eoseSpy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
adapter1.receive(["EOSE", id1])
|
adapter1.receive(["EOSE", id1])
|
||||||
@@ -145,4 +145,3 @@ describe("MultiRequest", () => {
|
|||||||
expect(closeSpy).toHaveBeenCalledTimes(1)
|
expect(closeSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { sleep } from "@welshman/lib"
|
import WebSocket from "isomorphic-ws"
|
||||||
import WebSocket from 'isomorphic-ws'
|
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
import {Socket, SocketStatus, SocketEvent} from "../src/socket"
|
||||||
import { Socket, SocketStatus, SocketEvent } from "../src/socket"
|
import {ClientMessage, RelayMessage} from "../src/message"
|
||||||
import { ClientMessage, RelayMessage } from "../src/message"
|
|
||||||
|
|
||||||
vi.mock('isomorphic-ws', () => {
|
vi.mock("isomorphic-ws", () => {
|
||||||
const WebSocket = vi.fn(function (this: any) {
|
const WebSocket = vi.fn(function (this: any) {
|
||||||
setTimeout(() => this.onopen())
|
setTimeout(() => this.onopen())
|
||||||
})
|
})
|
||||||
@@ -15,7 +14,7 @@ vi.mock('isomorphic-ws', () => {
|
|||||||
this.onclose()
|
this.onclose()
|
||||||
})
|
})
|
||||||
|
|
||||||
return { default: WebSocket }
|
return {default: WebSocket}
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("Socket", () => {
|
describe("Socket", () => {
|
||||||
@@ -77,9 +76,11 @@ describe("Socket", () => {
|
|||||||
|
|
||||||
socket.open()
|
socket.open()
|
||||||
|
|
||||||
|
const closeSpy = vi.spyOn(socket._ws, "close")
|
||||||
|
|
||||||
socket.close()
|
socket.close()
|
||||||
|
|
||||||
expect(socket._ws!.close).toHaveBeenCalled()
|
expect(closeSpy).toHaveBeenCalled()
|
||||||
expect(statusSpy).toHaveBeenCalledWith(SocketStatus.Closed, "wss://test.relay")
|
expect(statusSpy).toHaveBeenCalledWith(SocketStatus.Closed, "wss://test.relay")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -89,7 +90,7 @@ describe("Socket", () => {
|
|||||||
const enqueueSpy = vi.fn()
|
const enqueueSpy = vi.fn()
|
||||||
socket.on(SocketEvent.Sending, enqueueSpy)
|
socket.on(SocketEvent.Sending, enqueueSpy)
|
||||||
|
|
||||||
const message: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const message: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.send(message)
|
socket.send(message)
|
||||||
|
|
||||||
expect(enqueueSpy).toHaveBeenCalledWith(message, "wss://test.relay")
|
expect(enqueueSpy).toHaveBeenCalledWith(message, "wss://test.relay")
|
||||||
@@ -102,7 +103,7 @@ describe("Socket", () => {
|
|||||||
socket.open()
|
socket.open()
|
||||||
socket._ws?.onopen?.(undefined as unknown as any)
|
socket._ws?.onopen?.(undefined as unknown as any)
|
||||||
|
|
||||||
const message: ClientMessage = ["EVENT", { id: "123", kind: 1 }]
|
const message: ClientMessage = ["EVENT", {id: "123", kind: 1}]
|
||||||
socket.send(message)
|
socket.send(message)
|
||||||
|
|
||||||
await vi.runAllTimers()
|
await vi.runAllTimers()
|
||||||
@@ -118,7 +119,7 @@ describe("Socket", () => {
|
|||||||
socket.on(SocketEvent.Receive, receiveSpy)
|
socket.on(SocketEvent.Receive, receiveSpy)
|
||||||
|
|
||||||
socket.open()
|
socket.open()
|
||||||
const message: RelayMessage = ["EVENT", "123", { id: "123", kind: 1 }]
|
const message: RelayMessage = ["EVENT", "123", {id: "123", kind: 1}]
|
||||||
socket._ws?.onmessage?.({data: JSON.stringify(message)} as unknown as any)
|
socket._ws?.onmessage?.({data: JSON.stringify(message)} as unknown as any)
|
||||||
|
|
||||||
await vi.runAllTimers()
|
await vi.runAllTimers()
|
||||||
|
|||||||
+517
-490
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import {on, nthEq, always, call, sleep, spec, ago, now} from "@welshman/lib"
|
import {on, nthEq, always, call, sleep, ago, now} from "@welshman/lib"
|
||||||
import {AUTH_JOIN, StampedEvent, SignedEvent} from "@welshman/util"
|
import {AUTH_JOIN, StampedEvent, SignedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
isClientEvent,
|
isClientEvent,
|
||||||
isClientReq,
|
isClientReq,
|
||||||
isClientNegClose,
|
isClientNegClose,
|
||||||
ClientMessageType,
|
|
||||||
RelayMessage,
|
RelayMessage,
|
||||||
isRelayOk,
|
isRelayOk,
|
||||||
isRelayEose,
|
isRelayEose,
|
||||||
@@ -50,7 +49,7 @@ export const socketPolicyAuthBuffer = (socket: Socket) => {
|
|||||||
}),
|
}),
|
||||||
on(socket, SocketEvent.Receiving, (message: RelayMessage) => {
|
on(socket, SocketEvent.Receiving, (message: RelayMessage) => {
|
||||||
// If the client is closing a request during auth, don't tell the caller, we'll retry it
|
// If the client is closing a request during auth, don't tell the caller, we'll retry it
|
||||||
if (isRelayClosed(message) && message[2]?.startsWith('auth-required:')) {
|
if (isRelayClosed(message) && message[2]?.startsWith("auth-required:")) {
|
||||||
socket._recvQueue.remove(message)
|
socket._recvQueue.remove(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ export const socketPolicyAuthBuffer = (socket: Socket) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the client is rejecting an event during auth, don't tell the caller, we'll retry it
|
// If the client is rejecting an event during auth, don't tell the caller, we'll retry it
|
||||||
if (isRelayOk(message) && !message[2] && message[3]?.startsWith('auth-required:')) {
|
if (isRelayOk(message) && !message[2] && message[3]?.startsWith("auth-required:")) {
|
||||||
socket._recvQueue.remove(message)
|
socket._recvQueue.remove(message)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ export const makeLoader = (options: LoaderOptions) =>
|
|||||||
tracker,
|
tracker,
|
||||||
relays: [relay],
|
relays: [relay],
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
...options
|
...options,
|
||||||
})
|
})
|
||||||
|
|
||||||
let count = 0
|
let count = 0
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ describe("LocalRelay", () => {
|
|||||||
const id = "ff".repeat(32)
|
const id = "ff".repeat(32)
|
||||||
const sig = "00".repeat(64)
|
const sig = "00".repeat(64)
|
||||||
const currentTime = now()
|
const currentTime = now()
|
||||||
const onionUrl = "abcdefghijklmnopqrstuvwxyz234567abcdefghijklmnopqrstuvwx.onion"
|
|
||||||
|
|
||||||
const createEvent = (overrides = {}): TrustedEvent => ({
|
const createEvent = (overrides = {}): TrustedEvent => ({
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {Repository} from "@welshman/relay"
|
|||||||
import {get} from "svelte/store"
|
import {get} from "svelte/store"
|
||||||
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
|
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
|
||||||
import {
|
import {
|
||||||
adapter,
|
|
||||||
custom,
|
custom,
|
||||||
deriveEvents,
|
deriveEvents,
|
||||||
deriveEventsMapped,
|
deriveEventsMapped,
|
||||||
@@ -114,28 +113,6 @@ describe("Store utilities", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("adapter", () => {
|
|
||||||
it("should adapt between different types", () => {
|
|
||||||
const source = synced<number>("test", 0)
|
|
||||||
const adapted = adapter({
|
|
||||||
store: source,
|
|
||||||
forward: n => n.toString(),
|
|
||||||
backward: s => parseInt(s, 10),
|
|
||||||
})
|
|
||||||
|
|
||||||
const mockFn = vi.fn()
|
|
||||||
adapted.subscribe(mockFn)
|
|
||||||
|
|
||||||
adapted.set("42")
|
|
||||||
expect(get(source)).toBe(42)
|
|
||||||
expect(mockFn).toHaveBeenLastCalledWith("42")
|
|
||||||
|
|
||||||
adapted.update(s => (parseInt(s, 10) + 1).toString())
|
|
||||||
expect(get(source)).toBe(43)
|
|
||||||
expect(mockFn).toHaveBeenLastCalledWith("43")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("Event-related stores", () => {
|
describe("Event-related stores", () => {
|
||||||
const mockRepository = {
|
const mockRepository = {
|
||||||
query: vi.fn(),
|
query: vi.fn(),
|
||||||
|
|||||||
@@ -121,22 +121,6 @@ export const custom = <T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple adapter
|
|
||||||
|
|
||||||
export const adapter = <Source, Target>({
|
|
||||||
store,
|
|
||||||
forward,
|
|
||||||
backward,
|
|
||||||
}: {
|
|
||||||
store: Writable<Source>
|
|
||||||
forward: (x: Source) => Target
|
|
||||||
backward: (x: Target) => Source
|
|
||||||
}) => ({
|
|
||||||
...derived(store, forward),
|
|
||||||
set: (x: Target) => store.set(backward(x)),
|
|
||||||
update: (f: (x: Target) => Target) => store.update((x: Source) => backward(f(forward(x)))),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Event related stores
|
// Event related stores
|
||||||
|
|
||||||
export type DeriveEventsMappedOptions<T> = {
|
export type DeriveEventsMappedOptions<T> = {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ describe("Events", () => {
|
|||||||
|
|
||||||
describe("signature validation", () => {
|
describe("signature validation", () => {
|
||||||
it("should validate signature using verifiedSymbol", () => {
|
it("should validate signature using verifiedSymbol", () => {
|
||||||
let event = createSignedEvent() as Events.SignedEvent
|
const event = createSignedEvent() as Events.SignedEvent
|
||||||
event[verifiedSymbol] = true
|
event[verifiedSymbol] = true
|
||||||
expect(Events.verifyEvent(event)).toBe(true)
|
expect(Events.verifyEvent(event)).toBe(true)
|
||||||
|
|
||||||
|
|||||||
@@ -211,9 +211,7 @@ describe("Filters", () => {
|
|||||||
it("should calculate filter generality", () => {
|
it("should calculate filter generality", () => {
|
||||||
expect(getFilterGenerality({ids: [id]})).toBe(0)
|
expect(getFilterGenerality({ids: [id]})).toBe(0)
|
||||||
expect(getFilterGenerality({authors: [pubkey], "#p": [pubkey]})).toBe(0.2)
|
expect(getFilterGenerality({authors: [pubkey], "#p": [pubkey]})).toBe(0.2)
|
||||||
expect(getFilterGenerality({authors: [pubkey, pubkey, pubkey], kinds: [1]})).toBe(
|
expect(getFilterGenerality({authors: [pubkey, pubkey, pubkey], kinds: [1]})).toBe(0.01)
|
||||||
0.01,
|
|
||||||
)
|
|
||||||
expect(getFilterGenerality({kinds: [1]})).toBe(1)
|
expect(getFilterGenerality({kinds: [1]})).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ export const verifyEvent = (() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (event: TrustedEvent) => event.sig && (event[verifiedSymbol] || verify(event as SignedEvent))
|
return (event: TrustedEvent) =>
|
||||||
|
event.sig && (event[verifiedSymbol] || verify(event as SignedEvent))
|
||||||
})()
|
})()
|
||||||
|
|
||||||
export const isEventTemplate = (e: EventTemplate): e is EventTemplate =>
|
export const isEventTemplate = (e: EventTemplate): e is EventTemplate =>
|
||||||
|
|||||||
Generated
-1157
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user