@proj-airi/server-sdk 0.12.0-beta.2 → 0.12.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { ClientConnector, ClientConnector as ClientConnector$1, ClientEvents } from "@proj-airi/better-ws";
2
2
  import { ContextUpdateStrategy, ExtensionIdentity, ExtensionModuleIdentity, MessageHeartbeat, ModuleConfigSchema, ModuleDependency, ModulePermissionDeclaration, ProtocolEvents, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
3
- export * from "@proj-airi/server-shared/types";
4
-
3
+ export type * from "@proj-airi/server-shared/types";
5
4
  //#region src/client.d.ts
6
5
  type ClientStatus = 'idle' | 'connecting' | 'authenticating' | 'announcing' | 'ready' | 'reconnecting' | 'closing' | 'closed' | 'failed';
7
6
  interface ClientHeartbeatOptions {
@@ -59,7 +58,7 @@ declare class Client<C = undefined> {
59
58
  get connectionStatus(): ClientStatus;
60
59
  get isReady(): boolean;
61
60
  get isSocketOpen(): boolean;
62
- get lastError(): Error;
61
+ get lastError(): Error | undefined;
63
62
  connect(options?: ConnectOptions): Promise<void>;
64
63
  ready(options?: ConnectOptions): Promise<void>;
65
64
  ensureConnected(options?: ConnectOptions): Promise<void>;
package/dist/index.mjs CHANGED
@@ -230,7 +230,8 @@ var Client = class {
230
230
  if (!this.opts.autoReconnect) return false;
231
231
  return {
232
232
  retries: (attempt, error) => {
233
- if (isTerminalAuthenticationServerErrorMessage(this.normalizeError(error, "Failed to connect websocket client").message)) return false;
233
+ const normalized = this.normalizeError(error, "Failed to connect websocket client");
234
+ if (isTerminalAuthenticationServerErrorMessage(normalized.message)) return false;
234
235
  return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts;
235
236
  },
236
237
  onFailed: (error) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["createBetterWsClient"],"sources":["../src/codec.ts","../src/client.ts","../src/extension-peer.ts"],"sourcesContent":["import type { WebSocketBaseEvent, WebSocketEvent } from '@proj-airi/server-shared/types'\n\nimport { parse, stringify } from 'superjson'\nimport { check, objectWithRest, pipe, safeParse, string, unknown } from 'valibot'\n\nconst invalidMessage = 'Invalid AIRI websocket message.'\n\nconst eventDataSchema = pipe(\n unknown(),\n check(\n value => Boolean(value) && typeof value === 'object' && !Array.isArray(value),\n 'Expected event data to be a non-array object.',\n ),\n)\n\nconst eventEnvelopeSchema = objectWithRest({\n type: string(),\n data: eventDataSchema,\n}, unknown())\n\n/** Options for websocket message validation failures. */\nexport interface InvalidMessageErrorOptions {\n /** Original parser or validator failure that made the websocket message unusable. */\n cause?: unknown\n /** Parsed candidate event when available; otherwise the original websocket text. */\n source?: unknown\n}\n\n/** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */\nexport class InvalidMessageError extends Error {\n readonly source?: unknown\n\n constructor(options: InvalidMessageErrorOptions = {}) {\n super(invalidMessage, { cause: options.cause })\n this.name = 'InvalidMessageError'\n this.source = options.source\n }\n}\n\n/** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */\nexport function parseEvent<C = undefined>(text: string): WebSocketEvent<C> {\n let superJsonParsed: WebSocketEvent<C> | undefined\n let superJsonError: unknown\n\n try {\n superJsonParsed = parse<WebSocketEvent<C>>(text)\n }\n catch (error) {\n superJsonError = error\n }\n\n const potentialEvent = superJsonParsed && typeof superJsonParsed === 'object' && 'type' in superJsonParsed\n ? superJsonParsed\n : parsePlainJson(text, superJsonError)\n\n const result = safeParse(eventEnvelopeSchema, potentialEvent)\n if (!result.success) {\n throw new InvalidMessageError({ cause: result.issues, source: potentialEvent })\n }\n\n return potentialEvent as WebSocketEvent<C>\n}\n\n/** Serializes one AIRI websocket protocol event with SuperJSON. */\nexport function stringifyEvent<C = undefined>(\n event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>,\n) {\n return stringify(event)\n}\n\nfunction parsePlainJson(text: string, superJsonError: unknown): unknown {\n try {\n return JSON.parse(text)\n }\n catch (jsonError) {\n throw new InvalidMessageError({\n cause: superJsonError ?? jsonError,\n source: text,\n })\n }\n}\n","import type {\n Client as BetterWsClient,\n ClientConnector,\n PrepareContext,\n ReconnectOptions,\n} from '@proj-airi/better-ws'\nimport type {\n ExtensionIdentity,\n ExtensionModuleIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n WebSocketBaseEvent,\n WebSocketEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport { errorMessageFrom } from '@moeru/std'\nimport { createClient as createBetterWsClient } from '@proj-airi/better-ws'\nimport { createCrossWsConnector } from '@proj-airi/better-ws/client/crossws'\nimport { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from '@proj-airi/server-shared'\nimport { MessageHeartbeat, MessageHeartbeatKind } from '@proj-airi/server-shared/types'\n\nimport { parseEvent, stringifyEvent } from './codec'\n\nexport type { ClientConnector, ClientEvents } from '@proj-airi/better-ws'\n\nexport type ClientStatus\n = | 'idle'\n | 'connecting'\n | 'authenticating'\n | 'announcing'\n | 'ready'\n | 'reconnecting'\n | 'closing'\n | 'closed'\n | 'failed'\n\nexport interface ClientHeartbeatOptions {\n pingInterval?: number\n readTimeout?: number\n message?: MessageHeartbeat | string\n}\n\nexport interface ClientStateChangeContext {\n previousStatus: ClientStatus\n status: ClientStatus\n}\n\nexport interface ConnectOptions {\n abortSignal?: AbortSignal\n timeout?: number\n}\n\nexport interface ClientOptions<C = undefined> {\n url?: string\n name: string\n token?: string\n connector?: ClientConnector<WebSocketEvent<C>>\n /**\n * Selects the connection handshake owned by this client.\n *\n * @default 'module'\n */\n handshake?: 'module' | 'manual'\n\n connectTimeoutMs?: number\n possibleEvents?: Array<keyof WebSocketEvents<C>>\n extension?: ExtensionIdentity\n identity?: ExtensionModuleIdentity\n dependencies?: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat?: false | ClientHeartbeatOptions\n\n autoConnect?: boolean\n autoReconnect?: boolean\n maxReconnectAttempts?: number\n\n onError?: (error: unknown) => void\n onClose?: () => void\n onReady?: () => void\n onStateChange?: (context: ClientStateChangeContext) => void\n\n onAnyMessage?: (data: WebSocketEvent<C>) => void\n onAnySend?: (data: WebSocketEvent<C>) => void\n}\n\ninterface NormalizedClientOptions<C> {\n url: string\n name: string\n token?: string\n connector: ClientConnector<WebSocketEvent<C>>\n handshake: 'module' | 'manual'\n connectTimeoutMs: number\n possibleEvents: Array<keyof WebSocketEvents<C>>\n extension: ExtensionIdentity\n identity: ExtensionModuleIdentity\n dependencies: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat: false | Required<ClientHeartbeatOptions>\n autoConnect: boolean\n autoReconnect: boolean\n maxReconnectAttempts: number\n onError: (error: unknown) => void\n onClose: () => void\n onReady: () => void\n onStateChange: (context: ClientStateChangeContext) => void\n onAnyMessage: (data: WebSocketEvent<C>) => void\n onAnySend: (data: WebSocketEvent<C>) => void\n}\n\ninterface ProtocolWaitResult {\n ready: boolean\n error?: Error\n}\n\nfunction createInstanceId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createEventId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction normalizeHeartbeatOptions(heartbeat?: false | ClientHeartbeatOptions): false | Required<ClientHeartbeatOptions> {\n if (heartbeat === false) {\n return false\n }\n\n const readTimeout = heartbeat?.readTimeout ?? 30_000\n const pingInterval = heartbeat?.pingInterval ?? Math.max(1_000, Math.floor(readTimeout / 2))\n\n return {\n readTimeout,\n pingInterval: Math.min(pingInterval, readTimeout),\n message: heartbeat?.message ?? MessageHeartbeat.Ping,\n }\n}\n\n/** Wraps a text websocket connector with AIRI protocol serialization. */\nexport function createTextProtocolConnector<C = undefined>(\n textConnector: ClientConnector<string>,\n): ClientConnector<WebSocketEvent<C>> {\n return {\n async connect(events) {\n const connection = await textConnector.connect({\n message(text) {\n try {\n events.message(parseEvent<C>(text))\n }\n catch (error) {\n events.error(error)\n }\n },\n close: details => events.close(details),\n error: error => events.error(error),\n })\n\n return {\n send: message => connection.send(stringifyEvent(message)),\n close: (code, reason) => connection.close?.(code, reason),\n ping: connection.ping,\n pong: connection.pong,\n }\n },\n }\n}\n\nfunction createDefaultProtocolConnector<C>(url: string): ClientConnector<WebSocketEvent<C>> {\n return createTextProtocolConnector(createCrossWsConnector({ url }))\n}\n\nfunction normalizeOptions<C>(options: ClientOptions<C>): NormalizedClientOptions<C> {\n const url = options.url ?? 'ws://localhost:6121/ws'\n const extension = options.extension ?? { id: options.name }\n const identity = options.identity ?? {\n id: createInstanceId(),\n extension,\n }\n\n return {\n url,\n name: options.name,\n token: options.token,\n connector: options.connector ?? createDefaultProtocolConnector<C>(url),\n handshake: options.handshake ?? 'module',\n connectTimeoutMs: options.connectTimeoutMs ?? 15_000,\n possibleEvents: options.possibleEvents ?? [],\n extension,\n identity,\n dependencies: options.dependencies ?? [],\n configSchema: options.configSchema,\n heartbeat: normalizeHeartbeatOptions(options.heartbeat),\n autoConnect: options.autoConnect ?? true,\n autoReconnect: options.autoReconnect ?? true,\n maxReconnectAttempts: options.maxReconnectAttempts ?? -1,\n onError: options.onError ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onReady: options.onReady ?? (() => {}),\n onStateChange: options.onStateChange ?? (() => {}),\n onAnyMessage: options.onAnyMessage ?? (() => {}),\n onAnySend: options.onAnySend ?? (() => {}),\n }\n}\n\nfunction createConnectionTimeoutError(timeout: number) {\n return new Error(`Connection timed out after ${timeout}ms`)\n}\n\nfunction createAbortError() {\n return new Error('Connection aborted')\n}\n\nexport class Client<C = undefined> {\n private readonly opts: NormalizedClientOptions<C>\n private readonly transport: BetterWsClient<WebSocketEvent<C>>\n private readonly eventListeners = new Map<\n keyof WebSocketEvents<C>,\n Set<(data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>>\n >()\n\n private readonly stateListeners = new Set<(context: ClientStateChangeContext) => void>()\n private status: ClientStatus = 'idle'\n private connectTask?: Promise<void>\n private failureReason?: Error\n\n constructor(options: ClientOptions<C>) {\n this.opts = normalizeOptions(options)\n this.transport = createBetterWsClient<WebSocketEvent<C>>({\n connector: this.opts.connector,\n reconnect: this.createReconnectOptions(),\n heartbeat: this.createHeartbeatOptions(),\n prepare: context => this.prepareProtocolConnection(context),\n })\n\n this.transport.onMessage(({ message }) => {\n void this.handleMessage(message)\n })\n this.transport.onStateChange(({ previousState, state }) => {\n this.handleTransportStateChange(previousState, state)\n })\n\n if (this.opts.autoConnect) {\n void this.connect().catch((error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n this.failureReason = normalized\n this.opts.onError(normalized)\n })\n }\n }\n\n get connectionStatus() {\n return this.status\n }\n\n get isReady() {\n return this.status === 'ready'\n }\n\n get isSocketOpen() {\n return this.transport.state === 'open' || this.transport.state === 'preparing' || this.transport.state === 'ready'\n }\n\n get lastError() {\n return this.failureReason\n }\n\n async connect(options?: ConnectOptions) {\n if (this.status === 'ready') {\n return\n }\n\n if (!this.connectTask && this.transport.state === 'reconnecting') {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask && (this.transport.state === 'open' || this.transport.state === 'preparing')) {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask) {\n this.connectTask = this.transport.connect().finally(() => {\n this.connectTask = undefined\n })\n }\n\n return this.waitForConnection(this.connectTask, options)\n }\n\n ready(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n ensureConnected(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n onConnectionStateChange(callback: (context: ClientStateChangeContext) => void): () => void {\n this.stateListeners.add(callback)\n\n return () => {\n this.stateListeners.delete(callback)\n }\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n let listeners = this.eventListeners.get(event)\n if (!listeners) {\n listeners = new Set()\n this.eventListeners.set(event, listeners)\n }\n\n listeners.add(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n\n return () => {\n this.offEvent(event, callback)\n }\n }\n\n offEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): void {\n const listeners = this.eventListeners.get(event)\n if (!listeners) {\n return\n }\n\n if (callback) {\n listeners.delete(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n if (!listeners.size) {\n this.eventListeners.delete(event)\n }\n return\n }\n\n this.eventListeners.delete(event)\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n const payload = this.createPayload(data)\n const result = this.transport.send(payload)\n if (!result.ok) {\n return false\n }\n\n this.opts.onAnySend(payload)\n return true\n }\n\n sendOrThrow(data: WebSocketEventOptionalSource<C>): void {\n if (!this.send(data)) {\n throw new Error(`Client is not connected, current status: ${this.status}`)\n }\n }\n\n close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n\n private createReconnectOptions(): false | ReconnectOptions {\n if (!this.opts.autoReconnect) {\n return false\n }\n\n return {\n retries: (attempt, error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (isTerminalAuthenticationServerErrorMessage(normalized.message)) {\n return false\n }\n\n return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts\n },\n onFailed: (error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (this.failureReason === normalized) {\n return\n }\n\n this.failureReason = normalized\n this.opts.onError(normalized)\n },\n }\n }\n\n private createHeartbeatOptions() {\n if (!this.opts.heartbeat) {\n return false\n }\n\n return {\n mode: 'message' as const,\n interval: this.opts.heartbeat.pingInterval,\n timeout: this.opts.heartbeat.readTimeout,\n message: () => this.createPayload({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Ping,\n message: this.opts.heartbeat ? this.opts.heartbeat.message : MessageHeartbeat.Ping,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>),\n }\n }\n\n private async prepareProtocolConnection(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n if (this.opts.handshake === 'manual') {\n if (!context.reconnecting) {\n return\n }\n\n this.transitionTo('authenticating')\n await this.waitForManualReconnectHandshake(context)\n return\n }\n\n if (this.opts.token) {\n this.transitionTo('authenticating')\n context.send(this.createPayload({\n type: 'module:authenticate',\n data: { token: this.opts.token },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'module:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n this.transitionTo('announcing')\n context.send(this.createPayload({\n type: 'extension:module:announce',\n data: {\n name: this.opts.name,\n identity: this.opts.identity,\n possibleEvents: this.opts.possibleEvents,\n configSchema: this.opts.configSchema,\n dependencies: this.opts.dependencies,\n },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return result.ready\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private async waitForManualReconnectHandshake(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'peer:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n\n this.transitionTo('announcing')\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'extension:announced'\n && message.data.identity.id === this.opts.extension.id\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private consumePrepareMessage(message: WebSocketEvent<C>): ProtocolWaitResult {\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n return { ready: false, error }\n }\n\n if (message.type === 'extension:module:announced') {\n return { ready: this.isSelfModuleAnnouncement(message) }\n }\n\n if (message.type === 'registry:modules:sync') {\n return { ready: this.hasSelfModuleInRegistrySync(message) }\n }\n\n return { ready: false }\n }\n\n private async handleMessage(message: WebSocketEvent<C>): Promise<void> {\n this.opts.onAnyMessage(message)\n\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n this.opts.onError(error)\n }\n\n if (message.type === 'transport:connection:heartbeat' && message.data.kind === MessageHeartbeatKind.Ping) {\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Pong,\n message: MessageHeartbeat.Pong,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>)\n }\n\n const listeners = this.eventListeners.get(message.type)\n if (!listeners?.size) {\n return\n }\n\n const results = await Promise.allSettled(\n Array.from(listeners).map(listener => Promise.resolve(listener(message as WebSocketBaseEvent<string, unknown>))),\n )\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.failureReason = this.normalizeError(result.reason, 'Client event listener failed')\n this.opts.onError(result.reason)\n }\n }\n }\n\n private handleTransportStateChange(previousState: BetterWsClient<WebSocketEvent<C>>['state'], state: BetterWsClient<WebSocketEvent<C>>['state']) {\n if (state === 'ready') {\n this.transitionTo('ready')\n this.opts.onReady()\n return\n }\n\n const nextStatus = this.mapTransportStatus(state)\n if (!nextStatus) {\n return\n }\n\n this.transitionTo(nextStatus)\n\n if (previousState === 'ready' && state === 'reconnecting') {\n this.opts.onClose()\n }\n else if (state === 'closed') {\n this.opts.onClose()\n }\n }\n\n private mapTransportStatus(state: BetterWsClient<WebSocketEvent<C>>['state']): ClientStatus | undefined {\n switch (state) {\n case 'idle':\n case 'connecting':\n case 'reconnecting':\n case 'closing':\n case 'closed':\n case 'failed':\n return state\n case 'open':\n case 'preparing':\n case 'ready':\n return undefined\n }\n }\n\n private transitionTo(status: ClientStatus) {\n if (this.status === status) {\n return\n }\n\n const previousStatus = this.status\n this.status = status\n const context = { previousStatus, status }\n\n this.opts.onStateChange(context)\n\n for (const listener of this.stateListeners) {\n listener(context)\n }\n }\n\n private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {\n if (!options?.timeout && !options?.abortSignal) {\n return connectPromise\n }\n\n const timeout = options?.timeout\n if (typeof timeout !== 'undefined' && timeout <= 0) {\n throw createConnectionTimeoutError(timeout)\n }\n\n const abortSignal = options?.abortSignal\n if (abortSignal?.aborted) {\n throw createAbortError()\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n let removeAbortListener: (() => void) | undefined\n\n try {\n await Promise.race([\n connectPromise,\n new Promise<void>((_, reject) => {\n if (typeof timeout !== 'undefined') {\n timeoutHandle = setTimeout(() => {\n reject(createConnectionTimeoutError(timeout))\n }, timeout)\n }\n\n if (abortSignal) {\n const onAbort = () => reject(createAbortError())\n abortSignal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => abortSignal.removeEventListener('abort', onAbort)\n }\n }),\n ])\n }\n finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n\n removeAbortListener?.()\n }\n }\n\n private waitForReady(): Promise<void> {\n if (this.status === 'ready') {\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve, reject) => {\n const dispose = this.onConnectionStateChange(({ status }) => {\n if (status === 'ready') {\n dispose()\n resolve()\n return\n }\n\n if (status === 'failed' || status === 'closed') {\n dispose()\n reject(this.failureReason ?? new Error(`Client connection ended with status: ${status}`))\n }\n })\n })\n }\n\n private errorFromServerEvent(message: WebSocketEvent<C>): Error | undefined {\n if (message.type !== 'error') {\n return undefined\n }\n\n const errorMessage = typeof message.data.message === 'string'\n ? message.data.message\n : 'Unknown server error'\n const parsed = parseServerErrorMessage(errorMessage)\n\n if (parsed.code === 'unknown') {\n return new Error(errorMessage)\n }\n\n return new Error(parsed.message)\n }\n\n private normalizeError(error: unknown, fallback: string): Error {\n return error instanceof Error\n ? error\n : new Error(errorMessageFrom(error) ?? fallback)\n }\n\n private isSelfModuleAnnouncement(event: WebSocketBaseEvent<'extension:module:announced', WebSocketEvents<C>['extension:module:announced']>) {\n return event.data.name === this.opts.name && event.data.identity?.id === this.opts.identity.id\n }\n\n private hasSelfModuleInRegistrySync(event: WebSocketBaseEvent<'registry:modules:sync', WebSocketEvents<C>['registry:modules:sync']>) {\n return event.data.modules.some(module =>\n module.name === this.opts.name\n && module.identity?.id === this.opts.identity.id,\n )\n }\n\n private createPayload(data: WebSocketEventOptionalSource<C>) {\n return {\n ...data,\n metadata: {\n ...data.metadata,\n source: data.metadata?.source ?? {\n kind: 'plugin',\n ...this.opts.identity,\n plugin: { id: this.opts.extension.id },\n },\n event: {\n ...data.metadata?.event,\n id: data.metadata?.event?.id ?? createEventId(),\n },\n },\n } as WebSocketEvent<C>\n }\n}\n\nexport function createClient<C = undefined>(options: ClientOptions<C>): Client<C> {\n return new Client(options)\n}\n","import type {\n ExtensionIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n ModulePermissionDeclaration,\n ProtocolEvents,\n WebSocketBaseEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport type { ClientOptions, ConnectOptions } from './client'\n\nimport { createClient } from './client'\n\n/**\n * Describes the client operations required by {@link WebSocketExtensionPeer}.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface ExtensionPeerClient<C = undefined> {\n connect: (options?: ConnectOptions) => Promise<void>\n send: (data: WebSocketEventOptionalSource<C>) => boolean\n sendOrThrow: (data: WebSocketEventOptionalSource<C>) => void\n close: () => void\n onEvent?: <E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ) => () => void\n}\n\n/**\n * Describes one module announcement emitted through a websocket extension peer.\n *\n * @param C - Optional custom protocol event map used for possible event declarations.\n */\nexport interface AnnounceExtensionModuleInput<C = undefined> {\n /** Stable module id within the owning extension session. */\n id: string\n /** Human-readable module name used by registry and diagnostics. */\n name: string\n /** Protocol events this module may emit or handle. */\n possibleEvents?: Array<keyof ProtocolEvents<C>>\n /** Runtime permissions requested by this module. */\n permissions?: ModulePermissionDeclaration\n /** Optional configuration schema understood by the module. */\n configSchema?: ModuleConfigSchema\n /** Other modules or capabilities this module expects to exist. */\n dependencies?: ModuleDependency[]\n /** Optional labels for routing, diagnostics, or inspector views. */\n labels?: Record<string, string>\n}\n\n/**\n * Options for creating a websocket-backed extension peer. Supplying `client` lets\n * tests and embedding runtimes provide their own protocol client implementation.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface WebSocketExtensionPeerOptions<C = undefined> {\n extension: ExtensionIdentity\n client?: ExtensionPeerClient<C>\n clientOptions?: Omit<ClientOptions<C>, 'name' | 'identity'>\n}\n\n/**\n * Provides extension-level protocol helpers over a server-sdk protocol client.\n */\nexport class WebSocketExtensionPeer<C = undefined> {\n private readonly client: ExtensionPeerClient<C>\n private readonly extension: ExtensionIdentity\n\n constructor(options: WebSocketExtensionPeerOptions<C>) {\n this.extension = options.extension\n this.client = options.client ?? createClient<C>({\n ...options.clientOptions,\n name: options.extension.id,\n handshake: 'manual',\n autoConnect: options.clientOptions?.autoConnect ?? false,\n autoReconnect: options.clientOptions?.autoReconnect ?? false,\n })\n }\n\n connect(options?: ConnectOptions): Promise<void> {\n return this.client.connect(options)\n }\n\n authenticatePeer(input: { token?: string, peerId?: string } = {}): void {\n this.client.sendOrThrow({\n type: 'peer:authenticate',\n data: input,\n })\n }\n\n announceExtension(input: { permissions?: ModulePermissionDeclaration } = {}): void {\n this.client.sendOrThrow({\n type: 'extension:announce',\n data: {\n identity: this.extension,\n permissions: input.permissions,\n },\n })\n }\n\n announceModule(input: AnnounceExtensionModuleInput<C>): void {\n this.client.sendOrThrow({\n type: 'extension:module:announce',\n data: {\n name: input.name,\n identity: {\n id: input.id,\n extension: this.extension,\n labels: input.labels,\n },\n possibleEvents: input.possibleEvents ?? [],\n permissions: input.permissions,\n configSchema: input.configSchema,\n dependencies: input.dependencies,\n },\n })\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n return this.client.send(data)\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n if (!this.client.onEvent) {\n throw new Error('Wrapped extension peer client does not support event listeners.')\n }\n\n return this.client.onEvent(event, callback)\n }\n\n close(): void {\n this.client.close()\n }\n}\n\n/** Creates a websocket extension peer over a server-sdk protocol client. */\nexport function createWebSocketExtensionPeer<C = undefined>(\n options: WebSocketExtensionPeerOptions<C>,\n): WebSocketExtensionPeer<C> {\n return new WebSocketExtensionPeer(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["createBetterWsClient"],"sources":["../src/codec.ts","../src/client.ts","../src/extension-peer.ts"],"sourcesContent":["import type { WebSocketBaseEvent, WebSocketEvent } from '@proj-airi/server-shared/types'\n\nimport { parse, stringify } from 'superjson'\nimport { check, objectWithRest, pipe, safeParse, string, unknown } from 'valibot'\n\nconst invalidMessage = 'Invalid AIRI websocket message.'\n\nconst eventDataSchema = pipe(\n unknown(),\n check(\n value => Boolean(value) && typeof value === 'object' && !Array.isArray(value),\n 'Expected event data to be a non-array object.',\n ),\n)\n\nconst eventEnvelopeSchema = objectWithRest({\n type: string(),\n data: eventDataSchema,\n}, unknown())\n\n/** Options for websocket message validation failures. */\nexport interface InvalidMessageErrorOptions {\n /** Original parser or validator failure that made the websocket message unusable. */\n cause?: unknown\n /** Parsed candidate event when available; otherwise the original websocket text. */\n source?: unknown\n}\n\n/** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */\nexport class InvalidMessageError extends Error {\n readonly source?: unknown\n\n constructor(options: InvalidMessageErrorOptions = {}) {\n super(invalidMessage, { cause: options.cause })\n this.name = 'InvalidMessageError'\n this.source = options.source\n }\n}\n\n/** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */\nexport function parseEvent<C = undefined>(text: string): WebSocketEvent<C> {\n let superJsonParsed: WebSocketEvent<C> | undefined\n let superJsonError: unknown\n\n try {\n superJsonParsed = parse<WebSocketEvent<C>>(text)\n }\n catch (error) {\n superJsonError = error\n }\n\n const potentialEvent = superJsonParsed && typeof superJsonParsed === 'object' && 'type' in superJsonParsed\n ? superJsonParsed\n : parsePlainJson(text, superJsonError)\n\n const result = safeParse(eventEnvelopeSchema, potentialEvent)\n if (!result.success) {\n throw new InvalidMessageError({ cause: result.issues, source: potentialEvent })\n }\n\n return potentialEvent as WebSocketEvent<C>\n}\n\n/** Serializes one AIRI websocket protocol event with SuperJSON. */\nexport function stringifyEvent<C = undefined>(\n event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>,\n) {\n return stringify(event)\n}\n\nfunction parsePlainJson(text: string, superJsonError: unknown): unknown {\n try {\n return JSON.parse(text)\n }\n catch (jsonError) {\n throw new InvalidMessageError({\n cause: superJsonError ?? jsonError,\n source: text,\n })\n }\n}\n","import type {\n Client as BetterWsClient,\n ClientConnector,\n PrepareContext,\n ReconnectOptions,\n} from '@proj-airi/better-ws'\nimport type {\n ExtensionIdentity,\n ExtensionModuleIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n WebSocketBaseEvent,\n WebSocketEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport { errorMessageFrom } from '@moeru/std'\nimport { createClient as createBetterWsClient } from '@proj-airi/better-ws'\nimport { createCrossWsConnector } from '@proj-airi/better-ws/client/crossws'\nimport { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from '@proj-airi/server-shared'\nimport { MessageHeartbeat, MessageHeartbeatKind } from '@proj-airi/server-shared/types'\n\nimport { parseEvent, stringifyEvent } from './codec'\n\nexport type { ClientConnector, ClientEvents } from '@proj-airi/better-ws'\n\nexport type ClientStatus\n = | 'idle'\n | 'connecting'\n | 'authenticating'\n | 'announcing'\n | 'ready'\n | 'reconnecting'\n | 'closing'\n | 'closed'\n | 'failed'\n\nexport interface ClientHeartbeatOptions {\n pingInterval?: number\n readTimeout?: number\n message?: MessageHeartbeat | string\n}\n\nexport interface ClientStateChangeContext {\n previousStatus: ClientStatus\n status: ClientStatus\n}\n\nexport interface ConnectOptions {\n abortSignal?: AbortSignal\n timeout?: number\n}\n\nexport interface ClientOptions<C = undefined> {\n url?: string\n name: string\n token?: string\n connector?: ClientConnector<WebSocketEvent<C>>\n /**\n * Selects the connection handshake owned by this client.\n *\n * @default 'module'\n */\n handshake?: 'module' | 'manual'\n\n connectTimeoutMs?: number\n possibleEvents?: Array<keyof WebSocketEvents<C>>\n extension?: ExtensionIdentity\n identity?: ExtensionModuleIdentity\n dependencies?: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat?: false | ClientHeartbeatOptions\n\n autoConnect?: boolean\n autoReconnect?: boolean\n maxReconnectAttempts?: number\n\n onError?: (error: unknown) => void\n onClose?: () => void\n onReady?: () => void\n onStateChange?: (context: ClientStateChangeContext) => void\n\n onAnyMessage?: (data: WebSocketEvent<C>) => void\n onAnySend?: (data: WebSocketEvent<C>) => void\n}\n\ninterface NormalizedClientOptions<C> {\n url: string\n name: string\n token?: string\n connector: ClientConnector<WebSocketEvent<C>>\n handshake: 'module' | 'manual'\n connectTimeoutMs: number\n possibleEvents: Array<keyof WebSocketEvents<C>>\n extension: ExtensionIdentity\n identity: ExtensionModuleIdentity\n dependencies: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat: false | Required<ClientHeartbeatOptions>\n autoConnect: boolean\n autoReconnect: boolean\n maxReconnectAttempts: number\n onError: (error: unknown) => void\n onClose: () => void\n onReady: () => void\n onStateChange: (context: ClientStateChangeContext) => void\n onAnyMessage: (data: WebSocketEvent<C>) => void\n onAnySend: (data: WebSocketEvent<C>) => void\n}\n\ninterface ProtocolWaitResult {\n ready: boolean\n error?: Error\n}\n\nfunction createInstanceId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createEventId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction normalizeHeartbeatOptions(heartbeat?: false | ClientHeartbeatOptions): false | Required<ClientHeartbeatOptions> {\n if (heartbeat === false) {\n return false\n }\n\n const readTimeout = heartbeat?.readTimeout ?? 30_000\n const pingInterval = heartbeat?.pingInterval ?? Math.max(1_000, Math.floor(readTimeout / 2))\n\n return {\n readTimeout,\n pingInterval: Math.min(pingInterval, readTimeout),\n message: heartbeat?.message ?? MessageHeartbeat.Ping,\n }\n}\n\n/** Wraps a text websocket connector with AIRI protocol serialization. */\nexport function createTextProtocolConnector<C = undefined>(\n textConnector: ClientConnector<string>,\n): ClientConnector<WebSocketEvent<C>> {\n return {\n async connect(events) {\n const connection = await textConnector.connect({\n message(text) {\n try {\n events.message(parseEvent<C>(text))\n }\n catch (error) {\n events.error(error)\n }\n },\n close: details => events.close(details),\n error: error => events.error(error),\n })\n\n return {\n send: message => connection.send(stringifyEvent(message)),\n close: (code, reason) => connection.close?.(code, reason),\n ping: connection.ping,\n pong: connection.pong,\n }\n },\n }\n}\n\nfunction createDefaultProtocolConnector<C>(url: string): ClientConnector<WebSocketEvent<C>> {\n return createTextProtocolConnector(createCrossWsConnector({ url }))\n}\n\nfunction normalizeOptions<C>(options: ClientOptions<C>): NormalizedClientOptions<C> {\n const url = options.url ?? 'ws://localhost:6121/ws'\n const extension = options.extension ?? { id: options.name }\n const identity = options.identity ?? {\n id: createInstanceId(),\n extension,\n }\n\n return {\n url,\n name: options.name,\n token: options.token,\n connector: options.connector ?? createDefaultProtocolConnector<C>(url),\n handshake: options.handshake ?? 'module',\n connectTimeoutMs: options.connectTimeoutMs ?? 15_000,\n possibleEvents: options.possibleEvents ?? [],\n extension,\n identity,\n dependencies: options.dependencies ?? [],\n configSchema: options.configSchema,\n heartbeat: normalizeHeartbeatOptions(options.heartbeat),\n autoConnect: options.autoConnect ?? true,\n autoReconnect: options.autoReconnect ?? true,\n maxReconnectAttempts: options.maxReconnectAttempts ?? -1,\n onError: options.onError ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onReady: options.onReady ?? (() => {}),\n onStateChange: options.onStateChange ?? (() => {}),\n onAnyMessage: options.onAnyMessage ?? (() => {}),\n onAnySend: options.onAnySend ?? (() => {}),\n }\n}\n\nfunction createConnectionTimeoutError(timeout: number) {\n return new Error(`Connection timed out after ${timeout}ms`)\n}\n\nfunction createAbortError() {\n return new Error('Connection aborted')\n}\n\nexport class Client<C = undefined> {\n private readonly opts: NormalizedClientOptions<C>\n private readonly transport: BetterWsClient<WebSocketEvent<C>>\n private readonly eventListeners = new Map<\n keyof WebSocketEvents<C>,\n Set<(data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>>\n >()\n\n private readonly stateListeners = new Set<(context: ClientStateChangeContext) => void>()\n private status: ClientStatus = 'idle'\n private connectTask?: Promise<void>\n private failureReason?: Error\n\n constructor(options: ClientOptions<C>) {\n this.opts = normalizeOptions(options)\n this.transport = createBetterWsClient<WebSocketEvent<C>>({\n connector: this.opts.connector,\n reconnect: this.createReconnectOptions(),\n heartbeat: this.createHeartbeatOptions(),\n prepare: context => this.prepareProtocolConnection(context),\n })\n\n this.transport.onMessage(({ message }) => {\n void this.handleMessage(message)\n })\n this.transport.onStateChange(({ previousState, state }) => {\n this.handleTransportStateChange(previousState, state)\n })\n\n if (this.opts.autoConnect) {\n void this.connect().catch((error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n this.failureReason = normalized\n this.opts.onError(normalized)\n })\n }\n }\n\n get connectionStatus() {\n return this.status\n }\n\n get isReady() {\n return this.status === 'ready'\n }\n\n get isSocketOpen() {\n return this.transport.state === 'open' || this.transport.state === 'preparing' || this.transport.state === 'ready'\n }\n\n get lastError() {\n return this.failureReason\n }\n\n async connect(options?: ConnectOptions) {\n if (this.status === 'ready') {\n return\n }\n\n if (!this.connectTask && this.transport.state === 'reconnecting') {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask && (this.transport.state === 'open' || this.transport.state === 'preparing')) {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask) {\n this.connectTask = this.transport.connect().finally(() => {\n this.connectTask = undefined\n })\n }\n\n return this.waitForConnection(this.connectTask, options)\n }\n\n ready(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n ensureConnected(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n onConnectionStateChange(callback: (context: ClientStateChangeContext) => void): () => void {\n this.stateListeners.add(callback)\n\n return () => {\n this.stateListeners.delete(callback)\n }\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n let listeners = this.eventListeners.get(event)\n if (!listeners) {\n listeners = new Set()\n this.eventListeners.set(event, listeners)\n }\n\n listeners.add(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n\n return () => {\n this.offEvent(event, callback)\n }\n }\n\n offEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): void {\n const listeners = this.eventListeners.get(event)\n if (!listeners) {\n return\n }\n\n if (callback) {\n listeners.delete(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n if (!listeners.size) {\n this.eventListeners.delete(event)\n }\n return\n }\n\n this.eventListeners.delete(event)\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n const payload = this.createPayload(data)\n const result = this.transport.send(payload)\n if (!result.ok) {\n return false\n }\n\n this.opts.onAnySend(payload)\n return true\n }\n\n sendOrThrow(data: WebSocketEventOptionalSource<C>): void {\n if (!this.send(data)) {\n throw new Error(`Client is not connected, current status: ${this.status}`)\n }\n }\n\n close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n\n private createReconnectOptions(): false | ReconnectOptions {\n if (!this.opts.autoReconnect) {\n return false\n }\n\n return {\n retries: (attempt, error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (isTerminalAuthenticationServerErrorMessage(normalized.message)) {\n return false\n }\n\n return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts\n },\n onFailed: (error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (this.failureReason === normalized) {\n return\n }\n\n this.failureReason = normalized\n this.opts.onError(normalized)\n },\n }\n }\n\n private createHeartbeatOptions() {\n if (!this.opts.heartbeat) {\n return false\n }\n\n return {\n mode: 'message' as const,\n interval: this.opts.heartbeat.pingInterval,\n timeout: this.opts.heartbeat.readTimeout,\n message: () => this.createPayload({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Ping,\n message: this.opts.heartbeat ? this.opts.heartbeat.message : MessageHeartbeat.Ping,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>),\n }\n }\n\n private async prepareProtocolConnection(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n if (this.opts.handshake === 'manual') {\n if (!context.reconnecting) {\n return\n }\n\n this.transitionTo('authenticating')\n await this.waitForManualReconnectHandshake(context)\n return\n }\n\n if (this.opts.token) {\n this.transitionTo('authenticating')\n context.send(this.createPayload({\n type: 'module:authenticate',\n data: { token: this.opts.token },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'module:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n this.transitionTo('announcing')\n context.send(this.createPayload({\n type: 'extension:module:announce',\n data: {\n name: this.opts.name,\n identity: this.opts.identity,\n possibleEvents: this.opts.possibleEvents,\n configSchema: this.opts.configSchema,\n dependencies: this.opts.dependencies,\n },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return result.ready\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private async waitForManualReconnectHandshake(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'peer:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n\n this.transitionTo('announcing')\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'extension:announced'\n && message.data.identity.id === this.opts.extension.id\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private consumePrepareMessage(message: WebSocketEvent<C>): ProtocolWaitResult {\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n return { ready: false, error }\n }\n\n if (message.type === 'extension:module:announced') {\n return { ready: this.isSelfModuleAnnouncement(message) }\n }\n\n if (message.type === 'registry:modules:sync') {\n return { ready: this.hasSelfModuleInRegistrySync(message) }\n }\n\n return { ready: false }\n }\n\n private async handleMessage(message: WebSocketEvent<C>): Promise<void> {\n this.opts.onAnyMessage(message)\n\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n this.opts.onError(error)\n }\n\n if (message.type === 'transport:connection:heartbeat' && message.data.kind === MessageHeartbeatKind.Ping) {\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Pong,\n message: MessageHeartbeat.Pong,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>)\n }\n\n const listeners = this.eventListeners.get(message.type)\n if (!listeners?.size) {\n return\n }\n\n const results = await Promise.allSettled(\n Array.from(listeners).map(listener => Promise.resolve(listener(message as WebSocketBaseEvent<string, unknown>))),\n )\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.failureReason = this.normalizeError(result.reason, 'Client event listener failed')\n this.opts.onError(result.reason)\n }\n }\n }\n\n private handleTransportStateChange(previousState: BetterWsClient<WebSocketEvent<C>>['state'], state: BetterWsClient<WebSocketEvent<C>>['state']) {\n if (state === 'ready') {\n this.transitionTo('ready')\n this.opts.onReady()\n return\n }\n\n const nextStatus = this.mapTransportStatus(state)\n if (!nextStatus) {\n return\n }\n\n this.transitionTo(nextStatus)\n\n if (previousState === 'ready' && state === 'reconnecting') {\n this.opts.onClose()\n }\n else if (state === 'closed') {\n this.opts.onClose()\n }\n }\n\n private mapTransportStatus(state: BetterWsClient<WebSocketEvent<C>>['state']): ClientStatus | undefined {\n switch (state) {\n case 'idle':\n case 'connecting':\n case 'reconnecting':\n case 'closing':\n case 'closed':\n case 'failed':\n return state\n case 'open':\n case 'preparing':\n case 'ready':\n return undefined\n }\n }\n\n private transitionTo(status: ClientStatus) {\n if (this.status === status) {\n return\n }\n\n const previousStatus = this.status\n this.status = status\n const context = { previousStatus, status }\n\n this.opts.onStateChange(context)\n\n for (const listener of this.stateListeners) {\n listener(context)\n }\n }\n\n private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {\n if (!options?.timeout && !options?.abortSignal) {\n return connectPromise\n }\n\n const timeout = options?.timeout\n if (typeof timeout !== 'undefined' && timeout <= 0) {\n throw createConnectionTimeoutError(timeout)\n }\n\n const abortSignal = options?.abortSignal\n if (abortSignal?.aborted) {\n throw createAbortError()\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n let removeAbortListener: (() => void) | undefined\n\n try {\n await Promise.race([\n connectPromise,\n new Promise<void>((_, reject) => {\n if (typeof timeout !== 'undefined') {\n timeoutHandle = setTimeout(() => {\n reject(createConnectionTimeoutError(timeout))\n }, timeout)\n }\n\n if (abortSignal) {\n const onAbort = () => reject(createAbortError())\n abortSignal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => abortSignal.removeEventListener('abort', onAbort)\n }\n }),\n ])\n }\n finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n\n removeAbortListener?.()\n }\n }\n\n private waitForReady(): Promise<void> {\n if (this.status === 'ready') {\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve, reject) => {\n const dispose = this.onConnectionStateChange(({ status }) => {\n if (status === 'ready') {\n dispose()\n resolve()\n return\n }\n\n if (status === 'failed' || status === 'closed') {\n dispose()\n reject(this.failureReason ?? new Error(`Client connection ended with status: ${status}`))\n }\n })\n })\n }\n\n private errorFromServerEvent(message: WebSocketEvent<C>): Error | undefined {\n if (message.type !== 'error') {\n return undefined\n }\n\n const errorMessage = typeof message.data.message === 'string'\n ? message.data.message\n : 'Unknown server error'\n const parsed = parseServerErrorMessage(errorMessage)\n\n if (parsed.code === 'unknown') {\n return new Error(errorMessage)\n }\n\n return new Error(parsed.message)\n }\n\n private normalizeError(error: unknown, fallback: string): Error {\n return error instanceof Error\n ? error\n : new Error(errorMessageFrom(error) ?? fallback)\n }\n\n private isSelfModuleAnnouncement(event: WebSocketBaseEvent<'extension:module:announced', WebSocketEvents<C>['extension:module:announced']>) {\n return event.data.name === this.opts.name && event.data.identity?.id === this.opts.identity.id\n }\n\n private hasSelfModuleInRegistrySync(event: WebSocketBaseEvent<'registry:modules:sync', WebSocketEvents<C>['registry:modules:sync']>) {\n return event.data.modules.some(module =>\n module.name === this.opts.name\n && module.identity?.id === this.opts.identity.id,\n )\n }\n\n private createPayload(data: WebSocketEventOptionalSource<C>) {\n return {\n ...data,\n metadata: {\n ...data.metadata,\n source: data.metadata?.source ?? {\n kind: 'plugin',\n ...this.opts.identity,\n plugin: { id: this.opts.extension.id },\n },\n event: {\n ...data.metadata?.event,\n id: data.metadata?.event?.id ?? createEventId(),\n },\n },\n } as WebSocketEvent<C>\n }\n}\n\nexport function createClient<C = undefined>(options: ClientOptions<C>): Client<C> {\n return new Client(options)\n}\n","import type {\n ExtensionIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n ModulePermissionDeclaration,\n ProtocolEvents,\n WebSocketBaseEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport type { ClientOptions, ConnectOptions } from './client'\n\nimport { createClient } from './client'\n\n/**\n * Describes the client operations required by {@link WebSocketExtensionPeer}.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface ExtensionPeerClient<C = undefined> {\n connect: (options?: ConnectOptions) => Promise<void>\n send: (data: WebSocketEventOptionalSource<C>) => boolean\n sendOrThrow: (data: WebSocketEventOptionalSource<C>) => void\n close: () => void\n onEvent?: <E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ) => () => void\n}\n\n/**\n * Describes one module announcement emitted through a websocket extension peer.\n *\n * @param C - Optional custom protocol event map used for possible event declarations.\n */\nexport interface AnnounceExtensionModuleInput<C = undefined> {\n /** Stable module id within the owning extension session. */\n id: string\n /** Human-readable module name used by registry and diagnostics. */\n name: string\n /** Protocol events this module may emit or handle. */\n possibleEvents?: Array<keyof ProtocolEvents<C>>\n /** Runtime permissions requested by this module. */\n permissions?: ModulePermissionDeclaration\n /** Optional configuration schema understood by the module. */\n configSchema?: ModuleConfigSchema\n /** Other modules or capabilities this module expects to exist. */\n dependencies?: ModuleDependency[]\n /** Optional labels for routing, diagnostics, or inspector views. */\n labels?: Record<string, string>\n}\n\n/**\n * Options for creating a websocket-backed extension peer. Supplying `client` lets\n * tests and embedding runtimes provide their own protocol client implementation.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface WebSocketExtensionPeerOptions<C = undefined> {\n extension: ExtensionIdentity\n client?: ExtensionPeerClient<C>\n clientOptions?: Omit<ClientOptions<C>, 'name' | 'identity'>\n}\n\n/**\n * Provides extension-level protocol helpers over a server-sdk protocol client.\n */\nexport class WebSocketExtensionPeer<C = undefined> {\n private readonly client: ExtensionPeerClient<C>\n private readonly extension: ExtensionIdentity\n\n constructor(options: WebSocketExtensionPeerOptions<C>) {\n this.extension = options.extension\n this.client = options.client ?? createClient<C>({\n ...options.clientOptions,\n name: options.extension.id,\n handshake: 'manual',\n autoConnect: options.clientOptions?.autoConnect ?? false,\n autoReconnect: options.clientOptions?.autoReconnect ?? false,\n })\n }\n\n connect(options?: ConnectOptions): Promise<void> {\n return this.client.connect(options)\n }\n\n authenticatePeer(input: { token?: string, peerId?: string } = {}): void {\n this.client.sendOrThrow({\n type: 'peer:authenticate',\n data: input,\n })\n }\n\n announceExtension(input: { permissions?: ModulePermissionDeclaration } = {}): void {\n this.client.sendOrThrow({\n type: 'extension:announce',\n data: {\n identity: this.extension,\n permissions: input.permissions,\n },\n })\n }\n\n announceModule(input: AnnounceExtensionModuleInput<C>): void {\n this.client.sendOrThrow({\n type: 'extension:module:announce',\n data: {\n name: input.name,\n identity: {\n id: input.id,\n extension: this.extension,\n labels: input.labels,\n },\n possibleEvents: input.possibleEvents ?? [],\n permissions: input.permissions,\n configSchema: input.configSchema,\n dependencies: input.dependencies,\n },\n })\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n return this.client.send(data)\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n if (!this.client.onEvent) {\n throw new Error('Wrapped extension peer client does not support event listeners.')\n }\n\n return this.client.onEvent(event, callback)\n }\n\n close(): void {\n this.client.close()\n }\n}\n\n/** Creates a websocket extension peer over a server-sdk protocol client. */\nexport function createWebSocketExtensionPeer<C = undefined>(\n options: WebSocketExtensionPeerOptions<C>,\n): WebSocketExtensionPeer<C> {\n return new WebSocketExtensionPeer(options)\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/utils/node/process.ts"],"sourcesContent":["import process from 'node:process'\n\nlet running = true\n\nfunction killProcess() {\n running = false\n}\n\nprocess.on('SIGTERM', () => {\n killProcess()\n})\nprocess.on('SIGINT', () => {\n killProcess()\n})\nprocess.on('uncaughtException', (e) => {\n console.error(e)\n killProcess()\n})\n\nexport function runUntilSignal() {\n setTimeout(() => {\n if (running)\n runUntilSignal()\n }, 10)\n}\n"],"mappings":";;AAEA,IAAI,UAAU;AAEd,SAAS,cAAc;AACrB,WAAU;;AAGZ,QAAQ,GAAG,iBAAiB;AAC1B,cAAa;EACb;AACF,QAAQ,GAAG,gBAAgB;AACzB,cAAa;EACb;AACF,QAAQ,GAAG,sBAAsB,MAAM;AACrC,SAAQ,MAAM,EAAE;AAChB,cAAa;EACb;AAEF,SAAgB,iBAAiB;AAC/B,kBAAiB;AACf,MAAI,QACF,iBAAgB;IACjB,GAAG"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/utils/node/process.ts"],"sourcesContent":["import process from 'node:process'\n\nlet running = true\n\nfunction killProcess() {\n running = false\n}\n\nprocess.on('SIGTERM', () => {\n killProcess()\n})\nprocess.on('SIGINT', () => {\n killProcess()\n})\nprocess.on('uncaughtException', (e) => {\n console.error(e)\n killProcess()\n})\n\nexport function runUntilSignal() {\n setTimeout(() => {\n if (running)\n runUntilSignal()\n }, 10)\n}\n"],"mappings":";;AAEA,IAAI,UAAU;AAEd,SAAS,cAAc;CACrB,UAAU;AACZ;AAEA,QAAQ,GAAG,iBAAiB;CAC1B,YAAY;AACd,CAAC;AACD,QAAQ,GAAG,gBAAgB;CACzB,YAAY;AACd,CAAC;AACD,QAAQ,GAAG,sBAAsB,MAAM;CACrC,QAAQ,MAAM,CAAC;CACf,YAAY;AACd,CAAC;AAED,SAAgB,iBAAiB;CAC/B,iBAAiB;EACf,IAAI,SACF,eAAe;CACnB,GAAG,EAAE;AACP"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@proj-airi/server-sdk",
3
3
  "type": "module",
4
- "version": "0.12.0-beta.2",
4
+ "version": "0.12.0-beta.4",
5
5
  "description": "Client-side SDK implementation for connecting to AIRI server components and runtimes",
6
6
  "author": {
7
7
  "name": "Moeru AI Project AIRI Team",
@@ -33,10 +33,10 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@moeru/std": "0.1.0-beta.17",
36
+ "@proj-airi/better-ws": "^0.12.0-beta.4",
37
+ "@proj-airi/server-shared": "^0.12.0-beta.4",
36
38
  "superjson": "^2.2.6",
37
- "valibot": "^1.3.1",
38
- "@proj-airi/better-ws": "^0.12.0-beta.2",
39
- "@proj-airi/server-shared": "^0.12.0-beta.2"
39
+ "valibot": "^1.4.2"
40
40
  },
41
41
  "scripts": {
42
42
  "dev": "pnpm run build",