@reproapp/node-sdk 0.0.42 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -194,10 +194,11 @@ type ReproInitConfig = {
194
194
  ```
195
195
 
196
196
  Important options:
197
- - `tenantId`, `appId`, `appSecret`: Repro service credentials.
198
- - `appName`: Product or workspace label sent as `X-App-Name`.
197
+ - `tenantId`, `appId`, `appSecret`: Repro application credentials.
198
+ - `appName`: Application label sent as `X-App-Name`.
199
199
  - `serviceName`: Backend service label stored on request, trace, DB, queue, Redis,
200
- and email events.
200
+ and email events. The SDK also registers this service with the application in
201
+ the background so admins can see its environments and privacy policy status.
201
202
  - `apiBase`: Repro API base URL. Used by backend live sessions and privacy policy fetches.
202
203
  - `captureHeaders`: Enables header capture. Sensitive headers are masked unless
203
204
  explicitly unmasked.
package/dist/index.d.ts CHANGED
@@ -292,6 +292,7 @@ type BackendLiveTimingConfig = {
292
292
  segmentMaxDurationMs: number;
293
293
  segmentIdleMs: number;
294
294
  };
295
+ declare function registerRuntimeService(cfg: ReproMiddlewareConfig): Promise<void>;
295
296
  declare function sanitizeTraceValueForPrivacy(value: any): any;
296
297
  declare function sanitizeMaterializedTraceValue(value: any): any;
297
298
  export declare function __materializePendingTraceEventsForWorker(payload: TraceMaterializationWorkerPayload): Promise<PendingTraceEventRecord[]>;
@@ -458,6 +459,7 @@ export declare const __reproTestHooks: {
458
459
  }): void;
459
460
  drainSdkBackgroundQueueForTest(): Promise<void>;
460
461
  resetSdkBackgroundQueuesForTest(): void;
462
+ registerRuntimeServiceForTest: typeof registerRuntimeService;
461
463
  setBackendLiveTimingForTest(override: Partial<BackendLiveTimingConfig> | null): void;
462
464
  runWithRequestContextForTest<T>(ctx: Partial<Ctx>, fn: () => T): T;
463
465
  getNativeMongoSuppressionCountForTest(): number;
package/dist/index.js CHANGED
@@ -1813,6 +1813,47 @@ async function postLiveApiJson(cfg, liveMode, path, body) {
1813
1813
  return null;
1814
1814
  }
1815
1815
  }
1816
+ async function registerRuntimeService(cfg) {
1817
+ const serviceName = typeof cfg.serviceName === 'string' ? cfg.serviceName.trim() : '';
1818
+ const apiBase = resolveLiveApiBase(cfg, normalizeLiveModeConfig(cfg));
1819
+ const fetchImpl = globalThis.fetch;
1820
+ if (!serviceName ||
1821
+ !apiBase ||
1822
+ !cfg.appId ||
1823
+ !cfg.appSecret ||
1824
+ typeof fetchImpl !== 'function') {
1825
+ return;
1826
+ }
1827
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
1828
+ const timeout = setTimeout(() => controller?.abort(), 2500);
1829
+ try {
1830
+ timeout.unref?.();
1831
+ }
1832
+ catch { }
1833
+ try {
1834
+ await fetchImpl(`${apiBase}/v1/sdk/runtime-services`, {
1835
+ method: 'POST',
1836
+ headers: liveApiHeaders(cfg),
1837
+ body: JSON.stringify({
1838
+ serviceName,
1839
+ environment: (0, privacy_1.normalizePrivacyEnvironment)(cfg.privacy?.environment),
1840
+ }),
1841
+ ...(controller ? { signal: controller.signal } : {}),
1842
+ });
1843
+ }
1844
+ catch {
1845
+ // Service discovery is best-effort and must never affect host startup.
1846
+ }
1847
+ finally {
1848
+ try {
1849
+ clearTimeout(timeout);
1850
+ }
1851
+ catch { }
1852
+ }
1853
+ }
1854
+ function enqueueRuntimeServiceRegistration(cfg) {
1855
+ scheduleSdkBackgroundWork(() => registerRuntimeService(cfg));
1856
+ }
1816
1857
  function enqueueLiveSessionStart(cfg, liveMode, payload) {
1817
1858
  scheduleSdkBackgroundWork(() => postLiveApi(cfg, liveMode, '/v1/live-sessions', payload), { priority: 'high' });
1818
1859
  }
@@ -3190,6 +3231,7 @@ async function ensureRuntimePrivacyPolicyLoaded(cfg) {
3190
3231
  appId: cfg.appId,
3191
3232
  appSecret: cfg.appSecret,
3192
3233
  appName: cfg.appName,
3234
+ serviceName: cfg.serviceName,
3193
3235
  apiBase,
3194
3236
  environment: state.environment,
3195
3237
  fetchTimeoutMs: privacy?.fetchTimeoutMs,
@@ -3231,6 +3273,7 @@ async function initializeRuntimePrivacyPolicyAtStartup(cfg) {
3231
3273
  appId: cfg.appId,
3232
3274
  appSecret: cfg.appSecret,
3233
3275
  appName: cfg.appName,
3276
+ serviceName: cfg.serviceName,
3234
3277
  apiBase,
3235
3278
  environment: state.environment,
3236
3279
  fetchTimeoutMs: privacy?.fetchTimeoutMs,
@@ -4832,11 +4875,11 @@ function reproMiddleware(cfg) {
4832
4875
  ?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4833
4876
  const responseBody = responseBodyMaterialization.value;
4834
4877
  const requestValueEntries = [];
4835
- const bodyValueCapture = requestBodyMaterialization.skipped
4878
+ const bodyValueCapture = requestBodyRaw === undefined
4836
4879
  ? undefined
4837
4880
  : await maybeCaptureRequestValueAsync({
4838
4881
  target: 'request.body',
4839
- rawValue: req.body,
4882
+ rawValue: requestBodyRaw,
4840
4883
  previewValue: requestBody,
4841
4884
  capture: {
4842
4885
  runtimeConfig: captureCfg,
@@ -4886,7 +4929,7 @@ function reproMiddleware(cfg) {
4886
4929
  privacy: activePrivacy,
4887
4930
  },
4888
4931
  }, requestValueEntries);
4889
- const respBodyValueCapture = responseBodyMaterialization.skipped
4932
+ const respBodyValueCapture = capturedBody === undefined
4890
4933
  ? undefined
4891
4934
  : await maybeCaptureRequestValueAsync({
4892
4935
  target: 'response.body',
@@ -9835,6 +9878,7 @@ async function initRepro(cfg) {
9835
9878
  await installMongoDbImportHook();
9836
9879
  patchSendgridMail(integrationCfg);
9837
9880
  patchMongooseIntegration(reproAutoMiddlewareConfig);
9881
+ enqueueRuntimeServiceRegistration(reproAutoMiddlewareConfig);
9838
9882
  await initializeRuntimePrivacyPolicyAtStartup(reproAutoMiddlewareConfig);
9839
9883
  }
9840
9884
  exports.initRepro = initRepro;
@@ -9866,6 +9910,7 @@ exports.__reproTestHooks = {
9866
9910
  lastClientActivityAt = 0;
9867
9911
  sdkBackgroundQuietUntil = 0;
9868
9912
  },
9913
+ registerRuntimeServiceForTest: registerRuntimeService,
9869
9914
  setBackendLiveTimingForTest(override) {
9870
9915
  backendLiveTimingOverrideForTest = override;
9871
9916
  },
package/dist/privacy.d.ts CHANGED
@@ -98,6 +98,7 @@ export declare function fetchApprovedRuntimePrivacyPolicy(params: {
98
98
  appId: string;
99
99
  appSecret?: string;
100
100
  appName?: string;
101
+ serviceName?: string;
101
102
  apiBase: string;
102
103
  environment: string;
103
104
  fetchTimeoutMs?: number;
package/dist/privacy.js CHANGED
@@ -104,7 +104,13 @@ async function fetchApprovedRuntimePrivacyPolicy(params) {
104
104
  reason: !params.apiBase ? 'missing_config' : 'missing_credentials',
105
105
  };
106
106
  }
107
- const url = `${params.apiBase}/v1/sdk/privacy-policy?environment=${encodeURIComponent(params.environment)}`;
107
+ const query = new URLSearchParams({
108
+ environment: params.environment,
109
+ });
110
+ if (typeof params.serviceName === 'string' && params.serviceName.trim()) {
111
+ query.set('serviceName', params.serviceName.trim());
112
+ }
113
+ const url = `${params.apiBase}/v1/sdk/privacy-policy?${query.toString()}`;
108
114
  const headers = {
109
115
  accept: 'application/json',
110
116
  'x-app-id': params.appId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.42",
3
+ "version": "0.0.45",
4
4
  "description": "Repro Nest SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -12,7 +12,7 @@
12
12
  "build": "tsc -p tsconfig.json",
13
13
  "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput",
14
14
  "prepublishOnly": "npm run build",
15
- "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
15
+ "test": "npm run build && node test/unawaited.test.js && node test/integration-unawaited.js && node test/request-flush-timing.test.js && node test/request-capture-background.test.js && node test/init-auto-http-server.test.js && node test/backend-live-mode.test.js && node test/client-performance-regression.test.js && node test/request-manifest-priority.test.js && node test/runtime-no-double-invoke.test.js && node test/express-trace-http-args.test.js && node test/trace-batch-size.test.js && node test/sdk-background-priority.test.js && node test/runtime-service-registration.test.js && node test/ingest-queue-retention.test.js && node test/ingest-high-priority-retention.test.js && node test/ingest-critical-retention.test.js && node test/ingest-critical-retry-retention.test.js && node test/ingest-batching.test.js && node test/ingest-timeout.test.js && node -r ./tracer/register test/promise-map.test.js && node test/disable-subtree.test.js && node test/circular-capture.test.js && node test/wrap-plugin-arrow-args.test.js && node test/wrap-plugin-super-call.test.js && node test/function-arg-identity.test.js && node test/privacy-service-scope.test.js && node test/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js && node test/amqplib-integration.test.js && node test/mongodb-integration.test.js && node test/mongodb-esm-import-hook.test.js && node test/mongodb-suppression-cleanup.test.js && node test/postgresql-integration.test.js",
16
16
  "test:containers": "npm run build && node test/runtime-no-double-invoke.testcontainers.test.js && node test/wrap-plugin-super-call.testcontainers.test.js && node test/function-arg-identity.testcontainers.test.js && node test/mongodb-mongoose-native-suppression.testcontainers.test.js && node test/postgresql-integrations.testcontainers.test.js"
17
17
  },
18
18
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -2250,6 +2250,55 @@ async function postLiveApiJson<T = Record<string, any>>(
2250
2250
  }
2251
2251
  }
2252
2252
 
2253
+ async function registerRuntimeService(
2254
+ cfg: ReproMiddlewareConfig,
2255
+ ): Promise<void> {
2256
+ const serviceName =
2257
+ typeof cfg.serviceName === 'string' ? cfg.serviceName.trim() : '';
2258
+ const apiBase = resolveLiveApiBase(cfg, normalizeLiveModeConfig(cfg));
2259
+ const fetchImpl = (globalThis as any).fetch;
2260
+ if (
2261
+ !serviceName ||
2262
+ !apiBase ||
2263
+ !cfg.appId ||
2264
+ !cfg.appSecret ||
2265
+ typeof fetchImpl !== 'function'
2266
+ ) {
2267
+ return;
2268
+ }
2269
+
2270
+ const controller =
2271
+ typeof AbortController === 'function' ? new AbortController() : null;
2272
+ const timeout = setTimeout(() => controller?.abort(), 2_500);
2273
+ try {
2274
+ timeout.unref?.();
2275
+ } catch {}
2276
+
2277
+ try {
2278
+ await fetchImpl(`${apiBase}/v1/sdk/runtime-services`, {
2279
+ method: 'POST',
2280
+ headers: liveApiHeaders(cfg),
2281
+ body: JSON.stringify({
2282
+ serviceName,
2283
+ environment: normalizePrivacyEnvironment(
2284
+ cfg.privacy?.environment,
2285
+ ),
2286
+ }),
2287
+ ...(controller ? { signal: controller.signal } : {}),
2288
+ });
2289
+ } catch {
2290
+ // Service discovery is best-effort and must never affect host startup.
2291
+ } finally {
2292
+ try {
2293
+ clearTimeout(timeout);
2294
+ } catch {}
2295
+ }
2296
+ }
2297
+
2298
+ function enqueueRuntimeServiceRegistration(cfg: ReproMiddlewareConfig): void {
2299
+ scheduleSdkBackgroundWork(() => registerRuntimeService(cfg));
2300
+ }
2301
+
2253
2302
  function enqueueLiveSessionStart(
2254
2303
  cfg: ReproMiddlewareConfig,
2255
2304
  liveMode: ReproLiveModeConfig | null,
@@ -3844,6 +3893,7 @@ async function ensureRuntimePrivacyPolicyLoaded(cfg: ReproMiddlewareConfig): Pro
3844
3893
  appId: cfg.appId,
3845
3894
  appSecret: cfg.appSecret,
3846
3895
  appName: cfg.appName,
3896
+ serviceName: cfg.serviceName,
3847
3897
  apiBase,
3848
3898
  environment: state.environment,
3849
3899
  fetchTimeoutMs: privacy?.fetchTimeoutMs,
@@ -3894,6 +3944,7 @@ async function initializeRuntimePrivacyPolicyAtStartup(cfg: ReproMiddlewareConfi
3894
3944
  appId: cfg.appId,
3895
3945
  appSecret: cfg.appSecret,
3896
3946
  appName: cfg.appName,
3947
+ serviceName: cfg.serviceName,
3897
3948
  apiBase,
3898
3949
  environment: state.environment,
3899
3950
  fetchTimeoutMs: privacy?.fetchTimeoutMs,
@@ -5985,11 +6036,11 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
5985
6036
  );
5986
6037
  const responseBody = responseBodyMaterialization.value;
5987
6038
  const requestValueEntries: TraceValueBatchEntry[] = [];
5988
- const bodyValueCapture = requestBodyMaterialization.skipped
6039
+ const bodyValueCapture = requestBodyRaw === undefined
5989
6040
  ? undefined
5990
6041
  : await maybeCaptureRequestValueAsync({
5991
6042
  target: 'request.body',
5992
- rawValue: (req as any).body,
6043
+ rawValue: requestBodyRaw,
5993
6044
  previewValue: requestBody,
5994
6045
  capture: {
5995
6046
  runtimeConfig: captureCfg,
@@ -6039,7 +6090,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
6039
6090
  privacy: activePrivacy,
6040
6091
  },
6041
6092
  }, requestValueEntries);
6042
- const respBodyValueCapture = responseBodyMaterialization.skipped
6093
+ const respBodyValueCapture = capturedBody === undefined
6043
6094
  ? undefined
6044
6095
  : await maybeCaptureRequestValueAsync({
6045
6096
  target: 'response.body',
@@ -11365,6 +11416,7 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
11365
11416
  patchSendgridMail(integrationCfg);
11366
11417
  patchMongooseIntegration(reproAutoMiddlewareConfig);
11367
11418
 
11419
+ enqueueRuntimeServiceRegistration(reproAutoMiddlewareConfig);
11368
11420
  await initializeRuntimePrivacyPolicyAtStartup(reproAutoMiddlewareConfig);
11369
11421
  }
11370
11422
 
@@ -11396,6 +11448,7 @@ export const __reproTestHooks = {
11396
11448
  lastClientActivityAt = 0;
11397
11449
  sdkBackgroundQuietUntil = 0;
11398
11450
  },
11451
+ registerRuntimeServiceForTest: registerRuntimeService,
11399
11452
  setBackendLiveTimingForTest(override: Partial<BackendLiveTimingConfig> | null): void {
11400
11453
  backendLiveTimingOverrideForTest = override;
11401
11454
  },
package/src/privacy.ts CHANGED
@@ -230,6 +230,7 @@ export async function fetchApprovedRuntimePrivacyPolicy(params: {
230
230
  appId: string;
231
231
  appSecret?: string;
232
232
  appName?: string;
233
+ serviceName?: string;
233
234
  apiBase: string;
234
235
  environment: string;
235
236
  fetchTimeoutMs?: number;
@@ -240,7 +241,13 @@ export async function fetchApprovedRuntimePrivacyPolicy(params: {
240
241
  reason: !params.apiBase ? 'missing_config' : 'missing_credentials',
241
242
  };
242
243
  }
243
- const url = `${params.apiBase}/v1/sdk/privacy-policy?environment=${encodeURIComponent(params.environment)}`;
244
+ const query = new URLSearchParams({
245
+ environment: params.environment,
246
+ });
247
+ if (typeof params.serviceName === 'string' && params.serviceName.trim()) {
248
+ query.set('serviceName', params.serviceName.trim());
249
+ }
250
+ const url = `${params.apiBase}/v1/sdk/privacy-policy?${query.toString()}`;
244
251
  const headers: Record<string, string> = {
245
252
  accept: 'application/json',
246
253
  'x-app-id': params.appId,
@@ -142,6 +142,8 @@ async function main() {
142
142
  }
143
143
 
144
144
  const large = makeReqRes('S_perf_large_payload', 'A_perf_large_payload', '/large-payload');
145
+ large.req.method = 'POST';
146
+ large.req.body = largePayload();
145
147
  await invokeMiddleware(middleware, large.req, large.res, async () => {
146
148
  const traced = global.__repro_call(
147
149
  largeTraceReturn,
@@ -160,12 +162,48 @@ async function main() {
160
162
 
161
163
  const backendRequest = eventsOf(capturedBodies, 'backend_request')[0]?.payload?.request;
162
164
  assert(backendRequest, JSON.stringify(capturedBodies));
165
+ assert.equal(backendRequest.body, undefined, JSON.stringify(backendRequest));
166
+ assert.equal(
167
+ backendRequest.bodyMaterialization?.reason,
168
+ 'inline-size-limit',
169
+ JSON.stringify(backendRequest),
170
+ );
171
+ assert.equal(
172
+ backendRequest.bodyValueCapture?.status,
173
+ 'stored',
174
+ JSON.stringify(backendRequest),
175
+ );
163
176
  assert.equal(backendRequest.respBody, undefined, JSON.stringify(backendRequest));
164
177
  assert.equal(
165
178
  backendRequest.respBodyMaterialization?.reason,
166
179
  'inline-size-limit',
167
180
  JSON.stringify(backendRequest),
168
181
  );
182
+ assert.equal(
183
+ backendRequest.respBodyValueCapture?.status,
184
+ 'stored',
185
+ JSON.stringify(backendRequest),
186
+ );
187
+
188
+ const requestValues = eventsOf(capturedBodies, 'backend_request')
189
+ .flatMap((event) => Array.isArray(event?.payload?.requestValues) ? event.payload.requestValues : []);
190
+ const capturedRequestBody = requestValues.find((entry) =>
191
+ entry?.id === backendRequest.bodyValueCapture.valueId &&
192
+ entry?.target === 'request.body'
193
+ );
194
+ const capturedResponseBody = requestValues.find((entry) =>
195
+ entry?.id === backendRequest.respBodyValueCapture.valueId &&
196
+ entry?.target === 'response.body'
197
+ );
198
+ assert(capturedRequestBody, JSON.stringify(requestValues));
199
+ assert(capturedResponseBody, JSON.stringify(requestValues));
200
+ assert.equal(capturedRequestBody.value?.rows?.length, 400, JSON.stringify(capturedRequestBody));
201
+ assert.equal(capturedResponseBody.value?.rows?.length, 400, JSON.stringify(capturedResponseBody));
202
+ assert.equal(
203
+ capturedResponseBody.value.rows[399].depot,
204
+ 'depot-399',
205
+ JSON.stringify(capturedResponseBody),
206
+ );
169
207
 
170
208
  const traceEvents = eventsOf(capturedBodies, 'trace_batch')
171
209
  .flatMap((event) => Array.isArray(event?.payload?.trace) ? event.payload.trace : []);
@@ -307,7 +307,7 @@ async function testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest() {
307
307
  }
308
308
  }
309
309
 
310
- async function testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody() {
310
+ async function testLargeHttpResponseBodyKeepsFullValueOutOfBand() {
311
311
  const marker = 'OVERSIZED_HTTP_RESPONSE_MARKER';
312
312
  const capturedBodies = [];
313
313
  const originalFetch = global.fetch;
@@ -344,13 +344,28 @@ async function testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody()
344
344
  return text.includes('"event_type":"backend_request"') &&
345
345
  text.includes('"reason":"inline-size-limit"');
346
346
  }));
347
- const text = JSON.stringify(capturedBodies);
348
- assert(!text.includes(marker), text);
349
- assert(text.includes('respBodyMaterialization'), text);
350
- assert(text.includes('"target":"response.body"'), text);
351
- assert(!text.includes('__type":"json-string'), text);
352
- assert(!text.includes('__truncatedKeys'), text);
353
- assert(!text.includes('more chars'), text);
347
+ const backendEvent = capturedBodies
348
+ .flatMap((body) => Array.isArray(body?.events) ? body.events : [])
349
+ .find((event) => event?.event_type === 'backend_request');
350
+ const request = backendEvent?.payload?.request;
351
+ const requestValues = backendEvent?.payload?.requestValues;
352
+ assert(request, JSON.stringify(capturedBodies));
353
+ assert.strictEqual(request.respBody, undefined, JSON.stringify(request));
354
+ assert.strictEqual(request.respBodyMaterialization?.reason, 'inline-size-limit');
355
+ assert.strictEqual(request.respBodyValueCapture?.status, 'stored');
356
+ assert(!JSON.stringify(request).includes(marker), JSON.stringify(request));
357
+ const capturedResponse = requestValues?.find((entry) =>
358
+ entry?.id === request.respBodyValueCapture.valueId &&
359
+ entry?.target === 'response.body'
360
+ );
361
+ assert(capturedResponse, JSON.stringify(requestValues));
362
+ assert.strictEqual(
363
+ capturedResponse.value?.payload,
364
+ `${marker}:${'x'.repeat(12 * 1024)}`,
365
+ );
366
+ assert(!JSON.stringify(request).includes('__type":"json-string'), JSON.stringify(request));
367
+ assert(!JSON.stringify(request).includes('__truncatedKeys'), JSON.stringify(request));
368
+ assert(!JSON.stringify(request).includes('more chars'), JSON.stringify(request));
354
369
  } finally {
355
370
  await flushIngestQueue();
356
371
  global.fetch = originalFetch;
@@ -746,7 +761,7 @@ async function main() {
746
761
  await testKafkaConsumerTraceUsesConsumeTraceId();
747
762
  await testKafkaProducerCapturesFullPublishedMessageValue();
748
763
  await testKafkaTraceFlushIsNotBlockedForeverByActiveHttpRequest();
749
- await testLargeHttpResponseBodyKeepsRequestMetadataWithoutInlineBody();
764
+ await testLargeHttpResponseBodyKeepsFullValueOutOfBand();
750
765
  await testJsonBuiltinTracePayloadsAreOmitted();
751
766
  await testLargeStringTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
752
767
  await testLargeStructuredTraceValuesStayIngestibleAndCaptureFullValueOutOfBand();
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert');
4
+ const {
5
+ fetchApprovedRuntimePrivacyPolicy,
6
+ } = require('../dist/privacy.js');
7
+
8
+ async function main() {
9
+ const originalFetch = global.fetch;
10
+ const calls = [];
11
+ global.fetch = async (url, init) => {
12
+ calls.push({ url: String(url), init });
13
+ return {
14
+ ok: true,
15
+ json: async () => ({ enabled: false }),
16
+ };
17
+ };
18
+
19
+ try {
20
+ await fetchApprovedRuntimePrivacyPolicy({
21
+ appId: 'APP_shared',
22
+ appSecret: 'secret',
23
+ appName: 'Shared app',
24
+ serviceName: ' Orders API ',
25
+ apiBase: 'https://repro.example/api',
26
+ environment: 'prod',
27
+ });
28
+ await fetchApprovedRuntimePrivacyPolicy({
29
+ appId: 'APP_shared',
30
+ appSecret: 'secret',
31
+ apiBase: 'https://repro.example/api',
32
+ environment: 'dev',
33
+ });
34
+ } finally {
35
+ global.fetch = originalFetch;
36
+ }
37
+
38
+ assert.strictEqual(calls.length, 2);
39
+ const serviceUrl = new URL(calls[0].url);
40
+ assert.strictEqual(serviceUrl.pathname, '/api/v1/sdk/privacy-policy');
41
+ assert.strictEqual(serviceUrl.searchParams.get('environment'), 'prod');
42
+ assert.strictEqual(serviceUrl.searchParams.get('serviceName'), 'Orders API');
43
+ assert.strictEqual(calls[0].init.headers['x-app-id'], 'APP_shared');
44
+ assert.strictEqual(calls[0].init.headers['x-app-secret'], 'secret');
45
+
46
+ const appWideUrl = new URL(calls[1].url);
47
+ assert.strictEqual(appWideUrl.searchParams.get('environment'), 'dev');
48
+ assert.strictEqual(appWideUrl.searchParams.has('serviceName'), false);
49
+
50
+ console.log('runtime privacy service scope OK');
51
+ }
52
+
53
+ main().catch((error) => {
54
+ console.error(error);
55
+ process.exitCode = 1;
56
+ });
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const assert = require('node:assert/strict');
4
+
5
+ const { __reproTestHooks } = require('../dist');
6
+
7
+ async function main() {
8
+ const originalFetch = global.fetch;
9
+ const calls = [];
10
+
11
+ try {
12
+ global.fetch = async (url, options) => {
13
+ calls.push({ url: String(url), options });
14
+ return { ok: true };
15
+ };
16
+
17
+ await __reproTestHooks.registerRuntimeServiceForTest({
18
+ tenantId: 'TENANT_test',
19
+ appId: 'APP_test',
20
+ appSecret: 'secret',
21
+ appName: 'Acme',
22
+ serviceName: ' orders-api ',
23
+ apiBase: 'http://repro-api.test/api/',
24
+ privacy: {
25
+ environment: 'production',
26
+ captureRaw: true,
27
+ },
28
+ ingestBase: 'http://collector.test',
29
+ });
30
+
31
+ assert.equal(calls.length, 1);
32
+ assert.equal(
33
+ calls[0].url,
34
+ 'http://repro-api.test/api/v1/sdk/runtime-services',
35
+ );
36
+ assert.equal(calls[0].options.method, 'POST');
37
+ assert.deepEqual(calls[0].options.headers, {
38
+ 'Content-Type': 'application/json',
39
+ 'X-App-Id': 'APP_test',
40
+ 'X-App-Secret': 'secret',
41
+ 'X-App-Name': 'Acme',
42
+ });
43
+ assert.deepEqual(JSON.parse(calls[0].options.body), {
44
+ serviceName: 'orders-api',
45
+ environment: 'prod',
46
+ });
47
+ assert.ok(calls[0].options.signal);
48
+
49
+ await __reproTestHooks.registerRuntimeServiceForTest({
50
+ tenantId: 'TENANT_test',
51
+ appId: 'APP_test',
52
+ appSecret: 'secret',
53
+ appName: 'Acme',
54
+ apiBase: 'http://repro-api.test/api',
55
+ ingestBase: 'http://collector.test',
56
+ });
57
+ assert.equal(calls.length, 1);
58
+
59
+ global.fetch = async () => {
60
+ throw new Error('network down');
61
+ };
62
+ await __reproTestHooks.registerRuntimeServiceForTest({
63
+ tenantId: 'TENANT_test',
64
+ appId: 'APP_test',
65
+ appSecret: 'secret',
66
+ serviceName: 'billing-api',
67
+ apiBase: 'http://repro-api.test/api',
68
+ ingestBase: 'http://collector.test',
69
+ });
70
+ } finally {
71
+ global.fetch = originalFetch;
72
+ }
73
+
74
+ console.log('runtime service registration OK');
75
+ }
76
+
77
+ main().catch((error) => {
78
+ console.error(error);
79
+ process.exitCode = 1;
80
+ });