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:
parent
7a5e2be138
commit
9e1add4ad9
27 changed files with 809 additions and 386 deletions
|
|
@ -77,6 +77,7 @@ const (
|
|||
MsgBatchMetadata = 81
|
||||
MsgPartitionedMessage = 82
|
||||
MsgNetworkRequest = 83
|
||||
MsgWSChannel = 84
|
||||
MsgInputChange = 112
|
||||
MsgSelectionChange = 113
|
||||
MsgMouseThrashing = 114
|
||||
|
|
@ -109,7 +110,6 @@ const (
|
|||
MsgIOSIssueEvent = 111
|
||||
)
|
||||
|
||||
|
||||
type Timestamp struct {
|
||||
message
|
||||
Timestamp uint64
|
||||
|
|
@ -276,7 +276,6 @@ func (msg *SetViewportScroll) TypeID() int {
|
|||
|
||||
type CreateDocument struct {
|
||||
message
|
||||
|
||||
}
|
||||
|
||||
func (msg *CreateDocument) Encode() []byte {
|
||||
|
|
@ -2069,6 +2068,37 @@ func (msg *NetworkRequest) TypeID() int {
|
|||
return 83
|
||||
}
|
||||
|
||||
type WSChannel struct {
|
||||
message
|
||||
ChType string
|
||||
ChannelName string
|
||||
Data string
|
||||
Timestamp uint64
|
||||
Dir string
|
||||
MessageType string
|
||||
}
|
||||
|
||||
func (msg *WSChannel) Encode() []byte {
|
||||
buf := make([]byte, 61+len(msg.ChType)+len(msg.ChannelName)+len(msg.Data)+len(msg.Dir)+len(msg.MessageType))
|
||||
buf[0] = 84
|
||||
p := 1
|
||||
p = WriteString(msg.ChType, buf, p)
|
||||
p = WriteString(msg.ChannelName, buf, p)
|
||||
p = WriteString(msg.Data, buf, p)
|
||||
p = WriteUint(msg.Timestamp, buf, p)
|
||||
p = WriteString(msg.Dir, buf, p)
|
||||
p = WriteString(msg.MessageType, buf, p)
|
||||
return buf[:p]
|
||||
}
|
||||
|
||||
func (msg *WSChannel) Decode() Message {
|
||||
return msg
|
||||
}
|
||||
|
||||
func (msg *WSChannel) TypeID() int {
|
||||
return 84
|
||||
}
|
||||
|
||||
type InputChange struct {
|
||||
message
|
||||
ID uint64
|
||||
|
|
@ -2908,4 +2938,3 @@ func (msg *IOSIssueEvent) Decode() Message {
|
|||
func (msg *IOSIssueEvent) TypeID() int {
|
||||
return 111
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1254,6 +1254,30 @@ func DecodeNetworkRequest(reader BytesReader) (Message, error) {
|
|||
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) {
|
||||
var err error = nil
|
||||
msg := &InputChange{}
|
||||
|
|
@ -1991,6 +2015,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
|||
return DecodePartitionedMessage(reader)
|
||||
case 83:
|
||||
return DecodeNetworkRequest(reader)
|
||||
case 84:
|
||||
return DecodeWSChannel(reader)
|
||||
case 112:
|
||||
return DecodeInputChange(reader)
|
||||
case 113:
|
||||
|
|
|
|||
|
|
@ -723,6 +723,18 @@ class NetworkRequest(Message):
|
|||
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):
|
||||
__id__ = 112
|
||||
|
||||
|
|
|
|||
|
|
@ -1069,6 +1069,25 @@ cdef class NetworkRequest(PyMessage):
|
|||
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 public int __id__
|
||||
cdef public unsigned long id
|
||||
|
|
|
|||
|
|
@ -660,6 +660,16 @@ class MessageCodec(Codec):
|
|||
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:
|
||||
return InputChange(
|
||||
id=self.read_uint(reader),
|
||||
|
|
|
|||
|
|
@ -758,6 +758,16 @@ cdef class MessageCodec:
|
|||
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:
|
||||
return InputChange(
|
||||
id=self.read_uint(reader),
|
||||
|
|
|
|||
|
|
@ -9,14 +9,15 @@ function EventsList({ scale }: { scale: number }) {
|
|||
|
||||
const { tabStates, eventCount } = store.get();
|
||||
const events = React.useMemo(() => {
|
||||
return Object.values(tabStates)[0]?.eventList || [];
|
||||
return Object.values(tabStates)[0]?.eventList.filter(e => e.time) || [];
|
||||
}, [eventCount]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{events.map((e) => (
|
||||
<div
|
||||
/*@ts-ignore TODO */
|
||||
key={e.key}
|
||||
key={`${e.key}_${e.time}`}
|
||||
className={stl.event}
|
||||
style={{ left: `${getTimelinePosition(e.time, scale)}%` }}
|
||||
/>
|
||||
|
|
@ -35,7 +36,7 @@ function MobileEventsList({ scale }: { scale: number }) {
|
|||
{events.map((e) => (
|
||||
<div
|
||||
/*@ts-ignore TODO */
|
||||
key={e.key}
|
||||
key={`${e.key}_${e.time}`}
|
||||
className={stl.event}
|
||||
style={{ left: `${getTimelinePosition(e.time, scale)}%` }}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import BottomBlock from '../BottomBlock';
|
|||
import InfoLine from '../BottomBlock/InfoLine';
|
||||
import useAutoscroll, { getLastItemTime } from '../useAutoscroll';
|
||||
import { useRegExListFilterMemo, useTabListFilterMemo } from '../useListFilter';
|
||||
import WSModal from './WSModal'
|
||||
|
||||
const INDEX_KEY = 'network';
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ const CSS = 'css';
|
|||
const IMG = 'img';
|
||||
const MEDIA = 'media';
|
||||
const OTHER = 'other';
|
||||
const WS = 'websocket';
|
||||
|
||||
const TYPE_TO_TAB = {
|
||||
[ResourceType.XHR]: XHR,
|
||||
|
|
@ -36,10 +38,11 @@ const TYPE_TO_TAB = {
|
|||
[ResourceType.CSS]: CSS,
|
||||
[ResourceType.IMG]: IMG,
|
||||
[ResourceType.MEDIA]: MEDIA,
|
||||
[ResourceType.WS]: WS,
|
||||
[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) => ({
|
||||
text: tab === 'xhr' ? 'Fetch/XHR' : tab,
|
||||
key: tab,
|
||||
|
|
@ -156,6 +159,8 @@ function NetworkPanelCont({ startedAt, panelHeight }: { startedAt: number; panel
|
|||
resourceList = [],
|
||||
fetchListNow = [],
|
||||
resourceListNow = [],
|
||||
websocketList = [],
|
||||
websocketListNow = [],
|
||||
} = tabStates[currentTab];
|
||||
|
||||
return (
|
||||
|
|
@ -170,11 +175,19 @@ function NetworkPanelCont({ startedAt, panelHeight }: { startedAt: number; panel
|
|||
resourceListNow={resourceListNow}
|
||||
player={player}
|
||||
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 domContentLoadedTime = undefined;
|
||||
|
|
@ -185,6 +198,8 @@ function MobileNetworkPanelCont({ startedAt, panelHeight }: { startedAt: number,
|
|||
resourceList = [],
|
||||
fetchListNow = [],
|
||||
resourceListNow = [],
|
||||
websocketList = [],
|
||||
websocketListNow = [],
|
||||
} = store.get();
|
||||
|
||||
return (
|
||||
|
|
@ -200,10 +215,22 @@ function MobileNetworkPanelCont({ startedAt, panelHeight }: { startedAt: number,
|
|||
resourceListNow={resourceListNow}
|
||||
player={player}
|
||||
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 {
|
||||
domContentLoadedTime?: {
|
||||
time: number;
|
||||
|
|
@ -218,6 +245,8 @@ interface Props {
|
|||
resourceList: Timed[];
|
||||
fetchListNow: Timed[];
|
||||
resourceListNow: Timed[];
|
||||
websocketList: Array<WSMessage>;
|
||||
websocketListNow: Array<WSMessage>;
|
||||
player: WebPlayer | MobilePlayer;
|
||||
startedAt: number;
|
||||
isMobile?: boolean;
|
||||
|
|
@ -237,6 +266,7 @@ const NetworkPanelComp = observer(
|
|||
startedAt,
|
||||
isMobile,
|
||||
panelHeight,
|
||||
websocketList,
|
||||
}: Props) => {
|
||||
const { showModal } = useModal();
|
||||
const [sortBy, setSortBy] = useState('time');
|
||||
|
|
@ -251,6 +281,14 @@ const NetworkPanelComp = observer(
|
|||
const activeTab = devTools[INDEX_KEY].activeTab;
|
||||
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(
|
||||
() =>
|
||||
// TODO: better merge (with body size info) - do it in player
|
||||
|
|
@ -283,8 +321,20 @@ const NetworkPanelComp = observer(
|
|||
})
|
||||
)
|
||||
.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),
|
||||
[resourceList.length, fetchList.length]
|
||||
[resourceList.length, fetchList.length, socketList]
|
||||
);
|
||||
|
||||
let filteredList = useMemo(() => {
|
||||
|
|
@ -354,6 +404,18 @@ const NetworkPanelComp = observer(
|
|||
}, [domContentLoadedTime, loadTime]);
|
||||
|
||||
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);
|
||||
showModal(
|
||||
<FetchDetailsModal
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -44,10 +44,10 @@ export function durationFromMsFormatted(ms: number): string {
|
|||
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)
|
||||
|
||||
return dur.toFormat('hh:mm:ss')
|
||||
return dur.toFormat(`hh:mm:ss${ isFull ? '.SSS' : '' }`)
|
||||
}
|
||||
|
||||
export const durationFormattedFull = (duration: Duration): string => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const SIMPLE_LIST_NAMES = [
|
|||
"frustrations",
|
||||
"performance"
|
||||
] 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
|
||||
|
||||
|
|
@ -226,6 +226,9 @@ export default class IOSMessageManager implements IMessageManager {
|
|||
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'});
|
||||
|
|
|
|||
|
|
@ -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 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 LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ] as const
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
import type { Store } from "Player";
|
||||
import { getResourceFromNetworkRequest, getResourceFromResourceTiming, Log, ResourceType } from "Player";
|
||||
import ListWalker from "Player/common/ListWalker";
|
||||
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 type { Store } from 'Player';
|
||||
import {
|
||||
getResourceFromNetworkRequest,
|
||||
getResourceFromResourceTiming,
|
||||
Log,
|
||||
ResourceType,
|
||||
} from 'Player';
|
||||
import ListWalker from 'Player/common/ListWalker';
|
||||
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 {
|
||||
CanvasNode,
|
||||
ConnectionInformation,
|
||||
|
|
@ -15,22 +24,22 @@ import {
|
|||
ResourceTiming,
|
||||
SetPageLocation,
|
||||
SetViewportScroll,
|
||||
SetViewportSize
|
||||
} from "Player/web/messages";
|
||||
import { isDOMType } from "Player/web/messages/filters.gen";
|
||||
import Screen from "Player/web/Screen/Screen";
|
||||
SetViewportSize,
|
||||
} from 'Player/web/messages';
|
||||
import { isDOMType } from 'Player/web/messages/filters.gen';
|
||||
import Screen from 'Player/web/Screen/Screen';
|
||||
// @ts-ignore
|
||||
import { Decoder } from "syncod";
|
||||
import { TYPES as EVENT_TYPES } from "Types/session/event";
|
||||
import type { PerformanceChartPoint } from "./managers/PerformanceTrackManager";
|
||||
import { Decoder } from 'syncod';
|
||||
import { TYPES as EVENT_TYPES } from 'Types/session/event';
|
||||
import type { PerformanceChartPoint } from './managers/PerformanceTrackManager';
|
||||
|
||||
export interface TabState extends ListsState {
|
||||
performanceAvailability?: PerformanceTrackManager['availability']
|
||||
performanceChartData: PerformanceChartPoint[],
|
||||
performanceChartTime: PerformanceChartPoint[]
|
||||
cssLoading: boolean
|
||||
location: string
|
||||
urlsList: SetPageLocation[]
|
||||
performanceAvailability?: PerformanceTrackManager['availability'];
|
||||
performanceChartData: PerformanceChartPoint[];
|
||||
performanceChartTime: PerformanceChartPoint[];
|
||||
cssLoading: boolean;
|
||||
location: string;
|
||||
urlsList: SetPageLocation[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,10 +55,10 @@ export default class TabSessionManager {
|
|||
cssLoading: false,
|
||||
location: '',
|
||||
urlsList: [],
|
||||
}
|
||||
};
|
||||
|
||||
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 connectionInfoManger: ListWalker<ConnectionInformation> = new ListWalker();
|
||||
private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager();
|
||||
|
|
@ -61,8 +70,10 @@ export default class TabSessionManager {
|
|||
|
||||
public readonly decoder = new Decoder();
|
||||
private lists: Lists;
|
||||
private navigationStartOffset = 0
|
||||
private canvasManagers: { [key: string]: { manager: CanvasManager, start: number, running: boolean } } = {}
|
||||
private navigationStartOffset = 0;
|
||||
private canvasManagers: {
|
||||
[key: string]: { manager: CanvasManager; start: number; running: boolean };
|
||||
} = {};
|
||||
private canvasReplayWalker: ListWalker<CanvasNode> = new ListWalker();
|
||||
|
||||
constructor(
|
||||
|
|
@ -70,36 +81,37 @@ export default class TabSessionManager {
|
|||
private readonly state: Store<{ tabStates: { [tabId: string]: TabState } }>,
|
||||
private readonly screen: Screen,
|
||||
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,
|
||||
initialLists?: Partial<InitialLists>,
|
||||
initialLists?: Partial<InitialLists>
|
||||
) {
|
||||
this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading)
|
||||
this.lists = new Lists(initialLists)
|
||||
initialLists?.event?.forEach((e: Record<string, string>) => { // TODO: to one of "Movable" module
|
||||
this.pagesManager = new PagesManager(screen, this.session.isMobile, this.setCSSLoading);
|
||||
this.lists = new Lists(initialLists);
|
||||
initialLists?.event?.forEach((e: Record<string, string>) => {
|
||||
// TODO: to one of "Movable" module
|
||||
if (e.type === EVENT_TYPES.LOCATION) {
|
||||
this.locationEventManager.append(e);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
public getNode = (id: number) => {
|
||||
return this.pagesManager.getNode(id)
|
||||
}
|
||||
return this.pagesManager.getNode(id);
|
||||
};
|
||||
|
||||
public updateLists(lists: Partial<InitialLists>) {
|
||||
Object.keys(lists).forEach((key: 'event' | 'stack' | 'exceptions') => {
|
||||
const currentList = this.lists.lists[key]
|
||||
lists[key]!.forEach(item => currentList.insert(item))
|
||||
})
|
||||
const currentList = this.lists.lists[key];
|
||||
lists[key]!.forEach((item) => currentList.insert(item));
|
||||
});
|
||||
lists?.event?.forEach((e: Record<string, string>) => {
|
||||
if (e.type === EVENT_TYPES.LOCATION) {
|
||||
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({
|
||||
// @ts-ignore comes from parent state
|
||||
eventCount: currentState.eventCount + eventCount,
|
||||
|
|
@ -108,9 +120,9 @@ export default class TabSessionManager {
|
|||
[this.id]: {
|
||||
...currentState.tabStates[this.id],
|
||||
...this.lists.getFullListsState(),
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateLocalState(state: Partial<TabState>) {
|
||||
|
|
@ -119,22 +131,22 @@ export default class TabSessionManager {
|
|||
...this.state.get().tabStates,
|
||||
[this.id]: {
|
||||
...this.state.get().tabStates[this.id],
|
||||
...state
|
||||
}
|
||||
}
|
||||
})
|
||||
...state,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private setCSSLoading = (cssLoading: boolean) => {
|
||||
this.screen.displayFrame(!cssLoading)
|
||||
this.screen.displayFrame(!cssLoading);
|
||||
this.updateLocalState({
|
||||
cssLoading
|
||||
})
|
||||
cssLoading,
|
||||
});
|
||||
this.state.update({
|
||||
// @ts-ignore
|
||||
ready: !this.state.get().messagesLoading && !cssLoading
|
||||
})
|
||||
}
|
||||
ready: !this.state.get().messagesLoading && !cssLoading,
|
||||
});
|
||||
};
|
||||
|
||||
public resetMessageManagers() {
|
||||
this.locationEventManager = new ListWalker();
|
||||
|
|
@ -144,12 +156,11 @@ export default class TabSessionManager {
|
|||
this.scrollManager = new ListWalker();
|
||||
this.resizeManager = new ListWalker();
|
||||
|
||||
this.performanceTrackManager = new PerformanceTrackManager()
|
||||
this.performanceTrackManager = new PerformanceTrackManager();
|
||||
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 {
|
||||
switch (msg.tp) {
|
||||
case MType.CanvasNode:
|
||||
|
|
@ -166,7 +177,6 @@ export default class TabSessionManager {
|
|||
);
|
||||
this.canvasManagers[managerId] = { manager, start: msg.timestamp, running: false };
|
||||
this.canvasReplayWalker.append(msg);
|
||||
|
||||
}
|
||||
break;
|
||||
case MType.SetPageLocation:
|
||||
|
|
@ -185,7 +195,7 @@ export default class TabSessionManager {
|
|||
this.performanceTrackManager.append(msg);
|
||||
break;
|
||||
case MType.SetPageVisibility:
|
||||
this.performanceTrackManager.handleVisibility(msg)
|
||||
this.performanceTrackManager.handleVisibility(msg);
|
||||
break;
|
||||
case MType.ConnectionInformation:
|
||||
this.connectionInfoManger.append(msg);
|
||||
|
|
@ -199,18 +209,23 @@ export default class TabSessionManager {
|
|||
this.lists.lists.log.append(
|
||||
// @ts-ignore : TODO: enums in the message schema
|
||||
Log(msg)
|
||||
)
|
||||
);
|
||||
break;
|
||||
case MType.ResourceTimingDeprecated:
|
||||
case MType.ResourceTiming:
|
||||
// TODO: merge `resource` and `fetch` lists into one here instead of UI
|
||||
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;
|
||||
case MType.Fetch:
|
||||
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;
|
||||
case MType.Redux:
|
||||
this.lists.lists.redux.append(msg);
|
||||
|
|
@ -222,8 +237,8 @@ export default class TabSessionManager {
|
|||
this.lists.lists.vuex.append(msg);
|
||||
break;
|
||||
case MType.Zustand:
|
||||
this.lists.lists.zustand.append(msg)
|
||||
break
|
||||
this.lists.lists.zustand.append(msg);
|
||||
break;
|
||||
case MType.MobX:
|
||||
this.lists.lists.mobx.append(msg);
|
||||
break;
|
||||
|
|
@ -254,8 +269,8 @@ export default class TabSessionManager {
|
|||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
}
|
||||
this.performanceTrackManager.addNodeCountPointIfNeed(msg.time)
|
||||
isDOMType(msg.tp) && this.pagesManager.appendMessage(msg)
|
||||
this.performanceTrackManager.addNodeCountPointIfNeed(msg.time);
|
||||
isDOMType(msg.tp) && this.pagesManager.appendMessage(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -274,13 +289,13 @@ export default class TabSessionManager {
|
|||
stateToUpdate.domContentLoadedTime = {
|
||||
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,
|
||||
}
|
||||
};
|
||||
}
|
||||
if (llEvent.loadTime != null) {
|
||||
stateToUpdate.loadTime = {
|
||||
time: llEvent.loadTime + this.navigationStartOffset,
|
||||
value: llEvent.loadTime,
|
||||
}
|
||||
};
|
||||
}
|
||||
if (llEvent.domBuildingTime != null) {
|
||||
stateToUpdate.domBuildingTime = llEvent.domBuildingTime;
|
||||
|
|
@ -290,7 +305,7 @@ export default class TabSessionManager {
|
|||
const lastLocationMsg = this.locationManager.moveGetLast(t, index);
|
||||
if (!!lastLocationMsg) {
|
||||
// @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);
|
||||
|
|
@ -303,40 +318,42 @@ export default class TabSessionManager {
|
|||
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);
|
||||
|
||||
/* Sequence of the managers is important here */
|
||||
// Preparing the size of "screen"
|
||||
const lastResize = this.resizeManager.moveGetLast(t, index);
|
||||
if (!!lastResize) {
|
||||
this.setSize(lastResize)
|
||||
this.setSize(lastResize);
|
||||
}
|
||||
this.pagesManager.moveReady(t).then(() => {
|
||||
const lastScroll = this.scrollManager.moveGetLast(t, index);
|
||||
if (!!lastScroll && this.screen.window) {
|
||||
this.screen.window.scrollTo(lastScroll.x, lastScroll.y);
|
||||
}
|
||||
const canvasMsg = this.canvasReplayWalker.moveGetLast(t)
|
||||
const canvasMsg = this.canvasReplayWalker.moveGetLast(t);
|
||||
if (canvasMsg) {
|
||||
this.canvasManagers[`${canvasMsg.timestamp}_${canvasMsg.nodeId}`].manager.startVideo();
|
||||
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) => {
|
||||
const manager = this.canvasManagers[key].manager;
|
||||
manager.move(t);
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public decodeMessage(msg: Message) {
|
||||
return this.decoder.decode(msg)
|
||||
return this.decoder.decode(msg);
|
||||
}
|
||||
|
||||
public _sortMessagesHack = (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 headChildrenIds = 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) {
|
||||
|
|
@ -358,25 +375,25 @@ export default class TabSessionManager {
|
|||
}
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
public onFileReadSuccess = () => {
|
||||
const stateToUpdate : Partial<Record<string,any>> = {
|
||||
const stateToUpdate: Partial<Record<string, any>> = {
|
||||
performanceChartData: this.performanceTrackManager.chartData,
|
||||
performanceAvailability: this.performanceTrackManager.availability,
|
||||
urlsList: this.locationManager.list,
|
||||
...this.lists.getFullListsState(),
|
||||
}
|
||||
};
|
||||
|
||||
this.updateLocalState(stateToUpdate)
|
||||
}
|
||||
this.updateLocalState(stateToUpdate);
|
||||
};
|
||||
|
||||
public getListsFullState = () => {
|
||||
return this.lists.getFullListsState()
|
||||
}
|
||||
return this.lists.getFullListsState();
|
||||
};
|
||||
|
||||
clean() {
|
||||
this.pagesManager.reset()
|
||||
this.pagesManager.reset();
|
||||
}
|
||||
}
|
||||
|
|
@ -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: {
|
||||
const selectionStart = this.readUint(); if (selectionStart === null) { return resetPointer() }
|
||||
const selectionEnd = this.readUint(); if (selectionEnd === null) { return resetPointer() }
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import type {
|
|||
RawAdoptedSsRemoveOwner,
|
||||
RawZustand,
|
||||
RawNetworkRequest,
|
||||
RawWsChannel,
|
||||
RawSelectionChange,
|
||||
RawMouseThrashing,
|
||||
RawResourceTiming,
|
||||
|
|
@ -181,6 +182,8 @@ export type Zustand = RawZustand & Timed
|
|||
|
||||
export type NetworkRequest = RawNetworkRequest & Timed
|
||||
|
||||
export type WsChannel = RawWsChannel & Timed
|
||||
|
||||
export type SelectionChange = RawSelectionChange & Timed
|
||||
|
||||
export type MouseThrashing = RawMouseThrashing & Timed
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export const enum MType {
|
|||
AdoptedSsRemoveOwner = 77,
|
||||
Zustand = 79,
|
||||
NetworkRequest = 83,
|
||||
WsChannel = 84,
|
||||
SelectionChange = 113,
|
||||
MouseThrashing = 114,
|
||||
ResourceTiming = 116,
|
||||
|
|
@ -441,6 +442,16 @@ export interface RawNetworkRequest {
|
|||
transferredBodySize: number,
|
||||
}
|
||||
|
||||
export interface RawWsChannel {
|
||||
tp: MType.WsChannel,
|
||||
chType: string,
|
||||
channelName: string,
|
||||
data: string,
|
||||
timestamp: number,
|
||||
dir: string,
|
||||
messageType: string,
|
||||
}
|
||||
|
||||
export interface RawSelectionChange {
|
||||
tp: MType.SelectionChange,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ export const TP_MAP = {
|
|||
77: MType.AdoptedSsRemoveOwner,
|
||||
79: MType.Zustand,
|
||||
83: MType.NetworkRequest,
|
||||
84: MType.WsChannel,
|
||||
113: MType.SelectionChange,
|
||||
114: MType.MouseThrashing,
|
||||
116: MType.ResourceTiming,
|
||||
|
|
|
|||
|
|
@ -442,6 +442,16 @@ type TrNetworkRequest = [
|
|||
transferredBodySize: number,
|
||||
]
|
||||
|
||||
type TrWSChannel = [
|
||||
type: 84,
|
||||
chType: string,
|
||||
channelName: string,
|
||||
data: string,
|
||||
timestamp: number,
|
||||
dir: string,
|
||||
messageType: string,
|
||||
]
|
||||
|
||||
type TrInputChange = [
|
||||
type: 112,
|
||||
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 {
|
||||
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: {
|
||||
return {
|
||||
tp: MType.SelectionChange,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const enum ResourceType {
|
|||
CSS = 'css',
|
||||
IMG = 'img',
|
||||
MEDIA = 'media',
|
||||
WS = 'websocket',
|
||||
OTHER = 'other',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -458,6 +458,15 @@ message 83, 'NetworkRequest', :replayer => :devtools do
|
|||
uint 'TransferredBodySize'
|
||||
end
|
||||
|
||||
message 84, 'WSChannel', :replayer => :devtools do
|
||||
string 'ChType'
|
||||
string 'ChannelName'
|
||||
string 'Data'
|
||||
uint 'Timestamp'
|
||||
string 'Dir'
|
||||
string 'MessageType'
|
||||
end
|
||||
|
||||
# 90-111 reserved iOS
|
||||
|
||||
message 112, 'InputChange', :replayer => false do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "11.0.2",
|
||||
"version": "11.0.0-beta.7",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ export declare const enum Type {
|
|||
BatchMetadata = 81,
|
||||
PartitionedMessage = 82,
|
||||
NetworkRequest = 83,
|
||||
WSChannel = 84,
|
||||
InputChange = 112,
|
||||
SelectionChange = 113,
|
||||
MouseThrashing = 114,
|
||||
|
|
@ -512,6 +513,16 @@ export type NetworkRequest = [
|
|||
/*transferredBodySize:*/ number,
|
||||
]
|
||||
|
||||
export type WSChannel = [
|
||||
/*type:*/ Type.WSChannel,
|
||||
/*chType:*/ string,
|
||||
/*channelName:*/ string,
|
||||
/*data:*/ string,
|
||||
/*timestamp:*/ number,
|
||||
/*dir:*/ string,
|
||||
/*messageType:*/ string,
|
||||
]
|
||||
|
||||
export type InputChange = [
|
||||
/*type:*/ Type.InputChange,
|
||||
/*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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
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 {
|
||||
now,
|
||||
adjustTimeOrigin,
|
||||
|
|
@ -812,6 +820,26 @@ export default class App {
|
|||
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 {
|
||||
if (this.activityState !== ActivityState.NotActive) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
id: number,
|
||||
value: string,
|
||||
|
|
|
|||
|
|
@ -234,6 +234,20 @@ export default class API {
|
|||
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> {
|
||||
if (!IN_BROWSER) {
|
||||
console.error(
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
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:
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue