diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index a12565f1b..5946e9a06 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -43,27 +43,6 @@ export default class MessageLoader { this.session = session; } - /** - * TODO: has to be moved out of messageLoader logic somehow - * */ - spriteMapSvg: SVGElement | null = null; - - potentialSpriteMap: Record = {}; - - domParser: DOMParser | null = null; - - createSpriteMap = () => { - if (!this.spriteMapSvg) { - this.domParser = new DOMParser(); - this.spriteMapSvg = document.createElementNS( - 'http://www.w3.org/2000/svg', - 'svg', - ); - this.spriteMapSvg.setAttribute('style', 'display: none;'); - this.spriteMapSvg.setAttribute('id', 'reconstructed-sprite'); - } - }; - createNewParser( shouldDecrypt = true, onMessagesDone: (msgs: PlayerMsg[], file?: string) => void, @@ -101,21 +80,6 @@ export default class MessageLoader { let startTimeSet = false; msgs.forEach((msg, i) => { - if (msg.tp === MType.SetNodeAttribute) { - if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) { - this.createSpriteMap(); - if (!this.domParser) { - return console.error('DOM parser is not initialized?'); - } - handleSprites( - this.potentialSpriteMap, - this.domParser, - msg, - this.spriteMapSvg!, - i, - ); - } - } if (msg.tp === MType.Redux || msg.tp === MType.ReduxDeprecated) { if ('actionTime' in msg && msg.actionTime) { msg.time = msg.actionTime - this.session.startedAt; @@ -153,7 +117,7 @@ export default class MessageLoader { // .sort((m1, m2) => m1.time - m2.time) .sort(brokenDomSorter) .sort(sortIframes); - + if (brokenMessages > 0) { console.warn( 'Broken timestamp messages', @@ -333,10 +297,6 @@ export default class MessageLoader { await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]); this.messageManager.onFileReadSuccess(); - // no sprites for mobile - if (this.spriteMapSvg && 'injectSpriteMap' in this.messageManager) { - this.messageManager.injectSpriteMap(this.spriteMapSvg); - } }; loadEFSMobs = async () => { @@ -471,40 +431,6 @@ function findBrokenNodes(nodes: any[]) { return result; } -function handleSprites( - potentialSpriteMap: Record, - parser: DOMParser, - msg: Record, - spriteMapSvg: SVGElement, - i: number, -) { - const [_, svgData] = msg.value.split('_$OPENREPLAY_SPRITE$_'); - const potentialSprite = potentialSpriteMap[svgData]; - if (potentialSprite) { - msg.value = potentialSprite; - } else { - const svgDoc = parser.parseFromString(svgData, 'image/svg+xml'); - const originalSvg = svgDoc.querySelector('svg'); - if (originalSvg) { - const symbol = document.createElementNS( - 'http://www.w3.org/2000/svg', - 'symbol', - ); - const symbolId = `symbol-${msg.id || `ind-${i}`}`; // Generate an ID if missing - symbol.setAttribute('id', symbolId); - symbol.setAttribute( - 'viewBox', - originalSvg.getAttribute('viewBox') || '0 0 24 24', - ); - symbol.innerHTML = originalSvg.innerHTML; - - spriteMapSvg.appendChild(symbol); - msg.value = `#${symbolId}`; - potentialSpriteMap[svgData] = `#${symbolId}`; - } - } -} - // @ts-ignore window.searchOrphans = (msgs) => findBrokenNodes(msgs.filter((m) => [8, 9, 10, 70].includes(m.tp))); diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index d6052fd5e..785b1d21e 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -201,8 +201,16 @@ export default class MessageManager { } Object.values(this.tabs).forEach((tab) => tab.onFileReadSuccess?.()); + + this.updateSpriteMap(); }; + public updateSpriteMap = () => { + if (this.spriteMapSvg) { + this.injectSpriteMap(this.spriteMapSvg); + } + } + public onFileReadFailed = (...e: any[]) => { logger.error(e); this.state.update({ error: true }); @@ -337,9 +345,38 @@ export default class MessageManager { this.state.update({ tabChangeEvents: this.tabChangeEvents }); } + spriteMapSvg: SVGElement | null = null; + potentialSpriteMap: Record = {}; + domParser: DOMParser | null = null; + createSpriteMap = () => { + if (!this.spriteMapSvg) { + this.domParser = new DOMParser(); + this.spriteMapSvg = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'svg', + ); + this.spriteMapSvg.setAttribute('style', 'display: none;'); + this.spriteMapSvg.setAttribute('id', 'reconstructed-sprite'); + } + }; + distributeMessage = (msg: Message & { tabId: string }): void => { // @ts-ignore placeholder msg for timestamps if (msg.tp === 9999) return; + if (msg.tp === MType.SetNodeAttribute) { + if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) { + this.createSpriteMap(); + if (!this.domParser) { + return console.error('DOM parser is not initialized?'); + } + handleSprites( + this.potentialSpriteMap, + this.domParser, + msg, + this.spriteMapSvg!, + ); + } + } if (!this.tabs[msg.tabId]) { this.tabsAmount++; this.state.update({ @@ -454,3 +491,36 @@ function mapTabs(tabs: Record) { return tabMap; } + +function handleSprites( + potentialSpriteMap: Record, + parser: DOMParser, + msg: Record, + spriteMapSvg: SVGElement, +) { + const [_, svgData] = msg.value.split('_$OPENREPLAY_SPRITE$_'); + const potentialSprite = potentialSpriteMap[svgData]; + if (potentialSprite) { + msg.value = potentialSprite; + } else { + const svgDoc = parser.parseFromString(svgData, 'image/svg+xml'); + const originalSvg = svgDoc.querySelector('svg'); + if (originalSvg) { + const symbol = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'symbol', + ); + const symbolId = `symbol-${msg.id || `ind-${msg.time}`}`; // Generate an ID if missing + symbol.setAttribute('id', symbolId); + symbol.setAttribute( + 'viewBox', + originalSvg.getAttribute('viewBox') || '0 0 24 24', + ); + symbol.innerHTML = originalSvg.innerHTML; + + spriteMapSvg.appendChild(symbol); + msg.value = `#${symbolId}`; + potentialSpriteMap[svgData] = `#${symbolId}`; + } + } +} diff --git a/frontend/app/player/web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts index bcd217d8d..0baa194ef 100644 --- a/frontend/app/player/web/WebLivePlayer.ts +++ b/frontend/app/player/web/WebLivePlayer.ts @@ -43,6 +43,7 @@ export default class WebLivePlayer extends WebPlayer { wpState, (id) => this.messageManager.getNode(id), agentId, + this.messageManager.updateSpriteMap, uiErrorHandler, ); this.assistManager.connect(session.agentToken!, agentId, projectId); diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index 9ff091aef..ded2c61b7 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -3,14 +3,14 @@ import type { Socket } from 'socket.io-client'; import type { PlayerMsg, Store } from 'App/player'; import CanvasReceiver from 'Player/web/assist/CanvasReceiver'; import { gunzipSync } from 'fflate'; -import { Message } from '../messages'; +import { Message, MType } from '../messages'; import type Screen from '../Screen/Screen'; import MStreamReader from '../messages/MStreamReader'; import JSONRawMessageReader from '../messages/JSONRawMessageReader'; import Call, { CallingState } from './Call'; import RemoteControl, { RemoteControlStatus } from './RemoteControl'; import ScreenRecording, { SessionRecordingStatus } from './ScreenRecording'; - +import { debounceCall } from 'App/utils' export { RemoteControlStatus, SessionRecordingStatus, CallingState }; export enum ConnectionStatus { @@ -82,6 +82,7 @@ export default class AssistManager { private store: Store, private getNode: MessageManager['getNode'], public readonly agentId: number, + private readonly updateSpriteMap: () => void, public readonly uiErrorHandler?: { error: (msg: string) => void; }, @@ -243,6 +244,11 @@ export default class AssistManager { msg !== null; msg = reader.readNext() ) { + if (msg.tp === MType.SetNodeAttribute) { + if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) { + debounceCall(this.updateSpriteMap, 250)() + } + } this.handleMessage(msg, msg._index); } };