@reproapp/node-sdk 0.0.42 → 0.0.44

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,
@@ -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.44",
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,
@@ -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,
@@ -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
+ });