diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts deleted file mode 100644 index 49105200c..000000000 --- a/frontend/app/player/_web/MessageManager.ts +++ /dev/null @@ -1,570 +0,0 @@ -// @ts-ignore -import { Decoder } from "syncod"; -import logger from 'App/logger'; - -import Resource, { TYPES } from 'Types/session/resource'; -import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import Log from 'Types/session/log'; - -import { toast } from 'react-toastify'; - -import type { Store } from '../player/types'; -import ListWalker from '../_common/ListWalker'; - -import Screen from './Screen/Screen'; - -import PagesManager from './managers/PagesManager'; -import MouseMoveManager from './managers/MouseMoveManager'; - -import PerformanceTrackManager from './managers/PerformanceTrackManager'; -import WindowNodeCounter from './managers/WindowNodeCounter'; -import ActivityManager from './managers/ActivityManager'; - -import MFileReader from './messages/MFileReader'; -import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles'; -import { decryptSessionBytes } from './network/crypto'; - -import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen/Screen'; -import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './assist/AssistManager'; -import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE } from './Lists'; - -import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; -import type { SkipInterval } from './managers/ActivityManager'; - - -export interface State extends SuperState, AssistState { - performanceChartData: PerformanceChartPoint[], - skipIntervals: SkipInterval[], - connType?: string, - connBandwidth?: number, - location?: string, - performanceChartTime?: number, - - domContentLoadedTime?: any, - domBuildingTime?: any, - loadTime?: any, - error: boolean, - devtoolsLoading: boolean, - - liveTimeTravel: boolean, - messagesLoading: boolean, - cssLoading: boolean, - - ready: boolean, - lastMessageTime: number, -} - -export const INITIAL_STATE: State = { - ...SUPER_INITIAL_STATE, - ...LISTS_INITIAL_STATE, - ...ASSIST_INITIAL_STATE, - performanceChartData: [], - skipIntervals: [], - error: false, - devtoolsLoading: false, - - liveTimeTravel: false, - messagesLoading: false, - cssLoading: false, - get ready() { - return !this.messagesLoading && !this.cssLoading - }, - lastMessageTime: 0, -}; - - -import type { - Message, - SetPageLocation, - ConnectionInformation, - SetViewportSize, - SetViewportScroll, - MouseClick, -} from './messages'; - -const visualChanges = [ - "mouse_move", - "mouse_click", - "create_element_node", - "set_input_value", - "set_input_checked", - "set_viewport_size", - "set_viewport_scroll", -] - -export default class MessageManager extends Screen { - // TODO: consistent with the other data-lists - private locationEventManager: ListWalker/**/ = new ListWalker(); - private locationManager: ListWalker = new ListWalker(); - private loadedLocationManager: ListWalker = new ListWalker(); - private connectionInfoManger: ListWalker = new ListWalker(); - private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); - private windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); - private clickManager: ListWalker = new ListWalker(); - - private resizeManager: ListWalker = new ListWalker([]); - private pagesManager: PagesManager; - private mouseMoveManager: MouseMoveManager; - - private scrollManager: ListWalker = new ListWalker(); - - private readonly decoder = new Decoder(); - private readonly lists: Lists; - - private activityManager: ActivityManager | null = null; - - private sessionStart: number; - private navigationStartOffset: number = 0; - private lastMessageTime: number = 0; - private lastMessageInFileTime: number = 0; - - constructor( - private readonly session: any /*Session*/, - private readonly state: Store, - config: any, - live: boolean, - ) { - super(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); - - this.sessionStart = this.session.startedAt; - - if (live) { - this.lists = new Lists() - } else { - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - /* == REFACTOR_ME == */ - const eventList = session.events.toJSON(); - // TODO: fix types for events, remove immutable js - eventList.forEach((e: Record) => { - if (e.type === EVENT_TYPES.LOCATION) { //TODO type system - this.locationEventManager.append(e); - } - }) - - this.lists = new Lists({ - event: eventList, - stack: session.stackEvents.toJSON(), - resource: session.resources.toJSON(), - exceptions: session.errors, - }) - - - /* === */ - this.loadMessages(); - } - } - - private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { - const msgs: Array = [] - let next: ReturnType - while (next = fileReader.next()) { - const [msg, index] = next - this.distributeMessage(msg, index) - msgs.push(msg) - onMessage?.(msg) - } - - logger.info("Messages count: ", msgs.length, msgs) - - - // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) - const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); - this.pagesManager.sortPages((m1, m2) => { - if (m1.time === m2.time) { - if (m1.tp === "remove_node" && m2.tp !== "remove_node") { - if (headChildrenIds.includes(m1.id)) { - return -1; - } - } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { - if (headChildrenIds.includes(m2.id)) { - return 1; - } - } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { - const m1FromHead = headChildrenIds.includes(m1.id); - const m2FromHead = headChildrenIds.includes(m2.id); - if (m1FromHead && !m2FromHead) { - return -1; - } else if (m2FromHead && !m1FromHead) { - return 1; - } - } - } - return 0; - }) - } - - - private waitingForFiles: boolean = false - private onFileReadSuccess = () => { - const stateToUpdate = { - performanceChartData: this.performanceTrackManager.chartData, - performanceAvaliability: this.performanceTrackManager.avaliability, - ...this.lists.getFullListsState() - } - if (this.activityManager) { - this.activityManager.end() - stateToUpdate.skipIntervals = this.activityManager.list - } - - this.state.update(stateToUpdate) - } - private onFileReadFailed = (e: any) => { - logger.error(e) - this.state.update({ error: true }) - toast.error('Error requesting a session file') - } - private onFileReadFinally = () => { - this.incomingMessages - .filter(msg => msg.time >= this.lastMessageInFileTime) - .forEach(msg => this.distributeMessage(msg, 0)) - - this.waitingForFiles = false - this.setMessagesLoading(false) - } - - private loadMessages() { - // TODO: reuseable decryptor instance - const createNewParser = (shouldDecrypt=true) => { - const decrypt = shouldDecrypt && this.session.fileKey - ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey) - : (b: Uint8Array) => Promise.resolve(b) - // Each time called - new fileReader created - const fileReader = new MFileReader(new Uint8Array(), this.sessionStart) - return (b: Uint8Array) => decrypt(b).then(b => { - fileReader.append(b) - this.parseAndDistributeMessages(fileReader) - this.setMessagesLoading(false) - }) - } - this.setMessagesLoading(true) - this.waitingForFiles = true - - loadFiles(this.session.domURL, createNewParser()) - .catch(() => // do if only the first file missing (404) (?) - requestEFSDom(this.session.sessionId) - .then(createNewParser(false)) - // Fallback to back Compatability with mobsUrl - .catch(e => - loadFiles(this.session.mobsUrl, createNewParser(false)) - ) - ) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) - - // load devtools - if (this.session.devtoolsURL.length) { - this.state.update({ devtoolsLoading: true }) - loadFiles(this.session.devtoolsURL, createNewParser()) - .catch(() => - requestEFSDevtools(this.session.sessionId) - .then(createNewParser(false)) - ) - //.catch() // not able to download the devtools file - .finally(() => this.state.update({ devtoolsLoading: false })) - } - } - - reloadWithUnprocessedFile() { - const onData = (byteArray: Uint8Array) => { - const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } - this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) - } - const updateState = () => - this.state.update({ - liveTimeTravel: true, - }); - - // assist will pause and skip messages to prevent timestamp related errors - this.reloadMessageManagers() - this.windowNodeCounter.reset() - - this.setMessagesLoading(true) - this.waitingForFiles = true - - return requestEFSDom(this.session.sessionId) - .then(onData) - .then(updateState) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) - } - - private reloadMessageManagers() { - this.locationEventManager = new ListWalker(); - this.locationManager = new ListWalker(); - this.loadedLocationManager = new ListWalker(); - this.connectionInfoManger = new ListWalker(); - this.clickManager = new ListWalker(); - this.scrollManager = new ListWalker(); - this.resizeManager = new ListWalker([]); - - this.performanceTrackManager = new PerformanceTrackManager() - this.windowNodeCounter = new WindowNodeCounter(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - } - - move(t: number, index?: number): void { - const stateToUpdate: Partial = {}; - /* == REFACTOR_ME == */ - const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); - if (!!lastLoadedLocationMsg) { - // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) - this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; - } - const llEvent = this.locationEventManager.moveGetLast(t, index); - if (!!llEvent) { - if (llEvent.domContentLoadedTime != null) { - stateToUpdate.domContentLoadedTime = { - time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) - value: llEvent.domContentLoadedTime, - } - } - if (llEvent.loadTime != null) { - stateToUpdate.loadTime = { - time: llEvent.loadTime + this.navigationStartOffset, - value: llEvent.loadTime, - } - } - if (llEvent.domBuildingTime != null) { - stateToUpdate.domBuildingTime = llEvent.domBuildingTime; - } - } - /* === */ - const lastLocationMsg = this.locationManager.moveGetLast(t, index); - if (!!lastLocationMsg) { - stateToUpdate.location = lastLocationMsg.url; - } - const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); - if (!!lastConnectionInfoMsg) { - stateToUpdate.connType = lastConnectionInfoMsg.type; - stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink; - } - const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); - if (!!lastPerformanceTrackMessage) { - stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; - } - - this.lists.moveGetState(t) - Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); - - /* Sequence of the managers is important here */ - // Preparing the size of "screen" - const lastResize = this.resizeManager.moveGetLast(t, index); - if (!!lastResize) { - this.setSize(lastResize) - } - this.pagesManager.moveReady(t).then(() => { - - const lastScroll = this.scrollManager.moveGetLast(t, index); - if (!!lastScroll && this.window) { - this.window.scrollTo(lastScroll.x, lastScroll.y); - } - // Moving mouse and setting :hover classes on ready view - this.mouseMoveManager.move(t); - const lastClick = this.clickManager.moveGetLast(t); - if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms - this.cursor.click(); - } - // After all changes - redraw the marker - //this.marker.redraw(); - }) - - if (this.waitingForFiles && this.lastMessageTime <= t) { - this.setMessagesLoading(true) - } - } - - private decodeStateMessage(msg: any, keys: Array) { - const decoded = {}; - try { - keys.forEach(key => { - // @ts-ignore TODO: types for decoder - decoded[key] = this.decoder.decode(msg[key]); - }); - } catch (e) { - logger.error("Error on message decoding: ", e, msg); - return null; - } - return { ...msg, ...decoded }; - } - - private readonly incomingMessages: Message[] = [] - appendMessage(msg: Message, index: number) { - // @ts-ignore - // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ - //TODO: put index in message type - this.incomingMessages.push(msg) - if (!this.waitingForFiles) { - this.distributeMessage(msg, index) - } - } - - private distributeMessage(msg: Message, index: number): void { - const lastMessageTime = Math.max(msg.time, this.lastMessageTime) - this.lastMessageTime = lastMessageTime - this.state.update({ lastMessageTime }) - if (visualChanges.includes(msg.tp)) { - this.activityManager?.updateAcctivity(msg.time); - } - let decoded; - const time = msg.time; - switch (msg.tp) { - /* Lists: */ - case "console_log": - if (msg.level === 'debug') break; - this.lists.lists.log.append(Log({ - level: msg.level, - value: msg.value, - time, - index, - })) - break; - case "fetch": - this.lists.lists.fetch.append(Resource({ - method: msg.method, - url: msg.url, - payload: msg.request, - response: msg.response, - status: msg.status, - duration: msg.duration, - type: TYPES.FETCH, - time: msg.timestamp - this.sessionStart, //~ - index, - })); - break; - /* */ - case "set_page_location": - this.locationManager.append(msg); - if (msg.navigationStart > 0) { - this.loadedLocationManager.append(msg); - } - break; - case "set_viewport_size": - this.resizeManager.append(msg); - break; - case "mouse_move": - this.mouseMoveManager.append(msg); - break; - case "mouse_click": - this.clickManager.append(msg); - break; - case "set_viewport_scroll": - this.scrollManager.append(msg); - break; - case "performance_track": - this.performanceTrackManager.append(msg); - break; - case "set_page_visibility": - this.performanceTrackManager.handleVisibility(msg) - break; - case "connection_information": - this.connectionInfoManger.append(msg); - break; - case "o_table": - this.decoder.set(msg.key, msg.value); - break; - case "redux": - decoded = this.decodeStateMessage(msg, ["state", "action"]); - logger.log('redux', decoded) - if (decoded != null) { - this.lists.lists.redux.append(decoded); - } - break; - case "ng_rx": - decoded = this.decodeStateMessage(msg, ["state", "action"]); - logger.log('ngrx', decoded) - if (decoded != null) { - this.lists.lists.ngrx.append(decoded); - } - break; - case "vuex": - decoded = this.decodeStateMessage(msg, ["state", "mutation"]); - logger.log('vuex', decoded) - if (decoded != null) { - this.lists.lists.vuex.append(decoded); - } - break; - case "zustand": - decoded = this.decodeStateMessage(msg, ["state", "mutation"]) - logger.log('zustand', decoded) - if (decoded != null) { - this.lists.lists.zustand.append(decoded) - } - case "mob_x": - decoded = this.decodeStateMessage(msg, ["payload"]); - logger.log('mobx', decoded) - - if (decoded != null) { - this.lists.lists.mobx.append(decoded); - } - break; - case "graph_ql": - this.lists.lists.graphql.append(msg); - break; - case "profiler": - this.lists.lists.profiles.append(msg); - break; - default: - switch (msg.tp) { - case "create_document": - this.windowNodeCounter.reset(); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "create_text_node": - case "create_element_node": - this.windowNodeCounter.addNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "move_node": - this.windowNodeCounter.moveNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "remove_node": - this.windowNodeCounter.removeNode(msg.id); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - } - this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) - this.pagesManager.appendMessage(msg); - break; - } - } - - getLastMessageTime(): number { - return this.lastMessageTime; - } - - getFirstMessageTime(): number { - return this.pagesManager.minTime; - } - - - setMessagesLoading(messagesLoading: boolean) { - this.display(!messagesLoading); - this.state.update({ messagesLoading }); - } - - setCSSLoading(cssLoading: boolean) { - this.displayFrame(!cssLoading); - this.state.update({ cssLoading }); - } - - private setSize({ height, width }: { height: number, width: number }) { - this.scale({ height, width }); - this.state.update({ width, height }); - - //this.updateMarketTargets() - } - - // TODO: clean managers? - clean() { - this.state.update(INITIAL_STATE); - this.incomingMessages.length = 0 - } - -} diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 92c67fd7b..19df79c24 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -41,6 +41,7 @@ export interface State extends ScreenState, ListsState { connBandwidth?: number, location?: string, performanceChartTime?: number, + performanceAvaliability?: PerformanceTrackManager['avaliability'] domContentLoadedTime?: any, domBuildingTime?: any, @@ -279,7 +280,7 @@ export default class MessageManager { this.connectionInfoManger = new ListWalker(); this.clickManager = new ListWalker(); this.scrollManager = new ListWalker(); - this.resizeManager = new ListWalker([]); + this.resizeManager = new ListWalker(); this.performanceTrackManager = new PerformanceTrackManager() this.windowNodeCounter = new WindowNodeCounter(); @@ -350,8 +351,6 @@ export default class MessageManager { if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms this.screen.cursor.click(); } - // After all changes - redraw the marker - //this.marker.redraw(); }) if (this.waitingForFiles && this.lastMessageTime <= t) { @@ -375,8 +374,6 @@ export default class MessageManager { private readonly incomingMessages: Message[] = [] appendMessage(msg: Message, index: number) { - // @ts-ignore - // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ //TODO: put index in message type this.incomingMessages.push(msg) if (!this.waitingForFiles) { diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 4467bdef5..6c00b6c65 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -58,7 +58,7 @@ export default class WebPlayer extends Player { live, livePlay: live, - endTime, // : 0, //TODO: through initialState + endTime, // : 0, }) // TODO: separate LiveWebPlayer @@ -85,22 +85,24 @@ export default class WebPlayer extends Player { mark(e: Element) { this.inspectorController.marker?.mark(e) } - toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => any) { + + toggleInspectorMode(flag: boolean, clickCallback?: Parameters[0]) { if (typeof flag !== 'boolean') { const { inspectorMode } = this.wpState.get() - flag = !inspectorMode; + flag = !inspectorMode } if (flag) { this.pause() this.wpState.update({ inspectorMode: true }) - return this.inspectorController.enableInspector(clickCallback); + return this.inspectorController.enableInspector(clickCallback) } else { - this.inspectorController.disableInspector(); - this.wpState.update({ inspectorMode: false }); + this.inspectorController.disableInspector() + this.wpState.update({ inspectorMode: false }) } } + // Target Marker setActiveTarget(args: Parameters) { this.targetMarker.setActiveTarget(...args) } @@ -111,10 +113,10 @@ export default class WebPlayer extends Player { } - // TODO + // TODO separate message receivers async toggleTimetravel() { if (!this.wpState.get().liveTimeTravel) { - return await this.messageManager.reloadWithUnprocessedFile(() => + await this.messageManager.reloadWithUnprocessedFile(() => this.wpState.update({ liveTimeTravel: true, }) diff --git a/frontend/app/player/web/assist/ListWalkerWithMarks.ts b/frontend/app/player/web/assist/ListWalkerWithMarks.ts deleted file mode 100644 index 43c05382b..000000000 --- a/frontend/app/player/web/assist/ListWalkerWithMarks.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Timed } from './types'; -import ListWalker from './ListWalker' - - -type CheckFn = (t: T) => boolean - - -export default class ListWalkerWithMarks extends ListWalker { - private _markCountNow: number = 0 - private _markCount: number = 0 - constructor(private isMarked: CheckFn, initialList: T[] = []) { - super(initialList) - this._markCount = initialList.reduce((n, item) => isMarked(item) ? n+1 : n, 0) - } - - append(item: T) { - if (this.isMarked(item)) { this._markCount++ } - super.append(item) - } - - protected moveNext() { - const val = super.moveNext() - if (val && this.isMarked(val)) { - this._markCountNow++ - } - return val - } - protected movePrev() { - const val = super.movePrev() - if (val && this.isMarked(val)) { - this._markCountNow-- - } - return val - } - get markedCountNow(): number { - return this._markCountNow - } - get markedCount(): number { - return this._markCount - } - -}