@reproapp/node-sdk 0.0.14 → 0.0.15
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 +9 -4
- package/dist/ingest/client.js +33 -0
- package/package.json +2 -2
- package/src/index.ts +66 -58
- package/src/ingest/client.ts +32 -0
- package/test/ingest-timeout.test.js +67 -0
- package/test/request-flush-timing.test.js +12 -0
package/dist/index.js
CHANGED
|
@@ -793,8 +793,11 @@ function createRequestCaptureScope(requestRid, transport) {
|
|
|
793
793
|
emailEventCount: 0,
|
|
794
794
|
};
|
|
795
795
|
}
|
|
796
|
-
function buildRequestCaptureManifestPayload(scope, traceBatchTotal) {
|
|
796
|
+
function buildRequestCaptureManifestPayload(scope, traceBatchTotal, closedAtMs) {
|
|
797
797
|
const expectedTraceBatches = Math.max(0, Math.trunc(traceBatchTotal));
|
|
798
|
+
const closedAt = typeof closedAtMs === 'number' && Number.isFinite(closedAtMs)
|
|
799
|
+
? closedAtMs
|
|
800
|
+
: alignedNow();
|
|
798
801
|
return {
|
|
799
802
|
scopeId: `req:${scope.requestRid}`,
|
|
800
803
|
requestRid: scope.requestRid,
|
|
@@ -811,7 +814,7 @@ function buildRequestCaptureManifestPayload(scope, traceBatchTotal) {
|
|
|
811
814
|
to: expectedTraceBatches - 1,
|
|
812
815
|
}
|
|
813
816
|
: undefined,
|
|
814
|
-
closedAt
|
|
817
|
+
closedAt,
|
|
815
818
|
};
|
|
816
819
|
}
|
|
817
820
|
const als = new async_hooks_1.AsyncLocalStorage();
|
|
@@ -3960,6 +3963,7 @@ function reproMiddleware(cfg) {
|
|
|
3960
3963
|
let flushed = false;
|
|
3961
3964
|
let finished = false;
|
|
3962
3965
|
let finishedAt = null;
|
|
3966
|
+
let finishedAtAligned = null;
|
|
3963
3967
|
let lastEventAt = Date.now();
|
|
3964
3968
|
let idleTimer = null;
|
|
3965
3969
|
let hardStopTimer = null;
|
|
@@ -4313,6 +4317,7 @@ function reproMiddleware(cfg) {
|
|
|
4313
4317
|
finished = true;
|
|
4314
4318
|
if (finishedAt === null) {
|
|
4315
4319
|
finishedAt = Date.now();
|
|
4320
|
+
finishedAtAligned = alignTimestamp(finishedAt);
|
|
4316
4321
|
}
|
|
4317
4322
|
if (capturedBody === undefined && chunks.length) {
|
|
4318
4323
|
const buf = Buffer.isBuffer(chunks[0])
|
|
@@ -4363,8 +4368,8 @@ function reproMiddleware(cfg) {
|
|
|
4363
4368
|
entries: [{
|
|
4364
4369
|
actionId: aid,
|
|
4365
4370
|
requestRid: rid,
|
|
4366
|
-
requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, traceBatches.length),
|
|
4367
|
-
t: alignedNow(),
|
|
4371
|
+
requestCaptureManifest: buildRequestCaptureManifestPayload(captureScope, traceBatches.length, finishedAtAligned),
|
|
4372
|
+
t: finishedAtAligned ?? alignedNow(),
|
|
4368
4373
|
}],
|
|
4369
4374
|
});
|
|
4370
4375
|
}
|
package/dist/ingest/client.js
CHANGED
|
@@ -14,6 +14,13 @@ const DEFAULT_QUEUE_MAX_ITEMS = (() => {
|
|
|
14
14
|
const DEFAULT_QUEUE_MAX_ATTEMPTS = 5;
|
|
15
15
|
const DEFAULT_QUEUE_BACKOFF_MAX_MS = 4000;
|
|
16
16
|
const DEFAULT_DEFER_DRAIN_MS = 25;
|
|
17
|
+
const DEFAULT_INGEST_REQUEST_TIMEOUT_MS = (() => {
|
|
18
|
+
const env = Number(typeof process !== 'undefined' ? process?.env?.REPRO_INGEST_REQUEST_TIMEOUT_MS : undefined);
|
|
19
|
+
if (Number.isFinite(env) && env >= 0) {
|
|
20
|
+
return Math.trunc(env);
|
|
21
|
+
}
|
|
22
|
+
return 5000;
|
|
23
|
+
})();
|
|
17
24
|
const ingestQueue = [];
|
|
18
25
|
let drainTimer = null;
|
|
19
26
|
let draining = false;
|
|
@@ -65,11 +72,29 @@ const sendIngestEntries = async (config, sessionId, entries) => {
|
|
|
65
72
|
if (!events.length) {
|
|
66
73
|
return true;
|
|
67
74
|
}
|
|
75
|
+
let timeout = null;
|
|
76
|
+
const Controller = globalThis.AbortController;
|
|
77
|
+
const controller = DEFAULT_INGEST_REQUEST_TIMEOUT_MS > 0 && typeof Controller === 'function'
|
|
78
|
+
? new Controller()
|
|
79
|
+
: null;
|
|
68
80
|
try {
|
|
81
|
+
if (controller) {
|
|
82
|
+
timeout = setTimeout(() => {
|
|
83
|
+
try {
|
|
84
|
+
controller.abort();
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
87
|
+
}, DEFAULT_INGEST_REQUEST_TIMEOUT_MS);
|
|
88
|
+
try {
|
|
89
|
+
timeout.unref?.();
|
|
90
|
+
}
|
|
91
|
+
catch { }
|
|
92
|
+
}
|
|
69
93
|
const response = await fetch(resolveIngestUrl(config), {
|
|
70
94
|
method: 'POST',
|
|
71
95
|
headers: toHeaders(config),
|
|
72
96
|
body: JSON.stringify({ events }),
|
|
97
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
73
98
|
});
|
|
74
99
|
return response.ok;
|
|
75
100
|
}
|
|
@@ -77,6 +102,14 @@ const sendIngestEntries = async (config, sessionId, entries) => {
|
|
|
77
102
|
// swallow in SDK
|
|
78
103
|
return false;
|
|
79
104
|
}
|
|
105
|
+
finally {
|
|
106
|
+
if (timeout) {
|
|
107
|
+
try {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
}
|
|
110
|
+
catch { }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
80
113
|
};
|
|
81
114
|
const scheduleDrain = (delayMs = 0) => {
|
|
82
115
|
if (drainTimer) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reproapp/node-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
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/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 -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/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.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/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-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/privacy-runtime-policy.test.js && node test/runtime-privacy-materialization.test.js && node test/kafka-runtime-privacy-policy.test.js"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"express": "^5.1.0",
|
package/src/index.ts
CHANGED
|
@@ -1181,8 +1181,13 @@ function createRequestCaptureScope(
|
|
|
1181
1181
|
function buildRequestCaptureManifestPayload(
|
|
1182
1182
|
scope: RequestCaptureScopeState,
|
|
1183
1183
|
traceBatchTotal: number,
|
|
1184
|
+
closedAtMs?: number | null,
|
|
1184
1185
|
): Record<string, any> {
|
|
1185
1186
|
const expectedTraceBatches = Math.max(0, Math.trunc(traceBatchTotal));
|
|
1187
|
+
const closedAt =
|
|
1188
|
+
typeof closedAtMs === 'number' && Number.isFinite(closedAtMs)
|
|
1189
|
+
? closedAtMs
|
|
1190
|
+
: alignedNow();
|
|
1186
1191
|
return {
|
|
1187
1192
|
scopeId: `req:${scope.requestRid}`,
|
|
1188
1193
|
requestRid: scope.requestRid,
|
|
@@ -1200,7 +1205,7 @@ function buildRequestCaptureManifestPayload(
|
|
|
1200
1205
|
to: expectedTraceBatches - 1,
|
|
1201
1206
|
}
|
|
1202
1207
|
: undefined,
|
|
1203
|
-
closedAt
|
|
1208
|
+
closedAt,
|
|
1204
1209
|
};
|
|
1205
1210
|
}
|
|
1206
1211
|
|
|
@@ -4836,7 +4841,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4836
4841
|
return fn();
|
|
4837
4842
|
};
|
|
4838
4843
|
|
|
4839
|
-
|
|
4844
|
+
runInTrace(() => als.run({ sid, aid, rid, clockSkewMs, excludedSpanIds: new Set<string>(), maskReq, captureScope }, () => {
|
|
4840
4845
|
const events: PendingTraceEventRecord[] = [];
|
|
4841
4846
|
let endpointTrace: EndpointTraceInfo | null = null;
|
|
4842
4847
|
let preferredAppTrace: EndpointTraceInfo | null = null;
|
|
@@ -4845,15 +4850,16 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
4845
4850
|
let flushed = false;
|
|
4846
4851
|
let finished = false;
|
|
4847
4852
|
let finishedAt: number | null = null;
|
|
4853
|
+
let finishedAtAligned: number | null = null;
|
|
4848
4854
|
let lastEventAt: number = Date.now();
|
|
4849
4855
|
let idleTimer: NodeJS.Timeout | null = null;
|
|
4850
|
-
|
|
4856
|
+
let hardStopTimer: NodeJS.Timeout | null = null;
|
|
4851
4857
|
let flushPayload: null | (() => Promise<void>) = null;
|
|
4852
4858
|
let requestCaptureScheduled = false;
|
|
4853
4859
|
let sessionDrainWait: Promise<void> | null = null;
|
|
4854
4860
|
const activeSpans = new Set<string>();
|
|
4855
4861
|
let anonymousSpanDepth = 0;
|
|
4856
|
-
|
|
4862
|
+
const ACTIVE_SPAN_FORCE_FLUSH_MS = 60000; // hard cutoff after finish to avoid endlessly running replies
|
|
4857
4863
|
|
|
4858
4864
|
const clearTimers = () => {
|
|
4859
4865
|
if (idleTimer) {
|
|
@@ -5231,6 +5237,7 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5231
5237
|
finished = true;
|
|
5232
5238
|
if (finishedAt === null) {
|
|
5233
5239
|
finishedAt = Date.now();
|
|
5240
|
+
finishedAtAligned = alignTimestamp(finishedAt);
|
|
5234
5241
|
}
|
|
5235
5242
|
if (capturedBody === undefined && chunks.length) {
|
|
5236
5243
|
const buf = Buffer.isBuffer(chunks[0])
|
|
@@ -5243,62 +5250,63 @@ export function reproMiddleware(cfg: ReproMiddlewareConfig) {
|
|
|
5243
5250
|
|
|
5244
5251
|
if (!flushPayload) {
|
|
5245
5252
|
flushPayload = async () => {
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
if (traceBatches.length) {
|
|
5265
|
-
captureScope.traceBatchCount = traceBatches.length;
|
|
5266
|
-
for (let i = 0; i < traceBatches.length; i++) {
|
|
5267
|
-
const batch = traceBatches[i];
|
|
5268
|
-
const traceValues = collectBatchTraceValueEntries(batch, i);
|
|
5269
|
-
|
|
5270
|
-
post(cfg, sid, {
|
|
5271
|
-
entries: [{
|
|
5272
|
-
actionId: aid,
|
|
5273
|
-
trace: batch,
|
|
5274
|
-
traceValues: traceValues.length ? traceValues : undefined,
|
|
5275
|
-
traceBatch: {
|
|
5276
|
-
rid,
|
|
5277
|
-
index: i,
|
|
5278
|
-
total: traceBatches.length,
|
|
5279
|
-
},
|
|
5280
|
-
t: alignedNow(),
|
|
5281
|
-
}],
|
|
5282
|
-
});
|
|
5283
|
-
}
|
|
5284
|
-
} else {
|
|
5285
|
-
captureScope.traceBatchCount = 0;
|
|
5286
|
-
}
|
|
5253
|
+
try {
|
|
5254
|
+
const pendingEvents = preparePendingTraceEventsForFlush(events.slice());
|
|
5255
|
+
const materializedEvents = await materializePendingTraceEventsAsync(pendingEvents, {
|
|
5256
|
+
cfg,
|
|
5257
|
+
maskReq,
|
|
5258
|
+
masking,
|
|
5259
|
+
resolvePrivacy,
|
|
5260
|
+
});
|
|
5261
|
+
const baseEvents = balanceTraceEvents(materializedEvents.slice());
|
|
5262
|
+
const orderedEvents = TRACE_ORDER_MODE === 'tree'
|
|
5263
|
+
? reorderTraceEvents(baseEvents)
|
|
5264
|
+
: sortTraceEventsChronologically(baseEvents);
|
|
5265
|
+
const traceBatches = chunkTraceEventsForTransport(
|
|
5266
|
+
orderedEvents,
|
|
5267
|
+
rid,
|
|
5268
|
+
aid,
|
|
5269
|
+
);
|
|
5287
5270
|
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5271
|
+
if (traceBatches.length) {
|
|
5272
|
+
captureScope.traceBatchCount = traceBatches.length;
|
|
5273
|
+
for (let i = 0; i < traceBatches.length; i++) {
|
|
5274
|
+
const batch = traceBatches[i];
|
|
5275
|
+
const traceValues = collectBatchTraceValueEntries(batch, i);
|
|
5276
|
+
|
|
5277
|
+
post(cfg, sid, {
|
|
5278
|
+
entries: [{
|
|
5279
|
+
actionId: aid,
|
|
5280
|
+
trace: batch,
|
|
5281
|
+
traceValues: traceValues.length ? traceValues : undefined,
|
|
5282
|
+
traceBatch: {
|
|
5283
|
+
rid,
|
|
5284
|
+
index: i,
|
|
5285
|
+
total: traceBatches.length,
|
|
5286
|
+
},
|
|
5287
|
+
t: alignedNow(),
|
|
5288
|
+
}],
|
|
5289
|
+
});
|
|
5290
|
+
}
|
|
5291
|
+
} else {
|
|
5292
|
+
captureScope.traceBatchCount = 0;
|
|
5301
5293
|
}
|
|
5294
|
+
|
|
5295
|
+
post(cfg, sid, {
|
|
5296
|
+
entries: [{
|
|
5297
|
+
actionId: aid,
|
|
5298
|
+
requestRid: rid,
|
|
5299
|
+
requestCaptureManifest: buildRequestCaptureManifestPayload(
|
|
5300
|
+
captureScope,
|
|
5301
|
+
traceBatches.length,
|
|
5302
|
+
finishedAtAligned,
|
|
5303
|
+
),
|
|
5304
|
+
t: finishedAtAligned ?? alignedNow(),
|
|
5305
|
+
}],
|
|
5306
|
+
});
|
|
5307
|
+
} catch {
|
|
5308
|
+
// never break user code
|
|
5309
|
+
}
|
|
5302
5310
|
};
|
|
5303
5311
|
}
|
|
5304
5312
|
|
package/src/ingest/client.ts
CHANGED
|
@@ -13,6 +13,13 @@ const DEFAULT_QUEUE_MAX_ITEMS = (() => {
|
|
|
13
13
|
const DEFAULT_QUEUE_MAX_ATTEMPTS = 5;
|
|
14
14
|
const DEFAULT_QUEUE_BACKOFF_MAX_MS = 4000;
|
|
15
15
|
const DEFAULT_DEFER_DRAIN_MS = 25;
|
|
16
|
+
const DEFAULT_INGEST_REQUEST_TIMEOUT_MS = (() => {
|
|
17
|
+
const env = Number(typeof process !== 'undefined' ? (process as any)?.env?.REPRO_INGEST_REQUEST_TIMEOUT_MS : undefined);
|
|
18
|
+
if (Number.isFinite(env) && env >= 0) {
|
|
19
|
+
return Math.trunc(env);
|
|
20
|
+
}
|
|
21
|
+
return 5000;
|
|
22
|
+
})();
|
|
16
23
|
|
|
17
24
|
type QueuedIngestBatch = {
|
|
18
25
|
config: IngestClientConfig;
|
|
@@ -91,16 +98,41 @@ const sendIngestEntries = async (
|
|
|
91
98
|
return true;
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
102
|
+
const Controller = (globalThis as any).AbortController;
|
|
103
|
+
const controller =
|
|
104
|
+
DEFAULT_INGEST_REQUEST_TIMEOUT_MS > 0 && typeof Controller === 'function'
|
|
105
|
+
? new Controller()
|
|
106
|
+
: null;
|
|
107
|
+
|
|
94
108
|
try {
|
|
109
|
+
if (controller) {
|
|
110
|
+
timeout = setTimeout(() => {
|
|
111
|
+
try {
|
|
112
|
+
controller.abort();
|
|
113
|
+
} catch {}
|
|
114
|
+
}, DEFAULT_INGEST_REQUEST_TIMEOUT_MS);
|
|
115
|
+
try {
|
|
116
|
+
(timeout as any).unref?.();
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
|
|
95
120
|
const response = await fetch(resolveIngestUrl(config), {
|
|
96
121
|
method: 'POST',
|
|
97
122
|
headers: toHeaders(config),
|
|
98
123
|
body: JSON.stringify({ events }),
|
|
124
|
+
...(controller ? { signal: controller.signal } : {}),
|
|
99
125
|
});
|
|
100
126
|
return response.ok;
|
|
101
127
|
} catch {
|
|
102
128
|
// swallow in SDK
|
|
103
129
|
return false;
|
|
130
|
+
} finally {
|
|
131
|
+
if (timeout) {
|
|
132
|
+
try {
|
|
133
|
+
clearTimeout(timeout);
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
104
136
|
}
|
|
105
137
|
};
|
|
106
138
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
process.env.REPRO_INGEST_REQUEST_TIMEOUT_MS = '25';
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
|
|
5
|
+
const { postIngestEntries } = require('../dist/ingest/client');
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const originalFetch = global.fetch;
|
|
9
|
+
let aborted = false;
|
|
10
|
+
|
|
11
|
+
global.fetch = async (_url, init = {}) => {
|
|
12
|
+
return await new Promise((_resolve, reject) => {
|
|
13
|
+
const signal = init.signal;
|
|
14
|
+
assert(signal, 'fetch should receive an AbortSignal');
|
|
15
|
+
const keepAlive = setTimeout(() => {}, 1000);
|
|
16
|
+
signal.addEventListener('abort', () => {
|
|
17
|
+
clearTimeout(keepAlive);
|
|
18
|
+
aborted = true;
|
|
19
|
+
const error = new Error('aborted');
|
|
20
|
+
error.name = 'AbortError';
|
|
21
|
+
reject(error);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const startedAt = Date.now();
|
|
28
|
+
await postIngestEntries(
|
|
29
|
+
{
|
|
30
|
+
tenantId: 'TENANT_test',
|
|
31
|
+
appId: 'APP_test',
|
|
32
|
+
appSecret: 'secret',
|
|
33
|
+
ingestBase: 'http://127.0.0.1:65535',
|
|
34
|
+
},
|
|
35
|
+
'S_ingest_timeout',
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
actionId: 'A_ingest_timeout',
|
|
39
|
+
request: {
|
|
40
|
+
rid: 'R_ingest_timeout',
|
|
41
|
+
method: 'GET',
|
|
42
|
+
url: '/timeout',
|
|
43
|
+
status: 200,
|
|
44
|
+
durMs: 1,
|
|
45
|
+
},
|
|
46
|
+
t: Date.now(),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
);
|
|
50
|
+
const elapsedMs = Date.now() - startedAt;
|
|
51
|
+
|
|
52
|
+
assert.equal(aborted, true, 'ingest fetch should be aborted on timeout');
|
|
53
|
+
assert(
|
|
54
|
+
elapsedMs < 500,
|
|
55
|
+
`ingest timeout should bound queue blocking; elapsed ${elapsedMs}ms`,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log('ingest timeout OK');
|
|
59
|
+
} finally {
|
|
60
|
+
global.fetch = originalFetch;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch((error) => {
|
|
65
|
+
console.error(error);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
@@ -74,6 +74,10 @@ async function main() {
|
|
|
74
74
|
ingestBase: 'http://127.0.0.1:65535',
|
|
75
75
|
};
|
|
76
76
|
const { req, res } = makeTaggedReqRes('S_request_flush_timing', 'A_request_flush_timing');
|
|
77
|
+
let responseFinishedAt = 0;
|
|
78
|
+
res.on('finish', () => {
|
|
79
|
+
responseFinishedAt = Date.now();
|
|
80
|
+
});
|
|
77
81
|
|
|
78
82
|
await new Promise((resolve, reject) => {
|
|
79
83
|
void reproMiddleware(cfg)(req, res, async (err) => {
|
|
@@ -114,6 +118,14 @@ async function main() {
|
|
|
114
118
|
1,
|
|
115
119
|
JSON.stringify(capturedBodies),
|
|
116
120
|
);
|
|
121
|
+
const manifestEvent = flattenEvents(capturedBodies)
|
|
122
|
+
.find((event) => event?.event_type === 'request_capture_manifest');
|
|
123
|
+
const manifestClosedAt = Number(manifestEvent?.payload?.requestCaptureManifest?.closedAt);
|
|
124
|
+
assert(Number.isFinite(manifestClosedAt), JSON.stringify(manifestEvent));
|
|
125
|
+
assert(
|
|
126
|
+
Math.abs(manifestClosedAt - responseFinishedAt) < 500,
|
|
127
|
+
`manifest closedAt should use response finish time; got ${manifestClosedAt}, finish ${responseFinishedAt}`,
|
|
128
|
+
);
|
|
117
129
|
|
|
118
130
|
console.log('request flush timing OK');
|
|
119
131
|
} finally {
|