move redux plugin hashing to worker thread, update redux panel look and style
* feat tracker moving redux stuff to worker thread * feat ui: sync redux messages to action time * feat ui: starting new redux ui * fix backend mob gen * feat tracker moving redux stuff to worker thread * feat ui: sync redux messages to action time * feat ui: starting new redux ui * fix backend mob gen * styles, third party etc * rm dead code * design fixes * wrapper around old redux stuff * prettier * icon sw * some changes to default style * some code style fixes
This commit is contained in:
parent
d58a356f1f
commit
5421aedfe6
43 changed files with 3336 additions and 3147 deletions
|
|
@ -40,7 +40,7 @@ const (
|
||||||
MsgProfiler = 40
|
MsgProfiler = 40
|
||||||
MsgOTable = 41
|
MsgOTable = 41
|
||||||
MsgStateAction = 42
|
MsgStateAction = 42
|
||||||
MsgRedux = 44
|
MsgReduxDeprecated = 44
|
||||||
MsgVuex = 45
|
MsgVuex = 45
|
||||||
MsgMobX = 46
|
MsgMobX = 46
|
||||||
MsgNgRx = 47
|
MsgNgRx = 47
|
||||||
|
|
@ -87,6 +87,7 @@ const (
|
||||||
MsgTabData = 118
|
MsgTabData = 118
|
||||||
MsgCanvasNode = 119
|
MsgCanvasNode = 119
|
||||||
MsgTagTrigger = 120
|
MsgTagTrigger = 120
|
||||||
|
MsgRedux = 121
|
||||||
MsgIssueEvent = 125
|
MsgIssueEvent = 125
|
||||||
MsgSessionEnd = 126
|
MsgSessionEnd = 126
|
||||||
MsgSessionSearch = 127
|
MsgSessionSearch = 127
|
||||||
|
|
@ -1104,14 +1105,14 @@ func (msg *StateAction) TypeID() int {
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
|
|
||||||
type Redux struct {
|
type ReduxDeprecated struct {
|
||||||
message
|
message
|
||||||
Action string
|
Action string
|
||||||
State string
|
State string
|
||||||
Duration uint64
|
Duration uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *Redux) Encode() []byte {
|
func (msg *ReduxDeprecated) Encode() []byte {
|
||||||
buf := make([]byte, 31+len(msg.Action)+len(msg.State))
|
buf := make([]byte, 31+len(msg.Action)+len(msg.State))
|
||||||
buf[0] = 44
|
buf[0] = 44
|
||||||
p := 1
|
p := 1
|
||||||
|
|
@ -1121,11 +1122,11 @@ func (msg *Redux) Encode() []byte {
|
||||||
return buf[:p]
|
return buf[:p]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *Redux) Decode() Message {
|
func (msg *ReduxDeprecated) Decode() Message {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *Redux) TypeID() int {
|
func (msg *ReduxDeprecated) TypeID() int {
|
||||||
return 44
|
return 44
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2323,6 +2324,33 @@ func (msg *TagTrigger) TypeID() int {
|
||||||
return 120
|
return 120
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Redux struct {
|
||||||
|
message
|
||||||
|
Action string
|
||||||
|
State string
|
||||||
|
Duration uint64
|
||||||
|
ActionTime uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Redux) Encode() []byte {
|
||||||
|
buf := make([]byte, 41+len(msg.Action)+len(msg.State))
|
||||||
|
buf[0] = 121
|
||||||
|
p := 1
|
||||||
|
p = WriteString(msg.Action, buf, p)
|
||||||
|
p = WriteString(msg.State, buf, p)
|
||||||
|
p = WriteUint(msg.Duration, buf, p)
|
||||||
|
p = WriteUint(msg.ActionTime, buf, p)
|
||||||
|
return buf[:p]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Redux) Decode() Message {
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *Redux) TypeID() int {
|
||||||
|
return 121
|
||||||
|
}
|
||||||
|
|
||||||
type IssueEvent struct {
|
type IssueEvent struct {
|
||||||
message
|
message
|
||||||
MessageID uint64
|
MessageID uint64
|
||||||
|
|
|
||||||
|
|
@ -639,9 +639,9 @@ func DecodeStateAction(reader BytesReader) (Message, error) {
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeRedux(reader BytesReader) (Message, error) {
|
func DecodeReduxDeprecated(reader BytesReader) (Message, error) {
|
||||||
var err error = nil
|
var err error = nil
|
||||||
msg := &Redux{}
|
msg := &ReduxDeprecated{}
|
||||||
if msg.Action, err = reader.ReadString(); err != nil {
|
if msg.Action, err = reader.ReadString(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1410,6 +1410,24 @@ func DecodeTagTrigger(reader BytesReader) (Message, error) {
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DecodeRedux(reader BytesReader) (Message, error) {
|
||||||
|
var err error = nil
|
||||||
|
msg := &Redux{}
|
||||||
|
if msg.Action, err = reader.ReadString(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if msg.State, err = reader.ReadString(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if msg.Duration, err = reader.ReadUint(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if msg.ActionTime, err = reader.ReadUint(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
|
||||||
func DecodeIssueEvent(reader BytesReader) (Message, error) {
|
func DecodeIssueEvent(reader BytesReader) (Message, error) {
|
||||||
var err error = nil
|
var err error = nil
|
||||||
msg := &IssueEvent{}
|
msg := &IssueEvent{}
|
||||||
|
|
@ -1951,7 +1969,7 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
||||||
case 42:
|
case 42:
|
||||||
return DecodeStateAction(reader)
|
return DecodeStateAction(reader)
|
||||||
case 44:
|
case 44:
|
||||||
return DecodeRedux(reader)
|
return DecodeReduxDeprecated(reader)
|
||||||
case 45:
|
case 45:
|
||||||
return DecodeVuex(reader)
|
return DecodeVuex(reader)
|
||||||
case 46:
|
case 46:
|
||||||
|
|
@ -2044,6 +2062,8 @@ func ReadMessage(t uint64, reader BytesReader) (Message, error) {
|
||||||
return DecodeCanvasNode(reader)
|
return DecodeCanvasNode(reader)
|
||||||
case 120:
|
case 120:
|
||||||
return DecodeTagTrigger(reader)
|
return DecodeTagTrigger(reader)
|
||||||
|
case 121:
|
||||||
|
return DecodeRedux(reader)
|
||||||
case 125:
|
case 125:
|
||||||
return DecodeIssueEvent(reader)
|
return DecodeIssueEvent(reader)
|
||||||
case 126:
|
case 126:
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,8 @@ ignore:
|
||||||
- "**/*/dist/**"
|
- "**/*/dist/**"
|
||||||
- "**/*/build/**"
|
- "**/*/build/**"
|
||||||
- "**/*/.test.*"
|
- "**/*/.test.*"
|
||||||
- "**/*/version.ts"
|
- "**/*/version.ts"
|
||||||
|
review:
|
||||||
|
poem: false
|
||||||
|
review_status: false
|
||||||
|
collapse_walkthrough: true
|
||||||
|
|
@ -370,7 +370,7 @@ class StateAction(Message):
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
|
|
||||||
class Redux(Message):
|
class ReduxDeprecated(Message):
|
||||||
__id__ = 44
|
__id__ = 44
|
||||||
|
|
||||||
def __init__(self, action, state, duration):
|
def __init__(self, action, state, duration):
|
||||||
|
|
@ -815,6 +815,16 @@ class TagTrigger(Message):
|
||||||
self.tag_id = tag_id
|
self.tag_id = tag_id
|
||||||
|
|
||||||
|
|
||||||
|
class Redux(Message):
|
||||||
|
__id__ = 121
|
||||||
|
|
||||||
|
def __init__(self, action, state, duration, action_time):
|
||||||
|
self.action = action
|
||||||
|
self.state = state
|
||||||
|
self.duration = duration
|
||||||
|
self.action_time = action_time
|
||||||
|
|
||||||
|
|
||||||
class IssueEvent(Message):
|
class IssueEvent(Message):
|
||||||
__id__ = 125
|
__id__ = 125
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -548,7 +548,7 @@ cdef class StateAction(PyMessage):
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
|
|
||||||
cdef class Redux(PyMessage):
|
cdef class ReduxDeprecated(PyMessage):
|
||||||
cdef public int __id__
|
cdef public int __id__
|
||||||
cdef public str action
|
cdef public str action
|
||||||
cdef public str state
|
cdef public str state
|
||||||
|
|
@ -1203,6 +1203,21 @@ cdef class TagTrigger(PyMessage):
|
||||||
self.tag_id = tag_id
|
self.tag_id = tag_id
|
||||||
|
|
||||||
|
|
||||||
|
cdef class Redux(PyMessage):
|
||||||
|
cdef public int __id__
|
||||||
|
cdef public str action
|
||||||
|
cdef public str state
|
||||||
|
cdef public unsigned long duration
|
||||||
|
cdef public unsigned long action_time
|
||||||
|
|
||||||
|
def __init__(self, str action, str state, unsigned long duration, unsigned long action_time):
|
||||||
|
self.__id__ = 121
|
||||||
|
self.action = action
|
||||||
|
self.state = state
|
||||||
|
self.duration = duration
|
||||||
|
self.action_time = action_time
|
||||||
|
|
||||||
|
|
||||||
cdef class IssueEvent(PyMessage):
|
cdef class IssueEvent(PyMessage):
|
||||||
cdef public int __id__
|
cdef public int __id__
|
||||||
cdef public unsigned long message_id
|
cdef public unsigned long message_id
|
||||||
|
|
|
||||||
|
|
@ -382,7 +382,7 @@ class MessageCodec(Codec):
|
||||||
)
|
)
|
||||||
|
|
||||||
if message_id == 44:
|
if message_id == 44:
|
||||||
return Redux(
|
return ReduxDeprecated(
|
||||||
action=self.read_string(reader),
|
action=self.read_string(reader),
|
||||||
state=self.read_string(reader),
|
state=self.read_string(reader),
|
||||||
duration=self.read_uint(reader)
|
duration=self.read_uint(reader)
|
||||||
|
|
@ -732,6 +732,14 @@ class MessageCodec(Codec):
|
||||||
tag_id=self.read_int(reader)
|
tag_id=self.read_int(reader)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if message_id == 121:
|
||||||
|
return Redux(
|
||||||
|
action=self.read_string(reader),
|
||||||
|
state=self.read_string(reader),
|
||||||
|
duration=self.read_uint(reader),
|
||||||
|
action_time=self.read_uint(reader)
|
||||||
|
)
|
||||||
|
|
||||||
if message_id == 125:
|
if message_id == 125:
|
||||||
return IssueEvent(
|
return IssueEvent(
|
||||||
message_id=self.read_uint(reader),
|
message_id=self.read_uint(reader),
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ cdef class MessageCodec:
|
||||||
)
|
)
|
||||||
|
|
||||||
if message_id == 44:
|
if message_id == 44:
|
||||||
return Redux(
|
return ReduxDeprecated(
|
||||||
action=self.read_string(reader),
|
action=self.read_string(reader),
|
||||||
state=self.read_string(reader),
|
state=self.read_string(reader),
|
||||||
duration=self.read_uint(reader)
|
duration=self.read_uint(reader)
|
||||||
|
|
@ -830,6 +830,14 @@ cdef class MessageCodec:
|
||||||
tag_id=self.read_int(reader)
|
tag_id=self.read_int(reader)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if message_id == 121:
|
||||||
|
return Redux(
|
||||||
|
action=self.read_string(reader),
|
||||||
|
state=self.read_string(reader),
|
||||||
|
duration=self.read_uint(reader),
|
||||||
|
action_time=self.read_uint(reader)
|
||||||
|
)
|
||||||
|
|
||||||
if message_id == 125:
|
if message_id == 125:
|
||||||
return IssueEvent(
|
return IssueEvent(
|
||||||
message_id=self.read_uint(reader),
|
message_id=self.read_uint(reader),
|
||||||
|
|
|
||||||
|
|
@ -1,221 +0,0 @@
|
||||||
import { useStore } from 'App/mstore';
|
|
||||||
import SummaryBlock from "Components/Session/Player/ReplayPlayer/SummaryBlock";
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { Icon, Tooltip } from 'UI';
|
|
||||||
import QueueControls from 'App/components/Session_/QueueControls';
|
|
||||||
import Bookmark from 'Shared/Bookmark';
|
|
||||||
import SharePopup from 'Shared/SharePopup/SharePopup';
|
|
||||||
import Issues from 'App/components/Session_/Issues/Issues';
|
|
||||||
import NotePopup from 'App/components/Session_/components/NotePopup';
|
|
||||||
import ItemMenu from 'App/components/Session_/components/HeaderMenu';
|
|
||||||
import { useModal } from 'App/components/Modal';
|
|
||||||
import BugReportModal from 'App/components/Session_/BugReport/BugReportModal';
|
|
||||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
|
||||||
import AutoplayToggle from 'Shared/AutoplayToggle';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import SessionTabs from 'Components/Session/Player/SharedComponents/SessionTabs';
|
|
||||||
import { IFRAME } from 'App/constants/storageKeys';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import { Switch } from 'antd';
|
|
||||||
|
|
||||||
const localhostWarn = (project: string): string => project + '_localhost_warn';
|
|
||||||
const disableDevtools = 'or_devtools_uxt_toggle';
|
|
||||||
|
|
||||||
function SubHeader(props: any) {
|
|
||||||
const localhostWarnKey = localhostWarn(props.siteId);
|
|
||||||
const defaultLocalhostWarn = localStorage.getItem(localhostWarnKey) !== '1';
|
|
||||||
const [showWarningModal, setWarning] = React.useState(defaultLocalhostWarn);
|
|
||||||
const { player, store } = React.useContext(PlayerContext);
|
|
||||||
const { width, height, endTime, location: currentLocation = 'loading...' } = store.get();
|
|
||||||
const hasIframe = localStorage.getItem(IFRAME) === 'true';
|
|
||||||
const { uxtestingStore } = useStore();
|
|
||||||
|
|
||||||
const enabledIntegration = useMemo(() => {
|
|
||||||
const { integrations } = props;
|
|
||||||
if (!integrations || !integrations.size) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return integrations.some((i: Record<string, any>) => i.token);
|
|
||||||
}, [props.integrations]);
|
|
||||||
|
|
||||||
const { showModal, hideModal } = useModal();
|
|
||||||
|
|
||||||
const location =
|
|
||||||
currentLocation && currentLocation.length > 70
|
|
||||||
? `${currentLocation.slice(0, 25)}...${currentLocation.slice(-40)}`
|
|
||||||
: currentLocation;
|
|
||||||
|
|
||||||
const showReportModal = () => {
|
|
||||||
const { tabStates, currentTab } = store.get();
|
|
||||||
const resourceList = tabStates[currentTab]?.resourceList || [];
|
|
||||||
const exceptionsList = tabStates[currentTab]?.exceptionsList || [];
|
|
||||||
const eventsList = tabStates[currentTab]?.eventList || [];
|
|
||||||
const graphqlList = tabStates[currentTab]?.graphqlList || [];
|
|
||||||
const fetchList = tabStates[currentTab]?.fetchList || [];
|
|
||||||
|
|
||||||
const mappedResourceList = resourceList
|
|
||||||
.filter((r) => r.isRed || r.isYellow)
|
|
||||||
// @ts-ignore
|
|
||||||
.concat(fetchList.filter((i) => parseInt(i.status) >= 400))
|
|
||||||
// @ts-ignore
|
|
||||||
.concat(graphqlList.filter((i) => parseInt(i.status) >= 400));
|
|
||||||
|
|
||||||
player.pause();
|
|
||||||
const xrayProps = {
|
|
||||||
currentLocation: currentLocation,
|
|
||||||
resourceList: mappedResourceList,
|
|
||||||
exceptionsList: exceptionsList,
|
|
||||||
eventsList: eventsList,
|
|
||||||
endTime: endTime,
|
|
||||||
};
|
|
||||||
showModal(
|
|
||||||
// @ts-ignore
|
|
||||||
<BugReportModal width={width} height={height} xrayProps={xrayProps} hideModal={hideModal} />,
|
|
||||||
{ right: true, width: 620 }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showSummary = () => {
|
|
||||||
player.pause();
|
|
||||||
showModal(<SummaryBlock sessionId={props.sessionId} />, { right: true, width: 330 })
|
|
||||||
}
|
|
||||||
|
|
||||||
const showWarning =
|
|
||||||
location && /(localhost)|(127.0.0.1)|(0.0.0.0)/.test(location) && showWarningModal;
|
|
||||||
const closeWarning = () => {
|
|
||||||
localStorage.setItem(localhostWarnKey, '1');
|
|
||||||
setWarning(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleDevtools = (enabled: boolean): void => {
|
|
||||||
localStorage.setItem(disableDevtools, enabled ? '0' : '1');
|
|
||||||
uxtestingStore.setHideDevtools(!enabled);
|
|
||||||
};
|
|
||||||
|
|
||||||
const additionalMenu = [
|
|
||||||
{
|
|
||||||
key: 1,
|
|
||||||
component: <AutoplayToggle />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 2,
|
|
||||||
component: <Bookmark noMargin sessionId={props.sessionId} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 3,
|
|
||||||
component: (
|
|
||||||
<div onClick={showReportModal} className={'flex items-center gap-2 p-3 text-black'}>
|
|
||||||
<Icon name={'file-pdf'} size={16} />
|
|
||||||
<div>Create Bug Report</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 4,
|
|
||||||
autoclose: true,
|
|
||||||
component: (
|
|
||||||
<SharePopup
|
|
||||||
entity="sessions"
|
|
||||||
id={props.sessionId}
|
|
||||||
showCopyLink={true}
|
|
||||||
trigger={
|
|
||||||
<div className={'flex items-center gap-2 p-3 text-black'}>
|
|
||||||
<Icon name={'share-alt'} size={16} />
|
|
||||||
<div>Share</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
if (enabledIntegration) {
|
|
||||||
additionalMenu.push({
|
|
||||||
key: 5,
|
|
||||||
component: <Issues isInline={true} sessionId={props.sessionId} />,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="w-full px-4 flex items-center border-b relative"
|
|
||||||
style={{
|
|
||||||
background: uxtestingStore.isUxt() ? (props.live ? '#F6FFED' : '#EBF4F5') : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showWarning ? (
|
|
||||||
<div
|
|
||||||
className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between"
|
|
||||||
style={{
|
|
||||||
zIndex: 999,
|
|
||||||
position: 'absolute',
|
|
||||||
left: '50%',
|
|
||||||
bottom: '-24px',
|
|
||||||
transform: 'translate(-50%, 0)',
|
|
||||||
fontWeight: 500,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Some assets may load incorrectly on localhost.
|
|
||||||
<a
|
|
||||||
href="https://docs.openreplay.com/en/troubleshooting/session-recordings/#testing-in-localhost"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="link ml-1"
|
|
||||||
>
|
|
||||||
Learn More
|
|
||||||
</a>
|
|
||||||
<div className="py-1 ml-3 cursor-pointer" onClick={closeWarning}>
|
|
||||||
<Icon name="close" size={16} color="black" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<SessionTabs />
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'ml-auto text-sm flex items-center color-gray-medium gap-2',
|
|
||||||
hasIframe ? 'opacity-50 pointer-events-none' : ''
|
|
||||||
)}
|
|
||||||
style={{ width: 'max-content' }}
|
|
||||||
>
|
|
||||||
<SummaryButton onClick={showSummary} />
|
|
||||||
<NotePopup />
|
|
||||||
<ItemMenu items={additionalMenu} useSc />
|
|
||||||
{uxtestingStore.isUxt() ? (
|
|
||||||
<Switch
|
|
||||||
checkedChildren={'DevTools'}
|
|
||||||
unCheckedChildren={'DevTools'}
|
|
||||||
onChange={toggleDevtools}
|
|
||||||
defaultChecked={!uxtestingStore.hideDevtools}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
{/* @ts-ignore */}
|
|
||||||
<QueueControls />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{location && (
|
|
||||||
<div className={'w-full bg-white border-b border-gray-lighter'}>
|
|
||||||
<div className="flex w-fit items-center cursor-pointer color-gray-medium text-sm p-1">
|
|
||||||
<Icon size="20" name="event/link" className="mr-1" />
|
|
||||||
<Tooltip title="Open in new tab" delay={0}>
|
|
||||||
<a href={currentLocation} target="_blank">
|
|
||||||
{location}
|
|
||||||
</a>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default connect((state: Record<string, any>) => ({
|
|
||||||
siteId: state.getIn(['site', 'siteId']),
|
|
||||||
integrations: state.getIn(['issues', 'list']),
|
|
||||||
modules: state.getIn(['user', 'account', 'modules']) || [],
|
|
||||||
}))(observer(SubHeader));
|
|
||||||
|
|
@ -3,7 +3,6 @@ import cn from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import Player from './PlayerInst';
|
import Player from './PlayerInst';
|
||||||
import SubHeader from 'Components/Session_/Subheader';
|
import SubHeader from 'Components/Session_/Subheader';
|
||||||
import AiSubheader from 'Components/Session/Player/ReplayPlayer/AiSubheader';
|
|
||||||
import styles from 'Components/Session_/playerBlock.module.css';
|
import styles from 'Components/Session_/playerBlock.module.css';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,24 @@ function WebPlayer(props: any) {
|
||||||
const [activeTab, setActiveTab] = useState('');
|
const [activeTab, setActiveTab] = useState('');
|
||||||
const [noteItem, setNoteItem] = useState<Note | undefined>(undefined);
|
const [noteItem, setNoteItem] = useState<Note | undefined>(undefined);
|
||||||
const [visuallyAdjusted, setAdjusted] = useState(false);
|
const [visuallyAdjusted, setAdjusted] = useState(false);
|
||||||
|
const [windowActive, setWindowActive] = useState(!document.hidden);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue);
|
const [contextValue, setContextValue] = useState<IPlayerContext>(defaultContextValue);
|
||||||
const params: { sessionId: string } = useParams();
|
const params: { sessionId: string } = useParams();
|
||||||
const [fullView, setFullView] = useState(false);
|
const [fullView, setFullView] = useState(false);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (windowActive) {
|
||||||
|
const handleActivation = () => {
|
||||||
|
if (!document.hidden) {
|
||||||
|
setWindowActive(true);
|
||||||
|
document.removeEventListener('visibilitychange', handleActivation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('visibilitychange', handleActivation);
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
playerInst = undefined;
|
playerInst = undefined;
|
||||||
if (!session.sessionId || contextValue.player !== undefined) return;
|
if (!session.sessionId || contextValue.player !== undefined) return;
|
||||||
|
|
@ -79,24 +92,32 @@ function WebPlayer(props: any) {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (noteItem !== undefined) {
|
if (noteItem !== undefined) {
|
||||||
contextValue.player.pause();
|
contextValue.player.pause();
|
||||||
}
|
} else {
|
||||||
|
if (activeTab === '' && ready && contextValue.player && windowActive) {
|
||||||
|
const jumpToTime = props.query.get('jumpto');
|
||||||
|
const shouldAdjustOffset = visualOffset !== 0 && !visuallyAdjusted;
|
||||||
|
|
||||||
if (activeTab === '' && !noteItem !== undefined && messagesProcessed && contextValue.player) {
|
if (jumpToTime || shouldAdjustOffset) {
|
||||||
const jumpToTime = props.query.get('jumpto');
|
if (jumpToTime > visualOffset) {
|
||||||
const shouldAdjustOffset = visualOffset !== 0 && !visuallyAdjusted;
|
contextValue.player.jump(parseInt(String(jumpToTime - startedAt)));
|
||||||
|
} else {
|
||||||
if (jumpToTime || shouldAdjustOffset) {
|
contextValue.player.jump(visualOffset);
|
||||||
if (jumpToTime > visualOffset) {
|
setAdjusted(true);
|
||||||
contextValue.player.jump(parseInt(String(jumpToTime - startedAt)));
|
}
|
||||||
} else {
|
|
||||||
contextValue.player.jump(visualOffset);
|
|
||||||
setAdjusted(true);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
contextValue.player.play();
|
contextValue.player.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [activeTab, noteItem, visualOffset, messagesProcessed]);
|
}, [activeTab, noteItem, visualOffset, ready, windowActive]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (cssLoading) {
|
||||||
|
contextValue.player?.pause();
|
||||||
|
} else if (ready) {
|
||||||
|
contextValue.player?.play();
|
||||||
|
}
|
||||||
|
}, [cssLoading, ready])
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (activeTab === 'Click Map') {
|
if (activeTab === 'Click Map') {
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,18 @@ import React from 'react';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import stl from './bottomBlock.module.css';
|
import stl from './bottomBlock.module.css';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
additionalHeight?: number;
|
||||||
|
}
|
||||||
|
|
||||||
const BottomBlock = ({
|
const BottomBlock = ({
|
||||||
children = null,
|
children = null,
|
||||||
className = '',
|
className = '',
|
||||||
additionalHeight = 0,
|
additionalHeight = 0,
|
||||||
...props
|
...props
|
||||||
}) => (
|
}: Props) => (
|
||||||
<div className={ cn(stl.wrapper, "flex flex-col mb-2") } { ...props } >
|
<div className={ cn(stl.wrapper, "flex flex-col mb-2") } { ...props } >
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2,7 +2,9 @@ import BottomBlock from './BottomBlock';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Content from './Content';
|
import Content from './Content';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
BottomBlock.Header = Header;
|
BottomBlock.Header = Header;
|
||||||
|
// @ts-ignore
|
||||||
BottomBlock.Content = Content;
|
BottomBlock.Content = Content;
|
||||||
|
|
||||||
export default BottomBlock;
|
export default BottomBlock as typeof BottomBlock & { Header: typeof Header, Content: typeof Content }
|
||||||
|
|
@ -27,12 +27,29 @@ function DiffRow({ diff, path }: Props) {
|
||||||
const [shortenOldVal, setShortenOldVal] = React.useState(true);
|
const [shortenOldVal, setShortenOldVal] = React.useState(true);
|
||||||
const [shortenNewVal, setShortenNewVal] = React.useState(true);
|
const [shortenNewVal, setShortenNewVal] = React.useState(true);
|
||||||
|
|
||||||
const oldValue = diff.item
|
// Adjust to handle the difference based on its type
|
||||||
? JSON.stringify(diff.item.lhs, getCircularReplacer(), 1)
|
let oldValue;
|
||||||
: JSON.stringify(diff.lhs, getCircularReplacer(), 1);
|
let newValue;
|
||||||
const newValue = diff.item
|
|
||||||
? JSON.stringify(diff.item.rhs, getCircularReplacer(), 1)
|
switch (diff.type) {
|
||||||
: JSON.stringify(diff.rhs, getCircularReplacer(), 1);
|
case 'CHANGE':
|
||||||
|
oldValue = JSON.stringify(diff.oldValue, null, 2);
|
||||||
|
newValue = JSON.stringify(diff.value, null, 2);
|
||||||
|
break;
|
||||||
|
case 'CREATE':
|
||||||
|
oldValue = 'undefined'; // No oldValue in CREATE type
|
||||||
|
newValue = JSON.stringify(diff.value, null, 2);
|
||||||
|
break;
|
||||||
|
case 'REMOVE':
|
||||||
|
oldValue = JSON.stringify(diff.oldValue, null, 2);
|
||||||
|
newValue = 'undefined'; // No newValue in REMOVE type
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Handle unexpected types, though not expected in current microdiff version
|
||||||
|
console.error('Unexpected diff type:', diff.type);
|
||||||
|
oldValue = 'error';
|
||||||
|
newValue = 'error';
|
||||||
|
}
|
||||||
|
|
||||||
const length = path.length;
|
const length = path.length;
|
||||||
const diffLengths = [oldValue?.length || 0, newValue?.length || 0];
|
const diffLengths = [oldValue?.length || 0, newValue?.length || 0];
|
||||||
|
|
|
||||||
137
frontend/app/components/Session_/Storage/ReduxViewer.tsx
Normal file
137
frontend/app/components/Session_/Storage/ReduxViewer.tsx
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
import { selectStorageListNow } from 'Player';
|
||||||
|
import { GitCommitVertical } from 'lucide-react';
|
||||||
|
import { observer } from 'mobx-react-lite';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||||
|
import { durationFromMs } from 'App/date';
|
||||||
|
import { Icon, JSONTree } from 'UI';
|
||||||
|
import JumpButton from "../../shared/DevTools/JumpButton";
|
||||||
|
|
||||||
|
import BottomBlock from '../BottomBlock/index';
|
||||||
|
|
||||||
|
interface ListItem {
|
||||||
|
action: { type: string; payload?: any };
|
||||||
|
actionTime: number;
|
||||||
|
duration: number;
|
||||||
|
state: Record<string, any>;
|
||||||
|
tabId: string;
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReduxViewer() {
|
||||||
|
const { player, store } = React.useContext(PlayerContext);
|
||||||
|
const { tabStates, currentTab, sessionStart } = store.get();
|
||||||
|
|
||||||
|
const state = tabStates[currentTab] || {};
|
||||||
|
const listNow = selectStorageListNow(state) || [];
|
||||||
|
|
||||||
|
const decodeMessage = (msg: any) => {
|
||||||
|
const decoded = {};
|
||||||
|
const pureMSG = { ...msg };
|
||||||
|
const keys = ['state', 'action'];
|
||||||
|
try {
|
||||||
|
keys.forEach((key) => {
|
||||||
|
if (pureMSG[key]) {
|
||||||
|
// @ts-ignore TODO: types for decoder
|
||||||
|
decoded[key] = player.decodeMessage(pureMSG[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error on state message decoding: ', e, pureMSG);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { ...pureMSG, ...decoded };
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodedList = React.useMemo(() => {
|
||||||
|
return listNow.map((msg) => {
|
||||||
|
return decodeMessage(msg) as ListItem;
|
||||||
|
});
|
||||||
|
}, [listNow.length]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomBlock>
|
||||||
|
<>
|
||||||
|
<BottomBlock.Header>
|
||||||
|
<h3
|
||||||
|
style={{ width: '25%', marginRight: 20 }}
|
||||||
|
className="font-semibold color-gray-medium"
|
||||||
|
>
|
||||||
|
Redux
|
||||||
|
</h3>
|
||||||
|
</BottomBlock.Header>
|
||||||
|
<BottomBlock.Content className={'overflow-y-auto'}>
|
||||||
|
{decodedList.map((msg, i) => (
|
||||||
|
<StateEvent
|
||||||
|
msg={msg}
|
||||||
|
key={i}
|
||||||
|
sessionStart={sessionStart}
|
||||||
|
prevMsg={decodedList[i - 1]}
|
||||||
|
onJump={player.jump}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</BottomBlock.Content>
|
||||||
|
</>
|
||||||
|
</BottomBlock>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StateEvent({
|
||||||
|
msg,
|
||||||
|
sessionStart,
|
||||||
|
prevMsg,
|
||||||
|
onJump,
|
||||||
|
}: {
|
||||||
|
msg: ListItem;
|
||||||
|
sessionStart: number;
|
||||||
|
onJump: (time: number) => void;
|
||||||
|
prevMsg?: ListItem;
|
||||||
|
}) {
|
||||||
|
const [isOpen, setOpen] = React.useState(false);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'w-full py-1 px-4 border-b border-gray-lightest flex flex-col hover:bg-active-blue group relative'
|
||||||
|
}
|
||||||
|
style={{ fontFamily: 'Menlo, Monaco, Consolas', letterSpacing: '-0.025rem' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={'w-full gap-2 flex items-center cursor-pointer h-full'}
|
||||||
|
onClick={() => setOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<Icon name={isOpen ? 'chevron-up' : 'chevron-down'} />
|
||||||
|
<GitCommitVertical strokeWidth={1} />
|
||||||
|
<div className={'font-medium'}>{msg.action.type ?? 'action'}</div>
|
||||||
|
<div className={'text-gray-medium'}>
|
||||||
|
@ {durationFromMs(msg.actionTime - sessionStart)} (in{' '}
|
||||||
|
{durationFromMs(msg.duration)})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isOpen ? (
|
||||||
|
<div className={'py-4 flex flex-col gap-2'} style={{ paddingLeft: '3.7rem' }}>
|
||||||
|
{prevMsg ? (
|
||||||
|
<div className={'flex items-start gap-2'}>
|
||||||
|
<div className={'text-gray-darkest tracking-tight'}>
|
||||||
|
prev state
|
||||||
|
</div>
|
||||||
|
<JSONTree src={prevMsg.state} collapsed />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className={'flex items-start gap-2'}>
|
||||||
|
<div className={'text-yellow2'}>action</div>
|
||||||
|
<JSONTree src={msg.action} collapsed />
|
||||||
|
</div>
|
||||||
|
<div className={'flex items-start gap-2'}>
|
||||||
|
<div className={'text-tealx'}>next state</div>
|
||||||
|
<JSONTree src={msg.state} collapsed />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<JumpButton onClick={() => onJump(msg.time)} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(ReduxViewer);
|
||||||
|
|
@ -1,367 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { hideHint } from 'Duck/components/player';
|
|
||||||
import {
|
|
||||||
connectPlayer,
|
|
||||||
selectStorageType,
|
|
||||||
STORAGE_TYPES,
|
|
||||||
selectStorageListNow,
|
|
||||||
selectStorageList,
|
|
||||||
} from 'Player';
|
|
||||||
import { JSONTree, NoContent, Tooltip } from 'UI';
|
|
||||||
import { formatMs } from 'App/date';
|
|
||||||
import { diff } from 'deep-diff';
|
|
||||||
import { jump } from 'Player';
|
|
||||||
import BottomBlock from '../BottomBlock/index';
|
|
||||||
import DiffRow from './DiffRow';
|
|
||||||
import stl from './storage.module.css';
|
|
||||||
import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtualized';
|
|
||||||
|
|
||||||
const ROW_HEIGHT = 90;
|
|
||||||
|
|
||||||
function getActionsName(type) {
|
|
||||||
switch (type) {
|
|
||||||
case STORAGE_TYPES.MOBX:
|
|
||||||
case STORAGE_TYPES.VUEX:
|
|
||||||
return 'MUTATIONS';
|
|
||||||
default:
|
|
||||||
return 'ACTIONS';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@connectPlayer((state) => ({
|
|
||||||
type: selectStorageType(state),
|
|
||||||
list: selectStorageList(state),
|
|
||||||
listNow: selectStorageListNow(state),
|
|
||||||
}))
|
|
||||||
@connect(
|
|
||||||
(state) => ({
|
|
||||||
hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
hideHint,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
export default class Storage extends React.PureComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.lastBtnRef = React.createRef();
|
|
||||||
this._list = React.createRef();
|
|
||||||
this.cache = new CellMeasurerCache({
|
|
||||||
fixedWidth: true,
|
|
||||||
keyMapper: (index) => this.props.listNow[index],
|
|
||||||
});
|
|
||||||
this._rowRenderer = this._rowRenderer.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
focusNextButton() {
|
|
||||||
if (this.lastBtnRef.current) {
|
|
||||||
this.lastBtnRef.current.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.focusNextButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
if (prevProps.listNow.length !== this.props.listNow.length) {
|
|
||||||
this.focusNextButton();
|
|
||||||
/** possible performance gain, but does not work with dynamic list insertion for some reason
|
|
||||||
* getting NaN offsets, maybe I detect changed rows wrongly
|
|
||||||
*/
|
|
||||||
// const newRows = this.props.listNow.filter(evt => prevProps.listNow.indexOf(evt._index) < 0);
|
|
||||||
// if (newRows.length > 0) {
|
|
||||||
// const newRowsIndexes = newRows.map(r => this.props.listNow.indexOf(r))
|
|
||||||
// newRowsIndexes.forEach(ind => this.cache.clear(ind))
|
|
||||||
// this._list.recomputeRowHeights(newRowsIndexes)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDiff(item, prevItem) {
|
|
||||||
if (!prevItem) {
|
|
||||||
// we don't have state before first action
|
|
||||||
return <div style={{ flex: 3 }} className="p-1" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateDiff = diff(prevItem.state, item.state);
|
|
||||||
|
|
||||||
if (!stateDiff) {
|
|
||||||
return (
|
|
||||||
<div style={{ flex: 3 }} className="flex flex-col p-2 pr-0 font-mono text-disabled-text">
|
|
||||||
No diff
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{ flex: 3, maxHeight: ROW_HEIGHT, overflowY: 'scroll' }}
|
|
||||||
className="flex flex-col p-1 font-mono"
|
|
||||||
>
|
|
||||||
{stateDiff.map((d, i) => this.renderDiffs(d, i))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDiffs(diff, i) {
|
|
||||||
const path = this.createPath(diff);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment key={i}>
|
|
||||||
<DiffRow shades={this.pathShades} path={path} diff={diff} />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
createPath = (diff) => {
|
|
||||||
let path = [];
|
|
||||||
|
|
||||||
if (diff.path) {
|
|
||||||
path = path.concat(diff.path);
|
|
||||||
}
|
|
||||||
if (typeof diff.index !== 'undefined') {
|
|
||||||
path.push(diff.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathStr = path.length ? path.join('.') : '';
|
|
||||||
return pathStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
ensureString(actionType) {
|
|
||||||
if (typeof actionType === 'string') return actionType;
|
|
||||||
return 'UNKNOWN';
|
|
||||||
}
|
|
||||||
|
|
||||||
goNext = () => {
|
|
||||||
const { list, listNow } = this.props;
|
|
||||||
jump(list[listNow.length].time, list[listNow.length]._index);
|
|
||||||
};
|
|
||||||
|
|
||||||
renderTab() {
|
|
||||||
const { listNow } = this.props;
|
|
||||||
if (listNow.length === 0) {
|
|
||||||
return 'Not initialized'; //?
|
|
||||||
}
|
|
||||||
return <JSONTree collapsed={2} src={listNow[listNow.length - 1].state} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderItem(item, i, prevItem, style) {
|
|
||||||
const { type } = this.props;
|
|
||||||
let src;
|
|
||||||
let name;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case STORAGE_TYPES.REDUX:
|
|
||||||
case STORAGE_TYPES.NGRX:
|
|
||||||
src = item.action;
|
|
||||||
name = src && src.type;
|
|
||||||
break;
|
|
||||||
case STORAGE_TYPES.VUEX:
|
|
||||||
src = item.mutation;
|
|
||||||
name = src && src.type;
|
|
||||||
break;
|
|
||||||
case STORAGE_TYPES.MOBX:
|
|
||||||
src = item.payload;
|
|
||||||
name = `@${item.type} ${src && src.type}`;
|
|
||||||
break;
|
|
||||||
case STORAGE_TYPES.ZUSTAND:
|
|
||||||
src = null;
|
|
||||||
name = item.mutation.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{ ...style, height: ROW_HEIGHT }}
|
|
||||||
className="flex justify-between items-start border-b"
|
|
||||||
key={`store-${i}`}
|
|
||||||
>
|
|
||||||
{src === null ? (
|
|
||||||
<div className="font-mono" style={{ flex: 2, marginLeft: '26.5%' }}>
|
|
||||||
{name}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{this.renderDiff(item, prevItem, i)}
|
|
||||||
<div
|
|
||||||
style={{ flex: 2, maxHeight: ROW_HEIGHT, overflowY: 'scroll', overflowX: 'scroll' }}
|
|
||||||
className="flex pl-10 pt-2"
|
|
||||||
>
|
|
||||||
<JSONTree
|
|
||||||
name={this.ensureString(name)}
|
|
||||||
src={src}
|
|
||||||
collapsed
|
|
||||||
collapseStringsAfterLength={7}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
className="flex-1 flex gap-2 pt-2 items-center justify-end self-start"
|
|
||||||
>
|
|
||||||
{typeof item.duration === 'number' && (
|
|
||||||
<div className="font-size-12 color-gray-medium">{formatMs(item.duration)}</div>
|
|
||||||
)}
|
|
||||||
<div className="w-12">
|
|
||||||
{i + 1 < this.props.listNow.length && (
|
|
||||||
<button className={stl.button} onClick={() => jump(item.time, item._index)}>
|
|
||||||
{'JUMP'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{i + 1 === this.props.listNow.length && i + 1 < this.props.list.length && (
|
|
||||||
<button className={stl.button} ref={this.lastBtnRef} onClick={this.goNext}>
|
|
||||||
{'NEXT'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_rowRenderer({ index, parent, key, style }) {
|
|
||||||
const { listNow } = this.props;
|
|
||||||
|
|
||||||
if (!listNow[index]) return console.warn(index, listNow);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CellMeasurer cache={this.cache} columnIndex={0} key={key} rowIndex={index} parent={parent}>
|
|
||||||
{this.renderItem(listNow[index], index, index > 0 ? listNow[index - 1] : undefined, style)}
|
|
||||||
</CellMeasurer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { type, list, listNow, hintIsHidden } = this.props;
|
|
||||||
|
|
||||||
const showStore = type !== STORAGE_TYPES.MOBX;
|
|
||||||
return (
|
|
||||||
<BottomBlock>
|
|
||||||
<BottomBlock.Header>
|
|
||||||
{list.length > 0 && (
|
|
||||||
<div className="flex w-full">
|
|
||||||
{showStore && (
|
|
||||||
<h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold">
|
|
||||||
{'STATE'}
|
|
||||||
</h3>
|
|
||||||
)}
|
|
||||||
{type !== STORAGE_TYPES.ZUSTAND ? (
|
|
||||||
<h3 style={{ width: '39%' }} className="font-semibold">
|
|
||||||
DIFFS
|
|
||||||
</h3>
|
|
||||||
) : null}
|
|
||||||
<h3 style={{ width: '30%' }} className="font-semibold">
|
|
||||||
{getActionsName(type)}
|
|
||||||
</h3>
|
|
||||||
<h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold">
|
|
||||||
<Tooltip title="Time to execute">TTE</Tooltip>
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</BottomBlock.Header>
|
|
||||||
<BottomBlock.Content className="flex">
|
|
||||||
<NoContent
|
|
||||||
title="Nothing to display yet"
|
|
||||||
subtext={
|
|
||||||
!hintIsHidden ? (
|
|
||||||
<>
|
|
||||||
{
|
|
||||||
'Inspect your application state while you’re replaying your users sessions. OpenReplay supports '
|
|
||||||
}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/redux"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Redux
|
|
||||||
</a>
|
|
||||||
{', '}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/vuex"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
VueX
|
|
||||||
</a>
|
|
||||||
{', '}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/pinia"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Pinia
|
|
||||||
</a>
|
|
||||||
{', '}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/zustand"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
Zustand
|
|
||||||
</a>
|
|
||||||
{', '}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/mobx"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
MobX
|
|
||||||
</a>
|
|
||||||
{' and '}
|
|
||||||
<a
|
|
||||||
className="underline color-teal"
|
|
||||||
href="https://docs.openreplay.com/plugins/ngrx"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
NgRx
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<button className="color-teal" onClick={() => this.props.hideHint('storage')}>
|
|
||||||
Got It!
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
size="small"
|
|
||||||
show={listNow.length === 0}
|
|
||||||
>
|
|
||||||
{showStore && (
|
|
||||||
<div className="ph-10 scroll-y" style={{ width: '25%' }}>
|
|
||||||
{listNow.length === 0 ? (
|
|
||||||
<div className="color-gray-light font-size-16 mt-20 text-center">
|
|
||||||
{'Empty state.'}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
this.renderTab()
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex" style={{ width: showStore ? '75%' : '100%' }}>
|
|
||||||
<AutoSizer>
|
|
||||||
{({ height, width }) => (
|
|
||||||
<List
|
|
||||||
ref={(element) => {
|
|
||||||
this._list = element;
|
|
||||||
}}
|
|
||||||
deferredMeasurementCache={this.cache}
|
|
||||||
overscanRowCount={1}
|
|
||||||
rowCount={Math.ceil(parseInt(this.props.listNow.length) || 1)}
|
|
||||||
rowHeight={ROW_HEIGHT}
|
|
||||||
rowRenderer={this._rowRenderer}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
</div>
|
|
||||||
</NoContent>
|
|
||||||
</BottomBlock.Content>
|
|
||||||
</BottomBlock>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,8 +5,7 @@ import { PlayerContext } from 'App/components/Session/playerContext';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { JSONTree, NoContent, Tooltip } from 'UI';
|
import { JSONTree, NoContent, Tooltip } from 'UI';
|
||||||
import { formatMs } from 'App/date';
|
import { formatMs } from 'App/date';
|
||||||
// @ts-ignore
|
import diff from 'microdiff'
|
||||||
import { diff } from 'deep-diff';
|
|
||||||
import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player';
|
import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player';
|
||||||
import Autoscroll from '../Autoscroll';
|
import Autoscroll from '../Autoscroll';
|
||||||
import BottomBlock from '../BottomBlock/index';
|
import BottomBlock from '../BottomBlock/index';
|
||||||
|
|
@ -14,6 +13,7 @@ import DiffRow from './DiffRow';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import stl from './storage.module.css';
|
import stl from './storage.module.css';
|
||||||
import logger from "App/logger";
|
import logger from "App/logger";
|
||||||
|
import ReduxViewer from './ReduxViewer'
|
||||||
|
|
||||||
function getActionsName(type: string) {
|
function getActionsName(type: string) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -129,7 +129,7 @@ function Storage(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDiffs = (diff: Record<string, any>, i: number) => {
|
const renderDiffs = (diff: Record<string, any>, i: number) => {
|
||||||
const path = createPath(diff);
|
const path = diff.path.join('.')
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
<DiffRow path={path} diff={diff} />
|
<DiffRow path={path} diff={diff} />
|
||||||
|
|
@ -137,20 +137,6 @@ function Storage(props: Props) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const createPath = (diff: Record<string, any>) => {
|
|
||||||
let path: string[] = [];
|
|
||||||
|
|
||||||
if (diff.path) {
|
|
||||||
path = path.concat(diff.path);
|
|
||||||
}
|
|
||||||
if (typeof diff.index !== 'undefined') {
|
|
||||||
path.push(diff.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathStr = path.length ? path.join('.') : '';
|
|
||||||
return pathStr;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ensureString = (actionType: string) => {
|
const ensureString = (actionType: string) => {
|
||||||
if (typeof actionType === 'string') return actionType;
|
if (typeof actionType === 'string') return actionType;
|
||||||
return 'UNKNOWN';
|
return 'UNKNOWN';
|
||||||
|
|
@ -239,115 +225,121 @@ function Storage(props: Props) {
|
||||||
|
|
||||||
const { hintIsHidden } = props;
|
const { hintIsHidden } = props;
|
||||||
|
|
||||||
|
if (type === STORAGE_TYPES.REDUX) {
|
||||||
|
return <ReduxViewer />
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<BottomBlock>
|
<BottomBlock>
|
||||||
<BottomBlock.Header>
|
{/*@ts-ignore*/}
|
||||||
{list.length > 0 && (
|
<>
|
||||||
<div className="flex w-full">
|
<BottomBlock.Header>
|
||||||
<h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold">
|
{list.length > 0 && (
|
||||||
{'STATE'}
|
<div className="flex w-full">
|
||||||
</h3>
|
<h3 style={{ width: '25%', marginRight: 20 }} className="font-semibold">
|
||||||
{showDiffs ? (
|
{'STATE'}
|
||||||
<h3 style={{ width: '39%' }} className="font-semibold">
|
|
||||||
DIFFS
|
|
||||||
</h3>
|
</h3>
|
||||||
) : null}
|
{showDiffs ? (
|
||||||
<h3 style={{ width: '30%' }} className="font-semibold">
|
<h3 style={{ width: '39%' }} className="font-semibold">
|
||||||
{getActionsName(type)}
|
DIFFS
|
||||||
</h3>
|
</h3>
|
||||||
<h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold">
|
) : null}
|
||||||
<Tooltip title="Time to execute">TTE</Tooltip>
|
<h3 style={{ width: '30%' }} className="font-semibold">
|
||||||
</h3>
|
{getActionsName(type)}
|
||||||
</div>
|
</h3>
|
||||||
)}
|
<h3 style={{ paddingRight: 30, marginLeft: 'auto' }} className="font-semibold">
|
||||||
</BottomBlock.Header>
|
<Tooltip title="Time to execute">TTE</Tooltip>
|
||||||
<BottomBlock.Content className="flex">
|
</h3>
|
||||||
<NoContent
|
</div>
|
||||||
title="Nothing to display yet"
|
)}
|
||||||
subtext={
|
</BottomBlock.Header>
|
||||||
!hintIsHidden ? (
|
<BottomBlock.Content className="flex">
|
||||||
<>
|
<NoContent
|
||||||
{
|
title="Nothing to display yet"
|
||||||
'Inspect your application state while you’re replaying your users sessions. OpenReplay supports '
|
subtext={
|
||||||
}
|
!hintIsHidden ? (
|
||||||
<a
|
<>
|
||||||
className="underline color-teal"
|
{
|
||||||
href="https://docs.openreplay.com/plugins/redux"
|
'Inspect your application state while you’re replaying your users sessions. OpenReplay supports '
|
||||||
target="_blank"
|
}
|
||||||
>
|
<a
|
||||||
Redux
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/redux"
|
||||||
{', '}
|
target="_blank"
|
||||||
<a
|
>
|
||||||
className="underline color-teal"
|
Redux
|
||||||
href="https://docs.openreplay.com/plugins/vuex"
|
</a>
|
||||||
target="_blank"
|
{', '}
|
||||||
>
|
<a
|
||||||
VueX
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/vuex"
|
||||||
{', '}
|
target="_blank"
|
||||||
<a
|
>
|
||||||
className="underline color-teal"
|
VueX
|
||||||
href="https://docs.openreplay.com/plugins/pinia"
|
</a>
|
||||||
target="_blank"
|
{', '}
|
||||||
>
|
<a
|
||||||
Pinia
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/pinia"
|
||||||
{', '}
|
target="_blank"
|
||||||
<a
|
>
|
||||||
className="underline color-teal"
|
Pinia
|
||||||
href="https://docs.openreplay.com/plugins/zustand"
|
</a>
|
||||||
target="_blank"
|
{', '}
|
||||||
>
|
<a
|
||||||
Zustand
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/zustand"
|
||||||
{', '}
|
target="_blank"
|
||||||
<a
|
>
|
||||||
className="underline color-teal"
|
Zustand
|
||||||
href="https://docs.openreplay.com/plugins/mobx"
|
</a>
|
||||||
target="_blank"
|
{', '}
|
||||||
>
|
<a
|
||||||
MobX
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/mobx"
|
||||||
{' and '}
|
target="_blank"
|
||||||
<a
|
>
|
||||||
className="underline color-teal"
|
MobX
|
||||||
href="https://docs.openreplay.com/plugins/ngrx"
|
</a>
|
||||||
target="_blank"
|
{' and '}
|
||||||
>
|
<a
|
||||||
NgRx
|
className="underline color-teal"
|
||||||
</a>
|
href="https://docs.openreplay.com/plugins/ngrx"
|
||||||
.
|
target="_blank"
|
||||||
<br />
|
>
|
||||||
<br />
|
NgRx
|
||||||
<button className="color-teal" onClick={() => props.hideHint('storage')}>
|
</a>
|
||||||
Got It!
|
.
|
||||||
</button>
|
<br />
|
||||||
</>
|
<br />
|
||||||
) : null
|
<button className="color-teal" onClick={() => props.hideHint('storage')}>
|
||||||
}
|
Got It!
|
||||||
size="small"
|
</button>
|
||||||
show={list.length === 0}
|
</>
|
||||||
>
|
) : null
|
||||||
<div className="ph-10 scroll-y" style={{ width: '25%' }}>
|
}
|
||||||
{list.length === 0 ? (
|
size="small"
|
||||||
<div className="color-gray-light font-size-16 mt-20 text-center">
|
show={list.length === 0}
|
||||||
{'Empty state.'}
|
>
|
||||||
</div>
|
<div className="ph-10 scroll-y" style={{ width: '25%' }}>
|
||||||
) : (
|
{list.length === 0 ? (
|
||||||
<JSONTree collapsed={2} src={stateObject}
|
<div className="color-gray-light font-size-16 mt-20 text-center">
|
||||||
/>
|
{'Empty state.'}
|
||||||
)}
|
</div>
|
||||||
</div>
|
) : (
|
||||||
<div className="flex" style={{ width: '75%' }}>
|
<JSONTree collapsed={2} src={stateObject}
|
||||||
<Autoscroll className="ph-10">
|
/>
|
||||||
{decodedList.map((item: Record<string, any>, i: number) =>
|
|
||||||
renderItem(item, i, i > 0 ? decodedList[i - 1] : undefined)
|
|
||||||
)}
|
)}
|
||||||
</Autoscroll>
|
</div>
|
||||||
</div>
|
<div className="flex" style={{ width: '75%' }}>
|
||||||
</NoContent>
|
<Autoscroll className="ph-10">
|
||||||
</BottomBlock.Content>
|
{decodedList.map((item: Record<string, any>, i: number) =>
|
||||||
|
renderItem(item, i, i > 0 ? decodedList[i - 1] : undefined)
|
||||||
|
)}
|
||||||
|
</Autoscroll>
|
||||||
|
</div>
|
||||||
|
</NoContent>
|
||||||
|
</BottomBlock.Content>
|
||||||
|
</>
|
||||||
</BottomBlock>
|
</BottomBlock>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ interface Props {
|
||||||
function JumpButton(props: Props) {
|
function JumpButton(props: Props) {
|
||||||
const { tooltip } = props;
|
const { tooltip } = props;
|
||||||
return (
|
return (
|
||||||
<div className="absolute right-0 top-0 bottom-0 my-auto flex items-center">
|
<div className="absolute right-2 top-0 bottom-0 my-auto flex items-center">
|
||||||
<Tooltip title={tooltip} disabled={!tooltip}>
|
<Tooltip title={tooltip} disabled={!tooltip}>
|
||||||
<div
|
<div
|
||||||
className="mr-2 border cursor-pointer invisible group-hover:visible rounded-lg bg-active-blue text-xs flex items-center px-2 py-1 color-teal hover:shadow h-6"
|
className="mr-2 border cursor-pointer invisible group-hover:visible rounded bg-white text-xs flex items-center px-2 py-1 color-teal hover:shadow h-6"
|
||||||
onClick={(e: any) => {
|
onClick={(e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
props.onClick();
|
props.onClick();
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import JSONTree from 'react-json-view';
|
|
||||||
|
|
||||||
function updateObjectLink(obj) {
|
|
||||||
if (typeof obj !== 'object' || obj === null) return obj;
|
|
||||||
if (Array.isArray(obj)) return [ ...obj ];
|
|
||||||
return { ...obj }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ src, ...props }) => (
|
|
||||||
<JSONTree
|
|
||||||
name={ false }
|
|
||||||
collapsed={ 1 }
|
|
||||||
enableClipboard={ false }
|
|
||||||
iconStyle="circle"
|
|
||||||
indentWidth={ 1 }
|
|
||||||
sortKeys
|
|
||||||
displayDataTypes={ false }
|
|
||||||
displayObjectSize={ false }
|
|
||||||
src={ updateObjectLink(src) }
|
|
||||||
iconStle="triangle"
|
|
||||||
{ ...props }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
62
frontend/app/components/ui/JSONTree/JSONTree.tsx
Normal file
62
frontend/app/components/ui/JSONTree/JSONTree.tsx
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
import React from 'react';
|
||||||
|
import JsonView from 'react18-json-view';
|
||||||
|
|
||||||
|
function updateObjectLink(obj: any): any {
|
||||||
|
if (typeof obj !== 'object' || obj === null) return obj;
|
||||||
|
if (Array.isArray(obj)) return [...obj];
|
||||||
|
return { ...obj };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
src: any;
|
||||||
|
className?: string;
|
||||||
|
dark?: boolean;
|
||||||
|
theme?:
|
||||||
|
| 'default'
|
||||||
|
| 'a11y'
|
||||||
|
| 'github'
|
||||||
|
| 'vscode'
|
||||||
|
| 'atom'
|
||||||
|
| 'winter-is-coming';
|
||||||
|
enableClipboard?: boolean;
|
||||||
|
matchesURL?: boolean;
|
||||||
|
displaySize?: boolean | number | 'collapsed';
|
||||||
|
collapseStringsAfterLength?: number;
|
||||||
|
collapsed?: number | boolean;
|
||||||
|
editable?: boolean;
|
||||||
|
onAdd?: (params: {
|
||||||
|
indexOrName: string | number;
|
||||||
|
depth: number;
|
||||||
|
src: any;
|
||||||
|
parentType: 'object' | 'array';
|
||||||
|
}) => void;
|
||||||
|
onDelete?: (params: {
|
||||||
|
value: any;
|
||||||
|
indexOrName: string | number;
|
||||||
|
depth: number;
|
||||||
|
src: any;
|
||||||
|
parentType: 'object' | 'array';
|
||||||
|
}) => void;
|
||||||
|
onEdit?: (params: {
|
||||||
|
newValue: any;
|
||||||
|
oldValue: any;
|
||||||
|
depth: number;
|
||||||
|
src: any;
|
||||||
|
indexOrName: string | number;
|
||||||
|
parentType: 'object' | 'array';
|
||||||
|
}) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function JSONTree(props: Props) {
|
||||||
|
return (
|
||||||
|
<JsonView
|
||||||
|
src={updateObjectLink(props.src)}
|
||||||
|
collapsed={1}
|
||||||
|
displaySize={'collapsed'}
|
||||||
|
enableClipboard={true}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JSONTree;
|
||||||
148
frontend/app/components/ui/JSONTree/styles.module.css
Normal file
148
frontend/app/components/ui/JSONTree/styles.module.css
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
.json-view {
|
||||||
|
display: block;
|
||||||
|
color: #4d4d4d;
|
||||||
|
text-align: left;
|
||||||
|
--json-property: #009033;
|
||||||
|
--json-index: #676dff;
|
||||||
|
--json-number: #676dff;
|
||||||
|
--json-string: #b2762e;
|
||||||
|
--json-boolean: #dc155e;
|
||||||
|
--json-null: #dc155e;
|
||||||
|
}
|
||||||
|
.json-view .json-view--property {
|
||||||
|
color: var(--json-property);
|
||||||
|
}
|
||||||
|
.json-view .json-view--index {
|
||||||
|
color: var(--json-index);
|
||||||
|
}
|
||||||
|
.json-view .json-view--number {
|
||||||
|
color: var(--json-number);
|
||||||
|
}
|
||||||
|
.json-view .json-view--string {
|
||||||
|
color: var(--json-string);
|
||||||
|
}
|
||||||
|
.json-view .json-view--boolean {
|
||||||
|
color: var(--json-boolean);
|
||||||
|
}
|
||||||
|
.json-view .json-view--null {
|
||||||
|
color: var(--json-null);
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .jv-indent {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
.json-view .jv-chevron {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -20%;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.4;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
:is(.json-view .jv-chevron:hover, .json-view .jv-size:hover + .jv-chevron) {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.json-view .jv-size {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.4;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
vertical-align: -5%;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view :is(.json-view--copy, .json-view--edit),
|
||||||
|
.json-view .json-view--link svg {
|
||||||
|
display: none;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .json-view--input {
|
||||||
|
width: 120px;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
padding: 0px 4px;
|
||||||
|
font-size: 87.5%;
|
||||||
|
line-height: 1.25;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.json-view .json-view--deleting {
|
||||||
|
outline: 1px solid #da0000;
|
||||||
|
background-color: #da000011;
|
||||||
|
text-decoration-line: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.json-view:hover, .json-view--pair:hover) > :is(.json-view--copy, .json-view--edit),
|
||||||
|
:is(.json-view:hover, .json-view--pair:hover) > .json-view--link svg {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .jv-button {
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.json-view .cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view svg {
|
||||||
|
vertical-align: -10%;
|
||||||
|
}
|
||||||
|
.jv-size-chevron ~ svg {
|
||||||
|
vertical-align: -16%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Themes */
|
||||||
|
.json-view_a11y {
|
||||||
|
color: #545454;
|
||||||
|
--json-property: #aa5d00;
|
||||||
|
--json-index: #007299;
|
||||||
|
--json-number: #007299;
|
||||||
|
--json-string: #008000;
|
||||||
|
--json-boolean: #d91e18;
|
||||||
|
--json-null: #d91e18;
|
||||||
|
}
|
||||||
|
.json-view_github {
|
||||||
|
color: #005cc5;
|
||||||
|
--json-property: #005cc5;
|
||||||
|
--json-index: #005cc5;
|
||||||
|
--json-number: #005cc5;
|
||||||
|
--json-string: #032f62;
|
||||||
|
--json-boolean: #005cc5;
|
||||||
|
--json-null: #005cc5;
|
||||||
|
}
|
||||||
|
.json-view_vscode {
|
||||||
|
color: #005cc5;
|
||||||
|
--json-property: #0451a5;
|
||||||
|
--json-index: #0000ff;
|
||||||
|
--json-number: #0000ff;
|
||||||
|
--json-string: #a31515;
|
||||||
|
--json-boolean: #0000ff;
|
||||||
|
--json-null: #0000ff;
|
||||||
|
}
|
||||||
|
.json-view_atom {
|
||||||
|
color: #383a42;
|
||||||
|
--json-property: #e45649;
|
||||||
|
--json-index: #986801;
|
||||||
|
--json-number: #986801;
|
||||||
|
--json-string: #50a14f;
|
||||||
|
--json-boolean: #0184bc;
|
||||||
|
--json-null: #0184bc;
|
||||||
|
}
|
||||||
|
.json-view_winter-is-coming {
|
||||||
|
color: #0431fa;
|
||||||
|
--json-property: #3a9685;
|
||||||
|
--json-index: #ae408b;
|
||||||
|
--json-number: #ae408b;
|
||||||
|
--json-string: #8123a9;
|
||||||
|
--json-boolean: #0184bc;
|
||||||
|
--json-null: #0184bc;
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import type { Store, SessionFilesInfo, PlayerMsg } from 'Player';
|
import type { PlayerMsg, SessionFilesInfo, Store } from 'Player';
|
||||||
import { decryptSessionBytes } from './network/crypto';
|
|
||||||
import MFileReader from './messages/MFileReader';
|
|
||||||
import { loadFiles, requestEFSDom, requestEFSDevtools, requestTarball } from './network/loadFiles';
|
|
||||||
import logger from 'App/logger';
|
|
||||||
import unpack from 'Player/common/unpack';
|
|
||||||
import unpackTar from 'Player/common/tarball';
|
import unpackTar from 'Player/common/tarball';
|
||||||
import MessageManager from 'Player/web/MessageManager';
|
import unpack from 'Player/common/unpack';
|
||||||
import IOSMessageManager from 'Player/mobile/IOSMessageManager';
|
import IOSMessageManager from 'Player/mobile/IOSMessageManager';
|
||||||
|
import MessageManager from 'Player/web/MessageManager';
|
||||||
import { MType } from 'Player/web/messages';
|
import { MType } from 'Player/web/messages';
|
||||||
|
|
||||||
|
import logger from 'App/logger';
|
||||||
|
|
||||||
|
import MFileReader from './messages/MFileReader';
|
||||||
|
import { decryptSessionBytes } from './network/crypto';
|
||||||
|
import {
|
||||||
|
loadFiles,
|
||||||
|
requestEFSDevtools,
|
||||||
|
requestEFSDom,
|
||||||
|
requestTarball,
|
||||||
|
} from './network/loadFiles';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
firstFileLoading: boolean;
|
firstFileLoading: boolean;
|
||||||
domLoading: boolean;
|
domLoading: boolean;
|
||||||
|
|
@ -39,9 +46,12 @@ export default class MessageLoader {
|
||||||
) {
|
) {
|
||||||
const decrypt =
|
const decrypt =
|
||||||
shouldDecrypt && this.session.fileKey
|
shouldDecrypt && this.session.fileKey
|
||||||
? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!)
|
? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey!)
|
||||||
: (b: Uint8Array) => Promise.resolve(b);
|
: (b: Uint8Array) => Promise.resolve(b);
|
||||||
const fileReader = new MFileReader(new Uint8Array(), this.session.startedAt);
|
const fileReader = new MFileReader(
|
||||||
|
new Uint8Array(),
|
||||||
|
this.session.startedAt
|
||||||
|
);
|
||||||
let fileNum = 0;
|
let fileNum = 0;
|
||||||
return async (b: Uint8Array) => {
|
return async (b: Uint8Array) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -65,6 +75,14 @@ export default class MessageLoader {
|
||||||
let artificialStartTime = Infinity;
|
let artificialStartTime = Infinity;
|
||||||
let startTimeSet = false;
|
let startTimeSet = false;
|
||||||
msgs.forEach((msg) => {
|
msgs.forEach((msg) => {
|
||||||
|
if (msg.tp === MType.Redux || msg.tp === MType.ReduxDeprecated) {
|
||||||
|
if ('actionTime' in msg && msg.actionTime) {
|
||||||
|
msg.time = msg.actionTime - this.session.startedAt;
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
Object.assign(msg, { actionTime: msg.time + this.session.startedAt });
|
||||||
|
}
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
msg.tp === MType.CreateDocument &&
|
msg.tp === MType.CreateDocument &&
|
||||||
msg.time !== undefined &&
|
msg.time !== undefined &&
|
||||||
|
|
@ -96,8 +114,10 @@ export default class MessageLoader {
|
||||||
const sortedMsgs = msgs.sort((m1, m2) => {
|
const sortedMsgs = msgs.sort((m1, m2) => {
|
||||||
if (m1.time !== m2.time) return m1.time - m2.time;
|
if (m1.time !== m2.time) return m1.time - m2.time;
|
||||||
|
|
||||||
if (m1.tp === MType.CreateDocument && m2.tp !== MType.CreateDocument) return -1;
|
if (m1.tp === MType.CreateDocument && m2.tp !== MType.CreateDocument)
|
||||||
if (m1.tp !== MType.CreateDocument && m2.tp === MType.CreateDocument) return 1;
|
return -1;
|
||||||
|
if (m1.tp !== MType.CreateDocument && m2.tp === MType.CreateDocument)
|
||||||
|
return 1;
|
||||||
|
|
||||||
const m1IsDOM = DOMMessages.includes(m1.tp);
|
const m1IsDOM = DOMMessages.includes(m1.tp);
|
||||||
const m2IsDOM = DOMMessages.includes(m2.tp);
|
const m2IsDOM = DOMMessages.includes(m2.tp);
|
||||||
|
|
@ -189,7 +209,10 @@ export default class MessageLoader {
|
||||||
try {
|
try {
|
||||||
await this.loadEFSMobs();
|
await this.loadEFSMobs();
|
||||||
} catch (unprocessedLoadError) {
|
} catch (unprocessedLoadError) {
|
||||||
this.messageManager.onFileReadFailed(sessionLoadError, unprocessedLoadError);
|
this.messageManager.onFileReadFailed(
|
||||||
|
sessionLoadError,
|
||||||
|
unprocessedLoadError
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.store.update({ domLoading: false, devtoolsLoading: false });
|
this.store.update({ domLoading: false, devtoolsLoading: false });
|
||||||
|
|
@ -199,17 +222,23 @@ export default class MessageLoader {
|
||||||
loadMobs = async () => {
|
loadMobs = async () => {
|
||||||
const loadMethod =
|
const loadMethod =
|
||||||
this.session.domURL && this.session.domURL.length > 0
|
this.session.domURL && this.session.domURL.length > 0
|
||||||
? {
|
? {
|
||||||
mobUrls: this.session.domURL,
|
mobUrls: this.session.domURL,
|
||||||
parser: () => this.createNewParser(true, this.processMessages, 'dom'),
|
parser: () =>
|
||||||
}
|
this.createNewParser(true, this.processMessages, 'dom'),
|
||||||
: {
|
}
|
||||||
mobUrls: this.session.mobsUrl,
|
: {
|
||||||
parser: () => this.createNewParser(false, this.processMessages, 'dom'),
|
mobUrls: this.session.mobsUrl,
|
||||||
};
|
parser: () =>
|
||||||
|
this.createNewParser(false, this.processMessages, 'dom'),
|
||||||
|
};
|
||||||
|
|
||||||
const parser = loadMethod.parser();
|
const parser = loadMethod.parser();
|
||||||
const devtoolsParser = this.createNewParser(true, this.processMessages, 'devtools');
|
const devtoolsParser = this.createNewParser(
|
||||||
|
true,
|
||||||
|
this.processMessages,
|
||||||
|
'devtools'
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* to speed up time to replay
|
* to speed up time to replay
|
||||||
|
|
@ -220,7 +249,10 @@ export default class MessageLoader {
|
||||||
* */
|
* */
|
||||||
await loadFiles([loadMethod.mobUrls[0]], parser);
|
await loadFiles([loadMethod.mobUrls[0]], parser);
|
||||||
this.messageManager.onFileReadFinally();
|
this.messageManager.onFileReadFinally();
|
||||||
const restDomFilesPromise = this.loadDomFiles([...loadMethod.mobUrls.slice(1)], parser);
|
const restDomFilesPromise = this.loadDomFiles(
|
||||||
|
[...loadMethod.mobUrls.slice(1)],
|
||||||
|
parser
|
||||||
|
);
|
||||||
const restDevtoolsFilesPromise = this.loadDevtools(devtoolsParser);
|
const restDevtoolsFilesPromise = this.loadDevtools(devtoolsParser);
|
||||||
|
|
||||||
await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]);
|
await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]);
|
||||||
|
|
@ -236,16 +268,24 @@ export default class MessageLoader {
|
||||||
efsDomFilePromise,
|
efsDomFilePromise,
|
||||||
efsDevtoolsFilePromise,
|
efsDevtoolsFilePromise,
|
||||||
]);
|
]);
|
||||||
const domParser = this.createNewParser(false, this.processMessages, 'domEFS');
|
const domParser = this.createNewParser(
|
||||||
const devtoolsParser = this.createNewParser(false, this.processMessages, 'devtoolsEFS');
|
false,
|
||||||
|
this.processMessages,
|
||||||
|
'domEFS'
|
||||||
|
);
|
||||||
|
const devtoolsParser = this.createNewParser(
|
||||||
|
false,
|
||||||
|
this.processMessages,
|
||||||
|
'devtoolsEFS'
|
||||||
|
);
|
||||||
const parseDomPromise: Promise<any> =
|
const parseDomPromise: Promise<any> =
|
||||||
domData.status === 'fulfilled'
|
domData.status === 'fulfilled'
|
||||||
? domParser(domData.value)
|
? domParser(domData.value)
|
||||||
: Promise.reject('No dom file in EFS');
|
: Promise.reject('No dom file in EFS');
|
||||||
const parseDevtoolsPromise: Promise<any> =
|
const parseDevtoolsPromise: Promise<any> =
|
||||||
devtoolsData.status === 'fulfilled'
|
devtoolsData.status === 'fulfilled'
|
||||||
? devtoolsParser(devtoolsData.value)
|
? devtoolsParser(devtoolsData.value)
|
||||||
: Promise.reject('No devtools file in EFS');
|
: Promise.reject('No devtools file in EFS');
|
||||||
|
|
||||||
await Promise.all([parseDomPromise, parseDevtoolsPromise]);
|
await Promise.all([parseDomPromise, parseDevtoolsPromise]);
|
||||||
this.messageManager.onFileReadSuccess();
|
this.messageManager.onFileReadSuccess();
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ export default class TabSessionManager {
|
||||||
case MType.WsChannel:
|
case MType.WsChannel:
|
||||||
this.lists.lists.websocket.insert(msg);
|
this.lists.lists.websocket.insert(msg);
|
||||||
break;
|
break;
|
||||||
|
case MType.ReduxDeprecated:
|
||||||
case MType.Redux:
|
case MType.Redux:
|
||||||
this.lists.lists.redux.append(msg);
|
this.lists.lists.redux.append(msg);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -304,7 +304,7 @@ export default class RawMessageReader extends PrimitiveReader {
|
||||||
const state = this.readString(); if (state === null) { return resetPointer() }
|
const state = this.readString(); if (state === null) { return resetPointer() }
|
||||||
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
||||||
return {
|
return {
|
||||||
tp: MType.Redux,
|
tp: MType.ReduxDeprecated,
|
||||||
action,
|
action,
|
||||||
state,
|
state,
|
||||||
duration,
|
duration,
|
||||||
|
|
@ -749,6 +749,20 @@ export default class RawMessageReader extends PrimitiveReader {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 121: {
|
||||||
|
const action = this.readString(); if (action === null) { return resetPointer() }
|
||||||
|
const state = this.readString(); if (state === null) { return resetPointer() }
|
||||||
|
const duration = this.readUint(); if (duration === null) { return resetPointer() }
|
||||||
|
const actionTime = this.readUint(); if (actionTime === null) { return resetPointer() }
|
||||||
|
return {
|
||||||
|
tp: MType.Redux,
|
||||||
|
action,
|
||||||
|
state,
|
||||||
|
duration,
|
||||||
|
actionTime,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 93: {
|
case 93: {
|
||||||
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
|
const timestamp = this.readUint(); if (timestamp === null) { return resetPointer() }
|
||||||
const length = this.readUint(); if (length === null) { return resetPointer() }
|
const length = this.readUint(); if (length === null) { return resetPointer() }
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import type {
|
||||||
RawFetch,
|
RawFetch,
|
||||||
RawProfiler,
|
RawProfiler,
|
||||||
RawOTable,
|
RawOTable,
|
||||||
RawRedux,
|
RawReduxDeprecated,
|
||||||
RawVuex,
|
RawVuex,
|
||||||
RawMobX,
|
RawMobX,
|
||||||
RawNgRx,
|
RawNgRx,
|
||||||
|
|
@ -64,6 +64,7 @@ import type {
|
||||||
RawTabData,
|
RawTabData,
|
||||||
RawCanvasNode,
|
RawCanvasNode,
|
||||||
RawTagTrigger,
|
RawTagTrigger,
|
||||||
|
RawRedux,
|
||||||
RawIosEvent,
|
RawIosEvent,
|
||||||
RawIosScreenChanges,
|
RawIosScreenChanges,
|
||||||
RawIosClickEvent,
|
RawIosClickEvent,
|
||||||
|
|
@ -127,7 +128,7 @@ export type Profiler = RawProfiler & Timed
|
||||||
|
|
||||||
export type OTable = RawOTable & Timed
|
export type OTable = RawOTable & Timed
|
||||||
|
|
||||||
export type Redux = RawRedux & Timed
|
export type ReduxDeprecated = RawReduxDeprecated & Timed
|
||||||
|
|
||||||
export type Vuex = RawVuex & Timed
|
export type Vuex = RawVuex & Timed
|
||||||
|
|
||||||
|
|
@ -199,6 +200,8 @@ export type CanvasNode = RawCanvasNode & Timed
|
||||||
|
|
||||||
export type TagTrigger = RawTagTrigger & Timed
|
export type TagTrigger = RawTagTrigger & Timed
|
||||||
|
|
||||||
|
export type Redux = RawRedux & Timed
|
||||||
|
|
||||||
export type IosEvent = RawIosEvent & Timed
|
export type IosEvent = RawIosEvent & Timed
|
||||||
|
|
||||||
export type IosScreenChanges = RawIosScreenChanges & Timed
|
export type IosScreenChanges = RawIosScreenChanges & Timed
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export const enum MType {
|
||||||
Fetch = 39,
|
Fetch = 39,
|
||||||
Profiler = 40,
|
Profiler = 40,
|
||||||
OTable = 41,
|
OTable = 41,
|
||||||
Redux = 44,
|
ReduxDeprecated = 44,
|
||||||
Vuex = 45,
|
Vuex = 45,
|
||||||
MobX = 46,
|
MobX = 46,
|
||||||
NgRx = 47,
|
NgRx = 47,
|
||||||
|
|
@ -62,6 +62,7 @@ export const enum MType {
|
||||||
TabData = 118,
|
TabData = 118,
|
||||||
CanvasNode = 119,
|
CanvasNode = 119,
|
||||||
TagTrigger = 120,
|
TagTrigger = 120,
|
||||||
|
Redux = 121,
|
||||||
IosEvent = 93,
|
IosEvent = 93,
|
||||||
IosScreenChanges = 96,
|
IosScreenChanges = 96,
|
||||||
IosClickEvent = 100,
|
IosClickEvent = 100,
|
||||||
|
|
@ -239,8 +240,8 @@ export interface RawOTable {
|
||||||
value: string,
|
value: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RawRedux {
|
export interface RawReduxDeprecated {
|
||||||
tp: MType.Redux,
|
tp: MType.ReduxDeprecated,
|
||||||
action: string,
|
action: string,
|
||||||
state: string,
|
state: string,
|
||||||
duration: number,
|
duration: number,
|
||||||
|
|
@ -500,6 +501,14 @@ export interface RawTagTrigger {
|
||||||
tagId: number,
|
tagId: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RawRedux {
|
||||||
|
tp: MType.Redux,
|
||||||
|
action: string,
|
||||||
|
state: string,
|
||||||
|
duration: number,
|
||||||
|
actionTime: number,
|
||||||
|
}
|
||||||
|
|
||||||
export interface RawIosEvent {
|
export interface RawIosEvent {
|
||||||
tp: MType.IosEvent,
|
tp: MType.IosEvent,
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
|
|
@ -592,4 +601,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 | RawWsChannel | RawSelectionChange | RawMouseThrashing | RawResourceTiming | RawTabChange | RawTabData | RawCanvasNode | RawTagTrigger | 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 | RawReduxDeprecated | 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 | RawTagTrigger | RawRedux | RawIosEvent | RawIosScreenChanges | RawIosClickEvent | RawIosInputEvent | RawIosPerformanceEvent | RawIosLog | RawIosInternalError | RawIosNetworkCall | RawIosSwipeEvent | RawIosIssueEvent;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export const TP_MAP = {
|
||||||
39: MType.Fetch,
|
39: MType.Fetch,
|
||||||
40: MType.Profiler,
|
40: MType.Profiler,
|
||||||
41: MType.OTable,
|
41: MType.OTable,
|
||||||
44: MType.Redux,
|
44: MType.ReduxDeprecated,
|
||||||
45: MType.Vuex,
|
45: MType.Vuex,
|
||||||
46: MType.MobX,
|
46: MType.MobX,
|
||||||
47: MType.NgRx,
|
47: MType.NgRx,
|
||||||
|
|
@ -63,6 +63,7 @@ export const TP_MAP = {
|
||||||
118: MType.TabData,
|
118: MType.TabData,
|
||||||
119: MType.CanvasNode,
|
119: MType.CanvasNode,
|
||||||
120: MType.TagTrigger,
|
120: MType.TagTrigger,
|
||||||
|
121: MType.Redux,
|
||||||
93: MType.IosEvent,
|
93: MType.IosEvent,
|
||||||
96: MType.IosScreenChanges,
|
96: MType.IosScreenChanges,
|
||||||
100: MType.IosClickEvent,
|
100: MType.IosClickEvent,
|
||||||
|
|
|
||||||
|
|
@ -216,7 +216,7 @@ type TrStateAction = [
|
||||||
type: string,
|
type: string,
|
||||||
]
|
]
|
||||||
|
|
||||||
type TrRedux = [
|
type TrReduxDeprecated = [
|
||||||
type: 44,
|
type: 44,
|
||||||
action: string,
|
action: string,
|
||||||
state: string,
|
state: string,
|
||||||
|
|
@ -514,8 +514,16 @@ type TrTagTrigger = [
|
||||||
tagId: number,
|
tagId: number,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
type TrRedux = [
|
||||||
|
type: 121,
|
||||||
|
action: string,
|
||||||
|
state: string,
|
||||||
|
duration: number,
|
||||||
|
actionTime: number,
|
||||||
|
]
|
||||||
|
|
||||||
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 | TrTagTrigger
|
|
||||||
|
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 | TrReduxDeprecated | 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 | TrTagTrigger | TrRedux
|
||||||
|
|
||||||
export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||||
switch(tMsg[0]) {
|
switch(tMsg[0]) {
|
||||||
|
|
@ -726,7 +734,7 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||||
|
|
||||||
case 44: {
|
case 44: {
|
||||||
return {
|
return {
|
||||||
tp: MType.Redux,
|
tp: MType.ReduxDeprecated,
|
||||||
action: tMsg[1],
|
action: tMsg[1],
|
||||||
state: tMsg[2],
|
state: tMsg[2],
|
||||||
duration: tMsg[3],
|
duration: tMsg[3],
|
||||||
|
|
@ -1040,6 +1048,16 @@ export default function translate(tMsg: TrackerMessage): RawMessage | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 121: {
|
||||||
|
return {
|
||||||
|
tp: MType.Redux,
|
||||||
|
action: tMsg[1],
|
||||||
|
state: tMsg[2],
|
||||||
|
duration: tMsg[3],
|
||||||
|
actionTime: tMsg[4],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -184,3 +184,155 @@ svg {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.json-view {
|
||||||
|
display: block;
|
||||||
|
color: #4d4d4d;
|
||||||
|
text-align: left;
|
||||||
|
--json-property: #009033;
|
||||||
|
--json-index: #676dff;
|
||||||
|
--json-number: #676dff;
|
||||||
|
--json-string: #b2762e;
|
||||||
|
--json-boolean: #dc155e;
|
||||||
|
--json-null: #dc155e;
|
||||||
|
}
|
||||||
|
.json-view .json-view--property {
|
||||||
|
color: var(--json-property);
|
||||||
|
}
|
||||||
|
.json-view .json-view--index {
|
||||||
|
color: var(--json-index);
|
||||||
|
}
|
||||||
|
.json-view .json-view--number {
|
||||||
|
color: var(--json-number);
|
||||||
|
}
|
||||||
|
.json-view .json-view--string {
|
||||||
|
color: var(--json-string);
|
||||||
|
}
|
||||||
|
.json-view .json-view--boolean {
|
||||||
|
color: var(--json-boolean);
|
||||||
|
}
|
||||||
|
.json-view .json-view--null {
|
||||||
|
color: var(--json-null);
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .jv-indent {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
.json-view .jv-chevron {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: -20%;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.4;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
:is(.json-view .jv-chevron:hover, .json-view .jv-size:hover + .jv-chevron) {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
.json-view .jv-size {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.4;
|
||||||
|
font-size: 0.875em;
|
||||||
|
font-style: italic;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
vertical-align: -5%;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view :is(.json-view--copy, .json-view--edit),
|
||||||
|
.json-view .json-view--link svg {
|
||||||
|
display: none;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .json-view--input {
|
||||||
|
width: 120px;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid currentColor;
|
||||||
|
padding: 0px 4px;
|
||||||
|
font-size: 87.5%;
|
||||||
|
line-height: 1.25;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.json-view .json-view--deleting {
|
||||||
|
outline: 1px solid #da0000;
|
||||||
|
background-color: #da000011;
|
||||||
|
text-decoration-line: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(.json-view:hover, .json-view--pair:hover) > :is(.json-view--copy, .json-view--edit),
|
||||||
|
:is(.json-view:hover, .json-view--pair:hover) > .json-view--link svg {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view .jv-button {
|
||||||
|
background: transparent;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.json-view .cursor-pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.json-view svg {
|
||||||
|
vertical-align: -10%;
|
||||||
|
}
|
||||||
|
.jv-size-chevron ~ svg {
|
||||||
|
vertical-align: -16%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Themes */
|
||||||
|
.json-view_a11y {
|
||||||
|
color: #545454;
|
||||||
|
--json-property: #aa5d00;
|
||||||
|
--json-index: #007299;
|
||||||
|
--json-number: #007299;
|
||||||
|
--json-string: #008000;
|
||||||
|
--json-boolean: #d91e18;
|
||||||
|
--json-null: #d91e18;
|
||||||
|
}
|
||||||
|
.json-view .jv-size {
|
||||||
|
opacity: 0.6!important;
|
||||||
|
}
|
||||||
|
.json-view_github {
|
||||||
|
color: #005cc5;
|
||||||
|
--json-property: #005cc5;
|
||||||
|
--json-index: #005cc5;
|
||||||
|
--json-number: #005cc5;
|
||||||
|
--json-string: #032f62;
|
||||||
|
--json-boolean: #005cc5;
|
||||||
|
--json-null: #005cc5;
|
||||||
|
}
|
||||||
|
.json-view_vscode {
|
||||||
|
color: #005cc5;
|
||||||
|
--json-property: #0451a5;
|
||||||
|
--json-index: #0000ff;
|
||||||
|
--json-number: #0000ff;
|
||||||
|
--json-string: #a31515;
|
||||||
|
--json-boolean: #0000ff;
|
||||||
|
--json-null: #0000ff;
|
||||||
|
}
|
||||||
|
.json-view_atom {
|
||||||
|
color: #383a42;
|
||||||
|
--json-property: #e45649;
|
||||||
|
--json-index: #986801;
|
||||||
|
--json-number: #986801;
|
||||||
|
--json-string: #50a14f;
|
||||||
|
--json-boolean: #0184bc;
|
||||||
|
--json-null: #0184bc;
|
||||||
|
}
|
||||||
|
.json-view_winter-is-coming {
|
||||||
|
color: #0431fa;
|
||||||
|
--json-property: #3a9685;
|
||||||
|
--json-index: #ae408b;
|
||||||
|
--json-number: #ae408b;
|
||||||
|
--json-string: #8123a9;
|
||||||
|
--json-boolean: #0184bc;
|
||||||
|
--json-null: #0184bc;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"country-flag-icons": "^1.5.7",
|
"country-flag-icons": "^1.5.7",
|
||||||
"deep-diff": "^1.0.2",
|
|
||||||
"fflate": "^0.7.4",
|
"fflate": "^0.7.4",
|
||||||
"fzstd": "^0.1.0",
|
"fzstd": "^0.1.0",
|
||||||
"html-to-image": "^1.9.0",
|
"html-to-image": "^1.9.0",
|
||||||
|
|
@ -51,6 +50,7 @@
|
||||||
"jsx-runtime": "^1.2.0",
|
"jsx-runtime": "^1.2.0",
|
||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.363.0",
|
||||||
"luxon": "^1.24.1",
|
"luxon": "^1.24.1",
|
||||||
|
"microdiff": "^1.4.0",
|
||||||
"mobx": "^6.3.8",
|
"mobx": "^6.3.8",
|
||||||
"mobx-react-lite": "^3.1.6",
|
"mobx-react-lite": "^3.1.6",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
|
@ -67,7 +67,6 @@
|
||||||
"react-draggable": "^4.4.5",
|
"react-draggable": "^4.4.5",
|
||||||
"react-google-recaptcha": "^2.1.0",
|
"react-google-recaptcha": "^2.1.0",
|
||||||
"react-highlight": "^0.14.0",
|
"react-highlight": "^0.14.0",
|
||||||
"react-json-view": "^1.21.3",
|
|
||||||
"react-lazyload": "^3.2.0",
|
"react-lazyload": "^3.2.0",
|
||||||
"react-merge-refs": "^2.0.1",
|
"react-merge-refs": "^2.0.1",
|
||||||
"react-redux": "^5.1.2",
|
"react-redux": "^5.1.2",
|
||||||
|
|
@ -78,6 +77,7 @@
|
||||||
"react-tippy": "^1.4.0",
|
"react-tippy": "^1.4.0",
|
||||||
"react-toastify": "^9.1.1",
|
"react-toastify": "^9.1.1",
|
||||||
"react-virtualized": "^9.22.3",
|
"react-virtualized": "^9.22.3",
|
||||||
|
"react18-json-view": "^0.2.8",
|
||||||
"recharts": "^2.8.0",
|
"recharts": "^2.8.0",
|
||||||
"redux": "^4.0.5",
|
"redux": "^4.0.5",
|
||||||
"redux-immutable": "^4.0.0",
|
"redux-immutable": "^4.0.0",
|
||||||
|
|
|
||||||
4291
frontend/yarn.lock
4291
frontend/yarn.lock
File diff suppressed because it is too large
Load diff
|
|
@ -224,7 +224,7 @@ message 42, 'StateAction', :replayer => false do
|
||||||
string 'Type'
|
string 'Type'
|
||||||
end
|
end
|
||||||
## 43
|
## 43
|
||||||
message 44, 'Redux', :replayer => :devtools do
|
message 44, 'ReduxDeprecated', :replayer => :devtools do
|
||||||
string 'Action'
|
string 'Action'
|
||||||
string 'State'
|
string 'State'
|
||||||
uint 'Duration'
|
uint 'Duration'
|
||||||
|
|
@ -522,6 +522,13 @@ message 120, 'TagTrigger', :replayer => :devtools do
|
||||||
int 'TagId'
|
int 'TagId'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
message 121, 'Redux', :replayer => :devtools do
|
||||||
|
string 'Action'
|
||||||
|
string 'State'
|
||||||
|
uint 'Duration'
|
||||||
|
uint 'ActionTime'
|
||||||
|
end
|
||||||
|
|
||||||
## Backend-only
|
## Backend-only
|
||||||
message 125, 'IssueEvent', :replayer => false, :tracker => false do
|
message 125, 'IssueEvent', :replayer => false, :tracker => false do
|
||||||
uint 'MessageID'
|
uint 'MessageID'
|
||||||
|
|
|
||||||
238
third-party.md
238
third-party.md
|
|
@ -2,123 +2,123 @@
|
||||||
|
|
||||||
Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use.
|
Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use.
|
||||||
|
|
||||||
| Library | License | Scope |
|
| Library | License | Scope |
|
||||||
|----------|-------------|-------------|
|
|----------------------------|-------------|-------------|
|
||||||
| btcutil | IST | Go |
|
| btcutil | IST | Go |
|
||||||
| confluent-kafka-go | Apache2 | Go |
|
| confluent-kafka-go | Apache2 | Go |
|
||||||
| compress | Apache2 | Go |
|
| compress | Apache2 | Go |
|
||||||
| uuid | BSD3 | Go |
|
| uuid | BSD3 | Go |
|
||||||
| mux | BSD3 | Go |
|
| mux | BSD3 | Go |
|
||||||
| lib/pq | MIT | Go |
|
| lib/pq | MIT | Go |
|
||||||
| pgconn | MIT | Go |
|
| pgconn | MIT | Go |
|
||||||
| pgx | MIT | Go |
|
| pgx | MIT | Go |
|
||||||
| go-redis | BSD2 | Go |
|
| go-redis | BSD2 | Go |
|
||||||
| pgerrcode | MIT | Go |
|
| pgerrcode | MIT | Go |
|
||||||
| pgzip | MIT | Go |
|
| pgzip | MIT | Go |
|
||||||
| maxminddb-golang | IST | Go |
|
| maxminddb-golang | IST | Go |
|
||||||
| realip | MIT | Go |
|
| realip | MIT | Go |
|
||||||
| uap-go | Apache2 | Go |
|
| uap-go | Apache2 | Go |
|
||||||
| clickhouse-go | MIT | Go |
|
| clickhouse-go | MIT | Go |
|
||||||
| aws-sdk-go | Apache2 | Go |
|
| aws-sdk-go | Apache2 | Go |
|
||||||
| logging | Apache2 | Go |
|
| logging | Apache2 | Go |
|
||||||
| squirrel | MIT | Go |
|
| squirrel | MIT | Go |
|
||||||
| go-elasticsearch | Apache2 | Go |
|
| go-elasticsearch | Apache2 | Go |
|
||||||
| gorilla/websocket | BSD2 | Go |
|
| gorilla/websocket | BSD2 | Go |
|
||||||
| radix | MIT | Go |
|
| radix | MIT | Go |
|
||||||
| api | BSD3 | Go |
|
| api | BSD3 | Go |
|
||||||
| urllib3 | MIT | Python |
|
| urllib3 | MIT | Python |
|
||||||
| boto3 | Apache2 | Python |
|
| boto3 | Apache2 | Python |
|
||||||
| requests | Apache2 | Python |
|
| requests | Apache2 | Python |
|
||||||
| pyjwt | MIT | Python |
|
| pyjwt | MIT | Python |
|
||||||
| jsbeautifier | MIT | Python |
|
| jsbeautifier | MIT | Python |
|
||||||
| psycopg2-binary | LGPL | Python |
|
| psycopg2-binary | LGPL | Python |
|
||||||
| fastapi | MIT | Python |
|
| fastapi | MIT | Python |
|
||||||
| uvicorn | BSD | Python |
|
| uvicorn | BSD | Python |
|
||||||
| python-decouple | MIT | Python |
|
| python-decouple | MIT | Python |
|
||||||
| pydantic | MIT | Python |
|
| pydantic | MIT | Python |
|
||||||
| apscheduler | MIT | Python |
|
| apscheduler | MIT | Python |
|
||||||
| python-multipart | Apache | Python |
|
| python-multipart | Apache | Python |
|
||||||
| elasticsearch-py | Apache2 | Python |
|
| elasticsearch-py | Apache2 | Python |
|
||||||
| jira | BSD2 | Python |
|
| jira | BSD2 | Python |
|
||||||
| redis-py | MIT | Python |
|
| redis-py | MIT | Python |
|
||||||
| clickhouse-driver | MIT | Python |
|
| clickhouse-driver | MIT | Python |
|
||||||
| python3-saml | MIT | Python |
|
| python3-saml | MIT | Python |
|
||||||
| kubernetes | Apache2 | Python |
|
| kubernetes | Apache2 | Python |
|
||||||
| chalice | Apache2 | Python |
|
| chalice | Apache2 | Python |
|
||||||
| pandas | BSD3 | Python |
|
| pandas | BSD3 | Python |
|
||||||
| numpy | BSD3 | Python |
|
| numpy | BSD3 | Python |
|
||||||
| scikit-learn | BSD3 | Python |
|
| scikit-learn | BSD3 | Python |
|
||||||
|apache-airflow| Apache2| Python|
|
| apache-airflow | Apache2| Python|
|
||||||
|airflow-code-editor| Apache2 | Python|
|
| airflow-code-editor | Apache2 | Python|
|
||||||
|mlflow| Apache2 | Python|
|
| mlflow | Apache2 | Python|
|
||||||
| sqlalchemy | MIT | Python |
|
| sqlalchemy | MIT | Python |
|
||||||
| pandas-redshift | MIT | Python |
|
| pandas-redshift | MIT | Python |
|
||||||
| confluent-kafka | Apache2 | Python |
|
| confluent-kafka | Apache2 | Python |
|
||||||
| amplitude-js | MIT | JavaScript |
|
| amplitude-js | MIT | JavaScript |
|
||||||
| classnames | MIT | JavaScript |
|
| classnames | MIT | JavaScript |
|
||||||
| codemirror | MIT | JavaScript |
|
| codemirror | MIT | JavaScript |
|
||||||
| copy-to-clipboard | MIT | JavaScript |
|
| copy-to-clipboard | MIT | JavaScript |
|
||||||
| jsonwebtoken | MIT | JavaScript |
|
| jsonwebtoken | MIT | JavaScript |
|
||||||
| datamaps | MIT | JavaScript |
|
| datamaps | MIT | JavaScript |
|
||||||
| deep-diff | MIT | JavaScript |
|
| microdiff | MIT | JavaScript |
|
||||||
| immutable | MIT | JavaScript |
|
| immutable | MIT | JavaScript |
|
||||||
| jsbi | Apache2 | JavaScript |
|
| jsbi | Apache2 | JavaScript |
|
||||||
| jshint | MIT | JavaScript |
|
| jshint | MIT | JavaScript |
|
||||||
| luxon | MIT | JavaScript |
|
| luxon | MIT | JavaScript |
|
||||||
| mobx | MIT | JavaScript |
|
| mobx | MIT | JavaScript |
|
||||||
| mobx-react-lite | MIT | JavaScript |
|
| mobx-react-lite | MIT | JavaScript |
|
||||||
| moment | MIT | JavaScript |
|
| moment | MIT | JavaScript |
|
||||||
| moment-range | Unlicense | JavaScript |
|
| moment-range | Unlicense | JavaScript |
|
||||||
| optimal-select | MIT | JavaScript |
|
| optimal-select | MIT | JavaScript |
|
||||||
| rc-time-picker | MIT | JavaScript |
|
| rc-time-picker | MIT | JavaScript |
|
||||||
| snabbdom | MIT | JavaScript |
|
| snabbdom | MIT | JavaScript |
|
||||||
| react | MIT | JavaScript |
|
| react | MIT | JavaScript |
|
||||||
| react-circular-progressbar | MIT | JavaScript |
|
| react-circular-progressbar | MIT | JavaScript |
|
||||||
| react-codemirror2 | MIT | JavaScript |
|
| react-codemirror2 | MIT | JavaScript |
|
||||||
| react-confirm | MIT | JavaScript |
|
| react-confirm | MIT | JavaScript |
|
||||||
| react-datepicker | MIT | JavaScript |
|
| react-datepicker | MIT | JavaScript |
|
||||||
| react-daterange-picker | Apache2 | JavaScript |
|
| react-daterange-picker | Apache2 | JavaScript |
|
||||||
| react-dnd | MIT | JavaScript |
|
| react-dnd | MIT | JavaScript |
|
||||||
| react-dnd-html5-backend | MIT | JavaScript |
|
| react-dnd-html5-backend | MIT | JavaScript |
|
||||||
| react-dom | MIT | JavaScript |
|
| react-dom | MIT | JavaScript |
|
||||||
| react-google-recaptcha | MIT | JavaScript |
|
| react-google-recaptcha | MIT | JavaScript |
|
||||||
| react-json-view | MIT | JavaScript |
|
| react-json-view | MIT | JavaScript |
|
||||||
| react-lazyload | MIT | JavaScript |
|
| react-lazyload | MIT | JavaScript |
|
||||||
| react-redux | MIT | JavaScript |
|
| react-redux | MIT | JavaScript |
|
||||||
| react-router | MIT | JavaScript |
|
| react-router | MIT | JavaScript |
|
||||||
| react-router-dom | MIT | JavaScript |
|
| react-router-dom | MIT | JavaScript |
|
||||||
| react-stripe-elements | MIT | JavaScript |
|
| react-stripe-elements | MIT | JavaScript |
|
||||||
| react-toastify | MIT | JavaScript |
|
| react-toastify | MIT | JavaScript |
|
||||||
| react-virtualized | MIT | JavaScript |
|
| react-virtualized | MIT | JavaScript |
|
||||||
| recharts | MIT | JavaScript |
|
| recharts | MIT | JavaScript |
|
||||||
| redux | MIT | JavaScript |
|
| redux | MIT | JavaScript |
|
||||||
| redux-immutable | BSD3 | JavaScript |
|
| redux-immutable | BSD3 | JavaScript |
|
||||||
| redux-thunk | MIT | JavaScript |
|
| redux-thunk | MIT | JavaScript |
|
||||||
| semantic-ui-react | MIT | JavaScript |
|
| semantic-ui-react | MIT | JavaScript |
|
||||||
| socket.io | MIT | JavaScript |
|
| socket.io | MIT | JavaScript |
|
||||||
| socket.io-client | MIT | JavaScript |
|
| socket.io-client | MIT | JavaScript |
|
||||||
| uWebSockets.js | Apache2 | JavaScript |
|
| uWebSockets.js | Apache2 | JavaScript |
|
||||||
| source-map | BSD3 | JavaScript |
|
| source-map | BSD3 | JavaScript |
|
||||||
| aws-sdk | Apache2 | JavaScript |
|
| aws-sdk | Apache2 | JavaScript |
|
||||||
| serverless | MIT | JavaScript |
|
| serverless | MIT | JavaScript |
|
||||||
| peerjs | MIT | JavaScript |
|
| peerjs | MIT | JavaScript |
|
||||||
| geoip-lite | Apache2 | JavaScript |
|
| geoip-lite | Apache2 | JavaScript |
|
||||||
| ua-parser-js | MIT | JavaScript |
|
| ua-parser-js | MIT | JavaScript |
|
||||||
| express | MIT | JavaScript |
|
| express | MIT | JavaScript |
|
||||||
| jspdf | MIT | JavaScript |
|
| jspdf | MIT | JavaScript |
|
||||||
| html-to-image | MIT | JavaScript |
|
| html-to-image | MIT | JavaScript |
|
||||||
| kafka | Apache2 | Infrastructure |
|
| kafka | Apache2 | Infrastructure |
|
||||||
| stern | Apache2 | Infrastructure |
|
| stern | Apache2 | Infrastructure |
|
||||||
| k9s | Apache2 | Infrastructure |
|
| k9s | Apache2 | Infrastructure |
|
||||||
| minio | AGPLv3| Infrastructure |
|
| minio | AGPLv3| Infrastructure |
|
||||||
| postgreSQL | PostgreSQL License | Infrastructure |
|
| postgreSQL | PostgreSQL License | Infrastructure |
|
||||||
| k3s | Apache2 | Infrastructure |
|
| k3s | Apache2 | Infrastructure |
|
||||||
| nginx | BSD2 | Infrastructure |
|
| nginx | BSD2 | Infrastructure |
|
||||||
| clickhouse | Apache2 | Infrastructure |
|
| clickhouse | Apache2 | Infrastructure |
|
||||||
| redis | BSD3 | Infrastructure |
|
| redis | BSD3 | Infrastructure |
|
||||||
| yq | MIT | Infrastructure |
|
| yq | MIT | Infrastructure |
|
||||||
| html2canvas | MIT | JavaScript |
|
| html2canvas | MIT | JavaScript |
|
||||||
| eget | MIT | Infrastructure |
|
| eget | MIT | Infrastructure |
|
||||||
| @medv/finder | MIT | JavaScript |
|
| @medv/finder | MIT | JavaScript |
|
||||||
| fflate | MIT | JavaScript |
|
| fflate | MIT | JavaScript |
|
||||||
| fzstd | MIT | JavaScript |
|
| fzstd | MIT | JavaScript |
|
||||||
|
|
|
||||||
1
tracker/tracker-redux/build/webworker.js
Normal file
1
tracker/tracker-redux/build/webworker.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"use strict";const e={};["DEL","UNDEF","TRUE","FALSE","NUMBER","BIGINT","FUNCTION","STRING","SYMBOL","NULL","OBJECT","ARRAY"].forEach(((t,r)=>e[t]=String.fromCharCode(r+57344)));const t=new class{constructor(e,t=1/0){this._hash=e,this._slen=t,this._refmap=new Map,this._refset=new Set}_ref_str(t){if(t.length<this._slen&&!t.includes(e.DEL))return t;let r=this._refmap.get(t);return void 0===r&&(r=this._hash(t),this._refmap.set(t,r)),r}_encode_prim(t){switch(typeof t){case"undefined":return e.UNDEF;case"boolean":return t?e.TRUE:e.FALSE;case"number":return e.NUMBER+t.toString();case"bigint":return e.BIGINT+t.toString();case"function":return e.FUNCTION;case"string":return e.STRING+this._ref_str(t);case"symbol":return e.SYMBOL+this._ref_str(t.toString().slice(7,-1))}if(null===t)return e.NULL}_encode_obj(t,r=this._refmap.get(t)){return(Array.isArray(t)?e.ARRAY:e.OBJECT)+r}_encode_term(e){return this._encode_prim(e)||this._encode_obj(e)}_encode_deep(t,r){const s=this._encode_prim(t);if(void 0!==s)return s;const n=this._refmap.get(t);switch(typeof n){case"number":return(r-n).toString();case"string":return this._encode_obj(t,n)}this._refmap.set(t,r);const a=this._hash((Array.isArray(t)?t.map((e=>this._encode_deep(e,r+1))):Object.keys(t).sort().map((s=>this._ref_str(s)+e.DEL+this._encode_deep(t[s],r+1)))).join(e.DEL));return this._refmap.set(t,a),this._encode_obj(t,a)}encode(e){return this._encode_deep(e,0)}commit(){const t={};return this._refmap.forEach(((r,s)=>{this._refset.has(r)||(this._refset.add(r),"string"!=typeof s&&(s=(Array.isArray(s)?s.map((e=>this._encode_term(e))):Object.keys(s).map((t=>this._ref_str(t)+e.DEL+this._encode_term(s[t])))).join(e.DEL)),t[r]=s)})),this._refmap.clear(),t}clear(){this._refmap.clear(),this._refset.clear()}}((function(e,t=0){return function(e){let t=new ArrayBuffer(4);return new DataView(t).setUint32(0,e,!1),btoa(String.fromCharCode.apply(null,new Uint8Array(t)))}(function(e,t){var r,s,n,a,o,c,i,h,_,d,f,u,m,p;for(o=(a=(r=e.length)-(s=3&r))-(n=7&a),c=t,_=11601,d=3432906752,f=13715,u=461832192,p=3864292196,h=0;h<o;)i=255&e.charCodeAt(h)|(255&e.charCodeAt(++h))<<8|(255&e.charCodeAt(++h))<<16|(255&e.charCodeAt(++h))<<24,m=255&e.charCodeAt(++h)|(255&e.charCodeAt(++h))<<8|(255&e.charCodeAt(++h))<<16|(255&e.charCodeAt(++h))<<24,++h,c=5*(c=(c^=i=(u*(i=(i=(d*i|0)+_*i)<<15|i>>>17)|0)+f*i)<<13|c>>>19)+p,c=5*(c=(c^=m=(u*(m=(m=(d*m|0)+_*m)<<15|m>>>17)|0)+f*m)<<13|c>>>19)+p;switch(n&&(i=255&e.charCodeAt(h)|(255&e.charCodeAt(++h))<<8|(255&e.charCodeAt(++h))<<16|(255&e.charCodeAt(++h))<<24,++h,c=5*(c=(c^=i=(u*(i=(i=(d*i|0)+_*i)<<15|i>>>17)|0)+f*i)<<13|c>>>19)+p),i=0,s){case 3:i^=(255&e.charCodeAt(h+2))<<16;case 2:i^=(255&e.charCodeAt(h+1))<<8;case 1:c^=i=(u*(i=(i=(d*(i^=255&e.charCodeAt(h))|0)+_*i)<<15|i>>>17)|0)+f*i}return c^=r,c=(2246770688*(c^=c>>>16)|0)+51819*c,c=(3266445312*(c^=c>>>13)|0)+44597*c,(c^=c>>>16)>>>0}(e,t))}),50),r={enabled:!0,throttle:50};let s,n=null;self.onmessage=({data:e})=>{if("action"===e.type)try{const a=t.encode(e.action);let o;r.enabled&&(o=(e=>{if(!n||!s||Date.now()-s>r.throttle){const r=t.encode(e);return s=Date.now(),n=r,r}return n})(e.state));const c=t.commit();postMessage({type:"encoded",action:a,state:o,table:c,timestamp:e.timestamp})}catch{t.clear()}};
|
||||||
Binary file not shown.
|
|
@ -16,19 +16,26 @@
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "prettier --write 'src/**/*.ts' README.md && tsc --noEmit",
|
"lint": "prettier --write 'src/**/*.ts' README.md && tsc --noEmit",
|
||||||
"build": "npm run build-es && npm run build-cjs",
|
"build": "npm run build-es && npm run build-cjs && bun run rollup && bun run compile",
|
||||||
|
"compile": "node --experimental-modules --experimental-json-modules script/compile.cjs",
|
||||||
"build-es": "rm -Rf lib && tsc",
|
"build-es": "rm -Rf lib && tsc",
|
||||||
|
"rollup": "rollup --config rollup.config.js",
|
||||||
"build-cjs": "rm -Rf cjs && tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > cjs/package.json && replace-in-files cjs/* --string='@openreplay/tracker' --replacement='@openreplay/tracker/cjs'",
|
"build-cjs": "rm -Rf cjs && tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > cjs/package.json && replace-in-files cjs/* --string='@openreplay/tracker' --replacement='@openreplay/tracker/cjs'",
|
||||||
"prepublishOnly": "npm run build"
|
"prepublishOnly": "npm run build"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@openreplay/tracker": ">=12.0.6",
|
"@openreplay/tracker": ">=13.0.0",
|
||||||
"redux": "^4.0.0"
|
"redux": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openreplay/tracker": "file:../tracker",
|
"@openreplay/tracker": "file:../tracker",
|
||||||
"prettier": "^1.18.2",
|
"prettier": "^1.18.2",
|
||||||
"replace-in-files-cli": "^1.0.0",
|
"replace-in-files-cli": "^1.0.0",
|
||||||
"typescript": "^4.6.0-dev.20211126"
|
"typescript": "^4.6.0-dev.20211126",
|
||||||
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"replace-in-files": "^3.0.0",
|
||||||
|
"rollup": "^4.14.0",
|
||||||
|
"rollup-plugin-terser": "^7.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
tracker/tracker-redux/rollup.config.js
Normal file
12
tracker/tracker-redux/rollup.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import { babel } from '@rollup/plugin-babel'
|
||||||
|
import { terser } from 'rollup-plugin-terser'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
input: 'lib/worker/worker.js',
|
||||||
|
output: {
|
||||||
|
file: 'build/webworker.js',
|
||||||
|
format: 'cjs',
|
||||||
|
},
|
||||||
|
plugins: [resolve(), babel({ babelHelpers: 'bundled' }), terser({ mangle: { reserved: ['$'] } })],
|
||||||
|
}
|
||||||
30
tracker/tracker-redux/script/compile.cjs
Normal file
30
tracker/tracker-redux/script/compile.cjs
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const { promises: fs } = require('fs');
|
||||||
|
const replaceInFiles = require('replace-in-files');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const webworker = await fs.readFile('build/webworker.js', 'utf8');
|
||||||
|
await replaceInFiles({
|
||||||
|
files: 'cjs/**/*',
|
||||||
|
from: 'WEBWORKER_BODY',
|
||||||
|
to: webworker.replace(/'/g, "\\'").replace(/\n/g, ''),
|
||||||
|
});
|
||||||
|
await replaceInFiles({
|
||||||
|
files: 'lib/**/*',
|
||||||
|
from: 'WEBWORKER_BODY',
|
||||||
|
to: webworker.replace(/'/g, "\\'").replace(/\n/g, ''),
|
||||||
|
});
|
||||||
|
await fs.writeFile('cjs/package.json', `{ "type": "commonjs" }`);
|
||||||
|
await replaceInFiles({
|
||||||
|
files: 'cjs/*',
|
||||||
|
from: /\.\.\/common/g,
|
||||||
|
to: './common',
|
||||||
|
});
|
||||||
|
await replaceInFiles({
|
||||||
|
files: 'cjs/**/*',
|
||||||
|
from: /\.\.\/\.\.\/common/g,
|
||||||
|
to: '../common',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
main()
|
||||||
|
.then(() => console.log('compiled'))
|
||||||
|
.catch(err => console.error(err));
|
||||||
|
|
@ -30,6 +30,9 @@ export default function(opts: Partial<Options> = {}) {
|
||||||
if (app === null) {
|
if (app === null) {
|
||||||
return () => next => action => next(action);
|
return () => next => action => next(action);
|
||||||
}
|
}
|
||||||
|
const worker = new Worker(
|
||||||
|
URL.createObjectURL(new Blob(['WEBWORKER_BODY'], { type: 'text/javascript' })),
|
||||||
|
);
|
||||||
const encoder = new Encoder(murmur, 50);
|
const encoder = new Encoder(murmur, 50);
|
||||||
app.attachStopCallback(() => {
|
app.attachStopCallback(() => {
|
||||||
encoder.clear()
|
encoder.clear()
|
||||||
|
|
@ -55,23 +58,30 @@ export default function(opts: Partial<Options> = {}) {
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
const duration = performance.now() - startTime;
|
const duration = performance.now() - startTime;
|
||||||
try {
|
const actionTs = app?.timestamp() ?? 0
|
||||||
const type = options.actionType(action);
|
worker.postMessage({
|
||||||
if (typeof type === 'string' && type) {
|
type: 'action',
|
||||||
app.send(Messages.StateAction(type));
|
action: options.actionTransformer(action),
|
||||||
|
state: options.stateTransformer(getState()),
|
||||||
|
timestamp: actionTs,
|
||||||
|
})
|
||||||
|
worker.onmessage = ({ data }) => {
|
||||||
|
if (data.type === 'encoded') {
|
||||||
|
const _action = data.action;
|
||||||
|
const _currState = data.state;
|
||||||
|
const _table = data.table;
|
||||||
|
const _timestamp = data.timestamp;
|
||||||
|
console.log('encoded', _action, _currState, _table, _timestamp, app?.timestamp())
|
||||||
|
for (let key in _table) app.send(Messages.OTable(key, _table[key]));
|
||||||
|
app.send(Messages.Redux(_action, _currState, duration, _timestamp)); // TODO: add timestamp
|
||||||
}
|
}
|
||||||
const _action = encoder.encode(options.actionTransformer(action));
|
}
|
||||||
let _currState: string
|
worker.onerror = (e) => {
|
||||||
if (options.stateUpdateBatching.enabled) {
|
console.error('OR Redux: worker_error', e)
|
||||||
_currState = batchEncoding(getState());
|
}
|
||||||
} else {
|
const type = options.actionType(action);
|
||||||
_currState = encoder.encode(options.stateTransformer(getState()));
|
if (typeof type === 'string' && type) {
|
||||||
}
|
app.send(Messages.StateAction(type));
|
||||||
const _table = encoder.commit();
|
|
||||||
for (let key in _table) app.send(Messages.OTable(key, _table[key]));
|
|
||||||
app.send(Messages.Redux(_action, _currState, duration));
|
|
||||||
} catch {
|
|
||||||
encoder.clear();
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
70
tracker/tracker-redux/src/worker/worker.ts
Normal file
70
tracker/tracker-redux/src/worker/worker.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Encoder, murmur } from '../syncod-v2/index.js';
|
||||||
|
|
||||||
|
type FromWorker = {
|
||||||
|
type: 'encoded';
|
||||||
|
action: string;
|
||||||
|
state: string;
|
||||||
|
table: Record<string, any>;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
type ToWorker = {
|
||||||
|
type: 'action';
|
||||||
|
action: Record<string, any>;
|
||||||
|
state: Record<string, any>;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare function postMessage(message: FromWorker): void;
|
||||||
|
|
||||||
|
const encoder = new Encoder(murmur, 50);
|
||||||
|
const options = {
|
||||||
|
stateUpdateBatching: {
|
||||||
|
enabled: true,
|
||||||
|
throttle: 50,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let lastCommit: number;
|
||||||
|
let lastState: string | null = null;
|
||||||
|
|
||||||
|
const batchEncoding = (state: Record<string, any>) => {
|
||||||
|
if (
|
||||||
|
!lastState ||
|
||||||
|
!lastCommit ||
|
||||||
|
Date.now() - lastCommit > options.stateUpdateBatching.throttle
|
||||||
|
) {
|
||||||
|
const _state = encoder.encode(state);
|
||||||
|
lastCommit = Date.now();
|
||||||
|
lastState = _state;
|
||||||
|
return _state;
|
||||||
|
} else {
|
||||||
|
return lastState;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
self.onmessage = ({ data }: ToWorker) => {
|
||||||
|
switch (data.type) {
|
||||||
|
case 'action': {
|
||||||
|
try {
|
||||||
|
const _action = encoder.encode(data.action);
|
||||||
|
let _currState: string;
|
||||||
|
if (options.stateUpdateBatching.enabled) {
|
||||||
|
_currState = batchEncoding(data.state);
|
||||||
|
} else {
|
||||||
|
_currState = encoder.encode(data.state);
|
||||||
|
}
|
||||||
|
const _table = encoder.commit();
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
type: 'encoded',
|
||||||
|
action: _action,
|
||||||
|
state: _currState,
|
||||||
|
table: _table,
|
||||||
|
timestamp: data.timestamp,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
encoder.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -33,7 +33,7 @@ export declare const enum Type {
|
||||||
Profiler = 40,
|
Profiler = 40,
|
||||||
OTable = 41,
|
OTable = 41,
|
||||||
StateAction = 42,
|
StateAction = 42,
|
||||||
Redux = 44,
|
ReduxDeprecated = 44,
|
||||||
Vuex = 45,
|
Vuex = 45,
|
||||||
MobX = 46,
|
MobX = 46,
|
||||||
NgRx = 47,
|
NgRx = 47,
|
||||||
|
|
@ -74,6 +74,7 @@ export declare const enum Type {
|
||||||
TabData = 118,
|
TabData = 118,
|
||||||
CanvasNode = 119,
|
CanvasNode = 119,
|
||||||
TagTrigger = 120,
|
TagTrigger = 120,
|
||||||
|
Redux = 121,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -288,8 +289,8 @@ export type StateAction = [
|
||||||
/*type:*/ string,
|
/*type:*/ string,
|
||||||
]
|
]
|
||||||
|
|
||||||
export type Redux = [
|
export type ReduxDeprecated = [
|
||||||
/*type:*/ Type.Redux,
|
/*type:*/ Type.ReduxDeprecated,
|
||||||
/*action:*/ string,
|
/*action:*/ string,
|
||||||
/*state:*/ string,
|
/*state:*/ string,
|
||||||
/*duration:*/ number,
|
/*duration:*/ number,
|
||||||
|
|
@ -586,6 +587,14 @@ export type TagTrigger = [
|
||||||
/*tagId:*/ number,
|
/*tagId:*/ number,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export type Redux = [
|
||||||
|
/*type:*/ Type.Redux,
|
||||||
|
/*action:*/ string,
|
||||||
|
/*state:*/ string,
|
||||||
|
/*duration:*/ number,
|
||||||
|
/*actionTime:*/ number,
|
||||||
|
]
|
||||||
|
|
||||||
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 | TagTrigger
|
|
||||||
|
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 | ReduxDeprecated | 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 | TagTrigger | Redux
|
||||||
export default Message
|
export default Message
|
||||||
|
|
|
||||||
|
|
@ -665,6 +665,8 @@ export default class App {
|
||||||
deviceMemory,
|
deviceMemory,
|
||||||
jsHeapSizeLimit,
|
jsHeapSizeLimit,
|
||||||
timezone: getTimezone(),
|
timezone: getTimezone(),
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
|
|
|
||||||
|
|
@ -396,13 +396,13 @@ export function StateAction(
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Redux(
|
export function ReduxDeprecated(
|
||||||
action: string,
|
action: string,
|
||||||
state: string,
|
state: string,
|
||||||
duration: number,
|
duration: number,
|
||||||
): Messages.Redux {
|
): Messages.ReduxDeprecated {
|
||||||
return [
|
return [
|
||||||
Messages.Type.Redux,
|
Messages.Type.ReduxDeprecated,
|
||||||
action,
|
action,
|
||||||
state,
|
state,
|
||||||
duration,
|
duration,
|
||||||
|
|
@ -951,3 +951,18 @@ export function TagTrigger(
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Redux(
|
||||||
|
action: string,
|
||||||
|
state: string,
|
||||||
|
duration: number,
|
||||||
|
actionTime: number,
|
||||||
|
): Messages.Redux {
|
||||||
|
return [
|
||||||
|
Messages.Type.Redux,
|
||||||
|
action,
|
||||||
|
state,
|
||||||
|
duration,
|
||||||
|
actionTime,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
||||||
return this.string(msg[1])
|
return this.string(msg[1])
|
||||||
break
|
break
|
||||||
|
|
||||||
case Messages.Type.Redux:
|
case Messages.Type.ReduxDeprecated:
|
||||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3])
|
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -298,6 +298,10 @@ export default class MessageEncoder extends PrimitiveEncoder {
|
||||||
return this.int(msg[1])
|
return this.int(msg[1])
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case Messages.Type.Redux:
|
||||||
|
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.uint(msg[4])
|
||||||
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue