Get rid of typed emitter
This commit is contained in:
Generated
+3
-13
@@ -6333,7 +6333,7 @@
|
|||||||
"version": "6.6.7",
|
"version": "6.6.7",
|
||||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
|
||||||
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
"integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^1.9.0"
|
"tslib": "^1.9.0"
|
||||||
@@ -6982,7 +6982,7 @@
|
|||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "0BSD"
|
"license": "0BSD"
|
||||||
},
|
},
|
||||||
"node_modules/tsutils": {
|
"node_modules/tsutils": {
|
||||||
@@ -7027,15 +7027,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typed-emitter": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optionalDependencies": {
|
|
||||||
"rxjs": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/typedoc": {
|
"node_modules/typedoc": {
|
||||||
"version": "0.27.9",
|
"version": "0.27.9",
|
||||||
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz",
|
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.9.tgz",
|
||||||
@@ -7836,8 +7827,7 @@
|
|||||||
"@welshman/lib": "^0.1.1",
|
"@welshman/lib": "^0.1.1",
|
||||||
"@welshman/relay": "^0.1.1",
|
"@welshman/relay": "^0.1.1",
|
||||||
"@welshman/util": "^0.1.2",
|
"@welshman/util": "^0.1.2",
|
||||||
"isomorphic-ws": "^5.0.0",
|
"isomorphic-ws": "^5.0.0"
|
||||||
"typed-emitter": "^2.1.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/net2": {
|
"packages/net2": {
|
||||||
|
|||||||
+1
-1
@@ -15,13 +15,13 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitest/coverage-v8": "^3.0.5",
|
"@vitest/coverage-v8": "^3.0.5",
|
||||||
|
"fake-indexeddb": "^6.0.0",
|
||||||
"gts": "^6.0.2",
|
"gts": "^6.0.2",
|
||||||
"happy-dom": "^17.1.0",
|
"happy-dom": "^17.1.0",
|
||||||
"typedoc": "^0.27.9",
|
"typedoc": "^0.27.9",
|
||||||
"typedoc-plugin-markdown": "^4.4.2",
|
"typedoc-plugin-markdown": "^4.4.2",
|
||||||
"typedoc-vitepress-theme": "^1.1.2",
|
"typedoc-vitepress-theme": "^1.1.2",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"fake-indexeddb": "^6.0.0",
|
|
||||||
"vitepress": "^1.6.3",
|
"vitepress": "^1.6.3",
|
||||||
"vitest": "^3.0.5"
|
"vitest": "^3.0.5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import {throttle} from "@welshman/lib"
|
import {throttle} from "@welshman/lib"
|
||||||
import {verifyEvent, isEphemeralKind, isDVMKind} from "@welshman/util"
|
import {verifyEvent, isEphemeralKind, isDVMKind} from "@welshman/util"
|
||||||
import {Repository} from "@welshman/relay"
|
import {Repository, LocalRelay} from "@welshman/relay"
|
||||||
import {Pool, Tracker, SocketEvent, isRelayEvent} from "@welshman/net"
|
import {Pool, Tracker, SocketEvent, isRelayEvent} from "@welshman/net"
|
||||||
import {custom} from "@welshman/store"
|
import {custom} from "@welshman/store"
|
||||||
import {loadRelay, trackRelayStats} from "./relays.js"
|
import {loadRelay, trackRelayStats} from "./relays.js"
|
||||||
|
|
||||||
export const repository = Repository.getSingleton()
|
export const repository = Repository.getSingleton()
|
||||||
|
|
||||||
|
export const relay = new LocalRelay(repository)
|
||||||
|
|
||||||
export const tracker = new Tracker()
|
export const tracker = new Tracker()
|
||||||
|
|
||||||
Pool.getSingleton().subscribe(socket => {
|
Pool.getSingleton().subscribe(socket => {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export type ThunkRequest = {
|
|||||||
event: ThunkEvent
|
event: ThunkEvent
|
||||||
relays: string[]
|
relays: string[]
|
||||||
delay?: number
|
delay?: number
|
||||||
|
timeout?: number
|
||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +227,7 @@ export const thunkQueue = new TaskQueue<Thunk>({
|
|||||||
event: signedEvent,
|
event: signedEvent,
|
||||||
relays: thunk.request.relays,
|
relays: thunk.request.relays,
|
||||||
context: thunk.request.context,
|
context: thunk.request.context,
|
||||||
|
timeout: thunk.request.timeout,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Copy the signature over since we had deferred it
|
// Copy the signature over since we had deferred it
|
||||||
|
|||||||
@@ -1118,21 +1118,20 @@ export const pushToMapKey = <K, T>(m: Map<K, T[]>, k: K, v: T) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A generic type-safe event listener function that auto-detects the appropriate methods
|
* A generic type-safe event listener function that works with event emitters.
|
||||||
* for adding and removing event listeners.
|
|
||||||
*
|
*
|
||||||
* @param target - The event target object with add/remove listener methods
|
* @param target - The event target object with add/remove listener methods
|
||||||
* @param eventName - The name of the event to listen for
|
* @param eventName - The name of the event to listen for
|
||||||
* @param callback - The callback function to execute when the event occurs
|
* @param callback - The callback function to execute when the event occurs
|
||||||
* @returns A function that removes the event listener when called
|
* @returns A function that removes the event listener when called
|
||||||
*/
|
*/
|
||||||
export const on = <EventName extends string, Args extends any[]>(
|
export const on = <EventMap extends Record<string | symbol, any[]>, E extends keyof EventMap>(
|
||||||
target: {
|
target: {
|
||||||
on: (event: EventName, handler: (...args: Args) => any, ...rest: any[]) => any
|
on(event: E, listener: (...args: EventMap[E]) => any): any
|
||||||
off: (event: EventName, handler: (...args: Args) => any, ...rest: any[]) => any
|
off(event: E, listener: (...args: EventMap[E]) => any): any
|
||||||
},
|
},
|
||||||
eventName: EventName,
|
eventName: E,
|
||||||
callback: (...args: Args) => void,
|
callback: (...args: EventMap[E]) => void,
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
target.on(eventName, callback)
|
target.on(eventName, callback)
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@welshman/lib": "^0.1.1",
|
"@welshman/lib": "^0.1.1",
|
||||||
"@welshman/util": "^0.1.2",
|
|
||||||
"@welshman/relay": "^0.1.1",
|
"@welshman/relay": "^0.1.1",
|
||||||
"isomorphic-ws": "^5.0.0",
|
"@welshman/util": "^0.1.2",
|
||||||
"typed-emitter": "^2.1.0"
|
"isomorphic-ws": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {remove} from "@welshman/lib"
|
import {remove} from "@welshman/lib"
|
||||||
import {normalizeRelayUrl} from "@welshman/util"
|
import {normalizeRelayUrl} from "@welshman/util"
|
||||||
import {Socket} from "./socket.js"
|
import {Socket} from "./socket.js"
|
||||||
|
import {AuthState} from "./auth.js"
|
||||||
import {defaultSocketPolicies} from "./policy.js"
|
import {defaultSocketPolicies} from "./policy.js"
|
||||||
|
|
||||||
export const makeSocket = (url: string, policies = defaultSocketPolicies) => {
|
export const makeSocket = (url: string, policies = defaultSocketPolicies) => {
|
||||||
@@ -21,8 +22,13 @@ export type PoolOptions = {
|
|||||||
|
|
||||||
export let poolSingleton: Pool
|
export let poolSingleton: Pool
|
||||||
|
|
||||||
|
export type PoolItem = {
|
||||||
|
socket: Socket
|
||||||
|
auth: AuthState
|
||||||
|
}
|
||||||
|
|
||||||
export class Pool {
|
export class Pool {
|
||||||
_data = new Map<string, Socket>()
|
_data = new Map<string, PoolItem>()
|
||||||
_subs: PoolSubscription[] = []
|
_subs: PoolSubscription[] = []
|
||||||
|
|
||||||
static getSingleton() {
|
static getSingleton() {
|
||||||
@@ -49,15 +55,15 @@ export class Pool {
|
|||||||
|
|
||||||
get(_url: string): Socket {
|
get(_url: string): Socket {
|
||||||
const url = normalizeRelayUrl(_url)
|
const url = normalizeRelayUrl(_url)
|
||||||
const oldSocket = this._data.get(url)
|
const item = this._data.get(url)
|
||||||
|
|
||||||
if (oldSocket) {
|
if (item) {
|
||||||
return oldSocket
|
return item.socket
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket = this.makeSocket(url)
|
const socket = this.makeSocket(url)
|
||||||
|
|
||||||
this._data.set(url, socket)
|
this._data.set(url, {socket, auth: new AuthState(socket)})
|
||||||
|
|
||||||
for (const cb of this._subs) {
|
for (const cb of this._subs) {
|
||||||
cb(socket)
|
cb(socket)
|
||||||
@@ -66,6 +72,10 @@ export class Pool {
|
|||||||
return socket
|
return socket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAuth(url: string) {
|
||||||
|
return this._data.get(normalizeRelayUrl(url))?.auth
|
||||||
|
}
|
||||||
|
|
||||||
subscribe(cb: PoolSubscription) {
|
subscribe(cb: PoolSubscription) {
|
||||||
this._subs.push(cb)
|
this._subs.push(cb)
|
||||||
|
|
||||||
@@ -75,10 +85,11 @@ export class Pool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
remove(url: string) {
|
remove(url: string) {
|
||||||
const socket = this._data.get(url)
|
const item = this._data.get(url)
|
||||||
|
|
||||||
if (socket) {
|
if (item) {
|
||||||
socket.cleanup()
|
item.socket.cleanup()
|
||||||
|
item.auth.cleanup()
|
||||||
|
|
||||||
this._data.delete(url)
|
this._data.delete(url)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {on, fromPairs, sleep, yieldThread} from "@welshman/lib"
|
|||||||
import {SignedEvent} from "@welshman/util"
|
import {SignedEvent} from "@welshman/util"
|
||||||
import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
|
import {RelayMessage, ClientMessageType, isRelayOk} from "./message.js"
|
||||||
import {AbstractAdapter, AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
|
import {AbstractAdapter, AdapterEvent, AdapterContext, getAdapter} from "./adapter.js"
|
||||||
import {TypedEmitter} from "./util.js"
|
|
||||||
|
|
||||||
export enum PublishStatus {
|
export enum PublishStatus {
|
||||||
Pending = "publish:status:pending",
|
Pending = "publish:status:pending",
|
||||||
@@ -23,14 +22,6 @@ export enum PublishEvent {
|
|||||||
|
|
||||||
// SinglePublish
|
// SinglePublish
|
||||||
|
|
||||||
export type SinglePublishEvents = {
|
|
||||||
[PublishEvent.Success]: (id: string, detail: string) => void
|
|
||||||
[PublishEvent.Failure]: (id: string, detail: string) => void
|
|
||||||
[PublishEvent.Timeout]: () => void
|
|
||||||
[PublishEvent.Aborted]: () => void
|
|
||||||
[PublishEvent.Complete]: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SinglePublishOptions = {
|
export type SinglePublishOptions = {
|
||||||
event: SignedEvent
|
event: SignedEvent
|
||||||
relay: string
|
relay: string
|
||||||
@@ -38,7 +29,7 @@ export type SinglePublishOptions = {
|
|||||||
timeout?: number
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SinglePublish extends (EventEmitter as new () => TypedEmitter<SinglePublishEvents>) {
|
export class SinglePublish extends EventEmitter {
|
||||||
status = PublishStatus.Pending
|
status = PublishStatus.Pending
|
||||||
|
|
||||||
_unsubscriber: () => void
|
_unsubscriber: () => void
|
||||||
@@ -107,19 +98,11 @@ export class SinglePublish extends (EventEmitter as new () => TypedEmitter<Singl
|
|||||||
|
|
||||||
// MultiPublish
|
// MultiPublish
|
||||||
|
|
||||||
export type MultiPublishEvents = {
|
|
||||||
[PublishEvent.Success]: (id: string, detail: string, url: string) => void
|
|
||||||
[PublishEvent.Failure]: (id: string, detail: string, url: string) => void
|
|
||||||
[PublishEvent.Timeout]: (url: string) => void
|
|
||||||
[PublishEvent.Aborted]: (url: string) => void
|
|
||||||
[PublishEvent.Complete]: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MultiPublishOptions = Omit<SinglePublishOptions, "relay"> & {
|
export type MultiPublishOptions = Omit<SinglePublishOptions, "relay"> & {
|
||||||
relays: string[]
|
relays: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiPublish extends (EventEmitter as new () => TypedEmitter<MultiPublishEvents>) {
|
export class MultiPublish extends EventEmitter {
|
||||||
status: Record<string, PublishStatus>
|
status: Record<string, PublishStatus>
|
||||||
|
|
||||||
_children: SinglePublish[] = []
|
_children: SinglePublish[] = []
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import WebSocket from "isomorphic-ws"
|
|||||||
import EventEmitter from "events"
|
import EventEmitter from "events"
|
||||||
import {TaskQueue} from "@welshman/lib"
|
import {TaskQueue} from "@welshman/lib"
|
||||||
import {RelayMessage, ClientMessage} from "./message.js"
|
import {RelayMessage, ClientMessage} from "./message.js"
|
||||||
import {TypedEmitter} from "./util.js"
|
|
||||||
|
|
||||||
export enum SocketStatus {
|
export enum SocketStatus {
|
||||||
Open = "socket:status:open",
|
Open = "socket:status:open",
|
||||||
@@ -29,7 +28,7 @@ export type SocketEvents = {
|
|||||||
[SocketEvent.Receive]: (message: RelayMessage, url: string) => void
|
[SocketEvent.Receive]: (message: RelayMessage, url: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Socket extends (EventEmitter as new () => TypedEmitter<SocketEvents>) {
|
export class Socket extends EventEmitter {
|
||||||
status = SocketStatus.Closed
|
status = SocketStatus.Closed
|
||||||
|
|
||||||
_ws?: WebSocket
|
_ws?: WebSocket
|
||||||
@@ -57,6 +56,9 @@ export class Socket extends (EventEmitter as new () => TypedEmitter<SocketEvents
|
|||||||
this.on(SocketEvent.Status, (status: SocketStatus) => {
|
this.on(SocketEvent.Status, (status: SocketStatus) => {
|
||||||
this.status = status
|
this.status = status
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this._sendQueue.stop()
|
||||||
|
this.setMaxListeners(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
open = () => {
|
open = () => {
|
||||||
@@ -74,15 +76,16 @@ export class Socket extends (EventEmitter as new () => TypedEmitter<SocketEvents
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._ws.onerror = () => {
|
this._ws.onerror = () => {
|
||||||
this.emit(SocketEvent.Status, SocketStatus.Error, this.url)
|
|
||||||
this._sendQueue.stop()
|
|
||||||
this._ws = undefined
|
this._ws = undefined
|
||||||
|
this._sendQueue.stop()
|
||||||
|
this.emit(SocketEvent.Status, SocketStatus.Error, this.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ws.onclose = () => {
|
this._ws.onclose = () => {
|
||||||
this.emit(SocketEvent.Status, SocketStatus.Closed, this.url)
|
|
||||||
this._sendQueue.stop()
|
|
||||||
this._ws = undefined
|
this._ws = undefined
|
||||||
|
this._sendQueue.stop()
|
||||||
|
console.log("socket closed", this.url)
|
||||||
|
this.emit(SocketEvent.Status, SocketStatus.Closed, this.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ws.onmessage = (event: any) => {
|
this._ws.onmessage = (event: any) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {isRelayUrl} from "@welshman/util"
|
|||||||
import {LocalRelay, LOCAL_RELAY_URL} from "@welshman/relay"
|
import {LocalRelay, LOCAL_RELAY_URL} from "@welshman/relay"
|
||||||
import {RelayMessage, ClientMessage} from "./message.js"
|
import {RelayMessage, ClientMessage} from "./message.js"
|
||||||
import {Socket, SocketEvent} from "./socket.js"
|
import {Socket, SocketEvent} from "./socket.js"
|
||||||
import {TypedEmitter, Unsubscriber} from "./util.js"
|
import {Unsubscriber} from "./util.js"
|
||||||
import {netContext, NetContext} from "./context.js"
|
import {netContext, NetContext} from "./context.js"
|
||||||
|
|
||||||
export enum AdapterEvent {
|
export enum AdapterEvent {
|
||||||
@@ -15,7 +15,7 @@ export type AdapterEvents = {
|
|||||||
[AdapterEvent.Receive]: (message: RelayMessage, url: string) => void
|
[AdapterEvent.Receive]: (message: RelayMessage, url: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AbstractAdapter extends (EventEmitter as new () => TypedEmitter<AdapterEvents>) {
|
export abstract class AbstractAdapter extends EventEmitter {
|
||||||
_unsubscribers: Unsubscriber[] = []
|
_unsubscribers: Unsubscriber[] = []
|
||||||
|
|
||||||
abstract urls: string[]
|
abstract urls: string[]
|
||||||
|
|||||||
+34
-88
@@ -1,10 +1,10 @@
|
|||||||
import EventEmitter from "events"
|
import EventEmitter from "events"
|
||||||
import {on, call, sleep} from "@welshman/lib"
|
import {on, call} from "@welshman/lib"
|
||||||
import type {SignedEvent, StampedEvent} from "@welshman/util"
|
import {SignedEvent, StampedEvent} from "@welshman/util"
|
||||||
import {makeEvent, CLIENT_AUTH} from "@welshman/util"
|
import {makeEvent, CLIENT_AUTH} from "@welshman/util"
|
||||||
import {isRelayAuth, isClientAuth, isRelayOk, RelayMessage} from "./message.js"
|
import {isRelayAuth, isClientAuth, isRelayOk, RelayMessage} from "./message.js"
|
||||||
import {Socket, SocketStatus, SocketEvent} from "./socket.js"
|
import {Socket, SocketStatus, SocketEvent} from "./socket.js"
|
||||||
import {TypedEmitter, Unsubscriber} from "./util.js"
|
import {Unsubscriber} from "./util.js"
|
||||||
|
|
||||||
export const makeAuthEvent = (url: string, challenge: string) =>
|
export const makeAuthEvent = (url: string, challenge: string) =>
|
||||||
makeEvent(CLIENT_AUTH, {
|
makeEvent(CLIENT_AUTH, {
|
||||||
@@ -37,7 +37,7 @@ export type AuthStateEvents = {
|
|||||||
[AuthStateEvent.Status]: (status: AuthStatus) => void
|
[AuthStateEvent.Status]: (status: AuthStatus) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthState extends (EventEmitter as new () => TypedEmitter<AuthStateEvents>) {
|
export class AuthState extends EventEmitter {
|
||||||
challenge: string | undefined
|
challenge: string | undefined
|
||||||
request: string | undefined
|
request: string | undefined
|
||||||
details: string | undefined
|
details: string | undefined
|
||||||
@@ -52,6 +52,8 @@ export class AuthState extends (EventEmitter as new () => TypedEmitter<AuthState
|
|||||||
if (isRelayOk(message)) {
|
if (isRelayOk(message)) {
|
||||||
const [_, id, ok, details] = message
|
const [_, id, ok, details] = message
|
||||||
|
|
||||||
|
console.log("ok", message)
|
||||||
|
|
||||||
if (id === this.request) {
|
if (id === this.request) {
|
||||||
this.details = details
|
this.details = details
|
||||||
|
|
||||||
@@ -66,6 +68,8 @@ export class AuthState extends (EventEmitter as new () => TypedEmitter<AuthState
|
|||||||
if (isRelayAuth(message)) {
|
if (isRelayAuth(message)) {
|
||||||
const [_, challenge] = message
|
const [_, challenge] = message
|
||||||
|
|
||||||
|
console.log("relay auth", message)
|
||||||
|
|
||||||
this.challenge = challenge
|
this.challenge = challenge
|
||||||
this.request = undefined
|
this.request = undefined
|
||||||
this.details = undefined
|
this.details = undefined
|
||||||
@@ -74,11 +78,13 @@ export class AuthState extends (EventEmitter as new () => TypedEmitter<AuthState
|
|||||||
}),
|
}),
|
||||||
on(socket, SocketEvent.Enqueue, (message: RelayMessage) => {
|
on(socket, SocketEvent.Enqueue, (message: RelayMessage) => {
|
||||||
if (isClientAuth(message)) {
|
if (isClientAuth(message)) {
|
||||||
|
console.log("client auth", message)
|
||||||
this.setStatus(AuthStatus.PendingResponse)
|
this.setStatus(AuthStatus.PendingResponse)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
on(socket, SocketEvent.Status, (status: SocketStatus) => {
|
on(socket, SocketEvent.Status, (status: SocketStatus) => {
|
||||||
if (status === SocketStatus.Closed) {
|
if (status === SocketStatus.Closed) {
|
||||||
|
console.log("closed")
|
||||||
this.challenge = undefined
|
this.challenge = undefined
|
||||||
this.request = undefined
|
this.request = undefined
|
||||||
this.details = undefined
|
this.details = undefined
|
||||||
@@ -93,92 +99,32 @@ export class AuthState extends (EventEmitter as new () => TypedEmitter<AuthState
|
|||||||
this.emit(AuthStateEvent.Status, status)
|
this.emit(AuthStateEvent.Status, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async authenticate(sign: (event: StampedEvent) => Promise<SignedEvent>) {
|
||||||
|
if (!this.challenge) {
|
||||||
|
throw new Error("Attempted to authenticate with no challenge")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.status !== AuthStatus.Requested) {
|
||||||
|
throw new Error(`Attempted to authenticate when auth is already ${this.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setStatus(AuthStatus.PendingSignature)
|
||||||
|
|
||||||
|
const template = makeAuthEvent(this.socket.url, this.challenge)
|
||||||
|
const event = await sign(template)
|
||||||
|
|
||||||
|
console.log(event)
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
this.request = event.id
|
||||||
|
this.socket.send(["AUTH", event])
|
||||||
|
} else {
|
||||||
|
this.setStatus(AuthStatus.DeniedSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
this.removeAllListeners()
|
this.removeAllListeners()
|
||||||
this._unsubscribers.forEach(call)
|
this._unsubscribers.forEach(call)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthManagerOptions = {
|
|
||||||
sign: (event: StampedEvent) => Promise<SignedEvent>
|
|
||||||
eager?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthManager {
|
|
||||||
state: AuthState
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
readonly socket: Socket,
|
|
||||||
readonly options: AuthManagerOptions,
|
|
||||||
) {
|
|
||||||
this.state = new AuthState(socket)
|
|
||||||
this.state.on(AuthStateEvent.Status, (status: string) => {
|
|
||||||
if (status === AuthStatus.Requested && options.eager) {
|
|
||||||
this.respond()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitFor(condition: () => boolean, timeout = 300) {
|
|
||||||
const start = Date.now()
|
|
||||||
|
|
||||||
while (Date.now() - timeout <= start) {
|
|
||||||
if (condition()) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(Math.min(100, Math.ceil(timeout / 3)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForChallenge(timeout = 300) {
|
|
||||||
await this.waitFor(() => Boolean(this.state.challenge), timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForResolution(timeout = 300) {
|
|
||||||
await this.waitFor(
|
|
||||||
() =>
|
|
||||||
[AuthStatus.None, AuthStatus.DeniedSignature, AuthStatus.Forbidden, AuthStatus.Ok].includes(
|
|
||||||
this.state.status,
|
|
||||||
),
|
|
||||||
timeout,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async attempt(timeout = 300) {
|
|
||||||
await this.socket.attemptToOpen()
|
|
||||||
await this.waitForChallenge(Math.ceil(timeout / 2))
|
|
||||||
|
|
||||||
if (this.state.status === AuthStatus.Requested) {
|
|
||||||
await this.respond()
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.waitForResolution(Math.ceil(timeout / 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
async respond() {
|
|
||||||
if (!this.state.challenge) {
|
|
||||||
throw new Error("Attempted to authenticate with no challenge")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.state.status !== AuthStatus.Requested) {
|
|
||||||
throw new Error(`Attempted to authenticate when auth is already ${this.state.status}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.setStatus(AuthStatus.PendingSignature)
|
|
||||||
|
|
||||||
const template = makeAuthEvent(this.socket.url, this.state.challenge)
|
|
||||||
const event = await this.options.sign(template)
|
|
||||||
|
|
||||||
if (event) {
|
|
||||||
this.state.request = event.id
|
|
||||||
this.socket.send(["AUTH", event])
|
|
||||||
} else {
|
|
||||||
this.state.setStatus(AuthStatus.DeniedSignature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
this.state.cleanup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {EventEmitter} from "events"
|
import {EventEmitter} from "events"
|
||||||
import {on, sleep, randomId, groupBy, pushToMapKey, inc, flatten, chunk} from "@welshman/lib"
|
import {on, sleep, randomId, groupBy, pushToMapKey, inc, flatten, chunk} from "@welshman/lib"
|
||||||
import {SignedEvent, Filter} from "@welshman/util"
|
import {SignedEvent, Filter} from "@welshman/util"
|
||||||
import {TypedEmitter} from "./util.js"
|
|
||||||
import {
|
import {
|
||||||
RelayMessage,
|
RelayMessage,
|
||||||
isRelayNegErr,
|
isRelayNegErr,
|
||||||
@@ -33,7 +32,7 @@ export type DifferenceOptions = {
|
|||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Difference extends (EventEmitter as new () => TypedEmitter<DifferenceEvents>) {
|
export class Difference extends EventEmitter {
|
||||||
have = new Set<string>()
|
have = new Set<string>()
|
||||||
need = new Set<string>()
|
need = new Set<string>()
|
||||||
|
|
||||||
@@ -139,7 +138,6 @@ export const diff = async ({relays, filters, ...options}: DiffOptions) => {
|
|||||||
|
|
||||||
diff.on(DifferenceEvent.Close, () => {
|
diff.on(DifferenceEvent.Close, () => {
|
||||||
resolve({relay, have: diff.have, need: diff.need})
|
resolve({relay, have: diff.have, need: diff.need})
|
||||||
diff.close()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
diff.on(DifferenceEvent.Error, (url, message) => {
|
diff.on(DifferenceEvent.Error, (url, message) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {on, call, sleep, spec, ago, now} from "@welshman/lib"
|
import {on, always, call, sleep, spec, ago, now} from "@welshman/lib"
|
||||||
import {AUTH_JOIN} from "@welshman/util"
|
import {AUTH_JOIN, StampedEvent, SignedEvent} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
isClientAuth,
|
isClientAuth,
|
||||||
@@ -134,7 +134,6 @@ export const socketPolicyRetryAuthRequired = (socket: Socket) => {
|
|||||||
*/
|
*/
|
||||||
export const socketPolicyConnectOnSend = (socket: Socket) => {
|
export const socketPolicyConnectOnSend = (socket: Socket) => {
|
||||||
let lastError = 0
|
let lastError = 0
|
||||||
let currentStatus = SocketStatus.Closed
|
|
||||||
|
|
||||||
const unsubscribers = [
|
const unsubscribers = [
|
||||||
on(socket, SocketEvent.Status, (newStatus: SocketStatus) => {
|
on(socket, SocketEvent.Status, (newStatus: SocketStatus) => {
|
||||||
@@ -142,13 +141,10 @@ export const socketPolicyConnectOnSend = (socket: Socket) => {
|
|||||||
if (newStatus === SocketStatus.Error) {
|
if (newStatus === SocketStatus.Error) {
|
||||||
lastError = now()
|
lastError = now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep track of the current status
|
|
||||||
currentStatus = newStatus
|
|
||||||
}),
|
}),
|
||||||
on(socket, SocketEvent.Enqueue, (message: ClientMessage) => {
|
on(socket, SocketEvent.Enqueue, (message: ClientMessage) => {
|
||||||
// When a new message is sent, make sure the socket is open (unless there was a recent error)
|
// When a new message is sent, make sure the socket is open (unless there was a recent error)
|
||||||
if (currentStatus === SocketStatus.Closed && lastError < ago(30)) {
|
if (socket.status === SocketStatus.Closed && lastError < ago(30)) {
|
||||||
socket.open()
|
socket.open()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -235,6 +231,34 @@ export const socketPolicyReopenActive = (socket: Socket) => {
|
|||||||
return () => unsubscribers.forEach(call)
|
return () => unsubscribers.forEach(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SocketPolicyAuthOptions = {
|
||||||
|
sign: (event: StampedEvent) => Promise<SignedEvent>
|
||||||
|
shouldAuth?: (socket: Socket) => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function for a policy which may authenticate the socket
|
||||||
|
* @param options - SocketPolicyAuthOptions object
|
||||||
|
* @return a socket policy
|
||||||
|
*/
|
||||||
|
export const makeSocketPolicyAuth = (options: SocketPolicyAuthOptions) => (socket: Socket) => {
|
||||||
|
const authState = new AuthState(socket)
|
||||||
|
const shouldAuth = options.shouldAuth || always(true)
|
||||||
|
|
||||||
|
const unsubscribers = [
|
||||||
|
on(authState, AuthStateEvent.Status, (status: AuthStatus) => {
|
||||||
|
if (status === AuthStatus.Requested && shouldAuth(socket)) {
|
||||||
|
authState.authenticate(options.sign)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribers.forEach(call)
|
||||||
|
authState.cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const defaultSocketPolicies = [
|
export const defaultSocketPolicies = [
|
||||||
socketPolicyDeferOnAuth,
|
socketPolicyDeferOnAuth,
|
||||||
socketPolicyRetryAuthRequired,
|
socketPolicyRetryAuthRequired,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js"
|
import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js"
|
||||||
import {getAdapter, AdapterContext, AbstractAdapter, AdapterEvent} from "./adapter.js"
|
import {getAdapter, AdapterContext, AbstractAdapter, AdapterEvent} from "./adapter.js"
|
||||||
import {SocketEvent, SocketStatus} from "./socket.js"
|
import {SocketEvent, SocketStatus} from "./socket.js"
|
||||||
import {TypedEmitter, Unsubscriber} from "./util.js"
|
import {Unsubscriber} from "./util.js"
|
||||||
import {netContext} from "./context.js"
|
import {netContext} from "./context.js"
|
||||||
import {Tracker} from "./tracker.js"
|
import {Tracker} from "./tracker.js"
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ export type SingleRequestEvents = {
|
|||||||
export type SingleRequestOptions = {
|
export type SingleRequestOptions = {
|
||||||
relay: string
|
relay: string
|
||||||
filters: Filter[]
|
filters: Filter[]
|
||||||
|
signal?: AbortSignal
|
||||||
context?: AdapterContext
|
context?: AdapterContext
|
||||||
timeout?: number
|
timeout?: number
|
||||||
tracker?: Tracker
|
tracker?: Tracker
|
||||||
@@ -49,10 +50,7 @@ export type SingleRequestOptions = {
|
|||||||
isEventDeleted?: (event: TrustedEvent, url: string) => boolean
|
isEventDeleted?: (event: TrustedEvent, url: string) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for typescript to infer emitter methods
|
export class SingleRequest extends EventEmitter {
|
||||||
export interface SingleRequest extends TypedEmitter<SingleRequestEvents> {}
|
|
||||||
|
|
||||||
export class SingleRequest extends (EventEmitter as new () => TypedEmitter<SingleRequestEvents>) {
|
|
||||||
_ids = new Set<string>()
|
_ids = new Set<string>()
|
||||||
_eose = new Set<string>()
|
_eose = new Set<string>()
|
||||||
_unsubscribers: Unsubscriber[] = []
|
_unsubscribers: Unsubscriber[] = []
|
||||||
@@ -128,6 +126,9 @@ export class SingleRequest extends (EventEmitter as new () => TypedEmitter<Singl
|
|||||||
setTimeout(() => this.close(), this.options.timeout || 10000)
|
setTimeout(() => this.close(), this.options.timeout || 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle abort signal
|
||||||
|
this.options.signal?.addEventListener("abort", () => this.close())
|
||||||
|
|
||||||
// Start asynchronously so the caller can set up listeners
|
// Start asynchronously so the caller can set up listeners
|
||||||
yieldThread().then(() => {
|
yieldThread().then(() => {
|
||||||
for (const filter of this.options.filters) {
|
for (const filter of this.options.filters) {
|
||||||
@@ -171,10 +172,7 @@ export type MultiRequestOptions = Omit<SingleRequestOptions, "relay"> & {
|
|||||||
relays: string[]
|
relays: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needed for typescript to infer emitter methods
|
export class MultiRequest extends EventEmitter {
|
||||||
export interface MultiRequest extends TypedEmitter<MultiRequestEvents> {}
|
|
||||||
|
|
||||||
export class MultiRequest extends (EventEmitter as new () => TypedEmitter<MultiRequestEvents>) {
|
|
||||||
_children: SingleRequest[] = []
|
_children: SingleRequest[] = []
|
||||||
_closed = new Set<string>()
|
_closed = new Set<string>()
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1 @@
|
|||||||
import TypedEventEmitter, {EventMap} from "typed-emitter"
|
|
||||||
|
|
||||||
export type TypedEmitter<T extends EventMap> = TypedEventEmitter.default<T>
|
|
||||||
|
|
||||||
export type Unsubscriber = () => void
|
export type Unsubscriber = () => void
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {verifiedSymbol, verifyEvent as verifyEventPure} from "nostr-tools/pure"
|
import {verifiedSymbol, verifyEvent as verifyEventPure} from "nostr-tools/pure"
|
||||||
import {setNostrWasm, verifyEvent as verifyEventWasm} from "nostr-tools/wasm"
|
import {setNostrWasm, verifyEvent as verifyEventWasm} from "nostr-tools/wasm"
|
||||||
import {initNostrWasm} from "nostr-wasm"
|
import {initNostrWasm} from "nostr-wasm"
|
||||||
import {mapVals, first, pick, now} from "@welshman/lib"
|
import {mapVals, noop, first, pick, now} from "@welshman/lib"
|
||||||
import {getReplyTagValues, getCommentTagValues} from "./Tags.js"
|
import {getReplyTagValues, getCommentTagValues} from "./Tags.js"
|
||||||
import {getAddress, Address} from "./Address.js"
|
import {getAddress, Address} from "./Address.js"
|
||||||
import {
|
import {
|
||||||
@@ -66,7 +66,7 @@ export const verifyEvent = (() => {
|
|||||||
|
|
||||||
if (typeof WebAssembly === "object") {
|
if (typeof WebAssembly === "object") {
|
||||||
initNostrWasm()
|
initNostrWasm()
|
||||||
.then(setNostrWasm)
|
.then(setNostrWasm, noop)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
verify = verifyEventWasm
|
verify = verifyEventWasm
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user