@reproapp/node-sdk 0.0.38 → 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/README.md +225 -310
- package/dist/index.d.ts +42 -0
- package/dist/index.js +447 -20
- package/package.json +2 -2
- package/src/index.ts +670 -20
- package/test/backend-live-mode.test.js +403 -0
package/src/index.ts
CHANGED
|
@@ -323,6 +323,9 @@ const KAFKA_HEADER_SESSION_ID = 'x-bug-session-id';
|
|
|
323
323
|
const KAFKA_HEADER_ACTION_ID = 'x-bug-action-id';
|
|
324
324
|
const DEFAULT_INGEST_BASE = 'http://localhost:8080';
|
|
325
325
|
const DEFAULT_INGEST_PATH = '/v1/ingest/events';
|
|
326
|
+
const DEFAULT_LIVE_SESSION_DURATION_MS = 3 * 60_000;
|
|
327
|
+
const DEFAULT_LIVE_SEGMENT_IDLE_MS = 15_000;
|
|
328
|
+
const DEFAULT_LIVE_SEGMENT_DURATION_MS = DEFAULT_LIVE_SESSION_DURATION_MS;
|
|
326
329
|
|
|
327
330
|
function escapeRx(s: string) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
328
331
|
let __TRACER__: TracerApi | null = null;
|
|
@@ -2078,6 +2081,397 @@ function toIngestConfig(cfg: IngestClientConfig): IngestClientConfig {
|
|
|
2078
2081
|
};
|
|
2079
2082
|
}
|
|
2080
2083
|
|
|
2084
|
+
function normalizeLiveModeConfig(cfg: ReproMiddlewareConfig): ReproLiveModeConfig | null {
|
|
2085
|
+
const liveMode = cfg.liveMode;
|
|
2086
|
+
if (liveMode === true) {
|
|
2087
|
+
return { enabled: true };
|
|
2088
|
+
}
|
|
2089
|
+
if (!liveMode || typeof liveMode !== 'object') {
|
|
2090
|
+
return null;
|
|
2091
|
+
}
|
|
2092
|
+
if (liveMode.enabled === false) {
|
|
2093
|
+
return null;
|
|
2094
|
+
}
|
|
2095
|
+
return liveMode;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
type BackendLiveTimingConfig = {
|
|
2099
|
+
sessionMaxDurationMs: number;
|
|
2100
|
+
segmentMaxDurationMs: number;
|
|
2101
|
+
segmentIdleMs: number;
|
|
2102
|
+
};
|
|
2103
|
+
|
|
2104
|
+
let backendLiveTimingOverrideForTest: Partial<BackendLiveTimingConfig> | null = null;
|
|
2105
|
+
|
|
2106
|
+
function positiveInternalDuration(value: unknown, fallback: number): number {
|
|
2107
|
+
const numeric = Number(value);
|
|
2108
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
|
|
2109
|
+
return Math.max(1, Math.trunc(numeric));
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
function getBackendLiveTiming(): BackendLiveTimingConfig {
|
|
2113
|
+
return {
|
|
2114
|
+
sessionMaxDurationMs: positiveInternalDuration(
|
|
2115
|
+
backendLiveTimingOverrideForTest?.sessionMaxDurationMs,
|
|
2116
|
+
DEFAULT_LIVE_SESSION_DURATION_MS,
|
|
2117
|
+
),
|
|
2118
|
+
segmentMaxDurationMs: positiveInternalDuration(
|
|
2119
|
+
backendLiveTimingOverrideForTest?.segmentMaxDurationMs,
|
|
2120
|
+
DEFAULT_LIVE_SEGMENT_DURATION_MS,
|
|
2121
|
+
),
|
|
2122
|
+
segmentIdleMs: positiveInternalDuration(
|
|
2123
|
+
backendLiveTimingOverrideForTest?.segmentIdleMs,
|
|
2124
|
+
DEFAULT_LIVE_SEGMENT_IDLE_MS,
|
|
2125
|
+
),
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function normalizeLiveActorId(value: unknown): string | null {
|
|
2130
|
+
if (value === undefined || value === null) return null;
|
|
2131
|
+
const normalized = String(value).trim();
|
|
2132
|
+
return normalized.length ? normalized : null;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function normalizeLiveActorLabels(
|
|
2136
|
+
value: Record<string, string | number | boolean | null | undefined> | null | undefined,
|
|
2137
|
+
): Record<string, string> | undefined {
|
|
2138
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
2139
|
+
const labels: Record<string, string> = {};
|
|
2140
|
+
for (const [key, rawValue] of Object.entries(value)) {
|
|
2141
|
+
const normalizedKey = key.trim();
|
|
2142
|
+
if (!normalizedKey || rawValue === undefined || rawValue === null) continue;
|
|
2143
|
+
const normalizedValue = String(rawValue).trim();
|
|
2144
|
+
if (normalizedValue) labels[normalizedKey] = normalizedValue;
|
|
2145
|
+
}
|
|
2146
|
+
return Object.keys(labels).length ? labels : undefined;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
function newSessionId(base: number = Date.now()): string {
|
|
2150
|
+
return `S_${Math.max(0, Math.trunc(base))}_${randomUUID().slice(0, 8)}`;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
function newActionId(base: number = Date.now()): string {
|
|
2154
|
+
return `A_${Math.max(0, Math.trunc(base))}_${randomUUID().slice(0, 8)}`;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function resolveLiveApiBase(cfg: ReproMiddlewareConfig, liveMode?: ReproLiveModeConfig | null): string | null {
|
|
2158
|
+
const env = typeof process !== 'undefined' ? (process as any)?.env : undefined;
|
|
2159
|
+
const raw =
|
|
2160
|
+
liveMode?.apiBase ||
|
|
2161
|
+
cfg.apiBase ||
|
|
2162
|
+
env?.REPRO_API_BASE_URL ||
|
|
2163
|
+
env?.REPRO_API_BASE ||
|
|
2164
|
+
(cfg.privacy as RuntimePrivacyConfig | undefined)?.apiBase ||
|
|
2165
|
+
null;
|
|
2166
|
+
if (typeof raw !== 'string' || !raw.trim()) return null;
|
|
2167
|
+
return raw.trim().replace(/\/+$/, '');
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
function liveApiHeaders(cfg: ReproMiddlewareConfig): Record<string, string> {
|
|
2171
|
+
const headers: Record<string, string> = {
|
|
2172
|
+
'Content-Type': 'application/json',
|
|
2173
|
+
'X-App-Id': cfg.appId,
|
|
2174
|
+
};
|
|
2175
|
+
if (cfg.appSecret) {
|
|
2176
|
+
headers['X-App-Secret'] = cfg.appSecret;
|
|
2177
|
+
}
|
|
2178
|
+
if (cfg.appName) {
|
|
2179
|
+
headers['X-App-Name'] = cfg.appName;
|
|
2180
|
+
}
|
|
2181
|
+
return headers;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
async function postLiveApi(
|
|
2185
|
+
cfg: ReproMiddlewareConfig,
|
|
2186
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2187
|
+
path: string,
|
|
2188
|
+
body: Record<string, any>,
|
|
2189
|
+
): Promise<void> {
|
|
2190
|
+
await postLiveApiJson(cfg, liveMode, path, body);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
async function postLiveApiJson<T = Record<string, any>>(
|
|
2194
|
+
cfg: ReproMiddlewareConfig,
|
|
2195
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2196
|
+
path: string,
|
|
2197
|
+
body: Record<string, any>,
|
|
2198
|
+
): Promise<T | null> {
|
|
2199
|
+
const apiBase = resolveLiveApiBase(cfg, liveMode);
|
|
2200
|
+
const fetchImpl = (globalThis as any).fetch;
|
|
2201
|
+
if (!apiBase || !cfg.appId || !cfg.appSecret || typeof fetchImpl !== 'function') {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
const res = await fetchImpl(`${apiBase}${path}`, {
|
|
2206
|
+
method: 'POST',
|
|
2207
|
+
headers: liveApiHeaders(cfg),
|
|
2208
|
+
body: JSON.stringify(body),
|
|
2209
|
+
});
|
|
2210
|
+
if (!res?.ok) {
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
try {
|
|
2214
|
+
return await res.json();
|
|
2215
|
+
} catch {
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
} catch {
|
|
2219
|
+
// never break user code
|
|
2220
|
+
return null;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function enqueueLiveSessionStart(
|
|
2225
|
+
cfg: ReproMiddlewareConfig,
|
|
2226
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2227
|
+
payload: Record<string, any>,
|
|
2228
|
+
): void {
|
|
2229
|
+
scheduleSdkBackgroundWork(
|
|
2230
|
+
() => postLiveApi(cfg, liveMode, '/v1/live-sessions', payload),
|
|
2231
|
+
{ priority: 'high' },
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
function enqueueLiveSessionFinish(
|
|
2236
|
+
cfg: ReproMiddlewareConfig,
|
|
2237
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2238
|
+
sessionId: string,
|
|
2239
|
+
): void {
|
|
2240
|
+
scheduleSdkBackgroundWork(
|
|
2241
|
+
() => postLiveApi(
|
|
2242
|
+
cfg,
|
|
2243
|
+
liveMode,
|
|
2244
|
+
`/v1/live-sessions/${encodeURIComponent(sessionId)}/finish`,
|
|
2245
|
+
{ notes: '' },
|
|
2246
|
+
),
|
|
2247
|
+
{ priority: 'high' },
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
function emitBackendLiveSessionManifest(
|
|
2252
|
+
cfg: ReproMiddlewareConfig,
|
|
2253
|
+
session: BackendLiveParentSession | BackendLiveChildSession,
|
|
2254
|
+
options: {
|
|
2255
|
+
sessionKind: 'parent' | 'child';
|
|
2256
|
+
actorId: string;
|
|
2257
|
+
actorType: string;
|
|
2258
|
+
actorLabels?: Record<string, string>;
|
|
2259
|
+
parentSessionId?: string | null;
|
|
2260
|
+
previousSessionId?: string | null;
|
|
2261
|
+
closedAt: number;
|
|
2262
|
+
},
|
|
2263
|
+
): void {
|
|
2264
|
+
post(cfg, session.sessionId, {
|
|
2265
|
+
entries: [{
|
|
2266
|
+
sessionCaptureManifest: {
|
|
2267
|
+
mode: 'backend-only',
|
|
2268
|
+
captureMode: 'backend-only',
|
|
2269
|
+
sessionKind: options.sessionKind,
|
|
2270
|
+
parentSessionId: options.parentSessionId ?? null,
|
|
2271
|
+
previousSessionId: options.previousSessionId ?? null,
|
|
2272
|
+
actorId: options.actorId,
|
|
2273
|
+
actorType: options.actorType,
|
|
2274
|
+
actorLabels: options.actorLabels,
|
|
2275
|
+
expected: {
|
|
2276
|
+
frontendActionDoc: 0,
|
|
2277
|
+
rrwebChunk: 0,
|
|
2278
|
+
frontendSessionEnd: 0,
|
|
2279
|
+
},
|
|
2280
|
+
closedAt: options.closedAt,
|
|
2281
|
+
},
|
|
2282
|
+
t: options.closedAt,
|
|
2283
|
+
}],
|
|
2284
|
+
}, { priority: 'high' });
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function closeBackendLiveParent(
|
|
2288
|
+
cfg: ReproMiddlewareConfig,
|
|
2289
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2290
|
+
state: BackendLiveActorState,
|
|
2291
|
+
parent: BackendLiveParentSession,
|
|
2292
|
+
closedAt: number,
|
|
2293
|
+
): void {
|
|
2294
|
+
if (parent.closed) {
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
parent.closed = true;
|
|
2298
|
+
if (parent.closeTimer) {
|
|
2299
|
+
try { clearTimeout(parent.closeTimer); } catch {}
|
|
2300
|
+
parent.closeTimer = null;
|
|
2301
|
+
}
|
|
2302
|
+
state.previousParentSessionId = parent.sessionId;
|
|
2303
|
+
if (state.parent === parent) {
|
|
2304
|
+
state.parent = null;
|
|
2305
|
+
}
|
|
2306
|
+
emitBackendLiveSessionManifest(cfg, parent, {
|
|
2307
|
+
sessionKind: 'parent',
|
|
2308
|
+
actorId: parent.actorId,
|
|
2309
|
+
actorType: parent.actorType,
|
|
2310
|
+
actorLabels: parent.actorLabels,
|
|
2311
|
+
previousSessionId: parent.previousSessionId,
|
|
2312
|
+
closedAt,
|
|
2313
|
+
});
|
|
2314
|
+
enqueueLiveSessionFinish(cfg, liveMode, parent.sessionId);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function scheduleBackendLiveParentClose(
|
|
2318
|
+
cfg: ReproMiddlewareConfig,
|
|
2319
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2320
|
+
state: BackendLiveActorState,
|
|
2321
|
+
parent: BackendLiveParentSession,
|
|
2322
|
+
): void {
|
|
2323
|
+
if (parent.closed) {
|
|
2324
|
+
return;
|
|
2325
|
+
}
|
|
2326
|
+
if (parent.closeTimer) {
|
|
2327
|
+
try { clearTimeout(parent.closeTimer); } catch {}
|
|
2328
|
+
parent.closeTimer = null;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
const now = Date.now();
|
|
2332
|
+
const activeChild = state.activeChild;
|
|
2333
|
+
const activeChildBelongsToParent =
|
|
2334
|
+
activeChild?.parentSessionId === parent.sessionId;
|
|
2335
|
+
const closeAt =
|
|
2336
|
+
activeChildBelongsToParent && now >= parent.expiresAt
|
|
2337
|
+
? Math.max(parent.expiresAt, activeChild.closeDeadline, now + 1)
|
|
2338
|
+
: parent.expiresAt;
|
|
2339
|
+
|
|
2340
|
+
parent.closeTimer = setTimeout(() => {
|
|
2341
|
+
parent.closeTimer = null;
|
|
2342
|
+
if (parent.closed || state.parent !== parent) {
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
const currentChild = state.activeChild;
|
|
2347
|
+
if (currentChild?.parentSessionId === parent.sessionId) {
|
|
2348
|
+
if (currentChild.requestsInFlight <= 0) {
|
|
2349
|
+
scheduleBackendLiveChildClose(cfg, liveMode, state, currentChild);
|
|
2350
|
+
}
|
|
2351
|
+
scheduleBackendLiveParentClose(cfg, liveMode, state, parent);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2355
|
+
closeBackendLiveParent(cfg, liveMode, state, parent, Date.now());
|
|
2356
|
+
}, Math.max(0, closeAt - now));
|
|
2357
|
+
try { (parent.closeTimer as any).unref?.(); } catch {}
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
function scheduleBackendLiveChildClose(
|
|
2361
|
+
cfg: ReproMiddlewareConfig,
|
|
2362
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2363
|
+
state: BackendLiveActorState,
|
|
2364
|
+
child: BackendLiveChildSession,
|
|
2365
|
+
): void {
|
|
2366
|
+
if (child.closeTimer) {
|
|
2367
|
+
try { clearTimeout(child.closeTimer); } catch {}
|
|
2368
|
+
child.closeTimer = null;
|
|
2369
|
+
}
|
|
2370
|
+
const delay = Math.max(0, child.closeDeadline - Date.now());
|
|
2371
|
+
child.closeTimer = setTimeout(() => {
|
|
2372
|
+
child.closeTimer = null;
|
|
2373
|
+
if (child.requestsInFlight > 0 || Date.now() < child.closeDeadline) {
|
|
2374
|
+
scheduleBackendLiveChildClose(cfg, liveMode, state, child);
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
if (state.activeChild === child) {
|
|
2378
|
+
state.activeChild = null;
|
|
2379
|
+
}
|
|
2380
|
+
state.previousChildSessionId = child.sessionId;
|
|
2381
|
+
const closedAt = Date.now();
|
|
2382
|
+
emitBackendLiveSessionManifest(cfg, child, {
|
|
2383
|
+
sessionKind: 'child',
|
|
2384
|
+
actorId: child.actorId,
|
|
2385
|
+
actorType: child.actorType,
|
|
2386
|
+
actorLabels: child.actorLabels,
|
|
2387
|
+
parentSessionId: child.parentSessionId,
|
|
2388
|
+
previousSessionId: child.previousSessionId,
|
|
2389
|
+
closedAt,
|
|
2390
|
+
});
|
|
2391
|
+
enqueueLiveSessionFinish(cfg, liveMode, child.sessionId);
|
|
2392
|
+
}, delay);
|
|
2393
|
+
try { (child.closeTimer as any).unref?.(); } catch {}
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
function releaseBackendLiveRequest(
|
|
2397
|
+
cfg: ReproMiddlewareConfig,
|
|
2398
|
+
liveMode: ReproLiveModeConfig | null,
|
|
2399
|
+
state: BackendLiveActorState,
|
|
2400
|
+
child: BackendLiveChildSession,
|
|
2401
|
+
finishedAt: number,
|
|
2402
|
+
): void {
|
|
2403
|
+
child.requestsInFlight = Math.max(0, child.requestsInFlight - 1);
|
|
2404
|
+
const idleMs = getBackendLiveTiming().segmentIdleMs;
|
|
2405
|
+
child.closeDeadline = Math.max(child.closeDeadline, finishedAt + idleMs);
|
|
2406
|
+
if (child.requestsInFlight <= 0) {
|
|
2407
|
+
scheduleBackendLiveChildClose(cfg, liveMode, state, child);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
async function resolveBackendLiveContext(
|
|
2412
|
+
cfg: ReproMiddlewareConfig,
|
|
2413
|
+
req: Request,
|
|
2414
|
+
requestStart: number,
|
|
2415
|
+
): Promise<BackendLiveResolvedContext | null> {
|
|
2416
|
+
const liveMode = normalizeLiveModeConfig(cfg);
|
|
2417
|
+
if (!liveMode?.backendOnly) {
|
|
2418
|
+
return null;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
2422
|
+
if (method === 'OPTIONS' && !hasReproRequestCorrelation(req)) {
|
|
2423
|
+
return null;
|
|
2424
|
+
}
|
|
2425
|
+
const identity = await resolveLiveActorIdentity(cfg, req);
|
|
2426
|
+
if (!identity) {
|
|
2427
|
+
return null;
|
|
2428
|
+
}
|
|
2429
|
+
const { actorId, actorType, actorLabels } = identity;
|
|
2430
|
+
|
|
2431
|
+
const claim = await postLiveApiJson<BackendLiveClaimResponse>(
|
|
2432
|
+
cfg,
|
|
2433
|
+
liveMode,
|
|
2434
|
+
'/v1/live-sessions/claim',
|
|
2435
|
+
{
|
|
2436
|
+
clientTime: requestStart,
|
|
2437
|
+
captureMode: 'backend-only',
|
|
2438
|
+
actorId,
|
|
2439
|
+
actorIds: [actorId],
|
|
2440
|
+
actorType,
|
|
2441
|
+
actorLabels,
|
|
2442
|
+
},
|
|
2443
|
+
);
|
|
2444
|
+
const sid =
|
|
2445
|
+
normalizeLiveActorId(claim?.childSessionId) ??
|
|
2446
|
+
normalizeLiveActorId(claim?.sessionId);
|
|
2447
|
+
if (!sid) {
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
const aid = normalizeLiveActorId(claim?.actionId) ?? newActionId(requestStart);
|
|
2452
|
+
const parentSessionId = normalizeLiveActorId(claim?.parentSessionId) ?? '';
|
|
2453
|
+
const liveChild: BackendLiveChildSession = {
|
|
2454
|
+
sessionId: sid,
|
|
2455
|
+
parentSessionId,
|
|
2456
|
+
previousSessionId: null,
|
|
2457
|
+
actorId,
|
|
2458
|
+
actorType,
|
|
2459
|
+
actorLabels,
|
|
2460
|
+
startedAt: requestStart,
|
|
2461
|
+
expiresAt: requestStart,
|
|
2462
|
+
closeDeadline: requestStart,
|
|
2463
|
+
requestsInFlight: 0,
|
|
2464
|
+
closeTimer: null,
|
|
2465
|
+
};
|
|
2466
|
+
|
|
2467
|
+
return {
|
|
2468
|
+
sid: liveChild.sessionId,
|
|
2469
|
+
aid,
|
|
2470
|
+
requestStart,
|
|
2471
|
+
child: liveChild,
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2081
2475
|
function resolveIngestUrl(cfg: IngestClientConfig): string {
|
|
2082
2476
|
const envBase = typeof process !== 'undefined' ? (process as any)?.env?.REPRO_INGEST_BASE : undefined;
|
|
2083
2477
|
const ingestBase = String(envBase || cfg.ingestBase || DEFAULT_INGEST_BASE).replace(/\/+$/, '');
|
|
@@ -2125,6 +2519,24 @@ function readRequestQueryNumber(req: Request, name: string): number | null {
|
|
|
2125
2519
|
return Number.isFinite(parsed) ? parsed : null;
|
|
2126
2520
|
}
|
|
2127
2521
|
|
|
2522
|
+
function hasReproRequestCorrelation(req: Request): boolean {
|
|
2523
|
+
const headers = req.headers || {};
|
|
2524
|
+
if (
|
|
2525
|
+
hasHttpHeader(headers, KAFKA_HEADER_SESSION_ID) ||
|
|
2526
|
+
hasHttpHeader(headers, KAFKA_HEADER_ACTION_ID) ||
|
|
2527
|
+
hasHttpHeader(headers, KAFKA_HEADER_REQUEST_ID) ||
|
|
2528
|
+
hasHttpHeader(headers, KAFKA_HEADER_TRACE_ID) ||
|
|
2529
|
+
hasHttpHeader(headers, KAFKA_HEADER_SPAN_ID) ||
|
|
2530
|
+
hasHttpHeader(headers, KAFKA_HEADER_PARENT_SPAN_ID)
|
|
2531
|
+
) {
|
|
2532
|
+
return true;
|
|
2533
|
+
}
|
|
2534
|
+
return Boolean(
|
|
2535
|
+
readRequestQueryParam(req, REPRO_QUERY_SESSION_PARAM) ||
|
|
2536
|
+
readRequestQueryParam(req, REPRO_QUERY_ACTION_PARAM),
|
|
2537
|
+
);
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2128
2540
|
function stripReproQueryFromUrl(rawUrl: string): string {
|
|
2129
2541
|
if (!rawUrl || !rawUrl.includes('?')) {
|
|
2130
2542
|
return rawUrl;
|
|
@@ -5008,14 +5420,193 @@ async function maybeCaptureDbValueFromCandidatesAsync(
|
|
|
5008
5420
|
// reproMiddleware — unchanged behavior + passive per-request trace
|
|
5009
5421
|
// ===================================================================
|
|
5010
5422
|
export type ReproMiddlewareConfig = IngestClientConfig & {
|
|
5423
|
+
/** API base used for backend-owned live session documents. Defaults to REPRO_API_BASE_URL/REPRO_API_BASE/privacy.apiBase. */
|
|
5424
|
+
apiBase?: string;
|
|
5011
5425
|
/** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
|
|
5012
5426
|
captureHeaders?: boolean | HeaderCaptureOptions;
|
|
5013
5427
|
/** Optional masking rules for request/response payloads and function traces. */
|
|
5014
5428
|
masking?: ReproMaskingConfig;
|
|
5015
5429
|
/** Optional privacy policy config for deterministic runtime transformations. */
|
|
5016
5430
|
privacy?: RuntimePrivacyConfig;
|
|
5431
|
+
/** Opt-in production/live capture mode. Staging/manual tagged capture is unchanged when disabled. */
|
|
5432
|
+
liveMode?: ReproLiveModeConfig | boolean;
|
|
5433
|
+
};
|
|
5434
|
+
|
|
5435
|
+
export type ReproLiveActorArgs = {
|
|
5436
|
+
req: Request;
|
|
5437
|
+
headers: Request['headers'];
|
|
5438
|
+
method: string;
|
|
5439
|
+
url: string;
|
|
5440
|
+
path: string;
|
|
5441
|
+
ip?: string | null;
|
|
5442
|
+
appId: string;
|
|
5443
|
+
appName?: string;
|
|
5444
|
+
serviceName?: string;
|
|
5445
|
+
};
|
|
5446
|
+
|
|
5447
|
+
export type ReproLiveActorDecision = ReproLiveActorArgs & {
|
|
5448
|
+
actorId: string;
|
|
5449
|
+
actorType?: string;
|
|
5450
|
+
actorLabels?: Record<string, string>;
|
|
5451
|
+
};
|
|
5452
|
+
|
|
5453
|
+
export type ReproLiveModeConfig = {
|
|
5454
|
+
enabled?: boolean;
|
|
5455
|
+
backendOnly?: boolean;
|
|
5456
|
+
/**
|
|
5457
|
+
* Stable actor/correlation key for live capture. Return null/undefined/empty
|
|
5458
|
+
* to skip capture for that request.
|
|
5459
|
+
*/
|
|
5460
|
+
defineActorId?: (
|
|
5461
|
+
args: ReproLiveActorArgs,
|
|
5462
|
+
) => string | number | null | undefined | Promise<string | number | null | undefined>;
|
|
5463
|
+
/**
|
|
5464
|
+
* Optional filter for segmenting which actors/requests are captured.
|
|
5465
|
+
*/
|
|
5466
|
+
shouldCapture?: (
|
|
5467
|
+
args: ReproLiveActorDecision,
|
|
5468
|
+
) => boolean | Promise<boolean>;
|
|
5469
|
+
actorType?: string;
|
|
5470
|
+
actorLabels?:
|
|
5471
|
+
| Record<string, string | number | boolean | null | undefined>
|
|
5472
|
+
| ((args: ReproLiveActorDecision) =>
|
|
5473
|
+
| Record<string, string | number | boolean | null | undefined>
|
|
5474
|
+
| null
|
|
5475
|
+
| undefined
|
|
5476
|
+
| Promise<Record<string, string | number | boolean | null | undefined> | null | undefined>);
|
|
5477
|
+
apiBase?: string;
|
|
5478
|
+
};
|
|
5479
|
+
|
|
5480
|
+
type BackendLiveParentSession = {
|
|
5481
|
+
sessionId: string;
|
|
5482
|
+
startedAt: number;
|
|
5483
|
+
expiresAt: number;
|
|
5484
|
+
previousSessionId: string | null;
|
|
5485
|
+
actorId: string;
|
|
5486
|
+
actorType: string;
|
|
5487
|
+
actorLabels?: Record<string, string>;
|
|
5488
|
+
closeTimer?: NodeJS.Timeout | null;
|
|
5489
|
+
closed?: boolean;
|
|
5490
|
+
};
|
|
5491
|
+
|
|
5492
|
+
type BackendLiveChildSession = {
|
|
5493
|
+
sessionId: string;
|
|
5494
|
+
parentSessionId: string;
|
|
5495
|
+
previousSessionId: string | null;
|
|
5496
|
+
actorId: string;
|
|
5497
|
+
actorType: string;
|
|
5498
|
+
actorLabels?: Record<string, string>;
|
|
5499
|
+
startedAt: number;
|
|
5500
|
+
expiresAt: number;
|
|
5501
|
+
closeDeadline: number;
|
|
5502
|
+
requestsInFlight: number;
|
|
5503
|
+
closeTimer?: NodeJS.Timeout | null;
|
|
5504
|
+
};
|
|
5505
|
+
|
|
5506
|
+
type BackendLiveActorState = {
|
|
5507
|
+
actorId: string;
|
|
5508
|
+
parent: BackendLiveParentSession | null;
|
|
5509
|
+
previousParentSessionId: string | null;
|
|
5510
|
+
activeChild: BackendLiveChildSession | null;
|
|
5511
|
+
previousChildSessionId: string | null;
|
|
5512
|
+
};
|
|
5513
|
+
|
|
5514
|
+
type BackendLiveActorIdentity = {
|
|
5515
|
+
actorId: string;
|
|
5516
|
+
actorType: string;
|
|
5517
|
+
actorLabels?: Record<string, string>;
|
|
5518
|
+
};
|
|
5519
|
+
|
|
5520
|
+
type BackendLiveResolvedContext = {
|
|
5521
|
+
sid: string;
|
|
5522
|
+
aid: string;
|
|
5523
|
+
requestStart: number;
|
|
5524
|
+
child: BackendLiveChildSession;
|
|
5525
|
+
};
|
|
5526
|
+
|
|
5527
|
+
type BackendLiveClaimResponse = {
|
|
5528
|
+
parentSessionId?: string | null;
|
|
5529
|
+
childSessionId?: string | null;
|
|
5530
|
+
sessionId?: string | null;
|
|
5531
|
+
actionId?: string | null;
|
|
5532
|
+
clockOffsetMs?: number | null;
|
|
5017
5533
|
};
|
|
5018
5534
|
|
|
5535
|
+
function buildLiveActorArgs(cfg: ReproMiddlewareConfig, req: Request): ReproLiveActorArgs {
|
|
5536
|
+
const rawUrl = String((req as any).originalUrl || req.url || '/');
|
|
5537
|
+
const path = rawUrl.split('?')[0] || '/';
|
|
5538
|
+
return {
|
|
5539
|
+
req,
|
|
5540
|
+
headers: req.headers,
|
|
5541
|
+
method: String(req.method || 'GET').toUpperCase(),
|
|
5542
|
+
url: rawUrl,
|
|
5543
|
+
path,
|
|
5544
|
+
ip: (req as any).ip ?? (req as any).socket?.remoteAddress ?? null,
|
|
5545
|
+
appId: cfg.appId,
|
|
5546
|
+
appName: cfg.appName,
|
|
5547
|
+
serviceName: cfg.serviceName,
|
|
5548
|
+
};
|
|
5549
|
+
}
|
|
5550
|
+
|
|
5551
|
+
async function resolveLiveActorIdentity(
|
|
5552
|
+
cfg: ReproMiddlewareConfig,
|
|
5553
|
+
req: Request,
|
|
5554
|
+
): Promise<BackendLiveActorIdentity | null> {
|
|
5555
|
+
const liveMode = normalizeLiveModeConfig(cfg);
|
|
5556
|
+
if (!liveMode) {
|
|
5557
|
+
return null;
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
const args = buildLiveActorArgs(cfg, req);
|
|
5561
|
+
let rawActorId: string | number | null | undefined;
|
|
5562
|
+
try {
|
|
5563
|
+
rawActorId = liveMode.defineActorId
|
|
5564
|
+
? await liveMode.defineActorId(args)
|
|
5565
|
+
: cfg.actorId;
|
|
5566
|
+
} catch {
|
|
5567
|
+
rawActorId = null;
|
|
5568
|
+
}
|
|
5569
|
+
const actorId = normalizeLiveActorId(rawActorId);
|
|
5570
|
+
if (!actorId) {
|
|
5571
|
+
return null;
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
const actorType = normalizeLiveActorId(liveMode.actorType) ?? cfg.actorType ?? 'user';
|
|
5575
|
+
const decision: ReproLiveActorDecision = {
|
|
5576
|
+
...args,
|
|
5577
|
+
actorId,
|
|
5578
|
+
actorType,
|
|
5579
|
+
};
|
|
5580
|
+
if (liveMode.shouldCapture) {
|
|
5581
|
+
let shouldCapture = false;
|
|
5582
|
+
try {
|
|
5583
|
+
shouldCapture = (await liveMode.shouldCapture(decision)) === true;
|
|
5584
|
+
} catch {
|
|
5585
|
+
shouldCapture = false;
|
|
5586
|
+
}
|
|
5587
|
+
if (!shouldCapture) {
|
|
5588
|
+
return null;
|
|
5589
|
+
}
|
|
5590
|
+
}
|
|
5591
|
+
|
|
5592
|
+
let rawLabels:
|
|
5593
|
+
| Record<string, string | number | boolean | null | undefined>
|
|
5594
|
+
| null
|
|
5595
|
+
| undefined;
|
|
5596
|
+
try {
|
|
5597
|
+
rawLabels =
|
|
5598
|
+
typeof liveMode.actorLabels === 'function'
|
|
5599
|
+
? await liveMode.actorLabels(decision)
|
|
5600
|
+
: liveMode.actorLabels;
|
|
5601
|
+
} catch {
|
|
5602
|
+
rawLabels = undefined;
|
|
5603
|
+
}
|
|
5604
|
+
const actorLabels = normalizeLiveActorLabels(rawLabels) ?? normalizeLiveActorLabels(cfg.actorLabels);
|
|
5605
|
+
return { actorId, actorType, actorLabels };
|
|
5606
|
+
}
|
|
5607
|
+
|
|
5608
|
+
const backendLiveActors = new Map<string, BackendLiveActorState>();
|
|
5609
|
+
|
|
5019
5610
|
export type ReproInitConfig = ReproMiddlewareConfig & {
|
|
5020
5611
|
/**
|
|
5021
5612
|
* Tracing configuration for function/http instrumentation.
|
|
@@ -5060,20 +5651,65 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5060
5651
|
endClientRequest();
|
|
5061
5652
|
};
|
|
5062
5653
|
|
|
5063
|
-
const
|
|
5064
|
-
const
|
|
5654
|
+
const requestStartRaw = Date.now();
|
|
5655
|
+
const headerTs = readHeaderNumber(req.headers[REQUEST_START_HEADER]) ?? queryTs;
|
|
5656
|
+
const requestEpochMs = headerTs !== null ? headerTs : requestStartRaw;
|
|
5657
|
+
let backendLiveState: BackendLiveActorState | null = null;
|
|
5658
|
+
let backendLiveChild: BackendLiveChildSession | null = null;
|
|
5659
|
+
const headerSid = (req.headers['x-bug-session-id'] as string) || '';
|
|
5660
|
+
const headerAid = (req.headers['x-bug-action-id'] as string) || '';
|
|
5661
|
+
const hasIncomingReproSession = Boolean(headerSid || querySid || headerAid || queryAid);
|
|
5662
|
+
const backendLiveContext =
|
|
5663
|
+
!hasIncomingReproSession
|
|
5664
|
+
? await resolveBackendLiveContext(cfg, req, requestEpochMs)
|
|
5665
|
+
: null;
|
|
5666
|
+
const taggedActorIdentity = hasIncomingReproSession
|
|
5667
|
+
? await resolveLiveActorIdentity(cfg, req)
|
|
5668
|
+
: null;
|
|
5669
|
+
if (backendLiveContext) {
|
|
5670
|
+
backendLiveState = backendLiveActors.get(backendLiveContext.child.actorId) ?? null;
|
|
5671
|
+
backendLiveChild = backendLiveContext.child;
|
|
5672
|
+
}
|
|
5673
|
+
const sid = headerSid || querySid || backendLiveContext?.sid || '';
|
|
5674
|
+
const aid = headerAid || queryAid || backendLiveContext?.aid || '';
|
|
5065
5675
|
if (!sid || !aid) {
|
|
5066
5676
|
res.on('finish', endTrackedClientRequest);
|
|
5067
5677
|
res.on('close', endTrackedClientRequest);
|
|
5068
5678
|
return next(); // only capture tagged requests
|
|
5069
5679
|
}
|
|
5680
|
+
const captureCfg: ReproMiddlewareConfig = backendLiveChild
|
|
5681
|
+
? {
|
|
5682
|
+
...cfg,
|
|
5683
|
+
actorId: backendLiveChild.actorId,
|
|
5684
|
+
actorType: backendLiveChild.actorType,
|
|
5685
|
+
actorLabels: backendLiveChild.actorLabels,
|
|
5686
|
+
}
|
|
5687
|
+
: taggedActorIdentity
|
|
5688
|
+
? {
|
|
5689
|
+
...cfg,
|
|
5690
|
+
actorId: taggedActorIdentity.actorId,
|
|
5691
|
+
actorType: taggedActorIdentity.actorType,
|
|
5692
|
+
actorLabels: taggedActorIdentity.actorLabels,
|
|
5693
|
+
}
|
|
5694
|
+
: cfg;
|
|
5695
|
+
if (taggedActorIdentity) {
|
|
5696
|
+
post(captureCfg, sid, {
|
|
5697
|
+
entries: [{
|
|
5698
|
+
actionId: aid,
|
|
5699
|
+
sessionActor: {
|
|
5700
|
+
actorId: taggedActorIdentity.actorId,
|
|
5701
|
+
actorType: taggedActorIdentity.actorType,
|
|
5702
|
+
actorLabels: taggedActorIdentity.actorLabels,
|
|
5703
|
+
source: 'node-sdk',
|
|
5704
|
+
},
|
|
5705
|
+
t: requestEpochMs,
|
|
5706
|
+
}],
|
|
5707
|
+
}, { priority: 'high' });
|
|
5708
|
+
}
|
|
5070
5709
|
const resolvePrivacy = (): NormalizedRuntimePrivacyPolicy | null =>
|
|
5071
|
-
getRuntimePrivacyState(
|
|
5710
|
+
getRuntimePrivacyState(captureCfg).policy ?? null;
|
|
5072
5711
|
|
|
5073
|
-
const requestStartRaw = Date.now();
|
|
5074
|
-
const headerTs = readHeaderNumber(req.headers[REQUEST_START_HEADER]) ?? queryTs;
|
|
5075
5712
|
const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
|
|
5076
|
-
const requestEpochMs = headerTs !== null ? headerTs : requestStartRaw + clockSkewMs;
|
|
5077
5713
|
const rid = nextRequestId(requestEpochMs);
|
|
5078
5714
|
const captureScope = createRequestCaptureScope(rid, 'http');
|
|
5079
5715
|
const t0 = requestStartRaw;
|
|
@@ -5271,7 +5907,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5271
5907
|
?? await materializeInlinePrivacyValueAsync(
|
|
5272
5908
|
'request.body',
|
|
5273
5909
|
sanitizeRequestSnapshot(requestBodyRaw),
|
|
5274
|
-
|
|
5910
|
+
captureCfg,
|
|
5275
5911
|
maskReq,
|
|
5276
5912
|
endpointTraceCtx,
|
|
5277
5913
|
masking,
|
|
@@ -5281,7 +5917,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5281
5917
|
const requestParams = await applyPrivacyThenMaskAsync(
|
|
5282
5918
|
'request.params',
|
|
5283
5919
|
sanitizeRequestSnapshot((req as any).params),
|
|
5284
|
-
|
|
5920
|
+
captureCfg,
|
|
5285
5921
|
maskReq,
|
|
5286
5922
|
endpointTraceCtx,
|
|
5287
5923
|
masking,
|
|
@@ -5290,7 +5926,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5290
5926
|
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
5291
5927
|
'request.query',
|
|
5292
5928
|
sanitizeRequestSnapshot((req as any).query),
|
|
5293
|
-
|
|
5929
|
+
captureCfg,
|
|
5294
5930
|
maskReq,
|
|
5295
5931
|
endpointTraceCtx,
|
|
5296
5932
|
masking,
|
|
@@ -5299,7 +5935,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5299
5935
|
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
5300
5936
|
'request.headers',
|
|
5301
5937
|
requestHeaders,
|
|
5302
|
-
|
|
5938
|
+
captureCfg,
|
|
5303
5939
|
maskReq,
|
|
5304
5940
|
endpointTraceCtx,
|
|
5305
5941
|
masking,
|
|
@@ -5311,7 +5947,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5311
5947
|
?? await materializeInlinePrivacyValueAsync(
|
|
5312
5948
|
'response.body',
|
|
5313
5949
|
sanitizeRequestSnapshot(capturedBody),
|
|
5314
|
-
|
|
5950
|
+
captureCfg,
|
|
5315
5951
|
maskReq,
|
|
5316
5952
|
endpointTraceCtx,
|
|
5317
5953
|
masking,
|
|
@@ -5326,7 +5962,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5326
5962
|
rawValue: (req as any).body,
|
|
5327
5963
|
previewValue: requestBody,
|
|
5328
5964
|
capture: {
|
|
5329
|
-
runtimeConfig:
|
|
5965
|
+
runtimeConfig: captureCfg,
|
|
5330
5966
|
captureHeaders: cfg.captureHeaders,
|
|
5331
5967
|
maskReq,
|
|
5332
5968
|
trace: endpointTraceCtx,
|
|
@@ -5339,7 +5975,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5339
5975
|
rawValue: (req as any).params,
|
|
5340
5976
|
previewValue: requestParams,
|
|
5341
5977
|
capture: {
|
|
5342
|
-
runtimeConfig:
|
|
5978
|
+
runtimeConfig: captureCfg,
|
|
5343
5979
|
captureHeaders: cfg.captureHeaders,
|
|
5344
5980
|
maskReq,
|
|
5345
5981
|
trace: endpointTraceCtx,
|
|
@@ -5352,7 +5988,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5352
5988
|
rawValue: (req as any).query,
|
|
5353
5989
|
previewValue: requestQuery,
|
|
5354
5990
|
capture: {
|
|
5355
|
-
runtimeConfig:
|
|
5991
|
+
runtimeConfig: captureCfg,
|
|
5356
5992
|
captureHeaders: cfg.captureHeaders,
|
|
5357
5993
|
maskReq,
|
|
5358
5994
|
trace: endpointTraceCtx,
|
|
@@ -5365,7 +6001,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5365
6001
|
rawValue: req.headers,
|
|
5366
6002
|
previewValue: maskedHeaders,
|
|
5367
6003
|
capture: {
|
|
5368
|
-
runtimeConfig:
|
|
6004
|
+
runtimeConfig: captureCfg,
|
|
5369
6005
|
captureHeaders: cfg.captureHeaders,
|
|
5370
6006
|
maskReq,
|
|
5371
6007
|
trace: endpointTraceCtx,
|
|
@@ -5380,7 +6016,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5380
6016
|
rawValue: capturedBody,
|
|
5381
6017
|
previewValue: responseBody,
|
|
5382
6018
|
capture: {
|
|
5383
|
-
runtimeConfig:
|
|
6019
|
+
runtimeConfig: captureCfg,
|
|
5384
6020
|
captureHeaders: cfg.captureHeaders,
|
|
5385
6021
|
maskReq,
|
|
5386
6022
|
trace: endpointTraceCtx,
|
|
@@ -5440,7 +6076,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5440
6076
|
chosenEndpoint,
|
|
5441
6077
|
hasTraceEvents,
|
|
5442
6078
|
);
|
|
5443
|
-
post(
|
|
6079
|
+
post(captureCfg, sid, {
|
|
5444
6080
|
entries: [{
|
|
5445
6081
|
actionId: aid,
|
|
5446
6082
|
request: requestPayload,
|
|
@@ -5457,7 +6093,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5457
6093
|
const emitRequestCaptureManifest = (traceBatchTotal: number): void => {
|
|
5458
6094
|
if (requestCaptureManifestScheduled) return;
|
|
5459
6095
|
requestCaptureManifestScheduled = true;
|
|
5460
|
-
post(
|
|
6096
|
+
post(captureCfg, sid, {
|
|
5461
6097
|
entries: [{
|
|
5462
6098
|
actionId: aid,
|
|
5463
6099
|
requestRid: rid,
|
|
@@ -5569,7 +6205,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5569
6205
|
try {
|
|
5570
6206
|
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
5571
6207
|
const materializedEvents = await materializePendingTraceEventsAsync(pendingEvents, {
|
|
5572
|
-
cfg,
|
|
6208
|
+
cfg: captureCfg,
|
|
5573
6209
|
maskReq,
|
|
5574
6210
|
masking,
|
|
5575
6211
|
resolvePrivacy,
|
|
@@ -5602,7 +6238,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5602
6238
|
const batch = traceBatches[i];
|
|
5603
6239
|
const traceValues = collectBatchTraceValueEntries(batch, i);
|
|
5604
6240
|
|
|
5605
|
-
post(
|
|
6241
|
+
post(captureCfg, sid, {
|
|
5606
6242
|
entries: [{
|
|
5607
6243
|
actionId: aid,
|
|
5608
6244
|
trace: batch,
|
|
@@ -5624,6 +6260,15 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5624
6260
|
|
|
5625
6261
|
sessionDrainWait = waitForSessionDrain(sid);
|
|
5626
6262
|
endSessionRequest(sid);
|
|
6263
|
+
if (backendLiveState && backendLiveChild) {
|
|
6264
|
+
releaseBackendLiveRequest(
|
|
6265
|
+
cfg,
|
|
6266
|
+
normalizeLiveModeConfig(cfg),
|
|
6267
|
+
backendLiveState,
|
|
6268
|
+
backendLiveChild,
|
|
6269
|
+
finishedAtAligned ?? alignedNow(),
|
|
6270
|
+
);
|
|
6271
|
+
}
|
|
5627
6272
|
endTrackedClientRequest();
|
|
5628
6273
|
|
|
5629
6274
|
if (__TRACER_READY) {
|
|
@@ -10045,9 +10690,11 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
10045
10690
|
const ingestCfg = toIngestConfig(cfg);
|
|
10046
10691
|
reproAutoMiddlewareConfig = {
|
|
10047
10692
|
...ingestCfg,
|
|
10693
|
+
apiBase: cfg.apiBase,
|
|
10048
10694
|
captureHeaders: cfg.captureHeaders,
|
|
10049
10695
|
masking: cfg.masking,
|
|
10050
10696
|
privacy: cfg.privacy,
|
|
10697
|
+
liveMode: cfg.liveMode,
|
|
10051
10698
|
};
|
|
10052
10699
|
const integrationCfg = {
|
|
10053
10700
|
...reproAutoMiddlewareConfig,
|
|
@@ -10102,6 +10749,9 @@ export const __reproTestHooks = {
|
|
|
10102
10749
|
lastClientActivityAt = 0;
|
|
10103
10750
|
sdkBackgroundQuietUntil = 0;
|
|
10104
10751
|
},
|
|
10752
|
+
setBackendLiveTimingForTest(override: Partial<BackendLiveTimingConfig> | null): void {
|
|
10753
|
+
backendLiveTimingOverrideForTest = override;
|
|
10754
|
+
},
|
|
10105
10755
|
runWithRequestContextForTest<T>(ctx: Partial<Ctx>, fn: () => T): T {
|
|
10106
10756
|
return als.run({
|
|
10107
10757
|
...ctx,
|