From 1421eb8c044b6c8803eb07f9cbda426a595d4a92 Mon Sep 17 00:00:00 2001 From: irving ou Date: Wed, 8 Jan 2025 09:39:21 -0500 Subject: [PATCH] fix(dgw):properly fallback to recording when streaming is not available --- webapp/biome.json | 3 +- webapp/player-project/src/main.ts | 17 +++++++- webapp/player-project/src/notification.ts | 2 +- webapp/player-project/src/streamers/index.ts | 11 ++++- webapp/player-project/src/terminal.ts | 7 +++ webapp/player-project/src/ws-proxy.ts | 45 ++++++++++++++++++++ 6 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 webapp/player-project/src/ws-proxy.ts diff --git a/webapp/biome.json b/webapp/biome.json index d6af64a3f..fc3d62d04 100644 --- a/webapp/biome.json +++ b/webapp/biome.json @@ -11,7 +11,8 @@ "*.min.css", "player/", "player-project/dist/", - "player-project/node_modules/" + "player-project/node_modules/", + "player-project/src/hack.js" ] }, "linter": { diff --git a/webapp/player-project/src/main.ts b/webapp/player-project/src/main.ts index 01d5d958b..0ac39a923 100644 --- a/webapp/player-project/src/main.ts +++ b/webapp/player-project/src/main.ts @@ -1,6 +1,9 @@ import { GatewayAccessApi } from './gateway'; +import { showNotification } from './notification.ts'; import { getPlayer } from './players/index.js'; -import { getShadowPlayer } from './streamers/index.js'; +import { cleanUpStreamers, getShadowPlayer } from './streamers/index.js'; +import './ws-proxy.ts'; +import { OnBeforeClose as BeforeWebsocketClose } from './ws-proxy.ts'; async function main() { const { sessionId, token, gatewayAccessUrl, isActive } = getSessionDetails(); @@ -23,6 +26,18 @@ async function playSessionShadowing(gatewayAccessApi) { try { const recordingInfo = await gatewayAccessApi.fetchRecordingInfo(); const fileType = getFileType(recordingInfo); + BeforeWebsocketClose((closeEvent) => { + if (closeEvent.code !== 1000) { + // faild, try to play static recording + cleanUpStreamers(); + playStaticRecording(gatewayAccessApi); + showNotification('Session may have ended,playing recording instead', 'info'); + return { + ...closeEvent, + code: 1000, // for avoid extra handling by other listeners + }; + } + }); getShadowPlayer(fileType).play(gatewayAccessApi); } catch (error) { diff --git a/webapp/player-project/src/notification.ts b/webapp/player-project/src/notification.ts index fe05e4caa..ab96e3299 100644 --- a/webapp/player-project/src/notification.ts +++ b/webapp/player-project/src/notification.ts @@ -1,6 +1,6 @@ // This is a very basic notification system // We should definitely move away from plain JavaScript and use a library instead -export function showNotification(message: string, type: 'success' | 'error') { +export function showNotification(message: string, type: 'success' | 'error' | 'info') { const notification = document.getElementById('notification'); notification.style.display = 'flex'; const messageElement = document.getElementById('notification-message'); diff --git a/webapp/player-project/src/streamers/index.ts b/webapp/player-project/src/streamers/index.ts index 9732c6e6b..43c616a10 100644 --- a/webapp/player-project/src/streamers/index.ts +++ b/webapp/player-project/src/streamers/index.ts @@ -1,7 +1,7 @@ import { GatewayAccessApi } from '../gateway'; +import { removeTerminal } from '../terminal'; import { handleCast } from './cast'; import { handleWebm } from './webm'; - export const getShadowPlayer = (fileType) => { const player = { play: (_: GatewayAccessApi) => {}, @@ -17,3 +17,12 @@ export const getShadowPlayer = (fileType) => { return player; }; + +export const cleanUpStreamers = () => { + // Clean up any existing shadow-player elements + const shadowPlayers = document.querySelectorAll('shadow-player'); + for (const shadowPlayer of shadowPlayers) { + shadowPlayer.remove(); + } + removeTerminal(); +}; diff --git a/webapp/player-project/src/terminal.ts b/webapp/player-project/src/terminal.ts index dd375734c..7be2501d9 100644 --- a/webapp/player-project/src/terminal.ts +++ b/webapp/player-project/src/terminal.ts @@ -18,3 +18,10 @@ export function createTerminalDiv() { document.body.appendChild(terminalDiv); return terminalDiv; } + +export function removeTerminal() { + const terminalDiv = document.getElementById('terminal'); + if (terminalDiv) { + terminalDiv.remove(); + } +} diff --git a/webapp/player-project/src/ws-proxy.ts b/webapp/player-project/src/ws-proxy.ts new file mode 100644 index 000000000..ee6add51d --- /dev/null +++ b/webapp/player-project/src/ws-proxy.ts @@ -0,0 +1,45 @@ +let beforeClose = (args: CloseEvent): CloseEvent => { + return args; +}; + +export const OnBeforeClose = (callback: (args: CloseEvent) => CloseEvent) => { + beforeClose = callback; +}; + +const WebSocketProxy = new Proxy(window.WebSocket, { + construct(target, args: [url: string | URL, protocols?: string | string[]]) { + console.log('Proxying WebSocket connection', ...args); + const ws = new target(...args); // Create the actual WebSocket instance + + // Proxy for intercepting `addEventListener` + ws.addEventListener = new Proxy(ws.addEventListener, { + apply(target, thisArg, args) { + if (args[0] === 'close') { + console.log('Intercepted addEventListener for close event'); + const transformedArgs = beforeClose(args as unknown as CloseEvent); + return target.apply(thisArg, transformedArgs); + } + return target.apply(thisArg, args); + }, + }); + + // Proxy for intercepting `onclose` + return new Proxy(ws, { + set(target, prop, value) { + if (prop === 'onclose') { + console.log('Intercepted setting of onclose'); + const transformedValue = (...args) => { + const transformedArgs = beforeClose(args as unknown as CloseEvent); + if (typeof value === 'function') { + value(transformedArgs); // Call the original handler + } + }; + return Reflect.set(target, prop, transformedValue); + } + return Reflect.set(target, prop, value); + }, + }); + }, +}); + +window.WebSocket = WebSocketProxy;