@apifuse/provider-sdk 2.2.0-beta.60 → 2.2.0-beta.62
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 +17 -16
- package/CHANGELOG.md +8 -0
- package/bin/apifuse-dev.ts +5 -7
- package/bin/apifuse-pack-types.ts +26 -0
- package/bin/apifuse-perf.ts +4 -6
- package/bin/apifuse-record.ts +4 -7
- package/bin/apifuse-submit-check.ts +2 -2
- 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 +3 -4
- 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 +55 -0
- package/dist/handle-meta.js +57 -0
- package/dist/handle.d.ts +208 -0
- package/dist/handle.js +299 -0
- package/dist/i18n/catalog.js +21 -1
- package/dist/index.d.ts +7 -4
- package/dist/index.js +6 -3
- package/dist/lint.d.ts +3 -0
- package/dist/lint.js +372 -47
- package/dist/provider.d.ts +5 -3
- package/dist/provider.js +4 -2
- 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 +900 -0
- package/dist/runtime/http-telemetry-binding.d.ts +10 -0
- package/dist/runtime/http-telemetry-binding.js +152 -0
- package/dist/runtime/http-telemetry-guard.d.ts +6 -0
- package/dist/runtime/http-telemetry-guard.js +150 -0
- package/dist/runtime/http-telemetry.d.ts +104 -0
- package/dist/runtime/http-telemetry.js +190 -0
- package/dist/runtime/http.d.ts +31 -0
- package/dist/runtime/http.js +222 -146
- 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/state.d.ts +4 -1
- package/dist/runtime/state.js +32 -17
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +2 -0
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +0 -3
- package/dist/server/serve-implementation.d.ts +6 -11
- package/dist/server/serve-implementation.js +119 -52
- package/dist/testing/run.js +10 -7
- package/dist/types.d.ts +4 -107
- package/package.json +1 -1
- package/src/define.ts +4 -3
- package/src/dev.ts +3 -1
- package/src/engine.ts +5 -6
- package/src/error-resolution.ts +21 -4
- package/src/errors.ts +13 -0
- package/src/handle-meta.ts +93 -0
- package/src/handle.ts +612 -0
- package/src/i18n/catalog.ts +23 -1
- package/src/index.ts +45 -13
- package/src/lint.ts +437 -55
- package/src/provider.ts +19 -20
- package/src/runtime/{choice-wordlist.ts → handle-wordlist.ts} +130 -9
- package/src/runtime/handle.ts +1172 -0
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +155 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +295 -173
- package/src/runtime/native-network.ts +281 -33
- package/src/runtime/native-telemetry.ts +366 -0
- package/src/runtime/state.ts +44 -16
- package/src/server/index.ts +30 -0
- package/src/server/sensitive-values.ts +0 -3
- package/src/server/serve-implementation.ts +127 -89
- package/src/testing/run.ts +11 -7
- 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
|
@@ -101,6 +101,7 @@ async function synthesizeSmartproxyGateway(input) {
|
|
|
101
101
|
proxyPolicy: input.policy,
|
|
102
102
|
affinityKey: input.affinityKey,
|
|
103
103
|
protocol: input.protocol,
|
|
104
|
+
telemetry: input.telemetry,
|
|
104
105
|
}, {
|
|
105
106
|
protocol: input.protocol,
|
|
106
107
|
poolIndex: 0,
|
|
@@ -119,6 +120,9 @@ async function synthesizeSmartproxyGateway(input) {
|
|
|
119
120
|
};
|
|
120
121
|
}
|
|
121
122
|
catch (error) {
|
|
123
|
+
if (error instanceof ProxyResolutionError && error.telemetry) {
|
|
124
|
+
emitTelemetry(() => input.telemetry?.recordProxyResolution(error.telemetry));
|
|
125
|
+
}
|
|
122
126
|
const cause = error instanceof Error ? error : new Error(String(error));
|
|
123
127
|
return skipped({
|
|
124
128
|
kind: "allocation_failed",
|
|
@@ -184,6 +188,59 @@ function defaultVendorProtocol(vendor) {
|
|
|
184
188
|
? VENDOR_DEFAULT_PROTOCOL[vendor]
|
|
185
189
|
: "http";
|
|
186
190
|
}
|
|
191
|
+
// Instrumentation must not change the transport's exceptions or vendor ordering.
|
|
192
|
+
function emitTelemetry(emit) {
|
|
193
|
+
try {
|
|
194
|
+
emit();
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
/* An observer cannot fail an underlying operation. */
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function nativeErrorCode(error) {
|
|
201
|
+
const code = error instanceof NativeNetworkError || error instanceof ProxyResolutionError
|
|
202
|
+
? error.code
|
|
203
|
+
: undefined;
|
|
204
|
+
switch (code) {
|
|
205
|
+
case "native_connection_aborted":
|
|
206
|
+
case "native_connection_closed":
|
|
207
|
+
case "native_connection_failed":
|
|
208
|
+
case "native_connection_idle_timeout":
|
|
209
|
+
case "native_connection_timeout":
|
|
210
|
+
case "native_egress_authorization_failed":
|
|
211
|
+
case "native_egress_grant_expired":
|
|
212
|
+
case "native_egress_grant_invalid":
|
|
213
|
+
case "native_egress_grant_limit_exceeded":
|
|
214
|
+
case "native_egress_input_invalid":
|
|
215
|
+
case "native_egress_not_declared":
|
|
216
|
+
case "native_egress_policy_invalid":
|
|
217
|
+
case "native_dynamic_egress_unsupported":
|
|
218
|
+
case "native_proxy_expired":
|
|
219
|
+
case "native_proxy_invalid":
|
|
220
|
+
case "PROXY_REQUIRED":
|
|
221
|
+
case "PROXY_ALLOCATION_FAILED":
|
|
222
|
+
return code;
|
|
223
|
+
default:
|
|
224
|
+
return "other";
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function nativeErrorDiagnostics(error) {
|
|
228
|
+
if (!(error instanceof Error))
|
|
229
|
+
return {};
|
|
230
|
+
const cause = error.cause instanceof Error ? error.cause : undefined;
|
|
231
|
+
const detail = cause ?? error;
|
|
232
|
+
const systemCode = Reflect.get(detail, "code");
|
|
233
|
+
const status = Reflect.get(detail, "connectStatusCode");
|
|
234
|
+
const socksReplyCode = Reflect.get(detail, "socks5ReplyCode");
|
|
235
|
+
return {
|
|
236
|
+
errorName: error.name,
|
|
237
|
+
errorMessage: error.message,
|
|
238
|
+
...(cause ? { causeName: cause.name, causeMessage: cause.message } : {}),
|
|
239
|
+
...(typeof systemCode === "string" ? { systemCode } : {}),
|
|
240
|
+
...(typeof status === "number" ? { status } : {}),
|
|
241
|
+
...(typeof socksReplyCode === "number" ? { socksReplyCode } : {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
187
244
|
async function resolveNativeGatewayProxyDetailed(input) {
|
|
188
245
|
if (input.policy.mode === "disabled")
|
|
189
246
|
return { skips: [] };
|
|
@@ -191,14 +248,61 @@ async function resolveNativeGatewayProxyDetailed(input) {
|
|
|
191
248
|
const credentials = input.credentials ?? createEnvVendorCredentialResolver();
|
|
192
249
|
const now = input.now ?? Date.now();
|
|
193
250
|
const skips = [];
|
|
194
|
-
|
|
251
|
+
const chain = resolveNativeVendorChain(input.policy);
|
|
252
|
+
const recordSkip = (skip) => {
|
|
253
|
+
skips.push(skip);
|
|
254
|
+
emitTelemetry(() => input.nativeTelemetry?.recordVendorSkip({
|
|
255
|
+
vendor: skip.vendor,
|
|
256
|
+
reason: skip.reason.kind,
|
|
257
|
+
diagnostics: skip.reason.kind === "credentials_absent"
|
|
258
|
+
? { missingFields: skip.reason.missing }
|
|
259
|
+
: skip.reason.kind === "protocol_unsupported"
|
|
260
|
+
? { protocol: skip.reason.protocol }
|
|
261
|
+
: skip.reason.kind === "allocation_failed" ||
|
|
262
|
+
skip.reason.kind === "credential_lookup_failed"
|
|
263
|
+
? nativeErrorDiagnostics(skip.reason.cause)
|
|
264
|
+
: {},
|
|
265
|
+
}));
|
|
266
|
+
if (skip.vendor === "smartproxy" || skip.vendor === "nodemaven") {
|
|
267
|
+
const vendor = skip.vendor;
|
|
268
|
+
const next = chain[chain.indexOf(vendor) + 1];
|
|
269
|
+
emitTelemetry(() => input.telemetry?.recordProxyVendorFailover?.({
|
|
270
|
+
vendor,
|
|
271
|
+
...(next === "smartproxy" || next === "nodemaven" ? { nextVendor: next } : {}),
|
|
272
|
+
phase: "resolution",
|
|
273
|
+
reason: skip.reason.kind === "credentials_absent"
|
|
274
|
+
? "no_credentials"
|
|
275
|
+
: skip.reason.kind === "protocol_unsupported"
|
|
276
|
+
? "protocol_unsupported"
|
|
277
|
+
: "allocation_failed",
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
for (const vendor of chain) {
|
|
195
282
|
const protocol = input.protocol ?? defaultVendorProtocol(vendor);
|
|
196
283
|
if (!isProxyProtocol(protocol)) {
|
|
197
|
-
|
|
284
|
+
recordSkip({ vendor, reason: { kind: "protocol_unsupported", protocol: String(protocol) } });
|
|
198
285
|
continue;
|
|
199
286
|
}
|
|
287
|
+
let resolutionRecorded = false;
|
|
288
|
+
const telemetry = input.telemetry
|
|
289
|
+
? {
|
|
290
|
+
recordProxyResolution(event) {
|
|
291
|
+
resolutionRecorded = true;
|
|
292
|
+
emitTelemetry(() => input.telemetry?.recordProxyResolution(event));
|
|
293
|
+
},
|
|
294
|
+
recordProxyAttempt(event) {
|
|
295
|
+
emitTelemetry(() => input.telemetry?.recordProxyAttempt?.(event));
|
|
296
|
+
},
|
|
297
|
+
recordProxyVendorFailover(event) {
|
|
298
|
+
emitTelemetry(() => input.telemetry?.recordProxyVendorFailover?.(event));
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
: undefined;
|
|
200
302
|
let vendorSkip;
|
|
201
303
|
for (const synthesize of synthesizers) {
|
|
304
|
+
const startedAt = Date.now();
|
|
305
|
+
resolutionRecorded = false;
|
|
202
306
|
try {
|
|
203
307
|
const resolved = await synthesize({
|
|
204
308
|
vendor,
|
|
@@ -207,6 +311,7 @@ async function resolveNativeGatewayProxyDetailed(input) {
|
|
|
207
311
|
now,
|
|
208
312
|
protocol,
|
|
209
313
|
credentials,
|
|
314
|
+
telemetry,
|
|
210
315
|
});
|
|
211
316
|
if (!resolved)
|
|
212
317
|
continue;
|
|
@@ -216,6 +321,17 @@ async function resolveNativeGatewayProxyDetailed(input) {
|
|
|
216
321
|
}
|
|
217
322
|
if (resolved.vendor === vendor) {
|
|
218
323
|
assertTunnelingScheme(resolved.url);
|
|
324
|
+
if (!resolutionRecorded && (vendor === "smartproxy" || vendor === "nodemaven")) {
|
|
325
|
+
const resolvedProtocol = new URL(resolved.url).protocol === "http:" ? "http" : "socks5";
|
|
326
|
+
emitTelemetry(() => telemetry?.recordProxyResolution({
|
|
327
|
+
provider: vendor,
|
|
328
|
+
protocol: resolvedProtocol,
|
|
329
|
+
cacheStatus: "disabled",
|
|
330
|
+
cacheHit: false,
|
|
331
|
+
resolutionMs: Math.max(0, Date.now() - startedAt),
|
|
332
|
+
attempts: 1,
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
219
335
|
return { proxy: resolved, skips };
|
|
220
336
|
}
|
|
221
337
|
}
|
|
@@ -226,7 +342,7 @@ async function resolveNativeGatewayProxyDetailed(input) {
|
|
|
226
342
|
};
|
|
227
343
|
}
|
|
228
344
|
}
|
|
229
|
-
|
|
345
|
+
recordSkip({ vendor, reason: vendorSkip ?? { kind: "adapter_unavailable" } });
|
|
230
346
|
}
|
|
231
347
|
return { skips };
|
|
232
348
|
}
|
|
@@ -651,6 +767,10 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
651
767
|
if (socket.readableEnded || socket.destroyed)
|
|
652
768
|
return;
|
|
653
769
|
closeReason = new NativeIdleTimeoutError();
|
|
770
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({
|
|
771
|
+
kind: "idle",
|
|
772
|
+
errorCode: "native_connection_idle_timeout",
|
|
773
|
+
}));
|
|
654
774
|
socket.destroy(closeReason);
|
|
655
775
|
}, Math.max(0, idleTimeoutMs));
|
|
656
776
|
idleTimer.unref?.();
|
|
@@ -668,6 +788,11 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
668
788
|
};
|
|
669
789
|
socket.on("error", (error) => {
|
|
670
790
|
terminalError = error;
|
|
791
|
+
if (!closeReason)
|
|
792
|
+
emitTelemetry(() => options.nativeTelemetry?.recordError({
|
|
793
|
+
errorCode: "native_connection_failed",
|
|
794
|
+
diagnostics: nativeErrorDiagnostics(error),
|
|
795
|
+
}));
|
|
671
796
|
});
|
|
672
797
|
socket.once("end", clearIdleTimer);
|
|
673
798
|
socket.once("close", clearLifecycle);
|
|
@@ -689,16 +814,32 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
689
814
|
const leadDelay = Math.max(0, remaining - leadSeconds * 1_000);
|
|
690
815
|
expiringTimer = setTimeout(() => {
|
|
691
816
|
expiringTimer = undefined;
|
|
817
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({ kind: "drain" }));
|
|
692
818
|
const handler = drainHandler;
|
|
693
819
|
if (!handler) {
|
|
820
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({ kind: "drain_missing_handler" }));
|
|
694
821
|
warn("Native sticky proxy is expiring without a drain handler");
|
|
695
822
|
return;
|
|
696
823
|
}
|
|
697
824
|
void (async () => {
|
|
698
825
|
try {
|
|
699
|
-
await Promise.race([
|
|
826
|
+
await Promise.race([
|
|
827
|
+
Promise.resolve(handler(event)).then(() => {
|
|
828
|
+
if (socket.destroyed || lifecycleSettled)
|
|
829
|
+
return;
|
|
830
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({ kind: "drain_acknowledged" }));
|
|
831
|
+
}),
|
|
832
|
+
lifecycleCutoff,
|
|
833
|
+
]);
|
|
700
834
|
}
|
|
701
|
-
catch {
|
|
835
|
+
catch (error) {
|
|
836
|
+
if (socket.destroyed || lifecycleSettled)
|
|
837
|
+
return;
|
|
838
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({
|
|
839
|
+
kind: "drain_error",
|
|
840
|
+
errorCode: "other",
|
|
841
|
+
diagnostics: nativeErrorDiagnostics(error),
|
|
842
|
+
}));
|
|
702
843
|
// Drain failures do not bypass the hard-expiry fail-safe.
|
|
703
844
|
}
|
|
704
845
|
})();
|
|
@@ -709,6 +850,11 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
709
850
|
if (socket.destroyed)
|
|
710
851
|
return;
|
|
711
852
|
closeReason = new NativeProxyExpiredError(expiresAt);
|
|
853
|
+
emitTelemetry(() => options.nativeTelemetry?.recordLifecycle({
|
|
854
|
+
kind: "expiry",
|
|
855
|
+
errorCode: "native_proxy_expired",
|
|
856
|
+
diagnostics: { expiresAt },
|
|
857
|
+
}));
|
|
712
858
|
settleLifecycle();
|
|
713
859
|
socket.destroy();
|
|
714
860
|
}, remaining);
|
|
@@ -722,6 +868,7 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
722
868
|
const chunk = socket.read();
|
|
723
869
|
if (chunk) {
|
|
724
870
|
resetIdleTimer();
|
|
871
|
+
emitTelemetry(() => options.nativeTelemetry?.recordBytes({ direction: "in", bytes: chunk.byteLength }));
|
|
725
872
|
return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
726
873
|
}
|
|
727
874
|
if (socket.readableEnded || socket.destroyed)
|
|
@@ -770,6 +917,7 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
770
917
|
if (closeReason)
|
|
771
918
|
throw closeReason;
|
|
772
919
|
if (socket.destroyed) {
|
|
920
|
+
emitTelemetry(() => options.nativeTelemetry?.recordError({ errorCode: "native_connection_closed" }));
|
|
773
921
|
throw new NativeNetworkError("Native connection is closed", "native_connection_closed");
|
|
774
922
|
}
|
|
775
923
|
await new Promise((resolve, reject) => {
|
|
@@ -780,6 +928,7 @@ export function createNativeNetworkConnection(socket, proxy, options, idleTimeou
|
|
|
780
928
|
resolve();
|
|
781
929
|
});
|
|
782
930
|
});
|
|
931
|
+
emitTelemetry(() => options.nativeTelemetry?.recordBytes({ direction: "out", bytes: data.byteLength }));
|
|
783
932
|
},
|
|
784
933
|
close: async () => {
|
|
785
934
|
if (socket.closed || socket.destroyed) {
|
|
@@ -808,6 +957,8 @@ async function resolveConnectionProxy(options, input, deadline) {
|
|
|
808
957
|
protocol: options.proxyProtocol,
|
|
809
958
|
credentials: options.credentials,
|
|
810
959
|
gatewaySynthesizers: options.gatewaySynthesizers,
|
|
960
|
+
telemetry: options.telemetry,
|
|
961
|
+
nativeTelemetry: options.nativeTelemetry,
|
|
811
962
|
}), input.signal, deadline);
|
|
812
963
|
if (!resolution.proxy && policy.mode === "required") {
|
|
813
964
|
throw proxyRequiredError(policy, resolution.skips);
|
|
@@ -1201,32 +1352,82 @@ export function createNativeEgressAuthorization(options) {
|
|
|
1201
1352
|
/** Create the SDK byte-stream runtime with provider-declared egress enforcement. */
|
|
1202
1353
|
export function createNativeNetworkClient(options = {}) {
|
|
1203
1354
|
const egress = createNativeEgressAuthorization(options);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
return createNativeNetworkConnection(socket, proxy, options, request.idleTimeoutMs);
|
|
1216
|
-
},
|
|
1217
|
-
connectTls: async (input) => {
|
|
1218
|
-
const request = snapshotNativeConnectInput(input);
|
|
1219
|
-
egress.assertConnect(request, "required");
|
|
1355
|
+
const connect = async (input, kind) => {
|
|
1356
|
+
const startedAt = Date.now();
|
|
1357
|
+
let proxy;
|
|
1358
|
+
let request;
|
|
1359
|
+
let tunnelMs = 0;
|
|
1360
|
+
let outcome = "error";
|
|
1361
|
+
let failure;
|
|
1362
|
+
try {
|
|
1363
|
+
request = snapshotNativeConnectInput(input);
|
|
1364
|
+
const tls = kind === "tls" ? "required" : "disabled";
|
|
1365
|
+
egress.assertConnect(request, tls);
|
|
1220
1366
|
const deadline = deadlineFrom(request.timeoutMs);
|
|
1221
1367
|
assertCanStart(request.signal, deadline);
|
|
1222
|
-
|
|
1223
|
-
egress.assertConnect(request,
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1368
|
+
proxy = await resolveConnectionProxy(options, request, deadline);
|
|
1369
|
+
egress.assertConnect(request, tls);
|
|
1370
|
+
let tunnel;
|
|
1371
|
+
if (proxy) {
|
|
1372
|
+
const tunnelStartedAt = Date.now();
|
|
1373
|
+
try {
|
|
1374
|
+
const destination = request;
|
|
1375
|
+
tunnel = await connectProxyTunnel(proxy, request, deadline, () => egress.assertConnect(destination, tls));
|
|
1376
|
+
}
|
|
1377
|
+
finally {
|
|
1378
|
+
tunnelMs = Math.max(0, Date.now() - tunnelStartedAt);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
const socket = kind === "tls"
|
|
1382
|
+
? await upgradeTls(tunnel, request, deadline)
|
|
1383
|
+
: (tunnel ??
|
|
1384
|
+
(await connectPlainSocket(request.host, request.port, request.signal, deadline)));
|
|
1385
|
+
const connection = createNativeNetworkConnection(socket, proxy, options, request.idleTimeoutMs);
|
|
1386
|
+
outcome = "ok";
|
|
1387
|
+
return connection;
|
|
1388
|
+
}
|
|
1389
|
+
catch (error) {
|
|
1390
|
+
failure = error;
|
|
1391
|
+
throw error;
|
|
1392
|
+
}
|
|
1393
|
+
finally {
|
|
1394
|
+
// One terminal sample for this connect call, including preflight and rethrows.
|
|
1395
|
+
emitTelemetry(() => options.nativeTelemetry?.recordConnect({
|
|
1396
|
+
kind,
|
|
1397
|
+
outcome,
|
|
1398
|
+
ms: Math.max(0, Date.now() - startedAt),
|
|
1399
|
+
tunnelMs,
|
|
1400
|
+
proxyUsed: proxy !== undefined,
|
|
1401
|
+
...(proxy ? { vendor: proxy.vendor } : {}),
|
|
1402
|
+
...(outcome === "error" ? { errorCode: nativeErrorCode(failure) } : {}),
|
|
1403
|
+
diagnostics: {
|
|
1404
|
+
...(request
|
|
1405
|
+
? {
|
|
1406
|
+
host: request.host,
|
|
1407
|
+
port: request.port,
|
|
1408
|
+
...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
|
|
1409
|
+
...(request.idleTimeoutMs === undefined
|
|
1410
|
+
? {}
|
|
1411
|
+
: { idleTimeoutMs: request.idleTimeoutMs }),
|
|
1412
|
+
...(request.serverName ? { serverName: request.serverName } : {}),
|
|
1413
|
+
}
|
|
1414
|
+
: {}),
|
|
1415
|
+
...(proxy
|
|
1416
|
+
? {
|
|
1417
|
+
protocol: new URL(proxy.url).protocol,
|
|
1418
|
+
sticky: proxy.sticky,
|
|
1419
|
+
...(proxy.sessionId ? { sessionId: proxy.sessionId } : {}),
|
|
1420
|
+
...(proxy.expiresAt ? { expiresAt: proxy.expiresAt } : {}),
|
|
1421
|
+
}
|
|
1422
|
+
: {}),
|
|
1423
|
+
...nativeErrorDiagnostics(failure),
|
|
1424
|
+
},
|
|
1425
|
+
}));
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
return {
|
|
1429
|
+
connectTcp: (input) => connect(input, "tcp"),
|
|
1430
|
+
connectTls: (input) => connect(input, "tls"),
|
|
1230
1431
|
grantTcpEgress: (input) => egress.grant(snapshotNativeGrantInput(input)),
|
|
1231
1432
|
};
|
|
1232
1433
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ProviderProxyProvider } from "../types.js";
|
|
2
|
+
import type { NativeNetworkErrorCode } from "./native-network-errors.js";
|
|
3
|
+
import { type ClosedEnum, type TelemetryContributor } from "./request-telemetry.js";
|
|
4
|
+
export type NativeTelemetryErrorCode = NativeNetworkErrorCode | "PROXY_REQUIRED" | "PROXY_ALLOCATION_FAILED" | "other";
|
|
5
|
+
export type NativeTelemetryKind = "tcp" | "tls";
|
|
6
|
+
export type NativeTelemetryOutcome = "ok" | "error";
|
|
7
|
+
export type NativeTelemetryVendorSkipReason = "credentials_absent" | "protocol_unsupported" | "allocation_failed" | "credential_lookup_failed" | "adapter_unavailable";
|
|
8
|
+
export type NativeTelemetryLifecycleKind = "drain" | "drain_acknowledged" | "drain_error" | "drain_missing_handler" | "idle" | "expiry";
|
|
9
|
+
/** Operator-only text is redacted before it is detached and bounded to 300 code units. */
|
|
10
|
+
export type NativeTelemetryDiagnostics = {
|
|
11
|
+
host?: string;
|
|
12
|
+
serverName?: string;
|
|
13
|
+
protocol?: string;
|
|
14
|
+
sessionId?: string;
|
|
15
|
+
expiresAt?: string;
|
|
16
|
+
errorName?: string;
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
causeName?: string;
|
|
19
|
+
causeMessage?: string;
|
|
20
|
+
systemCode?: string;
|
|
21
|
+
missingFields?: readonly string[];
|
|
22
|
+
port?: number;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
idleTimeoutMs?: number;
|
|
25
|
+
status?: number;
|
|
26
|
+
socksReplyCode?: number;
|
|
27
|
+
sticky?: boolean;
|
|
28
|
+
};
|
|
29
|
+
export type NativeConnectTelemetryEvent = {
|
|
30
|
+
kind: NativeTelemetryKind;
|
|
31
|
+
outcome: NativeTelemetryOutcome;
|
|
32
|
+
/** Entire establishment, including lease resolution, tunnel and TLS handshake. */
|
|
33
|
+
ms: number;
|
|
34
|
+
/** Tunnel establishment only; included in ms, never added to it. */
|
|
35
|
+
tunnelMs: number;
|
|
36
|
+
proxyUsed: boolean;
|
|
37
|
+
/** Selected vendor for this connection; operator log only. */
|
|
38
|
+
vendor?: ProviderProxyProvider;
|
|
39
|
+
errorCode?: NativeTelemetryErrorCode;
|
|
40
|
+
diagnostics?: NativeTelemetryDiagnostics;
|
|
41
|
+
};
|
|
42
|
+
export type NativeVendorSkipTelemetryEvent = {
|
|
43
|
+
vendor: ProviderProxyProvider;
|
|
44
|
+
reason: NativeTelemetryVendorSkipReason;
|
|
45
|
+
diagnostics?: NativeTelemetryDiagnostics;
|
|
46
|
+
};
|
|
47
|
+
export type NativeLifecycleTelemetryEvent = {
|
|
48
|
+
kind: NativeTelemetryLifecycleKind;
|
|
49
|
+
errorCode?: NativeTelemetryErrorCode;
|
|
50
|
+
diagnostics?: NativeTelemetryDiagnostics;
|
|
51
|
+
};
|
|
52
|
+
export interface NativeTelemetrySink {
|
|
53
|
+
recordConnect(event: NativeConnectTelemetryEvent): void;
|
|
54
|
+
recordVendorSkip(event: NativeVendorSkipTelemetryEvent): void;
|
|
55
|
+
recordLifecycle(event: NativeLifecycleTelemetryEvent): void;
|
|
56
|
+
/** Application bytes returned by read or accepted by a successful write. Excludes tunnel/TLS framing. */
|
|
57
|
+
recordBytes(event: {
|
|
58
|
+
direction: "in" | "out";
|
|
59
|
+
bytes: number;
|
|
60
|
+
}): void;
|
|
61
|
+
recordError(event: {
|
|
62
|
+
errorCode: NativeTelemetryErrorCode;
|
|
63
|
+
diagnostics?: NativeTelemetryDiagnostics;
|
|
64
|
+
}): void;
|
|
65
|
+
}
|
|
66
|
+
export type NativeAttemptSample = NativeConnectTelemetryEvent & {
|
|
67
|
+
n: number;
|
|
68
|
+
};
|
|
69
|
+
export type NativeTelemetryLogPayload = {
|
|
70
|
+
attempts: number;
|
|
71
|
+
connectMs: number;
|
|
72
|
+
tunnelMs: number;
|
|
73
|
+
vendorSkips: number;
|
|
74
|
+
vendorSkipReasons: {
|
|
75
|
+
reason: NativeTelemetryVendorSkipReason;
|
|
76
|
+
count: number;
|
|
77
|
+
}[];
|
|
78
|
+
drain: number;
|
|
79
|
+
drainAcknowledged: number;
|
|
80
|
+
drainErrors: number;
|
|
81
|
+
drainMissingHandler: number;
|
|
82
|
+
idle: number;
|
|
83
|
+
expiry: number;
|
|
84
|
+
bytesIn: number;
|
|
85
|
+
bytesOut: number;
|
|
86
|
+
lastErrorCode?: NativeTelemetryErrorCode;
|
|
87
|
+
lastErrorDiagnostics?: NativeTelemetryDiagnostics;
|
|
88
|
+
attemptSamples?: NativeAttemptSample[];
|
|
89
|
+
attemptSamplesDropped?: number;
|
|
90
|
+
vendorSkipSamples?: NativeVendorSkipTelemetryEvent[];
|
|
91
|
+
vendorSkipSamplesDropped?: number;
|
|
92
|
+
};
|
|
93
|
+
export type NativeTelemetryHeaderPayload = {
|
|
94
|
+
attempts: number;
|
|
95
|
+
connectMs: number;
|
|
96
|
+
tunnelMs: number;
|
|
97
|
+
vendorSkips: number;
|
|
98
|
+
vendorSkipReasons: {
|
|
99
|
+
reason: ClosedEnum<NativeTelemetryVendorSkipReason>;
|
|
100
|
+
count: number;
|
|
101
|
+
}[];
|
|
102
|
+
drain: number;
|
|
103
|
+
drainAcknowledged: number;
|
|
104
|
+
drainErrors: number;
|
|
105
|
+
drainMissingHandler: number;
|
|
106
|
+
idle: number;
|
|
107
|
+
expiry: number;
|
|
108
|
+
bytesIn: number;
|
|
109
|
+
bytesOut: number;
|
|
110
|
+
lastErrorCode?: ClosedEnum<NativeTelemetryErrorCode>;
|
|
111
|
+
attemptSamples?: {
|
|
112
|
+
n: number;
|
|
113
|
+
kind: ClosedEnum<NativeTelemetryKind>;
|
|
114
|
+
outcome: ClosedEnum<NativeTelemetryOutcome>;
|
|
115
|
+
ms: number;
|
|
116
|
+
tunnelMs: number;
|
|
117
|
+
proxyUsed: boolean;
|
|
118
|
+
errorCode?: ClosedEnum<NativeTelemetryErrorCode>;
|
|
119
|
+
}[];
|
|
120
|
+
attemptSamplesDropped?: number;
|
|
121
|
+
vendorSkipSamplesDropped?: number;
|
|
122
|
+
};
|
|
123
|
+
export declare class NativeTelemetryCollector implements NativeTelemetrySink, TelemetryContributor<NativeTelemetryLogPayload, NativeTelemetryHeaderPayload> {
|
|
124
|
+
#private;
|
|
125
|
+
readonly key: "native";
|
|
126
|
+
constructor(options?: {
|
|
127
|
+
redact?: (text: string) => string;
|
|
128
|
+
});
|
|
129
|
+
recordConnect(event: NativeConnectTelemetryEvent): void;
|
|
130
|
+
recordVendorSkip(event: NativeVendorSkipTelemetryEvent): void;
|
|
131
|
+
recordLifecycle(event: NativeLifecycleTelemetryEvent): void;
|
|
132
|
+
recordBytes(event: {
|
|
133
|
+
direction: "in" | "out";
|
|
134
|
+
bytes: number;
|
|
135
|
+
}): void;
|
|
136
|
+
recordError(event: {
|
|
137
|
+
errorCode: NativeTelemetryErrorCode;
|
|
138
|
+
diagnostics?: NativeTelemetryDiagnostics;
|
|
139
|
+
}): void;
|
|
140
|
+
toLogPayload(): NativeTelemetryLogPayload | undefined;
|
|
141
|
+
toHeaderPayload(log: NativeTelemetryLogPayload): NativeTelemetryHeaderPayload;
|
|
142
|
+
}
|