@proj-airi/server-sdk 0.9.0-beta.4 → 0.9.0-beta.6
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 +29 -1
- package/dist/index.mjs +36 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { ContextUpdateStrategy, MessageHeartbeat, MetadataEventSource, ModuleConfigSchema, ModuleDependency, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
|
|
2
2
|
export * from "@proj-airi/server-shared/types";
|
|
3
3
|
|
|
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
|
|
4
29
|
//#region src/client.d.ts
|
|
5
30
|
type ClientStatus = 'idle' | 'connecting' | 'authenticating' | 'announcing' | 'ready' | 'reconnecting' | 'closing' | 'closed' | 'failed';
|
|
6
31
|
interface ClientHeartbeatOptions {
|
|
@@ -20,6 +45,8 @@ interface ClientOptions<C = undefined> {
|
|
|
20
45
|
url?: string;
|
|
21
46
|
name: string;
|
|
22
47
|
token?: string;
|
|
48
|
+
websocketConstructor?: WebSocketLikeConstructor;
|
|
49
|
+
connectTimeoutMs?: number;
|
|
23
50
|
possibleEvents?: Array<keyof WebSocketEvents<C>>;
|
|
24
51
|
identity?: MetadataEventSource;
|
|
25
52
|
dependencies?: ModuleDependency[];
|
|
@@ -49,6 +76,7 @@ declare class Client<C = undefined> {
|
|
|
49
76
|
private status;
|
|
50
77
|
private readonly identity;
|
|
51
78
|
private readonly heartbeat;
|
|
79
|
+
private readonly websocketConstructor;
|
|
52
80
|
private readonly opts;
|
|
53
81
|
private readonly eventListeners;
|
|
54
82
|
private readonly stateListeners;
|
|
@@ -93,5 +121,5 @@ declare class Client<C = undefined> {
|
|
|
93
121
|
private reconnectAfterProtocolError;
|
|
94
122
|
}
|
|
95
123
|
//#endregion
|
|
96
|
-
export { Client, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, WebSocketEventSource };
|
|
124
|
+
export { Client, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, WebSocketErrorEventLike, WebSocketEventSource, WebSocketLike, WebSocketLikeConstructor, WebSocketMessageEventLike };
|
|
97
125
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NativeWebSocket from "crossws/websocket";
|
|
2
2
|
import superjson from "superjson";
|
|
3
3
|
import { errorMessageFrom, sleep } from "@moeru/std";
|
|
4
4
|
import { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from "@proj-airi/server-shared";
|
|
@@ -45,10 +45,12 @@ var Client = class {
|
|
|
45
45
|
status = "idle";
|
|
46
46
|
identity;
|
|
47
47
|
heartbeat;
|
|
48
|
+
websocketConstructor;
|
|
48
49
|
opts;
|
|
49
50
|
eventListeners = /* @__PURE__ */ new Map();
|
|
50
51
|
stateListeners = /* @__PURE__ */ new Set();
|
|
51
52
|
constructor(options) {
|
|
53
|
+
const { websocketConstructor, ...clientOptions } = options;
|
|
52
54
|
const identity = options.identity ?? {
|
|
53
55
|
kind: "plugin",
|
|
54
56
|
plugin: { id: options.name },
|
|
@@ -57,6 +59,7 @@ var Client = class {
|
|
|
57
59
|
const heartbeat = normalizeHeartbeatOptions(options.heartbeat);
|
|
58
60
|
this.opts = {
|
|
59
61
|
url: "ws://localhost:6121/ws",
|
|
62
|
+
connectTimeoutMs: 15e3,
|
|
60
63
|
onAnyMessage: () => {},
|
|
61
64
|
onAnySend: () => {},
|
|
62
65
|
possibleEvents: [],
|
|
@@ -69,12 +72,13 @@ var Client = class {
|
|
|
69
72
|
autoConnect: true,
|
|
70
73
|
autoReconnect: true,
|
|
71
74
|
maxReconnectAttempts: -1,
|
|
72
|
-
...
|
|
75
|
+
...clientOptions,
|
|
73
76
|
heartbeat,
|
|
74
77
|
identity
|
|
75
78
|
};
|
|
76
79
|
this.identity = identity;
|
|
77
80
|
this.heartbeat = heartbeat;
|
|
81
|
+
this.websocketConstructor = websocketConstructor ?? NativeWebSocket;
|
|
78
82
|
if (this.opts.autoConnect) this.connect();
|
|
79
83
|
}
|
|
80
84
|
get connectionStatus() {
|
|
@@ -84,7 +88,7 @@ var Client = class {
|
|
|
84
88
|
return this.status === "ready";
|
|
85
89
|
}
|
|
86
90
|
get isSocketOpen() {
|
|
87
|
-
return this.websocket?.readyState ===
|
|
91
|
+
return this.websocket?.readyState === this.websocketConstructor.OPEN;
|
|
88
92
|
}
|
|
89
93
|
get lastError() {
|
|
90
94
|
return this.failureReason;
|
|
@@ -152,7 +156,7 @@ var Client = class {
|
|
|
152
156
|
this.rejectAttempt(/* @__PURE__ */ new Error("Client closed"));
|
|
153
157
|
const websocket = this.websocket;
|
|
154
158
|
this.websocket = void 0;
|
|
155
|
-
if (websocket && websocket.readyState !==
|
|
159
|
+
if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) websocket.close();
|
|
156
160
|
this.transitionTo("closed");
|
|
157
161
|
}
|
|
158
162
|
async runConnectLoop() {
|
|
@@ -189,7 +193,8 @@ var Client = class {
|
|
|
189
193
|
throw new Error("Client is closed");
|
|
190
194
|
}
|
|
191
195
|
connectOnce() {
|
|
192
|
-
const
|
|
196
|
+
const WebSocketConstructor = this.websocketConstructor;
|
|
197
|
+
const ws = new WebSocketConstructor(this.opts.url);
|
|
193
198
|
this.websocket = ws;
|
|
194
199
|
this.lastReadAt = Date.now();
|
|
195
200
|
this.lastPingAt = 0;
|
|
@@ -204,11 +209,21 @@ var Client = class {
|
|
|
204
209
|
};
|
|
205
210
|
this.connectionAttempt = attempt;
|
|
206
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
|
+
};
|
|
207
221
|
ws.onmessage = (event) => {
|
|
208
222
|
if (!isCurrentSocket()) return;
|
|
209
223
|
this.handleMessage(event);
|
|
210
224
|
};
|
|
211
225
|
ws.onerror = (event) => {
|
|
226
|
+
clearConnectTimer();
|
|
212
227
|
if (!isCurrentSocket()) return;
|
|
213
228
|
const error = event?.error instanceof Error ? event.error : /* @__PURE__ */ new Error("WebSocket error");
|
|
214
229
|
if (this.connectionAttempt) this.handleSocketFailure(error, ws);
|
|
@@ -218,6 +233,7 @@ var Client = class {
|
|
|
218
233
|
}
|
|
219
234
|
};
|
|
220
235
|
ws.onclose = () => {
|
|
236
|
+
clearConnectTimer();
|
|
221
237
|
if (!isCurrentSocket()) return;
|
|
222
238
|
const wasReady = this.status === "ready";
|
|
223
239
|
this.cleanupSocket(ws);
|
|
@@ -225,12 +241,14 @@ var Client = class {
|
|
|
225
241
|
if (this.shouldClose) return;
|
|
226
242
|
if (wasReady && this.opts.autoReconnect) {
|
|
227
243
|
this.pendingReconnect = true;
|
|
244
|
+
this.transitionTo("idle");
|
|
228
245
|
this.connect();
|
|
229
246
|
return;
|
|
230
247
|
}
|
|
231
248
|
this.rejectAttempt(/* @__PURE__ */ new Error("WebSocket closed"));
|
|
232
249
|
};
|
|
233
250
|
ws.onopen = () => {
|
|
251
|
+
clearConnectTimer();
|
|
234
252
|
if (!isCurrentSocket()) return;
|
|
235
253
|
this.startHeartbeat();
|
|
236
254
|
if (this.opts.token) {
|
|
@@ -249,7 +267,7 @@ var Client = class {
|
|
|
249
267
|
if (socket && this.websocket !== socket) return;
|
|
250
268
|
const currentSocket = socket ?? this.websocket;
|
|
251
269
|
this.cleanupSocket(socket);
|
|
252
|
-
if (currentSocket && currentSocket.readyState !==
|
|
270
|
+
if (currentSocket && currentSocket.readyState !== this.websocketConstructor.CLOSED && currentSocket.readyState !== this.websocketConstructor.CLOSING) currentSocket.close();
|
|
253
271
|
this.rejectAttempt(error);
|
|
254
272
|
}
|
|
255
273
|
cleanupSocket(socket) {
|
|
@@ -384,6 +402,16 @@ var Client = class {
|
|
|
384
402
|
throw new Error("Authentication failed");
|
|
385
403
|
case "module:announced":
|
|
386
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;
|
|
387
415
|
if (this.connectionAttempt) this.connectionAttempt.announced = true;
|
|
388
416
|
this.reconnectAttempts = 0;
|
|
389
417
|
this.transitionTo("ready");
|
|
@@ -472,12 +500,13 @@ var Client = class {
|
|
|
472
500
|
const websocket = this.websocket;
|
|
473
501
|
this.cleanupSocket(websocket);
|
|
474
502
|
this.rejectAttempt(error);
|
|
475
|
-
if (websocket && websocket.readyState !==
|
|
503
|
+
if (websocket && websocket.readyState !== this.websocketConstructor.CLOSED && websocket.readyState !== this.websocketConstructor.CLOSING) websocket.close();
|
|
476
504
|
if (hadSocket) this.opts.onClose?.();
|
|
477
505
|
if (!this.opts.autoReconnect) {
|
|
478
506
|
this.transitionTo("failed");
|
|
479
507
|
return;
|
|
480
508
|
}
|
|
509
|
+
this.transitionTo("idle");
|
|
481
510
|
this.connect();
|
|
482
511
|
}
|
|
483
512
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 WebSocket 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\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: WebSocket\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?: WebSocket\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\n private readonly opts: Required<Omit<ClientOptions<C>, 'token' | 'heartbeat'>> & Pick<ClientOptions<C>, 'token'> & {\n heartbeat: Required<ClientHeartbeatOptions>\n }\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 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 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 ...options,\n heartbeat,\n identity,\n }\n\n this.identity = identity\n this.heartbeat = heartbeat\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 === WebSocket.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 !== WebSocket.CLOSED && websocket.readyState !== WebSocket.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 ws = new WebSocket(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\n ws.onmessage = (event: MessageEvent) => {\n if (!isCurrentSocket()) {\n return\n }\n\n void this.handleMessage(event)\n }\n\n ws.onerror = (event: any) => {\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 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 void this.connect()\n return\n }\n\n this.rejectAttempt(new Error('WebSocket closed'))\n }\n\n ws.onopen = () => {\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?: WebSocket) {\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 !== WebSocket.CLOSED && currentSocket.readyState !== WebSocket.CLOSING) {\n currentSocket.close()\n }\n\n this.rejectAttempt(error)\n }\n\n private cleanupSocket(socket?: WebSocket) {\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: MessageEvent) {\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.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 WebSocket & {\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 !== WebSocket.CLOSED && websocket.readyState !== WebSocket.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 void this.connect()\n }\n}\n"],"mappings":";;;;;;AA6EA,SAAS,mBAAmB;AAC1B,QAAO,GAAG,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;;AAG7E,SAAS,gBAAgB;AACvB,QAAO,GAAG,KAAK,KAAK,CAAC,SAAS,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,GAAG;;AAG9E,SAAS,wBAAwB;CAC/B,IAAI;CACJ,IAAI;AAOJ,QAAO;EAAE,SALO,IAAI,SAAe,cAAc,gBAAgB;AAC/D,aAAU;AACV,YAAS;IACT;EAEgB;EAAQ;EAAS;;AAGrC,SAAS,0BAA0B,WAAsE;CACvG,MAAM,cAAc,WAAW,eAAe;CAC9C,MAAM,eAAe,WAAW,gBAAgB,KAAK,IAAI,KAAO,KAAK,MAAM,cAAc,EAAE,CAAC;AAE5F,QAAO;EACL;EACA,cAAc,KAAK,IAAI,cAAc,YAAY;EACjD,SAAS,WAAW,WAAW,iBAAiB;EACjD;;AAGH,IAAa,SAAb,MAAmC;CACjC;CACA,cAAsB;CACtB;CACA;CACA,aAAqB;CACrB,aAAqB;CACrB,oBAA4B;CAC5B,mBAA2B;CAC3B;CACA;CACA,SAA+B;CAC/B;CACA;CAEA;CAIA,iCAAkC,IAAI,KAGnC;CAEH,iCAAkC,IAAI,KAAkD;CAExF,YAAY,SAA2B;EACrC,MAAM,WAAW,QAAQ,YAAY;GACnC,MAAM;GACN,QAAQ,EAAE,IAAI,QAAQ,MAAM;GAC5B,IAAI,kBAAkB;GACvB;EAED,MAAM,YAAY,0BAA0B,QAAQ,UAAU;AAE9D,OAAK,OAAO;GACV,KAAK;GACL,oBAAoB;GACpB,iBAAiB;GACjB,gBAAgB,EAAE;GAClB,cAAc,EAAE;GAChB,cAAc,KAAA;GACd,eAAe;GACf,eAAe;GACf,eAAe;GACf,qBAAqB;GACrB,aAAa;GACb,eAAe;GACf,sBAAsB;GACtB,GAAG;GACH;GACA;GACD;AAED,OAAK,WAAW;AAChB,OAAK,YAAY;AAEjB,MAAI,KAAK,KAAK,YACP,MAAK,SAAS;;CAIvB,IAAI,mBAAmB;AACrB,SAAO,KAAK;;CAGd,IAAI,UAAU;AACZ,SAAO,KAAK,WAAW;;CAGzB,IAAI,eAAe;AACjB,SAAO,KAAK,WAAW,eAAe,UAAU;;CAGlD,IAAI,YAAY;AACd,SAAO,KAAK;;CAGd,MAAM,QAAQ,SAA0B;AACtC,MAAI,KAAK,YACP,OAAM,IAAI,MAAM,mBAAmB;AAGrC,MAAI,KAAK,WAAW,QAClB;AAGF,MAAI,KAAK,YACP,QAAO,KAAK,kBAAkB,KAAK,aAAa,QAAQ;AAG1D,OAAK,cAAc,KAAK,gBAAgB,CAAC,cAAc;AACrD,QAAK,cAAc,KAAA;IACnB;AAEF,SAAO,KAAK,kBAAkB,KAAK,aAAa,QAAQ;;CAG1D,MAAM,SAA0B;AAC9B,SAAO,KAAK,QAAQ,QAAQ;;CAG9B,gBAAgB,SAA0B;AACxC,SAAO,KAAK,QAAQ,QAAQ;;CAG9B,wBAAwB,UAAmE;AACzF,OAAK,eAAe,IAAI,SAAS;AAEjC,eAAa;AACX,QAAK,eAAe,OAAO,SAAS;;;CAIxC,QACE,OACA,UACY;EACZ,IAAI,YAAY,KAAK,eAAe,IAAI,MAAM;AAC9C,MAAI,CAAC,WAAW;AACd,+BAAY,IAAI,KAAK;AACrB,QAAK,eAAe,IAAI,OAAO,UAAU;;AAG3C,YAAU,IAAI,SAAgB;AAE9B,eAAa;AACX,QAAK,SAAS,OAAO,SAAS;;;CAIlC,SACE,OACA,UACM;EACN,MAAM,YAAY,KAAK,eAAe,IAAI,MAAM;AAChD,MAAI,CAAC,UACH;AAGF,MAAI,UAAU;AACZ,aAAU,OAAO,SAAgB;AACjC,OAAI,CAAC,UAAU,KACb,MAAK,eAAe,OAAO,MAAM;QAInC,MAAK,eAAe,OAAO,MAAM;;CAIrC,KAAK,MAAgD;AACnD,MAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,UAC9B,QAAO;EAGT,MAAM,UAAU,KAAK,cAAc,KAAK;AACxC,OAAK,KAAK,YAAY,QAAQ;AAC9B,OAAK,UAAU,KAAK,UAAU,UAAU,QAAQ,CAAC;AAEjD,SAAO;;CAGT,YAAY,MAA6C;AACvD,MAAI,CAAC,KAAK,KAAK,KAAK,CAClB,OAAM,IAAI,MAAM,4CAA4C,KAAK,SAAS;;CAI9E,QAAQ,MAA2D;AACjE,MAAI,CAAC,KAAK,gBAAgB,CAAC,KAAK,UAC9B,QAAO;AAGT,OAAK,UAAU,KAAK,KAAK;AACzB,SAAO;;CAGT,QAAc;AACZ,OAAK,cAAc;AACnB,OAAK,mBAAmB;AACxB,OAAK,aAAa,UAAU;AAC5B,OAAK,eAAe;AACpB,OAAK,8BAAc,IAAI,MAAM,gBAAgB,CAAC;EAE9C,MAAM,YAAY,KAAK;AACvB,OAAK,YAAY,KAAA;AAEjB,MAAI,aAAa,UAAU,eAAe,UAAU,UAAU,UAAU,eAAe,UAAU,QAC/F,WAAU,OAAO;AAGnB,OAAK,aAAa,SAAS;;CAG7B,MAAc,iBAAiB;AAC7B,OAAK,mBAAmB;AAExB,SAAO,CAAC,KAAK,aAAa;GACxB,MAAM,eAAe,KAAK,oBAAoB;AAC9C,QAAK,aAAa,eAAe,iBAAiB,aAAa;AAE/D,OAAI;AACF,UAAM,KAAK,aAAa;AACxB,SAAK,oBAAoB;AACzB;YAEK,OAAO;IACZ,MAAM,kBAAkB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,iBAAiB,MAAM,IAAI,qCAAqC;AACnI,SAAK,gBAAgB;AACrB,SAAK,KAAK,UAAU,gBAAgB;AAEpC,QAAI,KAAK,YACP,OAAM;AAGR,QAAI,2CAA2C,gBAAgB,QAAQ,EAAE;AACvE,UAAK,aAAa,SAAS;AAC3B,WAAM;;AAGR,QAAI,CAAC,KAAK,KAAK,iBAAiB,cAAc;AAC5C,UAAK,aAAa,SAAS;AAC3B,WAAM;;AAGR,QAAI,CAAC,KAAK,UAAU,EAAE;AACpB,UAAK,aAAa,SAAS;AAC3B,WAAM;;IAGR,MAAM,QAAQ,KAAK,kBAAkB,KAAK,kBAAkB;AAC5D,SAAK,qBAAqB;AAC1B,UAAM,MAAM,MAAM;;;AAItB,QAAM,IAAI,MAAM,mBAAmB;;CAGrC,cAAqC;EACnC,MAAM,KAAK,IAAI,UAAU,KAAK,KAAK,IAAI;AACvC,OAAK,YAAY;AACjB,OAAK,aAAa,KAAK,KAAK;AAC5B,OAAK,aAAa;EAElB,MAAM,WAAW,uBAAuB;EACxC,MAAM,UAA6B;GACjC,WAAW;GACX,eAAe,CAAC,KAAK,KAAK;GAC1B,SAAS,SAAS;GAClB,QAAQ,SAAS;GACjB,SAAS,SAAS;GAClB,QAAQ;GACT;AAED,OAAK,oBAAoB;EAEzB,MAAM,wBAAwB,KAAK,cAAc;AAEjD,KAAG,aAAa,UAAwB;AACtC,OAAI,CAAC,iBAAiB,CACpB;AAGG,QAAK,cAAc,MAAM;;AAGhC,KAAG,WAAW,UAAe;AAC3B,OAAI,CAAC,iBAAiB,CACpB;GAGF,MAAM,QAAQ,OAAO,iBAAiB,QAAQ,MAAM,wBAAQ,IAAI,MAAM,kBAAkB;AACxF,OAAI,KAAK,kBACP,MAAK,oBAAoB,OAAO,GAAG;QAEhC;AACH,SAAK,KAAK,UAAU,MAAM;AACrB,SAAK,4BAA4B,MAAM;;;AAIhD,KAAG,gBAAgB;AACjB,OAAI,CAAC,iBAAiB,CACpB;GAGF,MAAM,WAAW,KAAK,WAAW;AACjC,QAAK,cAAc,GAAG;AACtB,QAAK,KAAK,WAAW;AAErB,OAAI,KAAK,YACP;AAGF,OAAI,YAAY,KAAK,KAAK,eAAe;AACvC,SAAK,mBAAmB;AACnB,SAAK,SAAS;AACnB;;AAGF,QAAK,8BAAc,IAAI,MAAM,mBAAmB,CAAC;;AAGnD,KAAG,eAAe;AAChB,OAAI,CAAC,iBAAiB,CACpB;AAGF,QAAK,gBAAgB;AAErB,OAAI,KAAK,KAAK,OAAO;AACnB,YAAQ,gBAAgB;AACxB,SAAK,aAAa,iBAAiB;AACnC,SAAK,iBAAiB;UAEnB;AACH,YAAQ,gBAAgB;AACxB,SAAK,aAAa,aAAa;AAC/B,SAAK,aAAa;;;AAItB,SAAO,QAAQ;;CAGjB,oBAA4B,OAAc,QAAoB;AAC5D,MAAI,UAAU,KAAK,cAAc,OAC/B;EAGF,MAAM,gBAAgB,UAAU,KAAK;AACrC,OAAK,cAAc,OAAO;AAE1B,MAAI,iBAAiB,cAAc,eAAe,UAAU,UAAU,cAAc,eAAe,UAAU,QAC3G,eAAc,OAAO;AAGvB,OAAK,cAAc,MAAM;;CAG3B,cAAsB,QAAoB;AACxC,MAAI,UAAU,KAAK,cAAc,OAC/B;AAGF,OAAK,eAAe;AAEpB,MAAI,CAAC,UAAU,KAAK,cAAc,OAChC,MAAK,YAAY,KAAA;;CAIrB,cAAsB,OAAc;AAClC,MAAI,CAAC,KAAK,kBACR;EAGF,MAAM,UAAU,KAAK;AACrB,OAAK,oBAAoB,KAAA;AACzB,UAAQ,OAAO,MAAM;;CAGvB,iBAAyB;AACvB,MAAI,CAAC,KAAK,kBACR;EAGF,MAAM,UAAU,KAAK;AACrB,OAAK,oBAAoB,KAAA;AACzB,UAAQ,SAAS;;CAGnB,WAAmB;AACjB,SAAO,KAAK,KAAK,yBAAyB,MAAM,KAAK,oBAAoB,KAAK,KAAK;;CAGrF,kBAA0B,UAAkB;AAC1C,SAAO,KAAK,IAAI,KAAK,WAAW,KAAO,IAAO;;CAGhD,aAAqB,QAAsB;AACzC,MAAI,KAAK,WAAW,OAClB;EAGF,MAAM,iBAAiB,KAAK;AAC5B,OAAK,SAAS;EACd,MAAM,UAAU;GAAE;GAAgB;GAAQ;AAE1C,OAAK,KAAK,gBAAgB,QAAQ;AAElC,OAAK,MAAM,YAAY,KAAK,eAC1B,UAAS,QAAQ;;CAIrB,MAAc,kBAAkB,gBAA+B,SAA0B;AACvF,MAAI,CAAC,SAAS,WAAW,CAAC,SAAS,YACjC,QAAO;EAGT,MAAM,UAAU,SAAS;AACzB,MAAI,OAAO,YAAY,eAAe,WAAW,EAC/C,OAAM,IAAI,MAAM,8BAA8B,QAAQ,IAAI;EAG5D,MAAM,cAAc,SAAS;AAC7B,MAAI,aAAa,QACf,OAAM,IAAI,MAAM,qBAAqB;EAGvC,IAAI;EACJ,IAAI;AAEJ,MAAI;AACF,SAAM,QAAQ,KAAK,CACjB,gBACA,IAAI,SAAe,GAAG,WAAW;AAC/B,QAAI,OAAO,YAAY,YACrB,iBAAgB,iBAAiB;AAC/B,4BAAO,IAAI,MAAM,8BAA8B,QAAQ,IAAI,CAAC;OAC3D,QAAQ;AAGb,QAAI,aAAa;KACf,MAAM,gBAAgB;AACpB,6BAAO,IAAI,MAAM,qBAAqB,CAAC;;AAGzC,iBAAY,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AAC9D,iCAA4B,YAAY,oBAAoB,SAAS,QAAQ;;KAE/E,CACH,CAAC;YAEI;AACN,OAAI,cACF,cAAa,cAAc;AAG7B,0BAAuB;;;CAI3B,cAAsB;AACpB,OAAK,YAAY;GACf,MAAM;GACN,MAAM;IACJ,MAAM,KAAK,KAAK;IAChB,UAAU,KAAK;IACf,gBAAgB,KAAK,KAAK;IAC1B,cAAc,KAAK,KAAK;IACxB,cAAc,KAAK,KAAK;IACzB;GACF,CAAC;;CAGJ,kBAA0B;AACxB,MAAI,CAAC,KAAK,KAAK,MACb;AAGF,OAAK,YAAY;GACf,MAAM;GACN,MAAM,EAAE,OAAO,KAAK,KAAK,OAAO;GACjC,CAAC;;CAGJ,MAAc,cAAc,OAAqB;AAC/C,OAAK,aAAa,KAAK,KAAK;AAE5B,MAAI;GACF,MAAM,OAAO,KAAK,aAAa,MAAM,KAAe;AACpD,QAAK,KAAK,eAAe,KAAK;AAE9B,SAAM,KAAK,qBAAqB,KAAK;AACrC,SAAM,KAAK,gBAAgB,KAAK;WAE3B,OAAO;GACZ,MAAM,kBAAkB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,iBAAiB,MAAM,IAAI,qCAAqC;AACnI,QAAK,KAAK,UAAU,gBAAgB;AAEpC,OAAI,KAAK,qBAAqB,KAAK,WAAW,QAC5C,MAAK,oBAAoB,gBAAgB;;;CAK/C,aAAqB,KAAgC;AACnD,MAAI;GACF,MAAM,SAAS,UAAU,MAAqC,IAAI;AAClE,OAAI,UAAU,OAAO,WAAW,YAAY,UAAU,OACpD,QAAO;UAGL;EAIN,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,CAAC,UAAU,OAAO,WAAW,YAAY,EAAE,UAAU,QACvD,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO;;CAGT,MAAc,qBAAqB,MAAyB;AAC1D,UAAQ,KAAK,MAAb;GACE,KAAK,SAAS;IACZ,MAAM,UAAU,KAAK,MAAM;AAC3B,QAAI,CAAC,WAAW,OAAO,YAAY,SACjC;IAGF,MAAM,oBAAoB,wBAAwB,QAAQ;AAC1D,QAAI,kBAAkB,gBAAgB;KACpC,MAAM,QAAQ,IAAI,MAAM,QAAQ;AAChC,SAAI,kBAAkB,UAAU;AAC9B,WAAK,cAAc;AACnB,WAAK,oBAAoB,MAAM;AAC/B,WAAK,aAAa,SAAS;AAC3B;;AAGF,WAAM,KAAK,4BAA4B,MAAM;AAC7C;;AAGF,QAAI,kBAAkB,SAAS,UAC7B,OAAM,IAAI,MAAM,kBAAkB,QAAQ;AAG5C,UAAM,IAAI,MAAM,QAAQ;;GAG1B,KAAK;AACH,QAAI,KAAK,KAAK,eAAe;AAC3B,SAAI,CAAC,KAAK,qBAAqB,KAAK,kBAAkB,cACpD;AAGF,UAAK,kBAAkB,gBAAgB;AACvC,UAAK,aAAa,aAAa;AAC/B,UAAK,aAAa;AAClB;;AAGF,UAAM,IAAI,MAAM,wBAAwB;GAG1C,KAAK;AACH,QAAI,CAAC,KAAK,mBAAmB,KAAK,CAChC;AAGF,QAAI,KAAK,kBACP,MAAK,kBAAkB,YAAY;AAGrC,SAAK,oBAAoB;AACzB,SAAK,aAAa,QAAQ;AAC1B,SAAK,gBAAgB;AACrB,SAAK,KAAK,WAAW;AACrB;GAGF,KAAK,iCACH,KAAI,KAAK,KAAK,SAAS,qBAAqB,KAC1C,MAAK,mBAAmB;;;CAMhC,mBAA2B,OAAuF;AAChH,SAAO,MAAM,KAAK,SAAS,KAAK,KAAK,QAAQ,MAAM,KAAK,UAAU,OAAO,KAAK,SAAS;;CAGzF,MAAc,gBAAgB,MAAyB;EACrD,MAAM,YAAY,KAAK,eAAe,IAAI,KAAK,KAAK;AACpD,MAAI,CAAC,WAAW,KACd;EAGF,MAAM,UAAU,MAAM,QAAQ,WAC5B,MAAM,KAAK,UAAU,CAAC,KAAI,aAAY,QAAQ,QAAQ,SAAS,KAAY,CAAC,CAAC,CAC9E;AAED,OAAK,MAAM,UAAU,QACnB,KAAI,OAAO,WAAW,WACpB,MAAK,KAAK,UAAU,OAAO,OAAO;;CAKxC,cAAsB,MAAuC;AAC3D,SAAO;GACL,GAAG;GACH,UAAU;IACR,GAAG,MAAM;IACT,QAAQ,MAAM,UAAU,UAAU,KAAK;IACvC,OAAO;KACL,IAAI,MAAM,UAAU,OAAO,MAAM,eAAe;KAChD,GAAG,MAAM,UAAU;KACpB;IACF;GACF;;CAGH,iBAAyB;AACvB,MAAI,CAAC,KAAK,UAAU,eAAe,CAAC,KAAK,UAAU,aACjD;AAGF,OAAK,eAAe;AACpB,OAAK,aAAa,KAAK,KAAK;AAC5B,OAAK,aAAa;EAElB,MAAM,WAAW,KAAK,IAAI,KAAO,KAAK,IAAI,KAAK,UAAU,cAAc,KAAK,MAAM,KAAK,UAAU,cAAc,EAAE,CAAC,CAAC;AACnH,OAAK,iBAAiB,kBAAkB;AACtC,OAAI,CAAC,KAAK,aACR;GAGF,MAAM,MAAM,KAAK,KAAK;AACtB,OAAI,MAAM,KAAK,aAAa,KAAK,UAAU,aAAa;AACjD,SAAK,4CAA4B,IAAI,MAAM,sBAAsB,KAAK,UAAU,YAAY,IAAI,CAAC;AACtG;;AAGF,OAAI,MAAM,KAAK,cAAc,KAAK,UAAU,aAC1C,MAAK,mBAAmB;KAEzB,SAAS;;CAGd,gBAAwB;AACtB,MAAI,CAAC,KAAK,eACR;AAGF,gBAAc,KAAK,eAAe;AAClC,OAAK,iBAAiB,KAAA;;CAGxB,oBAA4B,MAAuB;EACjD,MAAM,YAAY,KAAK;AAKvB,MAAI,SAAS,OACX,WAAU,QAAQ;MAGlB,WAAU,QAAQ;;CAItB,oBAA4B;AAC1B,OAAK,aAAa,KAAK,KAAK;AAC5B,OAAK,KAAK;GACR,MAAM;GACN,MAAM;IACJ,MAAM,qBAAqB;IAC3B,SAAS,KAAK,UAAU;IACxB,IAAI,KAAK,KAAK;IACf;GACF,CAAC;AACF,OAAK,oBAAoB,OAAO;;CAGlC,oBAA4B;AAC1B,OAAK,KAAK;GACR,MAAM;GACN,MAAM;IACJ,MAAM,qBAAqB;IAC3B,SAAS,iBAAiB;IAC1B,IAAI,KAAK,KAAK;IACf;GACF,CAAC;AACF,OAAK,oBAAoB,OAAO;;CAGlC,MAAc,4BAA4B,OAAc;AACtD,MAAI,KAAK,eAAe,KAAK,iBAC3B;AAGF,OAAK,mBAAmB;EACxB,MAAM,YAAY,CAAC,CAAC,KAAK;AAEzB,MAAI,CAAC,KAAK,qBAAqB,KAAK,WAAW,QAC7C,MAAK,KAAK,UAAU,MAAM;EAG5B,MAAM,YAAY,KAAK;AACvB,OAAK,cAAc,UAAU;AAC7B,OAAK,cAAc,MAAM;AAEzB,MAAI,aAAa,UAAU,eAAe,UAAU,UAAU,UAAU,eAAe,UAAU,QAC/F,WAAU,OAAO;AAGnB,MAAI,UACF,MAAK,KAAK,WAAW;AAGvB,MAAI,CAAC,KAAK,KAAK,eAAe;AAC5B,QAAK,aAAa,SAAS;AAC3B;;AAGG,OAAK,SAAS"}
|
|
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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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proj-airi/server-sdk",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.0-beta.
|
|
4
|
+
"version": "0.9.0-beta.6",
|
|
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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@moeru/std": "0.1.0-beta.17",
|
|
36
36
|
"crossws": "^0.4.4",
|
|
37
37
|
"superjson": "^2.2.6",
|
|
38
|
-
"@proj-airi/server-shared": "^0.9.0-beta.
|
|
38
|
+
"@proj-airi/server-shared": "^0.9.0-beta.6"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "pnpm run build",
|