openreplay/frontend/app/player/web/WebPlayer.ts
Delirium 2ed4bba33e
feat(tracker/ui): support for multi tab sessions (#1236)
* feat(tracker): add support for multi tab sessions

* feat(backend): added support of multitabs

* fix(backend): added support of deprecated batch meta message to pre-decoder

* fix(backend): fixed nil meta issue for TabData messages in sink

* feat(player): add tabmanager

* feat(player): basic tabchange event support

* feat(player): pick tabstate for console panel and timeline

* fix(player): only display tabs that are created

* feat(player): connect performance, xray and events to tab state

* feat(player): merge all tabs data for overview

* feat(backend/tracker): extract tabdata into separate message from batchmeta

* fix(tracker): fix new session check

* fix(backend): remove batchmetadeprecated

* fix(backend): fix switch case

* fix(player): fix for tab message size

* feat(tracker): check for active tabs with broadcast channel

* feat(tracker): prevent multiple messages

* fix(tracker): ignore beacons from same tab, only ask if token isnt present yet, add small delay before start to wait for answer

* feat(player): support new msg struct in assist player

* fix(player): fix some livepl components for multi tab states

* feat(tracker): add option to disable multitab

* feat(tracker): add multitab to assist plugin

* feat(player): back compat for tab id

* fix(ui): fix missing list in controls

* fix(ui): optional list update

* feat(ui): fix visuals for multitab; use window focus event for tabs

* fix(tracker): fix for dying tests (added tabid to writer, refactored other tests)

* feat(ui): update LivePlayerSubHeader.tsx to support tabs

* feat(backend): added tabs support to devtools mob files

* feat(ui): connect state to current tab properly

* feat(backend): added multitab support to assits

* feat(backend): removed data check in agent message

* feat(backend): debug on

* fix(backend): fixed typo in message broadcast

* feat(backend): fixed issue in connect method

* fix(assist): fixed typo

* feat(assist): added more debug logs

* feat(assist): removed one log

* feat(assist): more logs

* feat(assist): use query.peerId

* feat(assist): more logs

* feat(assist): fixed session update

* fix(assist): fixed getSessions

* fix(assist): fixed request_control broadcast

* fix(assist): fixed typo

* fix(assist): added missed line

* fix(assist): fix typo

* feat(tracker): multitab support for assist sessions

* fix(tracker): fix dead tests (tabid prop)

* fix(tracker): fix yaml

* fix(tracker): timers issue

* fix(ui): fix ui E2E tests with magic?

* feat(assist): multitabs support for ee version

* fix(assist): added missed method import

* fix(tracker): fix fix events in assist

* feat(assist): added back compatibility for sessions without tabId

* fix(assist): apply message's top layer structure before broadcast call

* fix(assist): added random tabID for prev version

* fix(assist): added random tabID for prev version (ee)

* feat(assist): added debug logs

* fix(assist): fix typo in sessions_agents_count method

* fix(assist): fixed more typos in copy-pastes

* fix(tracker): fix restart timings

* feat(backend): added tabIDs for some events

* feat(ui): add tab change event to the user steps bar

* Revert "feat(backend): added tabIDs for some events"

This reverts commit 1467ad7f9f.

* feat(ui): revert timeline and xray to grab events from all tabs

* fix(ui): fix typo

---------

Co-authored-by: Alexander Zavorotynskiy <zavorotynskiy@pm.me>
2023-06-07 10:40:32 +02:00

180 lines
5.1 KiB
TypeScript

import { Log, LogLevel, SessionFilesInfo } from 'App/player'
import type { Store } from 'App/player'
import Player from '../player/Player'
import MessageManager from './MessageManager'
import MessageLoader from './MessageLoader'
import InspectorController from './addons/InspectorController'
import TargetMarker from './addons/TargetMarker'
import Screen, { ScaleMode } from './Screen/Screen'
import { Message } from "Player/web/messages";
export default class WebPlayer extends Player {
static readonly INITIAL_STATE = {
...Player.INITIAL_STATE,
...TargetMarker.INITIAL_STATE,
...MessageManager.INITIAL_STATE,
...MessageLoader.INITIAL_STATE,
inspectorMode: false,
}
private readonly inspectorController: InspectorController
protected screen: Screen
protected readonly messageManager: MessageManager
protected readonly messageLoader: MessageLoader
private targetMarker: TargetMarker
constructor(
protected wpState: Store<typeof WebPlayer.INITIAL_STATE>,
session: SessionFilesInfo,
live: boolean,
isClickMap = false,
public readonly uiErrorHandler?: { error: (msg: string) => void }
) {
let initialLists = live ? {} : {
event: session.events || [],
stack: session.stackEvents || [],
frustrations: session.frustrations || [],
exceptions: session.errors?.map(({ name, ...rest }: any) =>
Log({
level: LogLevel.ERROR,
value: name,
...rest,
})
) || [],
}
const screen = new Screen(session.isMobile, isClickMap ? ScaleMode.AdjustParentHeight : ScaleMode.Embed)
const messageManager = new MessageManager(session, wpState, screen, initialLists, uiErrorHandler)
const messageLoader = new MessageLoader(
session,
wpState,
messageManager,
isClickMap,
uiErrorHandler
)
super(wpState, messageManager)
this.screen = screen
this.messageManager = messageManager
this.messageLoader = messageLoader
if (!live) { // hack. TODO: split OfflinePlayer class
void messageLoader.loadFiles()
}
this.targetMarker = new TargetMarker(this.screen, wpState)
this.inspectorController = new InspectorController(screen)
const endTime = session.duration?.valueOf() || 0
wpState.update({
//@ts-ignore
session,
live,
livePlay: live,
endTime, // : 0,
})
// @ts-ignore
window.playerJumpToTime = this.jump.bind(this)
}
updateLists = (session: any) => {
const lists = {
event: session.events || [],
frustrations: session.frustrations || [],
stack: session.stackEvents || [],
exceptions: session.errors?.map(({ name, ...rest }: any) =>
Log({
level: LogLevel.ERROR,
value: name,
...rest,
})
) || [],
}
this.messageManager.updateLists(lists)
}
attach = (parent: HTMLElement, isClickmap?: boolean) => {
this.screen.attach(parent)
if (!isClickmap) {
window.addEventListener('resize', this.scale)
this.scale()
}
}
scale = () => {
const { width, height } = this.wpState.get()
if (!this.screen && !this.inspectorController) return;
// sometimes happens in live assist sessions for some reason
this.screen?.scale?.({ width, height })
this.inspectorController?.scale?.({ width, height })
this.targetMarker.updateMarkedTargets()
}
// delayed message decoding for state plugins
decodeMessage = (msg: Message) => {
return this.messageManager.decodeMessage(msg)
}
// Inspector & marker
mark(e: Element) {
this.inspectorController.marker?.mark(e)
}
toggleInspectorMode = (flag: boolean, clickCallback?: Parameters<InspectorController['enableInspector']>[0]) => {
if (typeof flag !== 'boolean') {
const { inspectorMode } = this.wpState.get()
flag = !inspectorMode
}
if (flag) {
this.pause()
this.wpState.update({ inspectorMode: true })
return this.inspectorController.enableInspector(clickCallback)
} else {
this.inspectorController.disableInspector()
this.wpState.update({ inspectorMode: false })
}
}
// Target Marker
setActiveTarget = (...args: Parameters<TargetMarker['setActiveTarget']>) => {
this.targetMarker.setActiveTarget(...args)
}
markTargets = (...args: Parameters<TargetMarker['markTargets']>) => {
this.pause()
this.targetMarker.markTargets(...args)
}
showClickmap = (...args: Parameters<TargetMarker['injectTargets']>) => {
this.screen?.overlay?.remove?.() // hack. TODO: 1.split Screen functionalities (overlay, mounter) 2. separate ClickMapPlayer class that does not create overlay
this.freeze().then(() => {
this.targetMarker.injectTargets(...args)
})
}
toggleUserName = (name?: string) => {
this.screen.cursor.showTag(name)
}
changeTab = (tab: string) => {
this.messageManager.changeTab(tab)
}
clean = () => {
super.clean()
this.screen.clean()
// @ts-ignore
this.screen = undefined;
this.messageLoader.clean()
// @ts-ignore
this.messageManager = undefined;
window.removeEventListener('resize', this.scale)
}
}