@apifuse/provider-sdk 2.2.0-beta.59 → 2.2.0-beta.61
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/AUTHORING.md +33 -16
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +7 -8
- package/bin/apifuse-pack-types.ts +22 -0
- package/bin/apifuse-perf.ts +45 -10
- package/bin/apifuse-record.ts +6 -8
- package/bin/apifuse-submit-check.ts +2 -2
- package/dist/config/loader.js +13 -12
- package/dist/define.d.ts +3 -2
- package/dist/define.js +1 -1
- package/dist/dev.js +3 -1
- package/dist/engine.d.ts +7 -6
- package/dist/engine.js +6 -6
- package/dist/error-resolution.js +21 -4
- package/dist/errors.d.ts +3 -0
- package/dist/errors.js +9 -0
- package/dist/handle-meta.d.ts +39 -0
- package/dist/handle-meta.js +29 -0
- package/dist/handle.d.ts +187 -0
- package/dist/handle.js +254 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +388 -62
- package/dist/provider.d.ts +4 -4
- package/dist/provider.js +3 -3
- package/dist/runtime/browser.js +2 -1
- package/dist/runtime/cache.js +3 -2
- package/dist/runtime/diagnostic-env-values.d.ts +5 -0
- package/dist/runtime/diagnostic-env-values.js +57 -0
- package/dist/runtime/diagnostic-env.d.ts +11 -0
- package/dist/runtime/diagnostic-env.js +35 -0
- package/dist/runtime/diagnostic-redactor.d.ts +49 -0
- package/dist/runtime/diagnostic-redactor.js +569 -0
- package/dist/runtime/egress-lease.d.ts +2 -0
- package/dist/runtime/egress-lease.js +4 -1
- package/dist/runtime/env.js +2 -1
- package/dist/runtime/handle-wordlist.d.ts +43 -0
- package/dist/runtime/{choice-wordlist.js → handle-wordlist.js} +127 -9
- package/dist/runtime/handle.d.ts +71 -0
- package/dist/runtime/handle.js +895 -0
- package/dist/runtime/instrumentation.js +3 -0
- package/dist/runtime/native-network.d.ts +10 -1
- package/dist/runtime/native-network.js +230 -29
- package/dist/runtime/native-telemetry.d.ts +142 -0
- package/dist/runtime/native-telemetry.js +231 -0
- package/dist/runtime/ocr.js +2 -1
- package/dist/runtime/otlp.js +5 -4
- package/dist/runtime/proxy-nodemaven.js +3 -2
- package/dist/runtime/request-options.d.ts +7 -0
- package/dist/runtime/request-options.js +62 -22
- package/dist/runtime/resolver-vendors/browser.js +0 -1
- package/dist/runtime/resolver.js +3 -2
- package/dist/runtime/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/runtime/stt.js +3 -2
- package/dist/runtime/trace.d.ts +5 -0
- package/dist/runtime/trace.js +115 -20
- package/dist/schema.d.ts +11 -0
- package/dist/schema.js +56 -2
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/self-test-redaction.js +8 -33
- package/dist/server/sensitive-values.d.ts +26 -0
- package/dist/server/sensitive-values.js +128 -0
- package/dist/server/serve-implementation.d.ts +4 -11
- package/dist/server/serve-implementation.js +305 -96
- package/dist/server/trace-output.d.ts +2 -1
- package/dist/server/trace-output.js +46 -14
- package/dist/testing/run.js +10 -7
- package/dist/trace-sanitization.d.ts +4 -3
- package/dist/trace-sanitization.js +45 -22
- package/dist/types.d.ts +4 -107
- package/package.json +2 -1
- package/src/config/loader.ts +13 -12
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +8 -8
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +58 -0
- package/src/handle.ts +529 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +28 -13
- package/src/lint.ts +456 -71
- package/src/provider.ts +10 -20
- package/src/runtime/browser.ts +2 -1
- package/src/runtime/cache.ts +3 -2
- package/src/runtime/diagnostic-env-values.ts +56 -0
- package/src/runtime/diagnostic-env.ts +55 -0
- package/src/runtime/diagnostic-redactor.ts +662 -0
- package/src/runtime/egress-lease.ts +6 -1
- package/src/runtime/env.ts +2 -1
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1162 -0
- package/src/runtime/instrumentation.ts +3 -0
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/ocr.ts +2 -1
- package/src/runtime/otlp.ts +5 -4
- package/src/runtime/proxy-nodemaven.ts +3 -2
- package/src/runtime/request-options.ts +76 -28
- package/src/runtime/resolver-vendors/browser.ts +0 -1
- package/src/runtime/resolver.ts +3 -2
- package/src/runtime/state.ts +44 -16
- package/src/runtime/stt.ts +3 -2
- package/src/runtime/trace.ts +168 -26
- package/src/schema.ts +63 -2
- package/src/server/index.ts +17 -0
- package/src/server/self-test-redaction.ts +10 -40
- package/src/server/sensitive-values.ts +197 -0
- package/src/server/serve-implementation.ts +388 -128
- package/src/server/trace-output.ts +77 -17
- package/src/testing/run.ts +11 -7
- package/src/trace-sanitization.ts +70 -23
- package/src/types.ts +4 -137
- package/dist/choice-token.d.ts +0 -24
- package/dist/choice-token.js +0 -74
- package/dist/runtime/choice-wordlist.d.ts +0 -9
- package/dist/runtime/choice.d.ts +0 -27
- package/dist/runtime/choice.js +0 -801
- package/src/choice-token.ts +0 -165
- package/src/runtime/choice.ts +0 -1197
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerDiagnosticSensitiveValues } from "./diagnostic-redactor.js";
|
|
1
2
|
import type {
|
|
2
3
|
HttpStreamResponse,
|
|
3
4
|
ProviderContext,
|
|
@@ -570,6 +571,7 @@ function wrapStealthRedirects(
|
|
|
570
571
|
run(...args: Parameters<StealthSession["redirects"]["run"]>) {
|
|
571
572
|
const diagnosticArgs = [args[0].url, args[0]];
|
|
572
573
|
const diagnostics = snapshotRequestDiagnostics("stealth", "fetch", diagnosticArgs);
|
|
574
|
+
registerDiagnosticSensitiveValues(trace, diagnostics.sensitiveValues);
|
|
573
575
|
let result: ReturnType<StealthSession["redirects"]["run"]>;
|
|
574
576
|
try {
|
|
575
577
|
result = redirects.run(...args);
|
|
@@ -750,6 +752,7 @@ function wrapNamespace<T extends object>(
|
|
|
750
752
|
return Reflect.apply(value, namespaceTarget, args);
|
|
751
753
|
}
|
|
752
754
|
const requestDiagnostics = snapshotRequestDiagnostics(namespace, methodName, args);
|
|
755
|
+
registerDiagnosticSensitiveValues(trace, requestDiagnostics.sensitiveValues);
|
|
753
756
|
// Invoke first and decide by the RETURN VALUE. `runSpan` always
|
|
754
757
|
// returns a Promise, so unconditionally span-wrapping every member
|
|
755
758
|
// silently rewrote synchronous contracts: `ctx.state.namespace()`
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
type ProxyProtocol,
|
|
12
12
|
VENDOR_DEFAULT_PROTOCOL,
|
|
13
13
|
resolveWithVendor,
|
|
14
|
+
type ProxyTelemetrySink,
|
|
14
15
|
} from "../config/loader.js";
|
|
15
16
|
import {
|
|
16
17
|
type DynamicEgressRuleSnapshot,
|
|
@@ -61,6 +62,12 @@ import {
|
|
|
61
62
|
synthesizeNodemavenProxy,
|
|
62
63
|
} from "./proxy-nodemaven.js";
|
|
63
64
|
import { redactSensitiveError } from "./request-options.js";
|
|
65
|
+
import type {
|
|
66
|
+
NativeConnectTelemetryEvent,
|
|
67
|
+
NativeTelemetryDiagnostics,
|
|
68
|
+
NativeTelemetryErrorCode,
|
|
69
|
+
NativeTelemetrySink,
|
|
70
|
+
} from "./native-telemetry.js";
|
|
64
71
|
|
|
65
72
|
export {
|
|
66
73
|
NativeEgressGrantExpiredError,
|
|
@@ -82,6 +89,8 @@ export type NativeGatewayProxySynthesisInput = {
|
|
|
82
89
|
readonly now: number;
|
|
83
90
|
readonly protocol: ProxyProtocol;
|
|
84
91
|
readonly credentials: VendorCredentialResolver;
|
|
92
|
+
/** Existing proxy lease telemetry; adapters may report detailed allocation facts. */
|
|
93
|
+
readonly telemetry?: ProxyTelemetrySink;
|
|
85
94
|
};
|
|
86
95
|
|
|
87
96
|
export type VendorCredentialLookup =
|
|
@@ -113,9 +122,15 @@ export type NativeGatewayProxyResolutionInput = {
|
|
|
113
122
|
readonly protocol?: ProxyProtocol;
|
|
114
123
|
readonly credentials?: VendorCredentialResolver;
|
|
115
124
|
readonly gatewaySynthesizers?: readonly NativeGatewayProxySynthesizer[];
|
|
125
|
+
readonly telemetry?: ProxyTelemetrySink;
|
|
126
|
+
readonly nativeTelemetry?: NativeTelemetrySink;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
129
|
export type NativeNetworkClientOptions = {
|
|
130
|
+
/** Existing proxy lease contributor, shared with HTTP and stealth. */
|
|
131
|
+
readonly telemetry?: ProxyTelemetrySink;
|
|
132
|
+
/** Request-scoped connection, byte and lifecycle observations. */
|
|
133
|
+
readonly nativeTelemetry?: NativeTelemetrySink;
|
|
119
134
|
readonly proxyPolicy?: ProviderProxyPolicy;
|
|
120
135
|
readonly affinityKey?: string;
|
|
121
136
|
/** Stable credential/account identity; hashed before vendor synthesis. */
|
|
@@ -238,6 +253,7 @@ async function synthesizeSmartproxyGateway(
|
|
|
238
253
|
proxyPolicy: input.policy,
|
|
239
254
|
affinityKey: input.affinityKey,
|
|
240
255
|
protocol: input.protocol,
|
|
256
|
+
telemetry: input.telemetry,
|
|
241
257
|
},
|
|
242
258
|
{
|
|
243
259
|
protocol: input.protocol,
|
|
@@ -257,6 +273,9 @@ async function synthesizeSmartproxyGateway(
|
|
|
257
273
|
sticky: isStickyPolicy(input.policy),
|
|
258
274
|
};
|
|
259
275
|
} catch (error) {
|
|
276
|
+
if (error instanceof ProxyResolutionError && error.telemetry) {
|
|
277
|
+
emitTelemetry(() => input.telemetry?.recordProxyResolution(error.telemetry!));
|
|
278
|
+
}
|
|
260
279
|
const cause = error instanceof Error ? error : new Error(String(error));
|
|
261
280
|
return skipped({
|
|
262
281
|
kind: "allocation_failed",
|
|
@@ -345,6 +364,61 @@ function defaultVendorProtocol(vendor: ProviderProxyProvider): ProxyProtocol {
|
|
|
345
364
|
: "http";
|
|
346
365
|
}
|
|
347
366
|
|
|
367
|
+
// Instrumentation must not change the transport's exceptions or vendor ordering.
|
|
368
|
+
function emitTelemetry(emit: () => void): void {
|
|
369
|
+
try {
|
|
370
|
+
emit();
|
|
371
|
+
} catch {
|
|
372
|
+
/* An observer cannot fail an underlying operation. */
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function nativeErrorCode(error: unknown): NativeTelemetryErrorCode {
|
|
377
|
+
const code =
|
|
378
|
+
error instanceof NativeNetworkError || error instanceof ProxyResolutionError
|
|
379
|
+
? error.code
|
|
380
|
+
: undefined;
|
|
381
|
+
switch (code) {
|
|
382
|
+
case "native_connection_aborted":
|
|
383
|
+
case "native_connection_closed":
|
|
384
|
+
case "native_connection_failed":
|
|
385
|
+
case "native_connection_idle_timeout":
|
|
386
|
+
case "native_connection_timeout":
|
|
387
|
+
case "native_egress_authorization_failed":
|
|
388
|
+
case "native_egress_grant_expired":
|
|
389
|
+
case "native_egress_grant_invalid":
|
|
390
|
+
case "native_egress_grant_limit_exceeded":
|
|
391
|
+
case "native_egress_input_invalid":
|
|
392
|
+
case "native_egress_not_declared":
|
|
393
|
+
case "native_egress_policy_invalid":
|
|
394
|
+
case "native_dynamic_egress_unsupported":
|
|
395
|
+
case "native_proxy_expired":
|
|
396
|
+
case "native_proxy_invalid":
|
|
397
|
+
case "PROXY_REQUIRED":
|
|
398
|
+
case "PROXY_ALLOCATION_FAILED":
|
|
399
|
+
return code;
|
|
400
|
+
default:
|
|
401
|
+
return "other";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function nativeErrorDiagnostics(error: unknown): NativeTelemetryDiagnostics {
|
|
406
|
+
if (!(error instanceof Error)) return {};
|
|
407
|
+
const cause = error.cause instanceof Error ? error.cause : undefined;
|
|
408
|
+
const detail = cause ?? error;
|
|
409
|
+
const systemCode: unknown = Reflect.get(detail, "code");
|
|
410
|
+
const status: unknown = Reflect.get(detail, "connectStatusCode");
|
|
411
|
+
const socksReplyCode: unknown = Reflect.get(detail, "socks5ReplyCode");
|
|
412
|
+
return {
|
|
413
|
+
errorName: error.name,
|
|
414
|
+
errorMessage: error.message,
|
|
415
|
+
...(cause ? { causeName: cause.name, causeMessage: cause.message } : {}),
|
|
416
|
+
...(typeof systemCode === "string" ? { systemCode } : {}),
|
|
417
|
+
...(typeof status === "number" ? { status } : {}),
|
|
418
|
+
...(typeof socksReplyCode === "number" ? { socksReplyCode } : {}),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
348
422
|
async function resolveNativeGatewayProxyDetailed(
|
|
349
423
|
input: NativeGatewayProxyResolutionInput,
|
|
350
424
|
): Promise<{ proxy?: NativeGatewayProxy; skips: readonly NativeGatewayVendorSkip[] }> {
|
|
@@ -353,14 +427,67 @@ async function resolveNativeGatewayProxyDetailed(
|
|
|
353
427
|
const credentials = input.credentials ?? createEnvVendorCredentialResolver();
|
|
354
428
|
const now = input.now ?? Date.now();
|
|
355
429
|
const skips: NativeGatewayVendorSkip[] = [];
|
|
356
|
-
|
|
430
|
+
const chain = resolveNativeVendorChain(input.policy);
|
|
431
|
+
const recordSkip = (skip: NativeGatewayVendorSkip): void => {
|
|
432
|
+
skips.push(skip);
|
|
433
|
+
emitTelemetry(() =>
|
|
434
|
+
input.nativeTelemetry?.recordVendorSkip({
|
|
435
|
+
vendor: skip.vendor,
|
|
436
|
+
reason: skip.reason.kind,
|
|
437
|
+
diagnostics:
|
|
438
|
+
skip.reason.kind === "credentials_absent"
|
|
439
|
+
? { missingFields: skip.reason.missing }
|
|
440
|
+
: skip.reason.kind === "protocol_unsupported"
|
|
441
|
+
? { protocol: skip.reason.protocol }
|
|
442
|
+
: skip.reason.kind === "allocation_failed" ||
|
|
443
|
+
skip.reason.kind === "credential_lookup_failed"
|
|
444
|
+
? nativeErrorDiagnostics(skip.reason.cause)
|
|
445
|
+
: {},
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
448
|
+
if (skip.vendor === "smartproxy" || skip.vendor === "nodemaven") {
|
|
449
|
+
const vendor = skip.vendor;
|
|
450
|
+
const next = chain[chain.indexOf(vendor) + 1];
|
|
451
|
+
emitTelemetry(() =>
|
|
452
|
+
input.telemetry?.recordProxyVendorFailover?.({
|
|
453
|
+
vendor,
|
|
454
|
+
...(next === "smartproxy" || next === "nodemaven" ? { nextVendor: next } : {}),
|
|
455
|
+
phase: "resolution",
|
|
456
|
+
reason:
|
|
457
|
+
skip.reason.kind === "credentials_absent"
|
|
458
|
+
? "no_credentials"
|
|
459
|
+
: skip.reason.kind === "protocol_unsupported"
|
|
460
|
+
? "protocol_unsupported"
|
|
461
|
+
: "allocation_failed",
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
for (const vendor of chain) {
|
|
357
467
|
const protocol = input.protocol ?? defaultVendorProtocol(vendor);
|
|
358
468
|
if (!isProxyProtocol(protocol)) {
|
|
359
|
-
|
|
469
|
+
recordSkip({ vendor, reason: { kind: "protocol_unsupported", protocol: String(protocol) } });
|
|
360
470
|
continue;
|
|
361
471
|
}
|
|
472
|
+
let resolutionRecorded = false;
|
|
473
|
+
const telemetry: ProxyTelemetrySink | undefined = input.telemetry
|
|
474
|
+
? {
|
|
475
|
+
recordProxyResolution(event) {
|
|
476
|
+
resolutionRecorded = true;
|
|
477
|
+
emitTelemetry(() => input.telemetry?.recordProxyResolution(event));
|
|
478
|
+
},
|
|
479
|
+
recordProxyAttempt(event) {
|
|
480
|
+
emitTelemetry(() => input.telemetry?.recordProxyAttempt?.(event));
|
|
481
|
+
},
|
|
482
|
+
recordProxyVendorFailover(event) {
|
|
483
|
+
emitTelemetry(() => input.telemetry?.recordProxyVendorFailover?.(event));
|
|
484
|
+
},
|
|
485
|
+
}
|
|
486
|
+
: undefined;
|
|
362
487
|
let vendorSkip: NativeGatewayVendorSkip["reason"] | undefined;
|
|
363
488
|
for (const synthesize of synthesizers) {
|
|
489
|
+
const startedAt = Date.now();
|
|
490
|
+
resolutionRecorded = false;
|
|
364
491
|
try {
|
|
365
492
|
const resolved = await synthesize({
|
|
366
493
|
vendor,
|
|
@@ -369,6 +496,7 @@ async function resolveNativeGatewayProxyDetailed(
|
|
|
369
496
|
now,
|
|
370
497
|
protocol,
|
|
371
498
|
credentials,
|
|
499
|
+
telemetry,
|
|
372
500
|
});
|
|
373
501
|
if (!resolved) continue;
|
|
374
502
|
if (isSkippedSynthesis(resolved)) {
|
|
@@ -377,6 +505,19 @@ async function resolveNativeGatewayProxyDetailed(
|
|
|
377
505
|
}
|
|
378
506
|
if (resolved.vendor === vendor) {
|
|
379
507
|
assertTunnelingScheme(resolved.url);
|
|
508
|
+
if (!resolutionRecorded && (vendor === "smartproxy" || vendor === "nodemaven")) {
|
|
509
|
+
const resolvedProtocol = new URL(resolved.url).protocol === "http:" ? "http" : "socks5";
|
|
510
|
+
emitTelemetry(() =>
|
|
511
|
+
telemetry?.recordProxyResolution({
|
|
512
|
+
provider: vendor,
|
|
513
|
+
protocol: resolvedProtocol,
|
|
514
|
+
cacheStatus: "disabled",
|
|
515
|
+
cacheHit: false,
|
|
516
|
+
resolutionMs: Math.max(0, Date.now() - startedAt),
|
|
517
|
+
attempts: 1,
|
|
518
|
+
}),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
380
521
|
return { proxy: resolved, skips };
|
|
381
522
|
}
|
|
382
523
|
} catch (error) {
|
|
@@ -386,7 +527,7 @@ async function resolveNativeGatewayProxyDetailed(
|
|
|
386
527
|
};
|
|
387
528
|
}
|
|
388
529
|
}
|
|
389
|
-
|
|
530
|
+
recordSkip({ vendor, reason: vendorSkip ?? { kind: "adapter_unavailable" } });
|
|
390
531
|
}
|
|
391
532
|
return { skips };
|
|
392
533
|
}
|
|
@@ -908,6 +1049,12 @@ export function createNativeNetworkConnection(
|
|
|
908
1049
|
idleTimer = undefined;
|
|
909
1050
|
if (socket.readableEnded || socket.destroyed) return;
|
|
910
1051
|
closeReason = new NativeIdleTimeoutError();
|
|
1052
|
+
emitTelemetry(() =>
|
|
1053
|
+
options.nativeTelemetry?.recordLifecycle({
|
|
1054
|
+
kind: "idle",
|
|
1055
|
+
errorCode: "native_connection_idle_timeout",
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
911
1058
|
socket.destroy(closeReason);
|
|
912
1059
|
},
|
|
913
1060
|
Math.max(0, idleTimeoutMs),
|
|
@@ -924,6 +1071,13 @@ export function createNativeNetworkConnection(
|
|
|
924
1071
|
};
|
|
925
1072
|
socket.on("error", (error) => {
|
|
926
1073
|
terminalError = error;
|
|
1074
|
+
if (!closeReason)
|
|
1075
|
+
emitTelemetry(() =>
|
|
1076
|
+
options.nativeTelemetry?.recordError({
|
|
1077
|
+
errorCode: "native_connection_failed",
|
|
1078
|
+
diagnostics: nativeErrorDiagnostics(error),
|
|
1079
|
+
}),
|
|
1080
|
+
);
|
|
927
1081
|
});
|
|
928
1082
|
socket.once("end", clearIdleTimer);
|
|
929
1083
|
socket.once("close", clearLifecycle);
|
|
@@ -948,15 +1102,35 @@ export function createNativeNetworkConnection(
|
|
|
948
1102
|
const leadDelay = Math.max(0, remaining - leadSeconds * 1_000);
|
|
949
1103
|
expiringTimer = setTimeout(() => {
|
|
950
1104
|
expiringTimer = undefined;
|
|
1105
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({ kind: "drain" }));
|
|
951
1106
|
const handler = drainHandler;
|
|
952
1107
|
if (!handler) {
|
|
1108
|
+
emitTelemetry(() =>
|
|
1109
|
+
options.nativeTelemetry?.recordLifecycle({ kind: "drain_missing_handler" }),
|
|
1110
|
+
);
|
|
953
1111
|
warn("Native sticky proxy is expiring without a drain handler");
|
|
954
1112
|
return;
|
|
955
1113
|
}
|
|
956
1114
|
void (async () => {
|
|
957
1115
|
try {
|
|
958
|
-
await Promise.race([
|
|
959
|
-
|
|
1116
|
+
await Promise.race([
|
|
1117
|
+
Promise.resolve(handler(event)).then(() => {
|
|
1118
|
+
if (socket.destroyed || lifecycleSettled) return;
|
|
1119
|
+
emitTelemetry(() =>
|
|
1120
|
+
options.nativeTelemetry?.recordLifecycle({ kind: "drain_acknowledged" }),
|
|
1121
|
+
);
|
|
1122
|
+
}),
|
|
1123
|
+
lifecycleCutoff,
|
|
1124
|
+
]);
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
if (socket.destroyed || lifecycleSettled) return;
|
|
1127
|
+
emitTelemetry(() =>
|
|
1128
|
+
options.nativeTelemetry?.recordLifecycle({
|
|
1129
|
+
kind: "drain_error",
|
|
1130
|
+
errorCode: "other",
|
|
1131
|
+
diagnostics: nativeErrorDiagnostics(error),
|
|
1132
|
+
}),
|
|
1133
|
+
);
|
|
960
1134
|
// Drain failures do not bypass the hard-expiry fail-safe.
|
|
961
1135
|
}
|
|
962
1136
|
})();
|
|
@@ -966,6 +1140,13 @@ export function createNativeNetworkConnection(
|
|
|
966
1140
|
hardExpiryTimer = undefined;
|
|
967
1141
|
if (socket.destroyed) return;
|
|
968
1142
|
closeReason = new NativeProxyExpiredError(expiresAt);
|
|
1143
|
+
emitTelemetry(() =>
|
|
1144
|
+
options.nativeTelemetry?.recordLifecycle({
|
|
1145
|
+
kind: "expiry",
|
|
1146
|
+
errorCode: "native_proxy_expired",
|
|
1147
|
+
diagnostics: { expiresAt },
|
|
1148
|
+
}),
|
|
1149
|
+
);
|
|
969
1150
|
settleLifecycle();
|
|
970
1151
|
socket.destroy();
|
|
971
1152
|
}, remaining);
|
|
@@ -978,6 +1159,9 @@ export function createNativeNetworkConnection(
|
|
|
978
1159
|
const chunk = socket.read() as Buffer | null;
|
|
979
1160
|
if (chunk) {
|
|
980
1161
|
resetIdleTimer();
|
|
1162
|
+
emitTelemetry(() =>
|
|
1163
|
+
options.nativeTelemetry?.recordBytes({ direction: "in", bytes: chunk.byteLength }),
|
|
1164
|
+
);
|
|
981
1165
|
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
982
1166
|
}
|
|
983
1167
|
if (socket.readableEnded || socket.destroyed) return null;
|
|
@@ -1025,6 +1209,9 @@ export function createNativeNetworkConnection(
|
|
|
1025
1209
|
write: async (data) => {
|
|
1026
1210
|
if (closeReason) throw closeReason;
|
|
1027
1211
|
if (socket.destroyed) {
|
|
1212
|
+
emitTelemetry(() =>
|
|
1213
|
+
options.nativeTelemetry?.recordError({ errorCode: "native_connection_closed" }),
|
|
1214
|
+
);
|
|
1028
1215
|
throw new NativeNetworkError("Native connection is closed", "native_connection_closed");
|
|
1029
1216
|
}
|
|
1030
1217
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -1033,6 +1220,9 @@ export function createNativeNetworkConnection(
|
|
|
1033
1220
|
else resolve();
|
|
1034
1221
|
});
|
|
1035
1222
|
});
|
|
1223
|
+
emitTelemetry(() =>
|
|
1224
|
+
options.nativeTelemetry?.recordBytes({ direction: "out", bytes: data.byteLength }),
|
|
1225
|
+
);
|
|
1036
1226
|
},
|
|
1037
1227
|
close: async () => {
|
|
1038
1228
|
if (socket.closed || socket.destroyed) {
|
|
@@ -1067,6 +1257,8 @@ async function resolveConnectionProxy(
|
|
|
1067
1257
|
protocol: options.proxyProtocol,
|
|
1068
1258
|
credentials: options.credentials,
|
|
1069
1259
|
gatewaySynthesizers: options.gatewaySynthesizers,
|
|
1260
|
+
telemetry: options.telemetry,
|
|
1261
|
+
nativeTelemetry: options.nativeTelemetry,
|
|
1070
1262
|
}),
|
|
1071
1263
|
input.signal,
|
|
1072
1264
|
deadline,
|
|
@@ -1570,36 +1762,92 @@ export function createNativeNetworkClient(
|
|
|
1570
1762
|
options: NativeNetworkClientOptions = {},
|
|
1571
1763
|
): NativeNetworkClient {
|
|
1572
1764
|
const egress = createNativeEgressAuthorization(options);
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
},
|
|
1588
|
-
connectTls: async (input) => {
|
|
1589
|
-
const request = snapshotNativeConnectInput(input);
|
|
1590
|
-
egress.assertConnect(request, "required");
|
|
1765
|
+
const connect = async (
|
|
1766
|
+
input: NativeNetworkConnectInput,
|
|
1767
|
+
kind: "tcp" | "tls",
|
|
1768
|
+
): Promise<NativeNetworkConnection> => {
|
|
1769
|
+
const startedAt = Date.now();
|
|
1770
|
+
let proxy: NativeGatewayProxy | undefined;
|
|
1771
|
+
let request: NativeNetworkConnectInput | undefined;
|
|
1772
|
+
let tunnelMs = 0;
|
|
1773
|
+
let outcome: NativeConnectTelemetryEvent["outcome"] = "error";
|
|
1774
|
+
let failure: unknown;
|
|
1775
|
+
try {
|
|
1776
|
+
request = snapshotNativeConnectInput(input);
|
|
1777
|
+
const tls = kind === "tls" ? "required" : "disabled";
|
|
1778
|
+
egress.assertConnect(request, tls);
|
|
1591
1779
|
const deadline = deadlineFrom(request.timeoutMs);
|
|
1592
1780
|
assertCanStart(request.signal, deadline);
|
|
1593
|
-
|
|
1594
|
-
egress.assertConnect(request,
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1781
|
+
proxy = await resolveConnectionProxy(options, request, deadline);
|
|
1782
|
+
egress.assertConnect(request, tls);
|
|
1783
|
+
let tunnel: Socket | undefined;
|
|
1784
|
+
if (proxy) {
|
|
1785
|
+
const tunnelStartedAt = Date.now();
|
|
1786
|
+
try {
|
|
1787
|
+
const destination = request;
|
|
1788
|
+
tunnel = await connectProxyTunnel(proxy, request, deadline, () =>
|
|
1789
|
+
egress.assertConnect(destination, tls),
|
|
1790
|
+
);
|
|
1791
|
+
} finally {
|
|
1792
|
+
tunnelMs = Math.max(0, Date.now() - tunnelStartedAt);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
const socket =
|
|
1796
|
+
kind === "tls"
|
|
1797
|
+
? await upgradeTls(tunnel, request, deadline)
|
|
1798
|
+
: (tunnel ??
|
|
1799
|
+
(await connectPlainSocket(request.host, request.port, request.signal, deadline)));
|
|
1800
|
+
const connection = createNativeNetworkConnection(
|
|
1801
|
+
socket,
|
|
1802
|
+
proxy,
|
|
1803
|
+
options,
|
|
1804
|
+
request.idleTimeoutMs,
|
|
1805
|
+
);
|
|
1806
|
+
outcome = "ok";
|
|
1807
|
+
return connection;
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
failure = error;
|
|
1810
|
+
throw error;
|
|
1811
|
+
} finally {
|
|
1812
|
+
// One terminal sample for this connect call, including preflight and rethrows.
|
|
1813
|
+
emitTelemetry(() =>
|
|
1814
|
+
options.nativeTelemetry?.recordConnect({
|
|
1815
|
+
kind,
|
|
1816
|
+
outcome,
|
|
1817
|
+
ms: Math.max(0, Date.now() - startedAt),
|
|
1818
|
+
tunnelMs,
|
|
1819
|
+
proxyUsed: proxy !== undefined,
|
|
1820
|
+
...(proxy ? { vendor: proxy.vendor } : {}),
|
|
1821
|
+
...(outcome === "error" ? { errorCode: nativeErrorCode(failure) } : {}),
|
|
1822
|
+
diagnostics: {
|
|
1823
|
+
...(request
|
|
1824
|
+
? {
|
|
1825
|
+
host: request.host,
|
|
1826
|
+
port: request.port,
|
|
1827
|
+
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
1828
|
+
...(request.idleTimeoutMs === undefined
|
|
1829
|
+
? {}
|
|
1830
|
+
: { idleTimeoutMs: request.idleTimeoutMs }),
|
|
1831
|
+
...(request.serverName ? { serverName: request.serverName } : {}),
|
|
1832
|
+
}
|
|
1833
|
+
: {}),
|
|
1834
|
+
...(proxy
|
|
1835
|
+
? {
|
|
1836
|
+
protocol: new URL(proxy.url).protocol,
|
|
1837
|
+
sticky: proxy.sticky,
|
|
1838
|
+
...(proxy.sessionId ? { sessionId: proxy.sessionId } : {}),
|
|
1839
|
+
...(proxy.expiresAt ? { expiresAt: proxy.expiresAt } : {}),
|
|
1840
|
+
}
|
|
1841
|
+
: {}),
|
|
1842
|
+
...nativeErrorDiagnostics(failure),
|
|
1843
|
+
},
|
|
1844
|
+
}),
|
|
1845
|
+
);
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
return {
|
|
1849
|
+
connectTcp: (input) => connect(input, "tcp"),
|
|
1850
|
+
connectTls: (input) => connect(input, "tls"),
|
|
1603
1851
|
grantTcpEgress: (input) => egress.grant(snapshotNativeGrantInput(input)),
|
|
1604
1852
|
};
|
|
1605
1853
|
}
|