diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 7a090faeb..08e974143 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -76,7 +76,7 @@ function MobileOverviewPanelCont({ issuesList }: { issuesList: Record[] }) { +function WebOverviewPanelCont() { const { store } = React.useContext(PlayerContext); const [selectedFeatures, setSelectedFeatures] = React.useState([ 'PERFORMANCE', @@ -92,7 +92,6 @@ function WebOverviewPanelCont({ issuesList }: { issuesList: Record[ } = store.get(); const stackEventList = tabStates[currentTab]?.stackList || [] - // const eventsList = tabStates[currentTab]?.eventList || [] const frustrationsList = tabStates[currentTab]?.frustrationsList || [] const exceptionsList = tabStates[currentTab]?.exceptionsList || [] const resourceListUnmap = tabStates[currentTab]?.resourceList || [] diff --git a/frontend/app/duck/sessions.ts b/frontend/app/duck/sessions.ts index d051eaf85..c985e9b50 100644 --- a/frontend/app/duck/sessions.ts +++ b/frontend/app/duck/sessions.ts @@ -234,8 +234,8 @@ const reducer = (state = initialState, action: IAction) => { errors, issues, resources, - stackEvents, userEvents, + stackEvents, userTesting ); diff --git a/frontend/app/player/common/unpack.ts b/frontend/app/player/common/unpack.ts new file mode 100644 index 000000000..52960daff --- /dev/null +++ b/frontend/app/player/common/unpack.ts @@ -0,0 +1,39 @@ +import * as fzstd from 'fzstd'; +import { gunzipSync } from 'fflate' + +const unpack = (b: Uint8Array): Uint8Array => { + // zstd magical numbers 40 181 47 253 + const isZstd = b[0] === 0x28 && b[1] === 0xb5 && b[2] === 0x2f && b[3] === 0xfd + const isGzip = b[0] === 0x1F && b[1] === 0x8B && b[2] === 0x08; + if (isGzip) { + const now = performance.now() + const data = gunzipSync(b) + console.debug( + "Gunzip time", + Math.floor(performance.now() - now) + 'ms', + 'size', + Math.floor(b.byteLength / 1024), + '->', + Math.floor(data.byteLength / 1024), + 'kb' + ) + return data + } + if (isZstd) { + const now = performance.now() + const data = fzstd.decompress(b) + console.debug( + "Zstd unpack time", + Math.floor(performance.now() - now) + 'ms', + 'size', + Math.floor(b.byteLength / 1024), + '->', + Math.floor(data.byteLength / 1024), + 'kb' + ) + return data + } + return b +} + +export default unpack \ No newline at end of file diff --git a/frontend/app/player/mobile/IOSMessageManager.ts b/frontend/app/player/mobile/IOSMessageManager.ts index def6025fd..38b896bf4 100644 --- a/frontend/app/player/mobile/IOSMessageManager.ts +++ b/frontend/app/player/mobile/IOSMessageManager.ts @@ -128,10 +128,15 @@ export default class IOSMessageManager implements IMessageManager { }); } - _sortMessagesHack() { + /** 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 = { @@ -148,7 +153,7 @@ export default class IOSMessageManager implements IMessageManager { this.state.update(newState); }; - public onFileReadFailed = (e: any) => { + public onFileReadFailed = (...e: any[]) => { logger.error(e); this.state.update({error: true}); this.uiErrorHandler?.error('Error requesting a session file'); diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index bfa537600..daaddb081 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -27,11 +27,12 @@ export interface IMessageManager { onFileReadFinally(): void; startLoading(): void; resetMessageManagers(): void; + getListsFullState(): Record move(t: number): any; distributeMessage(msg: Message): void; setMessagesLoading(messagesLoading: boolean): void; clean(): void; - _sortMessagesHack: (msgs: Message[]) => void; + sortDomRemoveMessages: (msgs: Message[]) => void; } export interface SetState { diff --git a/frontend/app/player/web/Lists.ts b/frontend/app/player/web/Lists.ts index 73021c4ac..f2c45db53 100644 --- a/frontend/app/player/web/Lists.ts +++ b/frontend/app/player/web/Lists.ts @@ -1,87 +1,142 @@ +import { InjectedEvent } from 'Types/session/event'; +import Issue from 'Types/session/issue'; import ListWalker from '../common/ListWalker'; import ListWalkerWithMarks from '../common/ListWalkerWithMarks'; -import type { Timed } from '../common/types'; +import type { IResourceRequest, IResourceTiming, Timed } from 'Player'; +import { + Redux as reduxMsg, + Vuex as vuexMsg, + MobX as mobxMsg, + Zustand as zustandMsg, + NgRx as ngrxMsg, + GraphQl as graphqlMsg, + ConsoleLog as logMsg, + WsChannel as websocketMsg, + Profiler as profilerMsg, +} from 'Player/web/messages'; +type stackMsg = { + name: string; + Payload: string; + tp: number; +} & Timed; +type exceptionsMsg = { + tp: number; + name: string; + message: string; + payload: string; + metadata: string; +} & Timed; -const SIMPLE_LIST_NAMES = [ "event", "redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles", "frustrations"] as const -const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack", "websocket" ] as const -//const entityNamesSimple = [ "event", "profile" ]; +type MsgTypeMap = { + reduxList: reduxMsg; + mobxList: mobxMsg; + vuexList: vuexMsg; + zustandList: zustandMsg; + ngrxList: ngrxMsg; + graphqlList: graphqlMsg; + logList: logMsg; + fetchList: IResourceRequest; + resourceList: IResourceTiming; + stackList: stackMsg; + websocketList: websocketMsg; + profilerList: profilerMsg; + exceptionsList: exceptionsMsg; + frustrationsList: Issue | InjectedEvent; +}; +type ListMessageType = K extends keyof MsgTypeMap ? Array : Array; -const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const +const SIMPLE_LIST_NAMES = [ + 'event', + 'redux', + 'mobx', + 'vuex', + 'zustand', + 'ngrx', + 'graphql', + 'exceptions', + 'profiles', + 'frustrations', +] as const; +const MARKED_LIST_NAMES = ['log', 'resource', 'fetch', 'stack', 'websocket'] as const; -type KeysList = `${typeof LIST_NAMES[number]}List` -type KeysListNow = `${typeof LIST_NAMES[number]}ListNow` -type KeysMarkedCountNow = `${typeof MARKED_LIST_NAMES[number]}MarkedCountNow` +const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES] as const; + +type KeysList = `${(typeof LIST_NAMES)[number]}List`; +type KeysMarkedCountNow = `${(typeof MARKED_LIST_NAMES)[number]}MarkedCountNow`; type StateList = { - [key in KeysList]: Timed[] -} + [K in KeysList]: ListMessageType; +}; type StateListNow = { - [key in KeysListNow]: Timed[] -} + [K in KeysList as `${K}Now`]: ListMessageType; +}; type StateMarkedCountNow = { - [key in KeysMarkedCountNow]: number -} -type StateNow = StateListNow & StateMarkedCountNow -export type State = StateList & StateNow + [key in KeysMarkedCountNow]: number; +}; +type StateNow = StateListNow & StateMarkedCountNow; +export type State = StateList & StateNow; // maybe use list object itself inside the store -export const INITIAL_STATE = LIST_NAMES.reduce((state, name) => { - state[`${name}List`] = [] - state[`${name}ListNow`] = [] - return state -}, MARKED_LIST_NAMES.reduce((state, name) => { - state[`${name}MarkedCountNow`] = 0 - return state +export const INITIAL_STATE = LIST_NAMES.reduce( + (state, name) => { + state[`${name}List`] = []; + state[`${name}ListNow`] = []; + return state; + }, + MARKED_LIST_NAMES.reduce((state, name) => { + state[`${name}MarkedCountNow`] = 0; + return state; }, {} as Partial) as Partial -) as State - +) as State; type SimpleListsObject = { - [key in typeof SIMPLE_LIST_NAMES[number]]: ListWalker -} + [key in (typeof SIMPLE_LIST_NAMES)[number]]: ListWalker; +}; type MarkedListsObject = { - [key in typeof MARKED_LIST_NAMES[number]]: ListWalkerWithMarks -} -type ListsObject = SimpleListsObject & MarkedListsObject + [key in (typeof MARKED_LIST_NAMES)[number]]: ListWalkerWithMarks; +}; +type ListsObject = SimpleListsObject & MarkedListsObject; -export type InitialLists = { - [key in typeof LIST_NAMES[number]]: any[] // .isRed()? -} +export type InitialLists = { + [key in (typeof LIST_NAMES)[number]]: any[]; // .isRed()? +}; export default class Lists { - lists: ListsObject + lists: ListsObject; + constructor(initialLists: Partial = {}) { - const lists: Partial = {} + const lists: Partial = {}; for (const name of SIMPLE_LIST_NAMES) { - lists[name] = new ListWalker(initialLists[name]) + lists[name] = new ListWalker(initialLists[name]); } for (const name of MARKED_LIST_NAMES) { // TODO: provide types - lists[name] = new ListWalkerWithMarks((el) => el.isRed, initialLists[name]) + lists[name] = new ListWalkerWithMarks((el) => el.isRed, initialLists[name]); } - this.lists = lists as ListsObject + this.lists = lists as ListsObject; } getFullListsState(): StateList { return LIST_NAMES.reduce((state, name) => { - state[`${name}List`] = this.lists[name].list - return state - }, {} as Partial) as StateList + state[`${name}List`] = this.lists[name].list; + return state; + }, {} as Partial) as StateList; } moveGetState(t: number): StateNow { - return LIST_NAMES.reduce((state, name) => { - const lastMsg = this.lists[name].moveGetLast(t) // index: name === 'exceptions' ? undefined : index); - if (lastMsg != null) { - state[`${name}ListNow`] = this.lists[name].listNow - } - return state - }, MARKED_LIST_NAMES.reduce((state, name) => { - state[`${name}MarkedCountNow`] = this.lists[name].markedCountNow // Red --> Marked - return state + return LIST_NAMES.reduce( + (state, name) => { + const lastMsg = this.lists[name].moveGetLast(t); // index: name === 'exceptions' ? undefined : index); + if (lastMsg != null) { + state[`${name}ListNow`] = this.lists[name].listNow; + } + return state; + }, + MARKED_LIST_NAMES.reduce((state, name) => { + state[`${name}MarkedCountNow`] = this.lists[name].markedCountNow; // Red --> Marked + return state; }, {} as Partial) as Partial - ) as State + ) as State; } - -} \ No newline at end of file +} diff --git a/frontend/app/player/web/MessageLoader.ts b/frontend/app/player/web/MessageLoader.ts index 8c54d36f3..e6ee25130 100644 --- a/frontend/app/player/web/MessageLoader.ts +++ b/frontend/app/player/web/MessageLoader.ts @@ -1,20 +1,18 @@ import type { Store, SessionFilesInfo } from 'Player'; -import {IMessageManager} from "Player/player/Animator"; import { decryptSessionBytes } from './network/crypto'; import MFileReader from './messages/MFileReader'; import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles'; -import type { - Message, -} from './messages'; +import type { Message } from './messages'; import logger from 'App/logger'; -import * as fzstd from 'fzstd'; - +import unpack from 'Player/common/unpack'; +import MessageManager from 'Player/web/MessageManager'; +import IOSMessageManager from 'Player/mobile/IOSMessageManager'; interface State { - firstFileLoading: boolean, - domLoading: boolean, - devtoolsLoading: boolean, - error: boolean, + firstFileLoading: boolean; + domLoading: boolean; + devtoolsLoading: boolean; + error: boolean; } export default class MessageLoader { @@ -23,131 +21,148 @@ export default class MessageLoader { domLoading: false, devtoolsLoading: false, error: false, - } + }; constructor( private readonly session: SessionFilesInfo, private store: Store, - private messageManager: IMessageManager, + private messageManager: MessageManager | IOSMessageManager, private isClickmap: boolean, private uiErrorHandler?: { error: (msg: string) => void } ) {} - createNewParser(shouldDecrypt = true, file?: string, toggleStatus?: (isLoading: boolean) => void) { - const decrypt = shouldDecrypt && this.session.fileKey - ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!) - : (b: Uint8Array) => Promise.resolve(b) + createNewParser( + shouldDecrypt = true, + file?: string + ) { + const decrypt = + shouldDecrypt && this.session.fileKey + ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!) + : (b: Uint8Array) => Promise.resolve(b); // Each time called - new fileReader created - const unarchived = (b: Uint8Array) => { - // zstd magical numbers 40 181 47 253 - const isZstd = b[0] === 0x28 && b[1] === 0xb5 && b[2] === 0x2f && b[3] === 0xfd - if (isZstd) { - return fzstd.decompress(b) - } else { - return b - } - } - const fileReader = new MFileReader(new Uint8Array(), this.session.startedAt) + const fileReader = new MFileReader(new Uint8Array(), this.session.startedAt); return (b: Uint8Array) => { - decrypt(b).then(b => { - const data = unarchived(b) - toggleStatus?.(true); - fileReader.append(data) - fileReader.checkForIndexes() - const msgs: Array = [] - for (let msg = fileReader.readNext();msg !== null;msg = fileReader.readNext()) { - msgs.push(msg) - } - const sorted = msgs.sort((m1, m2) => { - return m1.time - m2.time - }) + return decrypt(b) + .then((b) => { + const data = unpack(b); + fileReader.append(data); + fileReader.checkForIndexes(); + const msgs: Array = []; + let finished = false; + while (!finished) { + const msg = fileReader.readNext(); + if (msg) { + msgs.push(msg); + } else { + finished = true; + break; + } + } - sorted.forEach(msg => { - this.messageManager.distributeMessage(msg) - }) - logger.info("Messages count: ", msgs.length, sorted, file) + const sortedMessages = msgs.sort((m1, m2) => { + return m1.time - m2.time; + }); - this.messageManager._sortMessagesHack(sorted) - toggleStatus?.(false); - this.messageManager.setMessagesLoading(false) - }).catch(e => { - console.error(e) - this.uiErrorHandler?.error('Error parsing file: ' + e.message) - }) - } + 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); + }); + }; } loadDomFiles(urls: string[], parser: (b: Uint8Array) => Promise) { if (urls.length > 0) { - this.store.update({ domLoading: true }) - return loadFiles(urls, parser, true).then(() => this.store.update({ domLoading: false })) + this.store.update({ domLoading: true }); + return loadFiles(urls, parser, true).then(() => this.store.update({ domLoading: false })); } else { - return Promise.resolve() + return Promise.resolve(); } } loadDevtools(parser: (b: Uint8Array) => Promise) { if (!this.isClickmap) { - this.store.update({ devtoolsLoading: true }) - return loadFiles(this.session.devtoolsURL, parser) - // TODO: also in case of dynamic update through assist - .then(() => { - // @ts-ignore ? - this.store.update({ ...this.messageManager.getListsFullState(), devtoolsLoading: false }); - }) + this.store.update({ devtoolsLoading: true }); + return ( + loadFiles(this.session.devtoolsURL, parser) + // TODO: also in case of dynamic update through assist + .then(() => { + // @ts-ignore ? + this.store.update({ + ...this.messageManager.getListsFullState(), + devtoolsLoading: false, + }); + }) + ); } else { - return Promise.resolve() + return Promise.resolve(); } } async loadFiles() { - this.messageManager.startLoading() + this.messageManager.startLoading(); - const loadMethod = this.session.domURL && this.session.domURL.length > 0 - ? { url: this.session.domURL, parser: () => this.createNewParser(true, 'dom') } - : { url: this.session.mobsUrl, parser: () => this.createNewParser(false, 'dom') } + const loadMethod = + 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') }; - const parser = loadMethod.parser() - const devtoolsParser = this.createNewParser(true, 'devtools') + const parser = loadMethod.parser(); + const devtoolsParser = this.createNewParser(true, 'devtools'); /** - * We load first dom mob file before the rest * to speed up time to replay - * but as a tradeoff we have to have some copy-paste + * we load first dom mob file before the rest + * (because parser can read them in parallel) + * as a tradeoff we have some copy-paste code * for the devtools file * */ try { - await loadFiles([loadMethod.url[0]], parser) - const restDomFilesPromise = this.loadDomFiles([...loadMethod.url.slice(1)], parser) - const restDevtoolsFilesPromise = this.loadDevtools(devtoolsParser) + await loadFiles([loadMethod.mobUrls[0]], parser); + const restDomFilesPromise = this.loadDomFiles([...loadMethod.mobUrls.slice(1)], parser); + const restDevtoolsFilesPromise = this.loadDevtools(devtoolsParser); - await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]) - this.messageManager.onFileReadSuccess() - } catch (e) { + await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]); + this.messageManager.onFileReadSuccess(); + } catch (sessionLoadError) { try { - this.store.update({ domLoading: true, devtoolsLoading: true }) - const efsDomFilePromise = requestEFSDom(this.session.sessionId) - const efsDevtoolsFilePromise = requestEFSDevtools(this.session.sessionId) + this.store.update({ domLoading: true, devtoolsLoading: true }); + const efsDomFilePromise = requestEFSDom(this.session.sessionId); + const efsDevtoolsFilePromise = requestEFSDevtools(this.session.sessionId); - const [domData, devtoolsData] = await Promise.allSettled([efsDomFilePromise, efsDevtoolsFilePromise]) - const domParser = this.createNewParser(false, 'domEFS') - const devtoolsParser = this.createNewParser(false, 'devtoolsEFS') - const parseDomPromise: Promise = domData.status === 'fulfilled' - ? domParser(domData.value) : Promise.reject('No dom file in EFS') - const parseDevtoolsPromise: Promise = devtoolsData.status === 'fulfilled' - ? devtoolsParser(devtoolsData.value) : Promise.reject('No devtools file in EFS') + const [domData, devtoolsData] = await Promise.allSettled([ + efsDomFilePromise, + efsDevtoolsFilePromise, + ]); + const domParser = this.createNewParser(false, 'domEFS'); + const devtoolsParser = this.createNewParser(false, 'devtoolsEFS'); + const parseDomPromise: Promise = + domData.status === 'fulfilled' + ? domParser(domData.value) + : Promise.reject('No dom file in EFS'); + const parseDevtoolsPromise: Promise = + devtoolsData.status === 'fulfilled' + ? devtoolsParser(devtoolsData.value) + : Promise.reject('No devtools file in EFS'); - await Promise.all([parseDomPromise, parseDevtoolsPromise]) - this.messageManager.onFileReadSuccess() - } catch (e2) { - this.messageManager.onFileReadFailed(e) + await Promise.all([parseDomPromise, parseDevtoolsPromise]); + this.messageManager.onFileReadSuccess(); + } catch (unprocessedLoadError) { + this.messageManager.onFileReadFailed(sessionLoadError, unprocessedLoadError); } } finally { - this.messageManager.onFileReadFinally() - this.store.update({ domLoading: false, devtoolsLoading: false }) + this.messageManager.onFileReadFinally(); + this.store.update({ domLoading: false, devtoolsLoading: false }); } } clean() { this.store.update(MessageLoader.INITIAL_STATE); } -} \ No newline at end of file +} diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index b87406227..ff9d3e4c3 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -146,8 +146,12 @@ export default class MessageManager { }); } - public _sortMessagesHack = (msgs: Message[]) => { - Object.values(this.tabs).forEach((tab) => tab._sortMessagesHack(msgs)); + /** + * Legacy code. Iterates over all tab managers and sorts messages for their pagesManager. + * Ensures that RemoveNode messages with parent being are sorted before other RemoveNode messages. + * */ + public sortDomRemoveMessages = (msgs: Message[]) => { + Object.values(this.tabs).forEach((tab) => tab.sortDomRemoveMessages(msgs)); }; private waitingForFiles: boolean = false; @@ -159,7 +163,7 @@ export default class MessageManager { Object.values(this.tabs).forEach((tab) => tab.onFileReadSuccess?.()); }; - public onFileReadFailed = (e: any) => { + public onFileReadFailed = (...e: any[]) => { logger.error(e); this.state.update({ error: true }); this.uiErrorHandler?.error('Error requesting a session file'); diff --git a/frontend/app/player/web/TabManager.ts b/frontend/app/player/web/TabManager.ts index 2534dad01..f5e39aeda 100644 --- a/frontend/app/player/web/TabManager.ts +++ b/frontend/app/player/web/TabManager.ts @@ -351,22 +351,25 @@ export default class TabSessionManager { return this.decoder.decode(msg); } - public _sortMessagesHack = (msgs: Message[]) => { + /** + * Legacy code. Ensures that RemoveNode messages with parent being are sorted before other RemoveNode messages. + * */ + public sortDomRemoveMessages = (msgs: Message[]) => { // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) - const headChildrenIds = msgs.filter((m) => m.parentID === 1).map((m) => m.id); + const headChildrenMsgIds = msgs.filter((m) => m.parentID === 1).map((m) => m.id); this.pagesManager.sortPages((m1, m2) => { if (m1.time === m2.time) { if (m1.tp === MType.RemoveNode && m2.tp !== MType.RemoveNode) { - if (headChildrenIds.includes(m1.id)) { + if (headChildrenMsgIds.includes(m1.id)) { return -1; } } else if (m2.tp === MType.RemoveNode && m1.tp !== MType.RemoveNode) { - if (headChildrenIds.includes(m2.id)) { + if (headChildrenMsgIds.includes(m2.id)) { return 1; } } else if (m2.tp === MType.RemoveNode && m1.tp === MType.RemoveNode) { - const m1FromHead = headChildrenIds.includes(m1.id); - const m2FromHead = headChildrenIds.includes(m2.id); + const m1FromHead = headChildrenMsgIds.includes(m1.id); + const m2FromHead = headChildrenMsgIds.includes(m2.id); if (m1FromHead && !m2FromHead) { return -1; } else if (m2FromHead && !m1FromHead) { diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts index e5e075e1a..4564e52b7 100644 --- a/frontend/app/player/web/messages/MFileReader.ts +++ b/frontend/app/player/web/messages/MFileReader.ts @@ -65,7 +65,7 @@ export default class MFileReader extends RawMessageReader { } currentTab = 'back-compatability' - readNext(): Message & { _index?: number } | null { + readNext(): Message & { tabId: string; _index?: number } | null { if (this.error || !this.hasNextByte()) { return null } @@ -95,6 +95,7 @@ export default class MFileReader extends RawMessageReader { this.currentTime = rMsg.timestamp - this.startTime return { tp: 9999, + tabId: '', time: this.currentTime, } } diff --git a/frontend/app/player/web/network/crypto.ts b/frontend/app/player/web/network/crypto.ts index 565d076a6..0e4a65e29 100644 --- a/frontend/app/player/web/network/crypto.ts +++ b/frontend/app/player/web/network/crypto.ts @@ -1,5 +1,3 @@ -import { gunzipSync } from 'fflate' - const u8aFromHex = (hexString:string) => Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))) @@ -20,24 +18,6 @@ export function decryptSessionBytes(cypher: Uint8Array, keyString: string): Prom return crypto.subtle.importKey("raw", byteKey, { name: "AES-CBC" }, false, ["decrypt"]) .then(key => crypto.subtle.decrypt({ name: "AES-CBC", iv: iv}, key, cypher)) .then((bArray: ArrayBuffer) => new Uint8Array(bArray)) - .then(async (u8Array: Uint8Array) => { - const isGzip = u8Array[0] === 0x1F && u8Array[1] === 0x8B && u8Array[2] === 0x08; - if (isGzip) { - const now = performance.now() - const data = gunzipSync(u8Array) - console.debug( - "Decompression time", - Math.floor(performance.now() - now) + 'ms', - 'size', - Math.floor(u8Array.byteLength/1024), - '->', - Math.floor(data.byteLength/1024), - 'kb' - ) - return data - } else return u8Array - }) - //?? TS doesn not catch the `decrypt`` returning type } diff --git a/frontend/app/player/web/types/resource.ts b/frontend/app/player/web/types/resource.ts index d76557e78..c82882be0 100644 --- a/frontend/app/player/web/types/resource.ts +++ b/frontend/app/player/web/types/resource.ts @@ -83,6 +83,29 @@ interface IResource { responseBodySize?: number, } +export interface IResourceTiming extends IResource { + name: string, + isRed: boolean, + isYellow: boolean, + type: ResourceType, + method: "GET" | "POST" | "PUT" | "DELETE" | "..", + success: boolean, + status: "2xx-3xx" | "4xx-5xx", + time: number, +} + +export interface IResourceRequest extends IResource { + name: string, + isRed: boolean, + isYellow: boolean, + type: ResourceType.XHR | ResourceType.FETCH | ResourceType.IOS, + method: "GET" | "POST" | "PUT" | "DELETE" | "..", + success: boolean, + status: number, + time: number, + decodedBodySize?: number, +} + export const Resource = (resource: IResource) => ({ ...resource,