diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.tsx b/frontend/app/components/Session_/Player/Controls/Timeline.tsx index db241a30c..0061ba5b3 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.tsx +++ b/frontend/app/components/Session_/Player/Controls/Timeline.tsx @@ -112,6 +112,9 @@ function Timeline(props: IProps) { }; const jumpToTime = (e: React.MouseEvent) => { + if ((e.target as HTMLDivElement).id === 'click-ignore') { + return; + } seekProgress(e); }; diff --git a/frontend/app/player-ui/ProgressCircle.tsx b/frontend/app/player-ui/ProgressCircle.tsx index 6b1945080..7cad150ba 100644 --- a/frontend/app/player-ui/ProgressCircle.tsx +++ b/frontend/app/player-ui/ProgressCircle.tsx @@ -10,6 +10,7 @@ interface IProps { export const ProgressCircle = memo(({ preview, isGreen }: IProps) => (
) diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index e8b7dcce9..baa0176fb 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -3,14 +3,15 @@ import { Log, ResourceType, getResourceFromNetworkRequest, - getResourceFromResourceTiming + getResourceFromResourceTiming, } from 'Player'; import ListWalker from 'Player/common/ListWalker'; import Lists, { InitialLists, INITIAL_STATE as LISTS_INITIAL_STATE, - State as ListsState + State as ListsState, } from 'Player/web/Lists'; +import Screen from 'Player/web/Screen/Screen'; import CanvasManager from 'Player/web/managers/CanvasManager'; import { VElement } from 'Player/web/managers/DOM/VirtualDOM'; import PagesManager from 'Player/web/managers/PagesManager'; @@ -24,17 +25,15 @@ import { ResourceTiming, SetPageLocation, SetViewportScroll, - SetViewportSize + SetViewportSize, } from 'Player/web/messages'; import { isDOMType } from 'Player/web/messages/filters.gen'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import Screen from 'Player/web/Screen/Screen'; // @ts-ignore import { Decoder } from 'syncod'; import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; - export interface TabState extends ListsState { performanceAvailability?: PerformanceTrackManager['availability']; performanceChartData: PerformanceChartPoint[]; @@ -60,10 +59,13 @@ export default class TabSessionManager { }; public locationManager: ListWalker = new ListWalker(); - private locationEventManager: ListWalker /**/ = new ListWalker(); + private locationEventManager: ListWalker /**/ = + new ListWalker(); private loadedLocationManager: ListWalker = new ListWalker(); - private connectionInfoManger: ListWalker = new ListWalker(); - private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); + private connectionInfoManger: ListWalker = + new ListWalker(); + private performanceTrackManager: PerformanceTrackManager = + new PerformanceTrackManager(); private windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); private resizeManager: ListWalker = new ListWalker([]); @@ -81,14 +83,27 @@ export default class TabSessionManager { constructor( private session: any, - private readonly state: Store<{ tabStates: { [tabId: string]: TabState }, tabNames: { [tabId: string]: string } }>, + private readonly state: Store<{ + tabStates: { [tabId: string]: TabState }; + tabNames: { [tabId: string]: string }; + }>, private readonly screen: Screen, private readonly id: string, - private readonly setSize: ({ height, width }: { height: number; width: number }) => void, + private readonly setSize: ({ + height, + width, + }: { + height: number; + width: number; + }) => void, private readonly sessionStart: number, initialLists?: Partial ) { - this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading); + this.pagesManager = new PagesManager( + screen, + this.session.isMobile, + this.setCSSLoading + ); this.lists = new Lists(initialLists); initialLists?.event?.forEach((e: Record) => { // TODO: to one of "Movable" module @@ -100,7 +115,7 @@ export default class TabSessionManager { setSession = (session: any) => { this.session = session; - } + }; public getNode = (id: number) => { return this.pagesManager.getNode(id); @@ -109,12 +124,24 @@ export default class TabSessionManager { public updateLists(lists: Partial) { Object.keys(lists).forEach((key: 'event' | 'stack' | 'exceptions') => { const currentList = this.lists.lists[key]; - lists[key]!.forEach((item) => currentList.insert(item)); - }); - lists?.event?.forEach((e: Record) => { - if (e.type === EVENT_TYPES.LOCATION) { - this.locationEventManager.append(e); - } + const insertingList = lists[key]; + insertingList?.forEach((item) => { + if ( + currentList.list.findIndex( + (exv: { time: number; key: number; messageId?: number }) => + exv.time === item.time && + exv.key === item.key && + (exv.messageId && item.messageId + ? exv.messageId === item.messageId + : true) + ) === -1 + ) { + currentList.insert(item); + if (key === 'event' && item.type === EVENT_TYPES.LOCATION) { + this.locationEventManager.append(item); + } + } + }); }); const eventCount = lists?.event?.length || 0; @@ -168,11 +195,15 @@ export default class TabSessionManager { this.performanceTrackManager = new PerformanceTrackManager(); this.windowNodeCounter = new WindowNodeCounter(); - this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this.setCSSLoading); + this.pagesManager = new PagesManager( + this.screen, + this.session.isMobile, + this.setCSSLoading + ); } distributeMessage(msg: Message): void { - this.lastMessageTs = msg.time + this.lastMessageTs = msg.time; switch (msg.tp) { case MType.CanvasNode: const managerId = `${msg.timestamp}_${msg.nodeId}`; @@ -180,11 +211,17 @@ export default class TabSessionManager { const fileId = managerId; const delta = msg.timestamp - this.sessionStart; - const canvasNodeLinks = this.session.canvasURL.filter((url: string) => url.includes(fileId)) as string[]; - const tarball = canvasNodeLinks.find((url: string) => url.includes('.tar.')); - const mp4file = canvasNodeLinks.find((url: string) => url.includes('.mp4')); + const canvasNodeLinks = this.session.canvasURL.filter((url: string) => + url.includes(fileId) + ) as string[]; + const tarball = canvasNodeLinks.find((url: string) => + url.includes('.tar.') + ); + const mp4file = canvasNodeLinks.find((url: string) => + url.includes('.mp4') + ); if (!tarball && !mp4file) { - console.error('no canvas recording provided') + console.error('no canvas recording provided'); break; } const manager = new CanvasManager( @@ -192,9 +229,13 @@ export default class TabSessionManager { delta, [tarball, mp4file], this.getNode as (id: number) => VElement | undefined, - this.sessionStart, + this.sessionStart ); - this.canvasManagers[managerId] = { manager, start: msg.timestamp, running: false }; + this.canvasManagers[managerId] = { + manager, + start: msg.timestamp, + running: false, + }; this.canvasReplayWalker.append(msg); } break; @@ -233,15 +274,23 @@ export default class TabSessionManager { case MType.ResourceTimingDeprecated: case MType.ResourceTiming: // TODO: merge `resource` and `fetch` lists into one here instead of UI - if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) { + if ( + msg.initiator !== ResourceType.FETCH && + msg.initiator !== ResourceType.XHR + ) { this.lists.lists.resource.insert( - getResourceFromResourceTiming(msg as ResourceTiming, this.sessionStart) + getResourceFromResourceTiming( + msg as ResourceTiming, + this.sessionStart + ) ); } break; case MType.Fetch: case MType.NetworkRequest: - this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)); + this.lists.lists.fetch.insert( + getResourceFromNetworkRequest(msg, this.sessionStart) + ); break; case MType.WsChannel: this.lists.lists.websocket.insert(msg); @@ -274,25 +323,33 @@ export default class TabSessionManager { switch (msg.tp) { case MType.CreateDocument: this.windowNodeCounter.reset(); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + this.performanceTrackManager.setCurrentNodesCount( + this.windowNodeCounter.count + ); break; case MType.CreateTextNode: case MType.CreateElementNode: this.windowNodeCounter.addNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + this.performanceTrackManager.setCurrentNodesCount( + this.windowNodeCounter.count + ); break; case MType.MoveNode: this.windowNodeCounter.moveNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + this.performanceTrackManager.setCurrentNodesCount( + this.windowNodeCounter.count + ); break; case MType.RemoveNode: this.windowNodeCounter.removeNode(msg.id); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + this.performanceTrackManager.setCurrentNodesCount( + this.windowNodeCounter.count + ); break; case MType.LoadFontFace: if (msg.source.startsWith('url(/')) { const relativeUrl = msg.source.substring(4); - const lastUrl = this.locationManager.findLast(msg.time)?.url + const lastUrl = this.locationManager.findLast(msg.time)?.url; if (lastUrl) { const u = new URL(lastUrl); const base = u.protocol + '//' + u.hostname + '/'; @@ -310,16 +367,21 @@ export default class TabSessionManager { move(t: number, index?: number): void { const stateToUpdate: Record = {}; /* == REFACTOR_ME == */ - const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); + const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast( + t, + index + ); if (!!lastLoadedLocationMsg) { // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) - this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; + this.navigationStartOffset = + lastLoadedLocationMsg.navigationStart - this.sessionStart; } const lastLocationEvent = this.locationEventManager.moveGetLast(t, index); if (!!lastLocationEvent) { if (lastLocationEvent.domContentLoadedTime != null) { stateToUpdate.domContentLoadedTime = { - time: lastLocationEvent.domContentLoadedTime + this.navigationStartOffset, + time: + lastLocationEvent.domContentLoadedTime + this.navigationStartOffset, // TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) value: lastLocationEvent.domContentLoadedTime, }; @@ -339,19 +401,21 @@ export default class TabSessionManager { if (!!lastLocationMsg) { const tabNames = this.state.get().tabNames; if (lastLocationMsg.documentTitle) { - tabNames[this.id] = lastLocationMsg.documentTitle + tabNames[this.id] = lastLocationMsg.documentTitle; } // @ts-ignore comes from parent state this.state.update({ location: lastLocationMsg.url, tabNames }); } - const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); + const lastPerformanceTrackMessage = + this.performanceTrackManager.moveGetLast(t, index); if (!!lastPerformanceTrackMessage) { stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; } Object.assign(stateToUpdate, this.lists.moveGetState(t)); - Object.keys(stateToUpdate).length > 0 && this.updateLocalState(stateToUpdate); + Object.keys(stateToUpdate).length > 0 && + this.updateLocalState(stateToUpdate); /* Sequence of the managers is important here */ // Preparing the size of "screen" const lastResize = this.resizeManager.moveGetLast(t, index); @@ -365,17 +429,17 @@ export default class TabSessionManager { } this.canvasReplayWalker.moveApply(t, (canvasMsg) => { if (canvasMsg) { - const managerId = `${canvasMsg.timestamp}_${canvasMsg.nodeId}` - const possibleManager = this.canvasManagers[managerId] + const managerId = `${canvasMsg.timestamp}_${canvasMsg.nodeId}`; + const possibleManager = this.canvasManagers[managerId]; if (possibleManager && !possibleManager.running) { this.canvasManagers[managerId].manager.startVideo(); this.canvasManagers[managerId].running = true; } } - }) + }); const runningManagers = Object.values(this.canvasManagers).filter( (manager) => manager.running - ) + ); runningManagers.forEach(({ manager }) => { manager.move(t); }); @@ -394,7 +458,9 @@ export default class TabSessionManager { * */ public sortDomRemoveMessages = (msgs: Message[]) => { // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) - const headChildrenMsgIds = msgs.filter((m) => m.parentID === 1).map((m) => m.id); + const headChildrenMsgIds = msgs + .filter((m) => m.parentID === 1) + .map((m) => m.id); this.pagesManager.sortPages((m1, m2) => { if (m1.time === m2.time) { if (m1.tp === MType.RemoveNode && m2.tp !== MType.RemoveNode) { diff --git a/frontend/app/types/session/error.ts b/frontend/app/types/session/error.ts index 45bd759ef..978a6a0de 100644 --- a/frontend/app/types/session/error.ts +++ b/frontend/app/types/session/error.ts @@ -12,7 +12,7 @@ type Stack = { function: string; url: string }[] export interface IError { sessionId: string - messageId: string + messageId: number timestamp: number errorId: string projectId: string diff --git a/frontend/app/types/session/event.ts b/frontend/app/types/session/event.ts index dd207e04a..b9b7c1c07 100644 --- a/frontend/app/types/session/event.ts +++ b/frontend/app/types/session/event.ts @@ -33,6 +33,7 @@ interface IEvent { label: string; targetPath: string; tabId?: string; + messageId?: number; target: { path: string; label: string; @@ -91,6 +92,7 @@ class Event { label: IEvent['label']; target: IEvent['target']; tabId: IEvent['tabId']; + messageId: IEvent['messageId']; constructor(event: IEvent) { Object.assign(this, { @@ -98,6 +100,7 @@ class Event { label: event.label, key: event.key, tabId: event.tabId, + messageId: event.messageId, target: { path: event.target?.path || event.targetPath, label: event.target?.label, diff --git a/frontend/app/types/session/issue.ts b/frontend/app/types/session/issue.ts index 9c5384e76..5185a42d3 100644 --- a/frontend/app/types/session/issue.ts +++ b/frontend/app/types/session/issue.ts @@ -63,6 +63,7 @@ export interface IIssue { icon: string timestamp: number startedAt: number + messageId: number } export default class Issue { @@ -78,6 +79,7 @@ export default class Issue { context: IIssue["context"] icon: IIssue["icon"] key: number + messageId: IIssue["messageId"]; constructor({ type, ...rest }: IIssue & { key: number }) { Object.assign(this, { diff --git a/frontend/app/types/session/stackEvent.ts b/frontend/app/types/session/stackEvent.ts index 8bbea2778..590d5517a 100644 --- a/frontend/app/types/session/stackEvent.ts +++ b/frontend/app/types/session/stackEvent.ts @@ -44,6 +44,7 @@ export interface IStackEvent { payload: any; source: any; level: string; + messageId: number; isRed: boolean; } @@ -56,6 +57,7 @@ export default class StackEvent { payload: IStackEvent["payload"]; source: IStackEvent["source"]; level: IStackEvent["level"]; + messageId: IStackEvent["messageId"]; constructor(evt: IStackEvent) { const event = { ...evt, source: evt.source || OPENREPLAY, payload: evt.payload || {} };