Re-work connection auth
This commit is contained in:
@@ -2,10 +2,10 @@ import {partition} from "@welshman/lib"
|
|||||||
import {defaultOptimizeSubscriptions, getDefaultNetContext as originalGetDefaultNetContext} from "@welshman/net"
|
import {defaultOptimizeSubscriptions, getDefaultNetContext as originalGetDefaultNetContext} from "@welshman/net"
|
||||||
import type {Subscription, RelaysAndFilters, NetContext} from "@welshman/net"
|
import type {Subscription, RelaysAndFilters, NetContext} from "@welshman/net"
|
||||||
import {WRAP, unionFilters, isSignedEvent, hasValidSignature} from "@welshman/util"
|
import {WRAP, unionFilters, isSignedEvent, hasValidSignature} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent, StampedEvent} from "@welshman/util"
|
||||||
import {tracker, repository} from './core'
|
import {tracker, repository} from './core'
|
||||||
import {makeRouter, getFilterSelections} from './router'
|
import {makeRouter, getFilterSelections} from './router'
|
||||||
import {onAuth, getSession} from './session'
|
import {getSession, signer} from './session'
|
||||||
import type {Router} from './router'
|
import type {Router} from './router'
|
||||||
import {loadProfile} from './profiles'
|
import {loadProfile} from './profiles'
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export type AppContext = {
|
|||||||
|
|
||||||
export const getDefaultNetContext = (overrides: Partial<NetContext> = {}) => ({
|
export const getDefaultNetContext = (overrides: Partial<NetContext> = {}) => ({
|
||||||
...originalGetDefaultNetContext(),
|
...originalGetDefaultNetContext(),
|
||||||
onAuth: onAuth,
|
signEvent: (event: StampedEvent) => signer.get()?.sign(event),
|
||||||
onEvent: (url: string, event: TrustedEvent) => {
|
onEvent: (url: string, event: TrustedEvent) => {
|
||||||
tracker.track(event.id, url)
|
tracker.track(event.id, url)
|
||||||
repository.publish(event)
|
repository.publish(event)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {withGetter} from '@welshman/store'
|
|||||||
import {ctx, groupBy, indexBy, batch, now, uniq, batcher, postJson} from '@welshman/lib'
|
import {ctx, groupBy, indexBy, batch, now, uniq, batcher, postJson} from '@welshman/lib'
|
||||||
import type {RelayProfile} from "@welshman/util"
|
import type {RelayProfile} from "@welshman/util"
|
||||||
import {normalizeRelayUrl, displayRelayUrl, displayRelayProfile} from "@welshman/util"
|
import {normalizeRelayUrl, displayRelayUrl, displayRelayProfile} from "@welshman/util"
|
||||||
import {AuthStatus, asMessage, type Connection, type SocketMessage} from '@welshman/net'
|
import {asMessage, type Connection, type SocketMessage} from '@welshman/net'
|
||||||
import {collection} from './collection'
|
import {collection} from './collection'
|
||||||
|
|
||||||
export type RelayStats = {
|
export type RelayStats = {
|
||||||
@@ -13,7 +13,6 @@ export type RelayStats = {
|
|||||||
publish_count: number
|
publish_count: number
|
||||||
connect_count: number
|
connect_count: number
|
||||||
recent_errors: number[]
|
recent_errors: number[]
|
||||||
last_auth_status: AuthStatus
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Relays
|
// Relays
|
||||||
@@ -25,7 +24,6 @@ export const makeRelayStats = (): RelayStats => ({
|
|||||||
publish_count: 0,
|
publish_count: 0,
|
||||||
connect_count: 0,
|
connect_count: 0,
|
||||||
recent_errors: [],
|
recent_errors: [],
|
||||||
last_auth_status: AuthStatus.Pending,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Relay = {
|
export type Relay = {
|
||||||
@@ -136,14 +134,6 @@ const onConnectionReceive = ({url}: Connection, socketMessage: SocketMessage) =>
|
|||||||
|
|
||||||
if (verb === 'EVENT') {
|
if (verb === 'EVENT') {
|
||||||
updateRelayStats([url, stats => ++stats.event_count])
|
updateRelayStats([url, stats => ++stats.event_count])
|
||||||
} else if (verb === 'OK') {
|
|
||||||
updateRelayStats([url, stats => {
|
|
||||||
stats.last_auth_status = AuthStatus.Ok
|
|
||||||
}])
|
|
||||||
} else if (verb === 'AUTH') {
|
|
||||||
updateRelayStats([url, stats => {
|
|
||||||
stats.last_auth_status = AuthStatus.Unauthorized
|
|
||||||
}])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS, LOCAL_RELAY_URL, WRAP,
|
PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS, LOCAL_RELAY_URL, WRAP,
|
||||||
} from '@welshman/util'
|
} from '@welshman/util'
|
||||||
import type {TrustedEvent, Filter} from '@welshman/util'
|
import type {TrustedEvent, Filter} from '@welshman/util'
|
||||||
import {ConnectionStatus} from '@welshman/net'
|
import {ConnectionStatus, AuthStatus} from '@welshman/net'
|
||||||
import type {RelaysAndFilters} from '@welshman/net'
|
import type {RelaysAndFilters} from '@welshman/net'
|
||||||
import {pubkey} from './session'
|
import {pubkey} from './session'
|
||||||
import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, getReadRelayUrls, getWriteRelayUrls, getRelayUrls} from './relaySelections'
|
import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, getReadRelayUrls, getWriteRelayUrls, getRelayUrls} from './relaySelections'
|
||||||
@@ -415,15 +415,21 @@ export const getRelayQuality = (url: string) => {
|
|||||||
return Math.max(0, Math.min(0.5, (now() - oneMinute - lastFault) / oneHour))
|
return Math.max(0, Math.min(0.5, (now() - oneMinute - lastFault) / oneHour))
|
||||||
}
|
}
|
||||||
|
|
||||||
return switcher(connection.meta.getStatus(), {
|
const authScore = switcher(connection.auth.status, {
|
||||||
[ConnectionStatus.Unauthorized]: 0.5,
|
[AuthStatus.Forbidden]: 0,
|
||||||
[ConnectionStatus.Forbidden]: 0,
|
[AuthStatus.Ok]: 1,
|
||||||
|
default: 0.5,
|
||||||
|
})
|
||||||
|
|
||||||
|
const connectionScore = switcher(connection.meta.getStatus(), {
|
||||||
[ConnectionStatus.Error]: 0,
|
[ConnectionStatus.Error]: 0,
|
||||||
[ConnectionStatus.Closed]: 0.6,
|
[ConnectionStatus.Closed]: 0.6,
|
||||||
[ConnectionStatus.Slow]: 0.5,
|
[ConnectionStatus.Slow]: 0.5,
|
||||||
[ConnectionStatus.Ok]: 1,
|
[ConnectionStatus.Ok]: 1,
|
||||||
default: clamp([0.5, 1], connect_count / 1000),
|
default: clamp([0.5, 1], connect_count / 1000),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return authScore * connectionScore
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getPubkeyRelays = (pubkey: string, mode?: string) => {
|
export const getPubkeyRelays = (pubkey: string, mode?: string) => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import {ctx, memoize, omit, equals, assoc} from "@welshman/lib"
|
import {memoize, omit, equals, assoc} from "@welshman/lib"
|
||||||
import {createEvent} from "@welshman/util"
|
|
||||||
import {withGetter, synced} from "@welshman/store"
|
import {withGetter, synced} from "@welshman/store"
|
||||||
import {type Nip46Handler} from "@welshman/signer"
|
import {type Nip46Handler} from "@welshman/signer"
|
||||||
import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer, Nip55Signer} from "@welshman/signer"
|
import {Nip46Broker, Nip46Signer, Nip07Signer, Nip01Signer, Nip55Signer} from "@welshman/signer"
|
||||||
@@ -60,29 +59,6 @@ export const getSigner = memoize((session: Session) => {
|
|||||||
|
|
||||||
export const signer = withGetter(derived(session, getSigner))
|
export const signer = withGetter(derived(session, getSigner))
|
||||||
|
|
||||||
export const authChallenges = new Set()
|
|
||||||
|
|
||||||
export const onAuth = async (url: string, challenge: string) => {
|
|
||||||
if (authChallenges.has(challenge) || !signer.get()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authChallenges.add(challenge)
|
|
||||||
|
|
||||||
const event = await signer.get()!.sign(
|
|
||||||
createEvent(22242, {
|
|
||||||
tags: [
|
|
||||||
["relay", url],
|
|
||||||
["challenge", challenge],
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx.net.pool.get(url).send(["AUTH", event])
|
|
||||||
|
|
||||||
return event
|
|
||||||
}
|
|
||||||
|
|
||||||
export const nip44EncryptToSelf = (payload: string) => {
|
export const nip44EncryptToSelf = (payload: string) => {
|
||||||
const $pubkey = pubkey.get()
|
const $pubkey = pubkey.get()
|
||||||
const $signer = signer.get()
|
const $signer = signer.get()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import {ctx, Emitter, Worker, sleep} from '@welshman/lib'
|
import {Emitter, Worker, sleep} from '@welshman/lib'
|
||||||
import {AuthStatus, ConnectionMeta} from './ConnectionMeta'
|
import {ConnectionMeta} from './ConnectionMeta'
|
||||||
|
import {ConnectionAuth, AuthStatus} from './ConnectionAuth'
|
||||||
import {Socket, isMessage, asMessage} from './Socket'
|
import {Socket, isMessage, asMessage} from './Socket'
|
||||||
import type {SocketMessage, Message} from './Socket'
|
import type {SocketMessage} from './Socket'
|
||||||
|
|
||||||
export class Connection extends Emitter {
|
export class Connection extends Emitter {
|
||||||
url: string
|
url: string
|
||||||
@@ -9,6 +10,7 @@ export class Connection extends Emitter {
|
|||||||
sender: Worker<SocketMessage>
|
sender: Worker<SocketMessage>
|
||||||
receiver: Worker<SocketMessage>
|
receiver: Worker<SocketMessage>
|
||||||
meta: ConnectionMeta
|
meta: ConnectionMeta
|
||||||
|
auth: ConnectionAuth
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
super()
|
super()
|
||||||
@@ -18,6 +20,7 @@ export class Connection extends Emitter {
|
|||||||
this.sender = this.createSender()
|
this.sender = this.createSender()
|
||||||
this.receiver = this.createReceiver()
|
this.receiver = this.createReceiver()
|
||||||
this.meta = new ConnectionMeta(this)
|
this.meta = new ConnectionMeta(this)
|
||||||
|
this.auth = new ConnectionAuth(this)
|
||||||
this.setMaxListeners(100)
|
this.setMaxListeners(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +48,7 @@ export class Connection extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only defer for auth if we're not multiplexing
|
// Only defer for auth if we're not multiplexing
|
||||||
if (isMessage(message) && ![AuthStatus.Ok, AuthStatus.Pending].includes(this.meta.authStatus)) {
|
if (isMessage(message) && ![AuthStatus.None, AuthStatus.Ok].includes(this.auth.status)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,14 +96,6 @@ export class Connection extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReceive = (message: SocketMessage) => {
|
onReceive = (message: SocketMessage) => {
|
||||||
const [verb, ...extra] = asMessage(message)
|
|
||||||
|
|
||||||
if (verb === 'AUTH') {
|
|
||||||
const [challenge] = extra
|
|
||||||
|
|
||||||
ctx.net.onAuth(this.url, challenge)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('receive', this, message)
|
this.emit('receive', this, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,26 +116,6 @@ export class Connection extends Emitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureAuth = async ({timeout = 3000} = {}) => {
|
|
||||||
await this.ensureConnected()
|
|
||||||
|
|
||||||
if ([AuthStatus.Unauthorized, AuthStatus.Pending].includes(this.meta.authStatus)) {
|
|
||||||
await Promise.race([
|
|
||||||
sleep(timeout),
|
|
||||||
new Promise<void>(resolve => {
|
|
||||||
const onReceive = (cxn: Connection, message: Message) => {
|
|
||||||
if (message[0] === 'OK' && message[2]) {
|
|
||||||
this.off('receive', onReceive)
|
|
||||||
resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.on('receive', onReceive)
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
this.socket.disconnect()
|
this.socket.disconnect()
|
||||||
this.sender.clear()
|
this.sender.clear()
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import {ctx, sleep} from '@welshman/lib'
|
||||||
|
import {CLIENT_AUTH, createEvent} from '@welshman/util'
|
||||||
|
import type {Connection} from './Connection'
|
||||||
|
import type {SocketMessage} from './Socket'
|
||||||
|
import {asMessage} from './Socket'
|
||||||
|
|
||||||
|
export enum AuthMode {
|
||||||
|
Implicit = 'implicit',
|
||||||
|
Explicit = 'explicit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AuthStatus {
|
||||||
|
None = 'none',
|
||||||
|
Requested = 'requested',
|
||||||
|
PendingSignature = 'pending_signature',
|
||||||
|
DeniedSignature = 'denied_signature',
|
||||||
|
PendingResponse = 'pending_response',
|
||||||
|
Forbidden = 'forbidden',
|
||||||
|
Ok = 'ok',
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
None,
|
||||||
|
Requested,
|
||||||
|
PendingSignature,
|
||||||
|
DeniedSignature,
|
||||||
|
PendingResponse,
|
||||||
|
Forbidden,
|
||||||
|
Ok,
|
||||||
|
} = AuthStatus
|
||||||
|
|
||||||
|
export class ConnectionAuth {
|
||||||
|
challenge: string | undefined
|
||||||
|
request: string | undefined
|
||||||
|
message: string | undefined
|
||||||
|
status = None
|
||||||
|
|
||||||
|
constructor(readonly connection: Connection) {
|
||||||
|
this.connection.on('receive', this.#onReceive)
|
||||||
|
}
|
||||||
|
|
||||||
|
#onReceive = (connection: Connection, message: SocketMessage) => {
|
||||||
|
const [verb, ...extra] = asMessage(message)
|
||||||
|
|
||||||
|
if (verb === 'OK') {
|
||||||
|
const [id, ok, message] = extra
|
||||||
|
|
||||||
|
if (id === this.request) {
|
||||||
|
this.challenge = undefined
|
||||||
|
this.request = undefined
|
||||||
|
this.message = message
|
||||||
|
this.status = ok ? Ok : Forbidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verb === 'AUTH' && extra[0] !== this.challenge) {
|
||||||
|
this.challenge = extra[0]
|
||||||
|
this.request = undefined
|
||||||
|
this.message = undefined
|
||||||
|
this.status = Requested
|
||||||
|
|
||||||
|
if (ctx.net.authMode === AuthMode.Implicit) {
|
||||||
|
this.attempt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt = async () => {
|
||||||
|
if (!this.challenge) {
|
||||||
|
throw new Error("Attempted to authenticate with no challenge")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.status !== Requested) {
|
||||||
|
throw new Error(`Attempted to authenticate when auth is already ${this.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = PendingSignature
|
||||||
|
|
||||||
|
const template = createEvent(CLIENT_AUTH, {
|
||||||
|
tags: [
|
||||||
|
["relay", this.connection.url],
|
||||||
|
["challenge", this.challenge],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
const [event] = await Promise.all([
|
||||||
|
ctx.net.signEvent(template),
|
||||||
|
this.connection.ensureConnected(),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
this.request = event.id
|
||||||
|
this.connection.send(['AUTH', event])
|
||||||
|
this.status = PendingResponse
|
||||||
|
} else {
|
||||||
|
this.status = DeniedSignature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attemptIfRequested = async () => {
|
||||||
|
if (this.status === Requested) {
|
||||||
|
await this.attempt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wait = async ({timeout = 3000}: {timeout?: number} = {}) => {
|
||||||
|
const deadline = Date.now() + timeout
|
||||||
|
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
await sleep(100)
|
||||||
|
|
||||||
|
if ([None, Requested].includes(this.status)) {
|
||||||
|
throw new Error("Auth flow reset while waiting for auth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([DeniedSignature, Forbidden, Ok].includes(this.status)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitIfPending = async ({timeout = 3000}: {timeout?: number} = {}) => {
|
||||||
|
if ([PendingSignature, PendingResponse].includes(this.status)) {
|
||||||
|
await this.wait({timeout})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy = () => {
|
||||||
|
this.connection.off('recieve', this.#onReceive)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,16 +13,7 @@ export type RequestMeta = {
|
|||||||
eoseReceived: boolean
|
eoseReceived: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AuthStatus {
|
|
||||||
Ok = 'ok',
|
|
||||||
Pending = 'pending',
|
|
||||||
Unauthorized = 'unauthorized',
|
|
||||||
Forbidden = 'forbidden',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ConnectionStatus {
|
export enum ConnectionStatus {
|
||||||
Unauthorized = 'unauthorized',
|
|
||||||
Forbidden = 'forbidden',
|
|
||||||
Error = 'error',
|
Error = 'error',
|
||||||
Closed = 'closed',
|
Closed = 'closed',
|
||||||
Slow = 'slow',
|
Slow = 'slow',
|
||||||
@@ -30,7 +21,6 @@ export enum ConnectionStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ConnectionMeta {
|
export class ConnectionMeta {
|
||||||
authStatus = AuthStatus.Pending
|
|
||||||
pendingPublishes = new Map<string, PublishMeta>()
|
pendingPublishes = new Map<string, PublishMeta>()
|
||||||
pendingRequests = new Map<string, RequestMeta>()
|
pendingRequests = new Map<string, RequestMeta>()
|
||||||
publishCount = 0
|
publishCount = 0
|
||||||
@@ -96,12 +86,8 @@ export class ConnectionMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReceiveOk([verb, eventId, ok, notice]: Message) {
|
onReceiveOk([verb, eventId, ok, notice]: Message) {
|
||||||
const publish = this.pendingPublishes.get(eventId)
|
// Re-enqueue pending events when auth challenge is received
|
||||||
|
if (notice?.startsWith('auth-required:')) {
|
||||||
if (ok) {
|
|
||||||
this.authStatus = AuthStatus.Ok
|
|
||||||
} else if (notice?.startsWith('auth-required:')) {
|
|
||||||
// Re-enqueue pending events when auth challenge is received
|
|
||||||
const pub = this.pendingPublishes.get(eventId)
|
const pub = this.pendingPublishes.get(eventId)
|
||||||
|
|
||||||
if (pub) {
|
if (pub) {
|
||||||
@@ -109,6 +95,8 @@ export class ConnectionMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const publish = this.pendingPublishes.get(eventId)
|
||||||
|
|
||||||
if (publish) {
|
if (publish) {
|
||||||
this.responseCount++
|
this.responseCount++
|
||||||
this.responseTimer += Date.now() - publish.sent
|
this.responseTimer += Date.now() - publish.sent
|
||||||
@@ -116,8 +104,7 @@ export class ConnectionMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onReceiveAuth([verb, eventId]: Message) {
|
onReceiveAuth(message: Message) {
|
||||||
this.authStatus = AuthStatus.Unauthorized
|
|
||||||
this.lastAuth = Date.now()
|
this.lastAuth = Date.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +126,8 @@ export class ConnectionMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onReceiveClosed([verb, id, notice]: Message) {
|
onReceiveClosed([verb, id, notice]: Message) {
|
||||||
if (notice.startsWith('auth-required:')) {
|
// Re-enqueue pending reqs when auth challenge is received
|
||||||
// Re-enqueue pending reqs when auth challenge is received
|
if (notice?.startsWith('auth-required:')) {
|
||||||
const req = this.pendingRequests.get(id)
|
const req = this.pendingRequests.get(id)
|
||||||
|
|
||||||
if (req) {
|
if (req) {
|
||||||
@@ -163,24 +150,11 @@ export class ConnectionMeta {
|
|||||||
getStatus = () => {
|
getStatus = () => {
|
||||||
const socket = this.cxn.socket
|
const socket = this.cxn.socket
|
||||||
|
|
||||||
if (this.authStatus === AuthStatus.Unauthorized) return ConnectionStatus.Unauthorized
|
|
||||||
if (this.authStatus === AuthStatus.Forbidden) return ConnectionStatus.Forbidden
|
|
||||||
if (socket.isNew()) return ConnectionStatus.Closed
|
if (socket.isNew()) return ConnectionStatus.Closed
|
||||||
if (this.lastFault && this.lastFault > this.lastOpen) return ConnectionStatus.Error
|
if (this.lastFault && this.lastFault > this.lastOpen) return ConnectionStatus.Error
|
||||||
if (socket.isClosed() || socket.isClosing()) return ConnectionStatus.Closed
|
if (socket.isClosed() || socket.isClosing()) return ConnectionStatus.Closed
|
||||||
if (this.getSpeed() > 1000) return ConnectionStatus.Slow
|
if (this.getSpeed() > 2000) return ConnectionStatus.Slow
|
||||||
|
|
||||||
return ConnectionStatus.Ok
|
return ConnectionStatus.Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription = () => {
|
|
||||||
switch (this.getStatus()) {
|
|
||||||
case ConnectionStatus.Unauthorized: return 'Logging in'
|
|
||||||
case ConnectionStatus.Forbidden: return 'Failed to log in'
|
|
||||||
case ConnectionStatus.Error: return 'Failed to connect'
|
|
||||||
case ConnectionStatus.Closed: return 'Waiting to reconnect'
|
|
||||||
case ConnectionStatus.Slow: return 'Slow Connection'
|
|
||||||
case ConnectionStatus.Ok: return 'Connected'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import {ctx, uniq, noop, always} from '@welshman/lib'
|
import {ctx, uniq, noop, always} from '@welshman/lib'
|
||||||
import {matchFilters, unionFilters, isSignedEvent, hasValidSignature} from '@welshman/util'
|
import {matchFilters, unionFilters, isSignedEvent, hasValidSignature} from '@welshman/util'
|
||||||
import type {Filter, TrustedEvent} from '@welshman/util'
|
import type {StampedEvent, SignedEvent, Filter, TrustedEvent} from '@welshman/util'
|
||||||
import {Pool} from "./Pool"
|
import {Pool} from "./Pool"
|
||||||
import {Executor} from "./Executor"
|
import {Executor} from "./Executor"
|
||||||
|
import {AuthMode} from "./ConnectionAuth"
|
||||||
import {Relays} from "./target/Relays"
|
import {Relays} from "./target/Relays"
|
||||||
import type {Subscription, RelaysAndFilters} from "./Subscribe"
|
import type {Subscription, RelaysAndFilters} from "./Subscribe"
|
||||||
|
|
||||||
export type NetContext = {
|
export type NetContext = {
|
||||||
pool: Pool
|
pool: Pool
|
||||||
getExecutor: (relays: string[]) => Executor
|
authMode: AuthMode,
|
||||||
onEvent: (url: string, event: TrustedEvent) => void
|
onEvent: (url: string, event: TrustedEvent) => void
|
||||||
onAuth: (url: string, challenge: string) => void
|
signEvent: (event: StampedEvent) => Promise<SignedEvent>
|
||||||
|
getExecutor: (relays: string[]) => Executor
|
||||||
isDeleted: (url: string, event: TrustedEvent) => boolean
|
isDeleted: (url: string, event: TrustedEvent) => boolean
|
||||||
isValid: (url: string, event: TrustedEvent) => boolean
|
isValid: (url: string, event: TrustedEvent) => boolean
|
||||||
matchFilters: (url: string, filters: Filter[], event: TrustedEvent) => boolean
|
matchFilters: (url: string, filters: Filter[], event: TrustedEvent) => boolean
|
||||||
@@ -27,9 +29,10 @@ export const defaultOptimizeSubscriptions = (subs: Subscription[]) =>
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const getDefaultNetContext = (overrides: Partial<NetContext> = {}) => ({
|
export const getDefaultNetContext = (overrides: Partial<NetContext> = {}) => ({
|
||||||
onAuth: noop,
|
|
||||||
onEvent: noop,
|
|
||||||
pool: new Pool(),
|
pool: new Pool(),
|
||||||
|
authMode: AuthMode.Implicit,
|
||||||
|
onEvent: noop,
|
||||||
|
signEvent: noop,
|
||||||
isDeleted: always(false),
|
isDeleted: always(false),
|
||||||
isValid: (url: string, event: TrustedEvent) => isSignedEvent(event) && hasValidSignature(event),
|
isValid: (url: string, event: TrustedEvent) => isSignedEvent(event) && hasValidSignature(event),
|
||||||
getExecutor: (relays: string[]) => new Executor(new Relays(relays.map((relay: string) => ctx.net.pool.get(relay)))),
|
getExecutor: (relays: string[]) => new Executor(new Relays(relays.map((relay: string) => ctx.net.pool.get(relay)))),
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ const _executeSubscription = (sub: Subscription) => {
|
|||||||
Promise.all(
|
Promise.all(
|
||||||
executor.target.connections.map(async (connection: Connection) => {
|
executor.target.connections.map(async (connection: Connection) => {
|
||||||
if (authTimeout) {
|
if (authTimeout) {
|
||||||
await connection.ensureAuth({timeout: authTimeout})
|
await connection.auth.waitIfPending({timeout: authTimeout})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./Connection"
|
export * from "./Connection"
|
||||||
|
export * from "./ConnectionAuth"
|
||||||
export * from "./ConnectionMeta"
|
export * from "./ConnectionMeta"
|
||||||
export * from "./Context"
|
export * from "./Context"
|
||||||
export * from "./Executor"
|
export * from "./Executor"
|
||||||
|
|||||||
Reference in New Issue
Block a user