@api.global/typedsocket 5.1.2 → 6.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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.clienttagreconciler.d.ts +51 -0
- package/dist_ts/classes.clienttagreconciler.js +290 -0
- package/dist_ts/classes.nativebyteerror.d.ts +13 -0
- package/dist_ts/classes.nativebyteerror.js +26 -0
- package/dist_ts/classes.nativebytemanager.d.ts +327 -0
- package/dist_ts/classes.nativebytemanager.js +2343 -0
- package/dist_ts/{typedsocket.classes.typedsocket.d.ts → classes.typedsocket.d.ts} +122 -13
- package/dist_ts/classes.typedsocket.js +1733 -0
- package/dist_ts/classes.typedsockettagpolicymanager.d.ts +112 -0
- package/dist_ts/classes.typedsockettagpolicymanager.js +549 -0
- package/dist_ts/constants.nativebytes.d.ts +59 -0
- package/dist_ts/constants.nativebytes.js +58 -0
- package/dist_ts/helpers.nativebytecodec.d.ts +8 -0
- package/dist_ts/helpers.nativebytecodec.js +328 -0
- package/dist_ts/helpers.websocket.d.ts +8 -0
- package/dist_ts/helpers.websocket.js +13 -0
- package/dist_ts/index.d.ts +9 -1
- package/dist_ts/index.js +6 -2
- package/dist_ts/interfaces.diagnostics.d.ts +73 -0
- package/dist_ts/interfaces.diagnostics.js +2 -0
- package/dist_ts/interfaces.nativebytes.d.ts +187 -0
- package/dist_ts/interfaces.nativebytes.js +2 -0
- package/{ts/typedsocket.plugins.ts → dist_ts/plugins.d.ts} +3 -9
- package/dist_ts/plugins.js +14 -0
- package/{license → license.md} +2 -2
- package/package.json +22 -18
- package/readme.hints.md +60 -19
- package/readme.md +536 -56
- package/dist_ts/typedsocket.classes.typedsocket.js +0 -941
- package/dist_ts/typedsocket.plugins.d.ts +0 -11
- package/dist_ts/typedsocket.plugins.js +0 -13
- package/npmextra.json +0 -35
- package/ts/00_commitinfo_data.ts +0 -8
- package/ts/index.ts +0 -1
- package/ts/typedsocket.classes.typedsocket.ts +0 -1156
|
@@ -0,0 +1,1733 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { NATIVE_BYTE_PROTOCOL, NATIVE_BYTE_PROTOCOL_MAJOR } from './constants.nativebytes.js';
|
|
3
|
+
import { NativeByteCapabilityError } from './classes.nativebyteerror.js';
|
|
4
|
+
import { NativeByteManager } from './classes.nativebytemanager.js';
|
|
5
|
+
import { assertBoundedTextIdentifier, TYPEDSOCKET_MAX_TAG_NAME_BYTES, TypedSocketClientTagDeniedError, TypedSocketTagPolicyManager, } from './classes.typedsockettagpolicymanager.js';
|
|
6
|
+
import { ClientTagReconciler } from './classes.clienttagreconciler.js';
|
|
7
|
+
import { toClientWebSocketCloseCode } from './helpers.websocket.js';
|
|
8
|
+
export { toClientWebSocketCloseCode } from './helpers.websocket.js';
|
|
9
|
+
export { TYPEDSOCKET_MAX_RETAINED_CLIENT_TAG_MUTATIONS, TYPEDSOCKET_MAX_RETAINED_CLIENT_TAG_MUTATIONS_PER_NAME, } from './classes.clienttagreconciler.js';
|
|
10
|
+
const TAG_PREFIX = '__typedsocket_tag__';
|
|
11
|
+
const textEncoder = new TextEncoder();
|
|
12
|
+
export const TYPEDSOCKET_MAX_TEXT_FRAME_BYTES = 1024 * 1024;
|
|
13
|
+
export const TYPEDSOCKET_MAX_QUEUED_TEXT_FRAMES = 64;
|
|
14
|
+
export const TYPEDSOCKET_MAX_QUEUED_TEXT_BYTES = 4 * 1024 * 1024;
|
|
15
|
+
export const TYPEDSOCKET_MAX_CONCURRENT_CLIENT_HANDLERS = 16;
|
|
16
|
+
export const TYPEDSOCKET_MAX_RETAINED_CLIENT_CALLBACKS = 64;
|
|
17
|
+
export const TYPEDSOCKET_MAX_PENDING_CLIENT_REQUESTS = 1024;
|
|
18
|
+
export const TYPEDSOCKET_MAX_OUTBOUND_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
19
|
+
export const TYPEDSOCKET_MAX_METHOD_NAME_BYTES = 256;
|
|
20
|
+
export const TYPEDSOCKET_MAX_CORRELATION_ID_BYTES = 256;
|
|
21
|
+
export const TYPEDSOCKET_MAX_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
|
|
22
|
+
export const TYPEDSOCKET_MAX_CONNECTION_RESTORE_TIMEOUT_MS = 10_000;
|
|
23
|
+
export const TYPEDSOCKET_MAX_RECONNECT_RETRIES = 100;
|
|
24
|
+
export const TYPEDSOCKET_MAX_RECONNECT_BACKOFF_MS = 60_000;
|
|
25
|
+
export const TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS_PER_PEER = 64;
|
|
26
|
+
export const TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS = 1024;
|
|
27
|
+
export const TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS_PER_PEER = 8;
|
|
28
|
+
export const TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS = 64;
|
|
29
|
+
export class TypedSocketConnectionRestoreDeniedError extends Error {
|
|
30
|
+
constructor(messageArg) {
|
|
31
|
+
super(messageArg);
|
|
32
|
+
this.name = 'TypedSocketConnectionRestoreDeniedError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Creates a typed connection wrapper around one IWebSocketPeer.
|
|
37
|
+
*/
|
|
38
|
+
function wrapSmartServePeer(peer) {
|
|
39
|
+
return {
|
|
40
|
+
peer,
|
|
41
|
+
async getTagById(tagId) {
|
|
42
|
+
if (!peer.tags.has(tagId)) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const payload = peer.data.get(`${TAG_PREFIX}${tagId}`);
|
|
46
|
+
return { id: tagId, payload };
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function composeAbortSignals(configuredSignalArg, requestSignalArg) {
|
|
51
|
+
if (!configuredSignalArg || configuredSignalArg === requestSignalArg) {
|
|
52
|
+
return {
|
|
53
|
+
signal: requestSignalArg ?? configuredSignalArg,
|
|
54
|
+
cleanup: () => { },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!requestSignalArg) {
|
|
58
|
+
return {
|
|
59
|
+
signal: configuredSignalArg,
|
|
60
|
+
cleanup: () => { },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const abortController = new AbortController();
|
|
64
|
+
const configuredAbortListener = () => {
|
|
65
|
+
abortController.abort(configuredSignalArg.reason);
|
|
66
|
+
};
|
|
67
|
+
const requestAbortListener = () => {
|
|
68
|
+
abortController.abort(requestSignalArg.reason);
|
|
69
|
+
};
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
configuredSignalArg.removeEventListener('abort', configuredAbortListener);
|
|
72
|
+
requestSignalArg.removeEventListener('abort', requestAbortListener);
|
|
73
|
+
};
|
|
74
|
+
if (configuredSignalArg.aborted) {
|
|
75
|
+
configuredAbortListener();
|
|
76
|
+
}
|
|
77
|
+
else if (requestSignalArg.aborted) {
|
|
78
|
+
requestAbortListener();
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
configuredSignalArg.addEventListener('abort', configuredAbortListener, { once: true });
|
|
82
|
+
requestSignalArg.addEventListener('abort', requestAbortListener, { once: true });
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
signal: abortController.signal,
|
|
86
|
+
cleanup,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const packageClientLimits = Object.freeze({
|
|
90
|
+
textFrameBytes: TYPEDSOCKET_MAX_TEXT_FRAME_BYTES,
|
|
91
|
+
queuedTextFrames: TYPEDSOCKET_MAX_QUEUED_TEXT_FRAMES,
|
|
92
|
+
queuedTextBytes: TYPEDSOCKET_MAX_QUEUED_TEXT_BYTES,
|
|
93
|
+
concurrentHandlers: TYPEDSOCKET_MAX_CONCURRENT_CLIENT_HANDLERS,
|
|
94
|
+
retainedCallbacks: TYPEDSOCKET_MAX_RETAINED_CLIENT_CALLBACKS,
|
|
95
|
+
pendingRequests: TYPEDSOCKET_MAX_PENDING_CLIENT_REQUESTS,
|
|
96
|
+
outboundBufferedBytes: TYPEDSOCKET_MAX_OUTBOUND_BUFFERED_BYTES,
|
|
97
|
+
requestTimeoutMs: TYPEDSOCKET_MAX_REQUEST_TIMEOUT_MS,
|
|
98
|
+
connectionRestoreTimeoutMs: TYPEDSOCKET_MAX_CONNECTION_RESTORE_TIMEOUT_MS,
|
|
99
|
+
});
|
|
100
|
+
function normalizeClientLimits(limitsArg) {
|
|
101
|
+
const lower = (nameArg, valueArg, maximumArg) => {
|
|
102
|
+
const value = valueArg ?? maximumArg;
|
|
103
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximumArg) {
|
|
104
|
+
throw new RangeError(`TypedSocket client limit ${nameArg} must be a positive safe integer no greater than ${maximumArg}.`);
|
|
105
|
+
}
|
|
106
|
+
return value;
|
|
107
|
+
};
|
|
108
|
+
const normalized = {
|
|
109
|
+
textFrameBytes: lower('textFrameBytes', limitsArg?.textFrameBytes, packageClientLimits.textFrameBytes),
|
|
110
|
+
queuedTextFrames: lower('queuedTextFrames', limitsArg?.queuedTextFrames, packageClientLimits.queuedTextFrames),
|
|
111
|
+
queuedTextBytes: lower('queuedTextBytes', limitsArg?.queuedTextBytes, packageClientLimits.queuedTextBytes),
|
|
112
|
+
concurrentHandlers: lower('concurrentHandlers', limitsArg?.concurrentHandlers, packageClientLimits.concurrentHandlers),
|
|
113
|
+
retainedCallbacks: lower('retainedCallbacks', limitsArg?.retainedCallbacks, packageClientLimits.retainedCallbacks),
|
|
114
|
+
pendingRequests: lower('pendingRequests', limitsArg?.pendingRequests, packageClientLimits.pendingRequests),
|
|
115
|
+
outboundBufferedBytes: lower('outboundBufferedBytes', limitsArg?.outboundBufferedBytes, packageClientLimits.outboundBufferedBytes),
|
|
116
|
+
requestTimeoutMs: lower('requestTimeoutMs', limitsArg?.requestTimeoutMs, packageClientLimits.requestTimeoutMs),
|
|
117
|
+
connectionRestoreTimeoutMs: lower('connectionRestoreTimeoutMs', limitsArg?.connectionRestoreTimeoutMs, packageClientLimits.connectionRestoreTimeoutMs),
|
|
118
|
+
};
|
|
119
|
+
if (normalized.queuedTextBytes < normalized.textFrameBytes) {
|
|
120
|
+
throw new RangeError('TypedSocket queuedTextBytes must be at least textFrameBytes.');
|
|
121
|
+
}
|
|
122
|
+
return Object.freeze(normalized);
|
|
123
|
+
}
|
|
124
|
+
function normalizeReconnectInteger(nameArg, valueArg, defaultArg, maximumArg, allowZeroArg) {
|
|
125
|
+
const value = valueArg ?? defaultArg;
|
|
126
|
+
if (!Number.isSafeInteger(value)
|
|
127
|
+
|| value < (allowZeroArg ? 0 : 1)
|
|
128
|
+
|| value > maximumArg) {
|
|
129
|
+
throw new RangeError(`TypedSocket ${nameArg} must be ${allowZeroArg ? 'a non-negative' : 'a positive'} safe integer no greater than ${maximumArg}.`);
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function isPlainRecord(valueArg) {
|
|
134
|
+
if (!valueArg || typeof valueArg !== 'object' || Array.isArray(valueArg))
|
|
135
|
+
return false;
|
|
136
|
+
const prototype = Object.getPrototypeOf(valueArg);
|
|
137
|
+
return prototype === Object.prototype || prototype === null;
|
|
138
|
+
}
|
|
139
|
+
function isLoopbackHostname(hostnameArg) {
|
|
140
|
+
const hostname = hostnameArg.toLowerCase().replace(/^\[(.*)\]$/, '$1');
|
|
141
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '::1') {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
const octets = hostname.split('.');
|
|
145
|
+
return octets.length === 4
|
|
146
|
+
&& octets.every((octetArg) => /^\d{1,3}$/.test(octetArg) && Number(octetArg) <= 255)
|
|
147
|
+
&& Number(octets[0]) === 127;
|
|
148
|
+
}
|
|
149
|
+
export class TypedSocket {
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// STATIC METHODS
|
|
152
|
+
// ============================================================================
|
|
153
|
+
/**
|
|
154
|
+
* Creates a TypedSocket client using native WebSocket.
|
|
155
|
+
* Works in both browser and Node.js environments.
|
|
156
|
+
*
|
|
157
|
+
* @param typedrouterArg - TypedRouter for handling server-initiated requests
|
|
158
|
+
* @param serverUrlArg - Server URL (e.g., 'http://localhost:3000' or 'wss://example.com')
|
|
159
|
+
* @param options - Connection options
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const typedRouter = new TypedRouter();
|
|
164
|
+
* const client = await TypedSocket.createClient(
|
|
165
|
+
* typedRouter,
|
|
166
|
+
* 'http://localhost:3000',
|
|
167
|
+
* { autoReconnect: true }
|
|
168
|
+
* );
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
static async createClient(typedrouterArg, serverUrlArg, options = {}) {
|
|
172
|
+
if (options.autoReconnect !== undefined && typeof options.autoReconnect !== 'boolean') {
|
|
173
|
+
throw new TypeError('TypedSocket autoReconnect must be a boolean.');
|
|
174
|
+
}
|
|
175
|
+
const maxRetries = normalizeReconnectInteger('maxRetries', options.maxRetries, TYPEDSOCKET_MAX_RECONNECT_RETRIES, TYPEDSOCKET_MAX_RECONNECT_RETRIES, true);
|
|
176
|
+
const initialBackoffMs = normalizeReconnectInteger('initialBackoffMs', options.initialBackoffMs, 1000, TYPEDSOCKET_MAX_RECONNECT_BACKOFF_MS, false);
|
|
177
|
+
const maxBackoffMs = normalizeReconnectInteger('maxBackoffMs', options.maxBackoffMs, TYPEDSOCKET_MAX_RECONNECT_BACKOFF_MS, TYPEDSOCKET_MAX_RECONNECT_BACKOFF_MS, false);
|
|
178
|
+
if (initialBackoffMs > maxBackoffMs) {
|
|
179
|
+
throw new RangeError('TypedSocket initialBackoffMs must not exceed maxBackoffMs.');
|
|
180
|
+
}
|
|
181
|
+
const opts = {
|
|
182
|
+
autoReconnect: options.autoReconnect ?? true,
|
|
183
|
+
maxRetries,
|
|
184
|
+
initialBackoffMs,
|
|
185
|
+
maxBackoffMs,
|
|
186
|
+
abortSignal: options.abortSignal,
|
|
187
|
+
nativeByteCapabilityMode: options.nativeByteCapabilityMode ?? 'optional',
|
|
188
|
+
limits: normalizeClientLimits(options.limits),
|
|
189
|
+
restoreConnection: options.restoreConnection,
|
|
190
|
+
};
|
|
191
|
+
const typedSocket = new TypedSocket('client', typedrouterArg, opts.nativeByteCapabilityMode);
|
|
192
|
+
typedSocket.clientOptions = opts;
|
|
193
|
+
typedSocket.serverUrl = serverUrlArg;
|
|
194
|
+
typedSocket.currentBackoff = opts.initialBackoffMs;
|
|
195
|
+
if (opts.abortSignal?.aborted) {
|
|
196
|
+
await typedSocket.stop();
|
|
197
|
+
throw typedSocket.createAbortError();
|
|
198
|
+
}
|
|
199
|
+
if (opts.abortSignal) {
|
|
200
|
+
typedSocket.abortSignalListener = () => {
|
|
201
|
+
void typedSocket.stop();
|
|
202
|
+
};
|
|
203
|
+
opts.abortSignal.addEventListener('abort', typedSocket.abortSignalListener, { once: true });
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
await typedSocket.connect();
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
await typedSocket.stop();
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
212
|
+
return typedSocket;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Returns the current window location origin URL.
|
|
216
|
+
* Useful in browser environments for connecting to the same origin.
|
|
217
|
+
*/
|
|
218
|
+
static { this.useWindowLocationOriginUrl = () => {
|
|
219
|
+
return plugins.smarturl.Smarturl.createFromUrl(globalThis.location.origin).toString();
|
|
220
|
+
}; }
|
|
221
|
+
/**
|
|
222
|
+
* Creates a server-side TypedSocket and composes its private protocol
|
|
223
|
+
* handlers into one or more isolated application routers.
|
|
224
|
+
*
|
|
225
|
+
* Call attachSmartServe() after constructing the SmartServe transport.
|
|
226
|
+
*/
|
|
227
|
+
static createServer(typedRouterOrRoutersArg, optionsArg = {}) {
|
|
228
|
+
const typedRouters = Array.isArray(typedRouterOrRoutersArg)
|
|
229
|
+
? [...typedRouterOrRoutersArg]
|
|
230
|
+
: [typedRouterOrRoutersArg];
|
|
231
|
+
if (typedRouters.length === 0) {
|
|
232
|
+
throw new Error('TypedSocket.createServer requires at least one TypedRouter.');
|
|
233
|
+
}
|
|
234
|
+
if (new Set(typedRouters).size !== typedRouters.length) {
|
|
235
|
+
throw new Error('TypedSocket.createServer received the same TypedRouter more than once.');
|
|
236
|
+
}
|
|
237
|
+
const typedSocket = new TypedSocket('server', typedRouters[0], optionsArg.nativeByteCapabilityMode ?? 'optional', optionsArg.nativeByteAuthorizationAdapter, optionsArg.clientTagPolicy);
|
|
238
|
+
const protocolRouter = new plugins.typedrequest.TypedRouter();
|
|
239
|
+
TypedSocket.registerProtocolHandlers(protocolRouter, typedSocket);
|
|
240
|
+
const protocolMethods = new Set(protocolRouter.getMethodNames());
|
|
241
|
+
for (const typedRouter of typedRouters) {
|
|
242
|
+
const collision = typedRouter
|
|
243
|
+
.getMethodNames()
|
|
244
|
+
.find((methodArg) => protocolMethods.has(methodArg));
|
|
245
|
+
if (collision) {
|
|
246
|
+
throw new Error(`TypedSocket protocol handler collides with application method "${collision}".`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const detachProtocolRouters = [];
|
|
250
|
+
const detachLegacyPolicies = [];
|
|
251
|
+
try {
|
|
252
|
+
for (const typedRouter of typedRouters) {
|
|
253
|
+
detachProtocolRouters.push(typedRouter.addFallbackRouter(protocolRouter));
|
|
254
|
+
detachLegacyPolicies.push(typedRouter.registerLegacyVirtualStreamPolicy('deny'));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
for (const detachLegacyPolicy of detachLegacyPolicies.reverse()) {
|
|
259
|
+
detachLegacyPolicy();
|
|
260
|
+
}
|
|
261
|
+
for (const detachProtocolRouter of detachProtocolRouters.reverse()) {
|
|
262
|
+
detachProtocolRouter();
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
typedSocket.serverTypedRouters = typedRouters;
|
|
267
|
+
typedSocket.detachProtocolRouters = detachProtocolRouters;
|
|
268
|
+
typedSocket.detachLegacyVirtualStreamPolicies = detachLegacyPolicies;
|
|
269
|
+
return typedSocket;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates and attaches a TypedSocket server to an existing SmartServe
|
|
273
|
+
* instance. Prefer createServer() plus attachSmartServe() when transport
|
|
274
|
+
* construction needs the composed routers first.
|
|
275
|
+
*
|
|
276
|
+
* @param smartServeArg - SmartServe instance configured with the application router
|
|
277
|
+
* @param typedRouterArg - Application TypedRouter used by SmartServe
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* const typedRouter = new TypedRouter();
|
|
282
|
+
* const smartServe = new SmartServe({
|
|
283
|
+
* port: 3000,
|
|
284
|
+
* websocket: {
|
|
285
|
+
* typedRouter
|
|
286
|
+
* }
|
|
287
|
+
* });
|
|
288
|
+
* await smartServe.start();
|
|
289
|
+
* const typedSocket = TypedSocket.fromSmartServe(smartServe, typedRouter);
|
|
290
|
+
* // Authenticated server handlers assign protected connection metadata with:
|
|
291
|
+
* // typedSocket.setServerTag(peer, 'authenticated', { userId });
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
static fromSmartServe(smartServeArg, typedRouterArg, optionsArg = {}) {
|
|
295
|
+
const typedSocket = TypedSocket.createServer(typedRouterArg, optionsArg);
|
|
296
|
+
try {
|
|
297
|
+
return typedSocket.attachSmartServe(smartServeArg);
|
|
298
|
+
}
|
|
299
|
+
catch (errorArg) {
|
|
300
|
+
typedSocket.releaseServerComposition();
|
|
301
|
+
throw errorArg;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Registers built-in TypedHandlers for tag management
|
|
306
|
+
*/
|
|
307
|
+
static registerProtocolHandlers(typedRouter, typedSocketArg) {
|
|
308
|
+
typedRouter.addTypedHandler(new plugins.typedrequest.TypedHandler('__typedsocket_setTag', async (data, meta) => {
|
|
309
|
+
const peer = typedSocketArg.findServerPeerFromRequest(meta);
|
|
310
|
+
const response = await typedSocketArg.tagPolicyManager.setClientTag(peer, data?.name, data?.payload);
|
|
311
|
+
typedSocketArg.emitTagDenialDiagnostic('set', data?.name, response);
|
|
312
|
+
return response;
|
|
313
|
+
}));
|
|
314
|
+
typedRouter.addTypedHandler(new plugins.typedrequest.TypedHandler('__typedsocket_removeTag', async (data, meta) => {
|
|
315
|
+
const peer = typedSocketArg.findServerPeerFromRequest(meta);
|
|
316
|
+
const response = await typedSocketArg.tagPolicyManager.removeClientTag(peer, data?.name);
|
|
317
|
+
typedSocketArg.emitTagDenialDiagnostic('remove', data?.name, response);
|
|
318
|
+
return response;
|
|
319
|
+
}));
|
|
320
|
+
typedRouter.addTypedHandler(new plugins.typedrequest.TypedHandler('__typedsocket_negotiateCapabilities', async (data, meta) => {
|
|
321
|
+
const peer = typedSocketArg.findServerPeerFromRequest(meta);
|
|
322
|
+
if (!peer || !Array.isArray(data.capabilities)) {
|
|
323
|
+
return {
|
|
324
|
+
protocolMajor: NATIVE_BYTE_PROTOCOL_MAJOR,
|
|
325
|
+
capabilities: [],
|
|
326
|
+
required: false,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
const response = typedSocketArg.nativeBytes.negotiateServerPeer(peer, {
|
|
330
|
+
protocolMajor: data.protocolMajor,
|
|
331
|
+
capabilities: data.capabilities,
|
|
332
|
+
});
|
|
333
|
+
return {
|
|
334
|
+
protocolMajor: response.protocolMajor,
|
|
335
|
+
capabilities: [...response.capabilities],
|
|
336
|
+
required: response.required,
|
|
337
|
+
};
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
340
|
+
// ============================================================================
|
|
341
|
+
// CONSTRUCTOR
|
|
342
|
+
// ============================================================================
|
|
343
|
+
constructor(sideArg, typedrouterArg, nativeByteCapabilityModeArg = 'optional', nativeByteAuthorizationAdapterArg, clientTagPolicyArg) {
|
|
344
|
+
// Connection status observable
|
|
345
|
+
this.statusSubject = new plugins.smartrx.rxjs.Subject();
|
|
346
|
+
/**
|
|
347
|
+
* Structured diagnostics: why a connection was closed, denied, or retried.
|
|
348
|
+
* Never completes, mirroring statusSubject; subscribers own unsubscription.
|
|
349
|
+
*/
|
|
350
|
+
this.diagnosticsSubject = new plugins.smartrx.rxjs.Subject();
|
|
351
|
+
this.connectionStatus = 'new';
|
|
352
|
+
this.reconnectExhaustedReported = false;
|
|
353
|
+
// Client-specific properties
|
|
354
|
+
this.websocket = null;
|
|
355
|
+
this.connectionGeneration = 0;
|
|
356
|
+
this.clientConnectionState = null;
|
|
357
|
+
this.retainedClientCallbacks = 0;
|
|
358
|
+
this.clientOptions = null;
|
|
359
|
+
this.serverUrl = '';
|
|
360
|
+
this.retryCount = 0;
|
|
361
|
+
this.currentBackoff = 1000;
|
|
362
|
+
this.stopped = false;
|
|
363
|
+
this.stopListeners = new Set();
|
|
364
|
+
this.pendingRequests = new Map();
|
|
365
|
+
this.clientTagReconciler = new ClientTagReconciler({
|
|
366
|
+
isCurrentConnection: (stateArg) => this.isCurrentClientConnection(stateArg),
|
|
367
|
+
getCurrentConnectionState: () => this.clientConnectionState,
|
|
368
|
+
getConnectionGeneration: (stateArg) => stateArg.generation,
|
|
369
|
+
getCurrentGeneration: () => this.connectionGeneration,
|
|
370
|
+
fireSetTag: async (nameArg, payloadArg) => await this
|
|
371
|
+
.createTypedRequest('__typedsocket_setTag')
|
|
372
|
+
.fire({ name: nameArg, payload: payloadArg }),
|
|
373
|
+
fireRemoveTag: async (nameArg) => await this
|
|
374
|
+
.createTypedRequest('__typedsocket_removeTag')
|
|
375
|
+
.fire({ name: nameArg }),
|
|
376
|
+
emitDiagnostic: (eventArg) => this.emitDiagnostic(eventArg),
|
|
377
|
+
});
|
|
378
|
+
// Server-specific properties (SmartServe mode)
|
|
379
|
+
this.smartServeRef = null;
|
|
380
|
+
this.serverTypedRouters = [];
|
|
381
|
+
this.detachProtocolRouters = [];
|
|
382
|
+
this.detachLegacyVirtualStreamPolicies = [];
|
|
383
|
+
this.serverStopping = false;
|
|
384
|
+
this.serverLifecycleGeneration = 0;
|
|
385
|
+
this.unsubscribeSmartServeConnectionClose = null;
|
|
386
|
+
this.detachNativeStreamResolvers = [];
|
|
387
|
+
this.detachResponseAuthorityGuards = [];
|
|
388
|
+
this.pendingServerRequests = new Map();
|
|
389
|
+
this.pendingServerRequestIdsByPeer = new Map();
|
|
390
|
+
this.retainedServerInterests = 0;
|
|
391
|
+
this.retainedServerInterestsByPeer = new Map();
|
|
392
|
+
this.side = sideArg;
|
|
393
|
+
this.typedrouter = typedrouterArg;
|
|
394
|
+
this.nativeBytes = new NativeByteManager({
|
|
395
|
+
side: sideArg,
|
|
396
|
+
capabilityMode: nativeByteCapabilityModeArg,
|
|
397
|
+
onDiagnostic: (eventArg) => this.emitDiagnostic(eventArg),
|
|
398
|
+
});
|
|
399
|
+
this.nativeByteAuthorizationAdapter = nativeByteAuthorizationAdapterArg;
|
|
400
|
+
this.tagPolicyManager = new TypedSocketTagPolicyManager(clientTagPolicyArg);
|
|
401
|
+
this.webSocketTransportOwner = this.nativeBytes.webSocketTransportOwner;
|
|
402
|
+
if (sideArg === 'client') {
|
|
403
|
+
let detachLegacyPolicy;
|
|
404
|
+
let detachNativeStreamResolver;
|
|
405
|
+
try {
|
|
406
|
+
detachLegacyPolicy = typedrouterArg.registerLegacyVirtualStreamPolicy('deny');
|
|
407
|
+
detachNativeStreamResolver = typedrouterArg.registerNativeStreamTransportResolver(() => this.nativeBytes.getClientNativeStreamTransport());
|
|
408
|
+
this.detachLegacyVirtualStreamPolicies.push(detachLegacyPolicy);
|
|
409
|
+
this.detachNativeStreamResolvers.push(detachNativeStreamResolver);
|
|
410
|
+
}
|
|
411
|
+
catch (errorArg) {
|
|
412
|
+
detachNativeStreamResolver?.();
|
|
413
|
+
detachLegacyPolicy?.();
|
|
414
|
+
throw errorArg;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Attaches the SmartServe transport after server router composition.
|
|
420
|
+
*/
|
|
421
|
+
attachSmartServe(smartServeArg) {
|
|
422
|
+
if (this.side !== 'server') {
|
|
423
|
+
throw new Error('attachSmartServe is only available on servers.');
|
|
424
|
+
}
|
|
425
|
+
if (this.serverStopping) {
|
|
426
|
+
throw new Error('Cannot attach SmartServe after the TypedSocket server has stopped.');
|
|
427
|
+
}
|
|
428
|
+
if (this.smartServeRef) {
|
|
429
|
+
if (this.smartServeRef === smartServeArg) {
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
throw new Error('TypedSocket server is already attached to a SmartServe instance.');
|
|
433
|
+
}
|
|
434
|
+
const detachResolvers = [];
|
|
435
|
+
const detachResponseGuards = [];
|
|
436
|
+
let unsubscribe;
|
|
437
|
+
let nativeServerAttached = false;
|
|
438
|
+
try {
|
|
439
|
+
for (const typedRouter of this.serverTypedRouters) {
|
|
440
|
+
detachResponseGuards.push(typedRouter.registerIncomingResponseAuthorityGuard((responseArg, contextArg) => {
|
|
441
|
+
return this.authorizeIncomingServerResponse(responseArg, contextArg.typedRouter, contextArg.trustedLocalData);
|
|
442
|
+
}));
|
|
443
|
+
detachResolvers.push(typedRouter.registerNativeStreamTransportResolver((trustedLocalDataArg) => {
|
|
444
|
+
const peer = this.smartServeRef
|
|
445
|
+
?.getWebSocketConnections()
|
|
446
|
+
.find((peerArg) => peerArg === trustedLocalDataArg.peer);
|
|
447
|
+
return peer ? this.nativeBytes.getServerNativeStreamTransport(peer) : undefined;
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
450
|
+
unsubscribe = smartServeArg.subscribeWebSocketConnectionClose((peerArg) => {
|
|
451
|
+
this.tagPolicyManager.handlePeerClose(peerArg);
|
|
452
|
+
this.cancelPendingServerRequestsForPeer(peerArg, new Error(`TypedSocket target connection closed: ${peerArg.id}`));
|
|
453
|
+
});
|
|
454
|
+
this.nativeBytes.attachServer();
|
|
455
|
+
nativeServerAttached = true;
|
|
456
|
+
this.serverLifecycleGeneration++;
|
|
457
|
+
this.tagPolicyManager.setAttachedPeersProvider(() => smartServeArg.getWebSocketConnections().filter((peerArg) => {
|
|
458
|
+
const routingSurface = peerArg.routingSurface;
|
|
459
|
+
return Boolean(routingSurface && this.serverTypedRouters.includes(routingSurface));
|
|
460
|
+
}));
|
|
461
|
+
this.smartServeRef = smartServeArg;
|
|
462
|
+
this.detachNativeStreamResolvers = detachResolvers;
|
|
463
|
+
this.detachResponseAuthorityGuards = detachResponseGuards;
|
|
464
|
+
this.unsubscribeSmartServeConnectionClose = unsubscribe;
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
catch (errorArg) {
|
|
468
|
+
unsubscribe?.();
|
|
469
|
+
if (nativeServerAttached) {
|
|
470
|
+
this.nativeBytes.detachServer();
|
|
471
|
+
}
|
|
472
|
+
this.tagPolicyManager.setAttachedPeersProvider(undefined);
|
|
473
|
+
for (const detachResponseGuard of detachResponseGuards.reverse())
|
|
474
|
+
detachResponseGuard();
|
|
475
|
+
for (const detachResolver of detachResolvers.reverse())
|
|
476
|
+
detachResolver();
|
|
477
|
+
throw errorArg;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// CLIENT METHODS
|
|
482
|
+
// ============================================================================
|
|
483
|
+
/**
|
|
484
|
+
* Connects the client to the server using native WebSocket
|
|
485
|
+
*/
|
|
486
|
+
async connect() {
|
|
487
|
+
if (this.stopped || this.clientOptions?.abortSignal?.aborted) {
|
|
488
|
+
throw this.createAbortError();
|
|
489
|
+
}
|
|
490
|
+
const done = plugins.smartpromise.defer();
|
|
491
|
+
let connectionSettled = false;
|
|
492
|
+
let connectionTimeout;
|
|
493
|
+
let abortConnection;
|
|
494
|
+
const settleConnection = (errorArg) => {
|
|
495
|
+
if (connectionSettled) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
connectionSettled = true;
|
|
499
|
+
if (connectionTimeout) {
|
|
500
|
+
clearTimeout(connectionTimeout);
|
|
501
|
+
}
|
|
502
|
+
if (abortConnection) {
|
|
503
|
+
this.stopListeners.delete(abortConnection);
|
|
504
|
+
this.clientOptions?.abortSignal?.removeEventListener('abort', abortConnection);
|
|
505
|
+
}
|
|
506
|
+
if (errorArg) {
|
|
507
|
+
done.reject(errorArg);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
done.resolve();
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
this.updateStatus('connecting');
|
|
514
|
+
const wsUrl = this.toWebSocketUrl(this.serverUrl);
|
|
515
|
+
const logEndpoint = this.toRedactedLogEndpoint(wsUrl);
|
|
516
|
+
this.logDebug(`TypedSocket connecting to ${logEndpoint}...`);
|
|
517
|
+
if (this.clientConnectionState) {
|
|
518
|
+
this.abortClientConnectionState(this.clientConnectionState, new Error('TypedSocket connection replaced'));
|
|
519
|
+
}
|
|
520
|
+
const generation = ++this.connectionGeneration;
|
|
521
|
+
const websocket = new WebSocket(wsUrl);
|
|
522
|
+
websocket.binaryType = 'arraybuffer';
|
|
523
|
+
this.websocket = websocket;
|
|
524
|
+
const connectionState = {
|
|
525
|
+
generation,
|
|
526
|
+
connectionId: plugins.smartstring.create.createCryptoRandomString(),
|
|
527
|
+
websocket,
|
|
528
|
+
abortController: new AbortController(),
|
|
529
|
+
queuedTextFrames: [],
|
|
530
|
+
queuedTextBytes: 0,
|
|
531
|
+
activeHandlers: 0,
|
|
532
|
+
};
|
|
533
|
+
this.clientConnectionState = connectionState;
|
|
534
|
+
this.nativeBytes.bindClientConnection(websocket, generation);
|
|
535
|
+
abortConnection = () => {
|
|
536
|
+
if (this.websocket === websocket && this.connectionGeneration === generation) {
|
|
537
|
+
websocket.close();
|
|
538
|
+
}
|
|
539
|
+
settleConnection(this.createAbortError());
|
|
540
|
+
};
|
|
541
|
+
this.stopListeners.add(abortConnection);
|
|
542
|
+
this.clientOptions?.abortSignal?.addEventListener('abort', abortConnection, { once: true });
|
|
543
|
+
connectionTimeout = setTimeout(() => {
|
|
544
|
+
if (this.connectionStatus !== 'connected') {
|
|
545
|
+
this.logTransient(`TypedSocket connection timeout for ${logEndpoint}`);
|
|
546
|
+
if (this.websocket === websocket && this.connectionGeneration === generation) {
|
|
547
|
+
websocket.close();
|
|
548
|
+
}
|
|
549
|
+
settleConnection(new Error('Connection timeout'));
|
|
550
|
+
}
|
|
551
|
+
}, 10000);
|
|
552
|
+
websocket.onopen = async () => {
|
|
553
|
+
try {
|
|
554
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
555
|
+
return;
|
|
556
|
+
await this.negotiateClientCapabilities(websocket, generation);
|
|
557
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
558
|
+
return;
|
|
559
|
+
await this.restoreClientConnection(connectionState);
|
|
560
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
561
|
+
return;
|
|
562
|
+
await this.reapplyClientTags(connectionState);
|
|
563
|
+
if (this.websocket !== websocket
|
|
564
|
+
|| this.connectionGeneration !== generation
|
|
565
|
+
|| websocket.readyState !== WebSocket.OPEN) {
|
|
566
|
+
throw new Error('TypedSocket connection changed while restoring client tags');
|
|
567
|
+
}
|
|
568
|
+
this.logDebug('TypedSocket connected and client registration restored!');
|
|
569
|
+
this.retryCount = 0;
|
|
570
|
+
this.reconnectExhaustedReported = false;
|
|
571
|
+
this.currentBackoff = this.clientOptions?.initialBackoffMs ?? 1000;
|
|
572
|
+
this.updateStatus('connected');
|
|
573
|
+
settleConnection();
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
const restorationError = error instanceof Error
|
|
577
|
+
? error
|
|
578
|
+
: new Error(String(error));
|
|
579
|
+
this.logTransient('TypedSocket client registration restoration failed.');
|
|
580
|
+
settleConnection(restorationError);
|
|
581
|
+
websocket.close();
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
websocket.onmessage = (event) => {
|
|
585
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
586
|
+
return;
|
|
587
|
+
if (typeof event.data === 'string') {
|
|
588
|
+
this.enqueueClientTextFrame(connectionState, event.data);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (event.data instanceof ArrayBuffer || ArrayBuffer.isView(event.data)) {
|
|
592
|
+
this.nativeBytes.enqueueClientRawFrame(websocket, generation, event.data);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
this.emitConnectionClosedDiagnostic(1003, 'Unsupported WebSocket payload');
|
|
596
|
+
websocket.close(toClientWebSocketCloseCode(1003), 'Unsupported WebSocket payload');
|
|
597
|
+
};
|
|
598
|
+
websocket.onclose = () => {
|
|
599
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
600
|
+
return;
|
|
601
|
+
this.abortClientConnectionState(connectionState, new Error('TypedSocket disconnected'));
|
|
602
|
+
this.nativeBytes.unbindClientConnection(websocket, generation, new Error('TypedSocket disconnected'));
|
|
603
|
+
if (this.connectionStatus === 'connected') {
|
|
604
|
+
this.handleDisconnect();
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
this.rejectPendingClientRequests(new Error('TypedSocket disconnected'));
|
|
608
|
+
settleConnection(this.stopped || this.clientOptions?.abortSignal?.aborted
|
|
609
|
+
? this.createAbortError()
|
|
610
|
+
: new Error('TypedSocket connection closed before it opened'));
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
websocket.onerror = () => {
|
|
614
|
+
if (this.websocket !== websocket || this.connectionGeneration !== generation)
|
|
615
|
+
return;
|
|
616
|
+
// transient socket errors (network blips, proxy TLS hiccups) are expected
|
|
617
|
+
// and handled by reconnection; only exhausted retries surface as errors
|
|
618
|
+
this.logTransient(`TypedSocket websocket error on ${logEndpoint}`);
|
|
619
|
+
};
|
|
620
|
+
try {
|
|
621
|
+
await done.promise;
|
|
622
|
+
}
|
|
623
|
+
catch (err) {
|
|
624
|
+
if (connectionTimeout) {
|
|
625
|
+
clearTimeout(connectionTimeout);
|
|
626
|
+
}
|
|
627
|
+
if (!(err instanceof NativeByteCapabilityError)
|
|
628
|
+
&& !(err instanceof TypedSocketClientTagDeniedError)
|
|
629
|
+
&& !(err instanceof TypedSocketConnectionRestoreDeniedError)
|
|
630
|
+
&& this.shouldReconnect()) {
|
|
631
|
+
await this.scheduleReconnect();
|
|
632
|
+
if (this.connectionStatus !== 'connected') {
|
|
633
|
+
throw this.stopped || this.clientOptions?.abortSignal?.aborted ? this.createAbortError() : err;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
throw err;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async negotiateClientCapabilities(websocketArg, generationArg) {
|
|
642
|
+
const advertisement = this.nativeBytes.getClientNegotiationAdvertisement();
|
|
643
|
+
let negotiatedResponse;
|
|
644
|
+
try {
|
|
645
|
+
const request = {
|
|
646
|
+
method: '__typedsocket_negotiateCapabilities',
|
|
647
|
+
request: {
|
|
648
|
+
protocolMajor: advertisement.protocolMajor,
|
|
649
|
+
capabilities: [...advertisement.capabilities],
|
|
650
|
+
},
|
|
651
|
+
response: {
|
|
652
|
+
protocolMajor: 0,
|
|
653
|
+
capabilities: [],
|
|
654
|
+
required: false,
|
|
655
|
+
},
|
|
656
|
+
};
|
|
657
|
+
const response = await this.sendRequest(request, {
|
|
658
|
+
timeoutMs: Math.min(10_000, this.requireClientOptions().limits.requestTimeoutMs),
|
|
659
|
+
});
|
|
660
|
+
if (response.error)
|
|
661
|
+
throw new NativeByteCapabilityError(response.error.text);
|
|
662
|
+
if (!response.response
|
|
663
|
+
|| !Number.isSafeInteger(response.response.protocolMajor)
|
|
664
|
+
|| !Array.isArray(response.response.capabilities)
|
|
665
|
+
|| response.response.capabilities.some((capabilityArg) => typeof capabilityArg !== 'string')
|
|
666
|
+
|| typeof response.response.required !== 'boolean') {
|
|
667
|
+
throw new NativeByteCapabilityError('TypedSocket server returned malformed capabilities');
|
|
668
|
+
}
|
|
669
|
+
negotiatedResponse = response.response;
|
|
670
|
+
}
|
|
671
|
+
catch (errorArg) {
|
|
672
|
+
if (this.clientOptions?.nativeByteCapabilityMode === 'required') {
|
|
673
|
+
throw errorArg instanceof NativeByteCapabilityError
|
|
674
|
+
? errorArg
|
|
675
|
+
: new NativeByteCapabilityError('Required native-byte-v1 negotiation failed', {
|
|
676
|
+
cause: errorArg,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
this.nativeBytes.failOptionalClientNegotiation(websocketArg, generationArg);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (negotiatedResponse.required
|
|
683
|
+
&& (negotiatedResponse.protocolMajor !== NATIVE_BYTE_PROTOCOL_MAJOR
|
|
684
|
+
|| negotiatedResponse.capabilities.length !== 1
|
|
685
|
+
|| negotiatedResponse.capabilities[0] !== NATIVE_BYTE_PROTOCOL
|
|
686
|
+
|| this.clientOptions?.nativeByteCapabilityMode === 'disabled')) {
|
|
687
|
+
throw new NativeByteCapabilityError('TypedSocket server requires native-byte-v1');
|
|
688
|
+
}
|
|
689
|
+
this.nativeBytes.completeClientNegotiation(websocketArg, generationArg, negotiatedResponse);
|
|
690
|
+
}
|
|
691
|
+
async restoreClientConnection(stateArg) {
|
|
692
|
+
const restoreConnection = this.clientOptions?.restoreConnection;
|
|
693
|
+
if (!restoreConnection)
|
|
694
|
+
return;
|
|
695
|
+
const limits = this.requireClientOptions().limits;
|
|
696
|
+
if (this.retainedClientCallbacks >= limits.retainedCallbacks) {
|
|
697
|
+
throw new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration callback capacity exhausted.');
|
|
698
|
+
}
|
|
699
|
+
const timeoutMs = limits.connectionRestoreTimeoutMs;
|
|
700
|
+
const deadline = Date.now() + timeoutMs;
|
|
701
|
+
const restorationAbortController = new AbortController();
|
|
702
|
+
const interruptRestoration = () => {
|
|
703
|
+
restorationAbortController.abort(new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration was interrupted.'));
|
|
704
|
+
};
|
|
705
|
+
stateArg.abortController.signal.addEventListener('abort', interruptRestoration, { once: true });
|
|
706
|
+
if (stateArg.abortController.signal.aborted)
|
|
707
|
+
interruptRestoration();
|
|
708
|
+
const createTypedRequest = (methodNameArg) => {
|
|
709
|
+
if (restorationAbortController.signal.aborted) {
|
|
710
|
+
throw new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration request was interrupted.');
|
|
711
|
+
}
|
|
712
|
+
const remainingMs = deadline - Date.now();
|
|
713
|
+
if (!Number.isSafeInteger(remainingMs) || remainingMs < 1) {
|
|
714
|
+
throw new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration request exceeded its deadline.');
|
|
715
|
+
}
|
|
716
|
+
return this.createTypedRequest(methodNameArg, undefined, {
|
|
717
|
+
timeoutMs: Math.min(remainingMs + 1, limits.requestTimeoutMs),
|
|
718
|
+
abortSignal: restorationAbortController.signal,
|
|
719
|
+
});
|
|
720
|
+
};
|
|
721
|
+
this.retainedClientCallbacks++;
|
|
722
|
+
const callbackOutcome = Promise.resolve().then(() => restoreConnection(Object.freeze({
|
|
723
|
+
connectionId: stateArg.connectionId,
|
|
724
|
+
abortSignal: restorationAbortController.signal,
|
|
725
|
+
deadline,
|
|
726
|
+
createTypedRequest,
|
|
727
|
+
})));
|
|
728
|
+
void callbackOutcome.then(() => { this.retainedClientCallbacks--; }, () => { this.retainedClientCallbacks--; });
|
|
729
|
+
let timeout;
|
|
730
|
+
let abortListener;
|
|
731
|
+
const interrupted = new Promise((_resolveArg, rejectArg) => {
|
|
732
|
+
timeout = setTimeout(() => {
|
|
733
|
+
const error = new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration timed out.');
|
|
734
|
+
restorationAbortController.abort(error);
|
|
735
|
+
rejectArg(error);
|
|
736
|
+
}, timeoutMs);
|
|
737
|
+
abortListener = () => {
|
|
738
|
+
const reason = restorationAbortController.signal.reason;
|
|
739
|
+
rejectArg(reason instanceof TypedSocketConnectionRestoreDeniedError
|
|
740
|
+
? reason
|
|
741
|
+
: new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration was interrupted.'));
|
|
742
|
+
};
|
|
743
|
+
restorationAbortController.signal.addEventListener('abort', abortListener, { once: true });
|
|
744
|
+
if (restorationAbortController.signal.aborted)
|
|
745
|
+
abortListener();
|
|
746
|
+
});
|
|
747
|
+
try {
|
|
748
|
+
await Promise.race([callbackOutcome, interrupted]);
|
|
749
|
+
}
|
|
750
|
+
catch (errorArg) {
|
|
751
|
+
if (errorArg instanceof TypedSocketConnectionRestoreDeniedError)
|
|
752
|
+
throw errorArg;
|
|
753
|
+
throw new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration was denied.');
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
if (timeout)
|
|
757
|
+
clearTimeout(timeout);
|
|
758
|
+
if (abortListener) {
|
|
759
|
+
restorationAbortController.signal.removeEventListener('abort', abortListener);
|
|
760
|
+
}
|
|
761
|
+
stateArg.abortController.signal.removeEventListener('abort', interruptRestoration);
|
|
762
|
+
restorationAbortController.abort(new TypedSocketConnectionRestoreDeniedError('TypedSocket connection restoration callback completed.'));
|
|
763
|
+
}
|
|
764
|
+
if (!this.isCurrentClientConnection(stateArg)) {
|
|
765
|
+
throw new TypedSocketConnectionRestoreDeniedError('TypedSocket connection changed during restoration.');
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Converts an HTTP(S) URL to a WebSocket URL
|
|
770
|
+
*/
|
|
771
|
+
toWebSocketUrl(url) {
|
|
772
|
+
const parsed = new URL(url);
|
|
773
|
+
if (parsed.username || parsed.password) {
|
|
774
|
+
throw new Error('TypedSocket server URL must not contain userinfo.');
|
|
775
|
+
}
|
|
776
|
+
if (parsed.hash) {
|
|
777
|
+
throw new Error('TypedSocket server URL must not contain a fragment.');
|
|
778
|
+
}
|
|
779
|
+
const wsProtocol = (() => {
|
|
780
|
+
switch (parsed.protocol) {
|
|
781
|
+
case 'http:':
|
|
782
|
+
case 'ws:':
|
|
783
|
+
return 'ws:';
|
|
784
|
+
case 'https:':
|
|
785
|
+
case 'wss:':
|
|
786
|
+
return 'wss:';
|
|
787
|
+
default:
|
|
788
|
+
throw new Error('TypedSocket server URL must use http, https, ws, or wss.');
|
|
789
|
+
}
|
|
790
|
+
})();
|
|
791
|
+
if (wsProtocol === 'ws:' && !isLoopbackHostname(parsed.hostname)) {
|
|
792
|
+
throw new Error('TypedSocket insecure WebSocket transport is restricted to loopback hosts.');
|
|
793
|
+
}
|
|
794
|
+
parsed.protocol = wsProtocol;
|
|
795
|
+
return parsed.toString();
|
|
796
|
+
}
|
|
797
|
+
toRedactedLogEndpoint(urlArg) {
|
|
798
|
+
const parsed = new URL(urlArg);
|
|
799
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
800
|
+
}
|
|
801
|
+
enqueueClientTextFrame(stateArg, textArg) {
|
|
802
|
+
if (!this.isCurrentClientConnection(stateArg))
|
|
803
|
+
return;
|
|
804
|
+
const limits = this.requireClientOptions().limits;
|
|
805
|
+
const byteLength = textEncoder.encode(textArg).byteLength;
|
|
806
|
+
if (byteLength > limits.textFrameBytes) {
|
|
807
|
+
this.emitConnectionClosedDiagnostic(1009, 'TypedSocket text frame exceeds package limit');
|
|
808
|
+
stateArg.websocket.close(toClientWebSocketCloseCode(1009), 'TypedSocket text frame exceeds package limit');
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (stateArg.queuedTextFrames.length >= limits.queuedTextFrames
|
|
812
|
+
|| stateArg.queuedTextBytes + byteLength > limits.queuedTextBytes) {
|
|
813
|
+
this.emitConnectionClosedDiagnostic(1008, 'TypedSocket text queue exceeds package limit');
|
|
814
|
+
stateArg.websocket.close(toClientWebSocketCloseCode(1008), 'TypedSocket text queue exceeds package limit');
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
stateArg.queuedTextFrames.push({ text: textArg, byteLength });
|
|
818
|
+
stateArg.queuedTextBytes += byteLength;
|
|
819
|
+
this.drainClientTextFrames(stateArg);
|
|
820
|
+
}
|
|
821
|
+
drainClientTextFrames(stateArg) {
|
|
822
|
+
const limits = this.requireClientOptions().limits;
|
|
823
|
+
while (this.isCurrentClientConnection(stateArg)
|
|
824
|
+
&& !stateArg.abortController.signal.aborted
|
|
825
|
+
&& stateArg.queuedTextFrames.length > 0
|
|
826
|
+
&& stateArg.activeHandlers < limits.concurrentHandlers) {
|
|
827
|
+
if (this.retainedClientCallbacks >= limits.retainedCallbacks) {
|
|
828
|
+
this.emitConnectionClosedDiagnostic(1013, 'TypedSocket callback capacity exhausted');
|
|
829
|
+
stateArg.websocket.close(toClientWebSocketCloseCode(1013), 'TypedSocket callback capacity exhausted');
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const frame = stateArg.queuedTextFrames.shift();
|
|
833
|
+
if (!frame)
|
|
834
|
+
return;
|
|
835
|
+
stateArg.queuedTextBytes -= frame.byteLength;
|
|
836
|
+
stateArg.activeHandlers++;
|
|
837
|
+
this.retainedClientCallbacks++;
|
|
838
|
+
void this.handleMessage(frame.text, stateArg)
|
|
839
|
+
.catch(() => {
|
|
840
|
+
if (this.isCurrentClientConnection(stateArg)) {
|
|
841
|
+
this.emitConnectionClosedDiagnostic(1008, 'TypedSocket rejected malformed text envelope');
|
|
842
|
+
stateArg.websocket.close(toClientWebSocketCloseCode(1008), 'TypedSocket rejected malformed text envelope');
|
|
843
|
+
}
|
|
844
|
+
})
|
|
845
|
+
.finally(() => {
|
|
846
|
+
stateArg.activeHandlers--;
|
|
847
|
+
this.retainedClientCallbacks--;
|
|
848
|
+
this.drainClientTextFrames(stateArg);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
assertClientEnvelope(valueArg) {
|
|
853
|
+
if (!isPlainRecord(valueArg) || Object.hasOwn(valueArg, 'localData')) {
|
|
854
|
+
throw new Error('TypedSocket received an invalid text envelope.');
|
|
855
|
+
}
|
|
856
|
+
assertBoundedTextIdentifier(valueArg.method, 'method', TYPEDSOCKET_MAX_METHOD_NAME_BYTES);
|
|
857
|
+
const correlation = valueArg.correlation;
|
|
858
|
+
if (!isPlainRecord(correlation)) {
|
|
859
|
+
throw new Error('TypedSocket received an invalid correlation envelope.');
|
|
860
|
+
}
|
|
861
|
+
assertBoundedTextIdentifier(correlation.id, 'correlation ID', TYPEDSOCKET_MAX_CORRELATION_ID_BYTES);
|
|
862
|
+
if (correlation.phase !== 'request' && correlation.phase !== 'response') {
|
|
863
|
+
throw new Error('TypedSocket received an invalid correlation phase.');
|
|
864
|
+
}
|
|
865
|
+
if (correlation.phase === 'request' && !Object.hasOwn(valueArg, 'request')) {
|
|
866
|
+
throw new Error('TypedSocket request envelope has no request payload.');
|
|
867
|
+
}
|
|
868
|
+
if (correlation.phase === 'response'
|
|
869
|
+
&& !Object.hasOwn(valueArg, 'response')
|
|
870
|
+
&& !Object.hasOwn(valueArg, 'error')) {
|
|
871
|
+
throw new Error('TypedSocket response envelope has no response or error payload.');
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async handleMessage(dataArg, stateArg = this.clientConnectionState) {
|
|
875
|
+
if (!stateArg || !this.isCurrentClientConnection(stateArg))
|
|
876
|
+
return;
|
|
877
|
+
const message = plugins.smartjson.parse(dataArg);
|
|
878
|
+
this.assertClientEnvelope(message);
|
|
879
|
+
const correlation = message.correlation;
|
|
880
|
+
if (correlation.phase === 'response') {
|
|
881
|
+
const pending = this.pendingRequests.get(correlation.id);
|
|
882
|
+
if (!pending)
|
|
883
|
+
return;
|
|
884
|
+
if (pending.connectionGeneration !== stateArg.generation
|
|
885
|
+
|| pending.expectedMethod !== message.method) {
|
|
886
|
+
throw new Error('TypedSocket response authority mismatch.');
|
|
887
|
+
}
|
|
888
|
+
this.pendingRequests.delete(correlation.id);
|
|
889
|
+
pending.resolve(message);
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const response = await this.typedrouter.routeAndAddResponse(message);
|
|
893
|
+
if (!response || !this.isCurrentClientConnection(stateArg))
|
|
894
|
+
return;
|
|
895
|
+
this.sendClientTextEnvelope(response, stateArg);
|
|
896
|
+
}
|
|
897
|
+
sendClientTextEnvelope(envelopeArg, stateArg) {
|
|
898
|
+
if (!this.isCurrentClientConnection(stateArg)) {
|
|
899
|
+
throw new Error('TypedSocket connection changed before text send.');
|
|
900
|
+
}
|
|
901
|
+
const encoded = plugins.smartjson.stringify(envelopeArg);
|
|
902
|
+
const byteLength = textEncoder.encode(encoded).byteLength;
|
|
903
|
+
const limits = this.requireClientOptions().limits;
|
|
904
|
+
if (byteLength > limits.textFrameBytes) {
|
|
905
|
+
throw new Error(`TypedSocket outbound envelope exceeds ${limits.textFrameBytes} UTF-8 bytes.`);
|
|
906
|
+
}
|
|
907
|
+
if (stateArg.websocket.bufferedAmount + byteLength > limits.outboundBufferedBytes) {
|
|
908
|
+
throw new Error('TypedSocket outbound WebSocket buffer capacity exceeded.');
|
|
909
|
+
}
|
|
910
|
+
stateArg.websocket.send(encoded);
|
|
911
|
+
}
|
|
912
|
+
isCurrentClientConnection(stateArg) {
|
|
913
|
+
return Boolean(this.clientConnectionState === stateArg
|
|
914
|
+
&& this.websocket === stateArg.websocket
|
|
915
|
+
&& this.connectionGeneration === stateArg.generation
|
|
916
|
+
&& stateArg.websocket.readyState === WebSocket.OPEN);
|
|
917
|
+
}
|
|
918
|
+
abortClientConnectionState(stateArg, reasonArg) {
|
|
919
|
+
if (!stateArg.abortController.signal.aborted) {
|
|
920
|
+
stateArg.abortController.abort(reasonArg);
|
|
921
|
+
}
|
|
922
|
+
stateArg.queuedTextFrames.length = 0;
|
|
923
|
+
stateArg.queuedTextBytes = 0;
|
|
924
|
+
if (this.clientConnectionState === stateArg) {
|
|
925
|
+
this.clientConnectionState = null;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Handles WebSocket disconnection
|
|
930
|
+
*/
|
|
931
|
+
handleDisconnect() {
|
|
932
|
+
if (this.connectionStatus === 'disconnected') {
|
|
933
|
+
return; // Already handled
|
|
934
|
+
}
|
|
935
|
+
this.updateStatus('disconnected');
|
|
936
|
+
// Reject all pending requests — the connection is gone and they'll never receive a response
|
|
937
|
+
this.rejectPendingClientRequests(new Error('TypedSocket disconnected'));
|
|
938
|
+
if (this.shouldReconnect()) {
|
|
939
|
+
this.scheduleReconnect();
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Schedules a reconnection attempt with exponential backoff
|
|
944
|
+
*/
|
|
945
|
+
async scheduleReconnect() {
|
|
946
|
+
if (!this.shouldReconnect())
|
|
947
|
+
return;
|
|
948
|
+
const clientOptions = this.requireClientOptions();
|
|
949
|
+
this.updateStatus('reconnecting');
|
|
950
|
+
this.retryCount++;
|
|
951
|
+
// Exponential backoff with jitter
|
|
952
|
+
const jitter = this.currentBackoff * 0.2 * (Math.random() * 2 - 1);
|
|
953
|
+
const delay = Math.min(this.currentBackoff + jitter, clientOptions.maxBackoffMs);
|
|
954
|
+
this.logDebug(`TypedSocket reconnecting in ${Math.round(delay)}ms (attempt ${this.retryCount}/${clientOptions.maxRetries})`);
|
|
955
|
+
this.emitDiagnostic({
|
|
956
|
+
kind: 'reconnectScheduled',
|
|
957
|
+
side: 'client',
|
|
958
|
+
attempt: this.retryCount,
|
|
959
|
+
maxRetries: clientOptions.maxRetries,
|
|
960
|
+
delayMs: Math.round(delay),
|
|
961
|
+
endpoint: this.toRedactedLogEndpoint(this.toWebSocketUrl(this.serverUrl)),
|
|
962
|
+
});
|
|
963
|
+
await this.waitForReconnectDelay(delay);
|
|
964
|
+
if (!this.shouldReconnect())
|
|
965
|
+
return;
|
|
966
|
+
// Increase backoff for next time
|
|
967
|
+
this.currentBackoff = Math.min(this.currentBackoff * 2, clientOptions.maxBackoffMs);
|
|
968
|
+
try {
|
|
969
|
+
await this.connect();
|
|
970
|
+
}
|
|
971
|
+
catch {
|
|
972
|
+
if (this.shouldReconnect()) {
|
|
973
|
+
this.logDebug('TypedSocket reconnection attempt failed.');
|
|
974
|
+
}
|
|
975
|
+
else if (!this.stopped && !this.clientOptions?.abortSignal?.aborted) {
|
|
976
|
+
const endpoint = this.toRedactedLogEndpoint(this.toWebSocketUrl(this.serverUrl));
|
|
977
|
+
console.error(`TypedSocket giving up on ${endpoint} after ${this.retryCount} attempts.`);
|
|
978
|
+
if (!this.reconnectExhaustedReported) {
|
|
979
|
+
this.reconnectExhaustedReported = true;
|
|
980
|
+
this.emitDiagnostic({
|
|
981
|
+
kind: 'reconnectExhausted',
|
|
982
|
+
side: 'client',
|
|
983
|
+
attempts: this.retryCount,
|
|
984
|
+
endpoint,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Lifecycle chatter: hidden by default in browser devtools (verbose level)
|
|
992
|
+
* and irrelevant for servers, but available when debugging.
|
|
993
|
+
*/
|
|
994
|
+
logDebug(messageArg) {
|
|
995
|
+
console.debug(messageArg);
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Failures that reconnection is expected to recover from. Logged as debug
|
|
999
|
+
* while retries remain; the final failure is reported by scheduleReconnect.
|
|
1000
|
+
*/
|
|
1001
|
+
logTransient(messageArg) {
|
|
1002
|
+
if (this.shouldReconnect()) {
|
|
1003
|
+
this.logDebug(messageArg);
|
|
1004
|
+
}
|
|
1005
|
+
else if (!this.stopped && !this.clientOptions?.abortSignal?.aborted) {
|
|
1006
|
+
console.warn(messageArg);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
shouldReconnect() {
|
|
1010
|
+
return Boolean(this.clientOptions?.autoReconnect &&
|
|
1011
|
+
!this.stopped &&
|
|
1012
|
+
!this.clientOptions.abortSignal?.aborted &&
|
|
1013
|
+
this.retryCount < this.clientOptions.maxRetries);
|
|
1014
|
+
}
|
|
1015
|
+
async waitForReconnectDelay(delayMsArg) {
|
|
1016
|
+
await new Promise((resolve) => {
|
|
1017
|
+
let timeout;
|
|
1018
|
+
let finished = false;
|
|
1019
|
+
const finish = () => {
|
|
1020
|
+
if (finished)
|
|
1021
|
+
return;
|
|
1022
|
+
finished = true;
|
|
1023
|
+
if (timeout) {
|
|
1024
|
+
clearTimeout(timeout);
|
|
1025
|
+
}
|
|
1026
|
+
this.stopListeners.delete(finish);
|
|
1027
|
+
this.clientOptions?.abortSignal?.removeEventListener('abort', finish);
|
|
1028
|
+
resolve();
|
|
1029
|
+
};
|
|
1030
|
+
if (this.clientOptions?.abortSignal?.aborted) {
|
|
1031
|
+
finish();
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (this.stopped) {
|
|
1035
|
+
finish();
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
timeout = setTimeout(finish, delayMsArg);
|
|
1039
|
+
this.stopListeners.add(finish);
|
|
1040
|
+
this.clientOptions?.abortSignal?.addEventListener('abort', finish, { once: true });
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
createAbortError() {
|
|
1044
|
+
return new Error('TypedSocket client startup aborted');
|
|
1045
|
+
}
|
|
1046
|
+
rejectPendingClientRequests(errorArg) {
|
|
1047
|
+
for (const pending of this.pendingRequests.values()) {
|
|
1048
|
+
pending.reject(errorArg);
|
|
1049
|
+
}
|
|
1050
|
+
this.pendingRequests.clear();
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Updates connection status and notifies subscribers
|
|
1054
|
+
*/
|
|
1055
|
+
updateStatus(status) {
|
|
1056
|
+
if (this.connectionStatus !== status) {
|
|
1057
|
+
this.connectionStatus = status;
|
|
1058
|
+
this.statusSubject.next(status);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Diagnostics must never disturb transport code paths: subscriber errors
|
|
1063
|
+
* are swallowed here.
|
|
1064
|
+
*/
|
|
1065
|
+
emitDiagnostic(eventArg) {
|
|
1066
|
+
try {
|
|
1067
|
+
this.diagnosticsSubject.next(eventArg);
|
|
1068
|
+
}
|
|
1069
|
+
catch { }
|
|
1070
|
+
}
|
|
1071
|
+
emitConnectionClosedDiagnostic(closeCodeArg, reasonArg) {
|
|
1072
|
+
this.emitDiagnostic({
|
|
1073
|
+
kind: 'connectionClosed',
|
|
1074
|
+
side: this.side,
|
|
1075
|
+
scope: 'text',
|
|
1076
|
+
closeCode: closeCodeArg,
|
|
1077
|
+
reason: reasonArg,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
emitTagDenialDiagnostic(operationArg, nameArg, responseArg) {
|
|
1081
|
+
if (responseArg.success !== false)
|
|
1082
|
+
return;
|
|
1083
|
+
let name = '';
|
|
1084
|
+
try {
|
|
1085
|
+
assertBoundedTextIdentifier(nameArg, 'tag name', TYPEDSOCKET_MAX_TAG_NAME_BYTES);
|
|
1086
|
+
name = nameArg;
|
|
1087
|
+
}
|
|
1088
|
+
catch { }
|
|
1089
|
+
this.emitDiagnostic({
|
|
1090
|
+
kind: 'tagMutationDenied',
|
|
1091
|
+
side: 'server',
|
|
1092
|
+
operation: operationArg,
|
|
1093
|
+
name,
|
|
1094
|
+
code: responseArg.code,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Sends a request to the server and waits for response (client-side)
|
|
1099
|
+
*/
|
|
1100
|
+
async sendRequest(request, optionsArg = {}) {
|
|
1101
|
+
if (request.method === '##VirtualStream##') {
|
|
1102
|
+
throw new Error('Legacy VirtualStream control request rejected by transport policy');
|
|
1103
|
+
}
|
|
1104
|
+
const state = this.clientConnectionState;
|
|
1105
|
+
if (!state || !this.isCurrentClientConnection(state)) {
|
|
1106
|
+
throw new Error('WebSocket not connected');
|
|
1107
|
+
}
|
|
1108
|
+
const timeoutMs = this.normalizeRequestTimeout(optionsArg.timeoutMs);
|
|
1109
|
+
if (optionsArg.abortSignal?.aborted) {
|
|
1110
|
+
throw new Error('TypedSocket request aborted');
|
|
1111
|
+
}
|
|
1112
|
+
request.correlation ||= {
|
|
1113
|
+
id: plugins.smartstring.create.createCryptoRandomString(),
|
|
1114
|
+
phase: 'request',
|
|
1115
|
+
};
|
|
1116
|
+
assertBoundedTextIdentifier(request.method, 'method', TYPEDSOCKET_MAX_METHOD_NAME_BYTES);
|
|
1117
|
+
assertBoundedTextIdentifier(request.correlation.id, 'correlation ID', TYPEDSOCKET_MAX_CORRELATION_ID_BYTES);
|
|
1118
|
+
if (request.correlation.phase !== 'request') {
|
|
1119
|
+
throw new Error('TypedSocket outbound requests require request correlation phase.');
|
|
1120
|
+
}
|
|
1121
|
+
const correlationId = request.correlation.id;
|
|
1122
|
+
if (this.pendingRequests.has(correlationId)) {
|
|
1123
|
+
throw new Error(`Duplicate in-flight TypedSocket correlation ID: ${correlationId}`);
|
|
1124
|
+
}
|
|
1125
|
+
if (this.pendingRequests.size >= this.requireClientOptions().limits.pendingRequests) {
|
|
1126
|
+
throw new Error('TypedSocket pending client request capacity exceeded.');
|
|
1127
|
+
}
|
|
1128
|
+
return new Promise((resolve, reject) => {
|
|
1129
|
+
let settled = false;
|
|
1130
|
+
const cleanup = () => {
|
|
1131
|
+
clearTimeout(timeout);
|
|
1132
|
+
optionsArg.abortSignal?.removeEventListener('abort', onAbort);
|
|
1133
|
+
this.pendingRequests.delete(correlationId);
|
|
1134
|
+
};
|
|
1135
|
+
const settle = (errorArg, responseArg) => {
|
|
1136
|
+
if (settled) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
settled = true;
|
|
1140
|
+
cleanup();
|
|
1141
|
+
if (errorArg) {
|
|
1142
|
+
reject(errorArg);
|
|
1143
|
+
}
|
|
1144
|
+
else if (!responseArg) {
|
|
1145
|
+
reject(new Error('TypedSocket request settled without a response.'));
|
|
1146
|
+
}
|
|
1147
|
+
else {
|
|
1148
|
+
resolve(responseArg);
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
const onAbort = () => settle(new Error('TypedSocket request aborted'));
|
|
1152
|
+
const timeout = setTimeout(() => {
|
|
1153
|
+
settle(new Error('TypedSocket request timed out'));
|
|
1154
|
+
}, timeoutMs);
|
|
1155
|
+
this.pendingRequests.set(correlationId, {
|
|
1156
|
+
expectedMethod: request.method,
|
|
1157
|
+
connectionGeneration: state.generation,
|
|
1158
|
+
resolve: (response) => {
|
|
1159
|
+
request.response = response.response;
|
|
1160
|
+
request.correlation = response.correlation;
|
|
1161
|
+
if (response.error)
|
|
1162
|
+
request.error = response.error;
|
|
1163
|
+
else
|
|
1164
|
+
delete request.error;
|
|
1165
|
+
if (response.retry)
|
|
1166
|
+
request.retry = response.retry;
|
|
1167
|
+
else
|
|
1168
|
+
delete request.retry;
|
|
1169
|
+
settle(undefined, request);
|
|
1170
|
+
},
|
|
1171
|
+
reject: (error) => {
|
|
1172
|
+
settle(error);
|
|
1173
|
+
},
|
|
1174
|
+
});
|
|
1175
|
+
optionsArg.abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
1176
|
+
if (optionsArg.abortSignal?.aborted) {
|
|
1177
|
+
onAbort();
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
try {
|
|
1181
|
+
this.sendClientTextEnvelope(request, state);
|
|
1182
|
+
}
|
|
1183
|
+
catch (error) {
|
|
1184
|
+
settle(error instanceof Error ? error : new Error(String(error)));
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
normalizeRequestTimeout(timeoutMsArg) {
|
|
1189
|
+
const maximum = this.clientOptions?.limits.requestTimeoutMs
|
|
1190
|
+
?? TYPEDSOCKET_MAX_REQUEST_TIMEOUT_MS;
|
|
1191
|
+
const timeoutMs = timeoutMsArg ?? Math.min(30_000, maximum);
|
|
1192
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > maximum) {
|
|
1193
|
+
throw new Error(`TypedSocket request timeout must be a positive safe integer no greater than ${maximum}.`);
|
|
1194
|
+
}
|
|
1195
|
+
return timeoutMs;
|
|
1196
|
+
}
|
|
1197
|
+
requireClientOptions() {
|
|
1198
|
+
if (!this.clientOptions) {
|
|
1199
|
+
throw new Error('TypedSocket client options are unavailable on this instance.');
|
|
1200
|
+
}
|
|
1201
|
+
return this.clientOptions;
|
|
1202
|
+
}
|
|
1203
|
+
cancelPendingServerRequestsForPeer(peerArg, errorArg) {
|
|
1204
|
+
const correlationIds = this.pendingServerRequestIdsByPeer.get(peerArg);
|
|
1205
|
+
if (!correlationIds) {
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
for (const correlationId of Array.from(correlationIds)) {
|
|
1209
|
+
this.pendingServerRequests.get(correlationId)?.cancel(errorArg);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
retainServerInterestRegistration(peerArg) {
|
|
1213
|
+
const retainedForPeer = this.retainedServerInterestsByPeer.get(peerArg) ?? 0;
|
|
1214
|
+
if (retainedForPeer >= TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS_PER_PEER
|
|
1215
|
+
|| this.retainedServerInterests >= TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS) {
|
|
1216
|
+
throw new Error('TypedSocket server interest registration capacity exceeded.');
|
|
1217
|
+
}
|
|
1218
|
+
this.retainedServerInterests++;
|
|
1219
|
+
this.retainedServerInterestsByPeer.set(peerArg, retainedForPeer + 1);
|
|
1220
|
+
let released = false;
|
|
1221
|
+
return () => {
|
|
1222
|
+
if (released)
|
|
1223
|
+
return;
|
|
1224
|
+
released = true;
|
|
1225
|
+
this.retainedServerInterests--;
|
|
1226
|
+
const currentForPeer = this.retainedServerInterestsByPeer.get(peerArg);
|
|
1227
|
+
if (currentForPeer === undefined || currentForPeer < 1) {
|
|
1228
|
+
throw new Error('TypedSocket server interest registration accounting underflow.');
|
|
1229
|
+
}
|
|
1230
|
+
if (currentForPeer === 1) {
|
|
1231
|
+
this.retainedServerInterestsByPeer.delete(peerArg);
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
this.retainedServerInterestsByPeer.set(peerArg, currentForPeer - 1);
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
authorizeIncomingServerResponse(responseArg, routingSurfaceArg, trustedLocalDataArg) {
|
|
1239
|
+
if (this.serverStopping
|
|
1240
|
+
|| !this.smartServeRef
|
|
1241
|
+
|| responseArg.correlation?.phase !== 'response'
|
|
1242
|
+
|| typeof responseArg.correlation.id !== 'string'
|
|
1243
|
+
|| typeof responseArg.method !== 'string') {
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
const peer = trustedLocalDataArg.peer;
|
|
1247
|
+
if (!peer || typeof peer !== 'object')
|
|
1248
|
+
return false;
|
|
1249
|
+
const pending = this.pendingServerRequests.get(responseArg.correlation.id);
|
|
1250
|
+
return Boolean(pending
|
|
1251
|
+
&& pending.peer === peer
|
|
1252
|
+
&& pending.routingSurface === routingSurfaceArg
|
|
1253
|
+
&& pending.expectedMethod === responseArg.method
|
|
1254
|
+
&& pending.lifecycleGeneration === this.serverLifecycleGeneration
|
|
1255
|
+
&& pending.peer.routingSurface === routingSurfaceArg
|
|
1256
|
+
&& this.isAttachedServerPeer(pending.peer));
|
|
1257
|
+
}
|
|
1258
|
+
isAttachedServerPeer(peerArg) {
|
|
1259
|
+
return Boolean(!this.serverStopping
|
|
1260
|
+
&& this.smartServeRef
|
|
1261
|
+
&& peerArg.readyState === 1
|
|
1262
|
+
&& peerArg.routingSurface
|
|
1263
|
+
&& this.serverTypedRouters.includes(peerArg.routingSurface)
|
|
1264
|
+
&& this.smartServeRef.getWebSocketConnections().includes(peerArg));
|
|
1265
|
+
}
|
|
1266
|
+
findServerPeerFromRequest(typedToolsArg) {
|
|
1267
|
+
const candidate = typedToolsArg?.localData?.peer;
|
|
1268
|
+
const peer = this.smartServeRef
|
|
1269
|
+
?.getWebSocketConnections()
|
|
1270
|
+
.find((peerArg) => peerArg === candidate);
|
|
1271
|
+
return peer && this.isAttachedServerPeer(peer) ? peer : undefined;
|
|
1272
|
+
}
|
|
1273
|
+
async sendServerRequest(typedRequestDataArg, targetArg, optionsArg) {
|
|
1274
|
+
const timeoutMs = this.normalizeRequestTimeout(optionsArg.timeoutMs);
|
|
1275
|
+
if (this.serverStopping || !this.smartServeRef) {
|
|
1276
|
+
throw new Error('TypedSocket server stopped');
|
|
1277
|
+
}
|
|
1278
|
+
if (optionsArg.abortSignal?.aborted) {
|
|
1279
|
+
throw new Error('TypedSocket request aborted');
|
|
1280
|
+
}
|
|
1281
|
+
typedRequestDataArg.correlation ||= {
|
|
1282
|
+
id: plugins.smartstring.create.createCryptoRandomString(),
|
|
1283
|
+
phase: 'request',
|
|
1284
|
+
};
|
|
1285
|
+
assertBoundedTextIdentifier(typedRequestDataArg.method, 'method', TYPEDSOCKET_MAX_METHOD_NAME_BYTES);
|
|
1286
|
+
assertBoundedTextIdentifier(typedRequestDataArg.correlation.id, 'correlation ID', TYPEDSOCKET_MAX_CORRELATION_ID_BYTES);
|
|
1287
|
+
if (typedRequestDataArg.correlation.phase !== 'request') {
|
|
1288
|
+
throw new Error('TypedSocket outbound requests require request correlation phase.');
|
|
1289
|
+
}
|
|
1290
|
+
const correlationId = typedRequestDataArg.correlation.id;
|
|
1291
|
+
const peer = targetArg.peer;
|
|
1292
|
+
const peerId = targetArg.peer.id;
|
|
1293
|
+
const routingSurface = peer.routingSurface;
|
|
1294
|
+
if (!routingSurface || !this.serverTypedRouters.includes(routingSurface)) {
|
|
1295
|
+
throw new Error('TypedSocket target connection is not bound to this server routing surface');
|
|
1296
|
+
}
|
|
1297
|
+
if (!this.isAttachedServerPeer(peer)) {
|
|
1298
|
+
throw new Error(`TypedSocket target connection is detached or closed: ${peerId}`);
|
|
1299
|
+
}
|
|
1300
|
+
if (this.pendingServerRequests.has(correlationId)) {
|
|
1301
|
+
throw new Error(`Duplicate in-flight TypedSocket correlation ID: ${correlationId}`);
|
|
1302
|
+
}
|
|
1303
|
+
const requestsForPeer = this.pendingServerRequestIdsByPeer.get(peer) ?? new Set();
|
|
1304
|
+
if (requestsForPeer.size >= TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS_PER_PEER
|
|
1305
|
+
|| this.pendingServerRequests.size >= TYPEDSOCKET_MAX_PENDING_SERVER_REQUESTS) {
|
|
1306
|
+
throw new Error('TypedSocket pending server request capacity exceeded.');
|
|
1307
|
+
}
|
|
1308
|
+
if ((this.retainedServerInterestsByPeer.get(peer) ?? 0)
|
|
1309
|
+
>= TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS_PER_PEER
|
|
1310
|
+
|| this.retainedServerInterests >= TYPEDSOCKET_MAX_RETAINED_SERVER_INTERESTS) {
|
|
1311
|
+
throw new Error('TypedSocket server interest registration capacity exceeded.');
|
|
1312
|
+
}
|
|
1313
|
+
const cancellation = plugins.smartpromise.defer();
|
|
1314
|
+
// A stop/abort may happen while InterestMap.addInterest is yielding. Attach
|
|
1315
|
+
// a rejection handler immediately; Promise.race below still receives the
|
|
1316
|
+
// original rejection once the interest exists.
|
|
1317
|
+
void cancellation.promise.catch(() => { });
|
|
1318
|
+
const cleanup = plugins.smartpromise.defer();
|
|
1319
|
+
let cancellationRequested = false;
|
|
1320
|
+
const cancel = (errorArg) => {
|
|
1321
|
+
if (cancellationRequested) {
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
cancellationRequested = true;
|
|
1325
|
+
cancellation.reject(errorArg);
|
|
1326
|
+
};
|
|
1327
|
+
const pendingRequest = {
|
|
1328
|
+
peer,
|
|
1329
|
+
peerId,
|
|
1330
|
+
routingSurface,
|
|
1331
|
+
expectedMethod: typedRequestDataArg.method,
|
|
1332
|
+
lifecycleGeneration: this.serverLifecycleGeneration,
|
|
1333
|
+
cancel,
|
|
1334
|
+
cleanupComplete: cleanup.promise,
|
|
1335
|
+
};
|
|
1336
|
+
this.pendingServerRequests.set(correlationId, pendingRequest);
|
|
1337
|
+
requestsForPeer.add(correlationId);
|
|
1338
|
+
this.pendingServerRequestIdsByPeer.set(peer, requestsForPeer);
|
|
1339
|
+
const onAbort = () => cancel(new Error('TypedSocket request aborted'));
|
|
1340
|
+
const timeout = setTimeout(() => cancel(new Error(`TypedSocket request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
1341
|
+
optionsArg.abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
1342
|
+
if (optionsArg.abortSignal?.aborted) {
|
|
1343
|
+
onAbort();
|
|
1344
|
+
}
|
|
1345
|
+
const destroyInterests = [];
|
|
1346
|
+
try {
|
|
1347
|
+
const releaseInterestRegistration = this.retainServerInterestRegistration(peer);
|
|
1348
|
+
const interestRegistration = Promise.resolve()
|
|
1349
|
+
.then(() => routingSurface.fireEventInterestMap.addInterest(correlationId, typedRequestDataArg))
|
|
1350
|
+
.then((interestArg) => {
|
|
1351
|
+
if (cancellationRequested || this.serverStopping) {
|
|
1352
|
+
interestArg.destroy();
|
|
1353
|
+
}
|
|
1354
|
+
return interestArg;
|
|
1355
|
+
})
|
|
1356
|
+
.finally(releaseInterestRegistration);
|
|
1357
|
+
const interest = await Promise.race([
|
|
1358
|
+
interestRegistration,
|
|
1359
|
+
cancellation.promise,
|
|
1360
|
+
]);
|
|
1361
|
+
if (cancellationRequested || this.serverStopping) {
|
|
1362
|
+
interest.destroy();
|
|
1363
|
+
await cancellation.promise;
|
|
1364
|
+
}
|
|
1365
|
+
destroyInterests.push(() => interest.destroy());
|
|
1366
|
+
// Establish the rejecting race before send so synchronous close/abort and
|
|
1367
|
+
// send failures all use the same cleanup path.
|
|
1368
|
+
const response = Promise.race([
|
|
1369
|
+
interest.interestFullfilled,
|
|
1370
|
+
cancellation.promise,
|
|
1371
|
+
]);
|
|
1372
|
+
const targetIsStillConnected = this.smartServeRef
|
|
1373
|
+
?.getWebSocketConnections()
|
|
1374
|
+
.some((peerArg) => peerArg === peer) ?? false;
|
|
1375
|
+
if (!targetIsStillConnected) {
|
|
1376
|
+
cancel(new Error(`TypedSocket target connection closed: ${peerId}`));
|
|
1377
|
+
}
|
|
1378
|
+
if (this.serverStopping) {
|
|
1379
|
+
cancel(new Error('TypedSocket server stopped'));
|
|
1380
|
+
}
|
|
1381
|
+
if (!cancellationRequested && targetIsStillConnected && !this.serverStopping) {
|
|
1382
|
+
targetArg.peer.send(plugins.smartjson.stringify(typedRequestDataArg));
|
|
1383
|
+
}
|
|
1384
|
+
const routedResponse = await response;
|
|
1385
|
+
typedRequestDataArg.response = routedResponse.response;
|
|
1386
|
+
typedRequestDataArg.correlation = routedResponse.correlation;
|
|
1387
|
+
if (routedResponse.error)
|
|
1388
|
+
typedRequestDataArg.error = routedResponse.error;
|
|
1389
|
+
else
|
|
1390
|
+
delete typedRequestDataArg.error;
|
|
1391
|
+
if (routedResponse.retry)
|
|
1392
|
+
typedRequestDataArg.retry = routedResponse.retry;
|
|
1393
|
+
else
|
|
1394
|
+
delete typedRequestDataArg.retry;
|
|
1395
|
+
return typedRequestDataArg;
|
|
1396
|
+
}
|
|
1397
|
+
finally {
|
|
1398
|
+
clearTimeout(timeout);
|
|
1399
|
+
optionsArg.abortSignal?.removeEventListener('abort', onAbort);
|
|
1400
|
+
this.pendingServerRequests.delete(correlationId);
|
|
1401
|
+
const remainingRequestsForPeer = this.pendingServerRequestIdsByPeer.get(peer);
|
|
1402
|
+
remainingRequestsForPeer?.delete(correlationId);
|
|
1403
|
+
if (remainingRequestsForPeer?.size === 0) {
|
|
1404
|
+
this.pendingServerRequestIdsByPeer.delete(peer);
|
|
1405
|
+
}
|
|
1406
|
+
for (const destroyInterest of destroyInterests) {
|
|
1407
|
+
destroyInterest();
|
|
1408
|
+
}
|
|
1409
|
+
cleanup.resolve();
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
// ============================================================================
|
|
1413
|
+
// PUBLIC API - SHARED
|
|
1414
|
+
// ============================================================================
|
|
1415
|
+
/**
|
|
1416
|
+
* Creates a TypedRequest for the specified method.
|
|
1417
|
+
* On clients, sends to the server.
|
|
1418
|
+
* On servers, sends to the specified target connection.
|
|
1419
|
+
*/
|
|
1420
|
+
createTypedRequest(methodName, targetConnection, optionsArg = {}) {
|
|
1421
|
+
let implicitTargetingReported = false;
|
|
1422
|
+
const postMethod = async (requestDataArg, postOptionsArg = {}) => {
|
|
1423
|
+
const composedAbortSignal = composeAbortSignals(optionsArg.abortSignal, postOptionsArg.signal);
|
|
1424
|
+
const requestOptions = {
|
|
1425
|
+
...optionsArg,
|
|
1426
|
+
abortSignal: composedAbortSignal.signal,
|
|
1427
|
+
};
|
|
1428
|
+
try {
|
|
1429
|
+
if (this.side === 'client') {
|
|
1430
|
+
return await this.sendRequest(requestDataArg, requestOptions);
|
|
1431
|
+
}
|
|
1432
|
+
// Server-side: send to target connection
|
|
1433
|
+
if (!this.smartServeRef) {
|
|
1434
|
+
throw new Error('Server not initialized');
|
|
1435
|
+
}
|
|
1436
|
+
let target = targetConnection;
|
|
1437
|
+
if (!target) {
|
|
1438
|
+
if (this.serverTypedRouters.length > 1) {
|
|
1439
|
+
throw new Error('TypedSocket multi-router servers require an explicit targetConnection');
|
|
1440
|
+
}
|
|
1441
|
+
const routingSurface = this.serverTypedRouters[0];
|
|
1442
|
+
const allConnections = this.smartServeRef.getWebSocketConnections().filter((peerArg) => peerArg.routingSurface === routingSurface);
|
|
1443
|
+
if (allConnections.length === 1) {
|
|
1444
|
+
const peer = allConnections[0];
|
|
1445
|
+
target = wrapSmartServePeer(peer);
|
|
1446
|
+
// Deprecated: implicit single-connection targeting is slated for
|
|
1447
|
+
// removal in the next major; it also disables native-byte
|
|
1448
|
+
// transport for the request because no peer was known at
|
|
1449
|
+
// createTypedRequest() time.
|
|
1450
|
+
if (!implicitTargetingReported) {
|
|
1451
|
+
implicitTargetingReported = true;
|
|
1452
|
+
this.emitDiagnostic({
|
|
1453
|
+
kind: 'implicitTargetingUsed',
|
|
1454
|
+
side: 'server',
|
|
1455
|
+
method: methodName,
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
else if (allConnections.length === 0) {
|
|
1460
|
+
throw new Error('No WebSocket connections available');
|
|
1461
|
+
}
|
|
1462
|
+
else {
|
|
1463
|
+
throw new Error('Multiple connections available - specify targetConnection');
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return await this.sendServerRequest(requestDataArg, target, requestOptions);
|
|
1467
|
+
}
|
|
1468
|
+
finally {
|
|
1469
|
+
composedAbortSignal.cleanup();
|
|
1470
|
+
}
|
|
1471
|
+
};
|
|
1472
|
+
const nativeStreamTransport = this.side === 'client'
|
|
1473
|
+
? this.nativeBytes.getClientNativeStreamTransport()
|
|
1474
|
+
: targetConnection
|
|
1475
|
+
? this.nativeBytes.getServerNativeStreamTransport(targetConnection.peer)
|
|
1476
|
+
: undefined;
|
|
1477
|
+
return new plugins.typedrequest.TypedRequest(new plugins.typedrequest.TypedTarget({
|
|
1478
|
+
postMethod,
|
|
1479
|
+
supportsAbortSignal: true,
|
|
1480
|
+
nativeStreamTransport,
|
|
1481
|
+
legacyVirtualStreams: 'deny',
|
|
1482
|
+
}), methodName);
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Creates an exact authorized native-byte receive facade for one server peer.
|
|
1486
|
+
* TypedRequest carries its opaque descriptor when the facade enters a DTO.
|
|
1487
|
+
*/
|
|
1488
|
+
createVirtualStream(optionsArg) {
|
|
1489
|
+
if (this.side !== 'server') {
|
|
1490
|
+
throw new Error('createVirtualStream is only available on servers');
|
|
1491
|
+
}
|
|
1492
|
+
if (optionsArg.protocol !== NATIVE_BYTE_PROTOCOL
|
|
1493
|
+
|| optionsArg.direction !== 'receive') {
|
|
1494
|
+
throw new TypeError('createVirtualStream requires native-byte-v1 receive direction');
|
|
1495
|
+
}
|
|
1496
|
+
const authorizationAdapter = this.nativeByteAuthorizationAdapter;
|
|
1497
|
+
if (!authorizationAdapter) {
|
|
1498
|
+
throw new NativeByteCapabilityError('createVirtualStream requires a nativeByteAuthorizationAdapter');
|
|
1499
|
+
}
|
|
1500
|
+
const peer = 'peer' in optionsArg.target
|
|
1501
|
+
? optionsArg.target.peer
|
|
1502
|
+
: optionsArg.target;
|
|
1503
|
+
const transport = this.nativeBytes.getServerNativeStreamTransport(peer);
|
|
1504
|
+
if (!transport) {
|
|
1505
|
+
throw new NativeByteCapabilityError('createVirtualStream target is not an active native-byte-v1 peer');
|
|
1506
|
+
}
|
|
1507
|
+
transport.requireCapability({ capability: NATIVE_BYTE_PROTOCOL });
|
|
1508
|
+
const manifest = this.nativeBytes.normalizeManifest({
|
|
1509
|
+
byteLength: optionsArg.byteLength,
|
|
1510
|
+
sha256: optionsArg.sha256,
|
|
1511
|
+
contentType: optionsArg.contentType,
|
|
1512
|
+
});
|
|
1513
|
+
const authorizationBinding = authorizationAdapter.bind(optionsArg.authorization, Object.freeze({ target: peer, manifest }));
|
|
1514
|
+
if (!authorizationBinding
|
|
1515
|
+
|| typeof authorizationBinding !== 'object'
|
|
1516
|
+
|| typeof Reflect.get(authorizationBinding, 'then') === 'function') {
|
|
1517
|
+
throw new TypeError('nativeByteAuthorizationAdapter.bind() must return a synchronous binding');
|
|
1518
|
+
}
|
|
1519
|
+
const registration = this.nativeBytes.createReceiveRegistration({
|
|
1520
|
+
target: peer,
|
|
1521
|
+
...manifest,
|
|
1522
|
+
initialWindowBytes: optionsArg.initialWindowBytes,
|
|
1523
|
+
principalId: authorizationBinding.principalId,
|
|
1524
|
+
credentialRevision: authorizationBinding.credentialRevision,
|
|
1525
|
+
configRevision: authorizationBinding.configRevision,
|
|
1526
|
+
bindingRevision: authorizationBinding.bindingRevision,
|
|
1527
|
+
revalidate: authorizationBinding.revalidate,
|
|
1528
|
+
});
|
|
1529
|
+
return plugins.typedrequest.VirtualStream.fromNativeByteRegistration({
|
|
1530
|
+
transport,
|
|
1531
|
+
registration,
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
/**
|
|
1535
|
+
* Gets the current connection status
|
|
1536
|
+
*/
|
|
1537
|
+
getStatus() {
|
|
1538
|
+
return this.connectionStatus;
|
|
1539
|
+
}
|
|
1540
|
+
/**
|
|
1541
|
+
* Stops the TypedSocket client or cleans up server state
|
|
1542
|
+
*/
|
|
1543
|
+
async stop() {
|
|
1544
|
+
if (this.side === 'client') {
|
|
1545
|
+
this.stopped = true;
|
|
1546
|
+
for (const stopListener of Array.from(this.stopListeners)) {
|
|
1547
|
+
stopListener();
|
|
1548
|
+
}
|
|
1549
|
+
this.stopListeners.clear();
|
|
1550
|
+
if (this.clientOptions) {
|
|
1551
|
+
this.clientOptions.autoReconnect = false;
|
|
1552
|
+
if (this.clientOptions.abortSignal && this.abortSignalListener) {
|
|
1553
|
+
this.clientOptions.abortSignal.removeEventListener('abort', this.abortSignalListener);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
this.abortSignalListener = undefined;
|
|
1557
|
+
if (this.websocket) {
|
|
1558
|
+
const websocket = this.websocket;
|
|
1559
|
+
const generation = this.connectionGeneration;
|
|
1560
|
+
if (this.clientConnectionState) {
|
|
1561
|
+
this.abortClientConnectionState(this.clientConnectionState, new Error('TypedSocket stopped'));
|
|
1562
|
+
}
|
|
1563
|
+
this.nativeBytes.unbindClientConnection(websocket, generation, new Error('TypedSocket stopped'));
|
|
1564
|
+
websocket.close();
|
|
1565
|
+
this.websocket = null;
|
|
1566
|
+
}
|
|
1567
|
+
for (const pendingRequest of this.pendingRequests.values()) {
|
|
1568
|
+
pendingRequest.reject(new Error('TypedSocket stopped'));
|
|
1569
|
+
}
|
|
1570
|
+
this.pendingRequests.clear();
|
|
1571
|
+
}
|
|
1572
|
+
else {
|
|
1573
|
+
this.serverStopping = true;
|
|
1574
|
+
this.serverLifecycleGeneration++;
|
|
1575
|
+
this.tagPolicyManager.stop(this.smartServeRef?.getWebSocketConnections() ?? []);
|
|
1576
|
+
const pendingRequests = Array.from(this.pendingServerRequests.values());
|
|
1577
|
+
for (const pendingRequest of pendingRequests) {
|
|
1578
|
+
pendingRequest.cancel(new Error('TypedSocket server stopped'));
|
|
1579
|
+
}
|
|
1580
|
+
await Promise.all(pendingRequests.map((pendingRequest) => pendingRequest.cleanupComplete));
|
|
1581
|
+
this.unsubscribeSmartServeConnectionClose?.();
|
|
1582
|
+
this.unsubscribeSmartServeConnectionClose = null;
|
|
1583
|
+
this.nativeBytes.detachServer();
|
|
1584
|
+
this.tagPolicyManager.setAttachedPeersProvider(undefined);
|
|
1585
|
+
this.smartServeRef = null;
|
|
1586
|
+
this.pendingServerRequestIdsByPeer.clear();
|
|
1587
|
+
}
|
|
1588
|
+
for (const detachNativeStreamResolver of this.detachNativeStreamResolvers.reverse()) {
|
|
1589
|
+
detachNativeStreamResolver();
|
|
1590
|
+
}
|
|
1591
|
+
this.detachNativeStreamResolvers = [];
|
|
1592
|
+
for (const detachResponseAuthorityGuard of this.detachResponseAuthorityGuards.reverse()) {
|
|
1593
|
+
detachResponseAuthorityGuard();
|
|
1594
|
+
}
|
|
1595
|
+
this.detachResponseAuthorityGuards = [];
|
|
1596
|
+
if (this.side === 'server') {
|
|
1597
|
+
this.releaseServerComposition();
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
releaseServerComposition() {
|
|
1601
|
+
for (const detachLegacyPolicy of this.detachLegacyVirtualStreamPolicies.reverse()) {
|
|
1602
|
+
detachLegacyPolicy();
|
|
1603
|
+
}
|
|
1604
|
+
this.detachLegacyVirtualStreamPolicies = [];
|
|
1605
|
+
for (const detachProtocolRouter of this.detachProtocolRouters.reverse()) {
|
|
1606
|
+
detachProtocolRouter();
|
|
1607
|
+
}
|
|
1608
|
+
this.detachProtocolRouters = [];
|
|
1609
|
+
}
|
|
1610
|
+
// ============================================================================
|
|
1611
|
+
// CLIENT-ONLY METHODS
|
|
1612
|
+
// ============================================================================
|
|
1613
|
+
/**
|
|
1614
|
+
* Sets a tag on this client connection.
|
|
1615
|
+
* Tags are stored on the server and can be used for filtering.
|
|
1616
|
+
* @client-only
|
|
1617
|
+
*/
|
|
1618
|
+
async setTag(name, payload) {
|
|
1619
|
+
if (this.side !== 'client') {
|
|
1620
|
+
throw new Error('setTag is only available on clients');
|
|
1621
|
+
}
|
|
1622
|
+
return await this.clientTagReconciler.setTag(name, payload);
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* Re-applies all client tags after a reconnect, since server-side tags are
|
|
1626
|
+
* bound to the previous connection.
|
|
1627
|
+
*/
|
|
1628
|
+
async reapplyClientTags(stateArg = this.clientConnectionState) {
|
|
1629
|
+
await this.clientTagReconciler.reapplyTags(stateArg);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Removes a tag from this client connection.
|
|
1633
|
+
* @client-only
|
|
1634
|
+
*/
|
|
1635
|
+
async removeTag(name) {
|
|
1636
|
+
if (this.side !== 'client') {
|
|
1637
|
+
throw new Error('removeTag is only available on clients');
|
|
1638
|
+
}
|
|
1639
|
+
return await this.clientTagReconciler.removeTag(name);
|
|
1640
|
+
}
|
|
1641
|
+
// ============================================================================
|
|
1642
|
+
// SERVER-ONLY METHODS
|
|
1643
|
+
// ============================================================================
|
|
1644
|
+
/**
|
|
1645
|
+
* Resolves the exact SmartServe transport peer for one incoming TypedRequest.
|
|
1646
|
+
* Server handlers can pass their TypedTools argument without type assertions.
|
|
1647
|
+
*/
|
|
1648
|
+
getServerConnectionForRequest(typedToolsArg) {
|
|
1649
|
+
if (this.side !== 'server') {
|
|
1650
|
+
throw new Error('getServerConnectionForRequest is only available on servers');
|
|
1651
|
+
}
|
|
1652
|
+
const peer = this.findServerPeerFromRequest(typedToolsArg);
|
|
1653
|
+
if (!peer) {
|
|
1654
|
+
throw new Error('TypedSocket request is not bound to an attached transport peer');
|
|
1655
|
+
}
|
|
1656
|
+
return wrapSmartServePeer(peer);
|
|
1657
|
+
}
|
|
1658
|
+
/** Assigns a protected server-owned tag to one connection. */
|
|
1659
|
+
setServerTag(connectionArg, nameArg, payloadArg) {
|
|
1660
|
+
if (this.side !== 'server') {
|
|
1661
|
+
throw new Error('setServerTag is only available on servers');
|
|
1662
|
+
}
|
|
1663
|
+
this.tagPolicyManager.setServerTag('peer' in connectionArg ? connectionArg.peer : connectionArg, nameArg, payloadArg);
|
|
1664
|
+
}
|
|
1665
|
+
/** Removes a server-owned tag while keeping its name protected from clients. */
|
|
1666
|
+
removeServerTag(connectionArg, nameArg) {
|
|
1667
|
+
if (this.side !== 'server') {
|
|
1668
|
+
throw new Error('removeServerTag is only available on servers');
|
|
1669
|
+
}
|
|
1670
|
+
this.tagPolicyManager.removeServerTag('peer' in connectionArg ? connectionArg.peer : connectionArg, nameArg);
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Finds all connections matching the filter function.
|
|
1674
|
+
* @server-only
|
|
1675
|
+
*/
|
|
1676
|
+
async findAllTargetConnections(asyncFindFuncArg) {
|
|
1677
|
+
if (this.side !== 'server' || !this.smartServeRef) {
|
|
1678
|
+
throw new Error('findAllTargetConnections is only available on servers');
|
|
1679
|
+
}
|
|
1680
|
+
const matchingConnections = [];
|
|
1681
|
+
for (const peer of this.smartServeRef.getWebSocketConnections()) {
|
|
1682
|
+
if (!this.isAttachedServerPeer(peer))
|
|
1683
|
+
continue;
|
|
1684
|
+
const wrapper = wrapSmartServePeer(peer);
|
|
1685
|
+
if (await asyncFindFuncArg(wrapper)) {
|
|
1686
|
+
matchingConnections.push(wrapper);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
return matchingConnections;
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Finds the first connection matching the filter function.
|
|
1693
|
+
* @server-only
|
|
1694
|
+
*/
|
|
1695
|
+
async findTargetConnection(asyncFindFuncArg) {
|
|
1696
|
+
const allMatching = await this.findAllTargetConnections(asyncFindFuncArg);
|
|
1697
|
+
return allMatching[0];
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Finds all connections with the specified tag.
|
|
1701
|
+
* @server-only
|
|
1702
|
+
*/
|
|
1703
|
+
async findAllTargetConnectionsByTag(keyArg, payloadArg) {
|
|
1704
|
+
if (this.side !== 'server' || !this.smartServeRef) {
|
|
1705
|
+
throw new Error('findAllTargetConnectionsByTag is only available on servers');
|
|
1706
|
+
}
|
|
1707
|
+
const peers = this.smartServeRef
|
|
1708
|
+
.getWebSocketConnectionsByTag(keyArg)
|
|
1709
|
+
.filter((peerArg) => this.isAttachedServerPeer(peerArg));
|
|
1710
|
+
const results = [];
|
|
1711
|
+
for (const peer of peers) {
|
|
1712
|
+
const wrapper = wrapSmartServePeer(peer);
|
|
1713
|
+
// If payload specified, also filter by payload
|
|
1714
|
+
if (payloadArg !== undefined) {
|
|
1715
|
+
const tag = await wrapper.getTagById(keyArg);
|
|
1716
|
+
if (plugins.smartjson.stringify(tag?.payload) !== plugins.smartjson.stringify(payloadArg)) {
|
|
1717
|
+
continue;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
results.push(wrapper);
|
|
1721
|
+
}
|
|
1722
|
+
return results;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Finds the first connection with the specified tag.
|
|
1726
|
+
* @server-only
|
|
1727
|
+
*/
|
|
1728
|
+
async findTargetConnectionByTag(keyArg, payloadArg) {
|
|
1729
|
+
const allResults = await this.findAllTargetConnectionsByTag(keyArg, payloadArg);
|
|
1730
|
+
return allResults[0];
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy50eXBlZHNvY2tldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMudHlwZWRzb2NrZXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLG9CQUFvQixFQUFFLDBCQUEwQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDOUYsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sOEJBQThCLENBQUM7QUFDekUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUNMLDJCQUEyQixFQUMzQiw4QkFBOEIsRUFDOUIsK0JBQStCLEVBQy9CLDJCQUEyQixHQUM1QixNQUFNLDBDQUEwQyxDQUFDO0FBQ2xELE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ3ZFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3BFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ3BFLE9BQU8sRUFDTCw2Q0FBNkMsRUFDN0Msc0RBQXNELEdBQ3ZELE1BQU0sa0NBQWtDLENBQUM7QUFtQjFDLE1BQU0sVUFBVSxHQUFHLHFCQUFxQixDQUFDO0FBQ3pDLE1BQU0sV0FBVyxHQUFHLElBQUksV0FBVyxFQUFFLENBQUM7QUFFdEMsTUFBTSxDQUFDLE1BQU0sZ0NBQWdDLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQztBQUM1RCxNQUFNLENBQUMsTUFBTSxrQ0FBa0MsR0FBRyxFQUFFLENBQUM7QUFDckQsTUFBTSxDQUFDLE1BQU0saUNBQWlDLEdBQUcsQ0FBQyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFDakUsTUFBTSxDQUFDLE1BQU0sMENBQTBDLEdBQUcsRUFBRSxDQUFDO0FBQzdELE1BQU0sQ0FBQyxNQUFNLHlDQUF5QyxHQUFHLEVB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
|