@apifuse/provider-sdk 2.2.0-beta.61 → 2.2.0-beta.63
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 +268 -9
- package/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/bin/apifuse-check.ts +84 -2
- package/bin/apifuse-dev.ts +11 -31
- package/bin/apifuse-migrate-deployment.ts +200 -0
- package/bin/apifuse-pack-check.ts +11 -4
- package/bin/apifuse-pack-types.ts +191 -4
- package/bin/apifuse-record.ts +44 -32
- package/bin/apifuse-test.ts +3 -1
- package/dist/cli/commands.d.ts +1 -1
- package/dist/cli/commands.js +11 -0
- package/dist/cli/deployment-intent.d.ts +178 -0
- package/dist/cli/deployment-intent.js +277 -0
- package/dist/cli/migrate-deployment-intent.d.ts +132 -0
- package/dist/cli/migrate-deployment-intent.js +1552 -0
- package/dist/cli/migrate-provider-shape.d.ts +5 -1
- package/dist/cli/migrate-provider-shape.js +4 -2
- package/dist/cli/resolver-runtime.d.ts +21 -0
- package/dist/cli/resolver-runtime.js +31 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +5 -0
- package/dist/dev.d.ts +3 -1
- package/dist/dev.js +6 -1
- package/dist/engine.d.ts +35 -1
- package/dist/engine.js +21 -2
- package/dist/error-resolution.d.ts +1 -0
- package/dist/error-resolution.js +54 -0
- package/dist/errors.d.ts +39 -1
- package/dist/errors.js +50 -0
- package/dist/handle-meta.d.ts +18 -2
- package/dist/handle-meta.js +30 -2
- package/dist/handle.d.ts +33 -12
- package/dist/handle.js +69 -24
- package/dist/i18n/error-messages.d.ts +86 -0
- package/dist/i18n/error-messages.js +192 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +1 -0
- package/dist/i18n/keys.d.ts +12 -0
- package/dist/i18n/keys.js +76 -5
- package/dist/index.d.ts +10 -2
- package/dist/index.js +9 -1
- package/dist/lint.d.ts +10 -0
- package/dist/lint.js +440 -57
- package/dist/provider.d.ts +7 -5
- package/dist/provider.js +4 -2
- package/dist/runtime/browser-telemetry-binding.d.ts +5 -0
- package/dist/runtime/browser-telemetry-binding.js +83 -0
- package/dist/runtime/browser-telemetry.d.ts +76 -0
- package/dist/runtime/browser-telemetry.js +165 -0
- package/dist/runtime/browser.d.ts +3 -0
- package/dist/runtime/browser.js +208 -47
- package/dist/runtime/cache-telemetry.d.ts +71 -0
- package/dist/runtime/cache-telemetry.js +177 -0
- package/dist/runtime/cache.js +67 -7
- package/dist/runtime/capability-response.d.ts +3 -0
- package/dist/runtime/capability-response.js +56 -0
- package/dist/runtime/capability-telemetry.d.ts +46 -0
- package/dist/runtime/capability-telemetry.js +68 -0
- package/dist/runtime/diagnostic-env.d.ts +3 -0
- package/dist/runtime/diagnostic-env.js +10 -1
- package/dist/runtime/engine-mode.d.ts +74 -0
- package/dist/runtime/engine-mode.js +152 -0
- package/dist/runtime/handle.js +15 -10
- 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 +16 -0
- package/dist/runtime/http-telemetry-guard.js +202 -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 +356 -174
- package/dist/runtime/instrumentation.js +92 -13
- package/dist/runtime/ocr-telemetry.d.ts +82 -0
- package/dist/runtime/ocr-telemetry.js +192 -0
- package/dist/runtime/ocr.js +30 -8
- package/dist/runtime/request-telemetry.d.ts +2 -0
- package/dist/runtime/request-telemetry.js +2 -1
- package/dist/runtime/resolver-runtime-options.d.ts +14 -0
- package/dist/runtime/resolver-runtime-options.js +33 -0
- package/dist/runtime/resolver-shared.d.ts +3 -2
- package/dist/runtime/resolver-shared.js +4 -4
- package/dist/runtime/resolver-telemetry.d.ts +1 -0
- package/dist/runtime/resolver-telemetry.js +1 -1
- package/dist/runtime/resolver-vendors/browser.d.ts +2 -0
- package/dist/runtime/resolver-vendors/browser.js +46 -0
- package/dist/runtime/resolver-vendors/hosts.d.ts +21 -0
- package/dist/runtime/resolver-vendors/hosts.js +35 -0
- package/dist/runtime/resolver-vendors/hypersolutions.js +2 -2
- package/dist/runtime/resolver-vendors/types.d.ts +5 -4
- package/dist/runtime/resolver.d.ts +4 -1
- package/dist/runtime/resolver.js +46 -13
- package/dist/runtime/state-telemetry.d.ts +79 -0
- package/dist/runtime/state-telemetry.js +194 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +26 -7
- package/dist/runtime/stealth-cookies.d.ts +5 -1
- package/dist/runtime/stealth-cookies.js +21 -1
- package/dist/runtime/stealth-owned-headers.d.ts +26 -0
- package/dist/runtime/stealth-owned-headers.js +37 -0
- package/dist/runtime/stealth-telemetry.d.ts +92 -0
- package/dist/runtime/stealth-telemetry.js +179 -0
- package/dist/runtime/stealth.d.ts +4 -1
- package/dist/runtime/stealth.js +228 -85
- package/dist/runtime/stt-telemetry.d.ts +77 -0
- package/dist/runtime/stt-telemetry.js +176 -0
- package/dist/runtime/stt.js +15 -6
- package/dist/server/error-stack-frames.d.ts +35 -0
- package/dist/server/error-stack-frames.js +219 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.js +5 -0
- package/dist/server/sdk-version.d.ts +3 -0
- package/dist/server/sdk-version.js +19 -0
- package/dist/server/self-test.js +1 -18
- package/dist/server/sensitive-values.d.ts +1 -1
- package/dist/server/sensitive-values.js +9 -0
- package/dist/server/serve-implementation.d.ts +53 -2
- package/dist/server/serve-implementation.js +369 -123
- package/dist/server/types.d.ts +1 -0
- package/dist/server/types.js +7 -0
- package/dist/stateful/stateful-provider-owner-forwarder.js +22 -0
- package/dist/stateful/stateful-provider-runtime-context.d.ts +3 -0
- package/dist/stateful/stateful-provider-runtime-context.js +1 -0
- package/dist/types.d.ts +94 -1
- package/package.json +6 -1
- package/src/cli/commands.ts +14 -0
- package/src/cli/deployment-intent.ts +364 -0
- package/src/cli/migrate-deployment-intent.ts +1908 -0
- package/src/cli/migrate-provider-shape.ts +10 -4
- package/src/cli/resolver-runtime.ts +72 -0
- package/src/define.ts +13 -0
- package/src/dev.ts +9 -2
- package/src/engine.ts +38 -2
- package/src/error-resolution.ts +58 -0
- package/src/errors.ts +77 -1
- package/src/handle-meta.ts +39 -4
- package/src/handle.ts +121 -38
- package/src/i18n/error-messages.ts +255 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/keys.ts +80 -6
- package/src/index.ts +91 -0
- package/src/lint.ts +552 -57
- package/src/provider.ts +20 -0
- package/src/runtime/browser-telemetry-binding.ts +92 -0
- package/src/runtime/browser-telemetry.ts +289 -0
- package/src/runtime/browser.ts +249 -65
- package/src/runtime/cache-telemetry.ts +247 -0
- package/src/runtime/cache.ts +68 -17
- package/src/runtime/capability-response.ts +53 -0
- package/src/runtime/capability-telemetry.ts +101 -0
- package/src/runtime/diagnostic-env.ts +11 -1
- package/src/runtime/engine-mode.ts +233 -0
- package/src/runtime/handle.ts +20 -10
- package/src/runtime/http-telemetry-binding.ts +162 -0
- package/src/runtime/http-telemetry-guard.ts +208 -0
- package/src/runtime/http-telemetry.ts +292 -0
- package/src/runtime/http.ts +457 -204
- package/src/runtime/instrumentation.ts +105 -16
- package/src/runtime/ocr-telemetry.ts +308 -0
- package/src/runtime/ocr.ts +109 -74
- package/src/runtime/request-telemetry.ts +2 -1
- package/src/runtime/resolver-runtime-options.ts +50 -0
- package/src/runtime/resolver-shared.ts +9 -4
- package/src/runtime/resolver-telemetry.ts +1 -1
- package/src/runtime/resolver-vendors/browser.ts +65 -7
- package/src/runtime/resolver-vendors/hosts.ts +43 -0
- package/src/runtime/resolver-vendors/hypersolutions.ts +2 -2
- package/src/runtime/resolver-vendors/types.ts +5 -4
- package/src/runtime/resolver.ts +64 -21
- package/src/runtime/state-telemetry.ts +298 -0
- package/src/runtime/state.ts +46 -45
- package/src/runtime/stealth-cookies.ts +24 -1
- package/src/runtime/stealth-owned-headers.ts +41 -0
- package/src/runtime/stealth-telemetry.ts +304 -0
- package/src/runtime/stealth.ts +355 -150
- package/src/runtime/stt-telemetry.ts +282 -0
- package/src/runtime/stt.ts +93 -71
- package/src/server/error-stack-frames.ts +214 -0
- package/src/server/index.ts +66 -0
- package/src/server/sdk-version.ts +22 -0
- package/src/server/self-test.ts +1 -21
- package/src/server/sensitive-values.ts +14 -0
- package/src/server/serve-implementation.ts +601 -157
- package/src/server/types.ts +7 -0
- package/src/stateful/stateful-provider-owner-forwarder.ts +23 -0
- package/src/stateful/stateful-provider-runtime-context.ts +4 -0
- package/src/types.ts +99 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/approval-override.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/codemod-syntax.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/connection-precedence.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-a.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-b.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-ambiguous-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-computed.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-a.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-b.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-cycle-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-barrel.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-catalog.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-docs.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-daiso-stores.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-indirect-config.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-nonstatic.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-leaf.ts.txt +0 -10
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-one.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-reexport-two.ts.txt +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-one.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-composed-two.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-mixed-index.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-runtime-registry.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-barrel.ts.txt +0 -2
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-index.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/discovery-star-package-leaf.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/docs-conflict.ts.txt +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-hoisted-array-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-imported-array-spread.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-kebab-operation-id.ts.txt +0 -17
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-map.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-mixed-array-spread.ts.txt +0 -24
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-operation.ts.txt +0 -16
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/examples-snake-operation-id.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-duplicate-provider.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-id-unresolved.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-map.ts.txt +0 -3
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-many.ts.txt +0 -18
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/factory-one-to-one.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoist-all.ts.txt +0 -31
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/hoisted-const.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/imported-spread.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-map.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-cast-tail.ts.txt +0 -21
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-ekitan.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/inline-spread-override.ts.txt +0 -14
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-en.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ja.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-canonical-ko.json +0 -1
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/locale-existing-operation-namespace-en.json +0 -8
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/missing-english-locale.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/no-safety.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/non-literal.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/redundant-approval.ts.txt +0 -6
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-binding-ambiguous.ts.txt +0 -12
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported-other.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-imported.ts.txt +0 -5
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-key-ambiguous.ts.txt +0 -19
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-shorthand.ts.txt +0 -9
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-typed.ts.txt +0 -11
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/registry-unrelated.ts.txt +0 -13
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/safety-conflict.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/stream.ts.txt +0 -7
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/tool-router-spread.ts.txt +0 -15
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/unparseable.ts.txt +0 -4
- package/src/cli/__tests__/fixtures/migrate-operation-declaration/verbatim-template.ts.txt +0 -12
package/src/runtime/state.ts
CHANGED
|
@@ -119,10 +119,7 @@ type RedisBackend = {
|
|
|
119
119
|
|
|
120
120
|
const redisBackends = new Map<string, RedisBackend>();
|
|
121
121
|
|
|
122
|
-
function getRedisBackend(
|
|
123
|
-
redisUrl: string,
|
|
124
|
-
injectedRedis?: ProviderRedisClient,
|
|
125
|
-
): RedisBackend {
|
|
122
|
+
function getRedisBackend(redisUrl: string, injectedRedis?: ProviderRedisClient): RedisBackend {
|
|
126
123
|
const existing = redisBackends.get(redisUrl);
|
|
127
124
|
if (existing) return existing;
|
|
128
125
|
const redis =
|
|
@@ -287,20 +284,11 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
287
284
|
) {}
|
|
288
285
|
|
|
289
286
|
private redisKey(key: string): string {
|
|
290
|
-
return providerStateKey(
|
|
291
|
-
this.providerId,
|
|
292
|
-
this.namespaceName,
|
|
293
|
-
this.scopeDiscriminator,
|
|
294
|
-
key,
|
|
295
|
-
);
|
|
287
|
+
return providerStateKey(this.providerId, this.namespaceName, this.scopeDiscriminator, key);
|
|
296
288
|
}
|
|
297
289
|
|
|
298
290
|
private statePrefix(): string {
|
|
299
|
-
return providerStatePrefix(
|
|
300
|
-
this.providerId,
|
|
301
|
-
this.namespaceName,
|
|
302
|
-
this.scopeDiscriminator,
|
|
303
|
-
);
|
|
291
|
+
return providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator);
|
|
304
292
|
}
|
|
305
293
|
|
|
306
294
|
private indexKey(): string {
|
|
@@ -308,11 +296,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
308
296
|
// any state key (including "__index"), so a suffix inside the namespace
|
|
309
297
|
// could turn the ZSET into a string and break every subsequent write.
|
|
310
298
|
const namespaceIdentity = Buffer.from(
|
|
311
|
-
providerStatePrefix(
|
|
312
|
-
this.providerId,
|
|
313
|
-
this.namespaceName,
|
|
314
|
-
this.scopeDiscriminator,
|
|
315
|
-
),
|
|
299
|
+
providerStatePrefix(this.providerId, this.namespaceName, this.scopeDiscriminator),
|
|
316
300
|
"utf8",
|
|
317
301
|
).toString("base64url");
|
|
318
302
|
return `${REDIS_STATE_PREFIX}:index:${namespaceIdentity}`;
|
|
@@ -342,15 +326,17 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
342
326
|
private enforceValueSize(value: unknown): void {
|
|
343
327
|
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
344
328
|
if (bytes > this.options.maxValueBytes) {
|
|
345
|
-
throw
|
|
329
|
+
throw stateViolationError(
|
|
346
330
|
`Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
|
|
331
|
+
"size",
|
|
347
332
|
);
|
|
348
333
|
}
|
|
349
334
|
}
|
|
350
335
|
|
|
351
336
|
private quotaExceeded(): UnsupportedProviderStateError {
|
|
352
|
-
return
|
|
337
|
+
return stateViolationError(
|
|
353
338
|
`Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
|
|
339
|
+
"quota",
|
|
354
340
|
);
|
|
355
341
|
}
|
|
356
342
|
|
|
@@ -362,8 +348,9 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
362
348
|
const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
|
|
363
349
|
const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
|
|
364
350
|
if (ttlMs > maxTtlMs) {
|
|
365
|
-
throw
|
|
351
|
+
throw stateViolationError(
|
|
366
352
|
`Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
|
|
353
|
+
"ttl",
|
|
367
354
|
);
|
|
368
355
|
}
|
|
369
356
|
const expiresAtMs = Date.now() + ttlMs;
|
|
@@ -376,7 +363,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
376
363
|
|
|
377
364
|
async list<T>(options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
|
|
378
365
|
const requestedLimit = Math.max(0, options?.limit ?? this.options.maxEntries);
|
|
379
|
-
if (requestedLimit === 0) return [];
|
|
366
|
+
if (requestedLimit === 0) return recordStateRedisNoop([]);
|
|
380
367
|
const keys = await this.indexedKeys(this.options.maxEntries);
|
|
381
368
|
if (keys.length === 0) return [];
|
|
382
369
|
const values = await withRequiredRedis(() => this.backend.redis.mget(keys));
|
|
@@ -453,12 +440,7 @@ class RedisProviderStateNamespace implements ProviderStateNamespace {
|
|
|
453
440
|
this.enforceValueSize(value);
|
|
454
441
|
const createdAt = new Date().toISOString();
|
|
455
442
|
const timing = this.writeTiming(options?.ttl);
|
|
456
|
-
const envelope = redisEnvelope(
|
|
457
|
-
value,
|
|
458
|
-
expectedVersion + 1,
|
|
459
|
-
createdAt,
|
|
460
|
-
timing.expiresAt,
|
|
461
|
-
);
|
|
443
|
+
const envelope = redisEnvelope(value, expectedVersion + 1, createdAt, timing.expiresAt);
|
|
462
444
|
await requireRedisReady(this.backend.redis);
|
|
463
445
|
const result = await withRequiredRedis(() =>
|
|
464
446
|
this.backend.redis.eval(
|
|
@@ -556,9 +538,34 @@ export class UnsupportedProviderStateError extends ProviderError {
|
|
|
556
538
|
constructor(message = "Provider runtime state is not available in this runtime") {
|
|
557
539
|
super(message, { code: "PROVIDER_STATE_UNSUPPORTED" });
|
|
558
540
|
this.name = "UnsupportedProviderStateError";
|
|
541
|
+
stateViolationKinds.set(this, "unsupported");
|
|
559
542
|
}
|
|
560
543
|
}
|
|
561
544
|
|
|
545
|
+
// Keep the exact no-command result out of band, without inspecting caller options twice.
|
|
546
|
+
const stateRedisNoopResults = new WeakSet<object>();
|
|
547
|
+
function recordStateRedisNoop<T extends object>(result: T): T {
|
|
548
|
+
stateRedisNoopResults.add(result);
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
export function isStateRedisNoop(result: unknown): boolean {
|
|
552
|
+
return result !== null && typeof result === "object" && stateRedisNoopResults.has(result);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
type StateViolationKind = "quota" | "ttl" | "size" | "unsupported";
|
|
556
|
+
const stateViolationKinds = new WeakMap<object, StateViolationKind>();
|
|
557
|
+
function stateViolationError(
|
|
558
|
+
message: string,
|
|
559
|
+
kind: Exclude<StateViolationKind, "unsupported">,
|
|
560
|
+
): UnsupportedProviderStateError {
|
|
561
|
+
const error = new UnsupportedProviderStateError(message);
|
|
562
|
+
stateViolationKinds.set(error, kind);
|
|
563
|
+
return error;
|
|
564
|
+
}
|
|
565
|
+
export function getStateViolation(error: unknown): StateViolationKind | undefined {
|
|
566
|
+
return error !== null && typeof error === "object" ? stateViolationKinds.get(error) : undefined;
|
|
567
|
+
}
|
|
568
|
+
|
|
562
569
|
class UnsupportedProviderStateNamespace implements ProviderStateNamespace {
|
|
563
570
|
async list<T>(_options?: { limit?: number; prefix?: string }): Promise<StateValue<T>[]> {
|
|
564
571
|
throw new UnsupportedProviderStateError();
|
|
@@ -619,8 +626,9 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
|
|
|
619
626
|
private enforceValueSize(value: unknown): void {
|
|
620
627
|
const bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
621
628
|
if (bytes > this.options.maxValueBytes) {
|
|
622
|
-
throw
|
|
629
|
+
throw stateViolationError(
|
|
623
630
|
`Provider runtime state value exceeds maxValueBytes (${bytes} > ${this.options.maxValueBytes})`,
|
|
631
|
+
"size",
|
|
624
632
|
);
|
|
625
633
|
}
|
|
626
634
|
}
|
|
@@ -630,13 +638,15 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
|
|
|
630
638
|
const ttlMs = parseStateDurationMs(ttl ?? this.options.defaultTtl);
|
|
631
639
|
const maxTtlMs = parseStateDurationMs(this.options.maxTtl);
|
|
632
640
|
if (ttlMs > maxTtlMs) {
|
|
633
|
-
throw
|
|
641
|
+
throw stateViolationError(
|
|
634
642
|
`Provider runtime state ttl exceeds maxTtl (${ttlMs} > ${maxTtlMs})`,
|
|
643
|
+
"ttl",
|
|
635
644
|
);
|
|
636
645
|
}
|
|
637
646
|
if (!this.values.has(key) && this.values.size >= this.options.maxEntries) {
|
|
638
|
-
throw
|
|
647
|
+
throw stateViolationError(
|
|
639
648
|
`Provider runtime state namespace quota exceeded (${this.options.maxEntries + 1} > ${this.options.maxEntries})`,
|
|
649
|
+
"quota",
|
|
640
650
|
);
|
|
641
651
|
}
|
|
642
652
|
}
|
|
@@ -710,10 +720,7 @@ class MemoryProviderStateNamespace implements ProviderStateNamespace {
|
|
|
710
720
|
key,
|
|
711
721
|
value,
|
|
712
722
|
version: expectedVersion + 1,
|
|
713
|
-
expiresAt: resolveMemoryStateExpiresAt(
|
|
714
|
-
options?.ttl ?? this.options.defaultTtl,
|
|
715
|
-
this.now(),
|
|
716
|
-
),
|
|
723
|
+
expiresAt: resolveMemoryStateExpiresAt(options?.ttl ?? this.options.defaultTtl, this.now()),
|
|
717
724
|
createdAt: current?.createdAt ?? now,
|
|
718
725
|
updatedAt: now,
|
|
719
726
|
} satisfies StateValue<T>;
|
|
@@ -753,10 +760,7 @@ class MemoryProviderRuntimeState implements ProviderRuntimeState {
|
|
|
753
760
|
) {}
|
|
754
761
|
|
|
755
762
|
forConnection(connectionId: string | undefined): ProviderRuntimeState {
|
|
756
|
-
return new MemoryProviderRuntimeState(
|
|
757
|
-
this.backend,
|
|
758
|
-
connectionScopeDiscriminator(connectionId),
|
|
759
|
-
);
|
|
763
|
+
return new MemoryProviderRuntimeState(this.backend, connectionScopeDiscriminator(connectionId));
|
|
760
764
|
}
|
|
761
765
|
|
|
762
766
|
namespace(name: string, options: StateNamespaceOptions): ProviderStateNamespace {
|
|
@@ -771,10 +775,7 @@ class MemoryProviderRuntimeState implements ProviderRuntimeState {
|
|
|
771
775
|
}
|
|
772
776
|
}
|
|
773
777
|
|
|
774
|
-
function resolveMemoryStateExpiresAt(
|
|
775
|
-
ttl: StateWriteOptions["ttl"],
|
|
776
|
-
nowMs = Date.now(),
|
|
777
|
-
): string {
|
|
778
|
+
function resolveMemoryStateExpiresAt(ttl: StateWriteOptions["ttl"], nowMs = Date.now()): string {
|
|
778
779
|
const match = /^(\d+)(ms|s|m|h|d)$/.exec(ttl ?? "1h");
|
|
779
780
|
if (!match) return new Date(nowMs + 3_600_000).toISOString();
|
|
780
781
|
const amount = Number(match[1]);
|
|
@@ -14,16 +14,32 @@ function isRecord(value: unknown): value is Record<PropertyKey, unknown> {
|
|
|
14
14
|
|
|
15
15
|
const LEGACY_COOKIE_ORIGIN = "https://legacy-cookie.invalid/";
|
|
16
16
|
|
|
17
|
+
/** Extract cookie values from Set-Cookie/header strings for request-scoped registration. */
|
|
18
|
+
export function cookieValuesFromHeaders(headers: string | readonly string[]): string[] {
|
|
19
|
+
const values: string[] = [];
|
|
20
|
+
for (const header of typeof headers === "string" ? [headers] : headers) {
|
|
21
|
+
const cookie = Cookie.parse(header);
|
|
22
|
+
if (cookie?.value) values.push(cookie.value);
|
|
23
|
+
}
|
|
24
|
+
return values;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
export class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
18
28
|
private cookies: ToughCookieJar;
|
|
19
29
|
private readonly defaultUrl: string;
|
|
30
|
+
private readonly register: ((value: string) => void) | undefined;
|
|
20
31
|
|
|
21
|
-
constructor(
|
|
32
|
+
constructor(
|
|
33
|
+
cookieStrings: readonly string[],
|
|
34
|
+
defaultUrl = LEGACY_COOKIE_ORIGIN,
|
|
35
|
+
register?: (value: string) => void,
|
|
36
|
+
) {
|
|
22
37
|
this.cookies = new ToughCookieJar(undefined, {
|
|
23
38
|
allowSecureOnLocal: false,
|
|
24
39
|
rejectPublicSuffixes: true,
|
|
25
40
|
});
|
|
26
41
|
this.defaultUrl = this.normalizeUrl(defaultUrl) ?? LEGACY_COOKIE_ORIGIN;
|
|
42
|
+
this.register = register;
|
|
27
43
|
this.setFromCookieStrings(cookieStrings);
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -31,6 +47,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
|
31
47
|
const cookieUrl = this.normalizeUrl(url);
|
|
32
48
|
if (!cookieUrl) return;
|
|
33
49
|
for (const cookieString of cookieStrings) {
|
|
50
|
+
for (const value of cookieValuesFromHeaders(cookieString)) this.register?.(value);
|
|
34
51
|
this.cookies.setCookieSync(cookieString, cookieUrl, { ignoreError: true });
|
|
35
52
|
}
|
|
36
53
|
}
|
|
@@ -73,6 +90,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
|
73
90
|
this.clear();
|
|
74
91
|
for (const [name, value] of Object.entries(cookies)) {
|
|
75
92
|
if (!name) continue;
|
|
93
|
+
this.register?.(value);
|
|
76
94
|
this.cookies.setCookieSync(new Cookie({ key: name, path: "/", value }), this.defaultUrl, {
|
|
77
95
|
ignoreError: true,
|
|
78
96
|
});
|
|
@@ -111,6 +129,10 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
|
111
129
|
return undefined;
|
|
112
130
|
}
|
|
113
131
|
|
|
132
|
+
names(url?: string): string[] {
|
|
133
|
+
return this.getUniqueCookies(url ?? this.defaultUrl).map((cookie) => cookie.key);
|
|
134
|
+
}
|
|
135
|
+
|
|
114
136
|
private normalizeUrl(url: string): string | undefined {
|
|
115
137
|
try {
|
|
116
138
|
return new URL(url).toString();
|
|
@@ -124,6 +146,7 @@ export class StealthCookieJar implements CookieJar, StealthSessionCookies {
|
|
|
124
146
|
if (!cookieUrl) return [];
|
|
125
147
|
const names = new Set<string>();
|
|
126
148
|
return this.cookies.getCookiesSync(cookieUrl).filter((cookie) => {
|
|
149
|
+
if (cookie.value) this.register?.(cookie.value);
|
|
127
150
|
if (names.has(cookie.key)) return false;
|
|
128
151
|
names.add(cookie.key);
|
|
129
152
|
return true;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header names the stealth transport owns.
|
|
3
|
+
*
|
|
4
|
+
* `ctx.stealth` composes these from the Chrome emulation and the request class,
|
|
5
|
+
* so a caller value is rejected at request time with `SDKError` code
|
|
6
|
+
* `STEALTH_HEADER_OVERRIDE_UNSUPPORTED` (`assertCallerHeadersSupported` in
|
|
7
|
+
* stealth.ts). The `browser-version-literal` lint reports a subset of these
|
|
8
|
+
* names — the `sec-fetch-*` and `sec-ch-ua*` families — in files that use
|
|
9
|
+
* `ctx.stealth` (`isReportedOwnedHeaderName` in lint.ts). Both read this
|
|
10
|
+
* module, so the lint cannot claim a rejection the runtime does not perform;
|
|
11
|
+
* it reports deliberately less than the runtime rejects (host, connection,
|
|
12
|
+
* accept-encoding and a non-literal user-agent stay silent — see that
|
|
13
|
+
* predicate for why), so a green `apifuse check` is not proof that no owned
|
|
14
|
+
* header is set.
|
|
15
|
+
*
|
|
16
|
+
* Leaf module by design: no imports, so the lazily loaded CLI lint path does
|
|
17
|
+
* not pull the stealth runtime in.
|
|
18
|
+
*/
|
|
19
|
+
export const SDK_OWNED_EXACT_CHROME_HEADERS: ReadonlySet<string> = new Set([
|
|
20
|
+
"host",
|
|
21
|
+
"connection",
|
|
22
|
+
"user-agent",
|
|
23
|
+
"sec-ch-ua",
|
|
24
|
+
"sec-ch-ua-mobile",
|
|
25
|
+
"sec-ch-ua-platform",
|
|
26
|
+
"accept-encoding",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const SDK_OWNED_CHROME_HEADER_PREFIX = "sec-fetch-";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* True when `ctx.stealth` rejects a caller value for `name` (case-insensitive):
|
|
33
|
+
* an exact member of {@link SDK_OWNED_EXACT_CHROME_HEADERS} or any
|
|
34
|
+
* `sec-fetch-*` name.
|
|
35
|
+
*/
|
|
36
|
+
export function isStealthOwnedHeaderName(name: string): boolean {
|
|
37
|
+
const lower = name.toLowerCase();
|
|
38
|
+
return (
|
|
39
|
+
SDK_OWNED_EXACT_CHROME_HEADERS.has(lower) || lower.startsWith(SDK_OWNED_CHROME_HEADER_PREFIX)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
StealthBrowser,
|
|
5
|
+
StealthChallengeClassification,
|
|
6
|
+
StealthOS,
|
|
7
|
+
StealthProfileDescriptor,
|
|
8
|
+
} from "../types.js";
|
|
9
|
+
import { type ClosedEnum, closedEnum, type TelemetryContributor } from "./request-telemetry.js";
|
|
10
|
+
|
|
11
|
+
export type StealthTelemetryErrorCode =
|
|
12
|
+
| "transport_network_error"
|
|
13
|
+
| "transport_timeout"
|
|
14
|
+
| "transport_cancelled"
|
|
15
|
+
| "upstream_http_error"
|
|
16
|
+
| "response_too_large"
|
|
17
|
+
| "proxy_connect_failed"
|
|
18
|
+
| "PROXY_POOL_STALE"
|
|
19
|
+
| "PROXY_EDGE_AUTH_REJECTED"
|
|
20
|
+
| "PROXY_AUTH_IP_DENIED"
|
|
21
|
+
| "PROXY_EDGE_TLS_REJECTED"
|
|
22
|
+
| "PROXY_REQUIRED"
|
|
23
|
+
| "other";
|
|
24
|
+
|
|
25
|
+
export type StealthTelemetryRequestClass = "navigation" | "script_navigation" | "xhr" | "post";
|
|
26
|
+
|
|
27
|
+
export type StealthTelemetryAttemptKind = "request" | "resolver" | "proxy_diagnostic";
|
|
28
|
+
|
|
29
|
+
export type StealthTelemetrySbsdOutcome =
|
|
30
|
+
| StealthChallengeClassification["outcome"]
|
|
31
|
+
| "detected"
|
|
32
|
+
| "refetch_clear";
|
|
33
|
+
|
|
34
|
+
export type StealthTelemetryDiagnostics = {
|
|
35
|
+
readonly name?: string;
|
|
36
|
+
readonly message?: string;
|
|
37
|
+
readonly code?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type StealthTelemetryAttemptEvent = {
|
|
41
|
+
readonly ms: number;
|
|
42
|
+
readonly status?: number;
|
|
43
|
+
readonly errorCode?: StealthTelemetryErrorCode;
|
|
44
|
+
readonly diagnostics?: StealthTelemetryDiagnostics;
|
|
45
|
+
readonly profileId: StealthProfileDescriptor;
|
|
46
|
+
readonly proxyUsed: boolean;
|
|
47
|
+
readonly requestClass: StealthTelemetryRequestClass;
|
|
48
|
+
readonly kind?: StealthTelemetryAttemptKind;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type StealthTelemetrySbsdEvent = {
|
|
52
|
+
readonly detected: boolean;
|
|
53
|
+
readonly outcome?: StealthTelemetrySbsdOutcome;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export interface StealthTelemetrySink {
|
|
57
|
+
recordAttempt(event: StealthTelemetryAttemptEvent): void;
|
|
58
|
+
recordPoolRefresh(): void;
|
|
59
|
+
recordRedirectHop(): void;
|
|
60
|
+
recordSbsd(event?: StealthTelemetrySbsdEvent | StealthTelemetrySbsdOutcome): void;
|
|
61
|
+
recordSafeRefetch(): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type StealthTelemetryAttemptSample = {
|
|
65
|
+
n: number;
|
|
66
|
+
ms: number;
|
|
67
|
+
status?: number;
|
|
68
|
+
e?: StealthTelemetryErrorCode;
|
|
69
|
+
kind?: StealthTelemetryAttemptKind;
|
|
70
|
+
diagnostics?: StealthTelemetryDiagnostics;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type StealthTelemetryLogPayload = {
|
|
74
|
+
attempts: number;
|
|
75
|
+
poolRefreshes: number;
|
|
76
|
+
redirectHops: number;
|
|
77
|
+
profileId?: StealthProfileDescriptor;
|
|
78
|
+
proxyUsed: boolean;
|
|
79
|
+
requestClass?: StealthTelemetryRequestClass;
|
|
80
|
+
sbsdDetected?: boolean;
|
|
81
|
+
sbsdOutcome?: StealthTelemetrySbsdOutcome;
|
|
82
|
+
safeRefetch: number;
|
|
83
|
+
lastStatus?: number;
|
|
84
|
+
ms: number;
|
|
85
|
+
attemptSamples?: StealthTelemetryAttemptSample[];
|
|
86
|
+
attemptSamplesDropped?: number;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type StealthTelemetryHeaderPayload = {
|
|
90
|
+
attempts: number;
|
|
91
|
+
poolRefreshes: number;
|
|
92
|
+
redirectHops: number;
|
|
93
|
+
profileId?: {
|
|
94
|
+
browser: ClosedEnum<StealthBrowser>;
|
|
95
|
+
os: ClosedEnum<StealthOS>;
|
|
96
|
+
};
|
|
97
|
+
proxyUsed: boolean;
|
|
98
|
+
requestClass?: ClosedEnum<StealthTelemetryRequestClass>;
|
|
99
|
+
sbsdDetected?: boolean;
|
|
100
|
+
sbsdOutcome?: ClosedEnum<StealthTelemetrySbsdOutcome>;
|
|
101
|
+
safeRefetch: number;
|
|
102
|
+
lastStatus?: number;
|
|
103
|
+
ms: number;
|
|
104
|
+
attemptSamples?: {
|
|
105
|
+
n: number;
|
|
106
|
+
ms: number;
|
|
107
|
+
status?: number;
|
|
108
|
+
e?: ClosedEnum<StealthTelemetryErrorCode>;
|
|
109
|
+
kind?: ClosedEnum<StealthTelemetryAttemptKind>;
|
|
110
|
+
}[];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const MAX_ATTEMPT_SAMPLES = 24;
|
|
114
|
+
const MAX_DIAGNOSTIC_TEXT = 300;
|
|
115
|
+
|
|
116
|
+
function boundedInteger(value: number): number {
|
|
117
|
+
if (!Number.isFinite(value)) return Number.MAX_SAFE_INTEGER;
|
|
118
|
+
return Math.min(Number.MAX_SAFE_INTEGER, Math.max(0, Math.floor(value)));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function saturatingAdd(a: number, b: number): number {
|
|
122
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) return Number.MAX_SAFE_INTEGER;
|
|
123
|
+
return Math.min(Number.MAX_SAFE_INTEGER, boundedInteger(a) + boundedInteger(b));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function boundedText(value: string, redact?: (text: string) => string): string {
|
|
127
|
+
let result = value;
|
|
128
|
+
try {
|
|
129
|
+
result = redact?.(value) ?? value;
|
|
130
|
+
} catch {
|
|
131
|
+
result = "[REDACTION_FAILED]";
|
|
132
|
+
}
|
|
133
|
+
const length = Math.min(result.length, MAX_DIAGNOSTIC_TEXT);
|
|
134
|
+
const units = Buffer.alloc(length * 2);
|
|
135
|
+
for (let index = 0; index < length; index += 1) {
|
|
136
|
+
units.writeUInt16LE(result.charCodeAt(index), index * 2);
|
|
137
|
+
}
|
|
138
|
+
return units.toString("utf16le");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function cloneDiagnostics(
|
|
142
|
+
diagnostics: StealthTelemetryDiagnostics | undefined,
|
|
143
|
+
redact: ((text: string) => string) | undefined,
|
|
144
|
+
): StealthTelemetryDiagnostics | undefined {
|
|
145
|
+
if (!diagnostics) return undefined;
|
|
146
|
+
const result: StealthTelemetryDiagnostics = {
|
|
147
|
+
...(diagnostics.name === undefined ? {} : { name: boundedText(diagnostics.name, redact) }),
|
|
148
|
+
...(diagnostics.message === undefined
|
|
149
|
+
? {}
|
|
150
|
+
: { message: boundedText(diagnostics.message, redact) }),
|
|
151
|
+
...(diagnostics.code === undefined ? {} : { code: boundedText(diagnostics.code, redact) }),
|
|
152
|
+
};
|
|
153
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function cloneProfile(profile: StealthProfileDescriptor): StealthProfileDescriptor {
|
|
157
|
+
return { browser: profile.browser, os: profile.os } as StealthProfileDescriptor;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class StealthTelemetryCollector
|
|
161
|
+
implements
|
|
162
|
+
StealthTelemetrySink,
|
|
163
|
+
TelemetryContributor<StealthTelemetryLogPayload, StealthTelemetryHeaderPayload>
|
|
164
|
+
{
|
|
165
|
+
readonly key = "stealth" as const;
|
|
166
|
+
readonly #redact: ((text: string) => string) | undefined;
|
|
167
|
+
#attempts = 0;
|
|
168
|
+
#poolRefreshes = 0;
|
|
169
|
+
#redirectHops = 0;
|
|
170
|
+
#profileId: StealthProfileDescriptor | undefined;
|
|
171
|
+
#proxyUsed = false;
|
|
172
|
+
#requestClass: StealthTelemetryRequestClass | undefined;
|
|
173
|
+
#sbsdDetected: boolean | undefined;
|
|
174
|
+
#sbsdOutcome: StealthTelemetrySbsdOutcome | undefined;
|
|
175
|
+
#safeRefetch = 0;
|
|
176
|
+
#lastStatus: number | undefined;
|
|
177
|
+
#ms = 0;
|
|
178
|
+
#attemptSamples: StealthTelemetryAttemptSample[] = [];
|
|
179
|
+
#attemptSamplesDropped = 0;
|
|
180
|
+
|
|
181
|
+
constructor(options: { redact?: (text: string) => string } = {}) {
|
|
182
|
+
this.#redact = options.redact;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
recordAttempt(event: StealthTelemetryAttemptEvent): void {
|
|
186
|
+
const ms = boundedInteger(event.ms);
|
|
187
|
+
this.#attempts = saturatingAdd(this.#attempts, 1);
|
|
188
|
+
this.#ms = saturatingAdd(this.#ms, ms);
|
|
189
|
+
this.#profileId = cloneProfile(event.profileId);
|
|
190
|
+
this.#proxyUsed ||= event.proxyUsed;
|
|
191
|
+
this.#requestClass = event.requestClass;
|
|
192
|
+
if (event.status !== undefined) this.#lastStatus = boundedInteger(event.status);
|
|
193
|
+
if (this.#attemptSamples.length >= MAX_ATTEMPT_SAMPLES) {
|
|
194
|
+
this.#attemptSamplesDropped = saturatingAdd(this.#attemptSamplesDropped, 1);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const sample: StealthTelemetryAttemptSample = {
|
|
198
|
+
n: this.#attempts,
|
|
199
|
+
ms,
|
|
200
|
+
...(event.status === undefined ? {} : { status: boundedInteger(event.status) }),
|
|
201
|
+
...(event.errorCode === undefined ? {} : { e: event.errorCode }),
|
|
202
|
+
...(event.kind === undefined ? {} : { kind: event.kind }),
|
|
203
|
+
...(event.diagnostics
|
|
204
|
+
? { diagnostics: cloneDiagnostics(event.diagnostics, this.#redact) }
|
|
205
|
+
: {}),
|
|
206
|
+
};
|
|
207
|
+
if (sample.diagnostics === undefined) delete sample.diagnostics;
|
|
208
|
+
this.#attemptSamples.push(sample);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
recordPoolRefresh(): void {
|
|
212
|
+
this.#poolRefreshes = saturatingAdd(this.#poolRefreshes, 1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
recordRedirectHop(): void {
|
|
216
|
+
this.#redirectHops = saturatingAdd(this.#redirectHops, 1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
recordSbsd(event?: StealthTelemetrySbsdEvent | StealthTelemetrySbsdOutcome): void {
|
|
220
|
+
if (typeof event === "string") {
|
|
221
|
+
this.#sbsdDetected = true;
|
|
222
|
+
this.#sbsdOutcome = event;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (!event) return;
|
|
226
|
+
this.#sbsdDetected = (this.#sbsdDetected ?? false) || event.detected;
|
|
227
|
+
if (event.outcome !== undefined) this.#sbsdOutcome = event.outcome;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
recordSafeRefetch(): void {
|
|
231
|
+
this.#safeRefetch = saturatingAdd(this.#safeRefetch, 1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
toLogPayload(): StealthTelemetryLogPayload | undefined {
|
|
235
|
+
if (
|
|
236
|
+
this.#attempts === 0 &&
|
|
237
|
+
this.#poolRefreshes === 0 &&
|
|
238
|
+
this.#redirectHops === 0 &&
|
|
239
|
+
this.#safeRefetch === 0 &&
|
|
240
|
+
this.#sbsdDetected === undefined
|
|
241
|
+
) {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
attempts: this.#attempts,
|
|
246
|
+
poolRefreshes: this.#poolRefreshes,
|
|
247
|
+
redirectHops: this.#redirectHops,
|
|
248
|
+
...(this.#profileId ? { profileId: cloneProfile(this.#profileId) } : {}),
|
|
249
|
+
proxyUsed: this.#proxyUsed,
|
|
250
|
+
...(this.#requestClass ? { requestClass: this.#requestClass } : {}),
|
|
251
|
+
...(this.#sbsdDetected === undefined ? {} : { sbsdDetected: this.#sbsdDetected }),
|
|
252
|
+
...(this.#sbsdOutcome ? { sbsdOutcome: this.#sbsdOutcome } : {}),
|
|
253
|
+
safeRefetch: this.#safeRefetch,
|
|
254
|
+
...(this.#lastStatus === undefined ? {} : { lastStatus: this.#lastStatus }),
|
|
255
|
+
ms: this.#ms,
|
|
256
|
+
...(this.#attemptSamples.length > 0
|
|
257
|
+
? {
|
|
258
|
+
attemptSamples: this.#attemptSamples.map((sample) => ({
|
|
259
|
+
...sample,
|
|
260
|
+
...(sample.diagnostics
|
|
261
|
+
? {
|
|
262
|
+
diagnostics: { ...sample.diagnostics },
|
|
263
|
+
}
|
|
264
|
+
: {}),
|
|
265
|
+
})),
|
|
266
|
+
}
|
|
267
|
+
: {}),
|
|
268
|
+
...(this.#attemptSamplesDropped > 0
|
|
269
|
+
? { attemptSamplesDropped: this.#attemptSamplesDropped }
|
|
270
|
+
: {}),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
toHeaderPayload(log: StealthTelemetryLogPayload): StealthTelemetryHeaderPayload {
|
|
275
|
+
const attemptSamples = log.attemptSamples?.map((sample) => ({
|
|
276
|
+
n: sample.n,
|
|
277
|
+
ms: sample.ms,
|
|
278
|
+
...(sample.status === undefined ? {} : { status: sample.status }),
|
|
279
|
+
...(sample.e ? { e: closedEnum(sample.e) } : {}),
|
|
280
|
+
...(sample.kind ? { kind: closedEnum(sample.kind) } : {}),
|
|
281
|
+
}));
|
|
282
|
+
return {
|
|
283
|
+
attempts: log.attempts,
|
|
284
|
+
poolRefreshes: log.poolRefreshes,
|
|
285
|
+
redirectHops: log.redirectHops,
|
|
286
|
+
...(log.profileId
|
|
287
|
+
? {
|
|
288
|
+
profileId: {
|
|
289
|
+
browser: closedEnum(log.profileId.browser),
|
|
290
|
+
os: closedEnum(log.profileId.os),
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
: {}),
|
|
294
|
+
proxyUsed: log.proxyUsed,
|
|
295
|
+
...(log.requestClass ? { requestClass: closedEnum(log.requestClass) } : {}),
|
|
296
|
+
...(log.sbsdDetected === undefined ? {} : { sbsdDetected: log.sbsdDetected }),
|
|
297
|
+
...(log.sbsdOutcome ? { sbsdOutcome: closedEnum(log.sbsdOutcome) } : {}),
|
|
298
|
+
safeRefetch: log.safeRefetch,
|
|
299
|
+
...(log.lastStatus === undefined ? {} : { lastStatus: log.lastStatus }),
|
|
300
|
+
ms: log.ms,
|
|
301
|
+
...(attemptSamples && attemptSamples.length > 0 ? { attemptSamples } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|