@finalai/websockets 0.1.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.
Potentially problematic release.
This version of @finalai/websockets might be problematic. Click here for more details.
- package/README.md +10 -0
- package/dist/modules/channel-transport.d.mts +56 -0
- package/dist/modules/channel-transport.mjs +269 -0
- package/dist/modules/channel-transport.mjs.map +7 -0
- package/dist/modules/index.d.mts +295 -0
- package/dist/modules/index.mjs +1432 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/node-client-tls.d.mts +27 -0
- package/dist/modules/node-client-tls.mjs +54 -0
- package/dist/modules/node-client-tls.mjs.map +7 -0
- package/dist/modules/testing/client-tls-fixture.d.mts +15 -0
- package/dist/modules/testing/client-tls-fixture.mjs +85 -0
- package/dist/modules/testing/client-tls-fixture.mjs.map +7 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @finalai/websockets
|
|
2
|
+
|
|
3
|
+
Client for Final WebSockets channels, realtime, presence, RPC, and related public
|
|
4
|
+
wire operations. The default client supports browser and Node transports.
|
|
5
|
+
|
|
6
|
+
`/channel-transport` is a portable channel transport with an injected asynchronous
|
|
7
|
+
socket opener, renewal, reconnect, and cursor recovery. Agent relay adapters use
|
|
8
|
+
this transport and own their separate agent framing.
|
|
9
|
+
|
|
10
|
+
`/node-client-tls` provides optional Node mutual-TLS transport helpers.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type WebsocketGatewayData } from '@finalai/websocket-gateway-protocol';
|
|
2
|
+
export interface SocketEventIface {
|
|
3
|
+
data?: unknown;
|
|
4
|
+
code?: number;
|
|
5
|
+
reason?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WebSocketLikeIface {
|
|
8
|
+
readyState: number;
|
|
9
|
+
send(data: string): void;
|
|
10
|
+
close(code?: number, reason?: string): void;
|
|
11
|
+
addEventListener(type: string, listener: (event: SocketEventIface) => void): void;
|
|
12
|
+
}
|
|
13
|
+
export interface ChannelTransportIface {
|
|
14
|
+
send(data: WebsocketGatewayData): boolean;
|
|
15
|
+
onFrame(listener: (data: WebsocketGatewayData) => void): () => void;
|
|
16
|
+
onOpen(listener: () => void): () => void;
|
|
17
|
+
onClose(listener: (info: {
|
|
18
|
+
code?: number;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}) => void): () => void;
|
|
21
|
+
readonly isOpen: boolean;
|
|
22
|
+
close(reason?: string): void;
|
|
23
|
+
}
|
|
24
|
+
export interface ChannelTransportTokenIface {
|
|
25
|
+
token: string;
|
|
26
|
+
expiresAt: string;
|
|
27
|
+
}
|
|
28
|
+
export declare enum ChannelTransportStage {
|
|
29
|
+
SocketOpen = "socket-open",
|
|
30
|
+
Hello = "hello",
|
|
31
|
+
Subscribed = "subscribed"
|
|
32
|
+
}
|
|
33
|
+
export interface ChannelTransportOptionsIface {
|
|
34
|
+
gatewayUrl: string;
|
|
35
|
+
token: ChannelTransportTokenIface;
|
|
36
|
+
inbound: string;
|
|
37
|
+
outbound: string;
|
|
38
|
+
messageName: string;
|
|
39
|
+
initialCursor?: string;
|
|
40
|
+
openWebSocket: (args: {
|
|
41
|
+
url: string;
|
|
42
|
+
headers: Record<string, string>;
|
|
43
|
+
signal: AbortSignal;
|
|
44
|
+
}) => Promise<WebSocketLikeIface>;
|
|
45
|
+
renewToken?: () => Promise<ChannelTransportTokenIface>;
|
|
46
|
+
renewBeforeMs?: number;
|
|
47
|
+
subscribeAfter?: Promise<void>;
|
|
48
|
+
onStage?: (stage: ChannelTransportStage) => void;
|
|
49
|
+
now?: () => number;
|
|
50
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
51
|
+
log?: (event: string, fields: Record<string, string | number | boolean | null>) => void;
|
|
52
|
+
}
|
|
53
|
+
export declare const GATEWAY_PROTOCOL_VERSION = 1;
|
|
54
|
+
export declare const compareGatewayCursors: (left: string, right: string) => number;
|
|
55
|
+
export declare const gatewayConnectUrl: (gatewayUrl: string) => string;
|
|
56
|
+
export declare const createChannelTransport: (options: ChannelTransportOptionsIface) => ChannelTransportIface;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import {
|
|
3
|
+
websocketGatewayServerFrameSchema,
|
|
4
|
+
compareWebsocketGatewayCursors
|
|
5
|
+
} from "@finalai/websocket-gateway-protocol";
|
|
6
|
+
const backoffDelayMs = (args) => Math.min(args.maxDelayMs, args.minDelayMs * 2 ** Math.max(0, args.attempt - 1));
|
|
7
|
+
var ChannelTransportStage = /* @__PURE__ */ ((ChannelTransportStage2) => {
|
|
8
|
+
ChannelTransportStage2["SocketOpen"] = "socket-open";
|
|
9
|
+
ChannelTransportStage2["Hello"] = "hello";
|
|
10
|
+
ChannelTransportStage2["Subscribed"] = "subscribed";
|
|
11
|
+
return ChannelTransportStage2;
|
|
12
|
+
})(ChannelTransportStage || {});
|
|
13
|
+
const GATEWAY_PROTOCOL_VERSION = 1;
|
|
14
|
+
const DEFAULT_RENEW_BEFORE_MS = 2 * 60 * 1e3;
|
|
15
|
+
const PING_INTERVAL_MS = 2e4;
|
|
16
|
+
const RENEW_CHECK_MS = 15e3;
|
|
17
|
+
const SOCKET_OPEN = 1;
|
|
18
|
+
const gatewayServerFrameSchema = websocketGatewayServerFrameSchema;
|
|
19
|
+
const parseServerFrame = (event) => {
|
|
20
|
+
const text = z.string().safeParse(event.data);
|
|
21
|
+
if (!text.success) return null;
|
|
22
|
+
try {
|
|
23
|
+
const parsed = gatewayServerFrameSchema.safeParse(JSON.parse(text.data));
|
|
24
|
+
return parsed.success ? parsed.data : null;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const compareGatewayCursors = compareWebsocketGatewayCursors;
|
|
30
|
+
const abortableSleep = (ms, signal) => new Promise((resolve) => {
|
|
31
|
+
if (signal.aborted) {
|
|
32
|
+
resolve();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const timer = setTimeout(resolve, ms);
|
|
36
|
+
signal.addEventListener(
|
|
37
|
+
"abort",
|
|
38
|
+
() => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve();
|
|
41
|
+
},
|
|
42
|
+
{ once: true }
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
const gatewayConnectUrl = (gatewayUrl) => {
|
|
46
|
+
const url = new URL(gatewayUrl);
|
|
47
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
48
|
+
url.pathname = path.endsWith("/connect") ? path : `${path}/connect`;
|
|
49
|
+
url.searchParams.set("protocolVersion", String(GATEWAY_PROTOCOL_VERSION));
|
|
50
|
+
return url.toString();
|
|
51
|
+
};
|
|
52
|
+
const createChannelTransport = (options) => {
|
|
53
|
+
const { inbound, outbound } = options;
|
|
54
|
+
const now = options.now ?? Date.now;
|
|
55
|
+
const sleep = options.sleep ?? abortableSleep;
|
|
56
|
+
const log = options.log ?? (() => void 0);
|
|
57
|
+
const stage = (reached) => {
|
|
58
|
+
try {
|
|
59
|
+
options.onStage?.(reached);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const renewBeforeMs = options.renewBeforeMs ?? DEFAULT_RENEW_BEFORE_MS;
|
|
64
|
+
let subscribeAllowed = options.subscribeAfter == null;
|
|
65
|
+
options.subscribeAfter?.then(
|
|
66
|
+
() => {
|
|
67
|
+
subscribeAllowed = true;
|
|
68
|
+
},
|
|
69
|
+
() => void 0
|
|
70
|
+
);
|
|
71
|
+
const lifetime = new AbortController();
|
|
72
|
+
const frameListeners = /* @__PURE__ */ new Set();
|
|
73
|
+
const openListeners = /* @__PURE__ */ new Set();
|
|
74
|
+
const closeListeners = /* @__PURE__ */ new Set();
|
|
75
|
+
let token = options.token;
|
|
76
|
+
let socket = null;
|
|
77
|
+
let subscribed = false;
|
|
78
|
+
let lastCursor = null;
|
|
79
|
+
let requestCounter = 0;
|
|
80
|
+
let attempt = 0;
|
|
81
|
+
let renewing = false;
|
|
82
|
+
const nextId = (prefix) => {
|
|
83
|
+
requestCounter += 1;
|
|
84
|
+
return `${prefix}-${requestCounter}`;
|
|
85
|
+
};
|
|
86
|
+
const sendRaw = (target, frame) => {
|
|
87
|
+
if (target.readyState !== SOCKET_OPEN) return false;
|
|
88
|
+
try {
|
|
89
|
+
target.send(JSON.stringify(frame));
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const takeCursor = (cursor) => {
|
|
96
|
+
if (cursor == null) return;
|
|
97
|
+
if (lastCursor == null || compareGatewayCursors(cursor, lastCursor) > 0) lastCursor = cursor;
|
|
98
|
+
};
|
|
99
|
+
const renew = async () => {
|
|
100
|
+
if (renewing || options.renewToken == null) return;
|
|
101
|
+
const expiresAtMs = Date.parse(token.expiresAt);
|
|
102
|
+
if (Number.isFinite(expiresAtMs) && expiresAtMs - now() > renewBeforeMs) return;
|
|
103
|
+
renewing = true;
|
|
104
|
+
try {
|
|
105
|
+
token = await options.renewToken();
|
|
106
|
+
if (socket != null) sendRaw(socket, { type: "auth", id: nextId("auth"), token: token.token });
|
|
107
|
+
} catch (cause) {
|
|
108
|
+
log("websockets.channel.renew_failed", {
|
|
109
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
110
|
+
});
|
|
111
|
+
} finally {
|
|
112
|
+
renewing = false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const connectOnce = async () => {
|
|
116
|
+
await renew();
|
|
117
|
+
const url = gatewayConnectUrl(options.gatewayUrl);
|
|
118
|
+
let next;
|
|
119
|
+
try {
|
|
120
|
+
next = await options.openWebSocket({
|
|
121
|
+
url,
|
|
122
|
+
headers: { authorization: `Bearer ${token.token}` },
|
|
123
|
+
signal: lifetime.signal
|
|
124
|
+
});
|
|
125
|
+
} catch (cause) {
|
|
126
|
+
log("websockets.channel.connect_failed", {
|
|
127
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
128
|
+
});
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (lifetime.signal.aborted) {
|
|
132
|
+
next.close(1e3, "closed");
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
stage("socket-open" /* SocketOpen */);
|
|
136
|
+
socket = next;
|
|
137
|
+
subscribed = false;
|
|
138
|
+
const current = next;
|
|
139
|
+
const timers = [];
|
|
140
|
+
const closed = new Promise((resolve) => {
|
|
141
|
+
current.addEventListener("close", (event) => {
|
|
142
|
+
for (const timer of timers) clearInterval(timer);
|
|
143
|
+
if (socket === current) {
|
|
144
|
+
socket = null;
|
|
145
|
+
const wasSubscribed = subscribed;
|
|
146
|
+
subscribed = false;
|
|
147
|
+
if (wasSubscribed) {
|
|
148
|
+
for (const listener of [...closeListeners]) {
|
|
149
|
+
listener({ code: event.code, reason: event.reason });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
resolve();
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
let subscribeId = null;
|
|
157
|
+
const subscribe = () => {
|
|
158
|
+
if (socket !== current || subscribeId != null) return;
|
|
159
|
+
subscribeId = nextId("subscribe");
|
|
160
|
+
const frame = {
|
|
161
|
+
type: "channel.subscribe",
|
|
162
|
+
id: subscribeId,
|
|
163
|
+
channel: inbound
|
|
164
|
+
};
|
|
165
|
+
if (lastCursor != null) frame.afterCursor = lastCursor;
|
|
166
|
+
else if (options.initialCursor != null) frame.afterCursor = options.initialCursor;
|
|
167
|
+
sendRaw(current, frame);
|
|
168
|
+
};
|
|
169
|
+
current.addEventListener("message", (event) => {
|
|
170
|
+
if (socket !== current) return;
|
|
171
|
+
const frame = parseServerFrame(event);
|
|
172
|
+
if (frame == null) return;
|
|
173
|
+
switch (frame.type) {
|
|
174
|
+
case "hello": {
|
|
175
|
+
stage("hello" /* Hello */);
|
|
176
|
+
if (subscribeAllowed) subscribe();
|
|
177
|
+
else options.subscribeAfter?.then(subscribe, () => void 0);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
case "ack": {
|
|
181
|
+
if (frame.operation === "channel.subscribe" && frame.requestId === subscribeId) {
|
|
182
|
+
takeCursor(frame.cursor);
|
|
183
|
+
subscribed = true;
|
|
184
|
+
attempt = 0;
|
|
185
|
+
stage("subscribed" /* Subscribed */);
|
|
186
|
+
for (const listener of [...openListeners]) listener();
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
case "error": {
|
|
191
|
+
log("websockets.channel.gateway_error", { code: frame.code, message: frame.message });
|
|
192
|
+
if (frame.requestId === subscribeId) current.close(4e3, "subscribe failed");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
case "channel.message": {
|
|
196
|
+
if (frame.message.channel !== inbound) return;
|
|
197
|
+
takeCursor(frame.message.cursor);
|
|
198
|
+
if (frame.message.name !== options.messageName) return;
|
|
199
|
+
for (const listener of [...frameListeners]) listener(frame.message.data);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
case "pong":
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
timers.push(
|
|
207
|
+
setInterval(() => {
|
|
208
|
+
sendRaw(current, { type: "ping", nonce: nextId("ping") });
|
|
209
|
+
}, PING_INTERVAL_MS),
|
|
210
|
+
setInterval(() => {
|
|
211
|
+
void renew();
|
|
212
|
+
}, RENEW_CHECK_MS)
|
|
213
|
+
);
|
|
214
|
+
await closed;
|
|
215
|
+
return true;
|
|
216
|
+
};
|
|
217
|
+
const loop = async () => {
|
|
218
|
+
while (!lifetime.signal.aborted) {
|
|
219
|
+
await connectOnce();
|
|
220
|
+
if (lifetime.signal.aborted) return;
|
|
221
|
+
attempt += 1;
|
|
222
|
+
await sleep(backoffDelayMs({ attempt, minDelayMs: 1e3, maxDelayMs: 4e3 }), lifetime.signal);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
void loop();
|
|
226
|
+
return {
|
|
227
|
+
send: (frame) => {
|
|
228
|
+
if (socket == null || !subscribed) return false;
|
|
229
|
+
return sendRaw(socket, {
|
|
230
|
+
type: "channel.publish",
|
|
231
|
+
id: nextId("publish"),
|
|
232
|
+
channel: outbound,
|
|
233
|
+
name: options.messageName,
|
|
234
|
+
data: frame,
|
|
235
|
+
retain: true
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
onFrame: (listener) => {
|
|
239
|
+
frameListeners.add(listener);
|
|
240
|
+
return () => frameListeners.delete(listener);
|
|
241
|
+
},
|
|
242
|
+
onOpen: (listener) => {
|
|
243
|
+
openListeners.add(listener);
|
|
244
|
+
return () => openListeners.delete(listener);
|
|
245
|
+
},
|
|
246
|
+
onClose: (listener) => {
|
|
247
|
+
closeListeners.add(listener);
|
|
248
|
+
return () => closeListeners.delete(listener);
|
|
249
|
+
},
|
|
250
|
+
get isOpen() {
|
|
251
|
+
return socket != null && subscribed && socket.readyState === SOCKET_OPEN;
|
|
252
|
+
},
|
|
253
|
+
close: (reason) => {
|
|
254
|
+
lifetime.abort();
|
|
255
|
+
const current = socket;
|
|
256
|
+
socket = null;
|
|
257
|
+
subscribed = false;
|
|
258
|
+
current?.close(1e3, reason ?? "closed");
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
export {
|
|
263
|
+
ChannelTransportStage,
|
|
264
|
+
GATEWAY_PROTOCOL_VERSION,
|
|
265
|
+
compareGatewayCursors,
|
|
266
|
+
createChannelTransport,
|
|
267
|
+
gatewayConnectUrl
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=channel-transport.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/channel-transport.ts"],
|
|
4
|
+
"sourcesContent": ["import * as z from 'zod';\n\nimport {\n websocketGatewayServerFrameSchema,\n compareWebsocketGatewayCursors,\n type WebsocketGatewayData,\n} from '@finalai/websocket-gateway-protocol';\nexport interface SocketEventIface {\n data?: unknown;\n code?: number;\n reason?: string;\n}\nexport interface WebSocketLikeIface {\n readyState: number;\n send(data: string): void;\n close(code?: number, reason?: string): void;\n addEventListener(type: string, listener: (event: SocketEventIface) => void): void;\n}\nexport interface ChannelTransportIface {\n send(data: WebsocketGatewayData): boolean;\n onFrame(listener: (data: WebsocketGatewayData) => void): () => void;\n onOpen(listener: () => void): () => void;\n onClose(listener: (info: { code?: number; reason?: string }) => void): () => void;\n readonly isOpen: boolean;\n close(reason?: string): void;\n}\nconst backoffDelayMs = (args: { attempt: number; minDelayMs: number; maxDelayMs: number }) =>\n Math.min(args.maxDelayMs, args.minDelayMs * 2 ** Math.max(0, args.attempt - 1));\n\nexport interface ChannelTransportTokenIface {\n token: string;\n expiresAt: string;\n}\n\n// Points of each link's setup, for latency traces.\nexport enum ChannelTransportStage {\n SocketOpen = 'socket-open',\n // The gateway's hello arrived.\n Hello = 'hello',\n // The gateway acknowledged the subscribe: the link is up.\n Subscribed = 'subscribed',\n}\n\nexport interface ChannelTransportOptionsIface {\n // The gateway's WebSocket base URL (wss://\u2026); `/connect?protocolVersion=1` is appended.\n gatewayUrl: string;\n token: ChannelTransportTokenIface;\n // Separate inbound and outbound channels allow bidirectional integrations.\n inbound: string;\n outbound: string;\n messageName: string;\n initialCursor?: string;\n openWebSocket: (args: {\n url: string;\n headers: Record<string, string>;\n signal: AbortSignal;\n }) => Promise<WebSocketLikeIface>;\n // Supply a fresh token before expiry.\n renewToken?: () => Promise<ChannelTransportTokenIface>;\n renewBeforeMs?: number;\n // Delay subscriptions until the caller is ready to receive retained frames.\n subscribeAfter?: Promise<void>;\n onStage?: (stage: ChannelTransportStage) => void;\n now?: () => number;\n sleep?: (ms: number, signal: AbortSignal) => Promise<void>;\n log?: (event: string, fields: Record<string, string | number | boolean | null>) => void;\n}\n\nexport const GATEWAY_PROTOCOL_VERSION = 1;\n// websocket-gateway's subscribe cursor for \"no retained history\".\nconst DEFAULT_RENEW_BEFORE_MS = 2 * 60 * 1000;\nconst PING_INTERVAL_MS = 20_000;\nconst RENEW_CHECK_MS = 15_000;\nconst SOCKET_OPEN = 1;\n\nconst gatewayServerFrameSchema = websocketGatewayServerFrameSchema;\n\n// The client frames this transport sends.\ninterface GatewayClientFrameIface {\n type: 'auth' | 'channel.subscribe' | 'channel.publish' | 'ping';\n id?: string;\n token?: string;\n channel?: string;\n afterCursor?: string;\n name?: string;\n data?: WebsocketGatewayData;\n retain?: boolean;\n nonce?: string;\n}\n\nconst parseServerFrame = (\n event: SocketEventIface,\n): z.infer<typeof gatewayServerFrameSchema> | null => {\n const text = z.string().safeParse(event.data);\n if (!text.success) return null;\n try {\n const parsed = gatewayServerFrameSchema.safeParse(JSON.parse(text.data));\n return parsed.success ? parsed.data : null;\n } catch {\n return null;\n }\n};\n\n// Gateway cursors are Redis stream ids `<ms>-<seq>`.\nexport const compareGatewayCursors = compareWebsocketGatewayCursors;\n\nconst abortableSleep = (ms: number, signal: AbortSignal): Promise<void> =>\n new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n\n// Deployments configure the gateway as either its origin or its `/connect`\n// endpoint; both dial `/connect`.\nexport const gatewayConnectUrl = (gatewayUrl: string): string => {\n const url = new URL(gatewayUrl);\n const path = url.pathname.replace(/\\/+$/, '');\n url.pathname = path.endsWith('/connect') ? path : `${path}/connect`;\n url.searchParams.set('protocolVersion', String(GATEWAY_PROTOCOL_VERSION));\n return url.toString();\n};\n\nexport const createChannelTransport = (\n options: ChannelTransportOptionsIface,\n): ChannelTransportIface => {\n const { inbound, outbound } = options;\n const now = options.now ?? Date.now;\n const sleep = options.sleep ?? abortableSleep;\n const log = options.log ?? ((): void => undefined);\n const stage = (reached: ChannelTransportStage): void => {\n try {\n options.onStage?.(reached);\n } catch {\n // Stage listeners are informational.\n }\n };\n const renewBeforeMs = options.renewBeforeMs ?? DEFAULT_RENEW_BEFORE_MS;\n let subscribeAllowed = options.subscribeAfter == null;\n options.subscribeAfter?.then(\n () => {\n subscribeAllowed = true;\n },\n () => undefined,\n );\n const lifetime = new AbortController();\n const frameListeners = new Set<(frame: WebsocketGatewayData) => void>();\n const openListeners = new Set<() => void>();\n const closeListeners = new Set<(info: { code?: number; reason?: string }) => void>();\n let token = options.token;\n let socket: WebSocketLikeIface | null = null;\n let subscribed = false;\n // Newest retained cursor taken from the inbound channel; a reconnect resumes after it.\n let lastCursor: string | null = null;\n let requestCounter = 0;\n let attempt = 0;\n let renewing = false;\n\n const nextId = (prefix: string): string => {\n requestCounter += 1;\n return `${prefix}-${requestCounter}`;\n };\n\n const sendRaw = (target: WebSocketLikeIface, frame: GatewayClientFrameIface): boolean => {\n if (target.readyState !== SOCKET_OPEN) return false;\n try {\n target.send(JSON.stringify(frame));\n return true;\n } catch {\n return false;\n }\n };\n\n const takeCursor = (cursor: string | null | undefined): void => {\n if (cursor == null) return;\n if (lastCursor == null || compareGatewayCursors(cursor, lastCursor) > 0) lastCursor = cursor;\n };\n\n const renew = async (): Promise<void> => {\n if (renewing || options.renewToken == null) return;\n const expiresAtMs = Date.parse(token.expiresAt);\n if (Number.isFinite(expiresAtMs) && expiresAtMs - now() > renewBeforeMs) return;\n renewing = true;\n try {\n token = await options.renewToken();\n if (socket != null) sendRaw(socket, { type: 'auth', id: nextId('auth'), token: token.token });\n } catch (cause) {\n log('websockets.channel.renew_failed', {\n error: cause instanceof Error ? cause.message : String(cause),\n });\n } finally {\n renewing = false;\n }\n };\n\n const connectOnce = async (): Promise<boolean> => {\n await renew();\n const url = gatewayConnectUrl(options.gatewayUrl);\n let next: WebSocketLikeIface;\n try {\n next = await options.openWebSocket({\n url,\n headers: { authorization: `Bearer ${token.token}` },\n signal: lifetime.signal,\n });\n } catch (cause) {\n log('websockets.channel.connect_failed', {\n error: cause instanceof Error ? cause.message : String(cause),\n });\n return false;\n }\n if (lifetime.signal.aborted) {\n next.close(1000, 'closed');\n return true;\n }\n stage(ChannelTransportStage.SocketOpen);\n socket = next;\n subscribed = false;\n const current = next;\n const timers: ReturnType<typeof setInterval>[] = [];\n const closed = new Promise<void>((resolve) => {\n current.addEventListener('close', (event) => {\n for (const timer of timers) clearInterval(timer);\n if (socket === current) {\n socket = null;\n const wasSubscribed = subscribed;\n subscribed = false;\n if (wasSubscribed) {\n for (const listener of [...closeListeners]) {\n listener({ code: event.code, reason: event.reason });\n }\n }\n }\n resolve();\n });\n });\n let subscribeId: string | null = null;\n const subscribe = (): void => {\n if (socket !== current || subscribeId != null) return;\n subscribeId = nextId('subscribe');\n const frame: GatewayClientFrameIface = {\n type: 'channel.subscribe',\n id: subscribeId,\n channel: inbound,\n };\n // Resume after the newest received cursor; initial replay is caller-configured.\n if (lastCursor != null) frame.afterCursor = lastCursor;\n else if (options.initialCursor != null) frame.afterCursor = options.initialCursor;\n sendRaw(current, frame);\n };\n current.addEventListener('message', (event) => {\n if (socket !== current) return;\n const frame = parseServerFrame(event);\n if (frame == null) return;\n switch (frame.type) {\n case 'hello': {\n stage(ChannelTransportStage.Hello);\n if (subscribeAllowed) subscribe();\n else options.subscribeAfter?.then(subscribe, () => undefined);\n return;\n }\n case 'ack': {\n if (frame.operation === 'channel.subscribe' && frame.requestId === subscribeId) {\n takeCursor(frame.cursor);\n subscribed = true;\n attempt = 0;\n stage(ChannelTransportStage.Subscribed);\n for (const listener of [...openListeners]) listener();\n }\n return;\n }\n case 'error': {\n log('websockets.channel.gateway_error', { code: frame.code, message: frame.message });\n if (frame.requestId === subscribeId) current.close(4000, 'subscribe failed');\n return;\n }\n case 'channel.message': {\n if (frame.message.channel !== inbound) return;\n takeCursor(frame.message.cursor);\n if (frame.message.name !== options.messageName) return;\n for (const listener of [...frameListeners]) listener(frame.message.data);\n return;\n }\n case 'pong':\n return;\n }\n });\n timers.push(\n setInterval(() => {\n sendRaw(current, { type: 'ping', nonce: nextId('ping') });\n }, PING_INTERVAL_MS),\n setInterval(() => {\n void renew();\n }, RENEW_CHECK_MS),\n );\n await closed;\n return true;\n };\n\n const loop = async (): Promise<void> => {\n while (!lifetime.signal.aborted) {\n await connectOnce();\n if (lifetime.signal.aborted) return;\n attempt += 1;\n await sleep(backoffDelayMs({ attempt, minDelayMs: 1000, maxDelayMs: 4000 }), lifetime.signal);\n }\n };\n void loop();\n\n return {\n send: (frame) => {\n if (socket == null || !subscribed) return false;\n return sendRaw(socket, {\n type: 'channel.publish',\n id: nextId('publish'),\n channel: outbound,\n name: options.messageName,\n data: frame,\n retain: true,\n });\n },\n onFrame: (listener) => {\n frameListeners.add(listener);\n return () => frameListeners.delete(listener);\n },\n onOpen: (listener) => {\n openListeners.add(listener);\n return () => openListeners.delete(listener);\n },\n onClose: (listener) => {\n closeListeners.add(listener);\n return () => closeListeners.delete(listener);\n },\n get isOpen() {\n return socket != null && subscribed && socket.readyState === SOCKET_OPEN;\n },\n close: (reason) => {\n lifetime.abort();\n const current = socket;\n socket = null;\n subscribed = false;\n current?.close(1000, reason ?? 'closed');\n },\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["ChannelTransportStage"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { type WebsocketGatewayAction as WebsocketsAction, type WebsocketGatewayCapability as WebsocketsCapability, type WebsocketGatewayClientFrame as WebsocketsClientFrame, type WebsocketGatewayData as WebsocketsData, type WebsocketGatewayMessage as WebsocketsMessage, type WebsocketGatewayPresenceMember as WebsocketsPresenceMember, type WebsocketGatewayProxyUpstreamDescriptor as WebsocketsProxyUpstreamDescriptor, type WebsocketGatewayRealtimeMessage as WebsocketsRealtimeMessage, type WebsocketGatewayServerFrame as WebsocketsServerFrame } from '@finalai/websocket-gateway-protocol';
|
|
2
|
+
export type { WebsocketGatewayAction as WebsocketsAction, WebsocketGatewayCapability as WebsocketsCapability, WebsocketGatewayClientFrame as WebsocketsClientFrame, WebsocketGatewayData as WebsocketsData, WebsocketGatewayMessage as WebsocketsMessage, WebsocketGatewayPresenceMember as WebsocketsPresenceMember, WebsocketGatewayProxyPayload as WebsocketsProxyPayload, WebsocketGatewayProxyUpstreamDescriptor as WebsocketsProxyUpstreamDescriptor, WebsocketGatewayRealtimeMessage as WebsocketsRealtimeMessage, WebsocketGatewayServerFrame as WebsocketsServerFrame, } from '@finalai/websocket-gateway-protocol';
|
|
3
|
+
export type GatewayAuthProvider = string | (() => string | Promise<string>);
|
|
4
|
+
export type GatewayFrameHandler = (frame: WebsocketsServerFrame) => void;
|
|
5
|
+
export type WebsocketsProxyTarget = string | Pick<WebsocketsProxyUpstreamDescriptor, 'id'>;
|
|
6
|
+
export type WebsocketsProxySendData = string | ArrayBuffer | ArrayBufferView;
|
|
7
|
+
export type WebsocketsErrorFrame = Extract<WebsocketsServerFrame, {
|
|
8
|
+
type: 'error';
|
|
9
|
+
}>;
|
|
10
|
+
export interface WebsocketsProxyHandlers {
|
|
11
|
+
onOpen?: (upstream: string) => void;
|
|
12
|
+
onMessage?: (data: string | ArrayBuffer) => void;
|
|
13
|
+
onClose?: (event: {
|
|
14
|
+
code?: number;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
}
|
|
18
|
+
export interface WebsocketsChannelPublishOptions {
|
|
19
|
+
name?: string;
|
|
20
|
+
idempotencyKey?: string;
|
|
21
|
+
retain?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface WebsocketsChannelSubscribeOptionsIface {
|
|
24
|
+
afterCursor?: string;
|
|
25
|
+
onError?: (error: WebsocketsErrorFrame) => void;
|
|
26
|
+
onSubscribed?: () => void;
|
|
27
|
+
}
|
|
28
|
+
export interface WebsocketsRealtimeSubscribeOptionsIface {
|
|
29
|
+
afterCursor?: string;
|
|
30
|
+
onError?: (error: {
|
|
31
|
+
code: string;
|
|
32
|
+
message: string;
|
|
33
|
+
retryable: boolean;
|
|
34
|
+
}) => void;
|
|
35
|
+
onResyncRequired?: () => void;
|
|
36
|
+
onSubscribed?: () => void;
|
|
37
|
+
}
|
|
38
|
+
export interface WebsocketsRealtimePresenceEventIface {
|
|
39
|
+
action: 'enter' | 'update' | 'leave';
|
|
40
|
+
member: WebsocketsPresenceMember;
|
|
41
|
+
topic: string;
|
|
42
|
+
}
|
|
43
|
+
export interface WebsocketsRealtimePresenceIface {
|
|
44
|
+
enter: (data?: WebsocketsData) => void;
|
|
45
|
+
update: (data?: WebsocketsData) => void;
|
|
46
|
+
leave: () => void;
|
|
47
|
+
get: (options?: WebsocketsPresenceGetOptionsIface) => Promise<WebsocketsPresenceMember[]>;
|
|
48
|
+
subscribe: (handler: (event: WebsocketsRealtimePresenceEventIface) => void) => () => void;
|
|
49
|
+
}
|
|
50
|
+
export interface WebsocketsPresenceGetOptionsIface {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
}
|
|
53
|
+
export interface WebsocketsClientOptions {
|
|
54
|
+
url: string;
|
|
55
|
+
auth: GatewayAuthProvider;
|
|
56
|
+
WebSocketCtor?: typeof WebSocket;
|
|
57
|
+
reconnect?: boolean;
|
|
58
|
+
onReconnect?: () => void;
|
|
59
|
+
heartbeatIntervalMs?: number;
|
|
60
|
+
ackTimeoutMs?: number;
|
|
61
|
+
maxQueuedFrames?: number;
|
|
62
|
+
}
|
|
63
|
+
export interface WebsocketsChannelSubscriptionDiagnosticsIface {
|
|
64
|
+
bufferedBytes: number;
|
|
65
|
+
bufferedBytesHighWater: number;
|
|
66
|
+
bufferedFrames: number;
|
|
67
|
+
bufferedFramesHighWater: number;
|
|
68
|
+
channel: string;
|
|
69
|
+
pendingGapFill: boolean;
|
|
70
|
+
pendingSubscribeAck: boolean;
|
|
71
|
+
receivedBytes: number;
|
|
72
|
+
receivedFrames: number;
|
|
73
|
+
}
|
|
74
|
+
export interface WebsocketsConnectionDiagnosticsIface {
|
|
75
|
+
bufferedBytes: number;
|
|
76
|
+
bufferedBytesHighWater: number;
|
|
77
|
+
bufferedFrames: number;
|
|
78
|
+
bufferedFramesHighWater: number;
|
|
79
|
+
bufferLimitFlushes: number;
|
|
80
|
+
channelMessageBytes: number;
|
|
81
|
+
channelMessageFrames: number;
|
|
82
|
+
channelStreams: WebsocketsChannelSubscriptionDiagnosticsIface[];
|
|
83
|
+
channelSubscriptions: number;
|
|
84
|
+
gapFillErrors: number;
|
|
85
|
+
gapFillRequests: number;
|
|
86
|
+
gapFillResponses: number;
|
|
87
|
+
gapFillSendErrors: number;
|
|
88
|
+
gapFillTimeouts: number;
|
|
89
|
+
frameHandlers: number;
|
|
90
|
+
frameHandlersHighWater: number;
|
|
91
|
+
lastCloseCode: number | null;
|
|
92
|
+
lastCloseReason: string | null;
|
|
93
|
+
pendingGapFills: number;
|
|
94
|
+
pendingSubscribeAcks: number;
|
|
95
|
+
queuedBytes: number;
|
|
96
|
+
queuedFrames: number;
|
|
97
|
+
realtimeMessageBytes: number;
|
|
98
|
+
realtimeMessageFrames: number;
|
|
99
|
+
realtimeSubscriptions: number;
|
|
100
|
+
receivedBytes: number;
|
|
101
|
+
receivedFrames: number;
|
|
102
|
+
reconnects: number;
|
|
103
|
+
rpcCallBytes: number;
|
|
104
|
+
rpcCallFrames: number;
|
|
105
|
+
sentBytes: number;
|
|
106
|
+
sentFrames: number;
|
|
107
|
+
socketBufferedBytes: number;
|
|
108
|
+
socketBufferedBytesHighWater: number;
|
|
109
|
+
socketCloses: number;
|
|
110
|
+
socketOpens: number;
|
|
111
|
+
subscribeAcks: number;
|
|
112
|
+
subscribeErrors: number;
|
|
113
|
+
subscribeRequests: number;
|
|
114
|
+
subscribeSuperseded: number;
|
|
115
|
+
subscribeTimeouts: number;
|
|
116
|
+
}
|
|
117
|
+
interface RegisterChannelSubscriptionArgsIface {
|
|
118
|
+
afterCursor?: string;
|
|
119
|
+
channel: string;
|
|
120
|
+
onError?: (error: WebsocketsErrorFrame) => void;
|
|
121
|
+
onSubscribed?: () => void;
|
|
122
|
+
}
|
|
123
|
+
export declare class WebsocketsConnection {
|
|
124
|
+
private readonly options;
|
|
125
|
+
private socket;
|
|
126
|
+
private handlers;
|
|
127
|
+
private replayRegistrations;
|
|
128
|
+
private heartbeatTimer;
|
|
129
|
+
private channelSubscriptions;
|
|
130
|
+
private realtimeSubscriptions;
|
|
131
|
+
private realtimeSubscribeQueue;
|
|
132
|
+
private realtimeSubscribeQueued;
|
|
133
|
+
private realtimeUnsubscribeQueue;
|
|
134
|
+
private realtimeUnsubscribeQueued;
|
|
135
|
+
private socketAuthToken;
|
|
136
|
+
private authenticated;
|
|
137
|
+
private queuedFrames;
|
|
138
|
+
private queuedBytes;
|
|
139
|
+
private manuallyClosed;
|
|
140
|
+
private hasAuthenticated;
|
|
141
|
+
private diagnostics;
|
|
142
|
+
constructor(options: WebsocketsClientOptions);
|
|
143
|
+
connect(): Promise<void>;
|
|
144
|
+
private openSocket;
|
|
145
|
+
private resolveSocketUrlAndAuth;
|
|
146
|
+
private authenticateInitialSocket;
|
|
147
|
+
private startHeartbeat;
|
|
148
|
+
private clearHeartbeat;
|
|
149
|
+
private routeIncomingFrame;
|
|
150
|
+
private dispatchableSubscriptionDeliveries;
|
|
151
|
+
private settleSubscriptionBuffer;
|
|
152
|
+
private bufferSubscriptionDelivery;
|
|
153
|
+
private currentBufferDiagnostics;
|
|
154
|
+
private clearSubscriptionGapFill;
|
|
155
|
+
private beginSubscriptionGapFill;
|
|
156
|
+
private channelFrames;
|
|
157
|
+
private dispatchRealtimeDeliveries;
|
|
158
|
+
private beginRealtimeGapFill;
|
|
159
|
+
private beginChannelGapFill;
|
|
160
|
+
private resubscribeChannels;
|
|
161
|
+
private resubscribeRealtimeTopics;
|
|
162
|
+
private queueRealtimeSubscribe;
|
|
163
|
+
private sendRealtimeSubscribeBatches;
|
|
164
|
+
private queueRealtimeUnsubscribe;
|
|
165
|
+
private sendSubscribeFrame;
|
|
166
|
+
auth: {
|
|
167
|
+
authorize: (args: {
|
|
168
|
+
token: string;
|
|
169
|
+
}) => Promise<void>;
|
|
170
|
+
};
|
|
171
|
+
onFrame(handler: GatewayFrameHandler): () => void;
|
|
172
|
+
registerReplay(key: string, replay: () => void): () => void;
|
|
173
|
+
waitForAck(requestId: string, operation: string): Promise<void>;
|
|
174
|
+
channel(name: string): WebsocketsChannel;
|
|
175
|
+
realtime(topic: string): WebsocketsRealtimeTopic;
|
|
176
|
+
registerRealtimeSubscription(args: {
|
|
177
|
+
topic: string;
|
|
178
|
+
handler: (message: WebsocketsRealtimeMessage) => void;
|
|
179
|
+
options?: WebsocketsRealtimeSubscribeOptionsIface;
|
|
180
|
+
}): () => void;
|
|
181
|
+
registerChannelSubscription(args: RegisterChannelSubscriptionArgsIface): () => void;
|
|
182
|
+
rpc(name: string): WebsocketsRpc;
|
|
183
|
+
bridge(name: string): WebsocketsBridge;
|
|
184
|
+
proxy(upstream: WebsocketsProxyTarget): WebsocketsProxy;
|
|
185
|
+
send(frame: WebsocketsClientFrame): void;
|
|
186
|
+
getDiagnostics(): WebsocketsConnectionDiagnosticsIface;
|
|
187
|
+
private sendAuthFrame;
|
|
188
|
+
private sendEncodedFrame;
|
|
189
|
+
private flushQueuedFrames;
|
|
190
|
+
ping(nonce?: string): void;
|
|
191
|
+
close(code?: number, reason?: string): void;
|
|
192
|
+
}
|
|
193
|
+
export declare class WebsocketsChannel {
|
|
194
|
+
private readonly connection;
|
|
195
|
+
private readonly name;
|
|
196
|
+
constructor(connection: WebsocketsConnection, name: string);
|
|
197
|
+
subscribe(handler: (message: WebsocketsMessage) => void, options?: string | WebsocketsChannelSubscribeOptionsIface): () => void;
|
|
198
|
+
publish(data: WebsocketsData, options?: string | WebsocketsChannelPublishOptions): void;
|
|
199
|
+
history(args?: {
|
|
200
|
+
afterCursor?: string;
|
|
201
|
+
limit?: number;
|
|
202
|
+
}): Promise<WebsocketsMessage[]>;
|
|
203
|
+
presence(): WebsocketsPresence;
|
|
204
|
+
}
|
|
205
|
+
export declare class WebsocketsRealtimeTopic {
|
|
206
|
+
private readonly connection;
|
|
207
|
+
private readonly topic;
|
|
208
|
+
constructor(connection: WebsocketsConnection, topic: string);
|
|
209
|
+
subscribe(handler: (message: WebsocketsRealtimeMessage) => void, options?: WebsocketsRealtimeSubscribeOptionsIface): () => void;
|
|
210
|
+
presence(): WebsocketsRealtimePresenceIface;
|
|
211
|
+
}
|
|
212
|
+
export declare class WebsocketsPresence {
|
|
213
|
+
private readonly connection;
|
|
214
|
+
private readonly channel;
|
|
215
|
+
private unregisterPresenceReplay;
|
|
216
|
+
private data;
|
|
217
|
+
constructor(connection: WebsocketsConnection, channel: string);
|
|
218
|
+
enter(data?: WebsocketsData): void;
|
|
219
|
+
update(data?: WebsocketsData): void;
|
|
220
|
+
leave(): void;
|
|
221
|
+
get(options?: WebsocketsPresenceGetOptionsIface): Promise<WebsocketsPresenceMember[]>;
|
|
222
|
+
subscribe(handler: (event: {
|
|
223
|
+
channel: string;
|
|
224
|
+
action: 'enter' | 'update' | 'leave';
|
|
225
|
+
member: WebsocketsPresenceMember;
|
|
226
|
+
}) => void): () => void;
|
|
227
|
+
}
|
|
228
|
+
export declare class WebsocketsRpc {
|
|
229
|
+
private readonly connection;
|
|
230
|
+
private readonly name;
|
|
231
|
+
constructor(connection: WebsocketsConnection, name: string);
|
|
232
|
+
connect(handler: (data: WebsocketsData) => WebsocketsData | Promise<WebsocketsData>): () => void;
|
|
233
|
+
call(data: WebsocketsData, timeoutMs?: number, signal?: AbortSignal): Promise<WebsocketsData | undefined>;
|
|
234
|
+
}
|
|
235
|
+
export declare class WebsocketsBridge {
|
|
236
|
+
private readonly connection;
|
|
237
|
+
private readonly name;
|
|
238
|
+
constructor(connection: WebsocketsConnection, name: string);
|
|
239
|
+
join(role: string, handler?: (data: WebsocketsData) => void): () => void;
|
|
240
|
+
joinReady(role: string, handler?: (data: WebsocketsData) => void): Promise<() => void>;
|
|
241
|
+
send(data: WebsocketsData): void;
|
|
242
|
+
}
|
|
243
|
+
export declare class WebsocketsProxy {
|
|
244
|
+
private readonly connection;
|
|
245
|
+
private readonly upstreamId;
|
|
246
|
+
constructor(connection: WebsocketsConnection, upstream: WebsocketsProxyTarget);
|
|
247
|
+
connect(handlers?: WebsocketsProxyHandlers): () => void;
|
|
248
|
+
send(data: WebsocketsProxySendData): void;
|
|
249
|
+
close(code?: number, reason?: string): void;
|
|
250
|
+
}
|
|
251
|
+
export declare class WebsocketsWorker {
|
|
252
|
+
private readonly connection;
|
|
253
|
+
private helloArgs;
|
|
254
|
+
private inFlightLeases;
|
|
255
|
+
private pendingTerminalLeaseOperations;
|
|
256
|
+
private unregisterHelloReplay;
|
|
257
|
+
private unregisterTerminalOperationReplay;
|
|
258
|
+
constructor(connection: WebsocketsConnection);
|
|
259
|
+
hello(args: {
|
|
260
|
+
workerId: string;
|
|
261
|
+
namespace?: string;
|
|
262
|
+
appVersion?: string;
|
|
263
|
+
queues: string[];
|
|
264
|
+
maxConcurrency?: number;
|
|
265
|
+
inFlightLeaseIds?: string[];
|
|
266
|
+
inFlightLeases?: Array<{
|
|
267
|
+
leaseId: string;
|
|
268
|
+
queue: string;
|
|
269
|
+
}>;
|
|
270
|
+
}): void;
|
|
271
|
+
private sendHello;
|
|
272
|
+
onOffer(handler: (offer: {
|
|
273
|
+
queue: string;
|
|
274
|
+
leaseId: string;
|
|
275
|
+
payload: WebsocketsData;
|
|
276
|
+
}) => void): () => void;
|
|
277
|
+
accept(leaseId: string): Promise<void>;
|
|
278
|
+
heartbeat(leaseId: string): Promise<void>;
|
|
279
|
+
complete(leaseId: string, result?: WebsocketsData): Promise<void>;
|
|
280
|
+
fail(leaseId: string, error: string): Promise<void>;
|
|
281
|
+
drain(reason?: string): Promise<void>;
|
|
282
|
+
close(code?: number, reason?: string): void;
|
|
283
|
+
private sendTerminalLeaseOperation;
|
|
284
|
+
private clearPendingTerminalLeaseOperation;
|
|
285
|
+
private replayPendingTerminalLeaseOperations;
|
|
286
|
+
private sendTerminalLeaseOperationFrame;
|
|
287
|
+
private sendLeaseOperation;
|
|
288
|
+
}
|
|
289
|
+
export declare const client: {
|
|
290
|
+
connect: (options: WebsocketsClientOptions) => Promise<WebsocketsConnection>;
|
|
291
|
+
};
|
|
292
|
+
export declare const worker: {
|
|
293
|
+
connect: (options: WebsocketsClientOptions) => Promise<WebsocketsWorker>;
|
|
294
|
+
};
|
|
295
|
+
export declare const capability: (channels: string[], actions: WebsocketsAction[]) => WebsocketsCapability;
|