mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
Fix session recording seeking in long inactive periods (#59637)
* Fix the playback API not returning the screen state when there are no events in requested time range * Continue playing if in loading state when receiving the stop event * Push the batch to the buffer before resuming playback on batch events
This commit is contained in:
@@ -536,7 +536,9 @@ func (s *recordingPlayback) streamEvents(ctx context.Context, req *fetchRequest,
|
||||
case evt, ok := <-eventsChan:
|
||||
if !ok {
|
||||
flushBatch()
|
||||
if req.requestCurrentScreen && !screenSent && inTimeRange {
|
||||
// Send screen if requested and not already sent
|
||||
// This handles the case where the stream ends, but we haven't sent the screen yet
|
||||
if req.requestCurrentScreen && !screenSent {
|
||||
s.sendCurrentScreen(req.requestID, req.startOffset)
|
||||
}
|
||||
sendStop()
|
||||
@@ -546,6 +548,11 @@ func (s *recordingPlayback) streamEvents(ctx context.Context, req *fetchRequest,
|
||||
|
||||
if !processEvent(evt) {
|
||||
flushBatch()
|
||||
// Send screen if requested and not already sent when we reach the end of the time range
|
||||
// (i.e. there was no event in the time range)
|
||||
if req.requestCurrentScreen && !screenSent {
|
||||
s.sendCurrentScreen(req.requestID, req.startOffset)
|
||||
}
|
||||
sendStop()
|
||||
|
||||
return
|
||||
|
||||
@@ -439,6 +439,43 @@ func TestBufferedEvents_LargeGap(t *testing.T) {
|
||||
require.Equal(t, byte(eventTypeStop), responses[3][0], "Fourth message should be stop event")
|
||||
}
|
||||
|
||||
func TestScreenWithNoEventsInRange(t *testing.T) {
|
||||
ws, _ := createWebSocket(t, func(mockClient *mockStreamClient) {
|
||||
<-mockClient.eventRequested
|
||||
|
||||
mockClient.sendEvent(&apievents.SessionStart{
|
||||
TerminalSize: "80:24",
|
||||
})
|
||||
mockClient.sendEvent(&apievents.SessionPrint{
|
||||
DelayMilliseconds: 500,
|
||||
Data: []byte("Early content"),
|
||||
})
|
||||
mockClient.sendEvent(&apievents.SessionPrint{
|
||||
DelayMilliseconds: 1500,
|
||||
Data: []byte(" that should appear in screen"),
|
||||
})
|
||||
// No events in the 3000-4000ms range we'll request
|
||||
mockClient.sendEvent(&apievents.SessionEnd{
|
||||
StartTime: time.Now(),
|
||||
EndTime: time.Now().Add(5 * time.Second),
|
||||
})
|
||||
})
|
||||
|
||||
// Request a time range with no events but ask for screen
|
||||
responses := fetchAndCollectResponses(t, ws, 3000, 4000, true, 1)
|
||||
|
||||
require.Len(t, responses, 3, "Should receive 3 messages: start, screen, stop")
|
||||
|
||||
require.Equal(t, byte(eventTypeStart), responses[0][0], "First message should be start event")
|
||||
require.Equal(t, byte(eventTypeScreen), responses[1][0], "Second message should be screen event")
|
||||
|
||||
// The screen should contain the content from events before the requested range
|
||||
require.Contains(t, string(responses[1][responseHeaderSize:]), "Early content that should appear in screen",
|
||||
"Screen should contain terminal state even though no events in requested range")
|
||||
|
||||
require.Equal(t, byte(eventTypeStop), responses[2][0], "Third message should be stop event")
|
||||
}
|
||||
|
||||
func TestUnsupportedRequest(t *testing.T) {
|
||||
mockClient := newMockStreamClient()
|
||||
|
||||
|
||||
@@ -332,6 +332,8 @@ export class SessionStream<
|
||||
// mark loading as false as soon as we get any batch event so we can start playing again
|
||||
this.loading = false;
|
||||
|
||||
this.pushBatchToBuffer(parsed.events);
|
||||
|
||||
if (this.state === PlayerState.Loading) {
|
||||
if (this.wasPlayingBeforeSeek) {
|
||||
this.play();
|
||||
@@ -340,8 +342,6 @@ export class SessionStream<
|
||||
}
|
||||
}
|
||||
|
||||
this.pushBatchToBuffer(parsed.events);
|
||||
|
||||
const hasEnd =
|
||||
parsed.events[parsed.events.length - 1].type === this.endEventType;
|
||||
|
||||
@@ -362,6 +362,14 @@ export class SessionStream<
|
||||
|
||||
this.updateLoadedTimes(parsed.startTime, parsed.endTime);
|
||||
|
||||
if (this.state === PlayerState.Loading) {
|
||||
if (this.wasPlayingBeforeSeek) {
|
||||
this.play();
|
||||
} else {
|
||||
this.setState(PlayerState.Paused);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user