@proj-airi/server-sdk 0.10.2 → 0.11.0

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,31 +1,7 @@
1
- import { ContextUpdateStrategy, MessageHeartbeat, MetadataEventSource, ModuleConfigSchema, ModuleDependency, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
1
+ import { ClientConnector, ClientConnector as ClientConnector$1, ClientEvents } from "@proj-airi/better-ws";
2
+ import { ContextUpdateStrategy, ExtensionIdentity, ExtensionModuleIdentity, MessageHeartbeat, ModuleConfigSchema, ModuleDependency, ModulePermissionDeclaration, ProtocolEvents, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
2
3
  export * from "@proj-airi/server-shared/types";
3
4
 
4
- //#region src/websocket-like.d.ts
5
- interface WebSocketMessageEventLike<T = string> {
6
- data: T;
7
- }
8
- interface WebSocketErrorEventLike {
9
- error?: Error | unknown;
10
- }
11
- interface WebSocketLike {
12
- readonly readyState: number;
13
- onopen?: (event?: unknown) => void;
14
- onmessage?: (event: WebSocketMessageEventLike) => void;
15
- onerror?: (event: WebSocketErrorEventLike | unknown) => void;
16
- onclose?: (event?: unknown) => void;
17
- send: (data: string | ArrayBufferLike | ArrayBufferView) => void;
18
- close: (code?: number, reason?: string) => void;
19
- ping?: () => void;
20
- pong?: () => void;
21
- }
22
- interface WebSocketLikeConstructor {
23
- readonly OPEN: number;
24
- readonly CLOSING: number;
25
- readonly CLOSED: number;
26
- new (url: string): WebSocketLike;
27
- }
28
- //#endregion
29
5
  //#region src/client.d.ts
30
6
  type ClientStatus = 'idle' | 'connecting' | 'authenticating' | 'announcing' | 'ready' | 'reconnecting' | 'closing' | 'closed' | 'failed';
31
7
  interface ClientHeartbeatOptions {
@@ -45,13 +21,20 @@ interface ClientOptions<C = undefined> {
45
21
  url?: string;
46
22
  name: string;
47
23
  token?: string;
48
- websocketConstructor?: WebSocketLikeConstructor;
24
+ connector?: ClientConnector$1<WebSocketEvent<C>>;
25
+ /**
26
+ * Selects the connection handshake owned by this client.
27
+ *
28
+ * @default 'module'
29
+ */
30
+ handshake?: 'module' | 'manual';
49
31
  connectTimeoutMs?: number;
50
32
  possibleEvents?: Array<keyof WebSocketEvents<C>>;
51
- identity?: MetadataEventSource;
33
+ extension?: ExtensionIdentity;
34
+ identity?: ExtensionModuleIdentity;
52
35
  dependencies?: ModuleDependency[];
53
36
  configSchema?: ModuleConfigSchema;
54
- heartbeat?: ClientHeartbeatOptions;
37
+ heartbeat?: false | ClientHeartbeatOptions;
55
38
  autoConnect?: boolean;
56
39
  autoReconnect?: boolean;
57
40
  maxReconnectAttempts?: number;
@@ -62,24 +45,16 @@ interface ClientOptions<C = undefined> {
62
45
  onAnyMessage?: (data: WebSocketEvent<C>) => void;
63
46
  onAnySend?: (data: WebSocketEvent<C>) => void;
64
47
  }
48
+ /** Wraps a text websocket connector with AIRI protocol serialization. */
49
+ declare function createTextProtocolConnector<C = undefined>(textConnector: ClientConnector$1<string>): ClientConnector$1<WebSocketEvent<C>>;
65
50
  declare class Client<C = undefined> {
66
- private websocket?;
67
- private shouldClose;
68
- private connectTask?;
69
- private heartbeatTimer?;
70
- private lastPingAt;
71
- private lastReadAt;
72
- private reconnectAttempts;
73
- private pendingReconnect;
74
- private connectionAttempt?;
75
- private failureReason?;
76
- private status;
77
- private readonly identity;
78
- private readonly heartbeat;
79
- private readonly websocketConstructor;
80
51
  private readonly opts;
52
+ private readonly transport;
81
53
  private readonly eventListeners;
82
54
  private readonly stateListeners;
55
+ private status;
56
+ private connectTask?;
57
+ private failureReason?;
83
58
  constructor(options: ClientOptions<C>);
84
59
  get connectionStatus(): ClientStatus;
85
60
  get isReady(): boolean;
@@ -93,33 +68,111 @@ declare class Client<C = undefined> {
93
68
  offEvent<E extends keyof WebSocketEvents<C>>(event: E, callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>): void;
94
69
  send(data: WebSocketEventOptionalSource<C>): boolean;
95
70
  sendOrThrow(data: WebSocketEventOptionalSource<C>): void;
96
- sendRaw(data: string | ArrayBufferLike | ArrayBufferView): boolean;
97
- close(): void;
98
- private runConnectLoop;
99
- private connectOnce;
100
- private handleSocketFailure;
101
- private cleanupSocket;
102
- private rejectAttempt;
103
- private resolveAttempt;
104
- private canRetry;
105
- private getReconnectDelay;
71
+ close(code?: number, reason?: string): void;
72
+ private createReconnectOptions;
73
+ private createHeartbeatOptions;
74
+ private prepareProtocolConnection;
75
+ private waitForManualReconnectHandshake;
76
+ private consumePrepareMessage;
77
+ private handleMessage;
78
+ private handleTransportStateChange;
79
+ private mapTransportStatus;
106
80
  private transitionTo;
107
81
  private waitForConnection;
108
- private tryAnnounce;
109
- private tryAuthenticate;
110
- private handleMessage;
111
- private parseMessage;
112
- private handleControlMessage;
113
- private isSelfAnnouncement;
114
- private dispatchMessage;
82
+ private waitForReady;
83
+ private errorFromServerEvent;
84
+ private normalizeError;
85
+ private isSelfModuleAnnouncement;
86
+ private hasSelfModuleInRegistrySync;
115
87
  private createPayload;
116
- private startHeartbeat;
117
- private stopHeartbeat;
118
- private sendNativeHeartbeat;
119
- private sendHeartbeatPing;
120
- private sendHeartbeatPong;
121
- private reconnectAfterProtocolError;
122
88
  }
89
+ declare function createClient<C = undefined>(options: ClientOptions<C>): Client<C>;
90
+ //#endregion
91
+ //#region src/codec.d.ts
92
+ /** Options for websocket message validation failures. */
93
+ interface InvalidMessageErrorOptions {
94
+ /** Original parser or validator failure that made the websocket message unusable. */
95
+ cause?: unknown;
96
+ /** Parsed candidate event when available; otherwise the original websocket text. */
97
+ source?: unknown;
98
+ }
99
+ /** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */
100
+ declare class InvalidMessageError extends Error {
101
+ readonly source?: unknown;
102
+ constructor(options?: InvalidMessageErrorOptions);
103
+ }
104
+ /** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */
105
+ declare function parseEvent<C = undefined>(text: string): WebSocketEvent<C>;
106
+ /** Serializes one AIRI websocket protocol event with SuperJSON. */
107
+ declare function stringifyEvent<C = undefined>(event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>): string;
108
+ //#endregion
109
+ //#region src/extension-peer.d.ts
110
+ /**
111
+ * Describes the client operations required by {@link WebSocketExtensionPeer}.
112
+ *
113
+ * @param C - Optional custom protocol event map carried by the websocket client.
114
+ */
115
+ interface ExtensionPeerClient<C = undefined> {
116
+ connect: (options?: ConnectOptions) => Promise<void>;
117
+ send: (data: WebSocketEventOptionalSource<C>) => boolean;
118
+ sendOrThrow: (data: WebSocketEventOptionalSource<C>) => void;
119
+ close: () => void;
120
+ onEvent?: <E extends keyof WebSocketEvents<C>>(event: E, callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>) => () => void;
121
+ }
122
+ /**
123
+ * Describes one module announcement emitted through a websocket extension peer.
124
+ *
125
+ * @param C - Optional custom protocol event map used for possible event declarations.
126
+ */
127
+ interface AnnounceExtensionModuleInput<C = undefined> {
128
+ /** Stable module id within the owning extension session. */
129
+ id: string;
130
+ /** Human-readable module name used by registry and diagnostics. */
131
+ name: string;
132
+ /** Protocol events this module may emit or handle. */
133
+ possibleEvents?: Array<keyof ProtocolEvents<C>>;
134
+ /** Runtime permissions requested by this module. */
135
+ permissions?: ModulePermissionDeclaration;
136
+ /** Optional configuration schema understood by the module. */
137
+ configSchema?: ModuleConfigSchema;
138
+ /** Other modules or capabilities this module expects to exist. */
139
+ dependencies?: ModuleDependency[];
140
+ /** Optional labels for routing, diagnostics, or inspector views. */
141
+ labels?: Record<string, string>;
142
+ }
143
+ /**
144
+ * Options for creating a websocket-backed extension peer. Supplying `client` lets
145
+ * tests and embedding runtimes provide their own protocol client implementation.
146
+ *
147
+ * @param C - Optional custom protocol event map carried by the websocket client.
148
+ */
149
+ interface WebSocketExtensionPeerOptions<C = undefined> {
150
+ extension: ExtensionIdentity;
151
+ client?: ExtensionPeerClient<C>;
152
+ clientOptions?: Omit<ClientOptions<C>, 'name' | 'identity'>;
153
+ }
154
+ /**
155
+ * Provides extension-level protocol helpers over a server-sdk protocol client.
156
+ */
157
+ declare class WebSocketExtensionPeer<C = undefined> {
158
+ private readonly client;
159
+ private readonly extension;
160
+ constructor(options: WebSocketExtensionPeerOptions<C>);
161
+ connect(options?: ConnectOptions): Promise<void>;
162
+ authenticatePeer(input?: {
163
+ token?: string;
164
+ peerId?: string;
165
+ }): void;
166
+ announceExtension(input?: {
167
+ permissions?: ModulePermissionDeclaration;
168
+ }): void;
169
+ announceModule(input: AnnounceExtensionModuleInput<C>): void;
170
+ send(data: WebSocketEventOptionalSource<C>): boolean;
171
+ onEvent<E extends keyof WebSocketEvents<C>>(event: E, callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>): () => void;
172
+ close(): void;
173
+ }
174
+ /** Creates a websocket extension peer over a server-sdk protocol client. */
175
+ declare function createWebSocketExtensionPeer<C = undefined>(options: WebSocketExtensionPeerOptions<C>): WebSocketExtensionPeer<C>;
123
176
  //#endregion
124
- export { Client, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, WebSocketErrorEventLike, WebSocketEventSource, WebSocketLike, WebSocketLikeConstructor, WebSocketMessageEventLike };
177
+ export { AnnounceExtensionModuleInput, Client, type ClientConnector, type ClientEvents, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, ExtensionPeerClient, InvalidMessageError, InvalidMessageErrorOptions, WebSocketEventSource, WebSocketExtensionPeer, WebSocketExtensionPeerOptions, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
125
178
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,8 +1,58 @@
1
- import NativeWebSocket from "crossws/websocket";
2
- import superjson from "superjson";
3
- import { errorMessageFrom, sleep } from "@moeru/std";
1
+ import { errorMessageFrom } from "@moeru/std";
2
+ import { createClient as createClient$1 } from "@proj-airi/better-ws";
3
+ import { createCrossWsConnector } from "@proj-airi/better-ws/client/crossws";
4
4
  import { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from "@proj-airi/server-shared";
5
5
  import { ContextUpdateStrategy, MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from "@proj-airi/server-shared/types";
6
+ import { parse, stringify } from "superjson";
7
+ import { check, objectWithRest, pipe, safeParse, string, unknown } from "valibot";
8
+ //#region src/codec.ts
9
+ const invalidMessage = "Invalid AIRI websocket message.";
10
+ const eventDataSchema = pipe(unknown(), check((value) => Boolean(value) && typeof value === "object" && !Array.isArray(value), "Expected event data to be a non-array object."));
11
+ const eventEnvelopeSchema = objectWithRest({
12
+ type: string(),
13
+ data: eventDataSchema
14
+ }, unknown());
15
+ /** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */
16
+ var InvalidMessageError = class extends Error {
17
+ source;
18
+ constructor(options = {}) {
19
+ super(invalidMessage, { cause: options.cause });
20
+ this.name = "InvalidMessageError";
21
+ this.source = options.source;
22
+ }
23
+ };
24
+ /** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */
25
+ function parseEvent(text) {
26
+ let superJsonParsed;
27
+ let superJsonError;
28
+ try {
29
+ superJsonParsed = parse(text);
30
+ } catch (error) {
31
+ superJsonError = error;
32
+ }
33
+ const potentialEvent = superJsonParsed && typeof superJsonParsed === "object" && "type" in superJsonParsed ? superJsonParsed : parsePlainJson(text, superJsonError);
34
+ const result = safeParse(eventEnvelopeSchema, potentialEvent);
35
+ if (!result.success) throw new InvalidMessageError({
36
+ cause: result.issues,
37
+ source: potentialEvent
38
+ });
39
+ return potentialEvent;
40
+ }
41
+ /** Serializes one AIRI websocket protocol event with SuperJSON. */
42
+ function stringifyEvent(event) {
43
+ return stringify(event);
44
+ }
45
+ function parsePlainJson(text, superJsonError) {
46
+ try {
47
+ return JSON.parse(text);
48
+ } catch (jsonError) {
49
+ throw new InvalidMessageError({
50
+ cause: superJsonError ?? jsonError,
51
+ source: text
52
+ });
53
+ }
54
+ }
55
+ //#endregion
6
56
  //#region src/client.ts
7
57
  function createInstanceId() {
8
58
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
@@ -10,19 +60,8 @@ function createInstanceId() {
10
60
  function createEventId() {
11
61
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
12
62
  }
13
- function createDeferredPromise() {
14
- let resolve;
15
- let reject;
16
- return {
17
- promise: new Promise((innerResolve, innerReject) => {
18
- resolve = innerResolve;
19
- reject = innerReject;
20
- }),
21
- reject,
22
- resolve
23
- };
24
- }
25
63
  function normalizeHeartbeatOptions(heartbeat) {
64
+ if (heartbeat === false) return false;
26
65
  const readTimeout = heartbeat?.readTimeout ?? 3e4;
27
66
  const pingInterval = heartbeat?.pingInterval ?? Math.max(1e3, Math.floor(readTimeout / 2));
28
67
  return {
@@ -31,55 +70,95 @@ function normalizeHeartbeatOptions(heartbeat) {
31
70
  message: heartbeat?.message ?? MessageHeartbeat.Ping
32
71
  };
33
72
  }
73
+ /** Wraps a text websocket connector with AIRI protocol serialization. */
74
+ function createTextProtocolConnector(textConnector) {
75
+ return { async connect(events) {
76
+ const connection = await textConnector.connect({
77
+ message(text) {
78
+ try {
79
+ events.message(parseEvent(text));
80
+ } catch (error) {
81
+ events.error(error);
82
+ }
83
+ },
84
+ close: (details) => events.close(details),
85
+ error: (error) => events.error(error)
86
+ });
87
+ return {
88
+ send: (message) => connection.send(stringifyEvent(message)),
89
+ close: (code, reason) => connection.close?.(code, reason),
90
+ ping: connection.ping,
91
+ pong: connection.pong
92
+ };
93
+ } };
94
+ }
95
+ function createDefaultProtocolConnector(url) {
96
+ return createTextProtocolConnector(createCrossWsConnector({ url }));
97
+ }
98
+ function normalizeOptions(options) {
99
+ const url = options.url ?? "ws://localhost:6121/ws";
100
+ const extension = options.extension ?? { id: options.name };
101
+ const identity = options.identity ?? {
102
+ id: createInstanceId(),
103
+ extension
104
+ };
105
+ return {
106
+ url,
107
+ name: options.name,
108
+ token: options.token,
109
+ connector: options.connector ?? createDefaultProtocolConnector(url),
110
+ handshake: options.handshake ?? "module",
111
+ connectTimeoutMs: options.connectTimeoutMs ?? 15e3,
112
+ possibleEvents: options.possibleEvents ?? [],
113
+ extension,
114
+ identity,
115
+ dependencies: options.dependencies ?? [],
116
+ configSchema: options.configSchema,
117
+ heartbeat: normalizeHeartbeatOptions(options.heartbeat),
118
+ autoConnect: options.autoConnect ?? true,
119
+ autoReconnect: options.autoReconnect ?? true,
120
+ maxReconnectAttempts: options.maxReconnectAttempts ?? -1,
121
+ onError: options.onError ?? (() => {}),
122
+ onClose: options.onClose ?? (() => {}),
123
+ onReady: options.onReady ?? (() => {}),
124
+ onStateChange: options.onStateChange ?? (() => {}),
125
+ onAnyMessage: options.onAnyMessage ?? (() => {}),
126
+ onAnySend: options.onAnySend ?? (() => {})
127
+ };
128
+ }
129
+ function createConnectionTimeoutError(timeout) {
130
+ return /* @__PURE__ */ new Error(`Connection timed out after ${timeout}ms`);
131
+ }
132
+ function createAbortError() {
133
+ return /* @__PURE__ */ new Error("Connection aborted");
134
+ }
34
135
  var Client = class {
35
- websocket;
36
- shouldClose = false;
37
- connectTask;
38
- heartbeatTimer;
39
- lastPingAt = 0;
40
- lastReadAt = 0;
41
- reconnectAttempts = 0;
42
- pendingReconnect = false;
43
- connectionAttempt;
44
- failureReason;
45
- status = "idle";
46
- identity;
47
- heartbeat;
48
- websocketConstructor;
49
136
  opts;
137
+ transport;
50
138
  eventListeners = /* @__PURE__ */ new Map();
51
139
  stateListeners = /* @__PURE__ */ new Set();
140
+ status = "idle";
141
+ connectTask;
142
+ failureReason;
52
143
  constructor(options) {
53
- const { websocketConstructor, ...clientOptions } = options;
54
- const identity = options.identity ?? {
55
- kind: "plugin",
56
- plugin: { id: options.name },
57
- id: createInstanceId()
58
- };
59
- const heartbeat = normalizeHeartbeatOptions(options.heartbeat);
60
- this.opts = {
61
- url: "ws://localhost:6121/ws",
62
- connectTimeoutMs: 15e3,
63
- onAnyMessage: () => {},
64
- onAnySend: () => {},
65
- possibleEvents: [],
66
- dependencies: [],
67
- configSchema: void 0,
68
- onError: () => {},
69
- onClose: () => {},
70
- onReady: () => {},
71
- onStateChange: () => {},
72
- autoConnect: true,
73
- autoReconnect: true,
74
- maxReconnectAttempts: -1,
75
- ...clientOptions,
76
- heartbeat,
77
- identity
78
- };
79
- this.identity = identity;
80
- this.heartbeat = heartbeat;
81
- this.websocketConstructor = websocketConstructor ?? NativeWebSocket;
82
- if (this.opts.autoConnect) this.connect();
144
+ this.opts = normalizeOptions(options);
145
+ this.transport = createClient$1({
146
+ connector: this.opts.connector,
147
+ reconnect: this.createReconnectOptions(),
148
+ heartbeat: this.createHeartbeatOptions(),
149
+ prepare: (context) => this.prepareProtocolConnection(context)
150
+ });
151
+ this.transport.onMessage(({ message }) => {
152
+ this.handleMessage(message);
153
+ });
154
+ this.transport.onStateChange(({ previousState, state }) => {
155
+ this.handleTransportStateChange(previousState, state);
156
+ });
157
+ if (this.opts.autoConnect) this.connect().catch((error) => {
158
+ const normalized = this.normalizeError(error, "Failed to connect websocket client");
159
+ this.failureReason = normalized;
160
+ this.opts.onError(normalized);
161
+ });
83
162
  }
84
163
  get connectionStatus() {
85
164
  return this.status;
@@ -88,16 +167,16 @@ var Client = class {
88
167
  return this.status === "ready";
89
168
  }
90
169
  get isSocketOpen() {
91
- return this.websocket?.readyState === this.websocketConstructor.OPEN;
170
+ return this.transport.state === "open" || this.transport.state === "preparing" || this.transport.state === "ready";
92
171
  }
93
172
  get lastError() {
94
173
  return this.failureReason;
95
174
  }
96
175
  async connect(options) {
97
- if (this.shouldClose) throw new Error("Client is closed");
98
176
  if (this.status === "ready") return;
99
- if (this.connectTask) return this.waitForConnection(this.connectTask, options);
100
- this.connectTask = this.runConnectLoop().finally(() => {
177
+ if (!this.connectTask && this.transport.state === "reconnecting") return this.waitForConnection(this.waitForReady(), options);
178
+ if (!this.connectTask && (this.transport.state === "open" || this.transport.state === "preparing")) return this.waitForConnection(this.waitForReady(), options);
179
+ if (!this.connectTask) this.connectTask = this.transport.connect().finally(() => {
101
180
  this.connectTask = void 0;
102
181
  });
103
182
  return this.waitForConnection(this.connectTask, options);
@@ -131,167 +210,162 @@ var Client = class {
131
210
  if (callback) {
132
211
  listeners.delete(callback);
133
212
  if (!listeners.size) this.eventListeners.delete(event);
134
- } else this.eventListeners.delete(event);
213
+ return;
214
+ }
215
+ this.eventListeners.delete(event);
135
216
  }
136
217
  send(data) {
137
- if (!this.isSocketOpen || !this.websocket) return false;
138
218
  const payload = this.createPayload(data);
139
- this.opts.onAnySend?.(payload);
140
- this.websocket.send(superjson.stringify(payload));
219
+ if (!this.transport.send(payload).ok) return false;
220
+ this.opts.onAnySend(payload);
141
221
  return true;
142
222
  }
143
223
  sendOrThrow(data) {
144
224
  if (!this.send(data)) throw new Error(`Client is not connected, current status: ${this.status}`);
145
225
  }
146
- sendRaw(data) {
147
- if (!this.isSocketOpen || !this.websocket) return false;
148
- this.websocket.send(data);
149
- return true;
226
+ close(code, reason) {
227
+ this.transport.close(code, reason);
150
228
  }
151
- close() {
152
- this.shouldClose = true;
153
- this.pendingReconnect = false;
154
- this.transitionTo("closing");
155
- this.stopHeartbeat();
156
- this.rejectAttempt(/* @__PURE__ */ new Error("Client closed"));
157
- const websocket = this.websocket;
158
- this.websocket = void 0;
159
- if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) websocket.close();
160
- this.transitionTo("closed");
161
- }
162
- async runConnectLoop() {
163
- this.pendingReconnect = false;
164
- while (!this.shouldClose) {
165
- const reconnecting = this.reconnectAttempts > 0;
166
- this.transitionTo(reconnecting ? "reconnecting" : "connecting");
167
- try {
168
- await this.connectOnce();
169
- this.reconnectAttempts = 0;
170
- return;
171
- } catch (error) {
172
- const normalizedError = error instanceof Error ? error : new Error(errorMessageFrom(error) ?? "Failed to connect websocket client");
173
- this.failureReason = normalizedError;
174
- this.opts.onError?.(normalizedError);
175
- if (this.shouldClose) throw normalizedError;
176
- if (isTerminalAuthenticationServerErrorMessage(normalizedError.message)) {
177
- this.transitionTo("failed");
178
- throw normalizedError;
179
- }
180
- if (!this.opts.autoReconnect && reconnecting) {
181
- this.transitionTo("failed");
182
- throw normalizedError;
183
- }
184
- if (!this.canRetry()) {
185
- this.transitionTo("failed");
186
- throw normalizedError;
187
- }
188
- const delay = this.getReconnectDelay(this.reconnectAttempts);
189
- this.reconnectAttempts += 1;
190
- await sleep(delay);
229
+ createReconnectOptions() {
230
+ if (!this.opts.autoReconnect) return false;
231
+ return {
232
+ retries: (attempt, error) => {
233
+ if (isTerminalAuthenticationServerErrorMessage(this.normalizeError(error, "Failed to connect websocket client").message)) return false;
234
+ return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts;
235
+ },
236
+ onFailed: (error) => {
237
+ const normalized = this.normalizeError(error, "Failed to connect websocket client");
238
+ if (this.failureReason === normalized) return;
239
+ this.failureReason = normalized;
240
+ this.opts.onError(normalized);
191
241
  }
192
- }
193
- throw new Error("Client is closed");
194
- }
195
- connectOnce() {
196
- const WebSocketConstructor = this.websocketConstructor;
197
- const ws = new WebSocketConstructor(this.opts.url);
198
- this.websocket = ws;
199
- this.lastReadAt = Date.now();
200
- this.lastPingAt = 0;
201
- const deferred = createDeferredPromise();
202
- const attempt = {
203
- announced: false,
204
- authenticated: !this.opts.token,
205
- promise: deferred.promise,
206
- reject: deferred.reject,
207
- resolve: deferred.resolve,
208
- socket: ws
209
- };
210
- this.connectionAttempt = attempt;
211
- const isCurrentSocket = () => this.websocket === ws;
212
- const connectTimeoutMs = this.opts.connectTimeoutMs;
213
- const connectTimer = setTimeout(() => {
214
- if (ws.readyState === WebSocket.OPEN) return;
215
- ws.close();
216
- deferred.reject(/* @__PURE__ */ new Error(`Connection timeout after ${connectTimeoutMs}ms`));
217
- }, connectTimeoutMs);
218
- const clearConnectTimer = () => {
219
- clearTimeout(connectTimer);
220
242
  };
221
- ws.onmessage = (event) => {
222
- if (!isCurrentSocket()) return;
223
- this.handleMessage(event);
224
- };
225
- ws.onerror = (event) => {
226
- clearConnectTimer();
227
- if (!isCurrentSocket()) return;
228
- const error = event?.error instanceof Error ? event.error : /* @__PURE__ */ new Error("WebSocket error");
229
- if (this.connectionAttempt) this.handleSocketFailure(error, ws);
230
- else {
231
- this.opts.onError?.(error);
232
- this.reconnectAfterProtocolError(error);
233
- }
243
+ }
244
+ createHeartbeatOptions() {
245
+ if (!this.opts.heartbeat) return false;
246
+ return {
247
+ mode: "message",
248
+ interval: this.opts.heartbeat.pingInterval,
249
+ timeout: this.opts.heartbeat.readTimeout,
250
+ message: () => this.createPayload({
251
+ type: "transport:connection:heartbeat",
252
+ data: {
253
+ kind: MessageHeartbeatKind.Ping,
254
+ message: this.opts.heartbeat ? this.opts.heartbeat.message : MessageHeartbeat.Ping,
255
+ at: Date.now()
256
+ }
257
+ })
234
258
  };
235
- ws.onclose = () => {
236
- clearConnectTimer();
237
- if (!isCurrentSocket()) return;
238
- const wasReady = this.status === "ready";
239
- this.cleanupSocket(ws);
240
- this.opts.onClose?.();
241
- if (this.shouldClose) return;
242
- if (wasReady && this.opts.autoReconnect) {
243
- this.pendingReconnect = true;
244
- this.transitionTo("idle");
245
- this.connect();
246
- return;
259
+ }
260
+ async prepareProtocolConnection(context) {
261
+ if (this.opts.handshake === "manual") {
262
+ if (!context.reconnecting) return;
263
+ this.transitionTo("authenticating");
264
+ await this.waitForManualReconnectHandshake(context);
265
+ return;
266
+ }
267
+ if (this.opts.token) {
268
+ this.transitionTo("authenticating");
269
+ context.send(this.createPayload({
270
+ type: "module:authenticate",
271
+ data: { token: this.opts.token }
272
+ }));
273
+ await context.waitFor((message) => {
274
+ const result = this.consumePrepareMessage(message);
275
+ if (result.error) throw result.error;
276
+ return message.type === "module:authenticated" && message.data.authenticated === true;
277
+ }, { timeout: this.opts.connectTimeoutMs });
278
+ }
279
+ this.transitionTo("announcing");
280
+ context.send(this.createPayload({
281
+ type: "extension:module:announce",
282
+ data: {
283
+ name: this.opts.name,
284
+ identity: this.opts.identity,
285
+ possibleEvents: this.opts.possibleEvents,
286
+ configSchema: this.opts.configSchema,
287
+ dependencies: this.opts.dependencies
247
288
  }
248
- this.rejectAttempt(/* @__PURE__ */ new Error("WebSocket closed"));
249
- };
250
- ws.onopen = () => {
251
- clearConnectTimer();
252
- if (!isCurrentSocket()) return;
253
- this.startHeartbeat();
254
- if (this.opts.token) {
255
- attempt.authenticated = false;
256
- this.transitionTo("authenticating");
257
- this.tryAuthenticate();
258
- } else {
259
- attempt.authenticated = true;
260
- this.transitionTo("announcing");
261
- this.tryAnnounce();
289
+ }));
290
+ await context.waitFor((message) => {
291
+ const result = this.consumePrepareMessage(message);
292
+ if (result.error) throw result.error;
293
+ return result.ready;
294
+ }, { timeout: this.opts.connectTimeoutMs });
295
+ }
296
+ async waitForManualReconnectHandshake(context) {
297
+ await context.waitFor((message) => {
298
+ const result = this.consumePrepareMessage(message);
299
+ if (result.error) throw result.error;
300
+ return message.type === "peer:authenticated" && message.data.authenticated === true;
301
+ }, { timeout: this.opts.connectTimeoutMs });
302
+ this.transitionTo("announcing");
303
+ await context.waitFor((message) => {
304
+ const result = this.consumePrepareMessage(message);
305
+ if (result.error) throw result.error;
306
+ return message.type === "extension:announced" && message.data.identity.id === this.opts.extension.id;
307
+ }, { timeout: this.opts.connectTimeoutMs });
308
+ }
309
+ consumePrepareMessage(message) {
310
+ const error = this.errorFromServerEvent(message);
311
+ if (error) {
312
+ this.failureReason = error;
313
+ return {
314
+ ready: false,
315
+ error
316
+ };
317
+ }
318
+ if (message.type === "extension:module:announced") return { ready: this.isSelfModuleAnnouncement(message) };
319
+ if (message.type === "registry:modules:sync") return { ready: this.hasSelfModuleInRegistrySync(message) };
320
+ return { ready: false };
321
+ }
322
+ async handleMessage(message) {
323
+ this.opts.onAnyMessage(message);
324
+ const error = this.errorFromServerEvent(message);
325
+ if (error) {
326
+ this.failureReason = error;
327
+ this.opts.onError(error);
328
+ }
329
+ if (message.type === "transport:connection:heartbeat" && message.data.kind === MessageHeartbeatKind.Ping) this.send({
330
+ type: "transport:connection:heartbeat",
331
+ data: {
332
+ kind: MessageHeartbeatKind.Pong,
333
+ message: MessageHeartbeat.Pong,
334
+ at: Date.now()
262
335
  }
263
- };
264
- return attempt.promise;
265
- }
266
- handleSocketFailure(error, socket) {
267
- if (socket && this.websocket !== socket) return;
268
- const currentSocket = socket ?? this.websocket;
269
- this.cleanupSocket(socket);
270
- if (currentSocket && currentSocket.readyState !== this.websocketConstructor.CLOSED && currentSocket.readyState !== this.websocketConstructor.CLOSING) currentSocket.close();
271
- this.rejectAttempt(error);
272
- }
273
- cleanupSocket(socket) {
274
- if (socket && this.websocket !== socket) return;
275
- this.stopHeartbeat();
276
- if (!socket || this.websocket === socket) this.websocket = void 0;
277
- }
278
- rejectAttempt(error) {
279
- if (!this.connectionAttempt) return;
280
- const attempt = this.connectionAttempt;
281
- this.connectionAttempt = void 0;
282
- attempt.reject(error);
283
- }
284
- resolveAttempt() {
285
- if (!this.connectionAttempt) return;
286
- const attempt = this.connectionAttempt;
287
- this.connectionAttempt = void 0;
288
- attempt.resolve();
289
- }
290
- canRetry() {
291
- return this.opts.maxReconnectAttempts === -1 || this.reconnectAttempts < this.opts.maxReconnectAttempts;
292
- }
293
- getReconnectDelay(attempts) {
294
- return Math.min(2 ** attempts * 1e3, 3e4);
336
+ });
337
+ const listeners = this.eventListeners.get(message.type);
338
+ if (!listeners?.size) return;
339
+ const results = await Promise.allSettled(Array.from(listeners).map((listener) => Promise.resolve(listener(message))));
340
+ for (const result of results) if (result.status === "rejected") {
341
+ this.failureReason = this.normalizeError(result.reason, "Client event listener failed");
342
+ this.opts.onError(result.reason);
343
+ }
344
+ }
345
+ handleTransportStateChange(previousState, state) {
346
+ if (state === "ready") {
347
+ this.transitionTo("ready");
348
+ this.opts.onReady();
349
+ return;
350
+ }
351
+ const nextStatus = this.mapTransportStatus(state);
352
+ if (!nextStatus) return;
353
+ this.transitionTo(nextStatus);
354
+ if (previousState === "ready" && state === "reconnecting") this.opts.onClose();
355
+ else if (state === "closed") this.opts.onClose();
356
+ }
357
+ mapTransportStatus(state) {
358
+ switch (state) {
359
+ case "idle":
360
+ case "connecting":
361
+ case "reconnecting":
362
+ case "closing":
363
+ case "closed":
364
+ case "failed": return state;
365
+ case "open":
366
+ case "preparing":
367
+ case "ready": return;
368
+ }
295
369
  }
296
370
  transitionTo(status) {
297
371
  if (this.status === status) return;
@@ -301,26 +375,24 @@ var Client = class {
301
375
  previousStatus,
302
376
  status
303
377
  };
304
- this.opts.onStateChange?.(context);
378
+ this.opts.onStateChange(context);
305
379
  for (const listener of this.stateListeners) listener(context);
306
380
  }
307
381
  async waitForConnection(connectPromise, options) {
308
382
  if (!options?.timeout && !options?.abortSignal) return connectPromise;
309
383
  const timeout = options?.timeout;
310
- if (typeof timeout !== "undefined" && timeout <= 0) throw new Error(`Connection timed out after ${timeout}ms`);
384
+ if (typeof timeout !== "undefined" && timeout <= 0) throw createConnectionTimeoutError(timeout);
311
385
  const abortSignal = options?.abortSignal;
312
- if (abortSignal?.aborted) throw new Error("Connection aborted");
386
+ if (abortSignal?.aborted) throw createAbortError();
313
387
  let timeoutHandle;
314
388
  let removeAbortListener;
315
389
  try {
316
390
  await Promise.race([connectPromise, new Promise((_, reject) => {
317
391
  if (typeof timeout !== "undefined") timeoutHandle = setTimeout(() => {
318
- reject(/* @__PURE__ */ new Error(`Connection timed out after ${timeout}ms`));
392
+ reject(createConnectionTimeoutError(timeout));
319
393
  }, timeout);
320
394
  if (abortSignal) {
321
- const onAbort = () => {
322
- reject(/* @__PURE__ */ new Error("Connection aborted"));
323
- };
395
+ const onAbort = () => reject(createAbortError());
324
396
  abortSignal.addEventListener("abort", onAbort, { once: true });
325
397
  removeAbortListener = () => abortSignal.removeEventListener("abort", onAbort);
326
398
  }
@@ -330,187 +402,128 @@ var Client = class {
330
402
  removeAbortListener?.();
331
403
  }
332
404
  }
333
- tryAnnounce() {
334
- this.sendOrThrow({
335
- type: "module:announce",
336
- data: {
337
- name: this.opts.name,
338
- identity: this.identity,
339
- possibleEvents: this.opts.possibleEvents,
340
- dependencies: this.opts.dependencies,
341
- configSchema: this.opts.configSchema
342
- }
343
- });
344
- }
345
- tryAuthenticate() {
346
- if (!this.opts.token) return;
347
- this.sendOrThrow({
348
- type: "module:authenticate",
349
- data: { token: this.opts.token }
350
- });
351
- }
352
- async handleMessage(event) {
353
- this.lastReadAt = Date.now();
354
- try {
355
- const data = this.parseMessage(event.data);
356
- this.opts.onAnyMessage?.(data);
357
- await this.handleControlMessage(data);
358
- await this.dispatchMessage(data);
359
- } catch (error) {
360
- const normalizedError = error instanceof Error ? error : new Error(errorMessageFrom(error) ?? "Failed to handle websocket message");
361
- this.opts.onError?.(normalizedError);
362
- if (this.connectionAttempt && this.status !== "ready") this.handleSocketFailure(normalizedError);
363
- }
364
- }
365
- parseMessage(raw) {
366
- try {
367
- const parsed = superjson.parse(raw);
368
- if (parsed && typeof parsed === "object" && "type" in parsed) return parsed;
369
- } catch {}
370
- const parsed = JSON.parse(raw);
371
- if (!parsed || typeof parsed !== "object" || !("type" in parsed)) throw new Error("Received invalid websocket message");
372
- return parsed;
373
- }
374
- async handleControlMessage(data) {
375
- switch (data.type) {
376
- case "error": {
377
- const message = data.data?.message;
378
- if (!message || typeof message !== "string") return;
379
- const parsedServerError = parseServerErrorMessage(message);
380
- if (parsedServerError.authentication) {
381
- const error = new Error(message);
382
- if (parsedServerError.terminal) {
383
- this.shouldClose = true;
384
- this.handleSocketFailure(error);
385
- this.transitionTo("failed");
386
- return;
387
- }
388
- await this.reconnectAfterProtocolError(error);
405
+ waitForReady() {
406
+ if (this.status === "ready") return Promise.resolve();
407
+ return new Promise((resolve, reject) => {
408
+ const dispose = this.onConnectionStateChange(({ status }) => {
409
+ if (status === "ready") {
410
+ dispose();
411
+ resolve();
389
412
  return;
390
413
  }
391
- if (parsedServerError.code !== "unknown") throw new Error(parsedServerError.message);
392
- throw new Error(message);
393
- }
394
- case "module:authenticated":
395
- if (data.data.authenticated) {
396
- if (!this.connectionAttempt || this.connectionAttempt.authenticated) return;
397
- this.connectionAttempt.authenticated = true;
398
- this.transitionTo("announcing");
399
- this.tryAnnounce();
400
- return;
414
+ if (status === "failed" || status === "closed") {
415
+ dispose();
416
+ reject(this.failureReason ?? /* @__PURE__ */ new Error(`Client connection ended with status: ${status}`));
401
417
  }
402
- throw new Error("Authentication failed");
403
- case "module:announced":
404
- if (!this.isSelfAnnouncement(data)) return;
405
- if (this.status === "ready") return;
406
- if (this.connectionAttempt) this.connectionAttempt.announced = true;
407
- this.reconnectAttempts = 0;
408
- this.transitionTo("ready");
409
- this.resolveAttempt();
410
- this.opts.onReady?.();
411
- return;
412
- case "registry:modules:sync":
413
- if (this.status !== "announcing" || !this.connectionAttempt) return;
414
- if (!(data.data?.modules ?? []).some((m) => m.name === this.opts.name && m.identity?.id === this.identity.id)) return;
415
- if (this.connectionAttempt) this.connectionAttempt.announced = true;
416
- this.reconnectAttempts = 0;
417
- this.transitionTo("ready");
418
- this.resolveAttempt();
419
- this.opts.onReady?.();
420
- return;
421
- case "transport:connection:heartbeat": if (data.data.kind === MessageHeartbeatKind.Ping) this.sendHeartbeatPong();
422
- }
418
+ });
419
+ });
423
420
  }
424
- isSelfAnnouncement(event) {
425
- return event.data.name === this.opts.name && event.data.identity?.id === this.identity.id;
421
+ errorFromServerEvent(message) {
422
+ if (message.type !== "error") return;
423
+ const errorMessage = typeof message.data.message === "string" ? message.data.message : "Unknown server error";
424
+ const parsed = parseServerErrorMessage(errorMessage);
425
+ if (parsed.code === "unknown") return new Error(errorMessage);
426
+ return new Error(parsed.message);
426
427
  }
427
- async dispatchMessage(data) {
428
- const listeners = this.eventListeners.get(data.type);
429
- if (!listeners?.size) return;
430
- const results = await Promise.allSettled(Array.from(listeners).map((listener) => Promise.resolve(listener(data))));
431
- for (const result of results) if (result.status === "rejected") this.opts.onError?.(result.reason);
428
+ normalizeError(error, fallback) {
429
+ return error instanceof Error ? error : new Error(errorMessageFrom(error) ?? fallback);
430
+ }
431
+ isSelfModuleAnnouncement(event) {
432
+ return event.data.name === this.opts.name && event.data.identity?.id === this.opts.identity.id;
433
+ }
434
+ hasSelfModuleInRegistrySync(event) {
435
+ return event.data.modules.some((module) => module.name === this.opts.name && module.identity?.id === this.opts.identity.id);
432
436
  }
433
437
  createPayload(data) {
434
438
  return {
435
439
  ...data,
436
440
  metadata: {
437
- ...data?.metadata,
438
- source: data?.metadata?.source ?? this.identity,
441
+ ...data.metadata,
442
+ source: data.metadata?.source ?? {
443
+ kind: "plugin",
444
+ ...this.opts.identity,
445
+ plugin: { id: this.opts.extension.id }
446
+ },
439
447
  event: {
440
- id: data?.metadata?.event?.id ?? createEventId(),
441
- ...data?.metadata?.event
448
+ ...data.metadata?.event,
449
+ id: data.metadata?.event?.id ?? createEventId()
442
450
  }
443
451
  }
444
452
  };
445
453
  }
446
- startHeartbeat() {
447
- if (!this.heartbeat.readTimeout || !this.heartbeat.pingInterval) return;
448
- this.stopHeartbeat();
449
- this.lastReadAt = Date.now();
450
- this.lastPingAt = 0;
451
- const interval = Math.max(1e3, Math.min(this.heartbeat.pingInterval, Math.floor(this.heartbeat.readTimeout / 2)));
452
- this.heartbeatTimer = setInterval(() => {
453
- if (!this.isSocketOpen) return;
454
- const now = Date.now();
455
- if (now - this.lastReadAt > this.heartbeat.readTimeout) {
456
- this.reconnectAfterProtocolError(/* @__PURE__ */ new Error(`Read timeout after ${this.heartbeat.readTimeout}ms`));
457
- return;
458
- }
459
- if (now - this.lastPingAt >= this.heartbeat.pingInterval) this.sendHeartbeatPing();
460
- }, interval);
461
- }
462
- stopHeartbeat() {
463
- if (!this.heartbeatTimer) return;
464
- clearInterval(this.heartbeatTimer);
465
- this.heartbeatTimer = void 0;
466
- }
467
- sendNativeHeartbeat(kind) {
468
- const websocket = this.websocket;
469
- if (kind === "ping") websocket.ping?.();
470
- else websocket.pong?.();
471
- }
472
- sendHeartbeatPing() {
473
- this.lastPingAt = Date.now();
474
- this.send({
475
- type: "transport:connection:heartbeat",
454
+ };
455
+ function createClient(options) {
456
+ return new Client(options);
457
+ }
458
+ //#endregion
459
+ //#region src/extension-peer.ts
460
+ /**
461
+ * Provides extension-level protocol helpers over a server-sdk protocol client.
462
+ */
463
+ var WebSocketExtensionPeer = class {
464
+ client;
465
+ extension;
466
+ constructor(options) {
467
+ this.extension = options.extension;
468
+ this.client = options.client ?? createClient({
469
+ ...options.clientOptions,
470
+ name: options.extension.id,
471
+ handshake: "manual",
472
+ autoConnect: options.clientOptions?.autoConnect ?? false,
473
+ autoReconnect: options.clientOptions?.autoReconnect ?? false
474
+ });
475
+ }
476
+ connect(options) {
477
+ return this.client.connect(options);
478
+ }
479
+ authenticatePeer(input = {}) {
480
+ this.client.sendOrThrow({
481
+ type: "peer:authenticate",
482
+ data: input
483
+ });
484
+ }
485
+ announceExtension(input = {}) {
486
+ this.client.sendOrThrow({
487
+ type: "extension:announce",
476
488
  data: {
477
- kind: MessageHeartbeatKind.Ping,
478
- message: this.heartbeat.message,
479
- at: Date.now()
489
+ identity: this.extension,
490
+ permissions: input.permissions
480
491
  }
481
492
  });
482
- this.sendNativeHeartbeat("ping");
483
493
  }
484
- sendHeartbeatPong() {
485
- this.send({
486
- type: "transport:connection:heartbeat",
494
+ announceModule(input) {
495
+ this.client.sendOrThrow({
496
+ type: "extension:module:announce",
487
497
  data: {
488
- kind: MessageHeartbeatKind.Pong,
489
- message: MessageHeartbeat.Pong,
490
- at: Date.now()
498
+ name: input.name,
499
+ identity: {
500
+ id: input.id,
501
+ extension: this.extension,
502
+ labels: input.labels
503
+ },
504
+ possibleEvents: input.possibleEvents ?? [],
505
+ permissions: input.permissions,
506
+ configSchema: input.configSchema,
507
+ dependencies: input.dependencies
491
508
  }
492
509
  });
493
- this.sendNativeHeartbeat("pong");
494
- }
495
- async reconnectAfterProtocolError(error) {
496
- if (this.shouldClose || this.pendingReconnect) return;
497
- this.pendingReconnect = true;
498
- const hadSocket = !!this.websocket;
499
- if (!this.connectionAttempt || this.status === "ready") this.opts.onError?.(error);
500
- const websocket = this.websocket;
501
- this.cleanupSocket(websocket);
502
- this.rejectAttempt(error);
503
- if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) websocket.close();
504
- if (hadSocket) this.opts.onClose?.();
505
- if (!this.opts.autoReconnect) {
506
- this.transitionTo("failed");
507
- return;
508
- }
509
- this.transitionTo("idle");
510
- this.connect();
510
+ }
511
+ send(data) {
512
+ return this.client.send(data);
513
+ }
514
+ onEvent(event, callback) {
515
+ if (!this.client.onEvent) throw new Error("Wrapped extension peer client does not support event listeners.");
516
+ return this.client.onEvent(event, callback);
517
+ }
518
+ close() {
519
+ this.client.close();
511
520
  }
512
521
  };
522
+ /** Creates a websocket extension peer over a server-sdk protocol client. */
523
+ function createWebSocketExtensionPeer(options) {
524
+ return new WebSocketExtensionPeer(options);
525
+ }
513
526
  //#endregion
514
- export { Client, ContextUpdateStrategy, WebSocketEventSource };
527
+ export { Client, ContextUpdateStrategy, InvalidMessageError, WebSocketEventSource, WebSocketExtensionPeer, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
515
528
 
516
529
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import type {\n MetadataEventSource,\n ModuleConfigSchema,\n ModuleDependency,\n WebSocketBaseEvent,\n WebSocketEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport type { WebSocketLike, WebSocketLikeConstructor, WebSocketMessageEventLike } from './websocket-like'\n\nimport NativeWebSocket from 'crossws/websocket'\nimport superjson from 'superjson'\n\nimport { errorMessageFrom, sleep } from '@moeru/std'\nimport { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from '@proj-airi/server-shared'\nimport { MessageHeartbeat, MessageHeartbeatKind } from '@proj-airi/server-shared/types'\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 websocketConstructor?: WebSocketLikeConstructor\n\n connectTimeoutMs?: number\n possibleEvents?: Array<keyof WebSocketEvents<C>>\n identity?: MetadataEventSource\n dependencies?: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat?: 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 ConnectionAttempt {\n announced: boolean\n authenticated: boolean\n promise: Promise<void>\n reject: (error: Error) => void\n resolve: () => void\n socket: WebSocketLike\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 createDeferredPromise() {\n let resolve!: () => void\n let reject!: (error: Error) => void\n\n const promise = new Promise<void>((innerResolve, innerReject) => {\n resolve = innerResolve\n reject = innerReject\n })\n\n return { promise, reject, resolve }\n}\n\nfunction normalizeHeartbeatOptions(heartbeat?: ClientHeartbeatOptions): Required<ClientHeartbeatOptions> {\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\nexport class Client<C = undefined> {\n private websocket?: WebSocketLike\n private shouldClose = false\n private connectTask?: Promise<void>\n private heartbeatTimer?: ReturnType<typeof setInterval>\n private lastPingAt = 0\n private lastReadAt = 0\n private reconnectAttempts = 0\n private pendingReconnect = false\n private connectionAttempt?: ConnectionAttempt\n private failureReason?: Error\n private status: ClientStatus = 'idle'\n private readonly identity: MetadataEventSource\n private readonly heartbeat: Required<ClientHeartbeatOptions>\n private readonly websocketConstructor: WebSocketLikeConstructor\n\n private readonly opts:\n & Required<Omit<ClientOptions<C>, 'token' | 'heartbeat' | 'websocketConstructor' | 'configSchema'>>\n & Pick<ClientOptions<C>, 'token' | 'heartbeat' | 'configSchema'>\n\n private readonly eventListeners = new Map<\n keyof WebSocketEvents<C>,\n Set<(data: WebSocketBaseEvent<any, any>) => void | Promise<void>>\n >()\n\n private readonly stateListeners = new Set<(context: ClientStateChangeContext) => void>()\n\n constructor(options: ClientOptions<C>) {\n const { websocketConstructor, ...clientOptions } = options\n const identity = options.identity ?? {\n kind: 'plugin',\n plugin: { id: options.name },\n id: createInstanceId(),\n }\n\n const heartbeat = normalizeHeartbeatOptions(options.heartbeat)\n\n this.opts = {\n url: 'ws://localhost:6121/ws',\n connectTimeoutMs: 15_000,\n onAnyMessage: () => {},\n onAnySend: () => {},\n possibleEvents: [],\n dependencies: [],\n configSchema: undefined,\n onError: () => {},\n onClose: () => {},\n onReady: () => {},\n onStateChange: () => {},\n autoConnect: true,\n autoReconnect: true,\n maxReconnectAttempts: -1,\n ...clientOptions,\n heartbeat,\n identity,\n }\n\n this.identity = identity\n this.heartbeat = heartbeat\n this.websocketConstructor = websocketConstructor ?? (NativeWebSocket as unknown as WebSocketLikeConstructor)\n\n if (this.opts.autoConnect) {\n void this.connect()\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.websocket?.readyState === this.websocketConstructor.OPEN\n }\n\n get lastError() {\n return this.failureReason\n }\n\n async connect(options?: ConnectOptions) {\n if (this.shouldClose) {\n throw new Error('Client is closed')\n }\n\n if (this.status === 'ready') {\n return\n }\n\n if (this.connectTask) {\n return this.waitForConnection(this.connectTask, options)\n }\n\n this.connectTask = this.runConnectLoop().finally(() => {\n this.connectTask = undefined\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 any)\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 any)\n if (!listeners.size) {\n this.eventListeners.delete(event)\n }\n }\n else {\n this.eventListeners.delete(event)\n }\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n if (!this.isSocketOpen || !this.websocket) {\n return false\n }\n\n const payload = this.createPayload(data)\n this.opts.onAnySend?.(payload)\n this.websocket.send(superjson.stringify(payload))\n\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 sendRaw(data: string | ArrayBufferLike | ArrayBufferView): boolean {\n if (!this.isSocketOpen || !this.websocket) {\n return false\n }\n\n this.websocket.send(data)\n return true\n }\n\n close(): void {\n this.shouldClose = true\n this.pendingReconnect = false\n this.transitionTo('closing')\n this.stopHeartbeat()\n this.rejectAttempt(new Error('Client closed'))\n\n const websocket = this.websocket\n this.websocket = undefined\n\n if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) {\n websocket.close()\n }\n\n this.transitionTo('closed')\n }\n\n private async runConnectLoop() {\n this.pendingReconnect = false\n\n while (!this.shouldClose) {\n const reconnecting = this.reconnectAttempts > 0\n this.transitionTo(reconnecting ? 'reconnecting' : 'connecting')\n\n try {\n await this.connectOnce()\n this.reconnectAttempts = 0\n return\n }\n catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(errorMessageFrom(error) ?? 'Failed to connect websocket client')\n this.failureReason = normalizedError\n this.opts.onError?.(normalizedError)\n\n if (this.shouldClose) {\n throw normalizedError\n }\n\n if (isTerminalAuthenticationServerErrorMessage(normalizedError.message)) {\n this.transitionTo('failed')\n throw normalizedError\n }\n\n if (!this.opts.autoReconnect && reconnecting) {\n this.transitionTo('failed')\n throw normalizedError\n }\n\n if (!this.canRetry()) {\n this.transitionTo('failed')\n throw normalizedError\n }\n\n const delay = this.getReconnectDelay(this.reconnectAttempts)\n this.reconnectAttempts += 1\n await sleep(delay)\n }\n }\n\n throw new Error('Client is closed')\n }\n\n private connectOnce(): Promise<void> {\n const WebSocketConstructor = this.websocketConstructor\n const ws = new WebSocketConstructor(this.opts.url)\n this.websocket = ws\n this.lastReadAt = Date.now()\n this.lastPingAt = 0\n\n const deferred = createDeferredPromise()\n const attempt: ConnectionAttempt = {\n announced: false,\n authenticated: !this.opts.token,\n promise: deferred.promise,\n reject: deferred.reject,\n resolve: deferred.resolve,\n socket: ws,\n }\n\n this.connectionAttempt = attempt\n\n const isCurrentSocket = () => this.websocket === ws\n const connectTimeoutMs = this.opts.connectTimeoutMs\n const connectTimer = setTimeout(() => {\n if (ws.readyState === WebSocket.OPEN) {\n return\n }\n\n ws.close()\n deferred.reject(new Error(`Connection timeout after ${connectTimeoutMs}ms`))\n }, connectTimeoutMs)\n\n const clearConnectTimer = () => {\n clearTimeout(connectTimer)\n }\n\n ws.onmessage = (event: WebSocketMessageEventLike) => {\n if (!isCurrentSocket()) {\n return\n }\n\n void this.handleMessage(event)\n }\n\n ws.onerror = (event: any) => {\n clearConnectTimer()\n\n if (!isCurrentSocket()) {\n return\n }\n\n const error = event?.error instanceof Error ? event.error : new Error('WebSocket error')\n if (this.connectionAttempt) {\n this.handleSocketFailure(error, ws)\n }\n else {\n this.opts.onError?.(error)\n void this.reconnectAfterProtocolError(error)\n }\n }\n\n ws.onclose = () => {\n clearConnectTimer()\n\n if (!isCurrentSocket()) {\n return\n }\n\n const wasReady = this.status === 'ready'\n this.cleanupSocket(ws)\n this.opts.onClose?.()\n\n if (this.shouldClose) {\n return\n }\n\n if (wasReady && this.opts.autoReconnect) {\n this.pendingReconnect = true\n this.transitionTo('idle')\n void this.connect()\n return\n }\n\n this.rejectAttempt(new Error('WebSocket closed'))\n }\n\n ws.onopen = () => {\n clearConnectTimer()\n\n if (!isCurrentSocket()) {\n return\n }\n\n this.startHeartbeat()\n\n if (this.opts.token) {\n attempt.authenticated = false\n this.transitionTo('authenticating')\n this.tryAuthenticate()\n }\n else {\n attempt.authenticated = true\n this.transitionTo('announcing')\n this.tryAnnounce()\n }\n }\n\n return attempt.promise\n }\n\n private handleSocketFailure(error: Error, socket?: WebSocketLike) {\n if (socket && this.websocket !== socket) {\n return\n }\n\n const currentSocket = socket ?? this.websocket\n this.cleanupSocket(socket)\n\n if (currentSocket && currentSocket.readyState !== this.websocketConstructor.CLOSED && currentSocket.readyState !== this.websocketConstructor.CLOSING) {\n currentSocket.close()\n }\n\n this.rejectAttempt(error)\n }\n\n private cleanupSocket(socket?: WebSocketLike) {\n if (socket && this.websocket !== socket) {\n return\n }\n\n this.stopHeartbeat()\n\n if (!socket || this.websocket === socket) {\n this.websocket = undefined\n }\n }\n\n private rejectAttempt(error: Error) {\n if (!this.connectionAttempt) {\n return\n }\n\n const attempt = this.connectionAttempt\n this.connectionAttempt = undefined\n attempt.reject(error)\n }\n\n private resolveAttempt() {\n if (!this.connectionAttempt) {\n return\n }\n\n const attempt = this.connectionAttempt\n this.connectionAttempt = undefined\n attempt.resolve()\n }\n\n private canRetry() {\n return this.opts.maxReconnectAttempts === -1 || this.reconnectAttempts < this.opts.maxReconnectAttempts\n }\n\n private getReconnectDelay(attempts: number) {\n return Math.min(2 ** attempts * 1_000, 30_000)\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 new Error(`Connection timed out after ${timeout}ms`)\n }\n\n const abortSignal = options?.abortSignal\n if (abortSignal?.aborted) {\n throw new Error('Connection aborted')\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(new Error(`Connection timed out after ${timeout}ms`))\n }, timeout)\n }\n\n if (abortSignal) {\n const onAbort = () => {\n reject(new Error('Connection aborted'))\n }\n\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 tryAnnounce() {\n this.sendOrThrow({\n type: 'module:announce',\n data: {\n name: this.opts.name,\n identity: this.identity,\n possibleEvents: this.opts.possibleEvents,\n dependencies: this.opts.dependencies,\n configSchema: this.opts.configSchema,\n },\n })\n }\n\n private tryAuthenticate() {\n if (!this.opts.token) {\n return\n }\n\n this.sendOrThrow({\n type: 'module:authenticate',\n data: { token: this.opts.token },\n })\n }\n\n private async handleMessage(event: WebSocketMessageEventLike) {\n this.lastReadAt = Date.now()\n\n try {\n const data = this.parseMessage(event.data as string)\n this.opts.onAnyMessage?.(data)\n\n await this.handleControlMessage(data)\n await this.dispatchMessage(data)\n }\n catch (error) {\n const normalizedError = error instanceof Error ? error : new Error(errorMessageFrom(error) ?? 'Failed to handle websocket message')\n this.opts.onError?.(normalizedError)\n\n if (this.connectionAttempt && this.status !== 'ready') {\n this.handleSocketFailure(normalizedError)\n }\n }\n }\n\n private parseMessage(raw: string): WebSocketEvent<C> {\n try {\n const parsed = superjson.parse<WebSocketEvent<C> | undefined>(raw)\n if (parsed && typeof parsed === 'object' && 'type' in parsed) {\n return parsed\n }\n }\n catch {\n // Try standard JSON next.\n }\n\n const parsed = JSON.parse(raw) as WebSocketEvent<C>\n if (!parsed || typeof parsed !== 'object' || !('type' in parsed)) {\n throw new Error('Received invalid websocket message')\n }\n\n return parsed\n }\n\n private async handleControlMessage(data: WebSocketEvent<C>) {\n switch (data.type) {\n case 'error': {\n const message = data.data?.message\n if (!message || typeof message !== 'string') {\n return\n }\n\n const parsedServerError = parseServerErrorMessage(message)\n if (parsedServerError.authentication) {\n const error = new Error(message)\n if (parsedServerError.terminal) {\n this.shouldClose = true\n this.handleSocketFailure(error)\n this.transitionTo('failed')\n return\n }\n\n await this.reconnectAfterProtocolError(error)\n return\n }\n\n if (parsedServerError.code !== 'unknown') {\n throw new Error(parsedServerError.message)\n }\n\n throw new Error(message)\n }\n\n case 'module:authenticated': {\n if (data.data.authenticated) {\n if (!this.connectionAttempt || this.connectionAttempt.authenticated) {\n return\n }\n\n this.connectionAttempt.authenticated = true\n this.transitionTo('announcing')\n this.tryAnnounce()\n return\n }\n\n throw new Error('Authentication failed')\n }\n\n case 'module:announced': {\n if (!this.isSelfAnnouncement(data)) {\n return\n }\n\n if (this.status === 'ready') {\n return\n }\n\n if (this.connectionAttempt) {\n this.connectionAttempt.announced = true\n }\n\n this.reconnectAttempts = 0\n this.transitionTo('ready')\n this.resolveAttempt()\n this.opts.onReady?.()\n return\n }\n\n case 'registry:modules:sync': {\n // Fallback: If the status is stuck at 'announcing' but the sync already contains this module,\n // it means the announce succeeded; the server simply didn't send back 'module:announced'\n if (this.status !== 'announcing' || !this.connectionAttempt) {\n return\n }\n\n const modules = (data.data as any)?.modules as Array<{\n name: string\n identity?: { id?: string }\n }> ?? []\n\n const selfRegistered = modules.some(\n m => m.name === this.opts.name\n && m.identity?.id === this.identity.id,\n )\n\n if (!selfRegistered) {\n return\n }\n\n if (this.connectionAttempt) {\n this.connectionAttempt.announced = true\n }\n\n this.reconnectAttempts = 0\n this.transitionTo('ready')\n this.resolveAttempt()\n this.opts.onReady?.()\n return\n }\n\n case 'transport:connection:heartbeat': {\n if (data.data.kind === MessageHeartbeatKind.Ping) {\n this.sendHeartbeatPong()\n }\n }\n }\n }\n\n private isSelfAnnouncement(event: WebSocketBaseEvent<'module:announced', WebSocketEvents<C>['module:announced']>) {\n return event.data.name === this.opts.name && event.data.identity?.id === this.identity.id\n }\n\n private async dispatchMessage(data: WebSocketEvent<C>) {\n const listeners = this.eventListeners.get(data.type)\n if (!listeners?.size) {\n return\n }\n\n const results = await Promise.allSettled(\n Array.from(listeners).map(listener => Promise.resolve(listener(data as any))),\n )\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.opts.onError?.(result.reason)\n }\n }\n }\n\n private createPayload(data: WebSocketEventOptionalSource<C>) {\n return {\n ...data,\n metadata: {\n ...data?.metadata,\n source: data?.metadata?.source ?? this.identity,\n event: {\n id: data?.metadata?.event?.id ?? createEventId(),\n ...data?.metadata?.event,\n },\n },\n } as WebSocketEvent<C>\n }\n\n private startHeartbeat() {\n if (!this.heartbeat.readTimeout || !this.heartbeat.pingInterval) {\n return\n }\n\n this.stopHeartbeat()\n this.lastReadAt = Date.now()\n this.lastPingAt = 0\n\n const interval = Math.max(1_000, Math.min(this.heartbeat.pingInterval, Math.floor(this.heartbeat.readTimeout / 2)))\n this.heartbeatTimer = setInterval(() => {\n if (!this.isSocketOpen) {\n return\n }\n\n const now = Date.now()\n if (now - this.lastReadAt > this.heartbeat.readTimeout) {\n void this.reconnectAfterProtocolError(new Error(`Read timeout after ${this.heartbeat.readTimeout}ms`))\n return\n }\n\n if (now - this.lastPingAt >= this.heartbeat.pingInterval) {\n this.sendHeartbeatPing()\n }\n }, interval)\n }\n\n private stopHeartbeat() {\n if (!this.heartbeatTimer) {\n return\n }\n\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = undefined\n }\n\n private sendNativeHeartbeat(kind: 'ping' | 'pong') {\n const websocket = this.websocket as WebSocketLike & {\n ping?: () => void\n pong?: () => void\n }\n\n if (kind === 'ping') {\n websocket.ping?.()\n }\n else {\n websocket.pong?.()\n }\n }\n\n private sendHeartbeatPing() {\n this.lastPingAt = Date.now()\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Ping,\n message: this.heartbeat.message,\n at: Date.now(),\n },\n })\n this.sendNativeHeartbeat('ping')\n }\n\n private sendHeartbeatPong() {\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Pong,\n message: MessageHeartbeat.Pong,\n at: Date.now(),\n },\n })\n this.sendNativeHeartbeat('pong')\n }\n\n private async reconnectAfterProtocolError(error: Error) {\n if (this.shouldClose || this.pendingReconnect) {\n return\n }\n\n this.pendingReconnect = true\n const hadSocket = !!this.websocket\n\n if (!this.connectionAttempt || this.status === 'ready') {\n this.opts.onError?.(error)\n }\n\n const websocket = this.websocket\n this.cleanupSocket(websocket)\n this.rejectAttempt(error)\n\n if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) {\n websocket.close()\n }\n\n if (hadSocket) {\n this.opts.onClose?.()\n }\n\n if (!this.opts.autoReconnect) {\n this.transitionTo('failed')\n return\n }\n\n this.transitionTo('idle')\n void this.connect()\n }\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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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@proj-airi/server-sdk",
3
3
  "type": "module",
4
- "version": "0.10.2",
4
+ "version": "0.11.0",
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,9 +33,10 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@moeru/std": "0.1.0-beta.17",
36
- "crossws": "^0.4.5",
37
36
  "superjson": "^2.2.6",
38
- "@proj-airi/server-shared": "^0.10.2"
37
+ "valibot": "^1.3.1",
38
+ "@proj-airi/better-ws": "^0.11.0",
39
+ "@proj-airi/server-shared": "^0.11.0"
39
40
  },
40
41
  "scripts": {
41
42
  "dev": "pnpm run build",