From 0d21a39792dc8efbad2fdc31406dd4d314cea57a Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Fri, 12 May 2023 15:38:54 +0200 Subject: [PATCH] feat(player): add tabmanager --- frontend/app/player/web/TabManager.ts | 200 ++++++++++++++++++ .../web/messages/RawMessageReader.gen.ts | 2 + tracker/tracker/src/main/app/index.ts | 10 +- tracker/tracker/src/main/app/session.ts | 12 +- tracker/tracker/src/main/modules/tabs.ts | 1 + tracker/tracker/src/webworker/BatchWriter.ts | 8 + 6 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 frontend/app/player/web/TabManager.ts diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts new file mode 100644 index 000000000..f9b5ca59b --- /dev/null +++ b/frontend/app/player/web/TabManager.ts @@ -0,0 +1,200 @@ +import ListWalker from "Player/common/ListWalker"; +import { + ConnectionInformation, + Message, MType, + SetPageLocation, + SetViewportScroll, + SetViewportSize +} from "Player/web/messages"; +import PerformanceTrackManager from "Player/web/managers/PerformanceTrackManager"; +import WindowNodeCounter from "Player/web/managers/WindowNodeCounter"; +import PagesManager from "Player/web/managers/PagesManager"; +import { Decoder } from "syncod"; +import Lists, { InitialLists } from "Player/web/Lists"; +import type { Store } from '../common/types'; +import Screen from "Player/web/Screen/Screen"; +import { TYPES as EVENT_TYPES } from "Types/session/event"; +import MouseMoveManager from "Player/web/managers/MouseMoveManager"; +import ActivityManager from "Player/web/managers/ActivityManager"; +import { getResourceFromNetworkRequest, getResourceFromResourceTiming, Log, ResourceType } from "Player"; +import { isDOMType } from "Player/web/messages/filters.gen"; + +export default class TabManager { + 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 resizeManager: ListWalker = new ListWalker([]); + private pagesManager: PagesManager; + private scrollManager: ListWalker = new ListWalker(); + + public readonly decoder = new Decoder(); + private lists: Lists; + + + constructor( + private readonly session: any, + private readonly state: Store<{}>, + private readonly screen: Screen, + initialLists?: Partial + ) { + this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading) + this.lists = new Lists(initialLists) + initialLists?.event?.forEach((e: Record) => { // TODO: to one of "Moveable" module + if (e.type === EVENT_TYPES.LOCATION) { + this.locationEventManager.append(e); + } + }) + } + + 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); + } + }) + + this.state.update({ ...this.lists.getFullListsState() }); + } + + private setCSSLoading = (cssLoading: boolean) => { + this.screen.displayFrame(!cssLoading) + this.state.update({ cssLoading, ready: !this.state.get().messagesLoading && !cssLoading }) + } + + resetMessageManagers() { + this.locationEventManager = new ListWalker(); + this.locationManager = new ListWalker(); + this.loadedLocationManager = new ListWalker(); + this.connectionInfoManger = new ListWalker(); + this.scrollManager = new ListWalker(); + this.resizeManager = new ListWalker(); + + this.performanceTrackManager = new PerformanceTrackManager() + this.windowNodeCounter = new WindowNodeCounter(); + this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this.setCSSLoading) + } + + + distributeMessage(msg: Message): 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; + // console.log(msg) + switch (msg.tp) { + case MType.SetPageLocation: + this.locationManager.append(msg); + if (msg.navigationStart > 0) { + this.loadedLocationManager.append(msg); + } + break; + case MType.SetViewportSize: + this.resizeManager.append(msg); + break; + case MType.MouseThrashing: + this.mouseThrashingManager.append(msg); + break; + case MType.MouseMove: + this.mouseMoveManager.append(msg); + break; + case MType.MouseClick: + this.clickManager.append(msg); + break; + case MType.SetViewportScroll: + this.scrollManager.append(msg); + break; + case MType.PerformanceTrack: + this.performanceTrackManager.append(msg); + break; + case MType.SetPageVisibility: + this.performanceTrackManager.handleVisibility(msg) + break; + case MType.ConnectionInformation: + this.connectionInfoManger.append(msg); + break; + case MType.OTable: + this.decoder.set(msg.key, msg.value); + break; + /* Lists: */ + case MType.ConsoleLog: + if (msg.level === 'debug') break; + this.lists.lists.log.append( + // @ts-ignore : TODO: enums in the message schema + Log(msg) + ) + break; + 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) { + this.lists.lists.resource.insert(getResourceFromResourceTiming(msg, this.sessionStart)) + } + break; + case MType.Fetch: + case MType.NetworkRequest: + this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) + break; + case MType.Redux: + this.lists.lists.redux.append(msg); + break; + case MType.NgRx: + this.lists.lists.ngrx.append(msg); + break; + case MType.Vuex: + this.lists.lists.vuex.append(msg); + break; + case MType.Zustand: + this.lists.lists.zustand.append(msg) + break + case MType.MobX: + this.lists.lists.mobx.append(msg); + break; + case MType.GraphQl: + this.lists.lists.graphql.append(msg); + break; + case MType.Profiler: + this.lists.lists.profiles.append(msg); + break; + /* ===|=== */ + default: + switch (msg.tp) { + case MType.CreateDocument: + if (!this.firstVisualEventSet) { + this.state.update({ firstVisualEvent: msg.time }); + this.firstVisualEventSet = true; + } + this.windowNodeCounter.reset(); + 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); + break; + case MType.MoveNode: + this.windowNodeCounter.moveNode(msg.id, msg.parentID); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + case MType.RemoveNode: + this.windowNodeCounter.removeNode(msg.id); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + } + this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) + isDOMType(msg.tp) && this.pagesManager.appendMessage(msg) + break; + } + } +} \ No newline at end of file diff --git a/frontend/app/player/web/messages/RawMessageReader.gen.ts b/frontend/app/player/web/messages/RawMessageReader.gen.ts index 0fccc5564..3a18d019b 100644 --- a/frontend/app/player/web/messages/RawMessageReader.gen.ts +++ b/frontend/app/player/web/messages/RawMessageReader.gen.ts @@ -674,6 +674,7 @@ export default class RawMessageReader extends PrimitiveReader { } case 118: { + console.log('TabChange') const tabId = this.readString(); if (tabId === null) { return resetPointer() } return { tp: MType.TabChange, @@ -682,6 +683,7 @@ export default class RawMessageReader extends PrimitiveReader { } case 119: { + console.log('TabData') const tabId = this.readString(); if (tabId === null) { return resetPointer() } return { tp: MType.TabData, diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index ad10f1c7c..68bd2e13e 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -1,5 +1,5 @@ import type Message from './messages.gen.js' -import { Timestamp, Metadata, UserID, Type as MType } from './messages.gen.js' +import { Timestamp, Metadata, UserID, Type as MType, TabChange } from './messages.gen.js' import { now, adjustTimeOrigin, deprecationWarn } from '../utils.js' import Nodes from './nodes.js' import Observer from './observer/top_observer.js' @@ -463,6 +463,8 @@ export default class App { const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null this.sessionStorage.removeItem(this.options.session_reset_key) const needNewSessionID = startOpts.forceNew || lsReset || resetByWorker + const sessionToken = this.session.getSessionToken() + const isNewSession = needNewSessionID || !sessionToken return window .fetch(this.options.ingestPoint + '/v1/web/start', { @@ -474,7 +476,7 @@ export default class App { ...this.getTrackerInfo(), timestamp, userID: this.session.getInfo().userID, - token: needNewSessionID ? undefined : this.session.getSessionToken(), + token: isNewSession ? undefined : sessionToken, deviceMemory, jsHeapSizeLimit, }), @@ -526,6 +528,10 @@ export default class App { timestamp: startTimestamp || timestamp, projectID, }) + if (!isNewSession) { + console.log('continuing session on new tab', this.session.getTabId()) + this.send(TabChange(this.session.getTabId())) + } // (Re)send Metadata for the case of a new session Object.entries(this.session.getInfo().metadata).forEach(([key, value]) => this.send(Metadata(key, value)), diff --git a/tracker/tracker/src/main/app/session.ts b/tracker/tracker/src/main/app/session.ts index 614a2648b..54291cab3 100644 --- a/tracker/tracker/src/main/app/session.ts +++ b/tracker/tracker/src/main/app/session.ts @@ -23,7 +23,7 @@ export default class Session { private readonly callbacks: OnUpdateCallback[] = [] private timestamp = 0 private projectID: string | undefined - private readonly tabId: string + private tabId: string constructor(private readonly app: App, private readonly options: Options) { this.createTabId() @@ -123,11 +123,19 @@ export default class Session { } public getTabId(): string { + if (!this.tabId) this.createTabId() return this.tabId } private createTabId() { - this.app.sessionStorage.setItem(this.options.session_tabid_key, generateRandomId(16)) + const localId = this.app.sessionStorage.getItem(this.options.session_tabid_key) + if (localId) { + this.tabId = localId + } else { + const randomId = generateRandomId(12) + this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId) + this.tabId = randomId + } } getInfo(): SessionInfo { diff --git a/tracker/tracker/src/main/modules/tabs.ts b/tracker/tracker/src/main/modules/tabs.ts index 26281d844..1ede30685 100644 --- a/tracker/tracker/src/main/modules/tabs.ts +++ b/tracker/tracker/src/main/modules/tabs.ts @@ -3,6 +3,7 @@ import { TabChange } from '../app/messages.gen.js' export default function (app: App): void { function changeTab() { + console.log(!document.hidden, app.session.getTabId()) if (!document.hidden) app.safe(() => app.send(TabChange(app.session.getTabId()))) } diff --git a/tracker/tracker/src/webworker/BatchWriter.ts b/tracker/tracker/src/webworker/BatchWriter.ts index d3f2938ac..ce68b8dbb 100644 --- a/tracker/tracker/src/webworker/BatchWriter.ts +++ b/tracker/tracker/src/webworker/BatchWriter.ts @@ -53,6 +53,14 @@ export default class BatchWriter { this.url, this.tabId, ] + console.log('meta', { + pageNo: this.pageNo, + nextIndex: this.nextIndex, + timestamp: this.timestamp, + url: this.url, + tabId: this.tabId, + }) + this.writeType(batchMetadata) this.writeFields(batchMetadata) this.isEmpty = true