@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/README.md +225 -310
- package/dist/index.d.ts +42 -0
- package/dist/index.js +488 -22
- package/package.json +2 -2
- package/src/index.ts +721 -22
- package/test/backend-live-mode.test.js +403 -0
- package/test/mongodb-integration.test.js +164 -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;
|
|
@@ -4955,18 +5367,246 @@ async function maybeCaptureDbValueAsync(
|
|
|
4955
5367
|
return result.captureRef;
|
|
4956
5368
|
}
|
|
4957
5369
|
|
|
5370
|
+
function pushUniqueCaptureCandidate(candidates: any[], value: any): void {
|
|
5371
|
+
if (value === undefined) return;
|
|
5372
|
+
if (candidates.some((candidate) => candidate === value)) return;
|
|
5373
|
+
candidates.push(value);
|
|
5374
|
+
}
|
|
5375
|
+
|
|
5376
|
+
function buildResultMetaFullValueCandidates(resultMetaRaw: any, resultMetaSource: any): any[] {
|
|
5377
|
+
const candidates: any[] = [];
|
|
5378
|
+
pushUniqueCaptureCandidate(candidates, resultMetaRaw);
|
|
5379
|
+
if (
|
|
5380
|
+
resultMetaSource &&
|
|
5381
|
+
typeof resultMetaSource === 'object' &&
|
|
5382
|
+
Object.prototype.hasOwnProperty.call(resultMetaSource, 'result')
|
|
5383
|
+
) {
|
|
5384
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource.result);
|
|
5385
|
+
}
|
|
5386
|
+
pushUniqueCaptureCandidate(candidates, resultMetaSource);
|
|
5387
|
+
return candidates;
|
|
5388
|
+
}
|
|
5389
|
+
|
|
5390
|
+
async function maybeCaptureDbValueFromCandidatesAsync(
|
|
5391
|
+
params: {
|
|
5392
|
+
target: 'db.pk' | 'db.before' | 'db.after' | 'db.query' | 'db.resultMeta' | 'db.error';
|
|
5393
|
+
rawValues: any[];
|
|
5394
|
+
previewValue: any;
|
|
5395
|
+
capture: FullValueCaptureContext;
|
|
5396
|
+
},
|
|
5397
|
+
sink: TraceValueBatchEntry[],
|
|
5398
|
+
): Promise<TraceValueCaptureRef | undefined> {
|
|
5399
|
+
let skippedRef: TraceValueCaptureRef | undefined;
|
|
5400
|
+
for (const rawValue of params.rawValues) {
|
|
5401
|
+
const result = await createCapturedValueEntryAsync({
|
|
5402
|
+
target: params.target,
|
|
5403
|
+
rawValue,
|
|
5404
|
+
previewValue: params.previewValue,
|
|
5405
|
+
capture: params.capture,
|
|
5406
|
+
});
|
|
5407
|
+
if (!result) continue;
|
|
5408
|
+
if (result.entry) {
|
|
5409
|
+
sink.push(result.entry);
|
|
5410
|
+
return result.captureRef;
|
|
5411
|
+
}
|
|
5412
|
+
if (!skippedRef) {
|
|
5413
|
+
skippedRef = result.captureRef;
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
return skippedRef;
|
|
5417
|
+
}
|
|
5418
|
+
|
|
4958
5419
|
// ===================================================================
|
|
4959
5420
|
// reproMiddleware — unchanged behavior + passive per-request trace
|
|
4960
5421
|
// ===================================================================
|
|
4961
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;
|
|
4962
5425
|
/** Configure header capture/masking. Defaults to capturing with sensitive headers masked. */
|
|
4963
5426
|
captureHeaders?: boolean | HeaderCaptureOptions;
|
|
4964
5427
|
/** Optional masking rules for request/response payloads and function traces. */
|
|
4965
5428
|
masking?: ReproMaskingConfig;
|
|
4966
5429
|
/** Optional privacy policy config for deterministic runtime transformations. */
|
|
4967
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>;
|
|
4968
5518
|
};
|
|
4969
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;
|
|
5533
|
+
};
|
|
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
|
+
|
|
4970
5610
|
export type ReproInitConfig = ReproMiddlewareConfig & {
|
|
4971
5611
|
/**
|
|
4972
5612
|
* Tracing configuration for function/http instrumentation.
|
|
@@ -5011,20 +5651,65 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5011
5651
|
endClientRequest();
|
|
5012
5652
|
};
|
|
5013
5653
|
|
|
5014
|
-
const
|
|
5015
|
-
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 || '';
|
|
5016
5675
|
if (!sid || !aid) {
|
|
5017
5676
|
res.on('finish', endTrackedClientRequest);
|
|
5018
5677
|
res.on('close', endTrackedClientRequest);
|
|
5019
5678
|
return next(); // only capture tagged requests
|
|
5020
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
|
+
}
|
|
5021
5709
|
const resolvePrivacy = (): NormalizedRuntimePrivacyPolicy | null =>
|
|
5022
|
-
getRuntimePrivacyState(
|
|
5710
|
+
getRuntimePrivacyState(captureCfg).policy ?? null;
|
|
5023
5711
|
|
|
5024
|
-
const requestStartRaw = Date.now();
|
|
5025
|
-
const headerTs = readHeaderNumber(req.headers[REQUEST_START_HEADER]) ?? queryTs;
|
|
5026
5712
|
const clockSkewMs = headerTs !== null ? headerTs - requestStartRaw : 0;
|
|
5027
|
-
const requestEpochMs = headerTs !== null ? headerTs : requestStartRaw + clockSkewMs;
|
|
5028
5713
|
const rid = nextRequestId(requestEpochMs);
|
|
5029
5714
|
const captureScope = createRequestCaptureScope(rid, 'http');
|
|
5030
5715
|
const t0 = requestStartRaw;
|
|
@@ -5222,7 +5907,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5222
5907
|
?? await materializeInlinePrivacyValueAsync(
|
|
5223
5908
|
'request.body',
|
|
5224
5909
|
sanitizeRequestSnapshot(requestBodyRaw),
|
|
5225
|
-
|
|
5910
|
+
captureCfg,
|
|
5226
5911
|
maskReq,
|
|
5227
5912
|
endpointTraceCtx,
|
|
5228
5913
|
masking,
|
|
@@ -5232,7 +5917,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5232
5917
|
const requestParams = await applyPrivacyThenMaskAsync(
|
|
5233
5918
|
'request.params',
|
|
5234
5919
|
sanitizeRequestSnapshot((req as any).params),
|
|
5235
|
-
|
|
5920
|
+
captureCfg,
|
|
5236
5921
|
maskReq,
|
|
5237
5922
|
endpointTraceCtx,
|
|
5238
5923
|
masking,
|
|
@@ -5241,7 +5926,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5241
5926
|
const requestQuery = await applyPrivacyThenMaskAsync(
|
|
5242
5927
|
'request.query',
|
|
5243
5928
|
sanitizeRequestSnapshot((req as any).query),
|
|
5244
|
-
|
|
5929
|
+
captureCfg,
|
|
5245
5930
|
maskReq,
|
|
5246
5931
|
endpointTraceCtx,
|
|
5247
5932
|
masking,
|
|
@@ -5250,7 +5935,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5250
5935
|
const maskedHeaders = await applyPrivacyThenMaskAsync(
|
|
5251
5936
|
'request.headers',
|
|
5252
5937
|
requestHeaders,
|
|
5253
|
-
|
|
5938
|
+
captureCfg,
|
|
5254
5939
|
maskReq,
|
|
5255
5940
|
endpointTraceCtx,
|
|
5256
5941
|
masking,
|
|
@@ -5262,7 +5947,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5262
5947
|
?? await materializeInlinePrivacyValueAsync(
|
|
5263
5948
|
'response.body',
|
|
5264
5949
|
sanitizeRequestSnapshot(capturedBody),
|
|
5265
|
-
|
|
5950
|
+
captureCfg,
|
|
5266
5951
|
maskReq,
|
|
5267
5952
|
endpointTraceCtx,
|
|
5268
5953
|
masking,
|
|
@@ -5277,7 +5962,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5277
5962
|
rawValue: (req as any).body,
|
|
5278
5963
|
previewValue: requestBody,
|
|
5279
5964
|
capture: {
|
|
5280
|
-
runtimeConfig:
|
|
5965
|
+
runtimeConfig: captureCfg,
|
|
5281
5966
|
captureHeaders: cfg.captureHeaders,
|
|
5282
5967
|
maskReq,
|
|
5283
5968
|
trace: endpointTraceCtx,
|
|
@@ -5290,7 +5975,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5290
5975
|
rawValue: (req as any).params,
|
|
5291
5976
|
previewValue: requestParams,
|
|
5292
5977
|
capture: {
|
|
5293
|
-
runtimeConfig:
|
|
5978
|
+
runtimeConfig: captureCfg,
|
|
5294
5979
|
captureHeaders: cfg.captureHeaders,
|
|
5295
5980
|
maskReq,
|
|
5296
5981
|
trace: endpointTraceCtx,
|
|
@@ -5303,7 +5988,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5303
5988
|
rawValue: (req as any).query,
|
|
5304
5989
|
previewValue: requestQuery,
|
|
5305
5990
|
capture: {
|
|
5306
|
-
runtimeConfig:
|
|
5991
|
+
runtimeConfig: captureCfg,
|
|
5307
5992
|
captureHeaders: cfg.captureHeaders,
|
|
5308
5993
|
maskReq,
|
|
5309
5994
|
trace: endpointTraceCtx,
|
|
@@ -5316,7 +6001,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5316
6001
|
rawValue: req.headers,
|
|
5317
6002
|
previewValue: maskedHeaders,
|
|
5318
6003
|
capture: {
|
|
5319
|
-
runtimeConfig:
|
|
6004
|
+
runtimeConfig: captureCfg,
|
|
5320
6005
|
captureHeaders: cfg.captureHeaders,
|
|
5321
6006
|
maskReq,
|
|
5322
6007
|
trace: endpointTraceCtx,
|
|
@@ -5331,7 +6016,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5331
6016
|
rawValue: capturedBody,
|
|
5332
6017
|
previewValue: responseBody,
|
|
5333
6018
|
capture: {
|
|
5334
|
-
runtimeConfig:
|
|
6019
|
+
runtimeConfig: captureCfg,
|
|
5335
6020
|
captureHeaders: cfg.captureHeaders,
|
|
5336
6021
|
maskReq,
|
|
5337
6022
|
trace: endpointTraceCtx,
|
|
@@ -5391,7 +6076,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5391
6076
|
chosenEndpoint,
|
|
5392
6077
|
hasTraceEvents,
|
|
5393
6078
|
);
|
|
5394
|
-
post(
|
|
6079
|
+
post(captureCfg, sid, {
|
|
5395
6080
|
entries: [{
|
|
5396
6081
|
actionId: aid,
|
|
5397
6082
|
request: requestPayload,
|
|
@@ -5408,7 +6093,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5408
6093
|
const emitRequestCaptureManifest = (traceBatchTotal: number): void => {
|
|
5409
6094
|
if (requestCaptureManifestScheduled) return;
|
|
5410
6095
|
requestCaptureManifestScheduled = true;
|
|
5411
|
-
post(
|
|
6096
|
+
post(captureCfg, sid, {
|
|
5412
6097
|
entries: [{
|
|
5413
6098
|
actionId: aid,
|
|
5414
6099
|
requestRid: rid,
|
|
@@ -5520,7 +6205,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5520
6205
|
try {
|
|
5521
6206
|
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
5522
6207
|
const materializedEvents = await materializePendingTraceEventsAsync(pendingEvents, {
|
|
5523
|
-
cfg,
|
|
6208
|
+
cfg: captureCfg,
|
|
5524
6209
|
maskReq,
|
|
5525
6210
|
masking,
|
|
5526
6211
|
resolvePrivacy,
|
|
@@ -5553,7 +6238,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5553
6238
|
const batch = traceBatches[i];
|
|
5554
6239
|
const traceValues = collectBatchTraceValueEntries(batch, i);
|
|
5555
6240
|
|
|
5556
|
-
post(
|
|
6241
|
+
post(captureCfg, sid, {
|
|
5557
6242
|
entries: [{
|
|
5558
6243
|
actionId: aid,
|
|
5559
6244
|
trace: batch,
|
|
@@ -5575,6 +6260,15 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5575
6260
|
|
|
5576
6261
|
sessionDrainWait = waitForSessionDrain(sid);
|
|
5577
6262
|
endSessionRequest(sid);
|
|
6263
|
+
if (backendLiveState && backendLiveChild) {
|
|
6264
|
+
releaseBackendLiveRequest(
|
|
6265
|
+
cfg,
|
|
6266
|
+
normalizeLiveModeConfig(cfg),
|
|
6267
|
+
backendLiveState,
|
|
6268
|
+
backendLiveChild,
|
|
6269
|
+
finishedAtAligned ?? alignedNow(),
|
|
6270
|
+
);
|
|
6271
|
+
}
|
|
5578
6272
|
endTrackedClientRequest();
|
|
5579
6273
|
|
|
5580
6274
|
if (__TRACER_READY) {
|
|
@@ -6241,9 +6935,9 @@ async function emitDbQueryAsync(cfg: any, sid?: string, aid?: string, payload?:
|
|
|
6241
6935
|
previewValue: queryPreview,
|
|
6242
6936
|
capture,
|
|
6243
6937
|
}, dbValues);
|
|
6244
|
-
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await
|
|
6938
|
+
const resultMetaValueCapture = resultMetaMaterialization.skipped ? undefined : await maybeCaptureDbValueFromCandidatesAsync({
|
|
6245
6939
|
target: 'db.resultMeta',
|
|
6246
|
-
|
|
6940
|
+
rawValues: buildResultMetaFullValueCandidates(payload.resultMetaRaw, resultMetaSource),
|
|
6247
6941
|
previewValue: resultMetaPreview,
|
|
6248
6942
|
capture,
|
|
6249
6943
|
}, dbValues);
|
|
@@ -9996,9 +10690,11 @@ export async function initRepro(cfg: ReproInitConfig): Promise<void> {
|
|
|
9996
10690
|
const ingestCfg = toIngestConfig(cfg);
|
|
9997
10691
|
reproAutoMiddlewareConfig = {
|
|
9998
10692
|
...ingestCfg,
|
|
10693
|
+
apiBase: cfg.apiBase,
|
|
9999
10694
|
captureHeaders: cfg.captureHeaders,
|
|
10000
10695
|
masking: cfg.masking,
|
|
10001
10696
|
privacy: cfg.privacy,
|
|
10697
|
+
liveMode: cfg.liveMode,
|
|
10002
10698
|
};
|
|
10003
10699
|
const integrationCfg = {
|
|
10004
10700
|
...reproAutoMiddlewareConfig,
|
|
@@ -10053,6 +10749,9 @@ export const __reproTestHooks = {
|
|
|
10053
10749
|
lastClientActivityAt = 0;
|
|
10054
10750
|
sdkBackgroundQuietUntil = 0;
|
|
10055
10751
|
},
|
|
10752
|
+
setBackendLiveTimingForTest(override: Partial<BackendLiveTimingConfig> | null): void {
|
|
10753
|
+
backendLiveTimingOverrideForTest = override;
|
|
10754
|
+
},
|
|
10056
10755
|
runWithRequestContextForTest<T>(ctx: Partial<Ctx>, fn: () => T): T {
|
|
10057
10756
|
return als.run({
|
|
10058
10757
|
...ctx,
|