* feat(ui): new ios player start * fix(ui): image player * fix(ui): fix autoplay * fix(ui): fix copy paste code; error handler; default mode null * fix(ui): fix loader animation * fix(ui): memory cleanup * fix(ui): memory cleanup
298 lines
8.5 KiB
TypeScript
298 lines
8.5 KiB
TypeScript
import logger from 'App/logger';
|
|
import { TarFile } from "js-untar";
|
|
import { getResourceFromNetworkRequest } from 'Player';
|
|
|
|
import type { Store } from 'Player';
|
|
import { IMessageManager } from 'Player/player/Animator';
|
|
|
|
import TouchManager from 'Player/mobile/managers/TouchManager';
|
|
import ActivityManager from '../web/managers/ActivityManager';
|
|
import Lists, {
|
|
InitialLists,
|
|
INITIAL_STATE as LISTS_INITIAL_STATE,
|
|
State as ListsState,
|
|
} from './IOSLists';
|
|
import IOSPerformanceTrackManager, {
|
|
PerformanceChartPoint,
|
|
} from 'Player/mobile/managers/IOSPerformanceTrackManager';
|
|
import { MType } from '../web/messages';
|
|
import type { Message } from '../web/messages';
|
|
import SnapshotManager from 'Player/mobile/managers/SnapshotManager';
|
|
|
|
import Screen, {
|
|
INITIAL_STATE as SCREEN_INITIAL_STATE,
|
|
State as ScreenState,
|
|
} from '../web/Screen/Screen';
|
|
|
|
import { Log } from './types/log';
|
|
|
|
import type { SkipInterval } from '../web/managers/ActivityManager';
|
|
|
|
export const performanceWarnings = [
|
|
'thermalState',
|
|
'memoryWarning',
|
|
'lowDiskSpace',
|
|
'isLowPowerModeEnabled',
|
|
'batteryLevel',
|
|
];
|
|
|
|
const perfWarningFrustrations = {
|
|
thermalState: {
|
|
title: 'Overheating',
|
|
icon: 'thermometer-sun',
|
|
},
|
|
memoryWarning: {
|
|
title: 'High Memory Usage',
|
|
icon: 'memory-ios',
|
|
},
|
|
lowDiskSpace: {
|
|
title: 'Low Disk Space',
|
|
icon: 'low-disc-space',
|
|
},
|
|
isLowPowerModeEnabled: {
|
|
title: 'Low Power Mode',
|
|
icon: 'battery-charging',
|
|
},
|
|
batteryLevel: {
|
|
title: 'Low Battery',
|
|
icon: 'battery',
|
|
},
|
|
};
|
|
|
|
export interface State extends ScreenState, ListsState {
|
|
skipIntervals: SkipInterval[];
|
|
performanceChartData: PerformanceChartPoint[];
|
|
performanceChartTime: number;
|
|
location?: string;
|
|
|
|
error: boolean;
|
|
messagesLoading: boolean;
|
|
|
|
cssLoading: boolean;
|
|
ready: boolean;
|
|
lastMessageTime: number;
|
|
messagesProcessed: boolean;
|
|
eventCount: number;
|
|
updateWarnings: number;
|
|
currentSnapshot: TarFile | null;
|
|
}
|
|
|
|
const userEvents = [
|
|
MType.IosSwipeEvent,
|
|
MType.IosClickEvent,
|
|
MType.IosInputEvent,
|
|
MType.IosScreenChanges,
|
|
];
|
|
|
|
export default class IOSMessageManager implements IMessageManager {
|
|
static INITIAL_STATE: State = {
|
|
...SCREEN_INITIAL_STATE,
|
|
...LISTS_INITIAL_STATE,
|
|
updateWarnings: 0,
|
|
eventCount: 0,
|
|
performanceChartData: [],
|
|
performanceChartTime: 0,
|
|
skipIntervals: [],
|
|
error: false,
|
|
ready: false,
|
|
cssLoading: false,
|
|
lastMessageTime: 0,
|
|
messagesProcessed: false,
|
|
messagesLoading: false,
|
|
currentSnapshot: null,
|
|
};
|
|
|
|
private activityManager: ActivityManager | null = null;
|
|
private performanceManager = new IOSPerformanceTrackManager();
|
|
|
|
private readonly sessionStart: number;
|
|
private lastMessageTime: number = 0;
|
|
private touchManager: TouchManager;
|
|
private lists: Lists;
|
|
public snapshotManager: SnapshotManager;
|
|
|
|
constructor(
|
|
private readonly session: Record<string, any>,
|
|
private readonly state: Store<State & { time: number }>,
|
|
private readonly screen: Screen,
|
|
private readonly uiErrorHandler?: { error: (error: string) => void },
|
|
initialLists?: Partial<InitialLists>
|
|
) {
|
|
this.sessionStart = this.session.startedAt;
|
|
this.lists = new Lists(initialLists);
|
|
this.touchManager = new TouchManager(screen);
|
|
this.activityManager = new ActivityManager(this.session.duration.milliseconds); // only if not-live
|
|
this.snapshotManager = new SnapshotManager();
|
|
}
|
|
|
|
public updateDimensions(dimensions: { width: number; height: number }) {
|
|
this.touchManager.updateDimensions(dimensions);
|
|
}
|
|
|
|
public updateLists(lists: Partial<InitialLists>) {
|
|
const exceptions = lists.exceptions;
|
|
exceptions?.forEach((e) => {
|
|
this.lists.lists.exceptions.insert(e);
|
|
this.lists.lists.log.insert(e);
|
|
});
|
|
lists.frustrations?.forEach((f) => {
|
|
this.lists.lists.frustrations.insert(f);
|
|
});
|
|
|
|
const eventCount = this.lists.lists.event.count; //lists?.event?.length || 0;
|
|
const currentState = this.state.get();
|
|
this.state.update({
|
|
eventCount: currentState.eventCount + eventCount,
|
|
...this.lists.getFullListsState(),
|
|
});
|
|
}
|
|
|
|
/** empty here. Kept for consistency with normal manager */
|
|
sortDomRemoveMessages() {
|
|
return;
|
|
}
|
|
|
|
public getListsFullState = () => {
|
|
return this.lists.getFullListsState();
|
|
};
|
|
|
|
private waitingForFiles: boolean = false;
|
|
public onFileReadSuccess = () => {
|
|
let newState: Partial<State> = {
|
|
...this.state.get(),
|
|
eventCount: this.lists?.lists.event?.length || 0,
|
|
performanceChartData: this.performanceManager.chartData,
|
|
...this.lists.getFullListsState(),
|
|
};
|
|
|
|
if (this.activityManager) {
|
|
this.activityManager.end();
|
|
newState['skipIntervals'] = this.activityManager.list;
|
|
}
|
|
this.state.update(newState);
|
|
};
|
|
|
|
public onFileReadFailed = (...e: any[]) => {
|
|
logger.error(e);
|
|
this.state.update({ error: true });
|
|
this.uiErrorHandler?.error('Error requesting a session file');
|
|
};
|
|
|
|
public onFileReadFinally = () => {
|
|
this.waitingForFiles = false;
|
|
this.state.update({ messagesProcessed: true });
|
|
};
|
|
|
|
public startLoading = () => {
|
|
this.waitingForFiles = true;
|
|
this.state.update({ messagesProcessed: false });
|
|
this.setMessagesLoading(true);
|
|
};
|
|
|
|
resetMessageManagers() {
|
|
this.touchManager = new TouchManager(this.screen);
|
|
this.activityManager = new ActivityManager(this.session.duration.milliseconds);
|
|
}
|
|
|
|
move(t: number): any {
|
|
const stateToUpdate: Record<string, any> = {};
|
|
|
|
const lastPerformanceTrackMessage = this.performanceManager.moveGetLast(t);
|
|
if (lastPerformanceTrackMessage) {
|
|
Object.assign(stateToUpdate, {
|
|
performanceChartTime: lastPerformanceTrackMessage.time,
|
|
});
|
|
}
|
|
|
|
this.touchManager.move(t);
|
|
if (
|
|
this.waitingForFiles &&
|
|
this.lastMessageTime <= t &&
|
|
t !== this.session.duration.milliseconds
|
|
) {
|
|
this.setMessagesLoading(true);
|
|
}
|
|
|
|
const snapshot = this.snapshotManager.moveReady(t);
|
|
if (snapshot) {
|
|
Object.assign(stateToUpdate, {
|
|
currentSnapshot: snapshot,
|
|
});
|
|
}
|
|
Object.assign(stateToUpdate, this.lists.moveGetState(t));
|
|
Object.assign(stateToUpdate, { performanceListNow: this.lists.lists.performance.listNow });
|
|
Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate);
|
|
}
|
|
|
|
distributeMessage = (msg: Message & { tabId: string }): void => {
|
|
const lastMessageTime = Math.max(msg.time, this.lastMessageTime);
|
|
this.lastMessageTime = lastMessageTime;
|
|
this.state.update({ lastMessageTime });
|
|
if (userEvents.includes(msg.tp)) {
|
|
this.activityManager?.updateAcctivity(msg.time);
|
|
}
|
|
|
|
switch (msg.tp) {
|
|
case MType.IosPerformanceEvent:
|
|
const performanceStats = ['background', 'memoryUsage', 'mainThreadCPU'];
|
|
if (performanceStats.includes(msg.name)) {
|
|
this.performanceManager.append(msg);
|
|
}
|
|
if (performanceWarnings.includes(msg.name)) {
|
|
// @ts-ignore
|
|
const item = perfWarningFrustrations[msg.name];
|
|
this.lists.lists.performance.append({
|
|
...msg,
|
|
name: item.title,
|
|
techName: msg.name,
|
|
icon: item.icon,
|
|
type: 'ios_perf_event',
|
|
} as any);
|
|
}
|
|
break;
|
|
// case MType.IosInputEvent:
|
|
// console.log('input', msg)
|
|
// break;
|
|
case MType.IosNetworkCall:
|
|
this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart));
|
|
break;
|
|
case MType.WsChannel:
|
|
this.lists.lists.websocket.insert(msg);
|
|
break;
|
|
case MType.IosEvent:
|
|
// @ts-ignore
|
|
this.lists.lists.event.insert({ ...msg, source: 'openreplay' });
|
|
break;
|
|
case MType.IosSwipeEvent:
|
|
case MType.IosClickEvent:
|
|
this.touchManager.append(msg);
|
|
break;
|
|
case MType.IosLog:
|
|
const log = { ...msg, level: msg.severity };
|
|
// @ts-ignore
|
|
this.lists.lists.log.append(Log(log));
|
|
break;
|
|
default:
|
|
console.log(msg);
|
|
// stuff
|
|
break;
|
|
}
|
|
};
|
|
|
|
setMessagesLoading = (messagesLoading: boolean) => {
|
|
this.screen.display(!messagesLoading);
|
|
// @ts-ignore idk
|
|
this.state.update({ messagesLoading, ready: !messagesLoading && !this.state.get().cssLoading });
|
|
};
|
|
|
|
private setSize({ height, width }: { height: number; width: number }) {
|
|
this.screen.scale({ height, width });
|
|
this.state.update({ width, height });
|
|
}
|
|
|
|
// TODO: clean managers?
|
|
clean() {
|
|
this.snapshotManager?.clean();
|
|
this.state.update(IOSMessageManager.INITIAL_STATE);
|
|
}
|
|
}
|