This commit is contained in:
Jon Staab
2025-04-09 11:35:09 -07:00
parent 5f3624b8f3
commit 728ad1fba0
37 changed files with 1039 additions and 2183 deletions
+1 -2
View File
@@ -3,6 +3,5 @@
"printWidth": 100, "printWidth": 100,
"bracketSameLine": true, "bracketSameLine": true,
"arrowParens": "avoid", "arrowParens": "avoid",
"bracketSpacing": false, "bracketSpacing": false
"pluginSearchDirs": false
} }
+20 -20
View File
@@ -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
View File
@@ -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",
+5 -9
View File
@@ -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> = {}
+23 -15
View File
@@ -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) => {
+3 -1
View File
@@ -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)])
-2
View File
@@ -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
View File
@@ -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"
-2
View File
@@ -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"
-2
View File
@@ -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 -3
View File
@@ -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) =>
+11 -7
View File
@@ -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]}]),
}) })
+5 -3
View File
@@ -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()})
} }
+3 -6
View File
@@ -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
+16 -4
View File
@@ -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
View File
@@ -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,
+1 -3
View File
@@ -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", () => {
+2 -2
View File
@@ -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"
+3 -2
View File
@@ -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)[]) => {
+76 -68
View File
@@ -1,10 +1,11 @@
// 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.
@@ -20,7 +21,7 @@ export type Options = {
//=> '//sindresorhus.com' //=> '//sindresorhus.com'
``` ```
*/ */
readonly normalizeProtocol?: boolean; readonly normalizeProtocol?: boolean
/** /**
Normalizes HTTPS URLs to HTTP. Normalizes HTTPS URLs to HTTP.
@@ -36,7 +37,7 @@ 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.
@@ -54,7 +55,7 @@ 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.
@@ -70,7 +71,7 @@ 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.
@@ -86,7 +87,7 @@ 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`.
@@ -104,7 +105,7 @@ 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
@@ -128,7 +129,7 @@ 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.
@@ -144,7 +145,7 @@ 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.
@@ -177,7 +178,7 @@ 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.
@@ -194,7 +195,7 @@ 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.
@@ -215,7 +216,7 @@ 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`.
@@ -231,7 +232,7 @@ 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.
@@ -247,7 +248,7 @@ 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.
@@ -264,7 +265,7 @@ 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.
@@ -279,25 +280,22 @@ 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
} }
@@ -311,47 +309,45 @@ const normalizeDataURL = (urlString: string, {stripHash}: {stripHash: boolean})
} }
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}` : ""}`
} }
/** /**
@@ -375,7 +371,7 @@ 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,
@@ -393,7 +389,7 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
} }
// 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}:`
} }
@@ -408,7 +404,7 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
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
@@ -419,28 +415,28 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
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
@@ -456,7 +452,7 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
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) {
@@ -467,13 +463,13 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
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
} }
@@ -491,26 +487,29 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
} }
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 (
options.stripWWW &&
/^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)
) {
// Each label should be max 63 at length (min: 1). // Each label should be max 63 at length (min: 1).
// Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
// Each TLD should be up to 63 characters long (min: 2). // 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. // It is technically possible to have a single character TLD, but none currently exist.
urlObject.hostname = urlObject.hostname.replace(/^www\./, '') urlObject.hostname = urlObject.hostname.replace(/^www\./, "")
} }
} }
@@ -525,7 +524,7 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
} }
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
@@ -549,12 +548,12 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
} }
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
@@ -562,23 +561,32 @@ export default function normalizeUrl(urlString: string, opts?: Options): string
// 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
+22 -20
View File
@@ -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)
}) })
}) })
+14 -15
View File
@@ -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"
+58 -49
View File
@@ -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
+7 -8
View File
@@ -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)
+28 -26
View File
@@ -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")
+16 -17
View File
@@ -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)
}) })
}) })
+12 -11
View File
@@ -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()
+55 -28
View File
@@ -1,4 +1,5 @@
// (C) 2023 Doug Hoyte. MIT license // (C) 2023 Doug Hoyte. MIT license
/* eslint-disable */
// @ts-nocheck // @ts-nocheck
const PROTOCOL_VERSION = 0x61 // Version 1 const PROTOCOL_VERSION = 0x61 // Version 1
@@ -27,7 +28,7 @@ class WrappedBuffer {
extend(buf) { extend(buf) {
if (buf._raw) buf = buf.unwrap() if (buf._raw) buf = buf.unwrap()
if (typeof(buf.length) !== 'number') throw Error("bad length") if (typeof buf.length !== "number") throw Error("bad length")
const targetSize = buf.length + this.length const targetSize = buf.length + this.length
if (this.capacity < targetSize) { if (this.capacity < targetSize) {
const oldRaw = this._raw const oldRaw = this._raw
@@ -94,16 +95,18 @@ function getBytes(buf, n) {
return buf.shiftN(n) return buf.shiftN(n)
} }
class Accumulator { class Accumulator {
constructor() { constructor() {
this.setToZero() this.setToZero()
if (typeof window === 'undefined') { // node.js if (typeof window === "undefined") {
const crypto = require('crypto') // node.js
this.sha256 = async (slice) => new Uint8Array(crypto.createHash('sha256').update(slice).digest()) const crypto = require("crypto")
} else { // browser this.sha256 = async slice =>
this.sha256 = async (slice) => new Uint8Array(await crypto.subtle.digest("SHA-256", slice)) new Uint8Array(crypto.createHash("sha256").update(slice).digest())
} else {
// browser
this.sha256 = async slice => new Uint8Array(await crypto.subtle.digest("SHA-256", slice))
} }
} }
@@ -112,7 +115,8 @@ class Accumulator {
} }
add(otherBuf) { add(otherBuf) {
let currCarry = 0, nextCarry = 0 let currCarry = 0,
nextCarry = 0
const p = new DataView(this.buf.buffer) const p = new DataView(this.buf.buffer)
const po = new DataView(otherBuf.buffer) const po = new DataView(otherBuf.buffer)
@@ -125,9 +129,9 @@ class Accumulator {
next += currCarry next += currCarry
next += otherV next += otherV
if (next > 0xFFFFFFFF) nextCarry = 1 if (next > 0xffffffff) nextCarry = 1
p.setUint32(offset, next & 0xFFFFFFFF, true) p.setUint32(offset, next & 0xffffffff, true)
currCarry = nextCarry currCarry = nextCarry
nextCarry = 0 nextCarry = 0
} }
@@ -157,7 +161,6 @@ class Accumulator {
} }
} }
class NegentropyStorageVector { class NegentropyStorageVector {
constructor() { constructor() {
this.items = [] this.items = []
@@ -178,7 +181,8 @@ class NegentropyStorageVector {
this.items.sort(itemCompare) this.items.sort(itemCompare)
for (let i = 1; i < this.items.length; i++) { for (let i = 1; i < this.items.length; i++) {
if (itemCompare(this.items[i - 1], this.items[i]) === 0) throw Error("duplicate item inserted") if (itemCompare(this.items[i - 1], this.items[i]) === 0)
throw Error("duplicate item inserted")
} }
} }
@@ -210,7 +214,7 @@ class NegentropyStorageVector {
this._checkSealed() this._checkSealed()
this._checkBounds(begin, end) this._checkBounds(begin, end)
return this._binarySearch(this.items, begin, end, (a) => itemCompare(a, bound) < 0) return this._binarySearch(this.items, begin, end, a => itemCompare(a, bound) < 0)
} }
async fingerprint(begin, end) { async fingerprint(begin, end) {
@@ -253,7 +257,6 @@ class NegentropyStorageVector {
} }
} }
class Negentropy { class Negentropy {
constructor(storage, frameSizeLimit = 0) { constructor(storage, frameSizeLimit = 0) {
if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small") if (frameSizeLimit !== 0 && frameSizeLimit < 4096) throw Error("frameSizeLimit too small")
@@ -286,7 +289,8 @@ class Negentropy {
} }
async reconcile(query) { async reconcile(query) {
const haveIds = [], needIds = [] const haveIds = [],
needIds = []
query = new WrappedBuffer(loadInputBuffer(query)) query = new WrappedBuffer(loadInputBuffer(query))
this.lastTimestampIn = this.lastTimestampOut = 0 // reset for each message this.lastTimestampIn = this.lastTimestampOut = 0 // reset for each message
@@ -295,9 +299,13 @@ class Negentropy {
fullOutput.extend([PROTOCOL_VERSION]) fullOutput.extend([PROTOCOL_VERSION])
const protocolVersion = getByte(query) const protocolVersion = getByte(query)
if (protocolVersion < 0x60 || protocolVersion > 0x6F) throw Error("invalid negentropy protocol version byte") if (protocolVersion < 0x60 || protocolVersion > 0x6f)
throw Error("invalid negentropy protocol version byte")
if (protocolVersion !== PROTOCOL_VERSION) { if (protocolVersion !== PROTOCOL_VERSION) {
if (this.isInitiator) throw Error("unsupported negentropy protocol version requested: " + (protocolVersion - 0x60)) if (this.isInitiator)
throw Error(
"unsupported negentropy protocol version requested: " + (protocolVersion - 0x60),
)
else return [this._renderOutput(fullOutput), haveIds, needIds] else return [this._renderOutput(fullOutput), haveIds, needIds]
} }
@@ -347,7 +355,7 @@ class Negentropy {
if (this.isInitiator) { if (this.isInitiator) {
skip = true skip = true
this.storage.iterate(lower, upper, (item) => { this.storage.iterate(lower, upper, item => {
const k = item.id const k = item.id
if (!theirElems[k]) { if (!theirElems[k]) {
@@ -412,7 +420,11 @@ class Negentropy {
prevBound = currBound prevBound = currBound
} }
return [fullOutput.length === 1 && this.isInitiator ? null : this._renderOutput(fullOutput), haveIds, needIds] return [
fullOutput.length === 1 && this.isInitiator ? null : this._renderOutput(fullOutput),
haveIds,
needIds,
]
} }
async splitRange(lower, upper, upperBound, o) { async splitRange(lower, upper, upperBound, o) {
@@ -424,7 +436,7 @@ class Negentropy {
o.extend(encodeVarInt(Mode.IdList)) o.extend(encodeVarInt(Mode.IdList))
o.extend(encodeVarInt(numElems)) o.extend(encodeVarInt(numElems))
this.storage.iterate(lower, upper, (item) => { this.storage.iterate(lower, upper, item => {
o.extend(item.id) o.extend(item.id)
return true return true
}) })
@@ -536,13 +548,13 @@ class Negentropy {
} }
function loadInputBuffer(inp) { function loadInputBuffer(inp) {
if (typeof(inp) === 'string') inp = hexToUint8Array(inp) if (typeof inp === "string") inp = hexToUint8Array(inp)
else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp) // node Buffer? else if (__proto__ !== Uint8Array.prototype) inp = new Uint8Array(inp) // node Buffer?
return inp return inp
} }
function hexToUint8Array(h) { function hexToUint8Array(h) {
if (h.startsWith('0x')) h = h.substr(2) if (h.startsWith("0x")) h = h.substr(2)
if (h.length % 2 === 1) throw Error("odd length of hex string") if (h.length % 2 === 1) throw Error("odd length of hex string")
const arr = new Uint8Array(h.length / 2) const arr = new Uint8Array(h.length / 2)
for (let i = 0; i < arr.length; i++) arr[i] = parseInt(h.substr(i * 2, 2), 16) for (let i = 0; i < arr.length; i++) arr[i] = parseInt(h.substr(i * 2, 2), 16)
@@ -551,21 +563,37 @@ function hexToUint8Array(h) {
const uint8ArrayToHexLookupTable = new Array(256) const uint8ArrayToHexLookupTable = new Array(256)
{ {
const hexAlphabet = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'] const hexAlphabet = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"a",
"b",
"c",
"d",
"e",
"f",
]
for (let i = 0; i < 256; i++) { for (let i = 0; i < 256; i++) {
uint8ArrayToHexLookupTable[i] = hexAlphabet[(i >>> 4) & 0xF] + hexAlphabet[i & 0xF] uint8ArrayToHexLookupTable[i] = hexAlphabet[(i >>> 4) & 0xf] + hexAlphabet[i & 0xf]
} }
} }
function uint8ArrayToHex(arr) { function uint8ArrayToHex(arr) {
let out = '' let out = ""
for (let i = 0, edx = arr.length; i < edx; i++) { for (let i = 0, edx = arr.length; i < edx; i++) {
out += uint8ArrayToHexLookupTable[arr[i]] out += uint8ArrayToHexLookupTable[arr[i]]
} }
return out return out
} }
function compareUint8Array(a, b) { function compareUint8Array(a, b) {
for (let i = 0; i < a.byteLength; i++) { for (let i = 0; i < a.byteLength; i++) {
if (a[i] < b[i]) return -1 if (a[i] < b[i]) return -1
@@ -586,5 +614,4 @@ function itemCompare(a, b) {
return a.timestamp - b.timestamp return a.timestamp - b.timestamp
} }
export {Negentropy, NegentropyStorageVector}
export {Negentropy, NegentropyStorageVector,}
+3 -4
View File
@@ -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)
} }
}), }),
+1 -1
View File
@@ -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
-1
View File
@@ -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,
-23
View File
@@ -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(),
-16
View File
@@ -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> = {
+1 -1
View File
@@ -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)
+1 -3
View File
@@ -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)
}) })
+2 -1
View File
@@ -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 =>
-1157
View File
File diff suppressed because it is too large Load Diff