fix(inspector): preserve event stream replay order

This commit is contained in:
imccyu
2026-08-27 16:16:27 +08:00
parent 5a5d5de948
commit c5f34e8ada
2 changed files with 13 additions and 27 deletions
@@ -69,10 +69,7 @@ export type NetworkStoreEvent =
}
| { readonly type: 'request-evicted'; readonly requestKey: string }
type JournalNetworkEvent = Exclude<NetworkStoreEvent, {
readonly type: 'response-data' | 'event-source-message' | 'request-evicted'
}>
type ReplayableNetworkEvent = Exclude<NetworkStoreEvent, { readonly type: 'response-data' | 'request-evicted' }>
type JournalNetworkEvent = Exclude<NetworkStoreEvent, { readonly type: 'response-data' | 'request-evicted' }>
interface CapturedRequest {
readonly key: string
@@ -140,26 +137,8 @@ export class NetworkStore implements InspectorRecordConsumer {
* Read retained request lifecycle events.
* @returns Events in observation order.
*/
replay(): readonly ReplayableNetworkEvent[] {
const replay: ReplayableNetworkEvent[] = []
for (const event of this.journal) {
replay.push(event)
if (event.type !== 'response-received' || event.mimeType !== 'text/event-stream') continue
const request = this.requests.get(event.requestKey) as CapturedRequest
const messages = new InspectorEventSourceParser().push(Buffer.concat(request.responseBody))
let eventId = 0
for (const message of messages) {
replay.push({
type: 'event-source-message',
requestKey: request.key,
requestId: request.requestId,
timestampMs: event.timestampMs,
...message,
eventId: String(++eventId),
})
}
}
return replay
replay(): readonly JournalNetworkEvent[] {
return this.journal
}
/**
@@ -274,7 +253,7 @@ export class NetworkStore implements InspectorRecordConsumer {
const bytes = this.appendBody(request, 'response', data)
const byteLength = bytes.byteLength
for (const message of request.eventSourceParser?.push(bytes) ?? []) {
this.emit({
this.publish({
type: 'event-source-message',
requestKey: key,
requestId: request.requestId,
@@ -129,9 +129,16 @@ describe('Inspector Network domain', () => {
.filter(call => call[0] === 'Network.eventSourceMessageReceived')
.map(call => call[1] as unknown))
.toEqual([
expect.objectContaining({ eventName: 'message', eventId: '1', data: 'first' }),
expect.objectContaining({ eventName: 'update', eventId: '2', data: 'second\nline' }),
expect.objectContaining({ timestamp: 0.003, eventName: 'message', eventId: '1', data: 'first' }),
expect.objectContaining({ timestamp: 0.004, eventName: 'update', eventId: '2', data: 'second\nline' }),
])
expect(replay.mock.calls.map(call => String(call[0]))).toEqual([
'Network.requestWillBeSent',
'Network.responseReceived',
'Network.eventSourceMessageReceived',
'Network.eventSourceMessageReceived',
'Network.loadingFinished',
])
})
it('bounds active request metadata and does not retain per-chunk events for replay', () => {