fix(ui): refactor state types, prep to integrate lscache, fix session types
This commit is contained in:
parent
0dea805366
commit
cf9cad7f75
12 changed files with 213 additions and 181 deletions
|
|
@ -458,7 +458,6 @@ function Performance({
|
||||||
const availableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0);
|
const availableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0);
|
||||||
const height = availableCount === 0 ? '0' : `${100 / availableCount}%`;
|
const height = availableCount === 0 ? '0' : `${100 / availableCount}%`;
|
||||||
|
|
||||||
console.log(_data)
|
|
||||||
return (
|
return (
|
||||||
<BottomBlock>
|
<BottomBlock>
|
||||||
<BottomBlock.Header>
|
<BottomBlock.Header>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Store } from './types'
|
import { Store } from './types'
|
||||||
|
|
||||||
export default class SimpleSore<G, S=G> implements Store<G, S> {
|
export default class SimpleSore<G extends Object, S extends Object = G> implements Store<G, S> {
|
||||||
constructor(private state: G){}
|
constructor(private state: G){}
|
||||||
get(): G {
|
get(): G {
|
||||||
return this.state
|
return this.state
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export interface Timed {
|
import { Message } from "Player/web/messages";
|
||||||
|
|
||||||
|
export interface Timed {
|
||||||
time: number
|
time: number
|
||||||
/** present in mobile events and in db events */
|
/** present in mobile events and in db events */
|
||||||
timestamp?: number
|
timestamp?: number
|
||||||
|
|
@ -33,7 +35,10 @@ export interface SessionFilesInfo {
|
||||||
sessionId: string
|
sessionId: string
|
||||||
isMobile: boolean
|
isMobile: boolean
|
||||||
agentToken?: string
|
agentToken?: string
|
||||||
duration: number
|
duration: {
|
||||||
|
milliseconds: number
|
||||||
|
valueOf: () => number
|
||||||
|
}
|
||||||
domURL: string[]
|
domURL: string[]
|
||||||
devtoolsURL: string[]
|
devtoolsURL: string[]
|
||||||
/** deprecated */
|
/** deprecated */
|
||||||
|
|
@ -44,4 +49,6 @@ export interface SessionFilesInfo {
|
||||||
frustrations: Record<string, any>[]
|
frustrations: Record<string, any>[]
|
||||||
errors: Record<string, any>[]
|
errors: Record<string, any>[]
|
||||||
agentInfo?: { email: string, name: string }
|
agentInfo?: { email: string, name: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PlayerMsg = Message & { tabId: string }
|
||||||
|
|
@ -1,63 +1,64 @@
|
||||||
import * as lstore from './localStorage'
|
import * as lstore from './localStorage';
|
||||||
|
import SimpleStore from 'App/player/common/SimpleStore';
|
||||||
|
|
||||||
import type { SimpleState } from './PlayerState'
|
const SPEED_STORAGE_KEY = '__$player-speed$__';
|
||||||
|
const SKIP_STORAGE_KEY = '__$player-skip$__';
|
||||||
|
const SKIP_TO_ISSUE_STORAGE_KEY = '__$session-skipToIssue$__';
|
||||||
|
const AUTOPLAY_STORAGE_KEY = '__$player-autoplay$__';
|
||||||
|
const SHOW_EVENTS_STORAGE_KEY = '__$player-show-events$__';
|
||||||
|
|
||||||
const SPEED_STORAGE_KEY = "__$player-speed$__";
|
const storedSpeed = lstore.number(SPEED_STORAGE_KEY, 1);
|
||||||
const SKIP_STORAGE_KEY = "__$player-skip$__";
|
const initialSpeed = [0.5, 1, 2, 4, 8, 16].includes(storedSpeed) ? storedSpeed : 1;
|
||||||
const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__";
|
const initialSkip = lstore.boolean(SKIP_STORAGE_KEY);
|
||||||
const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__";
|
const initialSkipToIssue = lstore.boolean(SKIP_TO_ISSUE_STORAGE_KEY);
|
||||||
const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__";
|
const initialAutoplay = lstore.boolean(AUTOPLAY_STORAGE_KEY);
|
||||||
|
const initialShowEvents = lstore.boolean(SHOW_EVENTS_STORAGE_KEY);
|
||||||
|
|
||||||
|
const INITIAL_STATE = {
|
||||||
const storedSpeed = lstore.number(SPEED_STORAGE_KEY, 1)
|
|
||||||
const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1;
|
|
||||||
const initialSkip = lstore.boolean(SKIP_STORAGE_KEY)
|
|
||||||
const initialSkipToIssue = lstore.boolean(SKIP_TO_ISSUE_STORAGE_KEY)
|
|
||||||
const initialAutoplay = lstore.boolean(AUTOPLAY_STORAGE_KEY)
|
|
||||||
const initialShowEvents = lstore.boolean(SHOW_EVENTS_STORAGE_KEY)
|
|
||||||
|
|
||||||
export const INITIAL_STATE = {
|
|
||||||
skipToIssue: initialSkipToIssue,
|
skipToIssue: initialSkipToIssue,
|
||||||
autoplay: initialAutoplay,
|
autoplay: initialAutoplay,
|
||||||
showEvents: initialShowEvents,
|
showEvents: initialShowEvents,
|
||||||
skip: initialSkip,
|
skip: initialSkip,
|
||||||
speed: initialSpeed,
|
speed: initialSpeed,
|
||||||
}
|
};
|
||||||
|
|
||||||
const KEY_MAP = {
|
const KEY_MAP = {
|
||||||
speed: SPEED_STORAGE_KEY,
|
skipToIssue: SKIP_TO_ISSUE_STORAGE_KEY,
|
||||||
skip: SKIP_STORAGE_KEY,
|
autoplay: AUTOPLAY_STORAGE_KEY,
|
||||||
skipToIssue: SKIP_TO_ISSUE_STORAGE_KEY,
|
showEvents: SHOW_EVENTS_STORAGE_KEY,
|
||||||
autoplay: AUTOPLAY_STORAGE_KEY,
|
skip: SKIP_STORAGE_KEY,
|
||||||
showEvents: SHOW_EVENTS_STORAGE_KEY,
|
speed: SPEED_STORAGE_KEY,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
const keys = Object.keys(KEY_MAP) as (keyof typeof KEY_MAP)[];
|
||||||
|
const booleanKeys = ['skipToIssue', 'autoplay', 'showEvents', 'skip'] as const;
|
||||||
|
type LSCState = typeof INITIAL_STATE
|
||||||
|
|
||||||
|
export default class LSCache {
|
||||||
|
static readonly INITIAL_STATE = INITIAL_STATE;
|
||||||
|
private readonly state: SimpleStore<typeof LSCache.INITIAL_STATE>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.state = new SimpleStore<typeof LSCache.INITIAL_STATE>(LSCache.INITIAL_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(newState: Partial<LSCState>) {
|
||||||
|
for (let [k, v] of Object.entries(newState) as [keyof LSCState, LSCState[keyof LSCState]][]) {
|
||||||
|
if (k in keys) {
|
||||||
|
localStorage.setItem(KEY_MAP[k], String(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.state.update(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(key: typeof booleanKeys[number]) {
|
||||||
|
// @ts-ignore TODO: nice typing
|
||||||
|
this.update({
|
||||||
|
[key]: !this.get()[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.state.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type KeysOfBoolean<T> = keyof T & keyof { [ K in keyof T as T[K] extends boolean ? K : never ] : K };
|
|
||||||
|
|
||||||
type Entries<T> = {
|
|
||||||
[K in keyof T]: [K, T[K]];
|
|
||||||
}[keyof T][];
|
|
||||||
|
|
||||||
export default class LSCache<G extends Record<string, boolean | number | string>> {
|
|
||||||
constructor(private state: SimpleState<G>, private keyMap: Record<keyof Partial<G>, string>) {
|
|
||||||
}
|
|
||||||
update(newState: Partial<G>) {
|
|
||||||
for (let [k, v] of Object.entries(newState) as Entries<Partial<G>>) {
|
|
||||||
if (k in this.keyMap) {
|
|
||||||
// @ts-ignore TODO: nice typing
|
|
||||||
//lstore[typeof v](this.keyMap[k], v)
|
|
||||||
localStorage.setItem(this.keyMap[k], String(v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.state.update(newState)
|
|
||||||
}
|
|
||||||
toggle(key: KeysOfBoolean<G>) {
|
|
||||||
// @ts-ignore TODO: nice typing
|
|
||||||
this.update({
|
|
||||||
[key]: !this.get()[key]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
get() {
|
|
||||||
return this.state.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import type { Store, SessionFilesInfo } from 'Player';
|
import type { Store, SessionFilesInfo, PlayerMsg } from "Player";
|
||||||
import { decryptSessionBytes } from './network/crypto';
|
import { decryptSessionBytes } from './network/crypto';
|
||||||
import MFileReader from './messages/MFileReader';
|
import MFileReader from './messages/MFileReader';
|
||||||
import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles';
|
import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles';
|
||||||
import type { Message } from './messages';
|
|
||||||
import logger from 'App/logger';
|
import logger from 'App/logger';
|
||||||
import unpack from 'Player/common/unpack';
|
import unpack from 'Player/common/unpack';
|
||||||
import MessageManager from 'Player/web/MessageManager';
|
import MessageManager from 'Player/web/MessageManager';
|
||||||
|
|
@ -33,55 +32,58 @@ export default class MessageLoader {
|
||||||
|
|
||||||
createNewParser(
|
createNewParser(
|
||||||
shouldDecrypt = true,
|
shouldDecrypt = true,
|
||||||
|
onMessagesDone: (msgs: PlayerMsg[], file?: string) => void,
|
||||||
file?: string
|
file?: string
|
||||||
) {
|
) {
|
||||||
const decrypt =
|
const decrypt =
|
||||||
shouldDecrypt && this.session.fileKey
|
shouldDecrypt && this.session.fileKey
|
||||||
? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!)
|
? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!)
|
||||||
: (b: Uint8Array) => Promise.resolve(b);
|
: (b: Uint8Array) => Promise.resolve(b);
|
||||||
// Each time called - new fileReader created
|
|
||||||
const fileReader = new MFileReader(new Uint8Array(), this.session.startedAt);
|
const fileReader = new MFileReader(new Uint8Array(), this.session.startedAt);
|
||||||
return (b: Uint8Array) => {
|
return async (b: Uint8Array) => {
|
||||||
return decrypt(b)
|
try {
|
||||||
.then((b) => {
|
const mobBytes = await decrypt(b);
|
||||||
const data = unpack(b);
|
const data = unpack(mobBytes);
|
||||||
fileReader.append(data);
|
fileReader.append(data);
|
||||||
fileReader.checkForIndexes();
|
fileReader.checkForIndexes();
|
||||||
const msgs: Array<Message & { tabId: string }> = [];
|
const msgs: Array<PlayerMsg> = [];
|
||||||
let finished = false;
|
let finished = false;
|
||||||
while (!finished) {
|
while (!finished) {
|
||||||
const msg = fileReader.readNext();
|
const msg = fileReader.readNext();
|
||||||
if (msg) {
|
if (msg) {
|
||||||
msgs.push(msg);
|
msgs.push(msg);
|
||||||
} else {
|
} else {
|
||||||
finished = true;
|
finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const sortedMessages = msgs.sort((m1, m2) => {
|
const sortedMsgs = msgs.sort((m1, m2) => {
|
||||||
return m1.time - m2.time;
|
return m1.time - m2.time;
|
||||||
});
|
|
||||||
|
|
||||||
sortedMessages.forEach((msg) => {
|
|
||||||
this.messageManager.distributeMessage(msg);
|
|
||||||
});
|
|
||||||
logger.info('Messages count: ', msgs.length, sortedMessages, file);
|
|
||||||
|
|
||||||
this.messageManager.sortDomRemoveMessages(sortedMessages);
|
|
||||||
this.messageManager.setMessagesLoading(false);
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
this.uiErrorHandler?.error('Error parsing file: ' + e.message);
|
|
||||||
});
|
});
|
||||||
|
onMessagesDone(sortedMsgs, file);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
this.uiErrorHandler?.error('Error parsing file: ' + e.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDomFiles(urls: string[], parser: (b: Uint8Array) => Promise<void>) {
|
processMessages = (msgs: PlayerMsg[], file?: string) => {
|
||||||
|
msgs.forEach((msg) => {
|
||||||
|
this.messageManager.distributeMessage(msg);
|
||||||
|
});
|
||||||
|
logger.info('Messages count: ', msgs.length, msgs, file);
|
||||||
|
|
||||||
|
this.messageManager.sortDomRemoveMessages(msgs);
|
||||||
|
this.messageManager.setMessagesLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
async loadDomFiles(urls: string[], parser: (b: Uint8Array) => Promise<void>) {
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
this.store.update({ domLoading: true });
|
this.store.update({ domLoading: true });
|
||||||
return loadFiles(urls, parser, true).then(() => this.store.update({ domLoading: false }));
|
await loadFiles(urls, parser, true);
|
||||||
|
return this.store.update({ domLoading: false });
|
||||||
} else {
|
} else {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
@ -111,11 +113,17 @@ export default class MessageLoader {
|
||||||
|
|
||||||
const loadMethod =
|
const loadMethod =
|
||||||
this.session.domURL && this.session.domURL.length > 0
|
this.session.domURL && this.session.domURL.length > 0
|
||||||
? { mobUrls: this.session.domURL, parser: () => this.createNewParser(true, 'dom') }
|
? {
|
||||||
: { mobUrls: this.session.mobsUrl, parser: () => this.createNewParser(false, 'dom') };
|
mobUrls: this.session.domURL,
|
||||||
|
parser: () => this.createNewParser(true, this.processMessages, 'dom'),
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
mobUrls: this.session.mobsUrl,
|
||||||
|
parser: () => this.createNewParser(false, this.processMessages, 'dom'),
|
||||||
|
};
|
||||||
|
|
||||||
const parser = loadMethod.parser();
|
const parser = loadMethod.parser();
|
||||||
const devtoolsParser = this.createNewParser(true, 'devtools');
|
const devtoolsParser = this.createNewParser(true, this.processMessages, 'devtools');
|
||||||
/**
|
/**
|
||||||
* to speed up time to replay
|
* to speed up time to replay
|
||||||
* we load first dom mob file before the rest
|
* we load first dom mob file before the rest
|
||||||
|
|
@ -140,8 +148,8 @@ export default class MessageLoader {
|
||||||
efsDomFilePromise,
|
efsDomFilePromise,
|
||||||
efsDevtoolsFilePromise,
|
efsDevtoolsFilePromise,
|
||||||
]);
|
]);
|
||||||
const domParser = this.createNewParser(false, 'domEFS');
|
const domParser = this.createNewParser(false, this.processMessages, 'domEFS');
|
||||||
const devtoolsParser = this.createNewParser(false, 'devtoolsEFS');
|
const devtoolsParser = this.createNewParser(false, this.processMessages, 'devtoolsEFS');
|
||||||
const parseDomPromise: Promise<any> =
|
const parseDomPromise: Promise<any> =
|
||||||
domData.status === 'fulfilled'
|
domData.status === 'fulfilled'
|
||||||
? domParser(domData.value)
|
? domParser(domData.value)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { Decoder } from 'syncod';
|
import { Decoder } from 'syncod';
|
||||||
import logger from 'App/logger';
|
import logger from 'App/logger';
|
||||||
|
|
||||||
import type { Store, ILog } from 'Player';
|
import type { Store, ILog, SessionFilesInfo } from 'Player';
|
||||||
import ListWalker from '../common/ListWalker';
|
import ListWalker from '../common/ListWalker';
|
||||||
|
|
||||||
import MouseMoveManager from './managers/MouseMoveManager';
|
import MouseMoveManager from './managers/MouseMoveManager';
|
||||||
|
|
@ -108,7 +108,7 @@ export default class MessageManager {
|
||||||
private activeTab = '';
|
private activeTab = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly session: Record<string, any>,
|
private readonly session: SessionFilesInfo,
|
||||||
private readonly state: Store<State & { time: number }>,
|
private readonly state: Store<State & { time: number }>,
|
||||||
private readonly screen: Screen,
|
private readonly screen: Screen,
|
||||||
private readonly initialLists?: Partial<InitialLists>,
|
private readonly initialLists?: Partial<InitialLists>,
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,9 @@ export default class TabSessionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Because we use main state (from messageManager), we have to update it this way
|
||||||
|
* */
|
||||||
updateLocalState(state: Partial<TabState>) {
|
updateLocalState(state: Partial<TabState>) {
|
||||||
this.state.update({
|
this.state.update({
|
||||||
tabStates: {
|
tabStates: {
|
||||||
|
|
@ -283,22 +286,23 @@ export default class TabSessionManager {
|
||||||
// TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time)
|
// TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time)
|
||||||
this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart;
|
this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart;
|
||||||
}
|
}
|
||||||
const llEvent = this.locationEventManager.moveGetLast(t, index);
|
const lastLocationEvent = this.locationEventManager.moveGetLast(t, index);
|
||||||
if (!!llEvent) {
|
if (!!lastLocationEvent) {
|
||||||
if (llEvent.domContentLoadedTime != null) {
|
if (lastLocationEvent.domContentLoadedTime != null) {
|
||||||
stateToUpdate.domContentLoadedTime = {
|
stateToUpdate.domContentLoadedTime = {
|
||||||
time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db)
|
time: lastLocationEvent.domContentLoadedTime + this.navigationStartOffset,
|
||||||
value: llEvent.domContentLoadedTime,
|
// TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db)
|
||||||
|
value: lastLocationEvent.domContentLoadedTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (llEvent.loadTime != null) {
|
if (lastLocationEvent.loadTime != null) {
|
||||||
stateToUpdate.loadTime = {
|
stateToUpdate.loadTime = {
|
||||||
time: llEvent.loadTime + this.navigationStartOffset,
|
time: lastLocationEvent.loadTime + this.navigationStartOffset,
|
||||||
value: llEvent.loadTime,
|
value: lastLocationEvent.loadTime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (llEvent.domBuildingTime != null) {
|
if (lastLocationEvent.domBuildingTime != null) {
|
||||||
stateToUpdate.domBuildingTime = llEvent.domBuildingTime;
|
stateToUpdate.domBuildingTime = lastLocationEvent.domBuildingTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* === */
|
/* === */
|
||||||
|
|
@ -307,12 +311,7 @@ export default class TabSessionManager {
|
||||||
// @ts-ignore comes from parent state
|
// @ts-ignore comes from parent state
|
||||||
this.state.update({ location: lastLocationMsg.url });
|
this.state.update({ location: lastLocationMsg.url });
|
||||||
}
|
}
|
||||||
// ConnectionInformation message is not used at this moment
|
|
||||||
// const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index);
|
|
||||||
// if (!!lastConnectionInfoMsg) {
|
|
||||||
// stateToUpdate.connType = lastConnectionInfoMsg.type;
|
|
||||||
// stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink;
|
|
||||||
// }
|
|
||||||
const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index);
|
const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index);
|
||||||
if (!!lastPerformanceTrackMessage) {
|
if (!!lastPerformanceTrackMessage) {
|
||||||
stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time;
|
stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time;
|
||||||
|
|
@ -347,6 +346,9 @@ export default class TabSessionManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to decode state messages, because they can be large we only want to decode whats rendered atm
|
||||||
|
* */
|
||||||
public decodeMessage(msg: Message) {
|
public decodeMessage(msg: Message) {
|
||||||
return this.decoder.decode(msg);
|
return this.decoder.decode(msg);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,19 @@
|
||||||
import type { Store, SessionFilesInfo } from 'Player'
|
import type { Store, SessionFilesInfo, PlayerMsg } from 'Player';
|
||||||
import type { Message } from './messages'
|
import WebPlayer from './WebPlayer';
|
||||||
|
import AssistManager from './assist/AssistManager';
|
||||||
import WebPlayer from './WebPlayer'
|
import { requestEFSDom } from './network/loadFiles';
|
||||||
import AssistManager from './assist/AssistManager'
|
|
||||||
|
|
||||||
import MFileReader from './messages/MFileReader'
|
|
||||||
import { requestEFSDom } from './network/loadFiles'
|
|
||||||
|
|
||||||
export default class WebLivePlayer extends WebPlayer {
|
export default class WebLivePlayer extends WebPlayer {
|
||||||
static readonly INITIAL_STATE = {
|
static readonly INITIAL_STATE = {
|
||||||
...WebPlayer.INITIAL_STATE,
|
...WebPlayer.INITIAL_STATE,
|
||||||
...AssistManager.INITIAL_STATE,
|
...AssistManager.INITIAL_STATE,
|
||||||
liveTimeTravel: false,
|
liveTimeTravel: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
assistManager: AssistManager // public so far
|
assistManager: AssistManager; // public so far
|
||||||
private readonly incomingMessages: Message[] = []
|
private readonly incomingMessages: PlayerMsg[] = [];
|
||||||
private historyFileIsLoading = false
|
private historyFileIsLoading = false;
|
||||||
private lastMessageInFileTime = 0
|
private lastMessageInFileTime = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
wpState: Store<typeof WebLivePlayer.INITIAL_STATE>,
|
wpState: Store<typeof WebLivePlayer.INITIAL_STATE>,
|
||||||
|
|
@ -25,79 +21,91 @@ export default class WebLivePlayer extends WebPlayer {
|
||||||
config: RTCIceServer[] | null,
|
config: RTCIceServer[] | null,
|
||||||
agentId: number,
|
agentId: number,
|
||||||
projectId: number,
|
projectId: number,
|
||||||
uiErrorHandler?: { error: (msg: string) => void },
|
uiErrorHandler?: { error: (msg: string) => void }
|
||||||
) {
|
) {
|
||||||
super(wpState, session, true, false, uiErrorHandler)
|
super(wpState, session, true, false, uiErrorHandler);
|
||||||
|
|
||||||
this.assistManager = new AssistManager(
|
this.assistManager = new AssistManager(
|
||||||
session,
|
session,
|
||||||
f => this.messageManager.setMessagesLoading(f),
|
(f) => this.messageManager.setMessagesLoading(f),
|
||||||
(msg) => {
|
(msg) => {
|
||||||
this.incomingMessages.push(msg)
|
this.incomingMessages.push(msg);
|
||||||
if (!this.historyFileIsLoading) {
|
if (!this.historyFileIsLoading) {
|
||||||
// TODO: fix index-ing after historyFile-load
|
// TODO: fix index-ing after historyFile-load
|
||||||
this.messageManager.distributeMessage(msg)
|
this.messageManager.distributeMessage(msg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.screen,
|
this.screen,
|
||||||
config,
|
config,
|
||||||
wpState,
|
wpState,
|
||||||
(id) => this.messageManager.getNode(id),
|
(id) => this.messageManager.getNode(id),
|
||||||
uiErrorHandler,
|
uiErrorHandler
|
||||||
)
|
);
|
||||||
this.assistManager.connect(session.agentToken!, agentId, projectId)
|
this.assistManager.connect(session.agentToken!, agentId, projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads in-progress dom file from EFS directly
|
||||||
|
* then reads it to add everything happened before "now" to message manager
|
||||||
|
* to be able to replay it like usual
|
||||||
|
* */
|
||||||
toggleTimetravel = async () => {
|
toggleTimetravel = async () => {
|
||||||
if (this.wpState.get().liveTimeTravel) {
|
if ((this.wpState.get() as typeof WebLivePlayer.INITIAL_STATE).liveTimeTravel) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
let result = false;
|
let result = false;
|
||||||
this.historyFileIsLoading = true
|
this.historyFileIsLoading = true;
|
||||||
this.messageManager.setMessagesLoading(true) // do it in one place. update unique loading states each time instead
|
this.messageManager.setMessagesLoading(true); // do it in one place. update unique loading states each time instead
|
||||||
this.messageManager.resetMessageManagers()
|
this.messageManager.resetMessageManagers();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bytes = await requestEFSDom(this.session.sessionId)
|
const bytes = await requestEFSDom(this.session.sessionId);
|
||||||
const fileReader = new MFileReader(bytes, this.session.startedAt)
|
const reader = this.messageLoader.createNewParser(
|
||||||
for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) {
|
false,
|
||||||
this.messageManager.distributeMessage(msg)
|
(msgs) => {
|
||||||
}
|
msgs.forEach((msg) => {
|
||||||
|
this.messageManager.distributeMessage(msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'cobrowse dom'
|
||||||
|
);
|
||||||
|
await reader(bytes);
|
||||||
|
|
||||||
this.wpState.update({
|
this.wpState.update({
|
||||||
liveTimeTravel: true,
|
liveTimeTravel: true,
|
||||||
})
|
});
|
||||||
result = true
|
result = true;
|
||||||
// here we need to update also lists state, if we gonna use them this.messageManager.onFileReadSuccess
|
// here we need to update also lists state, if we're going use them this.messageManager.onFileReadSuccess
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
this.uiErrorHandler?.error('Error requesting a session file')
|
this.uiErrorHandler?.error('Error requesting a session file');
|
||||||
console.error("EFS file download error:", e)
|
console.error('EFS file download error:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append previously received messages
|
// Append previously received messages
|
||||||
this.incomingMessages
|
this.incomingMessages
|
||||||
.filter(msg => msg.time >= this.lastMessageInFileTime)
|
.filter((msg) => msg.time >= this.lastMessageInFileTime)
|
||||||
.forEach((msg) => this.messageManager.distributeMessage(msg))
|
.forEach((msg) => this.messageManager.distributeMessage(msg));
|
||||||
this.incomingMessages.length = 0
|
this.incomingMessages.length = 0;
|
||||||
|
|
||||||
this.historyFileIsLoading = false
|
this.historyFileIsLoading = false;
|
||||||
this.messageManager.setMessagesLoading(false)
|
this.messageManager.setMessagesLoading(false);
|
||||||
return result;
|
return result;
|
||||||
}
|
};
|
||||||
|
|
||||||
jumpToLive = () => {
|
jumpToLive = () => {
|
||||||
this.wpState.update({
|
this.wpState.update({
|
||||||
live: true,
|
live: true,
|
||||||
livePlay: true,
|
livePlay: true,
|
||||||
})
|
});
|
||||||
this.jump(this.wpState.get().lastMessageTime)
|
this.jump(this.wpState.get().lastMessageTime);
|
||||||
}
|
};
|
||||||
|
|
||||||
clean = () => {
|
clean = () => {
|
||||||
this.incomingMessages.length = 0
|
this.incomingMessages.length = 0;
|
||||||
this.assistManager.clean()
|
this.assistManager.clean();
|
||||||
this.screen?.clean?.()
|
this.screen?.clean?.();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.screen = undefined;
|
this.screen = undefined;
|
||||||
super.clean()
|
super.clean();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export default class WebPlayer extends Player {
|
||||||
...TargetMarker.INITIAL_STATE,
|
...TargetMarker.INITIAL_STATE,
|
||||||
...MessageManager.INITIAL_STATE,
|
...MessageManager.INITIAL_STATE,
|
||||||
...MessageLoader.INITIAL_STATE,
|
...MessageLoader.INITIAL_STATE,
|
||||||
|
liveTimeTravel: false,
|
||||||
inspectorMode: false,
|
inspectorMode: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import MessageManager from 'Player/web/MessageManager';
|
import MessageManager from 'Player/web/MessageManager';
|
||||||
import type { Socket } from 'socket.io-client';
|
import type { Socket } from 'socket.io-client';
|
||||||
import type Screen from '../Screen/Screen';
|
import type Screen from '../Screen/Screen';
|
||||||
import type { Store } from '../../common/types';
|
import type { PlayerMsg, Store } from 'App/player';
|
||||||
import type { Message } from '../messages';
|
|
||||||
import MStreamReader from '../messages/MStreamReader';
|
import MStreamReader from '../messages/MStreamReader';
|
||||||
import JSONRawMessageReader from '../messages/JSONRawMessageReader';
|
import JSONRawMessageReader from '../messages/JSONRawMessageReader';
|
||||||
import Call, { CallingState } from './Call';
|
import Call, { CallingState } from './Call';
|
||||||
|
|
@ -71,7 +70,7 @@ export default class AssistManager {
|
||||||
constructor(
|
constructor(
|
||||||
private session: any,
|
private session: any,
|
||||||
private setMessagesLoading: (flag: boolean) => void,
|
private setMessagesLoading: (flag: boolean) => void,
|
||||||
private handleMessage: (m: Message, index: number) => void,
|
private handleMessage: (m: PlayerMsg, index: number) => void,
|
||||||
private screen: Screen,
|
private screen: Screen,
|
||||||
private config: RTCIceServer[] | null,
|
private config: RTCIceServer[] | null,
|
||||||
private store: Store<typeof AssistManager.INITIAL_STATE>,
|
private store: Store<typeof AssistManager.INITIAL_STATE>,
|
||||||
|
|
@ -159,7 +158,7 @@ export default class AssistManager {
|
||||||
const reader = new MStreamReader(jmr, this.session.startedAt);
|
const reader = new MStreamReader(jmr, this.session.startedAt);
|
||||||
let waitingForMessages = true;
|
let waitingForMessages = true;
|
||||||
|
|
||||||
const now = +new Date();
|
const now = new Date().getTime();
|
||||||
this.store.update({ assistStart: now });
|
this.store.update({ assistStart: now });
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -168,7 +167,8 @@ export default class AssistManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const urlObject = new URL(window.env.API_EDP || window.location.origin); // does it handle ssl automatically?
|
const urlObject = new URL(window.env.API_EDP || window.location.origin);
|
||||||
|
// does it handle ssl automatically?
|
||||||
|
|
||||||
const socket: Socket = (this.socket = io(urlObject.origin, {
|
const socket: Socket = (this.socket = io(urlObject.origin, {
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|
@ -192,7 +192,8 @@ export default class AssistManager {
|
||||||
}));
|
}));
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
waitingForMessages = true;
|
waitingForMessages = true;
|
||||||
this.setStatus(ConnectionStatus.WaitingMessages); // TODO: reconnect happens frequently on bad network
|
// TODO: reconnect happens frequently on bad network
|
||||||
|
this.setStatus(ConnectionStatus.WaitingMessages);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('messages', (messages) => {
|
socket.on('messages', (messages) => {
|
||||||
|
|
@ -229,9 +230,12 @@ export default class AssistManager {
|
||||||
const { tabId } = meta;
|
const { tabId } = meta;
|
||||||
const usedData = this.assistVersion === 1 ? evData : data;
|
const usedData = this.assistVersion === 1 ? evData : data;
|
||||||
const { active } = usedData;
|
const { active } = usedData;
|
||||||
|
|
||||||
const currentTab = this.store.get().currentTab;
|
const currentTab = this.store.get().currentTab;
|
||||||
|
|
||||||
this.clearDisconnectTimeout();
|
this.clearDisconnectTimeout();
|
||||||
!this.inactiveTimeout && this.setStatus(ConnectionStatus.Connected);
|
!this.inactiveTimeout && this.setStatus(ConnectionStatus.Connected);
|
||||||
|
|
||||||
if (typeof active === 'boolean') {
|
if (typeof active === 'boolean') {
|
||||||
this.clearInactiveTimeout();
|
this.clearInactiveTimeout();
|
||||||
if (active) {
|
if (active) {
|
||||||
|
|
@ -291,14 +295,17 @@ export default class AssistManager {
|
||||||
this.getAssistVersion
|
this.getAssistVersion
|
||||||
);
|
);
|
||||||
this.canvasReceiver = new CanvasReceiver(this.peerID, this.config, this.getNode, {
|
this.canvasReceiver = new CanvasReceiver(this.peerID, this.config, this.getNode, {
|
||||||
...this.session.agentInfo,
|
...this.session.agentInfo,
|
||||||
id: agentId,
|
id: agentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.onVisChange);
|
document.addEventListener('visibilitychange', this.onVisChange);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends event ping to stats service
|
||||||
|
* */
|
||||||
public ping(event: StatsEvent, id: number) {
|
public ping(event: StatsEvent, id: number) {
|
||||||
this.socket?.emit(event, id);
|
this.socket?.emit(event, id);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default class MStreamReader {
|
||||||
private idx: number = 0
|
private idx: number = 0
|
||||||
|
|
||||||
currentTab = 'back-compatability'
|
currentTab = 'back-compatability'
|
||||||
readNext(): Message & { _index: number } | null {
|
readNext(): Message & { _index: number, tabId: string } | null {
|
||||||
let msg = this.r.readMessage()
|
let msg = this.r.readMessage()
|
||||||
if (msg === null) { return null }
|
if (msg === null) { return null }
|
||||||
if (msg.tp === MType.Timestamp) {
|
if (msg.tp === MType.Timestamp) {
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ export default class Session {
|
||||||
canvasURL: ISession['canvasURL'];
|
canvasURL: ISession['canvasURL'];
|
||||||
live: ISession['live'];
|
live: ISession['live'];
|
||||||
startedAt: ISession['startedAt'];
|
startedAt: ISession['startedAt'];
|
||||||
duration: ISession['duration'];
|
duration: Duration;
|
||||||
events: ISession['events'];
|
events: ISession['events'];
|
||||||
stackEvents: ISession['stackEvents'];
|
stackEvents: ISession['stackEvents'];
|
||||||
metadata: ISession['metadata'];
|
metadata: ISession['metadata'];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue