UNPKG

@bsv/authsocket-client

Version:
1 lines 10.3 kB
{"version":3,"file":"AuthSocketClient.cjs","names":["Utils","SocketClientTransport","Peer"],"sources":["../../src/AuthSocketClient.ts"],"sourcesContent":["import {\n io as realIo,\n Socket as IoClientSocket,\n ManagerOptions,\n SocketOptions\n} from 'socket.io-client'\nimport {\n RequestedCertificateSet,\n SessionManager,\n AsyncSessionManager,\n Peer,\n WalletInterface,\n Utils,\n OriginatorDomainNameStringUnder250Bytes,\n stringifyBRC100\n} from '@bsv/sdk'\nimport { SocketClientTransport } from './SocketClientTransport.js'\n\nexport type AuthSocketClientErrorPhase = 'authentication' | 'application' | 'send'\n\nexport interface AuthSocketClientErrorContext {\n phase: AuthSocketClientErrorPhase\n socketId?: string\n eventName?: string\n}\n\nexport type AuthSocketClientErrorHandler = (\n error: unknown,\n context: AuthSocketClientErrorContext\n) => void | Promise<void>\n\nexport function decodeAuthSocketEventPayload(payload: number[]): { eventName: string; data: any } {\n try {\n const str = Utils.toUTF8(payload)\n const decoded: unknown = JSON.parse(str)\n if (\n decoded === null ||\n typeof decoded !== 'object' ||\n Array.isArray(decoded) ||\n typeof (decoded as { eventName?: unknown }).eventName !== 'string'\n ) {\n return { eventName: '_unknown', data: undefined }\n }\n return {\n eventName: (decoded as { eventName: string }).eventName,\n data: (decoded as { data?: unknown }).data\n }\n } catch {\n return { eventName: '_unknown', data: undefined }\n }\n}\n\nexport interface AuthSocketClientOptions {\n wallet: WalletInterface\n requestedCertificates?: RequestedCertificateSet\n sessionManager?: SessionManager | AsyncSessionManager\n managerOptions?: Partial<ManagerOptions & SocketOptions>\n originator?: OriginatorDomainNameStringUnder250Bytes\n /** Maximum authentication messages processed concurrently. Defaults to 32. */\n maxPendingAuthMessages?: number\n /** Receives contained transport and application errors without exposing remote payloads. */\n onError?: AuthSocketClientErrorHandler\n}\n\n/**\n * Internal class that wraps a Socket.IO client connection with BRC-103 mutual authentication,\n * enabling secure and identity-aware communication with a server.\n */\nclass AuthSocketClientImpl {\n public connected = false\n public id: string = ''\n public serverIdentityKey: string | undefined\n private readonly eventCallbacks = new Map<string, Array<(data: any) => void | Promise<void>>>()\n\n /**\n * Creates an instance of AuthSocketClient.\n *\n * @param ioSocket - The underlying Socket.IO client socket instance.\n * @param peer - The BRC-103 Peer instance responsible for managing authenticated\n * communication, including message signing and verification.\n */\n constructor(\n private readonly ioSocket: IoClientSocket,\n private readonly peer: Peer,\n private readonly onError: AuthSocketClientErrorHandler = () => {}\n ) {\n // Listen for 'connect' and 'disconnect' from underlying Socket.IO\n this.ioSocket.on('connect', () => {\n this.connected = true\n this.id = this.ioSocket.id ?? ''\n // Re-dispatch to dev if they've called \"socket.on('connect', ...)\"\n void this.fireEventCallbacks('connect')\n })\n\n this.ioSocket.on('disconnect', reason => {\n this.connected = false\n // Re-dispatch\n void this.fireEventCallbacks('disconnect', reason)\n })\n\n // Also listen for BRC-103 \"general\" messages\n // We'll rely on peer.listenForGeneralMessages\n this.peer.listenForGeneralMessages(async (senderKey, payload) => {\n this.serverIdentityKey = senderKey\n const { eventName, data } = this.decodeEventPayload(payload)\n await this.fireEventCallbacks(eventName, data)\n })\n }\n\n on(eventName: string, callback: (data?: any) => void | Promise<void>): this {\n let arr = this.eventCallbacks.get(eventName)\n if (arr === undefined) {\n arr = []\n this.eventCallbacks.set(eventName, arr)\n }\n arr.push(callback)\n return this\n }\n\n emit(eventName: string, data: any): this {\n // We sign a BRC-103 \"general\" message and send to the server\n // via peer.toPeer\n let encoded: number[]\n try {\n encoded = this.encodeEventPayload(eventName, data)\n } catch (error) {\n this.reportError(error, {\n phase: 'send',\n socketId: this.ioSocket.id ?? this.id,\n eventName\n })\n return this\n }\n this.peer.toPeer(encoded, this.serverIdentityKey).catch(err => {\n this.reportError(err, {\n phase: 'send',\n socketId: this.ioSocket.id ?? this.id,\n eventName\n })\n })\n return this\n }\n\n disconnect(): void {\n this.serverIdentityKey = undefined\n this.ioSocket.disconnect()\n }\n\n private async fireEventCallbacks(eventName: string, data?: any): Promise<void> {\n const cbs = this.eventCallbacks.get(eventName)\n if (cbs === undefined) return\n try {\n for (const cb of cbs) {\n const result = cb(data)\n if (result != null && typeof (result as PromiseLike<void>).then === 'function') {\n await result\n }\n }\n } catch (error) {\n this.reportError(error, {\n phase: 'application',\n socketId: this.ioSocket.id ?? this.id,\n eventName\n })\n if (eventName !== 'disconnect') this.disconnectSafely()\n }\n }\n\n private encodeEventPayload(eventName: string, data: any): number[] {\n const obj = { eventName, data }\n return Utils.toArray(stringifyBRC100(obj), 'utf8')\n }\n\n private decodeEventPayload(payload: number[]): { eventName: string; data: any } {\n return decodeAuthSocketEventPayload(payload)\n }\n\n private reportError(error: unknown, context: AuthSocketClientErrorContext): void {\n void Promise.resolve()\n .then(async () => await this.onError(error, context))\n .catch(() => {})\n }\n\n private disconnectSafely(): void {\n try {\n this.ioSocket.disconnect()\n } catch {\n // The original failure is already contained and reported.\n }\n }\n}\n\n/**\n * Factory function for creating a new AuthSocketClientImpl instance.\n *\n * @param url - The server URL\n * @param opts - Contains wallet, requested certificates, and other optional settings\n */\nexport function AuthSocketClient(url: string, opts: AuthSocketClientOptions): AuthSocketClientImpl {\n // 1) Create real socket.io-client connection\n const socket = realIo(url, opts.managerOptions)\n\n // 2) Create a BRC-103 transport for the new socket\n const transport = new SocketClientTransport(socket, {\n maxPendingMessages: opts.maxPendingAuthMessages,\n onError: error => {\n reportErrorSafely(opts.onError, error, {\n phase: 'authentication',\n socketId: socket.id\n })\n }\n })\n\n // 3) Create a Peer\n const peer = new Peer(\n opts.wallet,\n transport,\n opts.requestedCertificates,\n opts.sessionManager,\n undefined,\n opts.originator\n )\n\n // 4) Return our new AuthSocketClientImpl\n return new AuthSocketClientImpl(socket, peer, (error, context) => {\n reportErrorSafely(opts.onError, error, context)\n })\n}\n\nfunction reportErrorSafely(\n handler: AuthSocketClientErrorHandler | undefined,\n error: unknown,\n context: AuthSocketClientErrorContext\n): void {\n void Promise.resolve()\n .then(async () => await handler?.(error, context))\n .catch(() => {})\n}\n"],"mappings":"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"}