fix(ui): refactor state types, prep to integrate lscache, fix session types

This commit is contained in:
nick-delirium 2024-01-16 13:04:12 +01:00
parent 0dea805366
commit cf9cad7f75
12 changed files with 213 additions and 181 deletions

View file

@ -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>

View file

@ -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

View file

@ -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 }

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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>,

View file

@ -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);
} }

View file

@ -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();
} };
} }

View file

@ -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,
} }

View file

@ -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);
} }

View file

@ -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) {

View file

@ -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'];