@hediet/linkrpc 0.0.2 → 0.0.3-next.20260922.1
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/README.md +266 -31
- package/dist/chunks/{hub.interfaces-Bf4VfWNX.js → hub.interfaces-Deslgtgr.js} +2 -2
- package/dist/chunks/{hub.interfaces-Bf4VfWNX.js.map → hub.interfaces-Deslgtgr.js.map} +1 -1
- package/dist/chunks/{hubAccess-C06qAKao.d.ts → hubAccess-BmPvNNaM.d.ts} +2 -2
- package/dist/chunks/{hubAccess-C06qAKao.d.ts.map → hubAccess-BmPvNNaM.d.ts.map} +1 -1
- package/dist/chunks/{hubFacade-BIB9CoAv.js → hubFacade-BKTQXCdF.js} +2 -2
- package/dist/chunks/{hubFacade-BIB9CoAv.js.map → hubFacade-BKTQXCdF.js.map} +1 -1
- package/dist/chunks/{hubFacade-DtQpGpUy.d.ts → hubFacade-Bmh-SFWd.d.ts} +2 -2
- package/dist/chunks/{hubFacade-DtQpGpUy.d.ts.map → hubFacade-Bmh-SFWd.d.ts.map} +1 -1
- package/dist/chunks/{boundedTrafficSubscription-XZ1KwXZC.js → inspectionHost-BIkLKvr1.js} +1014 -32
- package/dist/chunks/inspectionHost-BIkLKvr1.js.map +1 -0
- package/dist/chunks/{linkRpcConnection-BadD37ok.d.ts → linkRpcConnection-Bbq20hQG.d.ts} +2215 -1699
- package/dist/chunks/linkRpcConnection-Bbq20hQG.d.ts.map +1 -0
- package/dist/chunks/{src-CAhXne71.js → src-DKjTceek.js} +1975 -2051
- package/dist/chunks/src-DKjTceek.js.map +1 -0
- package/dist/hub/client/index.d.ts +3 -3
- package/dist/hub/client/index.js +3 -3
- package/dist/hub/common/index.d.ts +2 -325
- package/dist/hub/common/index.d.ts.map +1 -1
- package/dist/hub/common/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/inspection/index.d.ts +87 -2
- package/dist/inspection/index.d.ts.map +1 -1
- package/dist/inspection/index.js +2 -2
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -4
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/package.json +7 -2
- package/dist/chunks/boundedTrafficSubscription-XZ1KwXZC.js.map +0 -1
- package/dist/chunks/linkRpcConnection-BadD37ok.d.ts.map +0 -1
- package/dist/chunks/src-CAhXne71.js.map +0 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
|
-
import { any, array, boolean, enum as enum$1, int, literal, nonnegative, number, object, optional, record, string, union, unknown, void as void$1 } from "zod/mini";
|
|
6
|
-
import { toJSONSchema } from "zod/v4/core";
|
|
5
|
+
import { any, array, boolean, enum as enum$1, int, literal, nonnegative, number, object, optional, record, strictObject, string, union, unknown, void as void$1 } from "zod/mini";
|
|
6
|
+
import { safeParse, toJSONSchema } from "zod/v4/core";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
//#region src/schema/normalize.ts
|
|
9
9
|
/**
|
|
@@ -153,9 +153,171 @@ function _constPropNames(props) {
|
|
|
153
153
|
return out;
|
|
154
154
|
}
|
|
155
155
|
//#endregion
|
|
156
|
-
//#region src/
|
|
156
|
+
//#region src/connection/rpcFailure.ts
|
|
157
|
+
const rpcFailureBrand = Symbol("linkrpc.rpcFailure");
|
|
158
|
+
/** A nominal failure wrapper: successful application objects can never collide with it. */
|
|
159
|
+
var RpcFailure = class {
|
|
160
|
+
error;
|
|
161
|
+
[rpcFailureBrand] = true;
|
|
162
|
+
constructor(error) {
|
|
163
|
+
this.error = error;
|
|
164
|
+
Object.freeze(this);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function isRpcFailure(value) {
|
|
168
|
+
return typeof value === "object" && value !== null && value[rpcFailureBrand] === true;
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/protocol/jsonRpc.ts
|
|
172
|
+
const ErrorCode = {
|
|
173
|
+
/** Default code for named LinkRPC application errors. */
|
|
174
|
+
applicationError: 1,
|
|
175
|
+
parseError: -32700,
|
|
176
|
+
invalidRequest: -32600,
|
|
177
|
+
methodNotFound: -32601,
|
|
178
|
+
invalidParams: -32602,
|
|
179
|
+
internalError: -32603,
|
|
180
|
+
/** Caller is authenticated but lacks a capability covering the call. */
|
|
181
|
+
permissionRequired: -32401,
|
|
182
|
+
/** The peer a request was routed to detached before it could respond. */
|
|
183
|
+
peerDisconnected: -32402,
|
|
184
|
+
/** The request exceeded the hub's idle timeout with no stream activity. */
|
|
185
|
+
requestTimeout: -32403,
|
|
186
|
+
/** The request was cancelled (by the caller, or by the hub on disconnect). */
|
|
187
|
+
cancelled: -32800
|
|
188
|
+
};
|
|
189
|
+
function isRequest(m) {
|
|
190
|
+
return typeof m.method === "string" && m.id !== void 0;
|
|
191
|
+
}
|
|
192
|
+
function isNotification(m) {
|
|
193
|
+
return typeof m.method === "string" && m.id === void 0;
|
|
194
|
+
}
|
|
195
|
+
function isResponse(m) {
|
|
196
|
+
return typeof m.method !== "string";
|
|
197
|
+
}
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/schema/applicationErrorBrand.ts
|
|
157
200
|
const applicationErrorBrand = Symbol("linkrpc.applicationError");
|
|
158
|
-
function
|
|
201
|
+
function brandApplicationError(value) {
|
|
202
|
+
return Object.freeze(Object.defineProperty(value, applicationErrorBrand, { value: true }));
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/schema/schemaSource.ts
|
|
206
|
+
const schemaSources = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/schema/validationIssue.ts
|
|
209
|
+
function jsonPointerSegment(key) {
|
|
210
|
+
return `/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
211
|
+
}
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/schema/errorValidation.ts
|
|
214
|
+
const bodySchemas = /* @__PURE__ */ new WeakMap();
|
|
215
|
+
function parseDeclaredErrorBody(descriptor, body) {
|
|
216
|
+
return parseErrorBody(descriptor.bodySchema ?? errorBodySchema(descriptor), body);
|
|
217
|
+
}
|
|
218
|
+
function errorBodySchema(descriptor) {
|
|
219
|
+
let schema = bodySchemas.get(descriptor);
|
|
220
|
+
if (schema === void 0) {
|
|
221
|
+
const payload = descriptor.dataSchema === void 0 ? {} : { data: descriptor.dataSchema };
|
|
222
|
+
schema = descriptor.type === void 0 ? strictObject({
|
|
223
|
+
message: literal(descriptor.message),
|
|
224
|
+
...payload
|
|
225
|
+
}) : strictObject({
|
|
226
|
+
message: string(),
|
|
227
|
+
data: strictObject({
|
|
228
|
+
type: literal(descriptor.type),
|
|
229
|
+
...payload
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
bodySchemas.set(descriptor, schema);
|
|
233
|
+
}
|
|
234
|
+
return schema;
|
|
235
|
+
}
|
|
236
|
+
function parseErrorBody(schema, body) {
|
|
237
|
+
const parsed = safeParse(schema, body);
|
|
238
|
+
if (!parsed.success) return {
|
|
239
|
+
success: false,
|
|
240
|
+
issues: validationIssues(parsed.error.issues)
|
|
241
|
+
};
|
|
242
|
+
if (typeof parsed.data !== "object" || parsed.data === null || !("message" in parsed.data) || typeof parsed.data.message !== "string") return {
|
|
243
|
+
success: false,
|
|
244
|
+
issues: [{
|
|
245
|
+
path: "/message",
|
|
246
|
+
message: "Expected a string message"
|
|
247
|
+
}]
|
|
248
|
+
};
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
data: {
|
|
252
|
+
...parsed.data,
|
|
253
|
+
message: parsed.data.message
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function validationIssues(issues, prefix = []) {
|
|
258
|
+
return issues.flatMap((issue) => {
|
|
259
|
+
const path = [...prefix, ...issue.path];
|
|
260
|
+
if (issue.code === "invalid_union") return issue.errors.flatMap((branch) => validationIssues(branch, path));
|
|
261
|
+
if (issue.code === "unrecognized_keys") return issue.keys.map((key) => ({
|
|
262
|
+
path: pointer([...path, key]),
|
|
263
|
+
message: "Unexpected property"
|
|
264
|
+
}));
|
|
265
|
+
return [{
|
|
266
|
+
path: pointer(path),
|
|
267
|
+
message: issue.message
|
|
268
|
+
}];
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function pointer(path) {
|
|
272
|
+
return path.map(jsonPointerSegment).join("");
|
|
273
|
+
}
|
|
274
|
+
function jsonIssues(value, path = [], ancestors = /* @__PURE__ */ new Set()) {
|
|
275
|
+
if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) return [];
|
|
276
|
+
if (typeof value !== "object" || ancestors.has(value) || !Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return [{
|
|
277
|
+
path: pointer(path),
|
|
278
|
+
message: "Expected a JSON value"
|
|
279
|
+
}];
|
|
280
|
+
ancestors.add(value);
|
|
281
|
+
const issues = (Array.isArray(value) ? Array.from(value.entries()) : Object.entries(value)).flatMap(([key, child]) => jsonIssues(child, [...path, key], ancestors));
|
|
282
|
+
ancestors.delete(value);
|
|
283
|
+
return issues;
|
|
284
|
+
}
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region src/schema/memberTypes.ts
|
|
287
|
+
function rpcError(code, schemaOrShape) {
|
|
288
|
+
assertErrorCode(code);
|
|
289
|
+
const bodySchema = "_zod" in schemaOrShape ? schemaOrShape : object({
|
|
290
|
+
message: schemaOrShape.message ?? string(),
|
|
291
|
+
...schemaOrShape.data === void 0 ? {} : { data: schemaOrShape.data }
|
|
292
|
+
});
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
code,
|
|
295
|
+
bodySchema,
|
|
296
|
+
create(body) {
|
|
297
|
+
return brandApplicationError({
|
|
298
|
+
kind: "application",
|
|
299
|
+
code,
|
|
300
|
+
message: body.message,
|
|
301
|
+
...Object.hasOwn(body, "data") ? { data: body.data } : {}
|
|
302
|
+
});
|
|
303
|
+
},
|
|
304
|
+
is(value) {
|
|
305
|
+
const payload = isRpcFailure(value) ? value.error : value;
|
|
306
|
+
if (!isApplicationErrorValue(payload) || payload.code !== code || payload.type !== void 0) return false;
|
|
307
|
+
return parseErrorBody(bodySchema, {
|
|
308
|
+
message: payload.message,
|
|
309
|
+
...Object.hasOwn(payload, "data") ? { data: payload.data } : {}
|
|
310
|
+
}).success;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function applicationError(codeOrType, messageOrOptions, dataSchema) {
|
|
315
|
+
const type = typeof codeOrType === "string" ? codeOrType : void 0;
|
|
316
|
+
if (type === "") throw new Error("Application error type must be a nonempty string.");
|
|
317
|
+
const options = typeof messageOrOptions === "object" ? messageOrOptions : void 0;
|
|
318
|
+
const code = typeof codeOrType === "number" ? codeOrType : options?.code ?? ErrorCode.applicationError;
|
|
319
|
+
const message = type === void 0 ? messageOrOptions : options?.message ?? type;
|
|
320
|
+
if (type !== void 0) dataSchema = options?.data;
|
|
159
321
|
assertApplicationErrorCode(code);
|
|
160
322
|
if (typeof message !== "string") throw new Error("Application error message must be a string.");
|
|
161
323
|
const create = dataSchema === void 0 ? function() {
|
|
@@ -164,6 +326,7 @@ function applicationError(code, message, dataSchema) {
|
|
|
164
326
|
kind: "application",
|
|
165
327
|
code,
|
|
166
328
|
message,
|
|
329
|
+
...type === void 0 ? {} : { type },
|
|
167
330
|
[applicationErrorBrand]: true
|
|
168
331
|
});
|
|
169
332
|
} : function(data) {
|
|
@@ -172,25 +335,44 @@ function applicationError(code, message, dataSchema) {
|
|
|
172
335
|
kind: "application",
|
|
173
336
|
code,
|
|
174
337
|
message,
|
|
338
|
+
...type === void 0 ? {} : { type },
|
|
175
339
|
[applicationErrorBrand]: true,
|
|
176
340
|
data
|
|
177
341
|
});
|
|
178
342
|
};
|
|
179
|
-
|
|
343
|
+
const descriptor = Object.freeze({
|
|
344
|
+
type,
|
|
180
345
|
code,
|
|
181
346
|
message,
|
|
182
347
|
...dataSchema === void 0 ? {} : { dataSchema },
|
|
183
|
-
create
|
|
348
|
+
create,
|
|
349
|
+
is(value) {
|
|
350
|
+
const payload = isRpcFailure(value) ? value.error : value;
|
|
351
|
+
if (!isApplicationErrorValue(payload)) return false;
|
|
352
|
+
if (payload.kind !== "application" || payload.code !== code || payload.type !== type) return false;
|
|
353
|
+
const data = Object.hasOwn(payload, "data") ? { data: payload.data } : {};
|
|
354
|
+
return parseDeclaredErrorBody(descriptor, {
|
|
355
|
+
message: payload.message,
|
|
356
|
+
...type === void 0 ? data : { data: {
|
|
357
|
+
type,
|
|
358
|
+
...data
|
|
359
|
+
} }
|
|
360
|
+
}).success;
|
|
361
|
+
}
|
|
184
362
|
});
|
|
363
|
+
return descriptor;
|
|
185
364
|
}
|
|
186
365
|
/** Runtime nominal check for values produced by an application-error descriptor. */
|
|
187
366
|
function isApplicationErrorValue(value) {
|
|
188
367
|
return typeof value === "object" && value !== null && value[applicationErrorBrand] === true;
|
|
189
368
|
}
|
|
190
369
|
function assertApplicationErrorCode(code) {
|
|
191
|
-
|
|
370
|
+
assertErrorCode(code);
|
|
192
371
|
if (code >= -32768 && code <= -32e3 || code === -32800) throw new Error(`Application error code ${code} is reserved by JSON-RPC or LinkRPC.`);
|
|
193
372
|
}
|
|
373
|
+
function assertErrorCode(code) {
|
|
374
|
+
if (!Number.isInteger(code) || code < -2147483648 || code > 2147483647) throw new Error(`Application error code ${code} must be a signed 32-bit integer.`);
|
|
375
|
+
}
|
|
194
376
|
var RequestType = class RequestType {
|
|
195
377
|
paramsSchema;
|
|
196
378
|
resultSchema;
|
|
@@ -210,10 +392,20 @@ var RequestType = class RequestType {
|
|
|
210
392
|
this.applicationErrors = applicationErrors;
|
|
211
393
|
const seen = /* @__PURE__ */ new Set();
|
|
212
394
|
for (const error of applicationErrors) {
|
|
395
|
+
if (applicationErrors.some((other) => other !== error && other.code === error.code && (other.bodySchema !== void 0 || error.bodySchema !== void 0))) throw new Error(`Raw error code ${error.code} cannot be shared.`);
|
|
396
|
+
if (error.bodySchema !== void 0) {
|
|
397
|
+
assertErrorCode(error.code);
|
|
398
|
+
const key = `code:${error.code}`;
|
|
399
|
+
if (seen.has(key)) throw new Error(`Duplicate application error ${key}.`);
|
|
400
|
+
seen.add(key);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
213
403
|
assertApplicationErrorCode(error.code);
|
|
214
404
|
if (typeof error.message !== "string") throw new Error("Application error message must be a string.");
|
|
215
|
-
if (
|
|
216
|
-
|
|
405
|
+
if (error.type !== void 0 && (typeof error.type !== "string" || error.type.length === 0)) throw new Error("Application error type must be a nonempty string.");
|
|
406
|
+
const key = error.type === void 0 ? `code:${error.code}` : `type:${error.type}`;
|
|
407
|
+
if (seen.has(key)) throw new Error(`Duplicate application error ${key}.`);
|
|
408
|
+
seen.add(key);
|
|
217
409
|
}
|
|
218
410
|
this.applicationErrors = Object.freeze([...applicationErrors]);
|
|
219
411
|
this.errors = this.applicationErrors;
|
|
@@ -273,6 +465,14 @@ function notificationType(params, docs = {}) {
|
|
|
273
465
|
}
|
|
274
466
|
/** Convert a zod schema to our restricted SvcJsonSchema subset. */
|
|
275
467
|
function zodToSvcJsonSchema(schema, options) {
|
|
468
|
+
const source = schemaSources.get(schema);
|
|
469
|
+
if (source !== void 0) {
|
|
470
|
+
for (const [name, component] of Object.entries(source.components)) {
|
|
471
|
+
if (options === void 0) throw new Error("Imported schema references require a component destination");
|
|
472
|
+
addComponent(options.components, name, normalizeJsonSchema(component));
|
|
473
|
+
}
|
|
474
|
+
return normalizeJsonSchema(source.schema);
|
|
475
|
+
}
|
|
276
476
|
const type = schema._zod?.def?.type;
|
|
277
477
|
if (type === "void" || type === "undefined") return true;
|
|
278
478
|
return normalizeZodJsonSchema(toJSONSchema(schema), options);
|
|
@@ -602,6 +802,13 @@ function normalizeSchemaPositions(schema) {
|
|
|
602
802
|
const value = normalized[field];
|
|
603
803
|
if (value !== void 0) normalized[field] = normalizeJsonSchema(value);
|
|
604
804
|
}
|
|
805
|
+
if (method.errors !== void 0) normalized.errors = method.errors.map((error) => error.schema !== void 0 ? {
|
|
806
|
+
...error,
|
|
807
|
+
schema: normalizeJsonSchema(error.schema)
|
|
808
|
+
} : {
|
|
809
|
+
...error,
|
|
810
|
+
...error.data === void 0 ? {} : { data: normalizeJsonSchema(error.data) }
|
|
811
|
+
});
|
|
605
812
|
return [name, normalized];
|
|
606
813
|
}));
|
|
607
814
|
const componentSchemas = schema.components?.schemas;
|
|
@@ -757,6 +964,13 @@ function createSchemaToZod(components = {}) {
|
|
|
757
964
|
if (schema.additionalProperties === false) base = object.strict();
|
|
758
965
|
else if (schema.additionalProperties === true) base = object.loose();
|
|
759
966
|
else base = object.catchall(compile(schema.additionalProperties));
|
|
967
|
+
base = base.superRefine((value, context) => {
|
|
968
|
+
for (const name of required) if (typeof value === "object" && value !== null && !Object.hasOwn(value, name)) context.addIssue({
|
|
969
|
+
code: "custom",
|
|
970
|
+
path: [name],
|
|
971
|
+
message: "Required property is missing"
|
|
972
|
+
});
|
|
973
|
+
});
|
|
760
974
|
break;
|
|
761
975
|
}
|
|
762
976
|
}
|
|
@@ -764,7 +978,12 @@ function createSchemaToZod(components = {}) {
|
|
|
764
978
|
};
|
|
765
979
|
return { toZod(schema) {
|
|
766
980
|
assertSchemaReferences([schema], components);
|
|
767
|
-
|
|
981
|
+
const result = compile(schema);
|
|
982
|
+
schemaSources.set(result, {
|
|
983
|
+
schema,
|
|
984
|
+
components
|
|
985
|
+
});
|
|
986
|
+
return result;
|
|
768
987
|
} };
|
|
769
988
|
}
|
|
770
989
|
function isAnnotationOnlySchema(schema) {
|
|
@@ -814,6 +1033,34 @@ function stringSchema(format) {
|
|
|
814
1033
|
}
|
|
815
1034
|
}
|
|
816
1035
|
//#endregion
|
|
1036
|
+
//#region src/schema/validateInterfaceErrors.ts
|
|
1037
|
+
function validateInterfaceErrors(schema) {
|
|
1038
|
+
if (schema === void 0) return;
|
|
1039
|
+
for (const [methodName, method] of Object.entries(schema.methods)) {
|
|
1040
|
+
if (method.result === void 0 && method.errors !== void 0) throw new Error(`Notification "${methodName}" cannot declare application errors.`);
|
|
1041
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1042
|
+
const errors = method.errors ?? [];
|
|
1043
|
+
for (const error of errors) {
|
|
1044
|
+
if (!Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647) throw new Error(`Application error code ${error.code} on "${methodName}" must be a signed 32-bit integer.`);
|
|
1045
|
+
if (errors.some((other) => other !== error && other.code === error.code && (other.schema !== void 0 || error.schema !== void 0))) throw new Error(`Raw error code ${error.code} on "${methodName}" cannot be shared.`);
|
|
1046
|
+
if (error.schema !== void 0) {
|
|
1047
|
+
if ([
|
|
1048
|
+
"message",
|
|
1049
|
+
"type",
|
|
1050
|
+
"data"
|
|
1051
|
+
].some((key) => Object.hasOwn(error, key))) throw new Error(`Raw error ${error.code} on "${methodName}" must contain only code and schema.`);
|
|
1052
|
+
} else {
|
|
1053
|
+
if (typeof error.message !== "string") throw new Error(`Application error message on "${methodName}" must be a string.`);
|
|
1054
|
+
if (error.code >= -32768 && error.code <= -32e3 || error.code === -32800) throw new Error(`Application error code ${error.code} on "${methodName}" is reserved by JSON-RPC or LinkRPC.`);
|
|
1055
|
+
if (error.type !== void 0 && (typeof error.type !== "string" || error.type.length === 0)) throw new Error(`Application error type on "${methodName}" must be a nonempty string.`);
|
|
1056
|
+
}
|
|
1057
|
+
const key = error.type === void 0 ? `code:${error.code}` : `type:${error.type}`;
|
|
1058
|
+
if (seen.has(key)) throw new Error(`Duplicate application error ${key} on "${methodName}".`);
|
|
1059
|
+
seen.add(key);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
//#endregion
|
|
817
1064
|
//#region src/connection/interfaceDefinition.ts
|
|
818
1065
|
const interfaceDefinitionState = /* @__PURE__ */ new WeakMap();
|
|
819
1066
|
var InterfaceDefinition = class {
|
|
@@ -877,9 +1124,14 @@ function toMethodSchema(name, member, components) {
|
|
|
877
1124
|
if (member.clientStreamSchema !== void 0) m.clientStream = convertMemberSchema(name, "clientStream", member.clientStreamSchema, components);
|
|
878
1125
|
if (member.serverStreamSchema !== void 0) m.serverStream = convertMemberSchema(name, "serverStream", member.serverStreamSchema, components);
|
|
879
1126
|
if (member.applicationErrors.length > 0) m.errors = member.applicationErrors.map((error) => {
|
|
1127
|
+
if (error.bodySchema !== void 0) return {
|
|
1128
|
+
code: error.code,
|
|
1129
|
+
schema: convertMemberSchema(name, `error=${error.code}`, error.bodySchema, components)
|
|
1130
|
+
};
|
|
880
1131
|
const result = {
|
|
881
1132
|
code: error.code,
|
|
882
|
-
message: error.message
|
|
1133
|
+
message: error.message,
|
|
1134
|
+
...error.type === void 0 ? {} : { type: error.type }
|
|
883
1135
|
};
|
|
884
1136
|
if (error.dataSchema !== void 0) result.data = convertMemberSchema(name, `error=${error.code}`, error.dataSchema, components);
|
|
885
1137
|
return result;
|
|
@@ -895,20 +1147,6 @@ function toMethodSchema(name, member, components) {
|
|
|
895
1147
|
if (docs.annotations !== void 0) m.annotations = docs.annotations;
|
|
896
1148
|
return m;
|
|
897
1149
|
}
|
|
898
|
-
function validateInterfaceErrors(schema) {
|
|
899
|
-
if (schema === void 0) return;
|
|
900
|
-
for (const [methodName, method] of Object.entries(schema.methods)) {
|
|
901
|
-
if (method.result === void 0 && method.errors !== void 0) throw new Error(`Notification "${methodName}" cannot declare application errors.`);
|
|
902
|
-
const seen = /* @__PURE__ */ new Set();
|
|
903
|
-
for (const error of method.errors ?? []) {
|
|
904
|
-
if (typeof error.message !== "string") throw new Error(`Application error message on "${methodName}" must be a string.`);
|
|
905
|
-
if (!Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647) throw new Error(`Application error code ${error.code} on "${methodName}" must be a signed 32-bit integer.`);
|
|
906
|
-
if (error.code >= -32768 && error.code <= -32e3 || error.code === -32800) throw new Error(`Application error code ${error.code} on "${methodName}" is reserved by JSON-RPC or LinkRPC.`);
|
|
907
|
-
if (seen.has(error.code)) throw new Error(`Duplicate application error code ${error.code} on "${methodName}".`);
|
|
908
|
-
seen.add(error.code);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
1150
|
function convertMemberSchema(methodName, position, schema, components) {
|
|
913
1151
|
return zodToSvcJsonSchema(schema, {
|
|
914
1152
|
methodName,
|
|
@@ -946,21 +1184,29 @@ function defineInterface(info, members) {
|
|
|
946
1184
|
* streams use `z.any()` because no zod source is available — call-site
|
|
947
1185
|
* validation is therefore a no-op and the caller is responsible for
|
|
948
1186
|
* shape-checking inputs and outputs. Declared application-error payloads
|
|
949
|
-
* are materialized and validated
|
|
950
|
-
*
|
|
1187
|
+
* are materialized and validated after code selection. A known code whose
|
|
1188
|
+
* body fails all declared branches is a noncompliant-server failure.
|
|
951
1189
|
*
|
|
952
1190
|
* Methods with no `result` descriptor become notifications; methods with
|
|
953
1191
|
* `clientStream` / `serverStream` get pass-through stream payload
|
|
954
1192
|
* schemas attached.
|
|
955
1193
|
*/
|
|
956
1194
|
function interfaceFromSchema(schema) {
|
|
1195
|
+
validateInterfaceErrors(schema);
|
|
957
1196
|
const members = {};
|
|
958
1197
|
for (const [name, method] of Object.entries(schema.methods)) {
|
|
959
1198
|
if (method.result === void 0) {
|
|
960
1199
|
members[name] = new NotificationType(any());
|
|
961
1200
|
continue;
|
|
962
1201
|
}
|
|
963
|
-
const base = new RequestType(any(), any(), void$1()).withErrors((method.errors ?? []).map((error) => error.
|
|
1202
|
+
const base = new RequestType(any(), any(), void$1()).withErrors((method.errors ?? []).map((error) => error.schema !== void 0 ? rpcError(error.code, schemaToZod(error.schema, schema.components?.schemas)) : error.type !== void 0 ? error.data === void 0 ? applicationError(error.type, {
|
|
1203
|
+
code: error.code,
|
|
1204
|
+
message: error.message
|
|
1205
|
+
}) : applicationError(error.type, {
|
|
1206
|
+
code: error.code,
|
|
1207
|
+
message: error.message,
|
|
1208
|
+
data: schemaToZod(error.data, schema.components?.schemas)
|
|
1209
|
+
}) : error.data === void 0 ? applicationError(error.code, error.message) : applicationError(error.code, error.message, schemaToZod(error.data, schema.components?.schemas))));
|
|
964
1210
|
members[name] = method.clientStream !== void 0 || method.serverStream !== void 0 ? base.withStream({
|
|
965
1211
|
client: method.clientStream !== void 0 ? any() : void 0,
|
|
966
1212
|
server: method.serverStream !== void 0 ? any() : void 0
|
|
@@ -972,6 +1218,109 @@ function interfaceFromSchema(schema) {
|
|
|
972
1218
|
return new InterfaceDefinition(info, members, { frozenSchema: schema });
|
|
973
1219
|
}
|
|
974
1220
|
//#endregion
|
|
1221
|
+
//#region src/crypto/cryptoProvider.ts
|
|
1222
|
+
const _b64UrlAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
1223
|
+
const _b64UrlReverse = (() => {
|
|
1224
|
+
const m = {};
|
|
1225
|
+
for (let i = 0; i < 64; i++) m[_b64UrlAlphabet[i]] = i;
|
|
1226
|
+
return m;
|
|
1227
|
+
})();
|
|
1228
|
+
function bytesToBase64Url(bytes) {
|
|
1229
|
+
let out = "";
|
|
1230
|
+
let i = 0;
|
|
1231
|
+
for (; i + 3 <= bytes.length; i += 3) {
|
|
1232
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
1233
|
+
out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63] + _b64UrlAlphabet[n & 63];
|
|
1234
|
+
}
|
|
1235
|
+
const rem = bytes.length - i;
|
|
1236
|
+
if (rem === 1) {
|
|
1237
|
+
const n = bytes[i] << 16;
|
|
1238
|
+
out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63];
|
|
1239
|
+
} else if (rem === 2) {
|
|
1240
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8;
|
|
1241
|
+
out += _b64UrlAlphabet[n >> 18 & 63] + _b64UrlAlphabet[n >> 12 & 63] + _b64UrlAlphabet[n >> 6 & 63];
|
|
1242
|
+
}
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
function base64UrlToBytes(s) {
|
|
1246
|
+
const len = s.length;
|
|
1247
|
+
const fullGroups = Math.floor(len / 4);
|
|
1248
|
+
const rem = len % 4;
|
|
1249
|
+
if (rem === 1) throw new Error("base64UrlToBytes: invalid length");
|
|
1250
|
+
const outLen = fullGroups * 3 + (rem === 0 ? 0 : rem - 1);
|
|
1251
|
+
const out = new Uint8Array(outLen);
|
|
1252
|
+
let oi = 0;
|
|
1253
|
+
let i = 0;
|
|
1254
|
+
for (let g = 0; g < fullGroups; g++, i += 4) {
|
|
1255
|
+
const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6 | _lookup(s, i + 3);
|
|
1256
|
+
out[oi++] = n >> 16 & 255;
|
|
1257
|
+
out[oi++] = n >> 8 & 255;
|
|
1258
|
+
out[oi++] = n & 255;
|
|
1259
|
+
}
|
|
1260
|
+
if (rem === 2) {
|
|
1261
|
+
const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12;
|
|
1262
|
+
out[oi++] = n >> 16 & 255;
|
|
1263
|
+
} else if (rem === 3) {
|
|
1264
|
+
const n = _lookup(s, i) << 18 | _lookup(s, i + 1) << 12 | _lookup(s, i + 2) << 6;
|
|
1265
|
+
out[oi++] = n >> 16 & 255;
|
|
1266
|
+
out[oi++] = n >> 8 & 255;
|
|
1267
|
+
}
|
|
1268
|
+
return out;
|
|
1269
|
+
}
|
|
1270
|
+
function _lookup(s, i) {
|
|
1271
|
+
const v = _b64UrlReverse[s[i]];
|
|
1272
|
+
if (v === void 0) throw new Error(`base64UrlToBytes: invalid char at ${i}`);
|
|
1273
|
+
return v;
|
|
1274
|
+
}
|
|
1275
|
+
/** Role-tag prefix on a {@link KeyId}'s inline-key form. */
|
|
1276
|
+
const KEY_ID_PREFIX = "key:";
|
|
1277
|
+
/** Role-tag prefix that wraps a genesis {@link KeyId} into a {@link PrincipalId}. */
|
|
1278
|
+
const PRINCIPAL_PREFIX = "id:";
|
|
1279
|
+
/** The inline {@link KeyId} for a raw public key: `"key:" + b64url(pk)`. */
|
|
1280
|
+
function keyIdForPublicKey(pk) {
|
|
1281
|
+
return KEY_ID_PREFIX + bytesToBase64Url(pk);
|
|
1282
|
+
}
|
|
1283
|
+
/** The perpetual {@link PrincipalId} for a raw public key: `"id:key:" + b64url(pk)`. */
|
|
1284
|
+
function principalForPublicKey(pk) {
|
|
1285
|
+
return "id:" + keyIdForPublicKey(pk);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* The genesis {@link KeyId} embedded in a perpetual {@link PrincipalId}
|
|
1289
|
+
* (`"id:" + keyId`). Throws if `principal` is not a well-formed `id:` name.
|
|
1290
|
+
*/
|
|
1291
|
+
function keyIdForPrincipal(principal) {
|
|
1292
|
+
if (!principal.startsWith("id:")) throw new Error(`malformed principal (missing "id:" prefix): ${principal}`);
|
|
1293
|
+
return principal.slice(3);
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* The raw public key carried inline by a `"key:"` {@link KeyId}. Throws on a
|
|
1297
|
+
* missing prefix or malformed base64url.
|
|
1298
|
+
*/
|
|
1299
|
+
function publicKeyForKeyId(keyId) {
|
|
1300
|
+
if (!keyId.startsWith("key:")) throw new Error(`unsupported keyId (expected "${KEY_ID_PREFIX}" prefix): ${keyId}`);
|
|
1301
|
+
return base64UrlToBytes(keyId.slice(4));
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Resolve the verifying public key for `(principal, keyId)`, or `undefined`
|
|
1305
|
+
* to **reject** (fail closed). This is the single seam every verifier goes
|
|
1306
|
+
* through, replacing the old "the id *is* the key" decode.
|
|
1307
|
+
*
|
|
1308
|
+
* Phase 1 supports only inline genesis keys: the `keyId` must be a `"key:"`
|
|
1309
|
+
* form and the principal must be exactly `"id:" + keyId` (genesis
|
|
1310
|
+
* self-certification by string equality). `keydoc:`/rotation forms resolve
|
|
1311
|
+
* through `bindings`/`timeMs` later and are intentionally not handled here.
|
|
1312
|
+
*/
|
|
1313
|
+
function resolveSigningKey(args) {
|
|
1314
|
+
const { principal, keyId } = args;
|
|
1315
|
+
if (!keyId.startsWith("key:")) return;
|
|
1316
|
+
if ("id:" + keyId !== principal) return;
|
|
1317
|
+
try {
|
|
1318
|
+
return { publicKey: base64UrlToBytes(keyId.slice(4)) };
|
|
1319
|
+
} catch {
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
//#endregion
|
|
975
1324
|
//#region src/inspection/inspection.interfaces.ts
|
|
976
1325
|
const zTopologyPort = object({
|
|
977
1326
|
portId: string(),
|
|
@@ -1146,6 +1495,142 @@ const nodeInterface = defineInterface({
|
|
|
1146
1495
|
description: "Topology bootstrap: identify the node and port at this end of the direct connection."
|
|
1147
1496
|
}, { getNodeId: requestType(object({}), zNodeInfo) });
|
|
1148
1497
|
//#endregion
|
|
1498
|
+
//#region src/connection/streaming.ts
|
|
1499
|
+
/**
|
|
1500
|
+
* Direction a {@link STREAM_METHOD} message travels, relative to the
|
|
1501
|
+
* request it is correlated with. Made explicit on the wire so that a
|
|
1502
|
+
* middlebox (the hub) can *author* a stream message — e.g. inject a
|
|
1503
|
+
* cancel when a caller disconnects — without an inbound message whose
|
|
1504
|
+
* arrival link it could infer the direction from.
|
|
1505
|
+
*/
|
|
1506
|
+
const StreamDir = {
|
|
1507
|
+
/** callee → caller: progress, partial results. Routes like a response. */
|
|
1508
|
+
toCaller: "toCaller",
|
|
1509
|
+
/** caller → callee: input, cancellation, keepalive ping. */
|
|
1510
|
+
toCallee: "toCallee"
|
|
1511
|
+
};
|
|
1512
|
+
/**
|
|
1513
|
+
* Reserved control verbs. A control message carries no app `payload`; it
|
|
1514
|
+
* is interpreted by the runtime / hub itself, independently of the
|
|
1515
|
+
* originating method's stream schemas, so *any* request is cancellable
|
|
1516
|
+
* and keep-alive-able even when it declares no app stream.
|
|
1517
|
+
*/
|
|
1518
|
+
const StreamControlType = {
|
|
1519
|
+
/**
|
|
1520
|
+
* Ask the callee to abort the in-flight request. Always {@link
|
|
1521
|
+
* StreamDir.toCallee}. The callee surfaces this as an `AbortSignal`
|
|
1522
|
+
* and is expected to settle the request (typically with a
|
|
1523
|
+
* `cancelled` error response).
|
|
1524
|
+
*/
|
|
1525
|
+
cancel: "cancel",
|
|
1526
|
+
/**
|
|
1527
|
+
* Keepalive / liveness probe. Resets the hub's per-request idle timer
|
|
1528
|
+
* so a long-running call is not reaped, and lets either side actively
|
|
1529
|
+
* probe the peer. Carries a `nonce` the peer echoes in its {@link
|
|
1530
|
+
* StreamControlType.pong}. May travel in either direction. Emitted
|
|
1531
|
+
* automatically (without awaiting a pong) by the channel for streaming
|
|
1532
|
+
* calls; see {@link STREAM_METHOD}.
|
|
1533
|
+
*/
|
|
1534
|
+
ping: "ping",
|
|
1535
|
+
/**
|
|
1536
|
+
* Reply to a {@link StreamControlType.ping}, echoing the ping's
|
|
1537
|
+
* `nonce` so the prober can correlate it. Travels opposite the ping.
|
|
1538
|
+
* Carries no app effect — purely a liveness acknowledgement.
|
|
1539
|
+
*/
|
|
1540
|
+
pong: "pong"
|
|
1541
|
+
};
|
|
1542
|
+
/**
|
|
1543
|
+
* Well-known `control.reason` strings. The set is **open** — any string
|
|
1544
|
+
* is valid on the wire; these are the reasons linkrpc itself emits.
|
|
1545
|
+
*/
|
|
1546
|
+
const StreamControlReason = {
|
|
1547
|
+
/**
|
|
1548
|
+
* The caller's transport dropped while the request was in flight; the
|
|
1549
|
+
* hub cancels the callee's now-orphaned work.
|
|
1550
|
+
*/
|
|
1551
|
+
clientDisconnected: "clientDisconnected",
|
|
1552
|
+
/**
|
|
1553
|
+
* The request exceeded the hub's idle timeout with no stream activity
|
|
1554
|
+
* (no ping, no stream message). See {@link STREAM_METHOD} docs.
|
|
1555
|
+
*/
|
|
1556
|
+
idleTimeout: "idleTimeout"
|
|
1557
|
+
};
|
|
1558
|
+
/**
|
|
1559
|
+
* Streaming sub-protocol. Long-running requests can emit additional
|
|
1560
|
+
* notifications correlated with the original call in either direction
|
|
1561
|
+
* (callee → caller progress, caller → callee input / cancellation /
|
|
1562
|
+
* keepalive).
|
|
1563
|
+
*
|
|
1564
|
+
* Wire form: a JSON-RPC notification whose method is
|
|
1565
|
+
* `${streamInterface.info.id}::send` and whose params match
|
|
1566
|
+
* `streamInterface.members.send.paramsSchema`. The hub routes purely on
|
|
1567
|
+
* `requestId` (following the originating request's established path, like
|
|
1568
|
+
* a response); the originating request's capability authorization covers
|
|
1569
|
+
* its full stream lifetime, so stream notifications carry no method /
|
|
1570
|
+
* interface namespace of their own. App `payload` contents are typed per
|
|
1571
|
+
* request via the originating method's
|
|
1572
|
+
* `RequestType.clientStreamSchema` / `serverStreamSchema`; reserved
|
|
1573
|
+
* `control` messages are schema-independent.
|
|
1574
|
+
*
|
|
1575
|
+
* Idle timeout: a request that never streams (no `control` ping, no
|
|
1576
|
+
* stream message) is subject to the hub's per-request idle timeout
|
|
1577
|
+
* (default 30 minutes) — at which point it is cancelled and its caller
|
|
1578
|
+
* gets a `requestTimeout` error. This bounds the hub's pending-request
|
|
1579
|
+
* table, which is also what protects it from a slow-loris / DDoS that
|
|
1580
|
+
* opens requests and never completes them. Streaming-enabled calls keep
|
|
1581
|
+
* themselves alive by emitting a periodic {@link StreamControlType.ping}.
|
|
1582
|
+
*
|
|
1583
|
+
* The interface is declarative: nobody calls
|
|
1584
|
+
* `LinkRpcConnection.register(streamInterface, ...)`. The channel and the
|
|
1585
|
+
* hub intercept `$stream::send` directly. The definition exists so
|
|
1586
|
+
* reflection / consent UIs can describe the wire shape and so all wire
|
|
1587
|
+
* constants derive from one source.
|
|
1588
|
+
*/
|
|
1589
|
+
const streamInterface = defineInterface({
|
|
1590
|
+
id: "$stream",
|
|
1591
|
+
description: "Reserved sub-protocol for in-flight stream messages correlated to a request by `requestId`."
|
|
1592
|
+
}, { send: notificationType(object({
|
|
1593
|
+
/**
|
|
1594
|
+
* Id of the in-flight request the message belongs to.
|
|
1595
|
+
* The hub rewrites this across the forwarding boundary
|
|
1596
|
+
* in the same way it rewrites response ids.
|
|
1597
|
+
*/
|
|
1598
|
+
requestId: union([number(), string()]),
|
|
1599
|
+
/**
|
|
1600
|
+
* Which way this message travels relative to the request.
|
|
1601
|
+
* Explicit so the hub can author messages (e.g. cancel on
|
|
1602
|
+
* disconnect) without inferring direction from arrival.
|
|
1603
|
+
*/
|
|
1604
|
+
dir: enum$1(["toCaller", "toCallee"]),
|
|
1605
|
+
/**
|
|
1606
|
+
* Reserved control verb (cancel / ping / pong). Mutually
|
|
1607
|
+
* exclusive with `payload` in practice: a control message
|
|
1608
|
+
* is interpreted by the runtime, not the application.
|
|
1609
|
+
*/
|
|
1610
|
+
control: optional(object({
|
|
1611
|
+
type: enum$1([
|
|
1612
|
+
"cancel",
|
|
1613
|
+
"ping",
|
|
1614
|
+
"pong"
|
|
1615
|
+
]),
|
|
1616
|
+
/** Open-set human/diagnostic reason; see {@link StreamControlReason}. */
|
|
1617
|
+
reason: optional(string()),
|
|
1618
|
+
/**
|
|
1619
|
+
* Correlation token. A `ping` carries a fresh nonce;
|
|
1620
|
+
* the matching `pong` echoes it back so the prober
|
|
1621
|
+
* can resolve the right outstanding probe.
|
|
1622
|
+
*/
|
|
1623
|
+
nonce: optional(string())
|
|
1624
|
+
})),
|
|
1625
|
+
/** Opaque app payload — typed per call by the request's stream schema. */
|
|
1626
|
+
payload: optional(unknown())
|
|
1627
|
+
}), { description: "Emit a stream message tied to the request `requestId`." }) });
|
|
1628
|
+
/**
|
|
1629
|
+
* Full wire method name for stream notifications. Pulled from the
|
|
1630
|
+
* interface object so we have a single source of truth.
|
|
1631
|
+
*/
|
|
1632
|
+
const STREAM_METHOD = `${streamInterface.info.id}::send`;
|
|
1633
|
+
//#endregion
|
|
1149
1634
|
//#region src/inspection/trafficFlowFilter.ts
|
|
1150
1635
|
var TrafficFlowFilter = class {
|
|
1151
1636
|
_focused;
|
|
@@ -1196,24 +1681,37 @@ var TrafficWatchFlowTracker = class {
|
|
|
1196
1681
|
this._active.clear();
|
|
1197
1682
|
this._unclaimed.clear();
|
|
1198
1683
|
}
|
|
1684
|
+
forgetPort(portId) {
|
|
1685
|
+
for (const flow of this._active) if (flow.hasPort(portId)) this._removeFlow(flow);
|
|
1686
|
+
}
|
|
1199
1687
|
};
|
|
1200
1688
|
var CorrelatedFlow = class {
|
|
1201
1689
|
_endpoints = /* @__PURE__ */ new Set();
|
|
1690
|
+
_ports = /* @__PURE__ */ new Set();
|
|
1202
1691
|
_completionEndpoint;
|
|
1203
1692
|
constructor(endpoints, completionEndpoint = endpoints[0]) {
|
|
1204
|
-
for (const endpoint of endpoints)
|
|
1693
|
+
for (const endpoint of endpoints) {
|
|
1694
|
+
this._endpoints.add(endpointKey(endpoint));
|
|
1695
|
+
this._ports.add(endpoint.portId);
|
|
1696
|
+
}
|
|
1205
1697
|
this._completionEndpoint = completionEndpoint === void 0 ? void 0 : endpointKey(completionEndpoint);
|
|
1206
1698
|
}
|
|
1207
1699
|
matches(transit) {
|
|
1208
1700
|
return requestRefsOf(transit).some((endpoint) => this._endpoints.has(endpointKey(endpoint)));
|
|
1209
1701
|
}
|
|
1702
|
+
hasPort(portId) {
|
|
1703
|
+
return this._ports.has(portId);
|
|
1704
|
+
}
|
|
1210
1705
|
accept(transit) {
|
|
1211
1706
|
const endpoints = requestRefsOf(transit);
|
|
1212
1707
|
if (!this.matches(transit)) return {
|
|
1213
1708
|
matches: false,
|
|
1214
1709
|
completed: false
|
|
1215
1710
|
};
|
|
1216
|
-
for (const endpoint of endpoints)
|
|
1711
|
+
for (const endpoint of endpoints) {
|
|
1712
|
+
this._endpoints.add(endpointKey(endpoint));
|
|
1713
|
+
this._ports.add(endpoint.portId);
|
|
1714
|
+
}
|
|
1217
1715
|
const completed = transit.kind === "response" && (isTerminal(transit) || this._completionEndpoint !== void 0 && endpoints.some((endpoint) => endpointKey(endpoint) === this._completionEndpoint));
|
|
1218
1716
|
if (completed) this._endpoints.clear();
|
|
1219
1717
|
return {
|
|
@@ -1382,6 +1880,490 @@ function capPayload(value, maxBytes) {
|
|
|
1382
1880
|
return preview;
|
|
1383
1881
|
}
|
|
1384
1882
|
//#endregion
|
|
1385
|
-
|
|
1883
|
+
//#region src/inspection/endpointTrafficInspector.ts
|
|
1884
|
+
const REQUEST_CORRELATION_RETENTION_MS = 18e5;
|
|
1885
|
+
const MAX_REQUEST_CORRELATIONS = 4096;
|
|
1886
|
+
var EndpointTrafficInspector = class {
|
|
1887
|
+
_nodeId;
|
|
1888
|
+
_portId;
|
|
1889
|
+
_emit;
|
|
1890
|
+
_isObserved;
|
|
1891
|
+
_outboundRequests = /* @__PURE__ */ new Map();
|
|
1892
|
+
_inboundRequests = /* @__PURE__ */ new Map();
|
|
1893
|
+
observe = (direction, message) => {
|
|
1894
|
+
this._onMessage(direction, message);
|
|
1895
|
+
};
|
|
1896
|
+
constructor(_nodeId, _portId, _emit, _isObserved) {
|
|
1897
|
+
this._nodeId = _nodeId;
|
|
1898
|
+
this._portId = _portId;
|
|
1899
|
+
this._emit = _emit;
|
|
1900
|
+
this._isObserved = _isObserved;
|
|
1901
|
+
}
|
|
1902
|
+
dispose() {
|
|
1903
|
+
this._outboundRequests.clear();
|
|
1904
|
+
this._inboundRequests.clear();
|
|
1905
|
+
}
|
|
1906
|
+
_onMessage(direction, message) {
|
|
1907
|
+
const now = Date.now();
|
|
1908
|
+
this._pruneRequestCorrelations(now);
|
|
1909
|
+
const publiclyObserved = this._isObserved();
|
|
1910
|
+
if (!publiclyObserved) this.dispose();
|
|
1911
|
+
let transit;
|
|
1912
|
+
const endpoint = this._endpoint("id" in message ? message.id ?? void 0 : void 0);
|
|
1913
|
+
const base = {
|
|
1914
|
+
type: "transit",
|
|
1915
|
+
timeMs: now,
|
|
1916
|
+
nodeId: this._nodeId,
|
|
1917
|
+
...direction === "inbound" ? { in: endpoint } : { out: endpoint },
|
|
1918
|
+
disposition: direction === "inbound" ? "consumed" : "forwarded"
|
|
1919
|
+
};
|
|
1920
|
+
if (isRequest(message)) {
|
|
1921
|
+
const correlation = {
|
|
1922
|
+
method: message.method,
|
|
1923
|
+
timeMs: now
|
|
1924
|
+
};
|
|
1925
|
+
if (publiclyObserved) {
|
|
1926
|
+
const map = this._requestMap(direction);
|
|
1927
|
+
const key = requestKey(message.id);
|
|
1928
|
+
map.delete(key);
|
|
1929
|
+
map.set(key, correlation);
|
|
1930
|
+
}
|
|
1931
|
+
transit = {
|
|
1932
|
+
...base,
|
|
1933
|
+
kind: "request",
|
|
1934
|
+
method: message.method,
|
|
1935
|
+
params: message.params
|
|
1936
|
+
};
|
|
1937
|
+
} else if (isResponse(message)) {
|
|
1938
|
+
const responseMap = direction === "inbound" ? this._outboundRequests : this._inboundRequests;
|
|
1939
|
+
const key = requestKey(message.id);
|
|
1940
|
+
const correlation = responseMap.get(key);
|
|
1941
|
+
responseMap.delete(key);
|
|
1942
|
+
transit = {
|
|
1943
|
+
...base,
|
|
1944
|
+
kind: "response",
|
|
1945
|
+
method: correlation?.method,
|
|
1946
|
+
result: "result" in message ? message.result : void 0,
|
|
1947
|
+
error: "error" in message ? message.error : void 0
|
|
1948
|
+
};
|
|
1949
|
+
} else if (isNotification(message) && message.method === STREAM_METHOD) {
|
|
1950
|
+
const params = message.params;
|
|
1951
|
+
const streamEndpoint = this._endpoint(params?.requestId);
|
|
1952
|
+
transit = {
|
|
1953
|
+
...base,
|
|
1954
|
+
...direction === "inbound" ? { in: streamEndpoint } : { out: streamEndpoint },
|
|
1955
|
+
kind: "stream",
|
|
1956
|
+
method: STREAM_METHOD,
|
|
1957
|
+
params: message.params
|
|
1958
|
+
};
|
|
1959
|
+
} else transit = {
|
|
1960
|
+
...base,
|
|
1961
|
+
kind: "notification",
|
|
1962
|
+
method: message.method,
|
|
1963
|
+
params: message.params
|
|
1964
|
+
};
|
|
1965
|
+
function requestKey(requestId) {
|
|
1966
|
+
return JSON.stringify([typeof requestId, requestId]);
|
|
1967
|
+
}
|
|
1968
|
+
this._emit(transit);
|
|
1969
|
+
}
|
|
1970
|
+
_requestMap(direction) {
|
|
1971
|
+
return direction === "outbound" ? this._outboundRequests : this._inboundRequests;
|
|
1972
|
+
}
|
|
1973
|
+
_endpoint(requestId) {
|
|
1974
|
+
return {
|
|
1975
|
+
edgeId: this._portId,
|
|
1976
|
+
portId: this._portId,
|
|
1977
|
+
...requestId !== void 0 ? { requestId } : {}
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
_pruneRequestCorrelations(timeMs) {
|
|
1981
|
+
this._pruneRequestMap(this._outboundRequests, timeMs);
|
|
1982
|
+
this._pruneRequestMap(this._inboundRequests, timeMs);
|
|
1983
|
+
}
|
|
1984
|
+
_pruneRequestMap(map, timeMs) {
|
|
1985
|
+
while (map.size !== 0) {
|
|
1986
|
+
const first = map.entries().next().value;
|
|
1987
|
+
if (first === void 0 || map.size <= MAX_REQUEST_CORRELATIONS && timeMs - first[1].timeMs <= REQUEST_CORRELATION_RETENTION_MS) return;
|
|
1988
|
+
map.delete(first[0]);
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
//#endregion
|
|
1993
|
+
//#region src/inspection/connectionInspectionSource.ts
|
|
1994
|
+
var ConnectionInspectionSource = class {
|
|
1995
|
+
_connection;
|
|
1996
|
+
_nodeId;
|
|
1997
|
+
_options;
|
|
1998
|
+
_isObserved;
|
|
1999
|
+
constructor(_connection, _nodeId, _options, _isObserved) {
|
|
2000
|
+
this._connection = _connection;
|
|
2001
|
+
this._nodeId = _nodeId;
|
|
2002
|
+
this._options = _options;
|
|
2003
|
+
this._isObserved = _isObserved;
|
|
2004
|
+
}
|
|
2005
|
+
snapshotTopology() {
|
|
2006
|
+
const { portId, label, peer, transport } = this._options;
|
|
2007
|
+
const nodes = [{
|
|
2008
|
+
nodeId: this._nodeId,
|
|
2009
|
+
ports: [{
|
|
2010
|
+
portId,
|
|
2011
|
+
...label === void 0 ? {} : { label }
|
|
2012
|
+
}]
|
|
2013
|
+
}];
|
|
2014
|
+
const links = [];
|
|
2015
|
+
if (peer !== void 0) {
|
|
2016
|
+
nodes.push({
|
|
2017
|
+
nodeId: peer.nodeId,
|
|
2018
|
+
kind: "endpoint",
|
|
2019
|
+
...peer.label === void 0 ? {} : { label: peer.label },
|
|
2020
|
+
ports: [{ portId: peer.portId }]
|
|
2021
|
+
});
|
|
2022
|
+
links.push({
|
|
2023
|
+
from: {
|
|
2024
|
+
nodeId: this._nodeId,
|
|
2025
|
+
portId
|
|
2026
|
+
},
|
|
2027
|
+
to: {
|
|
2028
|
+
nodeId: peer.nodeId,
|
|
2029
|
+
portId: peer.portId
|
|
2030
|
+
},
|
|
2031
|
+
peerState: "identified",
|
|
2032
|
+
...transport === void 0 ? {} : { transport }
|
|
2033
|
+
});
|
|
2034
|
+
}
|
|
2035
|
+
return {
|
|
2036
|
+
nodes,
|
|
2037
|
+
links,
|
|
2038
|
+
routes: []
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
onTopologyChanged(_listener) {
|
|
2042
|
+
return { dispose: () => {} };
|
|
2043
|
+
}
|
|
2044
|
+
observeTraffic(listener) {
|
|
2045
|
+
const inspector = new EndpointTrafficInspector(this._nodeId, this._options.portId, listener, this._isObserved);
|
|
2046
|
+
const observation = this._connection.observeWireMessages(inspector.observe);
|
|
2047
|
+
return { dispose: () => {
|
|
2048
|
+
observation.dispose();
|
|
2049
|
+
inspector.dispose();
|
|
2050
|
+
} };
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
//#endregion
|
|
2054
|
+
//#region src/inspection/inspectionHost.ts
|
|
2055
|
+
/** A shared inspection scope, independent of both routing and its RPC connections. */
|
|
2056
|
+
var InspectionHost = class {
|
|
2057
|
+
_options;
|
|
2058
|
+
_sources = /* @__PURE__ */ new Map();
|
|
2059
|
+
_trackedConnections = /* @__PURE__ */ new Set();
|
|
2060
|
+
_bindings = /* @__PURE__ */ new Set();
|
|
2061
|
+
_subscribers = /* @__PURE__ */ new Set();
|
|
2062
|
+
_observers = /* @__PURE__ */ new Set();
|
|
2063
|
+
_topologyListeners = /* @__PURE__ */ new Set();
|
|
2064
|
+
_trafficSources = /* @__PURE__ */ new Set();
|
|
2065
|
+
_trafficWatches = new TrafficWatchFlowTracker();
|
|
2066
|
+
_disposed = false;
|
|
2067
|
+
nodeId;
|
|
2068
|
+
constructor(_options) {
|
|
2069
|
+
this._options = _options;
|
|
2070
|
+
this.nodeId = _options.nodeId;
|
|
2071
|
+
}
|
|
2072
|
+
get observerCount() {
|
|
2073
|
+
return this._subscribers.size + this._observers.size;
|
|
2074
|
+
}
|
|
2075
|
+
addSource(source) {
|
|
2076
|
+
this._assertActive();
|
|
2077
|
+
if (this._sources.has(source)) throw new Error("Inspection source is already registered");
|
|
2078
|
+
const readPorts = () => new Set(source.snapshotTopology().nodes.flatMap((node) => node.ports.map((port) => port.portId)));
|
|
2079
|
+
let ports = readPorts();
|
|
2080
|
+
const topology = source.onTopologyChanged(() => {
|
|
2081
|
+
const nextPorts = readPorts();
|
|
2082
|
+
const removed = [...ports].filter((port) => !nextPorts.has(port));
|
|
2083
|
+
ports = nextPorts;
|
|
2084
|
+
this._forgetUnusedPorts(removed);
|
|
2085
|
+
this._invalidateTopology();
|
|
2086
|
+
});
|
|
2087
|
+
let traffic;
|
|
2088
|
+
try {
|
|
2089
|
+
traffic = source.observeTraffic((event) => this.publishTransit(event));
|
|
2090
|
+
} catch (error) {
|
|
2091
|
+
topology.dispose();
|
|
2092
|
+
throw error;
|
|
2093
|
+
}
|
|
2094
|
+
const registration = { dispose: () => {
|
|
2095
|
+
if (this._sources.get(source) !== registration) return;
|
|
2096
|
+
this._sources.delete(source);
|
|
2097
|
+
topology.dispose();
|
|
2098
|
+
traffic.dispose();
|
|
2099
|
+
this._forgetUnusedPorts([...ports]);
|
|
2100
|
+
this._invalidateTopology();
|
|
2101
|
+
} };
|
|
2102
|
+
this._sources.set(source, registration);
|
|
2103
|
+
this._invalidateTopology();
|
|
2104
|
+
return registration;
|
|
2105
|
+
}
|
|
2106
|
+
trackConnection(connection, options) {
|
|
2107
|
+
this._assertActive();
|
|
2108
|
+
if (this.getGraph("").nodes.some((node) => node.ports.some((port) => port.portId === options.portId))) throw new Error(`Inspection port "${options.portId}" is already registered`);
|
|
2109
|
+
const source = new ConnectionInspectionSource(connection, this.nodeId, options, () => this.observerCount !== 0);
|
|
2110
|
+
const registration = this.addSource(source);
|
|
2111
|
+
let close;
|
|
2112
|
+
try {
|
|
2113
|
+
close = connection.onDidClose(() => result.dispose());
|
|
2114
|
+
} catch (error) {
|
|
2115
|
+
registration.dispose();
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
2118
|
+
const result = { dispose: () => {
|
|
2119
|
+
this._trackedConnections.delete(result);
|
|
2120
|
+
close.dispose();
|
|
2121
|
+
registration.dispose();
|
|
2122
|
+
} };
|
|
2123
|
+
this._trackedConnections.add(result);
|
|
2124
|
+
return result;
|
|
2125
|
+
}
|
|
2126
|
+
expose(connection, options) {
|
|
2127
|
+
this._assertActive();
|
|
2128
|
+
const portId = options.portId ?? createPortId();
|
|
2129
|
+
const binding = new InspectionBinding(portId);
|
|
2130
|
+
const registrations = [];
|
|
2131
|
+
this._bindings.add(binding);
|
|
2132
|
+
const registerOptions = { serviceId: options.serviceId };
|
|
2133
|
+
try {
|
|
2134
|
+
const controls = new EndpointTrafficInspector(this.nodeId, portId, (event) => this._trafficWatches.accept(event), () => false);
|
|
2135
|
+
registrations.push(connection.observeWireMessages(controls.observe));
|
|
2136
|
+
registrations.push({ dispose: () => controls.dispose() });
|
|
2137
|
+
registrations.push(connection.registerInspection(nodeInterface, { getNodeId: () => ({
|
|
2138
|
+
nodeId: this.nodeId,
|
|
2139
|
+
portId,
|
|
2140
|
+
...(options.descriptors ?? this._options.descriptors) === void 0 ? {} : { descriptors: [...options.descriptors ?? this._options.descriptors ?? []] }
|
|
2141
|
+
}) }, registerOptions));
|
|
2142
|
+
registrations.push(connection.registerInspection(topologyInterface, {
|
|
2143
|
+
getGraph: () => this.getGraph(options.serviceId, options.includeRoute === false ? void 0 : portId),
|
|
2144
|
+
watchGraph: (_params, _ctx, stream) => this._watchGraph(binding, stream)
|
|
2145
|
+
}, registerOptions));
|
|
2146
|
+
registrations.push(connection.registerInspection(trafficInterface, {
|
|
2147
|
+
watch: (params, _ctx, stream) => this._watchTraffic(binding, params, stream),
|
|
2148
|
+
watchWithPayloads: (params, _ctx, stream) => this._watchTraffic(binding, params, stream)
|
|
2149
|
+
}, registerOptions));
|
|
2150
|
+
} catch (error) {
|
|
2151
|
+
for (const registration of registrations.reverse()) registration.dispose();
|
|
2152
|
+
this._bindings.delete(binding);
|
|
2153
|
+
throw error;
|
|
2154
|
+
}
|
|
2155
|
+
binding.onDispose = () => {
|
|
2156
|
+
this._bindings.delete(binding);
|
|
2157
|
+
for (const registration of registrations.reverse()) registration.dispose();
|
|
2158
|
+
this._forgetUnusedPorts([portId]);
|
|
2159
|
+
this._invalidateTopology();
|
|
2160
|
+
};
|
|
2161
|
+
registrations.push(connection.onDidClose(() => binding.dispose()));
|
|
2162
|
+
this._invalidateTopology();
|
|
2163
|
+
return binding;
|
|
2164
|
+
}
|
|
2165
|
+
getGraph(observerServiceId, routePortId) {
|
|
2166
|
+
this._assertActive();
|
|
2167
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2168
|
+
const mergeNode = (node) => {
|
|
2169
|
+
const previous = nodes.get(node.nodeId);
|
|
2170
|
+
const ports = new Map(previous?.ports.map((port) => [port.portId, port]));
|
|
2171
|
+
for (const port of node.ports) ports.set(port.portId, {
|
|
2172
|
+
...ports.get(port.portId),
|
|
2173
|
+
...port
|
|
2174
|
+
});
|
|
2175
|
+
nodes.set(node.nodeId, {
|
|
2176
|
+
...previous,
|
|
2177
|
+
...node,
|
|
2178
|
+
ports: [...ports.values()]
|
|
2179
|
+
});
|
|
2180
|
+
};
|
|
2181
|
+
mergeNode({
|
|
2182
|
+
nodeId: this.nodeId,
|
|
2183
|
+
kind: this._options.kind ?? "endpoint",
|
|
2184
|
+
...this._options.label === void 0 ? {} : { label: this._options.label },
|
|
2185
|
+
...this._options.descriptors === void 0 ? {} : { descriptors: [...this._options.descriptors] },
|
|
2186
|
+
ports: []
|
|
2187
|
+
});
|
|
2188
|
+
const links = [];
|
|
2189
|
+
const routes = [];
|
|
2190
|
+
for (const source of this._sources.keys()) {
|
|
2191
|
+
const fragment = source.snapshotTopology();
|
|
2192
|
+
for (const node of fragment.nodes) mergeNode(node);
|
|
2193
|
+
links.push(...fragment.links);
|
|
2194
|
+
routes.push(...fragment.routes);
|
|
2195
|
+
}
|
|
2196
|
+
for (const binding of this._bindings) mergeNode({
|
|
2197
|
+
nodeId: this.nodeId,
|
|
2198
|
+
ports: [{ portId: binding.portId }]
|
|
2199
|
+
});
|
|
2200
|
+
if (routePortId !== void 0) routes.push({
|
|
2201
|
+
serviceId: observerServiceId,
|
|
2202
|
+
nodeId: this.nodeId,
|
|
2203
|
+
portId: routePortId,
|
|
2204
|
+
match: "exact"
|
|
2205
|
+
});
|
|
2206
|
+
return {
|
|
2207
|
+
observerServiceId,
|
|
2208
|
+
entryNodeId: this.nodeId,
|
|
2209
|
+
nodes: [...nodes.values()],
|
|
2210
|
+
links,
|
|
2211
|
+
routes
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
publishTransit(event) {
|
|
2215
|
+
if (this._disposed || this._trafficWatches.accept(event)) return;
|
|
2216
|
+
for (const observer of [...this._observers]) try {
|
|
2217
|
+
observer(event);
|
|
2218
|
+
} catch {}
|
|
2219
|
+
for (const subscriber of this._subscribers) subscriber.enqueue(event);
|
|
2220
|
+
}
|
|
2221
|
+
subscribe(options, send) {
|
|
2222
|
+
this._assertActive();
|
|
2223
|
+
if (options.trafficIgnoreKey !== void 0 && !this._trafficWatches.claim(options.trafficIgnoreKey)) throw new Error("Traffic watch request was not observed before subscription");
|
|
2224
|
+
const subscriber = new BoundedTrafficSubscription(options, send, () => {
|
|
2225
|
+
this._subscribers.delete(subscriber);
|
|
2226
|
+
this._stopUnusedTrafficSources();
|
|
2227
|
+
});
|
|
2228
|
+
this._subscribers.add(subscriber);
|
|
2229
|
+
try {
|
|
2230
|
+
this._startTrafficSources();
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
subscriber.dispose();
|
|
2233
|
+
throw error;
|
|
2234
|
+
}
|
|
2235
|
+
return subscriber;
|
|
2236
|
+
}
|
|
2237
|
+
observeTraffic(observer) {
|
|
2238
|
+
this._assertActive();
|
|
2239
|
+
this._observers.add(observer);
|
|
2240
|
+
const registration = { dispose: () => {
|
|
2241
|
+
this._observers.delete(observer);
|
|
2242
|
+
this._stopUnusedTrafficSources();
|
|
2243
|
+
} };
|
|
2244
|
+
try {
|
|
2245
|
+
this._startTrafficSources();
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
registration.dispose();
|
|
2248
|
+
throw error;
|
|
2249
|
+
}
|
|
2250
|
+
return registration;
|
|
2251
|
+
}
|
|
2252
|
+
addTrafficSource(source) {
|
|
2253
|
+
this._assertActive();
|
|
2254
|
+
const entry = { source };
|
|
2255
|
+
this._trafficSources.add(entry);
|
|
2256
|
+
try {
|
|
2257
|
+
this._startTrafficSources();
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
this._trafficSources.delete(entry);
|
|
2260
|
+
throw error;
|
|
2261
|
+
}
|
|
2262
|
+
return { dispose: () => {
|
|
2263
|
+
this._trafficSources.delete(entry);
|
|
2264
|
+
entry.observation?.dispose();
|
|
2265
|
+
entry.observation = void 0;
|
|
2266
|
+
} };
|
|
2267
|
+
}
|
|
2268
|
+
dispose() {
|
|
2269
|
+
if (this._disposed) return;
|
|
2270
|
+
this._disposed = true;
|
|
2271
|
+
for (const binding of [...this._bindings]) binding.dispose();
|
|
2272
|
+
for (const subscriber of [...this._subscribers]) subscriber.dispose();
|
|
2273
|
+
this._observers.clear();
|
|
2274
|
+
this._stopUnusedTrafficSources();
|
|
2275
|
+
this._trafficSources.clear();
|
|
2276
|
+
for (const connection of [...this._trackedConnections]) connection.dispose();
|
|
2277
|
+
for (const source of [...this._sources.values()]) source.dispose();
|
|
2278
|
+
this._topologyListeners.clear();
|
|
2279
|
+
this._trafficWatches.clear();
|
|
2280
|
+
}
|
|
2281
|
+
_startTrafficSources() {
|
|
2282
|
+
if (this.observerCount === 0) return;
|
|
2283
|
+
for (const entry of this._trafficSources) entry.observation ??= entry.source.observe((event) => this.publishTransit(event));
|
|
2284
|
+
}
|
|
2285
|
+
_stopUnusedTrafficSources() {
|
|
2286
|
+
if (this.observerCount !== 0) return;
|
|
2287
|
+
for (const entry of this._trafficSources) {
|
|
2288
|
+
entry.observation?.dispose();
|
|
2289
|
+
entry.observation = void 0;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
_invalidateTopology() {
|
|
2293
|
+
if (this._disposed) return;
|
|
2294
|
+
for (const listener of this._topologyListeners) listener();
|
|
2295
|
+
}
|
|
2296
|
+
_forgetUnusedPorts(portIds) {
|
|
2297
|
+
if (this._disposed) return;
|
|
2298
|
+
const remaining = new Set(this.getGraph("").nodes.flatMap((node) => node.ports.map((port) => port.portId)));
|
|
2299
|
+
for (const portId of portIds) if (!remaining.has(portId)) this._trafficWatches.forgetPort(portId);
|
|
2300
|
+
}
|
|
2301
|
+
_watchGraph(binding, stream) {
|
|
2302
|
+
return new Promise((resolve, reject) => {
|
|
2303
|
+
let pending = false;
|
|
2304
|
+
let active = true;
|
|
2305
|
+
const finish = () => {
|
|
2306
|
+
active = false;
|
|
2307
|
+
this._topologyListeners.delete(invalidate);
|
|
2308
|
+
stream.signal.removeEventListener("abort", finish);
|
|
2309
|
+
binding.abort.signal.removeEventListener("abort", finish);
|
|
2310
|
+
resolve({});
|
|
2311
|
+
};
|
|
2312
|
+
const invalidate = () => {
|
|
2313
|
+
if (pending || !active) return;
|
|
2314
|
+
pending = true;
|
|
2315
|
+
queueMicrotask(() => {
|
|
2316
|
+
pending = false;
|
|
2317
|
+
if (!active) return;
|
|
2318
|
+
stream.send({}).catch((error) => {
|
|
2319
|
+
reject(error);
|
|
2320
|
+
finish();
|
|
2321
|
+
});
|
|
2322
|
+
});
|
|
2323
|
+
};
|
|
2324
|
+
this._topologyListeners.add(invalidate);
|
|
2325
|
+
stream.signal.addEventListener("abort", finish, { once: true });
|
|
2326
|
+
binding.abort.signal.addEventListener("abort", finish, { once: true });
|
|
2327
|
+
if (stream.signal.aborted || binding.abort.signal.aborted) finish();
|
|
2328
|
+
});
|
|
2329
|
+
}
|
|
2330
|
+
async _watchTraffic(binding, options, stream) {
|
|
2331
|
+
const subscription = this.subscribe(options, (event) => stream.send(event));
|
|
2332
|
+
const dispose = () => subscription.dispose();
|
|
2333
|
+
stream.signal.addEventListener("abort", dispose, { once: true });
|
|
2334
|
+
binding.abort.signal.addEventListener("abort", dispose, { once: true });
|
|
2335
|
+
if (stream.signal.aborted || binding.abort.signal.aborted) dispose();
|
|
2336
|
+
try {
|
|
2337
|
+
return await subscription.closed;
|
|
2338
|
+
} finally {
|
|
2339
|
+
stream.signal.removeEventListener("abort", dispose);
|
|
2340
|
+
binding.abort.signal.removeEventListener("abort", dispose);
|
|
2341
|
+
subscription.dispose();
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
_assertActive() {
|
|
2345
|
+
if (this._disposed) throw new Error("Inspection host is disposed");
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
var InspectionBinding = class {
|
|
2349
|
+
portId;
|
|
2350
|
+
abort = new AbortController();
|
|
2351
|
+
onDispose = () => {};
|
|
2352
|
+
constructor(portId) {
|
|
2353
|
+
this.portId = portId;
|
|
2354
|
+
}
|
|
2355
|
+
dispose() {
|
|
2356
|
+
if (this.abort.signal.aborted) return;
|
|
2357
|
+
this.abort.abort();
|
|
2358
|
+
this.onDispose();
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
function createPortId() {
|
|
2362
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
2363
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
2364
|
+
return `port:${bytesToBase64Url(bytes)}`;
|
|
2365
|
+
}
|
|
2366
|
+
//#endregion
|
|
2367
|
+
export { RequestType as $, PRINCIPAL_PREFIX as A, interfaceFromSchema as B, zTopologyTransportInfo as C, zTrafficTransitEvent as D, zTrafficTransitEndpoint as E, principalForPublicKey as F, componentSchemaName as G, createSchemaToZod as H, publicKeyForKeyId as I, computeInterfaceHash as J, componentSchemaRef as K, resolveSigningKey as L, bytesToBase64Url as M, keyIdForPrincipal as N, zTrafficWatchResult as O, keyIdForPublicKey as P, NotificationType as Q, InterfaceDefinition as R, zTopologyTransportEndpoint as S, zTrafficOverflowEvent as T, schemaToZod as U, validateInterfaceErrors as V, assertSchemaReferences as W, jcsCanonicalize as X, sha256 as Y, jcsCanonicalizeBytes as Z, zTopologyGraph as _, TrafficWatchFlowTracker as a, zodToSvcJsonSchema as at, zTopologyNode as b, StreamControlType as c, jsonPointerSegment as ct, nodeInterface as d, isNotification as dt, applicationError as et, topologyInterface as f, isRequest as ft, zRouteClaim as g, normalizeJsonSchema as gt, zParticipantDescriptorSource as h, isRpcFailure as ht, TrafficFlowFilter as i, rpcError as it, base64UrlToBytes as j, KEY_ID_PREFIX as k, StreamDir as l, brandApplicationError as lt, zParticipantDescriptor as m, RpcFailure as mt, ConnectionInspectionSource as n, notificationType as nt, STREAM_METHOD as o, jsonIssues as ot, trafficInterface as p, isResponse as pt, EXTENSION_PREFIX as q, BoundedTrafficSubscription as r, requestType as rt, StreamControlReason as s, parseDeclaredErrorBody as st, InspectionHost as t, isApplicationErrorValue as tt, streamInterface as u, ErrorCode as ut, zTopologyLink as v, zTrafficEvent as w, zTopologyPort as x, zTopologyLinkEndpoint as y, defineInterface as z };
|
|
1386
2368
|
|
|
1387
|
-
//# sourceMappingURL=
|
|
2369
|
+
//# sourceMappingURL=inspectionHost-BIkLKvr1.js.map
|