From 6682a7deade46f3c020b2f2dc6c65fc5ebb0bfce Mon Sep 17 00:00:00 2001 From: Zac Bergquist Date: Wed, 25 Jun 2025 13:48:09 -0600 Subject: [PATCH] web player: make query parameters optional In #50262, we made it so that the "durationMs" query paramater in the session player URL is optional (at the expense of an extra API call to determine the recording length prior to playback). We did not, however, do the same for the "recordingType" parameter. This commit adds that information to the existing API call. As a result, users who want to build playback URLs directly instead of clicking the play button in the UI have a stable URL format that will play the session. Closes #55780 Closes gravitational/customer-sensitive-requests#472 --- lib/web/tty_playback.go | 12 +++++-- web/packages/teleport/src/Player/Player.tsx | 36 ++++++++++--------- .../src/services/recordings/recordings.ts | 2 +- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/web/tty_playback.go b/lib/web/tty_playback.go index e5c7151c96012..5d20a0d575e5b 100644 --- a/lib/web/tty_playback.go +++ b/lib/web/tty_playback.go @@ -73,6 +73,11 @@ func (h *Handler) sessionLengthHandle( return nil, trace.Wrap(err) } + type response struct { + Duration int64 `json:"durationMs"` + RecordingType string `json:"recordingType"` + } + evts, errs := clt.StreamSessionEvents(ctx, session.ID(sID), 0) for { select { @@ -82,13 +87,14 @@ func (h *Handler) sessionLengthHandle( if !ok { return nil, trace.NotFound("could not find end event for session %v", sID) } + switch evt := evt.(type) { case *events.SessionEnd: - return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + return response{evt.EndTime.Sub(evt.StartTime).Milliseconds(), "ssh"}, nil case *events.WindowsDesktopSessionEnd: - return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + return response{evt.EndTime.Sub(evt.StartTime).Milliseconds(), "desktop"}, nil case *events.DatabaseSessionEnd: - return map[string]any{"durationMs": evt.EndTime.Sub(evt.StartTime).Milliseconds()}, nil + return response{evt.EndTime.Sub(evt.StartTime).Milliseconds(), "database"}, nil } } } diff --git a/web/packages/teleport/src/Player/Player.tsx b/web/packages/teleport/src/Player/Player.tsx index 9b7694d9e23f5..7947185f3dbf9 100644 --- a/web/packages/teleport/src/Player/Player.tsx +++ b/web/packages/teleport/src/Player/Player.tsx @@ -64,8 +64,9 @@ export function Player() { const validRecordingType = validRecordingTypes.includes(recordingType); const durationMs = Number(getUrlParameter('durationMs', search)); - const shouldFetchSessionDuration = - validRecordingType && (!Number.isInteger(durationMs) || durationMs <= 0); + const validDuration = Number.isInteger(durationMs) && durationMs > 0; + + const shouldFetchSessionDuration = !validRecordingType || !validDuration; useEffect(() => { if (shouldFetchSessionDuration) { @@ -75,25 +76,12 @@ export function Player() { const combinedAttempt = shouldFetchSessionDuration ? fetchDurationAttempt - : makeSuccessAttempt({ durationMs }); + : makeSuccessAttempt({ durationMs, recordingType }); function onLogout() { session.logout(); } - if (!validRecordingType) { - return ( - - - - Invalid query parameter recordingType: {recordingType}, should be - one of {validRecordingTypes.join(', ')}. - - - - ); - } - if ( combinedAttempt.status === '' || combinedAttempt.status === 'processing' @@ -119,6 +107,20 @@ export function Player() { ); } + if (!validRecordingTypes.includes(combinedAttempt.data.recordingType)) { + return ( + + + + Invalid query parameter recordingType:{' '} + {combinedAttempt.data.recordingType}, should be one of{' '} + {validRecordingTypes.join(', ')}. + + + + ); + } + return ( @@ -134,7 +136,7 @@ export function Player() { position: 'relative', }} > - {recordingType === 'desktop' ? ( + {combinedAttempt.data.recordingType === 'desktop' ? ( { + ): Promise<{ durationMs: number; recordingType: string }> { return api.get(cfg.getSessionDurationUrl(clusterId, sessionId)); } }