feat(tracker/ui): add websocket support (#1733)

* feat(tracker/ui): add websocket support

* feat(tracker): expose ws tracker method

* fix(ui): add docs, fix types for ws methods

* fix(ui): some style fixes, rename field in mob

* fix(ui): change ws modal

* fix(ui): change ws modal

* fix(ui): rm mock data
This commit is contained in:
Delirium 2024-01-03 15:54:18 +01:00 committed by GitHub
parent 7a5e2be138
commit 9e1add4ad9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 809 additions and 386 deletions

File diff suppressed because it is too large Load diff

View file

@ -1254,6 +1254,30 @@ func DecodeNetworkRequest(reader BytesReader) (Message, error) {
return msg, err return msg, err
} }
func DecodeWSChannel(reader BytesReader) (Message, error) {
var err error = nil
msg := &WSChannel{}
if msg.ChType, err = reader.ReadString(); err != nil {
return nil, err
}
if msg.ChannelName, err = reader.ReadString(); err != nil {
return nil, err
}
if msg.Data, err = reader.ReadString(); err != nil {
return nil, err
}
if msg.Timestamp, err = reader.ReadUint(); err != nil {
return nil, err
}
if msg.Dir, err = reader.ReadString(); err != nil {
return nil, err
}
if msg.MessageType, err = reader.ReadString(); err != nil {
return nil, err
}
return msg, err
}
func DecodeInputChange(reader BytesReader) (Message, error) { func DecodeInputChange(reader BytesReader) (Message, error) {
var err error = nil var err error = nil
msg := &InputChange{} msg := &InputChange{}
@ -1991,6 +2015,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
return DecodePartitionedMessage(reader) return DecodePartitionedMessage(reader)
case 83: case 83:
return DecodeNetworkRequest(reader) return DecodeNetworkRequest(reader)
case 84:
return DecodeWSChannel(reader)
case 112: case 112:
return DecodeInputChange(reader) return DecodeInputChange(reader)
case 113: case 113:

View file

@ -723,6 +723,18 @@ class NetworkRequest(Message):
self.transferred_body_size = transferred_body_size self.transferred_body_size = transferred_body_size
class WSChannel(Message):
__id__ = 84
def __init__(self, ch_type, channel_name, data, timestamp, dir, message_type):
self.ch_type = ch_type
self.channel_name = channel_name
self.data = data
self.timestamp = timestamp
self.dir = dir
self.message_type = message_type
class InputChange(Message): class InputChange(Message):
__id__ = 112 __id__ = 112

View file

@ -1069,6 +1069,25 @@ cdef class NetworkRequest(PyMessage):
self.transferred_body_size = transferred_body_size self.transferred_body_size = transferred_body_size
cdef class WSChannel(PyMessage):
cdef public int __id__
cdef public str ch_type
cdef public str channel_name
cdef public str data
cdef public unsigned long timestamp
cdef public str dir
cdef public str message_type
def __init__(self, str ch_type, str channel_name, str data, unsigned long timestamp, str dir, str message_type):
self.__id__ = 84
self.ch_type = ch_type
self.channel_name = channel_name
self.data = data
self.timestamp = timestamp
self.dir = dir
self.message_type = message_type
cdef class InputChange(PyMessage): cdef class InputChange(PyMessage):
cdef public int __id__ cdef public int __id__
cdef public unsigned long id cdef public unsigned long id

View file

@ -660,6 +660,16 @@ class MessageCodec(Codec):
transferred_body_size=self.read_uint(reader) transferred_body_size=self.read_uint(reader)
) )
if message_id == 84:
return WSChannel(
ch_type=self.read_string(reader),
channel_name=self.read_string(reader),
data=self.read_string(reader),
timestamp=self.read_uint(reader),
dir=self.read_string(reader),
message_type=self.read_string(reader)
)
if message_id == 112: if message_id == 112:
return InputChange( return InputChange(
id=self.read_uint(reader), id=self.read_uint(reader),

View file

@ -758,6 +758,16 @@ cdef class MessageCodec:
transferred_body_size=self.read_uint(reader) transferred_body_size=self.read_uint(reader)
) )
if message_id == 84:
return WSChannel(
ch_type=self.read_string(reader),
channel_name=self.read_string(reader),
data=self.read_string(reader),
timestamp=self.read_uint(reader),
dir=self.read_string(reader),
message_type=self.read_string(reader)
)
if message_id == 112: if message_id == 112:
return InputChange( return InputChange(
id=self.read_uint(reader), id=self.read_uint(reader),

View file

@ -9,14 +9,15 @@ function EventsList({ scale }: { scale: number }) {
const { tabStates, eventCount } = store.get(); const { tabStates, eventCount } = store.get();
const events = React.useMemo(() => { const events = React.useMemo(() => {
return Object.values(tabStates)[0]?.eventList || []; return Object.values(tabStates)[0]?.eventList.filter(e => e.time) || [];
}, [eventCount]); }, [eventCount]);
return ( return (
<> <>
{events.map((e) => ( {events.map((e) => (
<div <div
/*@ts-ignore TODO */ /*@ts-ignore TODO */
key={e.key} key={`${e.key}_${e.time}`}
className={stl.event} className={stl.event}
style={{ left: `${getTimelinePosition(e.time, scale)}%` }} style={{ left: `${getTimelinePosition(e.time, scale)}%` }}
/> />
@ -35,7 +36,7 @@ function MobileEventsList({ scale }: { scale: number }) {
{events.map((e) => ( {events.map((e) => (
<div <div
/*@ts-ignore TODO */ /*@ts-ignore TODO */
key={e.key} key={`${e.key}_${e.time}`}
className={stl.event} className={stl.event}
style={{ left: `${getTimelinePosition(e.time, scale)}%` }} style={{ left: `${getTimelinePosition(e.time, scale)}%` }}
/> />

View file

@ -18,6 +18,7 @@ import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine'; import InfoLine from '../BottomBlock/InfoLine';
import useAutoscroll, { getLastItemTime } from '../useAutoscroll'; import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter'; import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
import WSModal from './WSModal'
const INDEX_KEY = 'network'; const INDEX_KEY = 'network';
@ -28,6 +29,7 @@ const CSS = 'css';
const IMG = 'img'; const IMG = 'img';
const MEDIA = 'media'; const MEDIA = 'media';
const OTHER = 'other'; const OTHER = 'other';
const WS = 'websocket';
const TYPE_TO_TAB = { const TYPE_TO_TAB = {
[ResourceType.XHR]: XHR, [ResourceType.XHR]: XHR,
@ -36,10 +38,11 @@ const TYPE_TO_TAB = {
[ResourceType.CSS]: CSS, [ResourceType.CSS]: CSS,
[ResourceType.IMG]: IMG, [ResourceType.IMG]: IMG,
[ResourceType.MEDIA]: MEDIA, [ResourceType.MEDIA]: MEDIA,
[ResourceType.WS]: WS,
[ResourceType.OTHER]: OTHER, [ResourceType.OTHER]: OTHER,
}; };
const TAP_KEYS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER] as const; const TAP_KEYS = [ALL, XHR, JS, CSS, IMG, MEDIA, OTHER, WS] as const;
const TABS = TAP_KEYS.map((tab) => ({ const TABS = TAP_KEYS.map((tab) => ({
text: tab === 'xhr' ? 'Fetch/XHR' : tab, text: tab === 'xhr' ? 'Fetch/XHR' : tab,
key: tab, key: tab,
@ -156,6 +159,8 @@ function NetworkPanelCont({ startedAt, panelHeight }: { startedAt: number; panel
resourceList = [], resourceList = [],
fetchListNow = [], fetchListNow = [],
resourceListNow = [], resourceListNow = [],
websocketList = [],
websocketListNow = [],
} = tabStates[currentTab]; } = tabStates[currentTab];
return ( return (
@ -170,11 +175,19 @@ function NetworkPanelCont({ startedAt, panelHeight }: { startedAt: number; panel
resourceListNow={resourceListNow} resourceListNow={resourceListNow}
player={player} player={player}
startedAt={startedAt} startedAt={startedAt}
websocketList={websocketList as WSMessage[]}
websocketListNow={websocketListNow as WSMessage[]}
/> />
); );
} }
function MobileNetworkPanelCont({ startedAt, panelHeight }: { startedAt: number, panelHeight: number }) { function MobileNetworkPanelCont({
startedAt,
panelHeight,
}: {
startedAt: number;
panelHeight: number;
}) {
const { player, store } = React.useContext(MobilePlayerContext); const { player, store } = React.useContext(MobilePlayerContext);
const domContentLoadedTime = undefined; const domContentLoadedTime = undefined;
@ -185,6 +198,8 @@ function MobileNetworkPanelCont({ startedAt, panelHeight }: { startedAt: number,
resourceList = [], resourceList = [],
fetchListNow = [], fetchListNow = [],
resourceListNow = [], resourceListNow = [],
websocketList = [],
websocketListNow = [],
} = store.get(); } = store.get();
return ( return (
@ -200,10 +215,22 @@ function MobileNetworkPanelCont({ startedAt, panelHeight }: { startedAt: number,
resourceListNow={resourceListNow} resourceListNow={resourceListNow}
player={player} player={player}
startedAt={startedAt} startedAt={startedAt}
// @ts-ignore
websocketList={websocketList}
// @ts-ignore
websocketListNow={websocketListNow}
/> />
); );
} }
type WSMessage = Timed & {
channelName: string;
data: string;
timestamp: number;
dir: 'up' | 'down';
messageType: string;
}
interface Props { interface Props {
domContentLoadedTime?: { domContentLoadedTime?: {
time: number; time: number;
@ -218,6 +245,8 @@ interface Props {
resourceList: Timed[]; resourceList: Timed[];
fetchListNow: Timed[]; fetchListNow: Timed[];
resourceListNow: Timed[]; resourceListNow: Timed[];
websocketList: Array<WSMessage>;
websocketListNow: Array<WSMessage>;
player: WebPlayer | MobilePlayer; player: WebPlayer | MobilePlayer;
startedAt: number; startedAt: number;
isMobile?: boolean; isMobile?: boolean;
@ -237,6 +266,7 @@ const NetworkPanelComp = observer(
startedAt, startedAt,
isMobile, isMobile,
panelHeight, panelHeight,
websocketList,
}: Props) => { }: Props) => {
const { showModal } = useModal(); const { showModal } = useModal();
const [sortBy, setSortBy] = useState('time'); const [sortBy, setSortBy] = useState('time');
@ -251,6 +281,14 @@ const NetworkPanelComp = observer(
const activeTab = devTools[INDEX_KEY].activeTab; const activeTab = devTools[INDEX_KEY].activeTab;
const activeIndex = devTools[INDEX_KEY].index; const activeIndex = devTools[INDEX_KEY].index;
const socketList = useMemo(
() =>
websocketList.filter(
(ws, i, arr) => arr.findIndex((it) => it.channelName === ws.channelName) === i
),
[websocketList]
);
const list = useMemo( const list = useMemo(
() => () =>
// TODO: better merge (with body size info) - do it in player // TODO: better merge (with body size info) - do it in player
@ -283,8 +321,20 @@ const NetworkPanelComp = observer(
}) })
) )
.concat(fetchList) .concat(fetchList)
.concat(
socketList.map((ws) => ({
...ws,
type: 'websocket',
method: 'ws',
url: ws.channelName,
name: ws.channelName,
status: '101',
duration: 0,
transferredBodySize: 0,
}))
)
.sort((a, b) => a.time - b.time), .sort((a, b) => a.time - b.time),
[resourceList.length, fetchList.length] [resourceList.length, fetchList.length, socketList]
); );
let filteredList = useMemo(() => { let filteredList = useMemo(() => {
@ -354,6 +404,18 @@ const NetworkPanelComp = observer(
}, [domContentLoadedTime, loadTime]); }, [domContentLoadedTime, loadTime]);
const showDetailsModal = (item: any) => { const showDetailsModal = (item: any) => {
if (item.type === 'websocket') {
const socketMsgList = websocketList.filter((ws) => ws.channelName === item.channelName);
console.log(socketMsgList)
return showModal(
<WSModal
socketMsgList={socketMsgList}
/>, {
right: true, width: 700,
}
)
}
setIsDetailsModalActive(true); setIsDetailsModalActive(true);
showModal( showModal(
<FetchDetailsModal <FetchDetailsModal

View file

@ -0,0 +1,93 @@
import { durationFromMs } from 'App/date';
import { Timed } from 'Player';
import React from 'react';
import { Icon } from 'UI';
type SocketMsg = Timed & {
channelName: string;
data: string;
timestamp: number;
dir: 'up' | 'down';
messageType: string;
};
interface Props {
socketMsgList: Array<SocketMsg>;
}
function WSModal({ socketMsgList }: Props) {
return (
<div className={'h-screen w-full bg-white shadow'}>
<div className={'grid grid-cols-12 font-semibold border-b px-4 py-2'}>
<div className={'col-span-9 flex items-center gap-2'}>Data</div>
<div className={'col-span-1'}>Length</div>
<div className={'col-span-2 text-right'}>Time</div>
</div>
<div style={{ height: '100%', maxWidth: 700, overflowY: 'auto' }}>
{socketMsgList.map((msg) => (
<Row msg={msg} key={msg.timestamp} />
))}
</div>
</div>
);
}
function MsgDirection({ dir }: { dir: 'up' | 'down' }) {
return (
<Icon
name={dir === 'up' ? 'arrow-up' : 'arrow-down'}
size="12"
color={dir === 'up' ? 'red' : 'main'}
/>
);
}
function Row({ msg }) {
const [isOpen, setIsOpen] = React.useState(false);
return (
<>
<div
className={`border-b grid grid-cols-12 ${
msg.data.length > 100 ? 'hover:bg-active-blue cursor-pointer' : ''
}`}
onClick={() => (msg.data.length > 100 ? setIsOpen(!isOpen) : null)}
style={{ width: 700 }}
>
<div className={'col-span-9 flex items-center gap-2 p-2'}>
<MsgDirection dir={msg.dir} />
<span className={'bg-active-blue px-2 py-1'}>{msg.messageType}</span>
<span
className={'overflow-hidden text-ellipsis whitespace-nowrap'}
style={{ maxHeight: 44 }}
>
{msg.data}
</span>
{msg.data.length > 100 ? (
<div
className={
'rounded-full font-bold text-xl p-2 bg-white w-6 h-6 flex items-center justify-center'
}
>
<span>{isOpen ? '-' : '+'}</span>
</div>
) : null}
</div>
<div className={'col-span-1 p-2'}>{msg.data.length}</div>
<div className={'col-span-2 p-2 text-right'}>{durationFromMs(msg.time, true)}</div>
</div>
{isOpen ? (
<div className={'w-full h-fit bg-active-blue pl-6 pb-4 pr-2'}>
<div
style={{ maxHeight: "100%", overflow: "auto" }}
className={'whitespace-pre-wrap'}
>
{msg.data}
</div>
</div>
) : null}
</>
);
}
export default WSModal;

View file

@ -44,10 +44,10 @@ export function durationFromMsFormatted(ms: number): string {
return durationFormatted(Duration.fromMillis(ms || 0)); return durationFormatted(Duration.fromMillis(ms || 0));
} }
export function durationFromMs(ms: number): string { export function durationFromMs(ms: number, isFull?: boolean): string {
const dur = Duration.fromMillis(ms) const dur = Duration.fromMillis(ms)
return dur.toFormat('hh:mm:ss') return dur.toFormat(`hh:mm:ss${ isFull ? '.SSS' : '' }`)
} }
export const durationFormattedFull = (duration: Duration): string => { export const durationFormattedFull = (duration: Duration): string => {

View file

@ -10,7 +10,7 @@ const SIMPLE_LIST_NAMES = [
"frustrations", "frustrations",
"performance" "performance"
] as const ] as const
const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack" ] as const const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack", "websocket" ] as const
const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const

View file

@ -226,6 +226,9 @@ export default class IOSMessageManager implements IMessageManager {
case MType.IosNetworkCall: case MType.IosNetworkCall:
this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart))
break; break;
case MType.WsChannel:
this.lists.lists.websocket.insert(msg)
break;
case MType.IosEvent: case MType.IosEvent:
// @ts-ignore // @ts-ignore
this.lists.lists.event.insert({...msg, source: 'openreplay'}); this.lists.lists.event.insert({...msg, source: 'openreplay'});

View file

@ -4,7 +4,7 @@ import type { Timed } from '../common/types';
const SIMPLE_LIST_NAMES = [ "event", "redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles", "frustrations"] 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" ] as const const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack", "websocket" ] as const
//const entityNamesSimple = [ "event", "profile" ]; //const entityNamesSimple = [ "event", "profile" ];
const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const

View file

@ -1,12 +1,21 @@
import type { Store } from "Player"; import type { Store } from 'Player';
import { getResourceFromNetworkRequest, getResourceFromResourceTiming, Log, ResourceType } from "Player"; import {
import ListWalker from "Player/common/ListWalker"; getResourceFromNetworkRequest,
import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE, InitialLists, State as ListsState } from "Player/web/Lists"; getResourceFromResourceTiming,
import CanvasManager from "Player/web/managers/CanvasManager"; Log,
import { VElement } from "Player/web/managers/DOM/VirtualDOM"; ResourceType,
import PagesManager from "Player/web/managers/PagesManager"; } from 'Player';
import PerformanceTrackManager from "Player/web/managers/PerformanceTrackManager"; import ListWalker from 'Player/common/ListWalker';
import WindowNodeCounter from "Player/web/managers/WindowNodeCounter"; import Lists, {
INITIAL_STATE as LISTS_INITIAL_STATE,
InitialLists,
State as ListsState,
} from 'Player/web/Lists';
import CanvasManager from 'Player/web/managers/CanvasManager';
import { VElement } from 'Player/web/managers/DOM/VirtualDOM';
import PagesManager from 'Player/web/managers/PagesManager';
import PerformanceTrackManager from 'Player/web/managers/PerformanceTrackManager';
import WindowNodeCounter from 'Player/web/managers/WindowNodeCounter';
import { import {
CanvasNode, CanvasNode,
ConnectionInformation, ConnectionInformation,
@ -15,22 +24,22 @@ import {
ResourceTiming, ResourceTiming,
SetPageLocation, SetPageLocation,
SetViewportScroll, SetViewportScroll,
SetViewportSize SetViewportSize,
} from "Player/web/messages"; } from 'Player/web/messages';
import { isDOMType } from "Player/web/messages/filters.gen"; import { isDOMType } from 'Player/web/messages/filters.gen';
import Screen from "Player/web/Screen/Screen"; import Screen from 'Player/web/Screen/Screen';
// @ts-ignore // @ts-ignore
import { Decoder } from "syncod"; import { Decoder } from 'syncod';
import { TYPES as EVENT_TYPES } from "Types/session/event"; import { TYPES as EVENT_TYPES } from 'Types/session/event';
import type { PerformanceChartPoint } from "./managers/PerformanceTrackManager"; import type { PerformanceChartPoint } from './managers/PerformanceTrackManager';
export interface TabState extends ListsState { export interface TabState extends ListsState {
performanceAvailability?: PerformanceTrackManager['availability'] performanceAvailability?: PerformanceTrackManager['availability'];
performanceChartData: PerformanceChartPoint[], performanceChartData: PerformanceChartPoint[];
performanceChartTime: PerformanceChartPoint[] performanceChartTime: PerformanceChartPoint[];
cssLoading: boolean cssLoading: boolean;
location: string location: string;
urlsList: SetPageLocation[] urlsList: SetPageLocation[];
} }
/** /**
@ -46,10 +55,10 @@ export default class TabSessionManager {
cssLoading: false, cssLoading: false,
location: '', location: '',
urlsList: [], urlsList: [],
} };
public locationManager: ListWalker<SetPageLocation> = new ListWalker(); public locationManager: ListWalker<SetPageLocation> = new ListWalker();
private locationEventManager: ListWalker<any>/*<LocationEvent>*/ = new ListWalker(); private locationEventManager: ListWalker<any> /*<LocationEvent>*/ = new ListWalker();
private loadedLocationManager: ListWalker<SetPageLocation> = new ListWalker(); private loadedLocationManager: ListWalker<SetPageLocation> = new ListWalker();
private connectionInfoManger: ListWalker<ConnectionInformation> = new ListWalker(); private connectionInfoManger: ListWalker<ConnectionInformation> = new ListWalker();
private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager();
@ -61,8 +70,10 @@ export default class TabSessionManager {
public readonly decoder = new Decoder(); public readonly decoder = new Decoder();
private lists: Lists; private lists: Lists;
private navigationStartOffset = 0 private navigationStartOffset = 0;
private canvasManagers: { [key: string]: { manager: CanvasManager, start: number, running: boolean } } = {} private canvasManagers: {
[key: string]: { manager: CanvasManager; start: number; running: boolean };
} = {};
private canvasReplayWalker: ListWalker<CanvasNode> = new ListWalker(); private canvasReplayWalker: ListWalker<CanvasNode> = new ListWalker();
constructor( constructor(
@ -70,36 +81,37 @@ export default class TabSessionManager {
private readonly state: Store<{ tabStates: { [tabId: string]: TabState } }>, private readonly state: Store<{ tabStates: { [tabId: string]: TabState } }>,
private readonly screen: Screen, private readonly screen: Screen,
private readonly id: string, private readonly id: string,
private readonly setSize: ({ height, width }: { height: number, width: number }) => void, private readonly setSize: ({ height, width }: { height: number; width: number }) => void,
private readonly sessionStart: number, private readonly sessionStart: number,
initialLists?: Partial<InitialLists>, initialLists?: Partial<InitialLists>
) { ) {
this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading) this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading);
this.lists = new Lists(initialLists) this.lists = new Lists(initialLists);
initialLists?.event?.forEach((e: Record<string, string>) => { // TODO: to one of "Movable" module initialLists?.event?.forEach((e: Record<string, string>) => {
// TODO: to one of "Movable" module
if (e.type === EVENT_TYPES.LOCATION) { if (e.type === EVENT_TYPES.LOCATION) {
this.locationEventManager.append(e); this.locationEventManager.append(e);
} }
}) });
} }
public getNode = (id: number) => { public getNode = (id: number) => {
return this.pagesManager.getNode(id) return this.pagesManager.getNode(id);
} };
public updateLists(lists: Partial<InitialLists>) { public updateLists(lists: Partial<InitialLists>) {
Object.keys(lists).forEach((key: 'event' | 'stack' | 'exceptions') => { Object.keys(lists).forEach((key: 'event' | 'stack' | 'exceptions') => {
const currentList = this.lists.lists[key] const currentList = this.lists.lists[key];
lists[key]!.forEach(item => currentList.insert(item)) lists[key]!.forEach((item) => currentList.insert(item));
}) });
lists?.event?.forEach((e: Record<string, string>) => { lists?.event?.forEach((e: Record<string, string>) => {
if (e.type === EVENT_TYPES.LOCATION) { if (e.type === EVENT_TYPES.LOCATION) {
this.locationEventManager.append(e); this.locationEventManager.append(e);
} }
}) });
const eventCount = lists?.event?.length || 0 const eventCount = lists?.event?.length || 0;
const currentState = this.state.get() const currentState = this.state.get();
this.state.update({ this.state.update({
// @ts-ignore comes from parent state // @ts-ignore comes from parent state
eventCount: currentState.eventCount + eventCount, eventCount: currentState.eventCount + eventCount,
@ -108,9 +120,9 @@ export default class TabSessionManager {
[this.id]: { [this.id]: {
...currentState.tabStates[this.id], ...currentState.tabStates[this.id],
...this.lists.getFullListsState(), ...this.lists.getFullListsState(),
} },
} },
}) });
} }
updateLocalState(state: Partial<TabState>) { updateLocalState(state: Partial<TabState>) {
@ -119,22 +131,22 @@ export default class TabSessionManager {
...this.state.get().tabStates, ...this.state.get().tabStates,
[this.id]: { [this.id]: {
...this.state.get().tabStates[this.id], ...this.state.get().tabStates[this.id],
...state ...state,
} },
} },
}) });
} }
private setCSSLoading = (cssLoading: boolean) => { private setCSSLoading = (cssLoading: boolean) => {
this.screen.displayFrame(!cssLoading) this.screen.displayFrame(!cssLoading);
this.updateLocalState({ this.updateLocalState({
cssLoading cssLoading,
}) });
this.state.update({ this.state.update({
// @ts-ignore // @ts-ignore
ready: !this.state.get().messagesLoading && !cssLoading ready: !this.state.get().messagesLoading && !cssLoading,
}) });
} };
public resetMessageManagers() { public resetMessageManagers() {
this.locationEventManager = new ListWalker(); this.locationEventManager = new ListWalker();
@ -144,12 +156,11 @@ export default class TabSessionManager {
this.scrollManager = new ListWalker(); this.scrollManager = new ListWalker();
this.resizeManager = new ListWalker(); this.resizeManager = new ListWalker();
this.performanceTrackManager = new PerformanceTrackManager() this.performanceTrackManager = new PerformanceTrackManager();
this.windowNodeCounter = new WindowNodeCounter(); this.windowNodeCounter = new WindowNodeCounter();
this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this.setCSSLoading) this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this.setCSSLoading);
} }
distributeMessage(msg: Message): void { distributeMessage(msg: Message): void {
switch (msg.tp) { switch (msg.tp) {
case MType.CanvasNode: case MType.CanvasNode:
@ -166,7 +177,6 @@ export default class TabSessionManager {
); );
this.canvasManagers[managerId] = { manager, start: msg.timestamp, running: false }; this.canvasManagers[managerId] = { manager, start: msg.timestamp, running: false };
this.canvasReplayWalker.append(msg); this.canvasReplayWalker.append(msg);
} }
break; break;
case MType.SetPageLocation: case MType.SetPageLocation:
@ -185,7 +195,7 @@ export default class TabSessionManager {
this.performanceTrackManager.append(msg); this.performanceTrackManager.append(msg);
break; break;
case MType.SetPageVisibility: case MType.SetPageVisibility:
this.performanceTrackManager.handleVisibility(msg) this.performanceTrackManager.handleVisibility(msg);
break; break;
case MType.ConnectionInformation: case MType.ConnectionInformation:
this.connectionInfoManger.append(msg); this.connectionInfoManger.append(msg);
@ -199,18 +209,23 @@ export default class TabSessionManager {
this.lists.lists.log.append( this.lists.lists.log.append(
// @ts-ignore : TODO: enums in the message schema // @ts-ignore : TODO: enums in the message schema
Log(msg) Log(msg)
) );
break; break;
case MType.ResourceTimingDeprecated: case MType.ResourceTimingDeprecated:
case MType.ResourceTiming: case MType.ResourceTiming:
// TODO: merge `resource` and `fetch` lists into one here instead of UI // TODO: merge `resource` and `fetch` lists into one here instead of UI
if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) { if (msg.initiator !== ResourceType.FETCH && msg.initiator !== ResourceType.XHR) {
this.lists.lists.resource.insert(getResourceFromResourceTiming(msg as ResourceTiming, this.sessionStart)) this.lists.lists.resource.insert(
getResourceFromResourceTiming(msg as ResourceTiming, this.sessionStart)
);
} }
break; break;
case MType.Fetch: case MType.Fetch:
case MType.NetworkRequest: case MType.NetworkRequest:
this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart)) this.lists.lists.fetch.insert(getResourceFromNetworkRequest(msg, this.sessionStart));
break;
case MType.WsChannel:
this.lists.lists.websocket.insert(msg);
break; break;
case MType.Redux: case MType.Redux:
this.lists.lists.redux.append(msg); this.lists.lists.redux.append(msg);
@ -222,8 +237,8 @@ export default class TabSessionManager {
this.lists.lists.vuex.append(msg); this.lists.lists.vuex.append(msg);
break; break;
case MType.Zustand: case MType.Zustand:
this.lists.lists.zustand.append(msg) this.lists.lists.zustand.append(msg);
break break;
case MType.MobX: case MType.MobX:
this.lists.lists.mobx.append(msg); this.lists.lists.mobx.append(msg);
break; break;
@ -254,8 +269,8 @@ export default class TabSessionManager {
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
break; break;
} }
this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) this.performanceTrackManager.addNodeCountPointIfNeed(msg.time);
isDOMType(msg.tp) && this.pagesManager.appendMessage(msg) isDOMType(msg.tp) && this.pagesManager.appendMessage(msg);
break; break;
} }
} }
@ -274,13 +289,13 @@ export default class TabSessionManager {
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: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db)
value: llEvent.domContentLoadedTime, value: llEvent.domContentLoadedTime,
} };
} }
if (llEvent.loadTime != null) { if (llEvent.loadTime != null) {
stateToUpdate.loadTime = { stateToUpdate.loadTime = {
time: llEvent.loadTime + this.navigationStartOffset, time: llEvent.loadTime + this.navigationStartOffset,
value: llEvent.loadTime, value: llEvent.loadTime,
} };
} }
if (llEvent.domBuildingTime != null) { if (llEvent.domBuildingTime != null) {
stateToUpdate.domBuildingTime = llEvent.domBuildingTime; stateToUpdate.domBuildingTime = llEvent.domBuildingTime;
@ -290,7 +305,7 @@ export default class TabSessionManager {
const lastLocationMsg = this.locationManager.moveGetLast(t, index); const lastLocationMsg = this.locationManager.moveGetLast(t, index);
if (!!lastLocationMsg) { if (!!lastLocationMsg) {
// @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 // ConnectionInformation message is not used at this moment
// const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); // const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index);
@ -303,40 +318,42 @@ export default class TabSessionManager {
stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time;
} }
Object.assign(stateToUpdate, this.lists.moveGetState(t)) Object.assign(stateToUpdate, this.lists.moveGetState(t));
Object.keys(stateToUpdate).length > 0 && this.updateLocalState(stateToUpdate); Object.keys(stateToUpdate).length > 0 && this.updateLocalState(stateToUpdate);
/* Sequence of the managers is important here */ /* Sequence of the managers is important here */
// Preparing the size of "screen" // Preparing the size of "screen"
const lastResize = this.resizeManager.moveGetLast(t, index); const lastResize = this.resizeManager.moveGetLast(t, index);
if (!!lastResize) { if (!!lastResize) {
this.setSize(lastResize) this.setSize(lastResize);
} }
this.pagesManager.moveReady(t).then(() => { this.pagesManager.moveReady(t).then(() => {
const lastScroll = this.scrollManager.moveGetLast(t, index); const lastScroll = this.scrollManager.moveGetLast(t, index);
if (!!lastScroll && this.screen.window) { if (!!lastScroll && this.screen.window) {
this.screen.window.scrollTo(lastScroll.x, lastScroll.y); this.screen.window.scrollTo(lastScroll.x, lastScroll.y);
} }
const canvasMsg = this.canvasReplayWalker.moveGetLast(t) const canvasMsg = this.canvasReplayWalker.moveGetLast(t);
if (canvasMsg) { if (canvasMsg) {
this.canvasManagers[`${canvasMsg.timestamp}_${canvasMsg.nodeId}`].manager.startVideo(); this.canvasManagers[`${canvasMsg.timestamp}_${canvasMsg.nodeId}`].manager.startVideo();
this.canvasManagers[`${canvasMsg.timestamp}_${canvasMsg.nodeId}`].running = true; this.canvasManagers[`${canvasMsg.timestamp}_${canvasMsg.nodeId}`].running = true;
} }
const runningManagers = Object.keys(this.canvasManagers).filter((key) => this.canvasManagers[key].running); const runningManagers = Object.keys(this.canvasManagers).filter(
(key) => this.canvasManagers[key].running
);
runningManagers.forEach((key) => { runningManagers.forEach((key) => {
const manager = this.canvasManagers[key].manager; const manager = this.canvasManagers[key].manager;
manager.move(t); manager.move(t);
}) });
}) });
} }
public decodeMessage(msg: Message) { public decodeMessage(msg: Message) {
return this.decoder.decode(msg) return this.decoder.decode(msg);
} }
public _sortMessagesHack = (msgs: Message[]) => { public _sortMessagesHack = (msgs: Message[]) => {
// @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) // @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 headChildrenIds = msgs.filter((m) => m.parentID === 1).map((m) => m.id);
this.pagesManager.sortPages((m1, m2) => { this.pagesManager.sortPages((m1, m2) => {
if (m1.time === m2.time) { if (m1.time === m2.time) {
if (m1.tp === MType.RemoveNode && m2.tp !== MType.RemoveNode) { if (m1.tp === MType.RemoveNode && m2.tp !== MType.RemoveNode) {
@ -347,7 +364,7 @@ export default class TabSessionManager {
if (headChildrenIds.includes(m2.id)) { if (headChildrenIds.includes(m2.id)) {
return 1; return 1;
} }
} else if (m2.tp === MType.RemoveNode && m1.tp === MType.RemoveNode) { } else if (m2.tp === MType.RemoveNode && m1.tp === MType.RemoveNode) {
const m1FromHead = headChildrenIds.includes(m1.id); const m1FromHead = headChildrenIds.includes(m1.id);
const m2FromHead = headChildrenIds.includes(m2.id); const m2FromHead = headChildrenIds.includes(m2.id);
if (m1FromHead && !m2FromHead) { if (m1FromHead && !m2FromHead) {
@ -358,25 +375,25 @@ export default class TabSessionManager {
} }
} }
return 0; return 0;
}) });
} };
public onFileReadSuccess = () => { public onFileReadSuccess = () => {
const stateToUpdate : Partial<Record<string,any>> = { const stateToUpdate: Partial<Record<string, any>> = {
performanceChartData: this.performanceTrackManager.chartData, performanceChartData: this.performanceTrackManager.chartData,
performanceAvailability: this.performanceTrackManager.availability, performanceAvailability: this.performanceTrackManager.availability,
urlsList: this.locationManager.list, urlsList: this.locationManager.list,
...this.lists.getFullListsState(), ...this.lists.getFullListsState(),
} };
this.updateLocalState(stateToUpdate) this.updateLocalState(stateToUpdate);
} };
public getListsFullState = () => { public getListsFullState = () => {
return this.lists.getFullListsState() return this.lists.getFullListsState();
} };
clean() { clean() {
this.pagesManager.reset() this.pagesManager.reset();
} }
} }

View file

@ -651,6 +651,24 @@ export default class RawMessageReader extends PrimitiveReader {
}; };
} }
case 84: {
const chType = this.readString(); if (chType === null) { return resetPointer() }
const channelName = this.readString(); if (channelName === null) { return resetPointer() }
const data = this.readString(); if (data === null) { return resetPointer() }
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
const dir = this.readString(); if (dir === null) { return resetPointer() }
const messageType = this.readString(); if (messageType === null) { return resetPointer() }
return {
tp: MType.WsChannel,
chType,
channelName,
data,
timestamp,
dir,
messageType,
};
}
case 113: { case 113: {
const selectionStart = this.readUint(); if (selectionStart === null) { return resetPointer() } const selectionStart = this.readUint(); if (selectionStart === null) { return resetPointer() }
const selectionEnd = this.readUint(); if (selectionEnd === null) { return resetPointer() } const selectionEnd = this.readUint(); if (selectionEnd === null) { return resetPointer() }

View file

@ -56,6 +56,7 @@ import type {
RawAdoptedSsRemoveOwner, RawAdoptedSsRemoveOwner,
RawZustand, RawZustand,
RawNetworkRequest, RawNetworkRequest,
RawWsChannel,
RawSelectionChange, RawSelectionChange,
RawMouseThrashing, RawMouseThrashing,
RawResourceTiming, RawResourceTiming,
@ -181,6 +182,8 @@ export type Zustand = RawZustand & Timed
export type NetworkRequest = RawNetworkRequest & Timed export type NetworkRequest = RawNetworkRequest & Timed
export type WsChannel = RawWsChannel & Timed
export type SelectionChange = RawSelectionChange & Timed export type SelectionChange = RawSelectionChange & Timed
export type MouseThrashing = RawMouseThrashing & Timed export type MouseThrashing = RawMouseThrashing & Timed

View file

@ -54,6 +54,7 @@ export const enum MType {
AdoptedSsRemoveOwner = 77, AdoptedSsRemoveOwner = 77,
Zustand = 79, Zustand = 79,
NetworkRequest = 83, NetworkRequest = 83,
WsChannel = 84,
SelectionChange = 113, SelectionChange = 113,
MouseThrashing = 114, MouseThrashing = 114,
ResourceTiming = 116, ResourceTiming = 116,
@ -441,6 +442,16 @@ export interface RawNetworkRequest {
transferredBodySize: number, transferredBodySize: number,
} }
export interface RawWsChannel {
tp: MType.WsChannel,
chType: string,
channelName: string,
data: string,
timestamp: number,
dir: string,
messageType: string,
}
export interface RawSelectionChange { export interface RawSelectionChange {
tp: MType.SelectionChange, tp: MType.SelectionChange,
selectionStart: number, selectionStart: number,
@ -575,4 +586,4 @@ export interface RawIosIssueEvent {
} }
export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent; export type RawMessage = RawTimestamp | RawSetPageLocation | RawSetViewportSize | RawSetViewportScroll | RawCreateDocument | RawCreateElementNode | RawCreateTextNode | RawMoveNode | RawRemoveNode | RawSetNodeAttribute | RawRemoveNodeAttribute | RawSetNodeData | RawSetCssData | RawSetNodeScroll | RawSetInputValue | RawSetInputChecked | RawMouseMove | RawNetworkRequestDeprecated | RawConsoleLog | RawCssInsertRule | RawCssDeleteRule | RawFetch | RawProfiler | RawOTable | RawRedux | RawVuex | RawMobX | RawNgRx | RawGraphQl | RawPerformanceTrack | RawStringDict | RawSetNodeAttributeDict | RawResourceTimingDeprecated | RawConnectionInformation | RawSetPageVisibility | RawLoadFontFace | RawSetNodeFocus | RawLongTask | RawSetNodeAttributeURLBased | RawSetCssDataURLBased | RawCssInsertRuleURLBased | RawMouseClick | RawCreateIFrameDocument | RawAdoptedSsReplaceURLBased | RawAdoptedSsReplace | RawAdoptedSsInsertRuleURLBased | RawAdoptedSsInsertRule | RawAdoptedSsDeleteRule | RawAdoptedSsAddOwner | RawAdoptedSsRemoveOwner | RawZustand | RawNetworkRequest | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent;

View file

@ -55,6 +55,7 @@ export const TP_MAP = {
77: MType.AdoptedSsRemoveOwner, 77: MType.AdoptedSsRemoveOwner,
79: MType.Zustand, 79: MType.Zustand,
83: MType.NetworkRequest, 83: MType.NetworkRequest,
84: MType.WsChannel,
113: MType.SelectionChange, 113: MType.SelectionChange,
114: MType.MouseThrashing, 114: MType.MouseThrashing,
116: MType.ResourceTiming, 116: MType.ResourceTiming,

View file

@ -442,6 +442,16 @@ type TrNetworkRequest = [
transferredBodySize: number, transferredBodySize: number,
] ]
type TrWSChannel = [
type: 84,
chType: string,
channelName: string,
data: string,
timestamp: number,
dir: string,
messageType: string,
]
type TrInputChange = [ type TrInputChange = [
type: 112, type: 112,
id: number, id: number,
@ -500,7 +510,7 @@ type TrCanvasNode = [
] ]
export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode export type TrackerMessage = TrTimestamp | TrSetPageLocation | TrSetViewportSize | TrSetViewportScroll | TrCreateDocument | TrCreateElementNode | TrCreateTextNode | TrMoveNode | TrRemoveNode | TrSetNodeAttribute | TrRemoveNodeAttribute | TrSetNodeData | TrSetNodeScroll | TrSetInputTarget | TrSetInputValue | TrSetInputChecked | TrMouseMove | TrNetworkRequestDeprecated | TrConsoleLog | TrPageLoadTiming | TrPageRenderTiming | TrCustomEvent | TrUserID | TrUserAnonymousID | TrMetadata | TrCSSInsertRule | TrCSSDeleteRule | TrFetch | TrProfiler | TrOTable | TrStateAction | TrRedux | TrVuex | TrMobX | TrNgRx | TrGraphQL | TrPerformanceTrack | TrStringDict | TrSetNodeAttributeDict | TrResourceTimingDeprecated | TrConnectionInformation | TrSetPageVisibility | TrLoadFontFace | TrSetNodeFocus | TrLongTask | TrSetNodeAttributeURLBased | TrSetCSSDataURLBased | TrTechnicalInfo | TrCustomIssue | TrCSSInsertRuleURLBased | TrMouseClick | TrCreateIFrameDocument | TrAdoptedSSReplaceURLBased | TrAdoptedSSInsertRuleURLBased | TrAdoptedSSDeleteRule | TrAdoptedSSAddOwner | TrAdoptedSSRemoveOwner | TrJSException | TrZustand | TrBatchMetadata | TrPartitionedMessage | TrNetworkRequest | TrWSChannel | TrInputChange | TrSelectionChange | TrMouseThrashing | TrUnbindNodes | TrResourceTiming | TrTabChange | TrTabData | TrCanvasNode
export default function translate(tMsg: TrackerMessage): RawMessage | null { export default function translate(tMsg: TrackerMessage): RawMessage | null {
switch(tMsg[0]) { switch(tMsg[0]) {
@ -952,6 +962,18 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
} }
} }
case 84: {
return {
tp: MType.WsChannel,
chType: tMsg[1],
channelName: tMsg[2],
data: tMsg[3],
timestamp: tMsg[4],
dir: tMsg[5],
messageType: tMsg[6],
}
}
case 113: { case 113: {
return { return {
tp: MType.SelectionChange, tp: MType.SelectionChange,

View file

@ -9,6 +9,7 @@ export const enum ResourceType {
CSS = 'css', CSS = 'css',
IMG = 'img', IMG = 'img',
MEDIA = 'media', MEDIA = 'media',
WS = 'websocket',
OTHER = 'other', OTHER = 'other',
} }

View file

@ -458,6 +458,15 @@ message 83, 'NetworkRequest', :replayer => :devtools do
uint 'TransferredBodySize' uint 'TransferredBodySize'
end end
message 84, 'WSChannel', :replayer => :devtools do
string 'ChType'
string 'ChannelName'
string 'Data'
uint 'Timestamp'
string 'Dir'
string 'MessageType'
end
# 90-111 reserved iOS # 90-111 reserved iOS
message 112, 'InputChange', :replayer => false do message 112, 'InputChange', :replayer => false do

View file

@ -1,7 +1,7 @@
{ {
"name": "@openreplay/tracker", "name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package", "description": "The OpenReplay tracker main package",
"version": "11.0.2", "version": "11.0.0-beta.7",
"keywords": [ "keywords": [
"logging", "logging",
"replay" "replay"

View file

@ -64,6 +64,7 @@ export declare const enum Type {
BatchMetadata = 81, BatchMetadata = 81,
PartitionedMessage = 82, PartitionedMessage = 82,
NetworkRequest = 83, NetworkRequest = 83,
WSChannel = 84,
InputChange = 112, InputChange = 112,
SelectionChange = 113, SelectionChange = 113,
MouseThrashing = 114, MouseThrashing = 114,
@ -512,6 +513,16 @@ export type NetworkRequest = [
/*transferredBodySize:*/ number, /*transferredBodySize:*/ number,
] ]
export type WSChannel = [
/*type:*/ Type.WSChannel,
/*chType:*/ string,
/*channelName:*/ string,
/*data:*/ string,
/*timestamp:*/ number,
/*dir:*/ string,
/*messageType:*/ string,
]
export type InputChange = [ export type InputChange = [
/*type:*/ Type.InputChange, /*type:*/ Type.InputChange,
/*id:*/ number, /*id:*/ number,
@ -570,5 +581,5 @@ export type CanvasNode = [
] ]
type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetNodeScroll | SetInputTarget | SetInputValue | SetInputChecked | MouseMove | NetworkRequestDeprecated | ConsoleLog | PageLoadTiming | PageRenderTiming | CustomEvent | UserID | UserAnonymousID | Metadata | CSSInsertRule | CSSDeleteRule | Fetch | Profiler | OTable | StateAction | Redux | Vuex | MobX | NgRx | GraphQL | PerformanceTrack | StringDict | SetNodeAttributeDict | ResourceTimingDeprecated | ConnectionInformation | SetPageVisibility | LoadFontFace | SetNodeFocus | LongTask | SetNodeAttributeURLBased | SetCSSDataURLBased | TechnicalInfo | CustomIssue | CSSInsertRuleURLBased | MouseClick | CreateIFrameDocument | AdoptedSSReplaceURLBased | AdoptedSSInsertRuleURLBased | AdoptedSSDeleteRule | AdoptedSSAddOwner | AdoptedSSRemoveOwner | JSException | Zustand | BatchMetadata | PartitionedMessage | NetworkRequest | WSChannel | InputChange | SelectionChange | MouseThrashing | UnbindNodes | ResourceTiming | TabChange | TabData | CanvasNode
export default Message export default Message

View file

@ -1,5 +1,13 @@
import type Message from './messages.gen.js' import type Message from './messages.gen.js'
import { Timestamp, Metadata, UserID, Type as MType, TabChange, TabData } from './messages.gen.js' import {
Timestamp,
Metadata,
UserID,
Type as MType,
TabChange,
TabData,
WSChannel,
} from './messages.gen.js'
import { import {
now, now,
adjustTimeOrigin, adjustTimeOrigin,
@ -812,6 +820,26 @@ export default class App {
return this.session.getTabId() return this.session.getTabId()
} }
/**
* Creates a named hook that expects event name, data string and msg direction (up/down),
* it will skip any message bigger than 5 mb or event name bigger than 255 symbols
* @returns {(msgType: string, data: string, dir: 'up' | 'down') => void}
* */
trackWs(channelName: string): (msgType: string, data: string, dir: 'up' | 'down') => void {
const channel = channelName
return (msgType: string, data: string, dir: 'up' | 'down' = 'down') => {
if (
typeof msgType !== 'string' ||
typeof data !== 'string' ||
data.length > 5 * 1024 * 1024 ||
msgType.length > 255
) {
return
}
this.send(WSChannel('websocket', channel, data, this.timestamp(), dir, msgType))
}
}
stop(stopWorker = true): void { stop(stopWorker = true): void {
if (this.activityState !== ActivityState.NotActive) { if (this.activityState !== ActivityState.NotActive) {
try { try {

View file

@ -817,6 +817,25 @@ export function NetworkRequest(
] ]
} }
export function WSChannel(
chType: string,
channelName: string,
data: string,
timestamp: number,
dir: string,
messageType: string,
): Messages.WSChannel {
return [
Messages.Type.WSChannel,
chType,
channelName,
data,
timestamp,
dir,
messageType,
]
}
export function InputChange( export function InputChange(
id: number, id: number,
value: string, value: string,

View file

@ -234,6 +234,20 @@ export default class API {
return this.app.active() return this.app.active()
} }
/**
* Creates a named hook that expects event name, data string and msg direction (up/down),
* it will skip any message bigger than 5 mb or event name bigger than 255 symbols
* msg direction is "down" (incoming) by default
*
* @returns {(msgType: string, data: string, dir: 'up' | 'down') => void}
* */
trackWs(channelName: string) {
if (this.app === null) {
return
}
return this.app.trackWs(channelName)
}
start(startOpts?: Partial<StartOptions>): Promise<StartPromiseReturn> { start(startOpts?: Partial<StartOptions>): Promise<StartPromiseReturn> {
if (!IN_BROWSER) { if (!IN_BROWSER) {
console.error( console.error(

View file

@ -258,6 +258,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.string(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) && this.uint(msg[8]) && this.uint(msg[9]) return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4]) && this.string(msg[5]) && this.uint(msg[6]) && this.uint(msg[7]) && this.uint(msg[8]) && this.uint(msg[9])
break break
case Messages.Type.WSChannel:
return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.uint(msg[4]) && this.string(msg[5]) && this.string(msg[6])
break
case Messages.Type.InputChange: case Messages.Type.InputChange:
return this.uint(msg[1]) && this.string(msg[2]) && this.boolean(msg[3]) && this.string(msg[4]) && this.int(msg[5]) && this.int(msg[6]) return this.uint(msg[1]) && this.string(msg[2]) && this.boolean(msg[3]) && this.string(msg[4]) && this.int(msg[5]) && this.int(msg[6])
break break