@reproapp/node-sdk 0.0.37 → 0.0.39

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/dist/index.js CHANGED
@@ -305,6 +305,9 @@ const KAFKA_HEADER_SESSION_ID = 'x-bug-session-id';
305
305
  const KAFKA_HEADER_ACTION_ID = 'x-bug-action-id';
306
306
  const DEFAULT_INGEST_BASE = 'http://localhost:8080';
307
307
  const DEFAULT_INGEST_PATH = '/v1/ingest/events';
308
+ const DEFAULT_LIVE_SESSION_DURATION_MS = 3 * 60000;
309
+ const DEFAULT_LIVE_SEGMENT_IDLE_MS = 15000;
310
+ const DEFAULT_LIVE_SEGMENT_DURATION_MS = DEFAULT_LIVE_SESSION_DURATION_MS;
308
311
  function escapeRx(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
309
312
  let __TRACER__ = null;
310
313
  let __TRACER_READY = false;
@@ -1681,6 +1684,299 @@ function toIngestConfig(cfg) {
1681
1684
  schemaVersion: cfg.schemaVersion,
1682
1685
  };
1683
1686
  }
1687
+ function normalizeLiveModeConfig(cfg) {
1688
+ const liveMode = cfg.liveMode;
1689
+ if (liveMode === true) {
1690
+ return { enabled: true };
1691
+ }
1692
+ if (!liveMode || typeof liveMode !== 'object') {
1693
+ return null;
1694
+ }
1695
+ if (liveMode.enabled === false) {
1696
+ return null;
1697
+ }
1698
+ return liveMode;
1699
+ }
1700
+ let backendLiveTimingOverrideForTest = null;
1701
+ function positiveInternalDuration(value, fallback) {
1702
+ const numeric = Number(value);
1703
+ if (!Number.isFinite(numeric) || numeric <= 0)
1704
+ return fallback;
1705
+ return Math.max(1, Math.trunc(numeric));
1706
+ }
1707
+ function getBackendLiveTiming() {
1708
+ return {
1709
+ sessionMaxDurationMs: positiveInternalDuration(backendLiveTimingOverrideForTest?.sessionMaxDurationMs, DEFAULT_LIVE_SESSION_DURATION_MS),
1710
+ segmentMaxDurationMs: positiveInternalDuration(backendLiveTimingOverrideForTest?.segmentMaxDurationMs, DEFAULT_LIVE_SEGMENT_DURATION_MS),
1711
+ segmentIdleMs: positiveInternalDuration(backendLiveTimingOverrideForTest?.segmentIdleMs, DEFAULT_LIVE_SEGMENT_IDLE_MS),
1712
+ };
1713
+ }
1714
+ function normalizeLiveActorId(value) {
1715
+ if (value === undefined || value === null)
1716
+ return null;
1717
+ const normalized = String(value).trim();
1718
+ return normalized.length ? normalized : null;
1719
+ }
1720
+ function normalizeLiveActorLabels(value) {
1721
+ if (!value || typeof value !== 'object' || Array.isArray(value))
1722
+ return undefined;
1723
+ const labels = {};
1724
+ for (const [key, rawValue] of Object.entries(value)) {
1725
+ const normalizedKey = key.trim();
1726
+ if (!normalizedKey || rawValue === undefined || rawValue === null)
1727
+ continue;
1728
+ const normalizedValue = String(rawValue).trim();
1729
+ if (normalizedValue)
1730
+ labels[normalizedKey] = normalizedValue;
1731
+ }
1732
+ return Object.keys(labels).length ? labels : undefined;
1733
+ }
1734
+ function newSessionId(base = Date.now()) {
1735
+ return `S_${Math.max(0, Math.trunc(base))}_${(0, crypto_1.randomUUID)().slice(0, 8)}`;
1736
+ }
1737
+ function newActionId(base = Date.now()) {
1738
+ return `A_${Math.max(0, Math.trunc(base))}_${(0, crypto_1.randomUUID)().slice(0, 8)}`;
1739
+ }
1740
+ function resolveLiveApiBase(cfg, liveMode) {
1741
+ const env = typeof process !== 'undefined' ? process?.env : undefined;
1742
+ const raw = liveMode?.apiBase ||
1743
+ cfg.apiBase ||
1744
+ env?.REPRO_API_BASE_URL ||
1745
+ env?.REPRO_API_BASE ||
1746
+ cfg.privacy?.apiBase ||
1747
+ null;
1748
+ if (typeof raw !== 'string' || !raw.trim())
1749
+ return null;
1750
+ return raw.trim().replace(/\/+$/, '');
1751
+ }
1752
+ function liveApiHeaders(cfg) {
1753
+ const headers = {
1754
+ 'Content-Type': 'application/json',
1755
+ 'X-App-Id': cfg.appId,
1756
+ };
1757
+ if (cfg.appSecret) {
1758
+ headers['X-App-Secret'] = cfg.appSecret;
1759
+ }
1760
+ if (cfg.appName) {
1761
+ headers['X-App-Name'] = cfg.appName;
1762
+ }
1763
+ return headers;
1764
+ }
1765
+ async function postLiveApi(cfg, liveMode, path, body) {
1766
+ await postLiveApiJson(cfg, liveMode, path, body);
1767
+ }
1768
+ async function postLiveApiJson(cfg, liveMode, path, body) {
1769
+ const apiBase = resolveLiveApiBase(cfg, liveMode);
1770
+ const fetchImpl = globalThis.fetch;
1771
+ if (!apiBase || !cfg.appId || !cfg.appSecret || typeof fetchImpl !== 'function') {
1772
+ return null;
1773
+ }
1774
+ try {
1775
+ const res = await fetchImpl(`${apiBase}${path}`, {
1776
+ method: 'POST',
1777
+ headers: liveApiHeaders(cfg),
1778
+ body: JSON.stringify(body),
1779
+ });
1780
+ if (!res?.ok) {
1781
+ return null;
1782
+ }
1783
+ try {
1784
+ return await res.json();
1785
+ }
1786
+ catch {
1787
+ return null;
1788
+ }
1789
+ }
1790
+ catch {
1791
+ // never break user code
1792
+ return null;
1793
+ }
1794
+ }
1795
+ function enqueueLiveSessionStart(cfg, liveMode, payload) {
1796
+ scheduleSdkBackgroundWork(() => postLiveApi(cfg, liveMode, '/v1/live-sessions', payload), { priority: 'high' });
1797
+ }
1798
+ function enqueueLiveSessionFinish(cfg, liveMode, sessionId) {
1799
+ scheduleSdkBackgroundWork(() => postLiveApi(cfg, liveMode, `/v1/live-sessions/${encodeURIComponent(sessionId)}/finish`, { notes: '' }), { priority: 'high' });
1800
+ }
1801
+ function emitBackendLiveSessionManifest(cfg, session, options) {
1802
+ post(cfg, session.sessionId, {
1803
+ entries: [{
1804
+ sessionCaptureManifest: {
1805
+ mode: 'backend-only',
1806
+ captureMode: 'backend-only',
1807
+ sessionKind: options.sessionKind,
1808
+ parentSessionId: options.parentSessionId ?? null,
1809
+ previousSessionId: options.previousSessionId ?? null,
1810
+ actorId: options.actorId,
1811
+ actorType: options.actorType,
1812
+ actorLabels: options.actorLabels,
1813
+ expected: {
1814
+ frontendActionDoc: 0,
1815
+ rrwebChunk: 0,
1816
+ frontendSessionEnd: 0,
1817
+ },
1818
+ closedAt: options.closedAt,
1819
+ },
1820
+ t: options.closedAt,
1821
+ }],
1822
+ }, { priority: 'high' });
1823
+ }
1824
+ function closeBackendLiveParent(cfg, liveMode, state, parent, closedAt) {
1825
+ if (parent.closed) {
1826
+ return;
1827
+ }
1828
+ parent.closed = true;
1829
+ if (parent.closeTimer) {
1830
+ try {
1831
+ clearTimeout(parent.closeTimer);
1832
+ }
1833
+ catch { }
1834
+ parent.closeTimer = null;
1835
+ }
1836
+ state.previousParentSessionId = parent.sessionId;
1837
+ if (state.parent === parent) {
1838
+ state.parent = null;
1839
+ }
1840
+ emitBackendLiveSessionManifest(cfg, parent, {
1841
+ sessionKind: 'parent',
1842
+ actorId: parent.actorId,
1843
+ actorType: parent.actorType,
1844
+ actorLabels: parent.actorLabels,
1845
+ previousSessionId: parent.previousSessionId,
1846
+ closedAt,
1847
+ });
1848
+ enqueueLiveSessionFinish(cfg, liveMode, parent.sessionId);
1849
+ }
1850
+ function scheduleBackendLiveParentClose(cfg, liveMode, state, parent) {
1851
+ if (parent.closed) {
1852
+ return;
1853
+ }
1854
+ if (parent.closeTimer) {
1855
+ try {
1856
+ clearTimeout(parent.closeTimer);
1857
+ }
1858
+ catch { }
1859
+ parent.closeTimer = null;
1860
+ }
1861
+ const now = Date.now();
1862
+ const activeChild = state.activeChild;
1863
+ const activeChildBelongsToParent = activeChild?.parentSessionId === parent.sessionId;
1864
+ const closeAt = activeChildBelongsToParent && now >= parent.expiresAt
1865
+ ? Math.max(parent.expiresAt, activeChild.closeDeadline, now + 1)
1866
+ : parent.expiresAt;
1867
+ parent.closeTimer = setTimeout(() => {
1868
+ parent.closeTimer = null;
1869
+ if (parent.closed || state.parent !== parent) {
1870
+ return;
1871
+ }
1872
+ const currentChild = state.activeChild;
1873
+ if (currentChild?.parentSessionId === parent.sessionId) {
1874
+ if (currentChild.requestsInFlight <= 0) {
1875
+ scheduleBackendLiveChildClose(cfg, liveMode, state, currentChild);
1876
+ }
1877
+ scheduleBackendLiveParentClose(cfg, liveMode, state, parent);
1878
+ return;
1879
+ }
1880
+ closeBackendLiveParent(cfg, liveMode, state, parent, Date.now());
1881
+ }, Math.max(0, closeAt - now));
1882
+ try {
1883
+ parent.closeTimer.unref?.();
1884
+ }
1885
+ catch { }
1886
+ }
1887
+ function scheduleBackendLiveChildClose(cfg, liveMode, state, child) {
1888
+ if (child.closeTimer) {
1889
+ try {
1890
+ clearTimeout(child.closeTimer);
1891
+ }
1892
+ catch { }
1893
+ child.closeTimer = null;
1894
+ }
1895
+ const delay = Math.max(0, child.closeDeadline - Date.now());
1896
+ child.closeTimer = setTimeout(() => {
1897
+ child.closeTimer = null;
1898
+ if (child.requestsInFlight > 0 || Date.now() < child.closeDeadline) {
1899
+ scheduleBackendLiveChildClose(cfg, liveMode, state, child);
1900
+ return;
1901
+ }
1902
+ if (state.activeChild === child) {
1903
+ state.activeChild = null;
1904
+ }
1905
+ state.previousChildSessionId = child.sessionId;
1906
+ const closedAt = Date.now();
1907
+ emitBackendLiveSessionManifest(cfg, child, {
1908
+ sessionKind: 'child',
1909
+ actorId: child.actorId,
1910
+ actorType: child.actorType,
1911
+ actorLabels: child.actorLabels,
1912
+ parentSessionId: child.parentSessionId,
1913
+ previousSessionId: child.previousSessionId,
1914
+ closedAt,
1915
+ });
1916
+ enqueueLiveSessionFinish(cfg, liveMode, child.sessionId);
1917
+ }, delay);
1918
+ try {
1919
+ child.closeTimer.unref?.();
1920
+ }
1921
+ catch { }
1922
+ }
1923
+ function releaseBackendLiveRequest(cfg, liveMode, state, child, finishedAt) {
1924
+ child.requestsInFlight = Math.max(0, child.requestsInFlight - 1);
1925
+ const idleMs = getBackendLiveTiming().segmentIdleMs;
1926
+ child.closeDeadline = Math.max(child.closeDeadline, finishedAt + idleMs);
1927
+ if (child.requestsInFlight <= 0) {
1928
+ scheduleBackendLiveChildClose(cfg, liveMode, state, child);
1929
+ }
1930
+ }
1931
+ async function resolveBackendLiveContext(cfg, req, requestStart) {
1932
+ const liveMode = normalizeLiveModeConfig(cfg);
1933
+ if (!liveMode?.backendOnly) {
1934
+ return null;
1935
+ }
1936
+ const method = String(req.method || 'GET').toUpperCase();
1937
+ if (method === 'OPTIONS' && !hasReproRequestCorrelation(req)) {
1938
+ return null;
1939
+ }
1940
+ const identity = await resolveLiveActorIdentity(cfg, req);
1941
+ if (!identity) {
1942
+ return null;
1943
+ }
1944
+ const { actorId, actorType, actorLabels } = identity;
1945
+ const claim = await postLiveApiJson(cfg, liveMode, '/v1/live-sessions/claim', {
1946
+ clientTime: requestStart,
1947
+ captureMode: 'backend-only',
1948
+ actorId,
1949
+ actorIds: [actorId],
1950
+ actorType,
1951
+ actorLabels,
1952
+ });
1953
+ const sid = normalizeLiveActorId(claim?.childSessionId) ??
1954
+ normalizeLiveActorId(claim?.sessionId);
1955
+ if (!sid) {
1956
+ return null;
1957
+ }
1958
+ const aid = normalizeLiveActorId(claim?.actionId) ?? newActionId(requestStart);
1959
+ const parentSessionId = normalizeLiveActorId(claim?.parentSessionId) ?? '';
1960
+ const liveChild = {
1961
+ sessionId: sid,
1962
+ parentSessionId,
1963
+ previousSessionId: null,
1964
+ actorId,
1965
+ actorType,
1966
+ actorLabels,
1967
+ startedAt: requestStart,
1968
+ expiresAt: requestStart,
1969
+ closeDeadline: requestStart,
1970
+ requestsInFlight: 0,
1971
+ closeTimer: null,
1972
+ };
1973
+ return {
1974
+ sid: liveChild.sessionId,
1975
+ aid,
1976
+ requestStart,
1977
+ child: liveChild,
1978
+ };
1979
+ }
1684
1980
  function resolveIngestUrl(cfg) {
1685
1981
  const envBase = typeof process !== 'undefined' ? process?.env?.REPRO_INGEST_BASE : undefined;
1686
1982
  const ingestBase = String(envBase || cfg.ingestBase || DEFAULT_INGEST_BASE).replace(/\/+$/, '');
@@ -1727,6 +2023,19 @@ function readRequestQueryNumber(req, name) {
1727
2023
  const parsed = Number(raw);
1728
2024
  return Number.isFinite(parsed) ? parsed : null;
1729
2025
  }
2026
+ function hasReproRequestCorrelation(req) {
2027
+ const headers = req.headers || {};
2028
+ if (hasHttpHeader(headers, KAFKA_HEADER_SESSION_ID) ||
2029
+ hasHttpHeader(headers, KAFKA_HEADER_ACTION_ID) ||
2030
+ hasHttpHeader(headers, KAFKA_HEADER_REQUEST_ID) ||
2031
+ hasHttpHeader(headers, KAFKA_HEADER_TRACE_ID) ||
2032
+ hasHttpHeader(headers, KAFKA_HEADER_SPAN_ID) ||
2033
+ hasHttpHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID)) {
2034
+ return true;
2035
+ }
2036
+ return Boolean(readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM) ||
2037
+ readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM));
2038
+ }
1730
2039
  function stripReproQueryFromUrl(rawUrl) {
1731
2040
  if (!rawUrl || !rawUrl.includes('?')) {
1732
2041
  return rawUrl;
@@ -4092,6 +4401,111 @@ async function maybeCaptureDbValueAsync(params, sink) {
4092
4401
  }
4093
4402
  return result.captureRef;
4094
4403
  }
4404
+ function pushUniqueCaptureCandidate(candidates, value) {
4405
+ if (value === undefined)
4406
+ return;
4407
+ if (candidates.some((candidate) => candidate === value))
4408
+ return;
4409
+ candidates.push(value);
4410
+ }
4411
+ function buildResultMetaFullValueCandidates(resultMetaRaw, resultMetaSource) {
4412
+ const candidates = [];
4413
+ pushUniqueCaptureCandidate(candidates, resultMetaRaw);
4414
+ if (resultMetaSource &&
4415
+ typeof resultMetaSource === 'object' &&
4416
+ Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')) {
4417
+ pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
4418
+ }
4419
+ pushUniqueCaptureCandidate(candidates, resultMetaSource);
4420
+ return candidates;
4421
+ }
4422
+ async function maybeCaptureDbValueFromCandidatesAsync(params, sink) {
4423
+ let skippedRef;
4424
+ for (const rawValue of params.rawValues) {
4425
+ const result = await createCapturedValueEntryAsync({
4426
+ target: params.target,
4427
+ rawValue,
4428
+ previewValue: params.previewValue,
4429
+ capture: params.capture,
4430
+ });
4431
+ if (!result)
4432
+ continue;
4433
+ if (result.entry) {
4434
+ sink.push(result.entry);
4435
+ return result.captureRef;
4436
+ }
4437
+ if (!skippedRef) {
4438
+ skippedRef = result.captureRef;
4439
+ }
4440
+ }
4441
+ return skippedRef;
4442
+ }
4443
+ function buildLiveActorArgs(cfg, req) {
4444
+ const rawUrl = String(req.originalUrl || req.url || '/');
4445
+ const path = rawUrl.split('?')[0] || '/';
4446
+ return {
4447
+ req,
4448
+ headers: req.headers,
4449
+ method: String(req.method || 'GET').toUpperCase(),
4450
+ url: rawUrl,
4451
+ path,
4452
+ ip: req.ip ?? req.socket?.remoteAddress ?? null,
4453
+ appId: cfg.appId,
4454
+ appName: cfg.appName,
4455
+ serviceName: cfg.serviceName,
4456
+ };
4457
+ }
4458
+ async function resolveLiveActorIdentity(cfg, req) {
4459
+ const liveMode = normalizeLiveModeConfig(cfg);
4460
+ if (!liveMode) {
4461
+ return null;
4462
+ }
4463
+ const args = buildLiveActorArgs(cfg, req);
4464
+ let rawActorId;
4465
+ try {
4466
+ rawActorId = liveMode.defineActorId
4467
+ ? await liveMode.defineActorId(args)
4468
+ : cfg.actorId;
4469
+ }
4470
+ catch {
4471
+ rawActorId = null;
4472
+ }
4473
+ const actorId = normalizeLiveActorId(rawActorId);
4474
+ if (!actorId) {
4475
+ return null;
4476
+ }
4477
+ const actorType = normalizeLiveActorId(liveMode.actorType) ?? cfg.actorType ?? 'user';
4478
+ const decision = {
4479
+ ...args,
4480
+ actorId,
4481
+ actorType,
4482
+ };
4483
+ if (liveMode.shouldCapture) {
4484
+ let shouldCapture = false;
4485
+ try {
4486
+ shouldCapture = (await liveMode.shouldCapture(decision)) === true;
4487
+ }
4488
+ catch {
4489
+ shouldCapture = false;
4490
+ }
4491
+ if (!shouldCapture) {
4492
+ return null;
4493
+ }
4494
+ }
4495
+ let rawLabels;
4496
+ try {
4497
+ rawLabels =
4498
+ typeof liveMode.actorLabels === 'function'
4499
+ ? await liveMode.actorLabels(decision)
4500
+ : liveMode.actorLabels;
4501
+ }
4502
+ catch {
4503
+ rawLabels = undefined;
4504
+ }
4505
+ const actorLabels = normalizeLiveActorLabels(rawLabels) ?? normalizeLiveActorLabels(cfg.actorLabels);
4506
+ return { actorId, actorType, actorLabels };
4507
+ }
4508
+ const backendLiveActors = new Map();
4095
4509
  function reproMiddleware(cfg) {
4096
4510
  const masking = normalizeMaskingConfig(cfg.masking);
4097
4511
  patchMongoDb(cfg);
@@ -4126,18 +4540,62 @@ function reproMiddleware(cfg) {
4126
4540
  clientRequestEnded = true;
4127
4541
  endClientRequest();
4128
4542
  };
4129
- const sid = req.headers['x-bug-session-id'] || querySid || '';
4130
- const aid = req.headers['x-bug-action-id'] || queryAid || '';
4543
+ const requestStartRaw = Date.now();
4544
+ const headerTs = readHeaderNumber(req.headers[REQUEST_START_HEADER]) ?? queryTs;
4545
+ const requestEpochMs = headerTs !== null ? headerTs : requestStartRaw;
4546
+ let backendLiveState = null;
4547
+ let backendLiveChild = null;
4548
+ const headerSid = req.headers['x-bug-session-id'] || '';
4549
+ const headerAid = req.headers['x-bug-action-id'] || '';
4550
+ const hasIncomingReproSession = Boolean(headerSid || querySid || headerAid || queryAid);
4551
+ const backendLiveContext = !hasIncomingReproSession
4552
+ ? await resolveBackendLiveContext(cfg, req, requestEpochMs)
4553
+ : null;
4554
+ const taggedActorIdentity = hasIncomingReproSession
4555
+ ? await resolveLiveActorIdentity(cfg, req)
4556
+ : null;
4557
+ if (backendLiveContext) {
4558
+ backendLiveState = backendLiveActors.get(backendLiveContext.child.actorId) ?? null;
4559
+ backendLiveChild = backendLiveContext.child;
4560
+ }
4561
+ const sid = headerSid || querySid || backendLiveContext?.sid || '';
4562
+ const aid = headerAid || queryAid || backendLiveContext?.aid || '';
4131
4563
  if (!sid || !aid) {
4132
4564
  res.on('finish', endTrackedClientRequest);
4133
4565
  res.on('close', endTrackedClientRequest);
4134
4566
  return next(); // only capture tagged requests
4135
4567
  }
4136
- const resolvePrivacy = () => getRuntimePrivacyState(cfg).policy ?? null;
4137
- const requestStartRaw = Date.now();
4138
- const headerTs = readHeaderNumber(req.headers[REQUEST_START_HEADER]) ?? queryTs;
4568
+ const captureCfg = backendLiveChild
4569
+ ? {
4570
+ ...cfg,
4571
+ actorId: backendLiveChild.actorId,
4572
+ actorType: backendLiveChild.actorType,
4573
+ actorLabels: backendLiveChild.actorLabels,
4574
+ }
4575
+ : taggedActorIdentity
4576
+ ? {
4577
+ ...cfg,
4578
+ actorId: taggedActorIdentity.actorId,
4579
+ actorType: taggedActorIdentity.actorType,
4580
+ actorLabels: taggedActorIdentity.actorLabels,
4581
+ }
4582
+ : cfg;
4583
+ if (taggedActorIdentity) {
4584
+ post(captureCfg, sid, {
4585
+ entries: [{
4586
+ actionId: aid,
4587
+ sessionActor: {
4588
+ actorId: taggedActorIdentity.actorId,
4589
+ actorType: taggedActorIdentity.actorType,
4590
+ actorLabels: taggedActorIdentity.actorLabels,
4591
+ source: 'node-sdk',
4592
+ },
4593
+ t: requestEpochMs,
4594
+ }],
4595
+ }, { priority: 'high' });
4596
+ }
4597
+ const resolvePrivacy = () => getRuntimePrivacyState(captureCfg).policy ?? null;
4139
4598
  const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
4140
- const requestEpochMs = headerTs !== null ? headerTs : requestStartRaw + clockSkewMs;
4141
4599
  const rid = nextRequestId(requestEpochMs);
4142
4600
  const captureScope = createRequestCaptureScope(rid, 'http');
4143
4601
  const t0 = requestStartRaw;
@@ -4341,15 +4799,15 @@ function reproMiddleware(cfg) {
4341
4799
  const activePrivacy = resolvePrivacy();
4342
4800
  const requestBodyRaw = req.body;
4343
4801
  const requestBodyMaterialization = limitRawInlinePrivacyValue('request.body', requestBodyRaw)
4344
- ?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4802
+ ?? await materializeInlinePrivacyValueAsync('request.body', sanitizeRequestSnapshot(requestBodyRaw), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4345
4803
  const requestBody = requestBodyMaterialization.value;
4346
- const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4347
- const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4348
- const maskedHeaders = await applyPrivacyThenMaskAsync('request.headers', requestHeaders, cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4804
+ const requestParams = await applyPrivacyThenMaskAsync('request.params', sanitizeRequestSnapshot(req.params), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4805
+ const requestQuery = await applyPrivacyThenMaskAsync('request.query', sanitizeRequestSnapshot(req.query), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4806
+ const maskedHeaders = await applyPrivacyThenMaskAsync('request.headers', requestHeaders, captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4349
4807
  const responseBodyMaterialization = capturedBody === undefined
4350
4808
  ? { value: undefined }
4351
4809
  : limitRawInlinePrivacyValue('response.body', capturedBody)
4352
- ?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), cfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4810
+ ?? await materializeInlinePrivacyValueAsync('response.body', sanitizeRequestSnapshot(capturedBody), captureCfg, maskReq, endpointTraceCtx, masking, activePrivacy);
4353
4811
  const responseBody = responseBodyMaterialization.value;
4354
4812
  const requestValueEntries = [];
4355
4813
  const bodyValueCapture = requestBodyMaterialization.skipped
@@ -4359,7 +4817,7 @@ function reproMiddleware(cfg) {
4359
4817
  rawValue: req.body,
4360
4818
  previewValue: requestBody,
4361
4819
  capture: {
4362
- runtimeConfig: cfg,
4820
+ runtimeConfig: captureCfg,
4363
4821
  captureHeaders: cfg.captureHeaders,
4364
4822
  maskReq,
4365
4823
  trace: endpointTraceCtx,
@@ -4372,7 +4830,7 @@ function reproMiddleware(cfg) {
4372
4830
  rawValue: req.params,
4373
4831
  previewValue: requestParams,
4374
4832
  capture: {
4375
- runtimeConfig: cfg,
4833
+ runtimeConfig: captureCfg,
4376
4834
  captureHeaders: cfg.captureHeaders,
4377
4835
  maskReq,
4378
4836
  trace: endpointTraceCtx,
@@ -4385,7 +4843,7 @@ function reproMiddleware(cfg) {
4385
4843
  rawValue: req.query,
4386
4844
  previewValue: requestQuery,
4387
4845
  capture: {
4388
- runtimeConfig: cfg,
4846
+ runtimeConfig: captureCfg,
4389
4847
  captureHeaders: cfg.captureHeaders,
4390
4848
  maskReq,
4391
4849
  trace: endpointTraceCtx,
@@ -4398,7 +4856,7 @@ function reproMiddleware(cfg) {
4398
4856
  rawValue: req.headers,
4399
4857
  previewValue: maskedHeaders,
4400
4858
  capture: {
4401
- runtimeConfig: cfg,
4859
+ runtimeConfig: captureCfg,
4402
4860
  captureHeaders: cfg.captureHeaders,
4403
4861
  maskReq,
4404
4862
  trace: endpointTraceCtx,
@@ -4413,7 +4871,7 @@ function reproMiddleware(cfg) {
4413
4871
  rawValue: capturedBody,
4414
4872
  previewValue: responseBody,
4415
4873
  capture: {
4416
- runtimeConfig: cfg,
4874
+ runtimeConfig: captureCfg,
4417
4875
  captureHeaders: cfg.captureHeaders,
4418
4876
  maskReq,
4419
4877
  trace: endpointTraceCtx,
@@ -4474,7 +4932,7 @@ function reproMiddleware(cfg) {
4474
4932
  }
4475
4933
  const { chosenEndpoint, hasTraceEvents } = chooseRequestEndpoint();
4476
4934
  const { requestPayload, requestValueEntries } = await buildRequestCapturePayloadAsync(chosenEndpoint, hasTraceEvents);
4477
- post(cfg, sid, {
4935
+ post(captureCfg, sid, {
4478
4936
  entries: [{
4479
4937
  actionId: aid,
4480
4938
  request: requestPayload,
@@ -4492,7 +4950,7 @@ function reproMiddleware(cfg) {
4492
4950
  if (requestCaptureManifestScheduled)
4493
4951
  return;
4494
4952
  requestCaptureManifestScheduled = true;
4495
- post(cfg, sid, {
4953
+ post(captureCfg, sid, {
4496
4954
  entries: [{
4497
4955
  actionId: aid,
4498
4956
  requestRid: rid,
@@ -4595,7 +5053,7 @@ function reproMiddleware(cfg) {
4595
5053
  try {
4596
5054
  const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
4597
5055
  const materializedEvents = await materializePendingTraceEventsAsync(pendingEvents, {
4598
- cfg,
5056
+ cfg: captureCfg,
4599
5057
  maskReq,
4600
5058
  masking,
4601
5059
  resolvePrivacy,
@@ -4623,7 +5081,7 @@ function reproMiddleware(cfg) {
4623
5081
  for (let i = 0; i < traceBatches.length; i++) {
4624
5082
  const batch = traceBatches[i];
4625
5083
  const traceValues = collectBatchTraceValueEntries(batch, i);
4626
- post(cfg, sid, {
5084
+ post(captureCfg, sid, {
4627
5085
  entries: [{
4628
5086
  actionId: aid,
4629
5087
  trace: batch,
@@ -4645,6 +5103,9 @@ function reproMiddleware(cfg) {
4645
5103
  }
4646
5104
  sessionDrainWait = waitForSessionDrain(sid);
4647
5105
  endSessionRequest(sid);
5106
+ if (backendLiveState && backendLiveChild) {
5107
+ releaseBackendLiveRequest(cfg, normalizeLiveModeConfig(cfg), backendLiveState, backendLiveChild, finishedAtAligned ?? alignedNow());
5108
+ }
4648
5109
  endTrackedClientRequest();
4649
5110
  if (__TRACER_READY) {
4650
5111
  bumpIdle();
@@ -5285,9 +5746,9 @@ async function emitDbQueryAsync(cfg, sid, aid, payload) {
5285
5746
  previewValue: queryPreview,
5286
5747
  capture,
5287
5748
  }, dbValues);
5288
- const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueAsync({
5749
+ const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
5289
5750
  target: 'db.resultMeta',
5290
- rawValue: payload.resultMetaRaw ?? resultMetaSource,
5751
+ rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
5291
5752
  previewValue: resultMetaPreview,
5292
5753
  capture,
5293
5754
  }, dbValues);
@@ -8762,9 +9223,11 @@ async function initRepro(cfg) {
8762
9223
  const ingestCfg = toIngestConfig(cfg);
8763
9224
  reproAutoMiddlewareConfig = {
8764
9225
  ...ingestCfg,
9226
+ apiBase: cfg.apiBase,
8765
9227
  captureHeaders: cfg.captureHeaders,
8766
9228
  masking: cfg.masking,
8767
9229
  privacy: cfg.privacy,
9230
+ liveMode: cfg.liveMode,
8768
9231
  };
8769
9232
  const integrationCfg = {
8770
9233
  ...reproAutoMiddlewareConfig,
@@ -8816,6 +9279,9 @@ exports.__reproTestHooks = {
8816
9279
  lastClientActivityAt = 0;
8817
9280
  sdkBackgroundQuietUntil = 0;
8818
9281
  },
9282
+ setBackendLiveTimingForTest(override) {
9283
+ backendLiveTimingOverrideForTest = override;
9284
+ },
8819
9285
  runWithRequestContextForTest(ctx, fn) {
8820
9286
  return als.run({
8821
9287
  ...ctx,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reproapp/node-sdk",
3
- "version": "0.0.37",
3
+ "version": "0.0.39",
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/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-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",
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-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",
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"
17
17
  },
18
18
  "peerDependencies": {