Merge branch 'assist' into dev
|
|
@ -1,6 +1,6 @@
|
|||
package intervals
|
||||
|
||||
const EVENTS_COMMIT_INTERVAL = 1 * 60 * 1000
|
||||
const EVENTS_COMMIT_INTERVAL = 30 * 1000
|
||||
const HEARTBEAT_INTERVAL = 2 * 60 * 1000
|
||||
const INTEGRATIONS_REQUEST_INTERVAL = 2 * 60 * 1000
|
||||
const EVENTS_PAGE_EVENT_TIMEOUT = 2 * 60 * 1000
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ const siteIdRequiredPaths = [
|
|||
'/rehydrations',
|
||||
'/sourcemaps',
|
||||
'/errors',
|
||||
'/funnels'
|
||||
'/funnels',
|
||||
'/assist'
|
||||
];
|
||||
|
||||
const noStoringFetchPathStarts = [
|
||||
|
|
|
|||
11
frontend/app/components/Assist/Assist.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
import ChatWindow from './ChatWindow';
|
||||
|
||||
|
||||
export default function Assist() {
|
||||
return (
|
||||
<div className="absolute">
|
||||
{/* <ChatWindow /> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
29
frontend/app/components/Assist/ChatControls/ChatControls.css
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
.controls {
|
||||
height: 38px;
|
||||
/* margin-top: 5px; */
|
||||
/* background-color: white; */
|
||||
/* border-top: solid thin #CCC; */
|
||||
}
|
||||
|
||||
.btnWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
font-size: 12px;
|
||||
color: $gray-medium;
|
||||
|
||||
&.disabled {
|
||||
/* background-color: red; */
|
||||
& svg {
|
||||
fill: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.endButton {
|
||||
background-color: $red;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
54
frontend/app/components/Assist/ChatControls/ChatControls.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import React, { useState } from 'react'
|
||||
import stl from './ChatControls.css'
|
||||
import cn from 'classnames'
|
||||
import { Button, Icon } from 'UI'
|
||||
|
||||
interface Props {
|
||||
stream: MediaStream | null,
|
||||
endCall: () => void
|
||||
}
|
||||
function ChatControls({ stream, endCall } : Props) {
|
||||
const [audioEnabled, setAudioEnabled] = useState(true)
|
||||
const [videoEnabled, setVideoEnabled] = useState(true)
|
||||
|
||||
const toggleAudio = () => {
|
||||
if (!stream) { return; }
|
||||
const aEn = !audioEnabled
|
||||
stream.getAudioTracks().forEach(track => track.enabled = aEn);
|
||||
setAudioEnabled(aEn);
|
||||
}
|
||||
|
||||
const toggleVideo = () => {
|
||||
if (!stream) { return; }
|
||||
const vEn = !videoEnabled;
|
||||
stream.getVideoTracks().forEach(track => track.enabled = vEn);
|
||||
setVideoEnabled(vEn)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn(stl.controls, "flex items-center w-full justify-start bottom-0 px-2")}>
|
||||
<div className="flex items-center">
|
||||
<div className={cn(stl.btnWrapper, { [stl.disabled]: !audioEnabled})}>
|
||||
<Button plain size="small" onClick={toggleAudio} noPadding className="flex items-center">
|
||||
<Icon name={audioEnabled ? 'mic' : 'mic-mute'} size="16" />
|
||||
<span className="ml-2 color-gray-medium text-sm">{audioEnabled ? 'Mute' : 'Unmute'}</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={cn(stl.btnWrapper, { [stl.disabled]: !videoEnabled})}>
|
||||
<Button plain size="small" onClick={toggleVideo} noPadding className="flex items-center">
|
||||
<Icon name={ videoEnabled ? 'camera-video' : 'camera-video-off' } size="16" />
|
||||
<span className="ml-2 color-gray-medium text-sm">{videoEnabled ? 'Stop Video' : 'Start Video'}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<button className={stl.endButton} onClick={endCall}>
|
||||
END
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatControls
|
||||
1
frontend/app/components/Assist/ChatControls/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ChatControls'
|
||||
42
frontend/app/components/Assist/ChatWindow/ChatWindow.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import React, { useState, FC } from 'react'
|
||||
import VideoContainer from '../components/VideoContainer'
|
||||
import { Icon, Popup, Button } from 'UI'
|
||||
import cn from 'classnames'
|
||||
import Counter from 'App/components/shared/SessionItem/Counter'
|
||||
import stl from './chatWindow.css'
|
||||
import ChatControls from '../ChatControls/ChatControls'
|
||||
import Draggable from 'react-draggable';
|
||||
|
||||
export interface Props {
|
||||
incomeStream: MediaStream | null,
|
||||
localStream: MediaStream | null,
|
||||
userId: String,
|
||||
endCall: () => void
|
||||
}
|
||||
|
||||
const ChatWindow: FC<Props> = function ChatWindow({ userId, incomeStream, localStream, endCall }) {
|
||||
const [minimize, setMinimize] = useState(false)
|
||||
|
||||
return (
|
||||
<Draggable handle=".handle" bounds="body">
|
||||
<div
|
||||
className={cn(stl.wrapper, "fixed radius bg-white shadow-xl mt-16")}
|
||||
style={{ width: '280px' }}
|
||||
>
|
||||
<div className="handle flex items-center p-2 cursor-move select-none">
|
||||
<div className={stl.headerTitle}><b>Meeting</b> {userId}</div>
|
||||
<Counter startTime={new Date().getTime() } className="text-sm ml-auto" />
|
||||
</div>
|
||||
<div className={cn(stl.videoWrapper, {'hidden' : minimize}, 'relative')}>
|
||||
<VideoContainer stream={ incomeStream } />
|
||||
<div className="absolute bottom-0 right-0 z-50">
|
||||
<VideoContainer stream={ localStream } muted width={50} />
|
||||
</div>
|
||||
</div>
|
||||
<ChatControls stream={localStream} endCall={endCall} />
|
||||
</div>
|
||||
</Draggable>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChatWindow
|
||||
21
frontend/app/components/Assist/ChatWindow/chatWindow.css
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.wrapper {
|
||||
background-color: white;
|
||||
border: solid thin #000;
|
||||
border-radius: 3px;
|
||||
position: fixed;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-size: 12px;
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.videoWrapper {
|
||||
height: 180px;
|
||||
overflow: hidden;
|
||||
background-color: #000;
|
||||
}
|
||||
1
frontend/app/components/Assist/ChatWindow/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ChatWindow'
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import React from 'react'
|
||||
import { Button } from 'UI'
|
||||
|
||||
function ScreenSharing() {
|
||||
const videoRef = React.createRef<HTMLVideoElement>()
|
||||
|
||||
function handleSuccess(stream) {
|
||||
// startButton.disabled = true;
|
||||
//videoRef.current?.srcObject = stream;
|
||||
// @ts-ignore
|
||||
window.stream = stream; // make variable available to browser console
|
||||
|
||||
stream.getVideoTracks()[0].addEventListener('ended', () => {
|
||||
console.log('The user has ended sharing the screen');
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(error) {
|
||||
console.log(`getDisplayMedia error: ${error.name}`, error);
|
||||
}
|
||||
|
||||
const startScreenSharing = () => {
|
||||
// @ts-ignore
|
||||
navigator.mediaDevices.getDisplayMedia({video: true})
|
||||
.then(handleSuccess, handleError);
|
||||
}
|
||||
|
||||
const stopScreenSharing = () => {
|
||||
// @ts-ignore
|
||||
window.stream.stop()
|
||||
console.log('Stop screen sharing')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-red">
|
||||
<video ref={ videoRef } id="screen-share" autoPlay loop muted></video>
|
||||
<div className="absolute left-0 right-0 bottom-0">
|
||||
<Button onClick={startScreenSharing}>Start</Button>
|
||||
<Button onClick={stopScreenSharing}>Stop</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScreenSharing
|
||||
1
frontend/app/components/Assist/ScreenSharing/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './ScreenSharing'
|
||||
8
frontend/app/components/Assist/assist.stories.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import ChatWindow from './ChatWindow';
|
||||
|
||||
storiesOf('Assist', module)
|
||||
.add('ChatWindow', () => (
|
||||
<ChatWindow userId="test@test.com" />
|
||||
))
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
.inCall {
|
||||
& svg {
|
||||
fill: $red
|
||||
}
|
||||
color: $red;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Popup, Icon } from 'UI'
|
||||
import { connect } from 'react-redux'
|
||||
import cn from 'classnames'
|
||||
import { toggleChatWindow } from 'Duck/sessions';
|
||||
import { connectPlayer } from 'Player/store';
|
||||
import ChatWindow from '../../ChatWindow';
|
||||
import { callPeer } from 'Player'
|
||||
import { CallingState, ConnectionStatus } from 'Player/MessageDistributor/managers/AssistManager';
|
||||
import { toast } from 'react-toastify';
|
||||
import stl from './AassistActions.css'
|
||||
|
||||
interface Props {
|
||||
userId: String,
|
||||
toggleChatWindow: (state) => void,
|
||||
calling: CallingState,
|
||||
peerConnectionStatus: ConnectionStatus
|
||||
}
|
||||
|
||||
function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus }: Props) {
|
||||
const [ incomeStream, setIncomeStream ] = useState<MediaStream | null>(null);
|
||||
const [ localStream, setLocalStream ] = useState<MediaStream | null>(null);
|
||||
const [ endCall, setEndCall ] = useState<()=>void>(()=>{});
|
||||
const [ disconnected, setDisconnected ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
return endCall
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
console.log('peerConnectionStatus', peerConnectionStatus)
|
||||
if (peerConnectionStatus == 4) {
|
||||
toast.info(`Live session is closed.`);
|
||||
setDisconnected(true)
|
||||
}
|
||||
}, [peerConnectionStatus])
|
||||
|
||||
function onClose(stream) {
|
||||
stream.getTracks().forEach(t=>t.stop());
|
||||
}
|
||||
|
||||
function onReject() {
|
||||
toast.info(`Call was rejected.`);
|
||||
}
|
||||
|
||||
function onError() {
|
||||
toast.error(`Something went wrong!`);
|
||||
}
|
||||
|
||||
function call() {
|
||||
navigator.mediaDevices.getUserMedia({video:true, audio:true})
|
||||
.then(lStream => {
|
||||
setLocalStream(lStream);
|
||||
setEndCall(() => callPeer(
|
||||
lStream,
|
||||
setIncomeStream,
|
||||
onClose.bind(null, lStream),
|
||||
onReject,
|
||||
onError
|
||||
));
|
||||
}).catch(onError);
|
||||
}
|
||||
|
||||
const inCall = calling == 0 || calling == 1
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Popup
|
||||
trigger={
|
||||
<div
|
||||
className={
|
||||
cn(
|
||||
'cursor-pointer p-2 mr-2 flex items-center',
|
||||
{[stl.inCall] : inCall },
|
||||
{[stl.disabled]: disconnected}
|
||||
)
|
||||
}
|
||||
onClick={inCall ? endCall : call}
|
||||
role="button"
|
||||
>
|
||||
<Icon
|
||||
name="headset"
|
||||
size="20"
|
||||
color={ inCall ? "red" : "gray-darkest" }
|
||||
/>
|
||||
<span className={cn("ml-2", { 'text-red' : inCall })}>{ inCall ? 'End Meeting' : 'Start Meeting' }</span>
|
||||
</div>
|
||||
}
|
||||
content={ `Call ${userId}` }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="top right"
|
||||
/>
|
||||
<div className="fixed ml-3 left-0 top-0" style={{ zIndex: 999 }}>
|
||||
{ inCall && <ChatWindow endCall={endCall} userId={userId} incomeStream={incomeStream} localStream={localStream} /> }
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const con = connect(null, { toggleChatWindow })
|
||||
|
||||
export default con(connectPlayer(state => ({
|
||||
calling: state.calling,
|
||||
peerConnectionStatus: state.peerConnectionStatus,
|
||||
}))(AssistActions))
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './AssistActions'
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React, { useEffect, useRef } from 'react'
|
||||
|
||||
interface Props {
|
||||
stream: MediaStream | null
|
||||
muted?: boolean,
|
||||
width?: number
|
||||
}
|
||||
|
||||
function VideoContainer({ stream, muted = false, width = 280 }: Props) {
|
||||
const ref = useRef<HTMLVideoElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
ref.current.srcObject = stream;
|
||||
}
|
||||
}, [ ref.current, stream ])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<video autoPlay ref={ ref } muted={ muted } style={{ width: width }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VideoContainer
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './VideoContainer'
|
||||
1
frontend/app/components/Assist/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './Assist'
|
||||
|
|
@ -25,6 +25,7 @@ import { LAST_7_DAYS } from 'Types/app/period';
|
|||
import { resetFunnel } from 'Duck/funnels';
|
||||
import { resetFunnelFilters } from 'Duck/funnelFilters'
|
||||
import NoSessionsMessage from '../shared/NoSessionsMessage';
|
||||
import LiveSessionList from './LiveSessionList'
|
||||
|
||||
const AUTOREFRESH_INTERVAL = 10 * 60 * 1000;
|
||||
|
||||
|
|
@ -134,7 +135,6 @@ export default class BugFinder extends React.PureComponent {
|
|||
|
||||
setActiveTab = tab => {
|
||||
this.props.setActiveTab(tab);
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -157,12 +157,10 @@ export default class BugFinder extends React.PureComponent {
|
|||
className="mb-5"
|
||||
>
|
||||
<EventFilter />
|
||||
</div>
|
||||
{activeFlow && activeFlow.type === 'flows' ?
|
||||
<FunnelList />
|
||||
:
|
||||
<SessionList onMenuItemClick={this.setActiveTab} />
|
||||
}
|
||||
</div>
|
||||
{ activeFlow && activeFlow.type === 'flows' && <FunnelList /> }
|
||||
{ activeTab.type !== 'live' && <SessionList onMenuItemClick={this.setActiveTab} /> }
|
||||
{ activeTab.type === 'live' && <LiveSessionList /> }
|
||||
</div>
|
||||
</div>
|
||||
<RehydrateSlidePanel
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const customFilterAutoCompleteKeys = ['METADATA', KEYS.CLICK, KEYS.USER_BROWSER,
|
|||
customFilters: state.getIn([ 'filters', 'customFilters' ]),
|
||||
variables: state.getIn([ 'customFields', 'list' ]),
|
||||
sources: state.getIn([ 'customFields', 'sources' ]),
|
||||
activeTab: state.getIn([ 'sessions', 'activeTab', 'type' ]),
|
||||
}), {
|
||||
applyFilter,
|
||||
setActiveKey,
|
||||
|
|
@ -81,10 +82,11 @@ export default class FilterModal extends React.PureComponent {
|
|||
};
|
||||
|
||||
renderList(type, list) {
|
||||
const { activeTab } = this.props;
|
||||
const blocks = [];
|
||||
for (let j = 0; j < list.length; j++) {
|
||||
blocks.push(
|
||||
<div key={`${ j }-block`} className="mr-5" >
|
||||
<div key={`${ j }-block`} className={cn("mr-5", { [stl.disabled]: activeTab === 'live' && list[j].key !== 'USERID' })} >
|
||||
{ list[ j ] && this.renderFilterItem(type, list[ j ]) }
|
||||
</div>
|
||||
);
|
||||
|
|
@ -136,6 +138,7 @@ export default class FilterModal extends React.PureComponent {
|
|||
loading = false,
|
||||
searchedEvents,
|
||||
searchQuery = '',
|
||||
activeTab,
|
||||
} = this.props;
|
||||
const { query } = this.state;
|
||||
const reg = getRE(query, 'i');
|
||||
|
|
@ -158,6 +161,8 @@ export default class FilterModal extends React.PureComponent {
|
|||
const staticFilters = preloadedFilters
|
||||
.filter(({ value, actualValue }) => !this.props.loading && this.test(actualValue || value))
|
||||
|
||||
// console.log('filteredList', filteredList);
|
||||
|
||||
return (!displayed ? null :
|
||||
<div className={ stl.modal }>
|
||||
{ loading &&
|
||||
|
|
@ -173,22 +178,26 @@ export default class FilterModal extends React.PureComponent {
|
|||
{ searchQuery &&
|
||||
<React.Fragment>
|
||||
{this.renderEventDropdownPart(TYPES.USERID, 'User Id')}
|
||||
{this.renderEventDropdownPart(TYPES.METADATA, 'Metadata')}
|
||||
{this.renderEventDropdownPart(TYPES.CONSOLE, 'Errors')}
|
||||
{this.renderEventDropdownPart(TYPES.CUSTOM, 'Custom Events')}
|
||||
{this.renderEventDropdownPart(KEYS.USER_COUNTRY, 'Country', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.USER_BROWSER, 'Browser', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.USER_DEVICE, 'Device', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(TYPES.LOCATION, 'Page')}
|
||||
{this.renderEventDropdownPart(TYPES.CLICK, 'Click')}
|
||||
{this.renderEventDropdownPart(TYPES.FETCH, 'Fetch')}
|
||||
{this.renderEventDropdownPart(TYPES.INPUT, 'Input')}
|
||||
|
||||
{this.renderEventDropdownPart(KEYS.USER_OS, 'Operating System', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.REFERRER, 'Referrer', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(TYPES.GRAPHQL, 'GraphQL')}
|
||||
{this.renderEventDropdownPart(TYPES.STATEACTION, 'Store Action')}
|
||||
{this.renderEventDropdownPart(TYPES.REVID, 'Rev ID')}
|
||||
{activeTab !== 'live' && (
|
||||
<>
|
||||
{this.renderEventDropdownPart(TYPES.METADATA, 'Metadata')}
|
||||
{this.renderEventDropdownPart(TYPES.CONSOLE, 'Errors')}
|
||||
{this.renderEventDropdownPart(TYPES.CUSTOM, 'Custom Events')}
|
||||
{this.renderEventDropdownPart(KEYS.USER_COUNTRY, 'Country', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.USER_BROWSER, 'Browser', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.USER_DEVICE, 'Device', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(TYPES.LOCATION, 'Page')}
|
||||
{this.renderEventDropdownPart(TYPES.CLICK, 'Click')}
|
||||
{this.renderEventDropdownPart(TYPES.FETCH, 'Fetch')}
|
||||
{this.renderEventDropdownPart(TYPES.INPUT, 'Input')}
|
||||
|
||||
{this.renderEventDropdownPart(KEYS.USER_OS, 'Operating System', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(KEYS.REFERRER, 'Referrer', _appliedFilterKeys)}
|
||||
{this.renderEventDropdownPart(TYPES.GRAPHQL, 'GraphQL')}
|
||||
{this.renderEventDropdownPart(TYPES.STATEACTION, 'Store Action')}
|
||||
{this.renderEventDropdownPart(TYPES.REVID, 'Rev ID')}
|
||||
</>
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -201,7 +210,7 @@ export default class FilterModal extends React.PureComponent {
|
|||
<div className={ stl.list }>
|
||||
{ this.renderList(category.type, category.keys) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -88,4 +88,9 @@ h5.title {
|
|||
& .filterGroup {
|
||||
width: 205px;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchLiveList } from 'Duck/sessions';
|
||||
import { connect } from 'react-redux';
|
||||
import { NoContent, Loader } from 'UI';
|
||||
import { List, Map } from 'immutable';
|
||||
import SessionItem from 'Shared/SessionItem';
|
||||
|
||||
interface Props {
|
||||
loading: Boolean,
|
||||
list?: List<any>,
|
||||
fetchLiveList: () => void,
|
||||
filters: List<any>
|
||||
}
|
||||
|
||||
function LiveSessionList(props: Props) {
|
||||
const { loading, list, filters } = props;
|
||||
const [userId, setUserId] = useState(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
props.fetchLiveList();
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (filters) {
|
||||
const userIdFilter = filters.filter(i => i.key === 'USERID').first()
|
||||
if (userIdFilter)
|
||||
setUserId(userIdFilter.value[0])
|
||||
else
|
||||
setUserId(undefined)
|
||||
}
|
||||
}, [filters])
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NoContent
|
||||
title={"No live sessions!"}
|
||||
subtext="Please try changing your search parameters."
|
||||
icon="exclamation-circle"
|
||||
show={ !loading && list && list.size === 0}
|
||||
>
|
||||
<Loader loading={ loading }>
|
||||
{list && (userId ? list.filter(i => i.userId === userId) : list).map(session => (
|
||||
<SessionItem
|
||||
key={ session.sessionId }
|
||||
session={ session }
|
||||
live
|
||||
// hasUserFilter={hasUserFilter}
|
||||
/>
|
||||
))}
|
||||
</Loader>
|
||||
</NoContent>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
list: state.getIn(['sessions', 'liveSessions']),
|
||||
loading: state.getIn([ 'sessions', 'loading' ]),
|
||||
filters: state.getIn([ 'filters', 'appliedFilter', 'filters' ]),
|
||||
}), { fetchLiveList })(LiveSessionList)
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './LiveSessionList'
|
||||
|
|
@ -123,7 +123,7 @@ export default class SessionList extends React.PureComponent {
|
|||
const { activeTab, allList, total } = this.props;
|
||||
var filteredList;
|
||||
|
||||
if (activeTab.type !== ALL && activeTab.type !== 'bookmark') { // Watchdog sessions
|
||||
if (activeTab.type !== ALL && activeTab.type !== 'bookmark' && activeTab.type !== 'live') { // Watchdog sessions
|
||||
filteredList = allList.filter(session => activeTab.fits(session))
|
||||
} else {
|
||||
filteredList = allList
|
||||
|
|
|
|||
|
|
@ -72,6 +72,16 @@ function SessionsMenu(props) {
|
|||
/>
|
||||
))}
|
||||
|
||||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
title="Assist"
|
||||
iconName="person"
|
||||
active={activeTab.type === 'live'}
|
||||
onClick={() => onMenuItemClick({ name: 'Assist', type: 'live' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={stl.divider} />
|
||||
<div className="my-3">
|
||||
<SideMenuitem
|
||||
|
|
@ -80,7 +90,8 @@ function SessionsMenu(props) {
|
|||
active={activeTab.type === 'bookmark'}
|
||||
onClick={() => onMenuItemClick({ name: 'Bookmarks', type: 'bookmark' })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn(stl.divider, 'mb-4')} />
|
||||
<SavedSearchList />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Input, Button, Label } from 'UI';
|
||||
import { save, edit, update , fetchList } from 'Duck/site';
|
||||
import { pushNewSite } from 'Duck/user';
|
||||
import { pushNewSite, setSiteId } from 'Duck/user';
|
||||
import styles from './siteForm.css';
|
||||
|
||||
@connect(state => ({
|
||||
|
|
@ -14,7 +14,8 @@ import styles from './siteForm.css';
|
|||
edit,
|
||||
update,
|
||||
pushNewSite,
|
||||
fetchList
|
||||
fetchList,
|
||||
setSiteId
|
||||
})
|
||||
export default class NewSiteForm extends React.PureComponent {
|
||||
state = {
|
||||
|
|
@ -34,8 +35,12 @@ export default class NewSiteForm extends React.PureComponent {
|
|||
})
|
||||
} else {
|
||||
this.props.save(this.props.site).then(() => {
|
||||
const { sites } = this.props;
|
||||
this.props.onClose(null, sites.last())
|
||||
const { sites } = this.props;
|
||||
const site = sites.last();
|
||||
|
||||
this.props.pushNewSite(site)
|
||||
this.props.setSiteId(site.id)
|
||||
this.props.onClose(null, site)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,11 +23,7 @@ export default class SiteDropdown extends React.PureComponent {
|
|||
state = { showProductModal: false }
|
||||
|
||||
closeModal = (e, newSite) => {
|
||||
this.setState({ showProductModal: false })
|
||||
if (newSite) {
|
||||
this.props.pushNewSite(newSite)
|
||||
this.props.setSiteId(newSite.id)
|
||||
}
|
||||
this.setState({ showProductModal: false })
|
||||
};
|
||||
|
||||
newSite = () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
.wrapper {
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
padding: 5px;
|
||||
box-shadow: -1px 1px 1px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import React from 'react'
|
||||
import { Icon, Popup } from 'UI'
|
||||
import { connectPlayer, toggleEvents } from 'Player';
|
||||
import cn from 'classnames'
|
||||
import stl from './EventsToggleButton.css'
|
||||
|
||||
function EventsToggleButton({ showEvents, toggleEvents }) {
|
||||
return (
|
||||
<Popup
|
||||
trigger={
|
||||
<button
|
||||
className={cn("absolute right-0 z-50", stl.wrapper)}
|
||||
onClick={toggleEvents}
|
||||
>
|
||||
<Icon
|
||||
name={ showEvents ? 'chevron-double-right' : 'chevron-double-left' }
|
||||
size="12"
|
||||
/>
|
||||
</button>
|
||||
}
|
||||
content={ showEvents ? 'Hide Events' : 'Show Events' }
|
||||
size="tiny"
|
||||
inverted
|
||||
position="bottom right"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default connectPlayer(state => ({
|
||||
showEvents: !state.showEvents
|
||||
}), { toggleEvents })(EventsToggleButton)
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from './EventsToggleButton'
|
||||
67
frontend/app/components/Session/LivePlayer.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader } from 'UI';
|
||||
import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player';
|
||||
import {
|
||||
PlayerProvider,
|
||||
connectPlayer,
|
||||
init as initPlayer,
|
||||
clean as cleanPlayer,
|
||||
} from 'Player';
|
||||
import { Controls as PlayerControls } from 'Player';
|
||||
import Assist from 'Components/Assist'
|
||||
|
||||
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import EventsBlock from '../Session_/EventsBlock';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.css';
|
||||
|
||||
|
||||
|
||||
const EventsBlockConnected = connectPlayer(state => ({
|
||||
currentTimeEventIndex: state.eventListNow.length > 0 ? state.eventListNow.length - 1 : 0,
|
||||
playing: state.playing,
|
||||
}))(EventsBlock)
|
||||
|
||||
|
||||
const InitLoader = connectPlayer(state => ({
|
||||
loading: !state.initialized
|
||||
}))(Loader);
|
||||
|
||||
|
||||
function WebPlayer ({ showAssist, session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt }) {
|
||||
useEffect(() => {
|
||||
initPlayer(session, jwt);
|
||||
return () => cleanPlayer()
|
||||
}, [ session.sessionId ]);
|
||||
|
||||
// LAYOUT (TODO: local layout state - useContext or something..)
|
||||
useEffect(() => () => {
|
||||
toggleFullscreen(false);
|
||||
closeBottomBlock();
|
||||
}, [])
|
||||
return (
|
||||
<PlayerProvider>
|
||||
<InitLoader className="flex-1 p-3">
|
||||
{ showAssist && <Assist session={session} /> }
|
||||
<PlayerBlockHeader fullscreen={fullscreen}/>
|
||||
<div className={ styles.session } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
</div>
|
||||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default connect(state => ({
|
||||
session: state.getIn([ 'sessions', 'current' ]),
|
||||
showAssist: state.getIn([ 'sessions', 'showChatWindow' ]),
|
||||
jwt: state.get('jwt'),
|
||||
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
|
||||
}), {
|
||||
toggleFullscreen,
|
||||
closeBottomBlock,
|
||||
})(WebPlayer)
|
||||
|
||||
|
|
@ -6,6 +6,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
|
|||
import { Link, NoContent, Loader } from 'UI';
|
||||
import { sessions as sessionsRoute } from 'App/routes';
|
||||
|
||||
import LivePlayer from './LivePlayer';
|
||||
import WebPlayer from './WebPlayer';
|
||||
import IOSPlayer from './IOSPlayer';
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ function Session({
|
|||
<Loader className="flex-1" loading={ loading || sessionId !== session.sessionId }>
|
||||
{ session.isIOS
|
||||
? <IOSPlayer session={session} />
|
||||
: <WebPlayer />
|
||||
: (session.live ? <LivePlayer /> : <WebPlayer />)
|
||||
}
|
||||
</Loader>
|
||||
</NoContent>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ import {
|
|||
init as initPlayer,
|
||||
clean as cleanPlayer,
|
||||
} from 'Player';
|
||||
import { Controls as PlayerControls } from 'Player';
|
||||
import { Controls as PlayerControls, toggleEvents } from 'Player';
|
||||
import cn from 'classnames'
|
||||
|
||||
|
||||
import PlayerBlockHeader from '../Session_/PlayerBlockHeader';
|
||||
import EventsBlock from '../Session_/EventsBlock';
|
||||
import PlayerBlock from '../Session_/PlayerBlock';
|
||||
import styles from '../Session_/session.css';
|
||||
import EventsToggleButton from './EventsToggleButton';
|
||||
|
||||
|
||||
|
||||
|
|
@ -28,6 +30,19 @@ const InitLoader = connectPlayer(state => ({
|
|||
loading: !state.initialized
|
||||
}))(Loader);
|
||||
|
||||
const PlayerContentConnected = connectPlayer(state => ({
|
||||
showEvents: !state.showEvents
|
||||
}), { toggleEvents })(PlayerContent);
|
||||
|
||||
|
||||
function PlayerContent({ live, fullscreen, showEvents, toggleEvents }) {
|
||||
return (
|
||||
<div className={ cn(styles.session, 'relative') } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
{ showEvents && !live && !fullscreen && <EventsBlockConnected player={PlayerControls}/> }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt }) {
|
||||
useEffect(() => {
|
||||
|
|
@ -44,10 +59,7 @@ function WebPlayer ({ session, toggleFullscreen, closeBottomBlock, live, fullscr
|
|||
<PlayerProvider>
|
||||
<InitLoader className="flex-1">
|
||||
<PlayerBlockHeader fullscreen={fullscreen}/>
|
||||
<div className={ styles.session } data-fullscreen={fullscreen}>
|
||||
<PlayerBlock />
|
||||
{ !live && !fullscreen && <EventsBlockConnected player={PlayerControls}/> }
|
||||
</div>
|
||||
<PlayerContentConnected fullscreen={fullscreen} live={live} />
|
||||
</InitLoader>
|
||||
</PlayerProvider>
|
||||
);
|
||||
|
|
@ -61,5 +73,5 @@ export default connect(state => ({
|
|||
}), {
|
||||
toggleFullscreen,
|
||||
closeBottomBlock,
|
||||
})(WebPlayer)
|
||||
})(WebPlayer)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
selectStorageType,
|
||||
selectStorageListNow,
|
||||
} from 'Player/store';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
|
||||
import { Popup, Icon } from 'UI';
|
||||
import { toggleInspectorMode } from 'Player';
|
||||
|
|
@ -96,8 +97,7 @@ function getStorageName(type) {
|
|||
showExceptions: state.exceptionsList.length > 0,
|
||||
showLongtasks: state.longtasksList.length > 0,
|
||||
}))
|
||||
@connect((state, props) => ({
|
||||
showDevTools: state.getIn([ 'user', 'account', 'appearance', 'sessionsDevtools' ]),
|
||||
@connect((state, props) => ({
|
||||
fullscreen: state.getIn([ 'components', 'player', 'fullscreen' ]),
|
||||
bottomBlock: state.getIn([ 'components', 'player', 'bottomBlock' ]),
|
||||
showStorage: props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']),
|
||||
|
|
@ -118,7 +118,7 @@ export default class Controls extends React.Component {
|
|||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.showDevTools !== this.props.showDevTools ||
|
||||
if (
|
||||
nextProps.fullscreen !== this.props.fullscreen ||
|
||||
nextProps.bottomBlock !== this.props.bottomBlock ||
|
||||
nextProps.endTime !== this.props.endTime ||
|
||||
|
|
@ -218,8 +218,7 @@ export default class Controls extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
showDevTools,
|
||||
const {
|
||||
bottomBlock,
|
||||
toggleBottomBlock,
|
||||
live,
|
||||
|
|
@ -247,16 +246,15 @@ export default class Controls extends React.Component {
|
|||
showLongtasks,
|
||||
exceptionsCount,
|
||||
showExceptions,
|
||||
fullscreen,
|
||||
skipToIssue,
|
||||
inspectorMode
|
||||
fullscreen,
|
||||
skipToIssue
|
||||
} = this.props;
|
||||
|
||||
// const inspectorMode = bottomBlock === INSPECTOR;
|
||||
const inspectorMode = bottomBlock === INSPECTOR;
|
||||
|
||||
return (
|
||||
<div className={ styles.controls }>
|
||||
<Timeline jump={ this.props.jump } />
|
||||
<div className={ cn(styles.controls, {'px-5 pt-0' : live}) }>
|
||||
{ !live && <Timeline jump={ this.props.jump } /> }
|
||||
{ !fullscreen &&
|
||||
<div className={ styles.buttons } data-is-live={ live }>
|
||||
{ !live ?
|
||||
|
|
@ -278,10 +276,7 @@ export default class Controls extends React.Component {
|
|||
</div>
|
||||
:
|
||||
<div className={ styles.buttonsLeft }>
|
||||
<button onClick={ this.goLive } className={ styles.liveTag } data-is-live={ livePlay }>
|
||||
<Icon name="circle" size="8" marginRight="5" color="white" />
|
||||
<div>{'Live'}</div>
|
||||
</button>
|
||||
<LiveTag isLive={livePlay} />
|
||||
{'Elapsed'}
|
||||
<ReduxTime name="time" />
|
||||
</div>
|
||||
|
|
@ -308,7 +303,7 @@ export default class Controls extends React.Component {
|
|||
</React.Fragment>
|
||||
}
|
||||
<div className={ styles.divider } />
|
||||
{ !live && showDevTools &&
|
||||
{ !live &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(NETWORK) }
|
||||
|
|
@ -330,7 +325,7 @@ export default class Controls extends React.Component {
|
|||
icon="fetch"
|
||||
/>
|
||||
}
|
||||
{ showGraphql &&
|
||||
{ !live && showGraphql &&
|
||||
<ControlButton
|
||||
disabled={disabled}
|
||||
onClick={ ()=> toggleBottomBlock(GRAPHQL) }
|
||||
|
|
@ -340,7 +335,7 @@ export default class Controls extends React.Component {
|
|||
icon="vendors/graphql"
|
||||
/>
|
||||
}
|
||||
{ showStorage && showDevTools &&
|
||||
{ !live && showStorage &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(STORAGE) }
|
||||
|
|
@ -350,7 +345,7 @@ export default class Controls extends React.Component {
|
|||
icon={ getStorageIconName(storageType) }
|
||||
/>
|
||||
}
|
||||
{ showDevTools &&
|
||||
{
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(CONSOLE) }
|
||||
|
|
@ -361,7 +356,7 @@ export default class Controls extends React.Component {
|
|||
hasErrors={ logRedCount > 0 }
|
||||
/>
|
||||
}
|
||||
{ showExceptions && showDevTools &&
|
||||
{ showExceptions &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(EXCEPTIONS) }
|
||||
|
|
@ -372,7 +367,7 @@ export default class Controls extends React.Component {
|
|||
hasErrors={ exceptionsCount > 0 }
|
||||
/>
|
||||
}
|
||||
{ !live && showDevTools && showStack &&
|
||||
{ showStack &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(STACKEVENTS) }
|
||||
|
|
@ -383,7 +378,7 @@ export default class Controls extends React.Component {
|
|||
hasErrors={ stackRedCount > 0 }
|
||||
/>
|
||||
}
|
||||
{ showProfiler && showDevTools &&
|
||||
{ !live && showProfiler &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(PROFILER) }
|
||||
|
|
@ -392,15 +387,18 @@ export default class Controls extends React.Component {
|
|||
label="Profiler"
|
||||
icon="code"
|
||||
/>
|
||||
}
|
||||
{
|
||||
!live &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(PERFORMANCE) }
|
||||
active={ bottomBlock === PERFORMANCE }
|
||||
label="Performance"
|
||||
icon="tachometer-slow"
|
||||
/>
|
||||
}
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(PERFORMANCE) }
|
||||
active={ bottomBlock === PERFORMANCE }
|
||||
label="Performance"
|
||||
icon="tachometer-slow"
|
||||
/>
|
||||
{ showLongtasks &&
|
||||
{ !live && showLongtasks &&
|
||||
<ControlButton
|
||||
disabled={ disabled }
|
||||
onClick={ () => toggleBottomBlock(LONGTASKS) }
|
||||
|
|
@ -421,13 +419,15 @@ export default class Controls extends React.Component {
|
|||
</React.Fragment>
|
||||
}
|
||||
|
||||
<ControlButton
|
||||
// disabled={ disabled && !inspectorMode }
|
||||
active={ bottomBlock === INSPECTOR }
|
||||
onClick={ toggleInspectorMode }
|
||||
icon={ inspectorMode ? 'close' : 'inspect' }
|
||||
label="Inspect"
|
||||
/>
|
||||
{!live && (
|
||||
<ControlButton
|
||||
disabled={ disabled && !inspectorMode }
|
||||
active={ bottomBlock === INSPECTOR }
|
||||
onClick={ () => toggleBottomBlock(INSPECTOR) }
|
||||
icon={ inspectorMode ? 'close' : 'inspect' }
|
||||
label="Inspect"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,7 @@ const getPointerIcon = (type) => {
|
|||
fetchList: state.fetchList,
|
||||
}))
|
||||
@connect(state => ({
|
||||
issues: state.getIn([ 'sessions', 'current', 'issues' ]),
|
||||
showDevTools: state.getIn([ 'user', 'account', 'appearance', 'sessionsDevtools' ]),
|
||||
issues: state.getIn([ 'sessions', 'current', 'issues' ]),
|
||||
clickRageTime: state.getIn([ 'sessions', 'current', 'clickRage' ]) &&
|
||||
state.getIn([ 'sessions', 'current', 'clickRageTime' ]),
|
||||
returningLocationTime: state.getIn([ 'sessions', 'current', 'returningLocation' ]) &&
|
||||
|
|
@ -102,8 +101,7 @@ export default class Timeline extends React.PureComponent {
|
|||
live,
|
||||
logList,
|
||||
exceptionsList,
|
||||
resourceList,
|
||||
showDevTools,
|
||||
resourceList,
|
||||
clickRageTime,
|
||||
stackList,
|
||||
fetchList,
|
||||
|
|
@ -255,7 +253,7 @@ export default class Timeline extends React.PureComponent {
|
|||
}
|
||||
/>
|
||||
*/ }
|
||||
{ showDevTools && exceptionsList
|
||||
{ exceptionsList
|
||||
.map(e => (
|
||||
<div
|
||||
key={ e.key }
|
||||
|
|
@ -297,7 +295,7 @@ export default class Timeline extends React.PureComponent {
|
|||
// />
|
||||
))
|
||||
}
|
||||
{ showDevTools && logList
|
||||
{ logList
|
||||
.map(l => l.isRed() && (
|
||||
<div
|
||||
key={ l.key }
|
||||
|
|
@ -352,7 +350,7 @@ export default class Timeline extends React.PureComponent {
|
|||
// />
|
||||
))
|
||||
}
|
||||
{ showDevTools && resourceList
|
||||
{ resourceList
|
||||
.filter(r => r.isRed() || r.isYellow())
|
||||
.map(r => (
|
||||
<div
|
||||
|
|
@ -402,7 +400,7 @@ export default class Timeline extends React.PureComponent {
|
|||
// />
|
||||
))
|
||||
}
|
||||
{ showDevTools && fetchList
|
||||
{ fetchList
|
||||
.filter(e => e.isRed())
|
||||
.map(e => (
|
||||
<div
|
||||
|
|
@ -443,7 +441,7 @@ export default class Timeline extends React.PureComponent {
|
|||
// />
|
||||
))
|
||||
}
|
||||
{ showDevTools && stackList
|
||||
{ stackList
|
||||
.filter(e => e.isRed())
|
||||
.map(e => (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { attach as attachPlayer, Controls as PlayerControls, connectPlayer } fro
|
|||
import Controls from './Controls';
|
||||
import stl from './player.css';
|
||||
import AutoplayTimer from '../AutoplayTimer';
|
||||
import EventsToggleButton from '../../Session/EventsToggleButton';
|
||||
|
||||
|
||||
const ScreenWrapper = withOverlay()(React.memo(() => <div className={ stl.screenWrapper } />));
|
||||
|
|
@ -18,9 +19,10 @@ const ScreenWrapper = withOverlay()(React.memo(() => <div className={ stl.screen
|
|||
loading: state.messagesLoading,
|
||||
disconnected: state.disconnected,
|
||||
disabled: state.cssLoading || state.messagesLoading || state.inspectorMode,
|
||||
inspectorMode: state.inspectorMode,
|
||||
removeOverlay: !state.messagesLoading && state.inspectorMode || state.live,
|
||||
completed: state.completed,
|
||||
autoplay: state.autoplay
|
||||
autoplay: state.autoplay,
|
||||
live: state.live
|
||||
}))
|
||||
@connect(state => ({
|
||||
//session: state.getIn([ 'sessions', 'current' ]),
|
||||
|
|
@ -96,7 +98,7 @@ export default class Player extends React.PureComponent {
|
|||
className,
|
||||
playing,
|
||||
disabled,
|
||||
inspectorMode,
|
||||
removeOverlay,
|
||||
bottomBlockIsActive,
|
||||
loading,
|
||||
disconnected,
|
||||
|
|
@ -105,6 +107,7 @@ export default class Player extends React.PureComponent {
|
|||
completed,
|
||||
autoplay,
|
||||
nextId,
|
||||
live,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
|
@ -124,8 +127,9 @@ export default class Player extends React.PureComponent {
|
|||
// label="Esc"
|
||||
// />
|
||||
}
|
||||
<div className={ stl.playerView }>
|
||||
{ !inspectorMode &&
|
||||
{!live && !fullscreen && <EventsToggleButton /> }
|
||||
<div className="relative flex-1">
|
||||
{ !removeOverlay &&
|
||||
<div
|
||||
className={ stl.overlay }
|
||||
onClick={ disabled ? null : this.togglePlay }
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { fetchList as fetchListIntegration } from 'Duck/integrations/actions';
|
|||
import cls from './playerBlockHeader.css';
|
||||
import Issues from './Issues/Issues';
|
||||
import Autoplay from './Autoplay';
|
||||
import AssistActions from '../Assist/components/AssistActions';
|
||||
|
||||
const SESSIONS_ROUTE = sessionsRoute();
|
||||
|
||||
|
|
@ -89,7 +90,7 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
live,
|
||||
disabled,
|
||||
jiraConfig,
|
||||
fullscreen
|
||||
fullscreen,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
|
@ -112,31 +113,36 @@ export default class PlayerBlockHeader extends React.PureComponent {
|
|||
<HeaderInfo icon={ osIcon(userOs) } label={ userOs } />
|
||||
|
||||
<div className='ml-auto flex items-center'>
|
||||
<Autoplay />
|
||||
<div className={ cls.divider } />
|
||||
<IconButton
|
||||
className="mr-2"
|
||||
tooltip="Bookmark"
|
||||
onClick={ this.toggleFavorite }
|
||||
loading={ loading }
|
||||
icon={ favorite ? 'star-solid' : 'star' }
|
||||
// label={ favorite ? 'Favourited' : 'Favourite' }
|
||||
plain
|
||||
/>
|
||||
<SharePopup
|
||||
entity="sessions"
|
||||
id={ sessionId }
|
||||
trigger={
|
||||
{ live && <AssistActions isLive userId={userId} /> }
|
||||
{ !live && (
|
||||
<>
|
||||
<Autoplay />
|
||||
<div className={ cls.divider } />
|
||||
<IconButton
|
||||
className="mr-2"
|
||||
tooltip="Share Session"
|
||||
disabled={ disabled }
|
||||
icon={ 'share-alt' }
|
||||
//label="Share"
|
||||
tooltip="Bookmark"
|
||||
onClick={ this.toggleFavorite }
|
||||
loading={ loading }
|
||||
icon={ favorite ? 'star-solid' : 'star' }
|
||||
// label={ favorite ? 'Favourited' : 'Favourite' }
|
||||
plain
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SharePopup
|
||||
entity="sessions"
|
||||
id={ sessionId }
|
||||
trigger={
|
||||
<IconButton
|
||||
className="mr-2"
|
||||
tooltip="Share Session"
|
||||
disabled={ disabled }
|
||||
icon={ 'share-alt' }
|
||||
//label="Share"
|
||||
plain
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{ !live && jiraConfig && jiraConfig.token && <Issues sessionId={ sessionId } /> }
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
33
frontend/app/components/shared/LiveTag/LiveTag.css
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
@keyframes fade {
|
||||
0% { opacity: 1}
|
||||
50% { opacity: 0}
|
||||
100% { opacity: 1}
|
||||
}
|
||||
|
||||
.liveTag {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
height: 26px;
|
||||
width: 56px;
|
||||
border-radius: 3px;
|
||||
background-color: $gray-light;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $gray-dark;
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
letter-spacing: 1px;
|
||||
margin-right: 10px;
|
||||
& svg {
|
||||
fill: $gray-dark;
|
||||
}
|
||||
&[data-is-live=true] {
|
||||
background-color: #42AE5E;
|
||||
color: white;
|
||||
& svg {
|
||||
fill: white;
|
||||
animation: fade 1s infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
frontend/app/components/shared/LiveTag/LiveTag.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import React from 'react'
|
||||
import { Icon } from 'UI'
|
||||
import stl from './LiveTag.css'
|
||||
|
||||
interface Props {
|
||||
onClick: () => void,
|
||||
isLive: Boolean
|
||||
}
|
||||
|
||||
function LiveTag({ isLive, onClick }: Props) {
|
||||
return (
|
||||
<button onClick={ onClick } className={ stl.liveTag } data-is-live={ isLive }>
|
||||
<Icon name="circle" size="8" marginRight="5" color="white" />
|
||||
<div>{'Live'}</div>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default LiveTag
|
||||
1
frontend/app/components/shared/LiveTag/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default } from './LiveTag'
|
||||
29
frontend/app/components/shared/SessionItem/Counter.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React, { useState, useEffect } from 'react'
|
||||
import { Duration } from 'luxon';
|
||||
|
||||
interface Props {
|
||||
startTime: any,
|
||||
className: string
|
||||
}
|
||||
|
||||
function Counter({ startTime, className }: Props) {
|
||||
let intervalId;
|
||||
const [duration, setDuration] = useState(new Date().getTime() - startTime)
|
||||
|
||||
useEffect(() => {
|
||||
if (!intervalId) {
|
||||
intervalId = setInterval(() => {
|
||||
setDuration(duration + 1000)
|
||||
}, 1000)
|
||||
}
|
||||
return () => clearInterval(intervalId)
|
||||
}, [duration])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{startTime && Duration.fromMillis(duration).toFormat('m:ss')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Counter
|
||||
|
|
@ -14,6 +14,9 @@ import { toggleFavorite } from 'Duck/sessions';
|
|||
import { session as sessionRoute } from 'App/routes';
|
||||
import { durationFormatted, formatTimeOrDate } from 'App/date';
|
||||
import stl from './sessionItem.css';
|
||||
import LiveTag from 'Shared/LiveTag';
|
||||
import { session } from '../../../routes';
|
||||
import Counter from './Counter'
|
||||
|
||||
const Label = ({ label = '', color = 'color-gray-medium'}) => (
|
||||
<div className={ cn('font-light text-sm', color)}>{label}</div>
|
||||
|
|
@ -51,7 +54,8 @@ export default class SessionItem extends React.PureComponent {
|
|||
favorite,
|
||||
userDeviceType,
|
||||
userUuid,
|
||||
userNumericHash,
|
||||
userNumericHash,
|
||||
live
|
||||
},
|
||||
timezone,
|
||||
onUserClick,
|
||||
|
|
@ -85,22 +89,30 @@ export default class SessionItem extends React.PureComponent {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center px-4" style={{ width: '150px'}}>
|
||||
<div className="text-xl">{ formattedDuration }</div>
|
||||
<div className="text-xl">
|
||||
{ live ? <Counter startTime={startedAt} /> : formattedDuration }
|
||||
</div>
|
||||
<Label label="Duration" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ stl.count }>{ eventsCount }</div>
|
||||
<Label label={ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' } />
|
||||
</div>
|
||||
{!live && (
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ stl.count }>{ eventsCount }</div>
|
||||
<Label label={ eventsCount === 0 || eventsCount > 1 ? 'Events' : 'Event' } />
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ cn(stl.count, { "color-gray-medium": errorsCount === 0 }) } >{ errorsCount }</div>
|
||||
<Label label="Errors" color={errorsCount > 0 ? '' : 'color-gray-medium'} />
|
||||
</div>
|
||||
{!live && (
|
||||
<div className="flex flex-col items-center px-4">
|
||||
<div className={ cn(stl.count, { "color-gray-medium": errorsCount === 0 }) } >{ errorsCount }</div>
|
||||
<Label label="Errors" color={errorsCount > 0 ? '' : 'color-gray-medium'} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ live && <LiveTag isLive={true} /> }
|
||||
|
||||
<div className={ cn(stl.iconDetails, 'px-4') }>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const INIT = 'sessions/INIT';
|
|||
const FETCH_LIST = new RequestTypes('sessions/FETCH_LIST');
|
||||
const FETCH = new RequestTypes('sessions/FETCH');
|
||||
const FETCH_FAVORITE_LIST = new RequestTypes('sessions/FETCH_FAVORITE_LIST');
|
||||
const FETCH_LIVE_LIST = new RequestTypes('sessions/FETCH_LIVE_LIST');
|
||||
const TOGGLE_FAVORITE = new RequestTypes('sessions/TOGGLE_FAVORITE');
|
||||
const FETCH_ERROR_STACK = new RequestTypes('sessions/FETCH_ERROR_STACK');
|
||||
const SORT = 'sessions/SORT';
|
||||
|
|
@ -19,6 +20,7 @@ const REDEFINE_TARGET = 'sessions/REDEFINE_TARGET';
|
|||
const SET_TIMEZONE = 'sessions/SET_TIMEZONE';
|
||||
const SET_EVENT_QUERY = 'sessions/SET_EVENT_QUERY';
|
||||
const SET_AUTOPLAY_VALUES = 'sessions/SET_AUTOPLAY_VALUES';
|
||||
const TOGGLE_CHAT_WINDOW = 'sessions/TOGGLE_CHAT_WINDOW';
|
||||
|
||||
const SET_ACTIVE_TAB = 'sessions/SET_ACTIVE_TAB';
|
||||
|
||||
|
|
@ -35,7 +37,9 @@ const initialState = Map({
|
|||
errorStack: List(),
|
||||
eventsIndex: [],
|
||||
sourcemapUploaded: true,
|
||||
filteredEvents: null
|
||||
filteredEvents: null,
|
||||
showChatWindow: false,
|
||||
liveSessions: List()
|
||||
});
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
|
|
@ -49,6 +53,11 @@ const reducer = (state = initialState, action = {}) => {
|
|||
: state;
|
||||
case FETCH_ERROR_STACK.SUCCESS:
|
||||
return state.set('errorStack', List(action.data.trace).map(ErrorStack)).set('sourcemapUploaded', action.data.sourcemapUploaded)
|
||||
case FETCH_LIVE_LIST.SUCCESS:
|
||||
// const { sessions, total } = action.data;
|
||||
const liveList = List(action.data).map(s => new Session({...s, live: true}));
|
||||
return state
|
||||
.set('liveSessions', liveList)
|
||||
case FETCH_LIST.SUCCESS:
|
||||
const { sessions, total } = action.data;
|
||||
const list = List(sessions).map(Session);
|
||||
|
|
@ -98,8 +107,7 @@ const reducer = (state = initialState, action = {}) => {
|
|||
.set('sessionIds', list.map(({ sessionId }) => sessionId ).toJS())
|
||||
.set('total', total)
|
||||
.set('keyMap', keyMap)
|
||||
.set('wdTypeCount', wdTypeCount);
|
||||
|
||||
.set('wdTypeCount', wdTypeCount);
|
||||
case SET_AUTOPLAY_VALUES: {
|
||||
const sessionIds = state.get('sessionIds')
|
||||
const currentSessionId = state.get('current').sessionId
|
||||
|
|
@ -194,6 +202,9 @@ const reducer = (state = initialState, action = {}) => {
|
|||
.set('sessionIds', allList.map(({ sessionId }) => sessionId ).toJS())
|
||||
case SET_TIMEZONE:
|
||||
return state.set('timezone', action.timezone)
|
||||
case TOGGLE_CHAT_WINDOW:
|
||||
console.log(action)
|
||||
return state.set('showChatWindow', action.state)
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
@ -252,6 +263,20 @@ export function fetchFavoriteList() {
|
|||
};
|
||||
}
|
||||
|
||||
export function fetchLiveList() {
|
||||
return {
|
||||
types: FETCH_LIVE_LIST.toArray(),
|
||||
call: client => client.get('/assist/sessions'),
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleChatWindow(state) {
|
||||
return {
|
||||
type: TOGGLE_CHAT_WINDOW,
|
||||
state
|
||||
};
|
||||
}
|
||||
|
||||
export function sort(sortKey, sign = 1, listName = 'list') {
|
||||
return {
|
||||
type: SORT,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
//@flow
|
||||
import { Decoder } from "syncod";
|
||||
import logger from 'App/logger';
|
||||
|
||||
|
|
@ -24,43 +23,68 @@ import MouseManager from './managers/MouseManager';
|
|||
import PerformanceTrackManager from './managers/PerformanceTrackManager';
|
||||
import WindowNodeCounter from './managers/WindowNodeCounter';
|
||||
import ActivityManager from './managers/ActivityManager';
|
||||
import AssistManager from './managers/AssistManager';
|
||||
|
||||
import MessageGenerator from './MessageGenerator';
|
||||
import MessageReader from './MessageReader';
|
||||
|
||||
import { INITIAL_STATE as PARENT_INITIAL_STATE } from './StatedScreen';
|
||||
import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './StatedScreen';
|
||||
import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager';
|
||||
|
||||
import type { TimedMessage } from './Timed';
|
||||
import type { PerformanceChartPoint } from './managers/PerformanceTrackManager';
|
||||
import type { SkipInterval } from './managers/ActivityManager';
|
||||
|
||||
const LIST_NAMES = [ "redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks" ]
|
||||
const LIST_NAMES = [ "redux", "mobx", "vuex", "ngrx", "graphql", "exceptions", "profiles", "longtasks" ] as const;
|
||||
const LISTS_INITIAL_STATE = {};
|
||||
LIST_NAMES.forEach(name => {
|
||||
LISTS_INITIAL_STATE[`${name}ListNow`] = [];
|
||||
LISTS_INITIAL_STATE[`${name}List`] = [];
|
||||
})
|
||||
export const INITIAL_STATE = {
|
||||
...PARENT_INITIAL_STATE,
|
||||
|
||||
export interface State extends SuperState, AssistState {
|
||||
performanceChartData: PerformanceChartPoint[],
|
||||
skipIntervals: SkipInterval[],
|
||||
connType?: string,
|
||||
connBandwidth?: number,
|
||||
location?: string,
|
||||
performanceChartTime?: number,
|
||||
|
||||
domContentLoadedTime?: any,
|
||||
domBuildingTime?: any,
|
||||
loadTime?: any,
|
||||
}
|
||||
export const INITIAL_STATE: State = {
|
||||
...SUPER_INITIAL_STATE,
|
||||
...LISTS_INITIAL_STATE,
|
||||
...ASSIST_INITIAL_STATE,
|
||||
performanceChartData: [],
|
||||
skipIntervals: [],
|
||||
};
|
||||
|
||||
type ListsObject = {
|
||||
[key in typeof LIST_NAMES[number]]: ListWalker<any> //
|
||||
}
|
||||
|
||||
function initLists() {
|
||||
const lists = {};
|
||||
function initLists(): ListsObject {
|
||||
const lists: Partial<ListsObject> = {} ;
|
||||
for (var i = 0; i < LIST_NAMES.length; i++) {
|
||||
lists[ LIST_NAMES[i] ] = new ListWalker();
|
||||
}
|
||||
return lists;
|
||||
return lists as ListsObject;
|
||||
}
|
||||
|
||||
|
||||
import type {
|
||||
Message,
|
||||
SetLocation,
|
||||
SetTitle,
|
||||
SetPageLocation,
|
||||
ConnectionInformation,
|
||||
SetViewportSize,
|
||||
SetViewportScroll,
|
||||
} from './messages';
|
||||
|
||||
interface Timed { //TODO: to common space
|
||||
time: number;
|
||||
}
|
||||
|
||||
type ReduxDecoded = Timed & {
|
||||
action: {},
|
||||
|
|
@ -70,71 +94,71 @@ type ReduxDecoded = Timed & {
|
|||
|
||||
export default class MessageDistributor extends StatedScreen {
|
||||
// TODO: consistent with the other data-lists
|
||||
#locationEventManager: ListWalker<> = new ListWalker();
|
||||
#locationManager: ListWalker<SetLocation> = new ListWalker();
|
||||
#loadedLocationManager: ListWalker<SetLocation> = new ListWalker();
|
||||
#titleManager: ListWalker<SetTitle> = new ListWalker();
|
||||
#connectionInfoManger: ListWalker<ConnectionInformation> = new ListWalker();
|
||||
#performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager();
|
||||
#windowNodeCounter: WindowNodeCounter = new WindowNodeCounter();
|
||||
#clickManager: ListWalker = new ListWalker();
|
||||
private readonly locationEventManager: ListWalker<any>/*<LocationEvent>*/ = new ListWalker();
|
||||
private readonly locationManager: ListWalker<SetPageLocation & Timed> = new ListWalker();
|
||||
private readonly loadedLocationManager: ListWalker<SetPageLocation & Timed> = new ListWalker();
|
||||
private readonly connectionInfoManger: ListWalker<ConnectionInformation & Timed> = new ListWalker();
|
||||
private readonly performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager();
|
||||
private readonly windowNodeCounter: WindowNodeCounter = new WindowNodeCounter();
|
||||
private readonly clickManager: ListWalker<Timed> = new ListWalker();
|
||||
|
||||
#resizeManager: ListWalker<SetViewportSize> = new ListWalker();
|
||||
#pagesManager: PagesManager;
|
||||
#mouseManager: MouseManager;
|
||||
private readonly resizeManager: ListWalker<SetViewportSize & Timed> = new ListWalker([]);
|
||||
private readonly pagesManager: PagesManager;
|
||||
private readonly mouseManager: MouseManager;
|
||||
private readonly assistManager: AssistManager;
|
||||
|
||||
#scrollManager: ListWalker<SetViewportScroll> = new ListWalker();
|
||||
private readonly scrollManager: ListWalker<SetViewportScroll & Timed> = new ListWalker();
|
||||
|
||||
#decoder = new Decoder();
|
||||
#lists = initLists();
|
||||
private readonly decoder = new Decoder();
|
||||
private readonly lists = initLists();
|
||||
|
||||
#activirtManager: ActivityManager;
|
||||
private activirtManager: ActivityManager | null = null;
|
||||
|
||||
#sessionStart: number;
|
||||
#navigationStartOffset: number = 0;
|
||||
#lastMessageTime: number = 0;
|
||||
private readonly sessionStart: number;
|
||||
private navigationStartOffset: number = 0;
|
||||
private lastMessageTime: number = 0;
|
||||
|
||||
constructor(sess: any /*Session*/, jwt: string) {
|
||||
constructor(private readonly session: any /*Session*/, jwt: string) {
|
||||
super();
|
||||
this.#pagesManager = new PagesManager(this, sess.isMobile)
|
||||
this.#mouseManager = new MouseManager(this);
|
||||
this.pagesManager = new PagesManager(this, this.session.isMobile)
|
||||
this.mouseManager = new MouseManager(this);
|
||||
this.assistManager = new AssistManager(session, this);
|
||||
|
||||
this.#activirtManager = new ActivityManager(sess.duration.milliseconds);
|
||||
this.sessionStart = this.session.startedAt;
|
||||
|
||||
this.#sessionStart = sess.startedAt;
|
||||
|
||||
/* == REFACTOR_ME == */
|
||||
const eventList = sess.events.toJSON();
|
||||
initListsDepr({
|
||||
event: eventList,
|
||||
stack: sess.stackEvents.toJSON(),
|
||||
resource: sess.resources.toJSON(),
|
||||
});
|
||||
|
||||
eventList.forEach(e => {
|
||||
if (e.type === EVENT_TYPES.LOCATION) { //TODO type system
|
||||
this.#locationEventManager.add(e);
|
||||
}
|
||||
if (e.type === EVENT_TYPES.CLICK) {
|
||||
this.#clickManager.add(e);
|
||||
}
|
||||
});
|
||||
sess.errors.forEach(e => {
|
||||
this.#lists.exceptions.add(e);
|
||||
});
|
||||
/* === */
|
||||
|
||||
|
||||
if (sess.live) {
|
||||
// const sockUrl = `wss://live.openreplay.com/1/${ sess.siteId }/${ sess.sessionId }/${ jwt }`;
|
||||
// this.#subscribeOnMessages(sockUrl);
|
||||
if (this.session.live) {
|
||||
// const sockUrl = `wss://live.openreplay.com/1/${ this.session.siteId }/${ this.session.sessionId }/${ jwt }`;
|
||||
// this.subscribeOnMessages(sockUrl);
|
||||
initListsDepr({})
|
||||
this.assistManager.connect();
|
||||
} else {
|
||||
this._loadMessages(sess.mobsUrl);
|
||||
this.activirtManager = new ActivityManager(this.session.duration.milliseconds);
|
||||
/* == REFACTOR_ME == */
|
||||
const eventList = this.session.events.toJSON();
|
||||
initListsDepr({
|
||||
event: eventList,
|
||||
stack: this.session.stackEvents.toJSON(),
|
||||
resource: this.session.resources.toJSON(),
|
||||
});
|
||||
|
||||
eventList.forEach(e => {
|
||||
if (e.type === EVENT_TYPES.LOCATION) { //TODO type system
|
||||
this.locationEventManager.add(e);
|
||||
}
|
||||
if (e.type === EVENT_TYPES.CLICK) {
|
||||
this.clickManager.add(e);
|
||||
}
|
||||
});
|
||||
this.session.errors.forEach(e => {
|
||||
this.lists.exceptions.add(e);
|
||||
});
|
||||
/* === */
|
||||
this.loadMessages();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #subscribeOnMessages(sockUrl) {
|
||||
// subscribeOnMessages(sockUrl) {
|
||||
// this.setMessagesLoading(true);
|
||||
// const socket = new WebSocket(sockUrl);
|
||||
// socket.binaryType = 'arraybuffer';
|
||||
|
|
@ -147,36 +171,35 @@ export default class MessageDistributor extends StatedScreen {
|
|||
// const msgs = [];
|
||||
// messageGenerator // parseBuffer(msgs, data);
|
||||
// // TODO: count indexes. Now will not work due to wrong indexes
|
||||
// //msgs.forEach(this.#distributeMessage);
|
||||
// //msgs.forEach(this.distributeMessage);
|
||||
// this.setMessagesLoading(false);
|
||||
// this.setDisconnected(false);
|
||||
// }
|
||||
// this._socket = socket;
|
||||
// }
|
||||
|
||||
_loadMessages(fileUrl): void {
|
||||
private loadMessages(): void {
|
||||
const fileUrl: string = this.session.mobsUrl;
|
||||
this.setMessagesLoading(true);
|
||||
window.fetch(fileUrl)
|
||||
.then(r => r.arrayBuffer())
|
||||
.then(b => {
|
||||
const mGen = new MessageGenerator(new Uint8Array(b), this.#sessionStart);
|
||||
let mCount = 0;
|
||||
const msgs = [];
|
||||
const r = new MessageReader(new Uint8Array(b), this.sessionStart);
|
||||
const msgs: Array<Message> = [];
|
||||
|
||||
while (mGen.hasNext()) {
|
||||
mCount++;
|
||||
const next = mGen.next();
|
||||
while (r.hasNext()) {
|
||||
const next = r.next();
|
||||
if (next != null) {
|
||||
this.#lastMessageTime = next[0].time;
|
||||
this.#distributeMessage(next[0], next[1]);
|
||||
this.lastMessageTime = next[0].time;
|
||||
this.distributeMessage(next[0], next[1]);
|
||||
msgs.push(next[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack for upet (TODO: fix ordering in one mutation (removes first))
|
||||
// @ts-ignore Hack for upet (TODO: fix ordering in one mutation (removes first))
|
||||
const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id);
|
||||
//const createNodeTypes = ["create_text_node", "create_element_node"];
|
||||
this.#pagesManager.sort((m1, m2) =>{
|
||||
this.pagesManager.sort((m1, m2) =>{
|
||||
if (m1.time === m2.time) {
|
||||
if (m1.tp === "remove_node" && m2.tp !== "remove_node") {
|
||||
if (headChildrenIds.includes(m1.id)) {
|
||||
|
|
@ -198,22 +221,22 @@ export default class MessageDistributor extends StatedScreen {
|
|||
}
|
||||
return 0;
|
||||
})
|
||||
//
|
||||
|
||||
|
||||
logger.info("Messages count: ", mCount, msgs);
|
||||
logger.info("Messages count: ", msgs.length, msgs);
|
||||
|
||||
const stateToUpdate = {
|
||||
performanceChartData: this.#performanceTrackManager.chartData,
|
||||
performanceAvaliability: this.#performanceTrackManager.avaliability,
|
||||
const stateToUpdate: {[key:string]: any} = {
|
||||
performanceChartData: this.performanceTrackManager.chartData,
|
||||
performanceAvaliability: this.performanceTrackManager.avaliability,
|
||||
};
|
||||
this.#activirtManager.end();
|
||||
stateToUpdate.skipIntervals = this.#activirtManager.list;
|
||||
this.activirtManager?.end();
|
||||
stateToUpdate.skipIntervals = this.activirtManager?.list || [];
|
||||
LIST_NAMES.forEach(key => {
|
||||
stateToUpdate[ `${ key }List` ] = this.#lists[ key ].list;
|
||||
stateToUpdate[ `${ key }List` ] = this.lists[ key ].list;
|
||||
});
|
||||
update(stateToUpdate);
|
||||
|
||||
this.#windowNodeCounter.reset();
|
||||
this.windowNodeCounter.reset();
|
||||
|
||||
this.setMessagesLoading(false);
|
||||
})
|
||||
|
|
@ -224,25 +247,25 @@ export default class MessageDistributor extends StatedScreen {
|
|||
});
|
||||
}
|
||||
|
||||
move(t: number, index: ?number):void {
|
||||
const stateToUpdate = {};
|
||||
move(t: number, index?: number):void {
|
||||
const stateToUpdate: Partial<State> = {};
|
||||
/* == REFACTOR_ME == */
|
||||
const lastLoadedLocationMsg = this.#loadedLocationManager.moveToLast(t, index);
|
||||
const lastLoadedLocationMsg = this.loadedLocationManager.moveToLast(t, index);
|
||||
if (!!lastLoadedLocationMsg) {
|
||||
setListsStartTime(lastLoadedLocationMsg.time)
|
||||
this.#navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.#sessionStart;
|
||||
this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart;
|
||||
}
|
||||
const llEvent = this.#locationEventManager.moveToLast(t, index);
|
||||
const llEvent = this.locationEventManager.moveToLast(t, index);
|
||||
if (!!llEvent) {
|
||||
if (llEvent.domContentLoadedTime != null) {
|
||||
stateToUpdate.domContentLoadedTime = {
|
||||
time: llEvent.domContentLoadedTime + this.#navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & setLocation: add navigationStart to db)
|
||||
time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db)
|
||||
value: llEvent.domContentLoadedTime,
|
||||
}
|
||||
}
|
||||
if (llEvent.loadTime != null) {
|
||||
stateToUpdate.loadTime = {
|
||||
time: llEvent.loadTime + this.#navigationStartOffset,
|
||||
time: llEvent.loadTime + this.navigationStartOffset,
|
||||
value: llEvent.loadTime,
|
||||
}
|
||||
}
|
||||
|
|
@ -251,28 +274,24 @@ export default class MessageDistributor extends StatedScreen {
|
|||
}
|
||||
}
|
||||
/* === */
|
||||
const lastLocationMsg = this.#locationManager.moveToLast(t, index);
|
||||
const lastLocationMsg = this.locationManager.moveToLast(t, index);
|
||||
if (!!lastLocationMsg) {
|
||||
stateToUpdate.location = lastLocationMsg.url;
|
||||
}
|
||||
const lastTitleMsg = this.#titleManager.moveToLast(t, index);
|
||||
if (!!lastTitleMsg) {
|
||||
stateToUpdate.title = lastTitleMsg.title;
|
||||
}
|
||||
const lastConnectionInfoMsg = this.#connectionInfoManger.moveToLast(t, index);
|
||||
const lastConnectionInfoMsg = this.connectionInfoManger.moveToLast(t, index);
|
||||
if (!!lastConnectionInfoMsg) {
|
||||
stateToUpdate.connType = lastConnectionInfoMsg.type;
|
||||
stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink;
|
||||
}
|
||||
const lastPerformanceTrackMessage = this.#performanceTrackManager.moveToLast(t, index);
|
||||
const lastPerformanceTrackMessage = this.performanceTrackManager.moveToLast(t, index);
|
||||
if (!!lastPerformanceTrackMessage) {
|
||||
stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time;
|
||||
}
|
||||
|
||||
LIST_NAMES.forEach(key => {
|
||||
const lastMsg = this.#lists[ key ].moveToLast(t, key === 'exceptions' ? null : index);
|
||||
const lastMsg = this.lists[ key ].moveToLast(t, key === 'exceptions' ? undefined : index);
|
||||
if (lastMsg != null) {
|
||||
stateToUpdate[`${key}ListNow`] = this.#lists[ key ].listNow;
|
||||
stateToUpdate[`${key}ListNow`] = this.lists[ key ].listNow;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -280,19 +299,21 @@ export default class MessageDistributor extends StatedScreen {
|
|||
|
||||
/* Sequence of the managers is important here */
|
||||
// Preparing the size of "screen"
|
||||
const lastResize = this.#resizeManager.moveToLast(t, index);
|
||||
const lastResize = this.resizeManager.moveToLast(t, index);
|
||||
if (!!lastResize) {
|
||||
this.setSize(lastResize)
|
||||
}
|
||||
this.#pagesManager.moveReady(t).then(() => {
|
||||
this.pagesManager.moveReady(t).then(() => {
|
||||
|
||||
const lastScroll = this.#scrollManager.moveToLast(t, index);
|
||||
const lastScroll = this.scrollManager.moveToLast(t, index);
|
||||
// @ts-ignore ??can't see double inheritance
|
||||
if (!!lastScroll && this.window) {
|
||||
// @ts-ignore
|
||||
this.window.scrollTo(lastScroll.x, lastScroll.y);
|
||||
}
|
||||
// Moving mouse and setting :hover classes on ready view
|
||||
this.#mouseManager.move(t);
|
||||
const lastClick = this.#clickManager.moveToLast(t);
|
||||
this.mouseManager.move(t);
|
||||
const lastClick = this.clickManager.moveToLast(t);
|
||||
// if (!!lastClick) {
|
||||
// this.cursor.click();
|
||||
// }
|
||||
|
|
@ -305,7 +326,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
const decoded = {};
|
||||
try {
|
||||
keys.forEach(key => {
|
||||
decoded[ key ] = this.#decoder.decode(msg[ key ]);
|
||||
decoded[ key ] = this.decoder.decode(msg[ key ]);
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error("Error on message decoding: ", e, msg);
|
||||
|
|
@ -315,7 +336,7 @@ export default class MessageDistributor extends StatedScreen {
|
|||
}
|
||||
|
||||
/* Binded */
|
||||
#distributeMessage = (msg: Message, index: number): void => {
|
||||
distributeMessage = (msg: TimedMessage, index: number): void => {
|
||||
if ([
|
||||
"mouse_move",
|
||||
"set_input_value",
|
||||
|
|
@ -323,24 +344,13 @@ export default class MessageDistributor extends StatedScreen {
|
|||
"set_viewport_size",
|
||||
"set_viewport_scroll",
|
||||
].includes(msg.tp)) {
|
||||
this.#activirtManager.updateAcctivity(msg.time);
|
||||
this.activirtManager?.updateAcctivity(msg.time);
|
||||
}
|
||||
//const index = #i + index; //?
|
||||
//const index = i + index; //?
|
||||
let decoded;
|
||||
const time = msg.time;
|
||||
switch (msg.tp) {
|
||||
/* Lists: */
|
||||
case "resource_timing":
|
||||
logger.log(msg)
|
||||
listAppend("resource", Resource({
|
||||
time,
|
||||
duration: msg.duration,
|
||||
ttfb: msg.ttfb,
|
||||
url: msg.url,
|
||||
initiator: msg.initiator,
|
||||
index,
|
||||
}));
|
||||
break;
|
||||
case "console_log":
|
||||
if (msg.level === 'debug') break;
|
||||
listAppend("log", Log({
|
||||
|
|
@ -359,60 +369,57 @@ export default class MessageDistributor extends StatedScreen {
|
|||
status: msg.status,
|
||||
duration: msg.duration,
|
||||
type: TYPES.FETCH,
|
||||
time: msg.timestamp - this.#sessionStart, //~
|
||||
time: msg.timestamp - this.sessionStart, //~
|
||||
index,
|
||||
}));
|
||||
break;
|
||||
/* */
|
||||
case "set_page_location":
|
||||
this.#locationManager.add(msg);
|
||||
this.locationManager.add(msg);
|
||||
if (msg.navigationStart > 0) {
|
||||
this.#loadedLocationManager.add(msg);
|
||||
this.loadedLocationManager.add(msg);
|
||||
}
|
||||
break;
|
||||
case "set_title":
|
||||
this.#titleManager.add(msg);
|
||||
break;
|
||||
case "set_viewport_size":
|
||||
this.#resizeManager.add(msg);
|
||||
this.resizeManager.add(msg);
|
||||
break;
|
||||
case "mouse_move":
|
||||
this.#mouseManager.add(msg);
|
||||
this.mouseManager.add(msg);
|
||||
break;
|
||||
case "set_viewport_scroll":
|
||||
this.#scrollManager.add(msg);
|
||||
this.scrollManager.add(msg);
|
||||
break;
|
||||
case "performance_track":
|
||||
this.#performanceTrackManager.add(msg);
|
||||
this.performanceTrackManager.add(msg);
|
||||
break;
|
||||
case "set_page_visibility":
|
||||
this.#performanceTrackManager.handleVisibility(msg)
|
||||
this.performanceTrackManager.handleVisibility(msg)
|
||||
break;
|
||||
case "connection_information":
|
||||
this.#connectionInfoManger.add(msg);
|
||||
this.connectionInfoManger.add(msg);
|
||||
break;
|
||||
case "o_table":
|
||||
this.#decoder.set(msg.key, msg.value);
|
||||
this.decoder.set(msg.key, msg.value);
|
||||
break;
|
||||
case "redux":
|
||||
decoded = this._decodeMessage(msg, ["state", "action"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.#lists.redux.add(decoded);
|
||||
this.lists.redux.add(decoded);
|
||||
}
|
||||
break;
|
||||
case "ng_rx":
|
||||
decoded = this._decodeMessage(msg, ["state", "action"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.#lists.ngrx.add(decoded);
|
||||
this.lists.ngrx.add(decoded);
|
||||
}
|
||||
break;
|
||||
case "vuex":
|
||||
decoded = this._decodeMessage(msg, ["state", "mutation"]);
|
||||
logger.log(decoded)
|
||||
if (decoded != null) {
|
||||
this.#lists.vuex.add(decoded);
|
||||
this.lists.vuex.add(decoded);
|
||||
}
|
||||
break;
|
||||
case "mob_x":
|
||||
|
|
@ -420,59 +427,62 @@ export default class MessageDistributor extends StatedScreen {
|
|||
logger.log(decoded)
|
||||
|
||||
if (decoded != null) {
|
||||
this.#lists.mobx.add(decoded);
|
||||
this.lists.mobx.add(decoded);
|
||||
}
|
||||
break;
|
||||
case "graph_ql":
|
||||
// @ts-ignore some hack? TODO: remove
|
||||
msg.duration = 0;
|
||||
this.#lists.graphql.add(msg);
|
||||
this.lists.graphql.add(msg);
|
||||
break;
|
||||
case "profiler":
|
||||
this.#lists.profiles.add(msg);
|
||||
this.lists.profiles.add(msg);
|
||||
break;
|
||||
case "long_task":
|
||||
this.#lists.longtasks.add({
|
||||
this.lists.longtasks.add({
|
||||
...msg,
|
||||
time: msg.timestamp - this.#sessionStart,
|
||||
time: msg.timestamp - this.sessionStart,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
switch (msg.tp){
|
||||
case "create_document":
|
||||
this.#windowNodeCounter.reset();
|
||||
this.#performanceTrackManager.setCurrentNodesCount(this.#windowNodeCounter.count);
|
||||
this.windowNodeCounter.reset();
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
case "create_text_node":
|
||||
case "create_element_node":
|
||||
this.#windowNodeCounter.addNode(msg.id, msg.parentID);
|
||||
this.#performanceTrackManager.setCurrentNodesCount(this.#windowNodeCounter.count);
|
||||
this.windowNodeCounter.addNode(msg.id, msg.parentID);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
case "move_node":
|
||||
this.#windowNodeCounter.moveNode(msg.id, msg.parentID);
|
||||
this.#performanceTrackManager.setCurrentNodesCount(this.#windowNodeCounter.count);
|
||||
this.windowNodeCounter.moveNode(msg.id, msg.parentID);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
case "remove_node":
|
||||
this.#windowNodeCounter.removeNode(msg.id);
|
||||
this.#performanceTrackManager.setCurrentNodesCount(this.#windowNodeCounter.count);
|
||||
this.windowNodeCounter.removeNode(msg.id);
|
||||
this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count);
|
||||
break;
|
||||
}
|
||||
this.#pagesManager.add(msg);
|
||||
this.pagesManager.add(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
getLastMessageTime():number {
|
||||
return this.#lastMessageTime;
|
||||
return this.lastMessageTime;
|
||||
}
|
||||
|
||||
getFirstMessageTime():number {
|
||||
return 0; //this.#pagesManager.minTime;
|
||||
return 0; //this.pagesManager.minTime;
|
||||
}
|
||||
|
||||
// TODO: clean managers?
|
||||
clean() {
|
||||
// @ts-ignore
|
||||
super.clean();
|
||||
if (this._socket) this._socket.close();
|
||||
//if (this._socket) this._socket.close();
|
||||
update(INITIAL_STATE);
|
||||
this.assistManager.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
import type { TimedMessage } from './Timed';
|
||||
|
||||
import logger from 'App/logger';
|
||||
import readMessage from './messages';
|
||||
|
||||
function needSkipMessage(data: Uint8Array, p: number, pLast: number): boolean {
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
if (data[ p + i ] !== data[ pLast + i ]) {
|
||||
return data[ p + i ] - data[ pLast + i ] < 0
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export default class MessageGenerator {
|
||||
#data: Uint8Array;
|
||||
#p: number = 0;
|
||||
#pLastMessageID: number = 0;
|
||||
#startTime: number;
|
||||
#currentTime: ?number;
|
||||
|
||||
#error: boolean = false;
|
||||
constructor(data: Uint8Array, startTime: number) {
|
||||
this.#startTime = startTime;
|
||||
this.#data = data;
|
||||
}
|
||||
|
||||
_needSkipMessage():boolean {
|
||||
if (this.#p === 0) return false;
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
if (this.#data[ this.#p + i ] !== this.#data[ this.#pLastMessageID + i ]) {
|
||||
return this.#data[ this.#p + i ] - this.#data[ this.#pLastMessageID + i ] < 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_readMessage(): ?Message {
|
||||
this.#p += 8;
|
||||
try {
|
||||
let msg
|
||||
[ msg, this.#p ] = readMessage(this.#data, this.#p);
|
||||
return msg;
|
||||
} catch (e) {
|
||||
this.#error = true;
|
||||
logger.error("Read message error:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hasNext():boolean {
|
||||
return !this.#error && this.#data.length > this.#p;
|
||||
}
|
||||
|
||||
next(): ?[ TimedMessage, number] {
|
||||
if (!this.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (this._needSkipMessage()) {
|
||||
this._readMessage();
|
||||
}
|
||||
this.#pLastMessageID = this.#p;
|
||||
|
||||
const msg = this._readMessage();
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (msg.tp === "timestamp") {
|
||||
// if (this.#startTime == null) {
|
||||
// this.#startTime = msg.timestamp
|
||||
// }
|
||||
this.#currentTime = msg.timestamp - this.#startTime;
|
||||
} else {
|
||||
msg.time = this.#currentTime;
|
||||
msg._index = this.#pLastMessageID;
|
||||
return [msg, this.#pLastMessageID];
|
||||
}
|
||||
}
|
||||
}
|
||||
80
frontend/app/player/MessageDistributor/MessageReader.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import type { TimedMessage, Indexed } from './Timed';
|
||||
|
||||
import logger from 'App/logger';
|
||||
import readMessage, { Message } from './messages';
|
||||
import PrimitiveReader from './PrimitiveReader';
|
||||
|
||||
// function needSkipMessage(data: Uint8Array, p: number, pLast: number): boolean {
|
||||
// for (let i = 7; i >= 0; i--) {
|
||||
// if (data[ p + i ] !== data[ pLast + i ]) {
|
||||
// return data[ p + i ] - data[ pLast + i ] < 0
|
||||
// }
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
|
||||
export default class MessageReader extends PrimitiveReader {
|
||||
private pLastMessageID: number = 0;
|
||||
private currentTime: number = 0;
|
||||
public error: boolean = false;
|
||||
constructor(data: Uint8Array, private readonly startTime: number) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
private needSkipMessage(): boolean {
|
||||
if (this.p === 0) return false;
|
||||
for (let i = 7; i >= 0; i--) {
|
||||
if (this.buf[ this.p + i ] !== this.buf[ this.pLastMessageID + i ]) {
|
||||
return this.buf[ this.p + i ] - this.buf[ this.pLastMessageID + i ] < 0;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private readMessage(): Message | null {
|
||||
this.skip(8);
|
||||
try {
|
||||
let msg
|
||||
msg = readMessage(this);
|
||||
return msg;
|
||||
} catch (e) {
|
||||
this.error = true;
|
||||
logger.error("Read message error:", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
hasNext():boolean {
|
||||
return !this.error && this.buf.length > this.p;
|
||||
}
|
||||
|
||||
next(): [ TimedMessage, number] | null {
|
||||
if (!this.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (this.needSkipMessage()) {
|
||||
this.readMessage();
|
||||
}
|
||||
this.pLastMessageID = this.p;
|
||||
|
||||
const msg = this.readMessage();
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (msg.tp === "timestamp") {
|
||||
// if (this.startTime == null) {
|
||||
// this.startTime = msg.timestamp
|
||||
// }
|
||||
this.currentTime = msg.timestamp - this.startTime;
|
||||
} else {
|
||||
const tMsg = Object.assign(msg, {
|
||||
time: this.currentTime,
|
||||
_index: this.pLastMessageID,
|
||||
})
|
||||
return [tMsg, this.pLastMessageID];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
36
frontend/app/player/MessageDistributor/PrimitiveReader.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export default class PrimitiveReader {
|
||||
protected p = 0
|
||||
constructor(protected readonly buf: Uint8Array) {}
|
||||
|
||||
readUint() {
|
||||
var r = 0, s = 1, b;
|
||||
do {
|
||||
b = this.buf[this.p++];
|
||||
r += (b & 0x7F) * s;
|
||||
s *= 128;
|
||||
} while (b >= 0x80)
|
||||
return r;
|
||||
}
|
||||
|
||||
readInt() {
|
||||
let u = this.readUint();
|
||||
if (u % 2) {
|
||||
u = (u + 1) / -2;
|
||||
} else {
|
||||
u = u / 2;
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
readString() {
|
||||
var l = this.readUint();
|
||||
return new TextDecoder().decode(this.buf.subarray(this.p, this.p+=l));
|
||||
}
|
||||
|
||||
readBoolean() {
|
||||
return !!this.buf[this.p++];
|
||||
}
|
||||
skip(n: number) {
|
||||
this.p += n;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,23 @@
|
|||
import Marker from './Marker';
|
||||
import Cursor from './Cursor';
|
||||
import Inspector from './Inspector';
|
||||
import styles from './screen.css';
|
||||
import { getState } from '../../../store';
|
||||
|
||||
import type { Point } from './types';
|
||||
|
||||
export const INITIAL_STATE: {
|
||||
|
||||
export interface State {
|
||||
width: number;
|
||||
height: number;
|
||||
} = {
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
|
||||
|
||||
export default class BaseScreen {
|
||||
export default abstract class BaseScreen {
|
||||
public readonly overlay: HTMLDivElement;
|
||||
private readonly iframe: HTMLIFrameElement;
|
||||
public readonly overlay: HTMLDivElement;
|
||||
private readonly _screen: HTMLDivElement;
|
||||
protected parentElement: HTMLElement | null = null;
|
||||
constructor() {
|
||||
|
|
@ -68,8 +68,18 @@ export default class BaseScreen {
|
|||
return this.iframe.contentDocument;
|
||||
}
|
||||
|
||||
_getInternalCoordinates({ x, y }: Point): Point {
|
||||
const { x: overlayX, y: overlayY, width } = this.overlay.getBoundingClientRect();
|
||||
private boundingRect: DOMRect | null = null;
|
||||
private getBoundingClientRect(): DOMRect {
|
||||
//if (this.boundingRect === null) {
|
||||
return this.boundingRect = this.overlay.getBoundingClientRect(); // expensive operation?
|
||||
//}
|
||||
//return this.boundingRect;
|
||||
}
|
||||
|
||||
getInternalCoordinates({ x, y }: Point): Point {
|
||||
const { x: overlayX, y: overlayY, width } = this.getBoundingClientRect();
|
||||
//console.log("x y ", x,y,'ovx y', overlayX, overlayY, width)
|
||||
|
||||
const screenWidth = this.overlay.offsetWidth;
|
||||
|
||||
const scale = screenWidth / width;
|
||||
|
|
@ -98,11 +108,11 @@ export default class BaseScreen {
|
|||
}
|
||||
|
||||
getElementFromPoint(point: Point): Element | null {
|
||||
return this.getElementFromInternalPoint(this._getInternalCoordinates(point));
|
||||
return this.getElementFromInternalPoint(this.getInternalCoordinates(point));
|
||||
}
|
||||
|
||||
getElementsFromPoint(point: Point): Element[] {
|
||||
return this.getElementsFromInternalPoint(this._getInternalCoordinates(point));
|
||||
return this.getElementsFromInternalPoint(this.getInternalCoordinates(point));
|
||||
}
|
||||
|
||||
display(flag: boolean = true) {
|
||||
|
|
@ -130,8 +140,13 @@ export default class BaseScreen {
|
|||
this._screen.style.height = height + 'px';
|
||||
this.iframe.style.width = width + 'px';
|
||||
this.iframe.style.height = height + 'px';
|
||||
|
||||
this.boundingRect = this.overlay.getBoundingClientRect();
|
||||
}
|
||||
|
||||
scale = () => { // TODO: solve classes inheritance issues in typescript
|
||||
this._scale();
|
||||
}
|
||||
scale = () => this._scale()
|
||||
|
||||
|
||||
clean() {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import styles from './screen.css';
|
|||
import { getState } from '../../../store';
|
||||
import BaseScreen from './BaseScreen';
|
||||
|
||||
export { INITIAL_STATE } from './BaseScreen';
|
||||
export { INITIAL_STATE, State } from './BaseScreen';
|
||||
|
||||
export default class Screen extends BaseScreen {
|
||||
private cursor: Cursor;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
.iframe {
|
||||
position: absolute;
|
||||
border: none;
|
||||
background: whilte;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE } from './Screen';
|
||||
import { update, getState } from '../../store';
|
||||
|
||||
export const INITIAL_STATE = {
|
||||
...SUPER_INITIAL_STATE,
|
||||
messagesLoading: false,
|
||||
cssLoading: false,
|
||||
disconnected: false,
|
||||
userPageLoading: false,
|
||||
}
|
||||
|
||||
export default class StatedScreen extends Screen {
|
||||
|
||||
setMessagesLoading(messagesLoading) {
|
||||
this.display(!messagesLoading);
|
||||
update({ messagesLoading });
|
||||
}
|
||||
|
||||
setCSSLoading(cssLoading) {
|
||||
this.displayFrame(!cssLoading);
|
||||
update({ cssLoading });
|
||||
}
|
||||
|
||||
setDisconnected(disconnected) {
|
||||
if (!getState().live) return; //?
|
||||
this.display(!disconnected);
|
||||
update({ disconnected });
|
||||
}
|
||||
|
||||
setUserPageLoading(userPageLoading) {
|
||||
this.display(!userPageLoading);
|
||||
update({ userPageLoading });
|
||||
}
|
||||
|
||||
setSize({ height, width }) {
|
||||
update({ width, height });
|
||||
this.scale();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen';
|
||||
import { update, getState } from '../../store';
|
||||
|
||||
|
||||
export interface State extends SuperState {
|
||||
messagesLoading: boolean,
|
||||
cssLoading: boolean,
|
||||
disconnected: boolean,
|
||||
userPageLoading: boolean,
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
...SUPER_INITIAL_STATE,
|
||||
messagesLoading: false,
|
||||
cssLoading: false,
|
||||
disconnected: false,
|
||||
userPageLoading: false,
|
||||
}
|
||||
|
||||
export default class StatedScreen extends Screen {
|
||||
constructor() { super(); }
|
||||
|
||||
setMessagesLoading(messagesLoading: boolean) {
|
||||
// @ts-ignore
|
||||
this.display(!messagesLoading);
|
||||
update({ messagesLoading });
|
||||
}
|
||||
|
||||
setCSSLoading(cssLoading: boolean) {
|
||||
// @ts-ignore
|
||||
|
||||
this.displayFrame(!cssLoading);
|
||||
update({ cssLoading });
|
||||
}
|
||||
|
||||
setDisconnected(disconnected: boolean) {
|
||||
if (!getState().live) return; //?
|
||||
// @ts-ignore
|
||||
this.display(!disconnected);
|
||||
update({ disconnected });
|
||||
}
|
||||
|
||||
setUserPageLoading(userPageLoading: boolean) {
|
||||
// @ts-ignore
|
||||
this.display(!userPageLoading);
|
||||
update({ userPageLoading });
|
||||
}
|
||||
|
||||
setSize({ height, width }: { height: number, width: number }) {
|
||||
update({ width, height });
|
||||
// @ts-ignore
|
||||
this.scale();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// @flow
|
||||
import type { Message } from './messages';
|
||||
|
||||
export type Timed = { +time: number };
|
||||
export type TimedMessage = Timed & Message;
|
||||
5
frontend/app/player/MessageDistributor/Timed.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { Message } from './messages';
|
||||
|
||||
export interface Timed { readonly time: number };
|
||||
export interface Indexed { readonly _index: number }; // TODO: remove dash (evwrywhere)
|
||||
export type TimedMessage = Timed & Message;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import ListWalker from './ListWalker';
|
||||
|
||||
|
||||
class SkipInterval {
|
||||
constructor({ start = 0, end = 0 }) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
get time(): number {
|
||||
return this.start;
|
||||
}
|
||||
contains(ts) {
|
||||
return ts > this.start && ts < this.end;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class ActivityManager extends ListWalker<SkipInterval> {
|
||||
#endTime: number = 0;
|
||||
#minInterval: number = 0;
|
||||
#lastActivity: number = 0;
|
||||
constructor(duration: number) {
|
||||
super();
|
||||
this.#endTime = duration;
|
||||
this.#minInterval = duration * 0.1;
|
||||
}
|
||||
|
||||
updateAcctivity(time: number) {
|
||||
if (time - this.#lastActivity >= this.#minInterval) {
|
||||
this.add(new SkipInterval({ start: this.#lastActivity, end: time }));
|
||||
}
|
||||
this.#lastActivity = time;
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.#endTime - this.#lastActivity >= this.#minInterval) {
|
||||
this.add(new SkipInterval({ start: this.#lastActivity, end: this.#endTime }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import ListWalker from './ListWalker';
|
||||
|
||||
|
||||
class SkipIntervalCls {
|
||||
constructor(private readonly start = 0, private readonly end = 0) {}
|
||||
|
||||
get time(): number {
|
||||
return this.start;
|
||||
}
|
||||
contains(ts) {
|
||||
return ts > this.start && ts < this.end;
|
||||
}
|
||||
}
|
||||
|
||||
export type SkipInterval = InstanceType<typeof SkipIntervalCls>;
|
||||
|
||||
|
||||
export default class ActivityManager extends ListWalker<SkipInterval> {
|
||||
private endTime: number = 0;
|
||||
private minInterval: number = 0;
|
||||
private lastActivity: number = 0;
|
||||
constructor(duration: number) {
|
||||
super();
|
||||
this.endTime = duration;
|
||||
this.minInterval = duration * 0.1;
|
||||
}
|
||||
|
||||
updateAcctivity(time: number) {
|
||||
if (time - this.lastActivity >= this.minInterval) {
|
||||
this.add(new SkipIntervalCls(this.lastActivity, time));
|
||||
}
|
||||
this.lastActivity = time;
|
||||
}
|
||||
|
||||
end() {
|
||||
if (this.endTime - this.lastActivity >= this.minInterval) {
|
||||
this.add(new SkipIntervalCls(this.lastActivity, this.endTime));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
339
frontend/app/player/MessageDistributor/managers/AssistManager.ts
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
import type Peer from 'peerjs';
|
||||
import type { DataConnection, MediaConnection } from 'peerjs';
|
||||
import type MessageDistributor from '../MessageDistributor';
|
||||
import type { TimedMessage } from '../Timed';
|
||||
import type { Message } from '../messages'
|
||||
import { ID_TP_MAP } from '../messages';
|
||||
import store from 'App/store';
|
||||
|
||||
import { update, getState } from '../../store';
|
||||
|
||||
|
||||
export enum CallingState {
|
||||
Requesting,
|
||||
True,
|
||||
False,
|
||||
};
|
||||
|
||||
export enum ConnectionStatus {
|
||||
Connecting,
|
||||
Connected,
|
||||
Inactive,
|
||||
Disconnected,
|
||||
Error,
|
||||
};
|
||||
|
||||
export interface State {
|
||||
calling: CallingState,
|
||||
peerConnectionStatus: ConnectionStatus,
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
calling: CallingState.False,
|
||||
peerConnectionStatus: ConnectionStatus.Connecting,
|
||||
}
|
||||
|
||||
const MAX_RECONNECTION_COUNT = 4;
|
||||
|
||||
|
||||
function resolveURL(baseURL: string, relURL: string): string {
|
||||
if (relURL.startsWith('#') || relURL === "") {
|
||||
return relURL;
|
||||
}
|
||||
return new URL(relURL, baseURL).toString();
|
||||
}
|
||||
|
||||
|
||||
var match = /bar/.exec("foobar");
|
||||
const re1 = /url\(("[^"]*"|'[^']*'|[^)]*)\)/g
|
||||
const re2 = /@import "(.*?)"/g
|
||||
function cssUrlsIndex(css: string): Array<[number, number]> {
|
||||
const idxs: Array<[number, number]> = [];
|
||||
const i1 = css.matchAll(re1);
|
||||
// @ts-ignore
|
||||
for (let m of i1) {
|
||||
// @ts-ignore
|
||||
const s: number = m.index + m[0].indexOf(m[1]);
|
||||
const e: number = s + m[1].length;
|
||||
idxs.push([s, e]);
|
||||
}
|
||||
const i2 = css.matchAll(re2);
|
||||
// @ts-ignore
|
||||
for (let m of i2) {
|
||||
// @ts-ignore
|
||||
const s = m.index + m[0].indexOf(m[1]);
|
||||
const e = s + m[1].length;
|
||||
idxs.push([s, e])
|
||||
}
|
||||
return idxs;
|
||||
}
|
||||
function unquote(str: string): [string, string] {
|
||||
str = str.trim();
|
||||
if (str.length <= 2) {
|
||||
return [str, ""]
|
||||
}
|
||||
if (str[0] == '"' && str[str.length-1] == '"') {
|
||||
return [ str.substring(1, str.length-1), "\""];
|
||||
}
|
||||
if (str[0] == '\'' && str[str.length-1] == '\'') {
|
||||
return [ str.substring(1, str.length-1), "'" ];
|
||||
}
|
||||
return [str, ""]
|
||||
}
|
||||
function rewriteCSSLinks(css: string, rewriter: (rawurl: string) => string): string {
|
||||
for (let idx of cssUrlsIndex(css)) {
|
||||
const f = idx[0]
|
||||
const t = idx[1]
|
||||
const [ rawurl, q ] = unquote(css.substring(f, t));
|
||||
css = css.substring(0,f) + q + rewriter(rawurl) + q + css.substring(t);
|
||||
}
|
||||
return css
|
||||
}
|
||||
|
||||
function resolveCSS(baseURL: string, css: string): string {
|
||||
return rewriteCSSLinks(css, rawurl => resolveURL(baseURL, rawurl));
|
||||
}
|
||||
|
||||
|
||||
export default class AssistManager {
|
||||
constructor(private session, private md: MessageDistributor) {}
|
||||
|
||||
private get peerID(): string {
|
||||
return `${this.session.projectKey}-${this.session.sessionId}`
|
||||
}
|
||||
|
||||
private peer: Peer | null = null;
|
||||
connectionAttempts: number = 0;
|
||||
connect() {
|
||||
if (this.peer != null) {
|
||||
console.error("AssistManager: trying to connect more than once");
|
||||
return;
|
||||
}
|
||||
this.md.setMessagesLoading(true);
|
||||
import('peerjs').then(({ default: Peer }) => {
|
||||
// @ts-ignore
|
||||
const peer = new Peer({
|
||||
// @ts-ignore
|
||||
host: new URL(window.ENV.API_EDP).host,
|
||||
path: '/assist',
|
||||
port: location.protocol === 'https:' ? 443 : 80,
|
||||
});
|
||||
this.peer = peer;
|
||||
this.peer.on('error', e => {
|
||||
if (e.type === 'peer-unavailable') {
|
||||
if (this.peer && this.connectionAttempts++ < MAX_RECONNECTION_COUNT) {
|
||||
update({ peerConnectionStatus: ConnectionStatus.Connecting })
|
||||
this.connectToPeer();
|
||||
} else {
|
||||
update({ peerConnectionStatus: ConnectionStatus.Disconnected })
|
||||
|
||||
}
|
||||
} else {
|
||||
console.error(`PeerJS error (on peer). Type ${e.type}`, e);
|
||||
update({ peerConnectionStatus: ConnectionStatus.Error })
|
||||
}
|
||||
})
|
||||
peer.on("open", me => {
|
||||
console.log("peer opened", me);
|
||||
this.connectToPeer();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private connectToPeer() {
|
||||
if (!this.peer) { return; }
|
||||
const id = this.peerID;
|
||||
console.log("trying to connect to", id)
|
||||
const conn = this.peer.connect(id, { serialization: 'json', reliable: true});
|
||||
|
||||
conn.on('open', () => {
|
||||
update({ peerConnectionStatus: ConnectionStatus.Inactive });
|
||||
console.log("peer connected")
|
||||
|
||||
let i = 0;
|
||||
let firstMessage = true;
|
||||
conn.on('data', (data) => {
|
||||
if (typeof data === 'string') { return this.handleCommand(data); }
|
||||
if (!Array.isArray(data)) { return; }
|
||||
if (firstMessage) {
|
||||
firstMessage = false;
|
||||
this.md.setMessagesLoading(false);
|
||||
update({ peerConnectionStatus: ConnectionStatus.Connected })
|
||||
}
|
||||
|
||||
let time = 0;
|
||||
let ts0 = 0;
|
||||
(data as Array<Message & { _id: number}>).forEach(msg => {
|
||||
|
||||
// TODO: more appropriate way to do it.
|
||||
if (msg._id === 60) {
|
||||
// @ts-ignore
|
||||
if (msg.name === 'src' || msg.name === 'href') {
|
||||
// @ts-ignore
|
||||
msg.value = resolveURL(msg.baseURL, msg.value);
|
||||
// @ts-ignore
|
||||
} else if (msg.name === 'style') {
|
||||
// @ts-ignore
|
||||
msg.value = resolveCSS(msg.baseURL, msg.value);
|
||||
}
|
||||
msg._id = 12;
|
||||
} else if (msg._id === 61) { // "SetCSSDataURLBased"
|
||||
// @ts-ignore
|
||||
msg.data = resolveCSS(msg.baseURL, msg.data);
|
||||
msg._id = 15;
|
||||
} else if (msg._id === 67) { // "insert_rule"
|
||||
// @ts-ignore
|
||||
msg.rule = resolveCSS(msg.baseURL, msg.rule);
|
||||
msg._id = 37;
|
||||
}
|
||||
|
||||
|
||||
msg.tp = ID_TP_MAP[msg._id]; // _id goes from tracker
|
||||
|
||||
if (msg.tp === "timestamp") {
|
||||
ts0 = ts0 || msg.timestamp
|
||||
time = msg.timestamp - ts0;
|
||||
return;
|
||||
}
|
||||
const tMsg: TimedMessage = Object.assign(msg, {
|
||||
time,
|
||||
_index: i,
|
||||
});
|
||||
this.md.distributeMessage(tMsg, i++);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const intervalID = setInterval(() => {
|
||||
if (!conn.open) {
|
||||
this.md.setMessagesLoading(true);
|
||||
this.assistentCallEnd();
|
||||
update({ peerConnectionStatus: ConnectionStatus.Disconnected })
|
||||
clearInterval(intervalID);
|
||||
}
|
||||
}, 5000);
|
||||
conn.on('close', () => this.onDataClose());// Doesn't work ?
|
||||
}
|
||||
|
||||
|
||||
|
||||
private onDataClose() {
|
||||
this.md.setMessagesLoading(true);
|
||||
this.assistentCallEnd();
|
||||
console.log('closed peer conn. Reconnecting...')
|
||||
update({ peerConnectionStatus: ConnectionStatus.Connecting })
|
||||
setTimeout(() => this.connectToPeer(), 300); // reconnect
|
||||
}
|
||||
|
||||
|
||||
private get dataConnection(): DataConnection | undefined {
|
||||
return this.peer?.connections[this.peerID]?.find(c => c.type === 'data' && c.open);
|
||||
}
|
||||
|
||||
private get callConnection(): MediaConnection | undefined {
|
||||
return this.peer?.connections[this.peerID]?.find(c => c.type === 'media' && c.open);
|
||||
}
|
||||
|
||||
|
||||
private onCallEnd: null | (()=>void) = null;
|
||||
private assistentCallEnd = () => {
|
||||
console.log('assistentCallEnd')
|
||||
const conn = this.callConnection?.close();
|
||||
const dataConn = this.dataConnection;
|
||||
if (dataConn) {
|
||||
console.log("call_end send")
|
||||
dataConn.send("call_end");
|
||||
}
|
||||
this.onCallEnd?.();
|
||||
}
|
||||
|
||||
private onTrackerCallEnd = () => {
|
||||
const conn = this.callConnection;
|
||||
if (conn && conn.open) {
|
||||
conn.close();
|
||||
}
|
||||
this.onCallEnd?.();
|
||||
}
|
||||
|
||||
|
||||
private handleCommand(command: string) {
|
||||
switch (command) {
|
||||
case "call_end":
|
||||
this.onTrackerCallEnd();
|
||||
return;
|
||||
case "call_error":
|
||||
this.onTrackerCallEnd();
|
||||
update({ peerConnectionStatus: ConnectionStatus.Error });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//private blocked: boolean = false;
|
||||
private onMouseMove = (e: MouseEvent ): void => {
|
||||
//if (this.blocked) { return; }
|
||||
//this.blocked = true;
|
||||
//setTimeout(() => this.blocked = false, 200);
|
||||
const conn = this.dataConnection;
|
||||
if (!conn || !conn.open) { return; }
|
||||
// @ts-ignore ???
|
||||
const data = this.md.getInternalCoordinates(e);
|
||||
conn.send({ x: Math.round(data.x), y: Math.round(data.y) });
|
||||
}
|
||||
|
||||
call(localStream: MediaStream, onStream: (s: MediaStream)=>void, onClose: () => void, onReject: () => void, onError?: ()=> void): null | Function {
|
||||
if (!this.peer || getState().calling !== CallingState.False) { return null; }
|
||||
|
||||
update({ calling: CallingState.Requesting });
|
||||
console.log('calling...')
|
||||
const call = this.peer.call(this.peerID, localStream);
|
||||
call.on('stream', stream => {
|
||||
update({ calling: CallingState.True });
|
||||
onStream(stream);
|
||||
this.dataConnection?.send({
|
||||
name: store.getState().getIn([ 'user', 'account', 'name']),
|
||||
});
|
||||
|
||||
// @ts-ignore ??
|
||||
this.md.overlay.addEventListener("mousemove", this.onMouseMove)
|
||||
});
|
||||
|
||||
this.onCallEnd = () => {
|
||||
if (getState().calling === CallingState.Requesting) {
|
||||
onReject();
|
||||
}
|
||||
onClose();
|
||||
|
||||
// @ts-ignore ??
|
||||
this.md.overlay.removeEventListener("mousemove", this.onMouseMove);
|
||||
update({ calling: CallingState.False });
|
||||
this.onCallEnd = null;
|
||||
}
|
||||
//call.on("close", this.onCallEnd);
|
||||
call.on("error", (e) => {
|
||||
console.error("PeerJS error (on call):", e)
|
||||
this.onCallEnd?.();
|
||||
onError?.();
|
||||
});
|
||||
|
||||
const intervalID = setInterval(() => {
|
||||
if (!call.open && getState().calling !== CallingState.Requesting) {
|
||||
this.onCallEnd?.();
|
||||
clearInterval(intervalID);
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
window.addEventListener("beforeunload", this.assistentCallEnd)
|
||||
|
||||
return this.assistentCallEnd;
|
||||
}
|
||||
|
||||
clear() {
|
||||
console.log('clearing', this.peerID)
|
||||
this.assistentCallEnd();
|
||||
console.log("destroying peer...")
|
||||
this.peer?.destroy();
|
||||
this.peer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,283 +0,0 @@
|
|||
//@flow
|
||||
import type StatedScreen from '../StatedScreen';
|
||||
import type { Message, SetNodeScroll, CreateElementNode } from '../messages';
|
||||
import type { TimedMessage } from '../Timed';
|
||||
|
||||
import logger from 'App/logger';
|
||||
import StylesManager from './StylesManager';
|
||||
import ListWalker from './ListWalker';
|
||||
|
||||
const IGNORED_ATTRS = [ "autocomplete", "name" ];
|
||||
|
||||
const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~
|
||||
|
||||
export default class DOMManager extends ListWalker<TimedMessage> {
|
||||
#isMobile: boolean;
|
||||
#screen: StatedScreen;
|
||||
// #prop compiles to method that costs mor than strict property call.
|
||||
_nl: Array<Element> = [];
|
||||
_isLink: Array<boolean> = []; // Optimisations
|
||||
_bodyId: number = -1;
|
||||
_postponedBodyMessage: ?CreateElementNode = null;
|
||||
#nodeScrollManagers: Array<ListWalker<SetNodeScroll>> = [];
|
||||
|
||||
#stylesManager: StylesManager;
|
||||
|
||||
#startTime: number;
|
||||
|
||||
constructor(screen: StatedScreen, isMobile: boolean, startTime: number) {
|
||||
super();
|
||||
this.#startTime = startTime;
|
||||
this.#isMobile = isMobile;
|
||||
this.#screen = screen;
|
||||
this.#stylesManager = new StylesManager(screen);
|
||||
}
|
||||
|
||||
get time(): number {
|
||||
return this.#startTime;
|
||||
}
|
||||
|
||||
add(m: TimedMessage): void {
|
||||
switch (m.tp) {
|
||||
case "set_node_scroll":
|
||||
if (!this.#nodeScrollManagers[ m.id ]) {
|
||||
this.#nodeScrollManagers[ m.id ] = new ListWalker();
|
||||
}
|
||||
this.#nodeScrollManagers[ m.id ].add(m);
|
||||
return;
|
||||
//case "css_insert_rule": // || //set_css_data ???
|
||||
//case "css_delete_rule":
|
||||
// (m.tp === "set_node_attribute" && this._isLink[ m.id ] && m.key === "href")) {
|
||||
// this.#stylesManager.add(m);
|
||||
// return;
|
||||
default:
|
||||
if (m.tp === "create_element_node") {
|
||||
switch(m.tag) {
|
||||
case "LINK":
|
||||
this._isLink[ m.id ] = true;
|
||||
break;
|
||||
case "BODY":
|
||||
this._bodyId = m.id; // Can be several body nodes at one document session?
|
||||
break;
|
||||
}
|
||||
} else if (m.tp === "set_node_attribute" &&
|
||||
(IGNORED_ATTRS.includes(m.key) || !ATTR_NAME_REGEXP.test(m.key))) {
|
||||
logger.log("Ignorring message: ", m)
|
||||
return; // Ignoring...
|
||||
}
|
||||
super.add(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_removeBodyScroll(id: number): void {
|
||||
if (this.#isMobile && this._bodyId === id) {
|
||||
this._nl[ id ].style.overflow = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
// May be make it as a message on message add?
|
||||
_removeAutocomplete({ id, tag }: { id: number, tag: string }): boolean {
|
||||
const node = this._nl[ id ];
|
||||
if ([ "FORM", "TEXTAREA", "SELECT" ].includes(tag)) {
|
||||
node.setAttribute("autocomplete", "off");
|
||||
return true;
|
||||
}
|
||||
if (tag === "INPUT") {
|
||||
node.setAttribute("autocomplete", "new-password");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// type = NodeMessage ?
|
||||
_insertNode({ parentID, id, index }: { parentID: number, id: number, index: number }): void {
|
||||
if (!this._nl[ id ]) {
|
||||
logger.error("Insert error. Node not found", id);
|
||||
return;
|
||||
}
|
||||
if (!this._nl[ parentID ]) {
|
||||
logger.error("Insert error. Parent node not found", parentID);
|
||||
return;
|
||||
}
|
||||
// WHAT if text info contains some rules and the ordering is just wrong???
|
||||
if ((this._nl[ parentID ] instanceof HTMLStyleElement) && // TODO: correct ordering OR filter in tracker
|
||||
this._nl[ parentID ].sheet &&
|
||||
this._nl[ parentID ].sheet.cssRules &&
|
||||
this._nl[ parentID ].sheet.cssRules.length > 0) {
|
||||
logger.log("Trying to insert child to style tag with virtual rules: ", this._nl[ parentID ], this._nl[ id ]);
|
||||
return;
|
||||
}
|
||||
|
||||
const childNodes = this._nl[ parentID ].childNodes;
|
||||
if (!childNodes) {
|
||||
logger.error("Node has no childNodes", this._nl[ parentID ]);
|
||||
return;
|
||||
}
|
||||
this._nl[ parentID ]
|
||||
.insertBefore(this._nl[ id ], childNodes[ index ]);
|
||||
}
|
||||
|
||||
#applyMessage: (Message => void) = msg => {
|
||||
let node;
|
||||
switch (msg.tp) {
|
||||
case "create_document":
|
||||
this.#screen.document.open();
|
||||
this.#screen.document.write(`${ msg.doctype || "<!DOCTYPE html>" }<html></html>`);
|
||||
this.#screen.document.close();
|
||||
const fRoot = this.#screen.document.documentElement;
|
||||
fRoot.innerText = '';
|
||||
//this._nl[ 0 ] = fRoot; // vs
|
||||
this._nl = [ fRoot ];
|
||||
|
||||
// the last load event I can control
|
||||
//if (this.document.fonts) {
|
||||
// this.document.fonts.onloadingerror = () => this.marker.redraw();
|
||||
// this.document.fonts.onloadingdone = () => this.marker.redraw();
|
||||
//}
|
||||
|
||||
//this.#screen.setDisconnected(false);
|
||||
this.#stylesManager.reset();
|
||||
break;
|
||||
case "create_text_node":
|
||||
this._nl[ msg.id ] = document.createTextNode('');
|
||||
this._insertNode(msg);
|
||||
break;
|
||||
case "create_element_node":
|
||||
if (msg.svg) {
|
||||
this._nl[ msg.id ] = document.createElementNS('http://www.w3.org/2000/svg', msg.tag);
|
||||
} else {
|
||||
this._nl[ msg.id ] = document.createElement(msg.tag);
|
||||
}
|
||||
if (this._bodyId === msg.id) {
|
||||
this._postponedBodyMessage = msg;
|
||||
} else {
|
||||
this._insertNode(msg);
|
||||
}
|
||||
this._removeBodyScroll(msg.id);
|
||||
this._removeAutocomplete(msg);
|
||||
break;
|
||||
case "move_node":
|
||||
this._insertNode(msg);
|
||||
break;
|
||||
case "remove_node":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
if (!this._nl[ msg.id ].parentElement) { logger.error("Parent node not found", msg); break; }
|
||||
this._nl[ msg.id ].parentElement.removeChild(this._nl[ msg.id ]);
|
||||
break;
|
||||
case "set_node_attribute":
|
||||
let { id, name, value } = msg;
|
||||
node = this._nl[ id ];
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
if (this._isLink[ id ] && name === "href") {
|
||||
if (value.startsWith(window.ENV.ASSETS_HOST)) { // Hack for queries in rewrited urls
|
||||
value = value.replace("?", "%3F");
|
||||
}
|
||||
this.#stylesManager.setStyleHandlers(node, value);
|
||||
}
|
||||
try {
|
||||
node.setAttribute(name, value);
|
||||
} catch(e) {
|
||||
logger.error(e, msg);
|
||||
}
|
||||
this._removeBodyScroll(msg.id);
|
||||
break;
|
||||
case "remove_node_attribute":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
try {
|
||||
this._nl[ msg.id ].removeAttribute(msg.name);
|
||||
} catch(e) {
|
||||
logger.error(e, msg);
|
||||
}
|
||||
break;
|
||||
case "set_input_value":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
const val = msg.mask > 0 ? '*'.repeat(msg.mask) : msg.value;
|
||||
this._nl[ msg.id ].value = val;
|
||||
break;
|
||||
case "set_input_checked":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
this._nl[ msg.id ].checked = msg.checked;
|
||||
break;
|
||||
case "set_node_data":
|
||||
case "set_css_data":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
this._nl[ msg.id ].data = msg.data;
|
||||
break;
|
||||
case "css_insert_rule":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
if (!(this._nl[ msg.id ] instanceof HTMLStyleElement) // link or null
|
||||
|| this._nl[ msg.id ].sheet == null) {
|
||||
logger.warn("Non-style node in CSS rules message (or sheet is null)", msg);
|
||||
|
||||
// prev version fallback (TODO: delete on 30.10.20)
|
||||
let styleSheet = this.#screen.document.styleSheets[ msg.id ];
|
||||
if (!styleSheet) {
|
||||
styleSheet = this.#screen.document.styleSheets[0];
|
||||
}
|
||||
if (!styleSheet) {
|
||||
logger.log("Old-fasion insert rule: No stylesheet found;", msg);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
styleSheet.insertRule(msg.rule, msg.index);
|
||||
} catch(e) {
|
||||
logger.log("Old-fasion insert rule:", e, msg);
|
||||
styleSheet.insertRule(msg.rule);
|
||||
}
|
||||
//
|
||||
|
||||
break;
|
||||
}
|
||||
try {
|
||||
this._nl[ msg.id ].sheet.insertRule(msg.rule, msg.index)
|
||||
} catch (e) {
|
||||
logger.warn(e, msg)
|
||||
this._nl[ msg.id ].sheet.insertRule(msg.rule)
|
||||
}
|
||||
break;
|
||||
case "css_delete_rule":
|
||||
if (!this._nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
if (!this._nl[ msg.id ] instanceof HTMLStyleElement) { // link or null
|
||||
logger.warn("Non-style node in CSS rules message", msg);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
this._nl[ msg.id ].sheet.deleteRule(msg.rule, msg.index)
|
||||
} catch (e) {
|
||||
logger.warn(e, msg)
|
||||
}
|
||||
break;
|
||||
//not sure what to do with this one
|
||||
//case "disconnected":
|
||||
//setTimeout(() => {
|
||||
// if last one
|
||||
//if (this.msgs[ this.msgs.length - 1 ] === msg) {
|
||||
// this.setDisconnected(true);
|
||||
// }
|
||||
//}, 10000);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
|
||||
moveReady(t: number): Promise<void> {
|
||||
this.moveApply(t, this.#applyMessage); // This function autoresets pointer if necessary (better name?)
|
||||
this.#nodeScrollManagers.forEach(manager => {
|
||||
const msg = manager.moveToLast(t); // TODO: reset (?)
|
||||
if (!!msg && !!this._nl[msg.id]) {
|
||||
this._nl[msg.id].scrollLeft = msg.x;
|
||||
this._nl[msg.id].scrollTop = msg.y;
|
||||
}
|
||||
});
|
||||
|
||||
/* Mount body as late as possible */
|
||||
if (this._postponedBodyMessage != null) {
|
||||
this._insertNode(this._postponedBodyMessage)
|
||||
this._postponedBodyMessage = null;
|
||||
}
|
||||
|
||||
// Thinkabout (read): css preload
|
||||
// What if we go back before it is ready? We'll have two handlres?
|
||||
return this.#stylesManager.moveReady(t);
|
||||
}
|
||||
}
|
||||
277
frontend/app/player/MessageDistributor/managers/DOMManager.ts
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
import type StatedScreen from '../StatedScreen';
|
||||
import type { Message, SetNodeScroll, CreateElementNode } from '../messages';
|
||||
import type { TimedMessage } from '../Timed';
|
||||
|
||||
import logger from 'App/logger';
|
||||
import StylesManager from './StylesManager';
|
||||
import ListWalker from './ListWalker';
|
||||
import type { Timed }from '../Timed';
|
||||
|
||||
const IGNORED_ATTRS = [ "autocomplete", "name" ];
|
||||
|
||||
const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~
|
||||
|
||||
export default class DOMManager extends ListWalker<TimedMessage> {
|
||||
private isMobile: boolean;
|
||||
private screen: StatedScreen;
|
||||
private nl: Array<Node> = [];
|
||||
private isLink: Array<boolean> = []; // Optimisations
|
||||
private bodyId: number = -1;
|
||||
private postponedBodyMessage: CreateElementNode | null = null;
|
||||
private nodeScrollManagers: Array<ListWalker<Timed & SetNodeScroll>> = [];
|
||||
|
||||
private stylesManager: StylesManager;
|
||||
|
||||
private startTime: number;
|
||||
|
||||
constructor(screen: StatedScreen, isMobile: boolean, startTime: number) {
|
||||
super();
|
||||
this.startTime = startTime;
|
||||
this.isMobile = isMobile;
|
||||
this.screen = screen;
|
||||
this.stylesManager = new StylesManager(screen);
|
||||
}
|
||||
|
||||
get time(): number {
|
||||
return this.startTime;
|
||||
}
|
||||
|
||||
add(m: TimedMessage): void {
|
||||
switch (m.tp) {
|
||||
case "set_node_scroll":
|
||||
if (!this.nodeScrollManagers[ m.id ]) {
|
||||
this.nodeScrollManagers[ m.id ] = new ListWalker();
|
||||
}
|
||||
this.nodeScrollManagers[ m.id ].add(m);
|
||||
return;
|
||||
//case "css_insert_rule": // || //set_css_data ???
|
||||
//case "css_delete_rule":
|
||||
// (m.tp === "set_node_attribute" && this.isLink[ m.id ] && m.key === "href")) {
|
||||
// this.stylesManager.add(m);
|
||||
// return;
|
||||
default:
|
||||
if (m.tp === "create_element_node") {
|
||||
switch(m.tag) {
|
||||
case "LINK":
|
||||
this.isLink[ m.id ] = true;
|
||||
break;
|
||||
case "BODY":
|
||||
this.bodyId = m.id; // Can be several body nodes at one document session?
|
||||
break;
|
||||
}
|
||||
} else if (m.tp === "set_node_attribute" &&
|
||||
(IGNORED_ATTRS.includes(m.name) || !ATTR_NAME_REGEXP.test(m.name))) {
|
||||
logger.log("Ignorring message: ", m)
|
||||
return; // Ignoring...
|
||||
}
|
||||
super.add(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private removeBodyScroll(id: number): void {
|
||||
if (this.isMobile && this.bodyId === id) {
|
||||
(this.nl[ id ] as HTMLBodyElement).style.overflow = "hidden";
|
||||
}
|
||||
}
|
||||
|
||||
// May be make it as a message on message add?
|
||||
private removeAutocomplete({ id, tag }: CreateElementNode): boolean {
|
||||
const node = this.nl[ id ] as HTMLElement;
|
||||
if ([ "FORM", "TEXTAREA", "SELECT" ].includes(tag)) {
|
||||
node.setAttribute("autocomplete", "off");
|
||||
return true;
|
||||
}
|
||||
if (tag === "INPUT") {
|
||||
node.setAttribute("autocomplete", "new-password");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// type = NodeMessage ?
|
||||
private insertNode({ parentID, id, index }: { parentID: number, id: number, index: number }): void {
|
||||
if (!this.nl[ id ]) {
|
||||
logger.error("Insert error. Node not found", id);
|
||||
return;
|
||||
}
|
||||
if (!this.nl[ parentID ]) {
|
||||
logger.error("Insert error. Parent node not found", parentID);
|
||||
return;
|
||||
}
|
||||
// WHAT if text info contains some rules and the ordering is just wrong???
|
||||
const el = this.nl[ parentID ]
|
||||
if ((el instanceof HTMLStyleElement) && // TODO: correct ordering OR filter in tracker
|
||||
el.sheet &&
|
||||
el.sheet.cssRules &&
|
||||
el.sheet.cssRules.length > 0) {
|
||||
logger.log("Trying to insert child to style tag with virtual rules: ", this.nl[ parentID ], this.nl[ id ]);
|
||||
return;
|
||||
}
|
||||
|
||||
const childNodes = this.nl[ parentID ].childNodes;
|
||||
if (!childNodes) {
|
||||
logger.error("Node has no childNodes", this.nl[ parentID ]);
|
||||
return;
|
||||
}
|
||||
this.nl[ parentID ]
|
||||
.insertBefore(this.nl[ id ], childNodes[ index ]);
|
||||
}
|
||||
|
||||
private applyMessage = (msg: Message): void => {
|
||||
let node;
|
||||
switch (msg.tp) {
|
||||
case "create_document":
|
||||
// @ts-ignore ??
|
||||
this.screen.document.open();
|
||||
// @ts-ignore ??
|
||||
this.screen.document.write(`${ msg.doctype || "<!DOCTYPE html>" }<html></html>`);
|
||||
// @ts-ignore ??
|
||||
this.screen.document.close();
|
||||
// @ts-ignore ??
|
||||
const fRoot = this.screen.document.documentElement;
|
||||
fRoot.innerText = '';
|
||||
this.nl = [ fRoot ];
|
||||
|
||||
// the last load event I can control
|
||||
//if (this.document.fonts) {
|
||||
// this.document.fonts.onloadingerror = () => this.marker.redraw();
|
||||
// this.document.fonts.onloadingdone = () => this.marker.redraw();
|
||||
//}
|
||||
|
||||
//this.screen.setDisconnected(false);
|
||||
this.stylesManager.reset();
|
||||
break;
|
||||
case "create_text_node":
|
||||
this.nl[ msg.id ] = document.createTextNode('');
|
||||
this.insertNode(msg);
|
||||
break;
|
||||
case "create_element_node":
|
||||
if (msg.svg) {
|
||||
this.nl[ msg.id ] = document.createElementNS('http://www.w3.org/2000/svg', msg.tag);
|
||||
} else {
|
||||
this.nl[ msg.id ] = document.createElement(msg.tag);
|
||||
}
|
||||
if (this.bodyId === msg.id) {
|
||||
this.postponedBodyMessage = msg;
|
||||
} else {
|
||||
this.insertNode(msg);
|
||||
}
|
||||
this.removeBodyScroll(msg.id);
|
||||
this.removeAutocomplete(msg);
|
||||
break;
|
||||
case "move_node":
|
||||
this.insertNode(msg);
|
||||
break;
|
||||
case "remove_node":
|
||||
node = this.nl[ msg.id ]
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
if (!node.parentElement) { logger.error("Parent node not found", msg); break; }
|
||||
node.parentElement.removeChild(node);
|
||||
break;
|
||||
case "set_node_attribute":
|
||||
let { id, name, value } = msg;
|
||||
node = this.nl[ id ];
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
if (this.isLink[ id ] && name === "href") {
|
||||
// @ts-ignore TODO: global ENV type
|
||||
if (value.startsWith(window.ENV.ASSETS_HOST)) { // Hack for queries in rewrited urls
|
||||
value = value.replace("?", "%3F");
|
||||
}
|
||||
this.stylesManager.setStyleHandlers(node, value);
|
||||
}
|
||||
try {
|
||||
node.setAttribute(name, value);
|
||||
} catch(e) {
|
||||
logger.error(e, msg);
|
||||
}
|
||||
this.removeBodyScroll(msg.id);
|
||||
break;
|
||||
case "remove_node_attribute":
|
||||
if (!this.nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
try {
|
||||
(this.nl[ msg.id ] as HTMLElement).removeAttribute(msg.name);
|
||||
} catch(e) {
|
||||
logger.error(e, msg);
|
||||
}
|
||||
break;
|
||||
case "set_input_value":
|
||||
if (!this.nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
const val = msg.mask > 0 ? '*'.repeat(msg.mask) : msg.value;
|
||||
(this.nl[ msg.id ] as HTMLInputElement).value = val;
|
||||
break;
|
||||
case "set_input_checked":
|
||||
node = this.nl[ msg.id ];
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
(node as HTMLInputElement).checked = msg.checked;
|
||||
break;
|
||||
case "set_node_data":
|
||||
case "set_css_data":
|
||||
if (!this.nl[ msg.id ]) { logger.error("Node not found", msg); break; }
|
||||
// @ts-ignore
|
||||
this.nl[ msg.id ].data = msg.data;
|
||||
break;
|
||||
case "css_insert_rule":
|
||||
node = this.nl[ msg.id ];
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
if (!(node instanceof HTMLStyleElement) // link or null
|
||||
|| node.sheet == null) {
|
||||
logger.warn("Non-style node in CSS rules message (or sheet is null)", msg);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
node.sheet.insertRule(msg.rule, msg.index)
|
||||
} catch (e) {
|
||||
logger.warn(e, msg)
|
||||
node.sheet.insertRule(msg.rule)
|
||||
}
|
||||
break;
|
||||
case "css_delete_rule":
|
||||
node = this.nl[ msg.id ];
|
||||
if (!node) { logger.error("Node not found", msg); break; }
|
||||
if (!(node instanceof HTMLStyleElement) // link or null
|
||||
|| node.sheet == null) {
|
||||
logger.warn("Non-style node in CSS rules message (or sheet is null)", msg);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
node.sheet.deleteRule(msg.index)
|
||||
} catch (e) {
|
||||
logger.warn(e, msg)
|
||||
}
|
||||
break;
|
||||
//not sure what to do with this one
|
||||
//case "disconnected":
|
||||
//setTimeout(() => {
|
||||
// if last one
|
||||
//if (this.msgs[ this.msgs.length - 1 ] === msg) {
|
||||
// this.setDisconnected(true);
|
||||
// }
|
||||
//}, 10000);
|
||||
//break;
|
||||
}
|
||||
}
|
||||
|
||||
moveReady(t: number): Promise<void> {
|
||||
this.moveApply(t, this.applyMessage); // This function autoresets pointer if necessary (better name?)
|
||||
this.nodeScrollManagers.forEach(manager => {
|
||||
const msg = manager.moveToLast(t); // TODO: reset (?)
|
||||
|
||||
if (!!msg && !!this.nl[msg.id]) {
|
||||
const node = this.nl[msg.id] as HTMLElement;
|
||||
node.scrollLeft = msg.x;
|
||||
node.scrollTop = msg.y;
|
||||
}
|
||||
});
|
||||
|
||||
/* Mount body as late as possible */
|
||||
if (this.postponedBodyMessage != null) {
|
||||
this.insertNode(this.postponedBodyMessage)
|
||||
this.postponedBodyMessage = null;
|
||||
}
|
||||
|
||||
// Thinkabout (read): css preload
|
||||
// What if we go back before it is ready? We'll have two handlres?
|
||||
return this.stylesManager.moveReady(t);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
//@flow
|
||||
|
||||
import type { Timed } from '../Timed';
|
||||
|
||||
export default class ListWalker<T: Timed> {
|
||||
export default class ListWalker<T extends Timed> {
|
||||
// Optimisation: #prop compiles to method that costs mor than strict property call.
|
||||
_p = 0;
|
||||
_list: Array<T>;
|
||||
|
|
@ -15,7 +13,7 @@ export default class ListWalker<T: Timed> {
|
|||
}
|
||||
|
||||
append(m: T): void {
|
||||
if (this.length > 0 && m.time < this.last.time) {
|
||||
if (this.length > 0 && this.last && m.time < this.last.time) {
|
||||
console.error("Trying to append message with the less time then the list tail: ", m);
|
||||
}
|
||||
this._list.push(m);
|
||||
|
|
@ -26,6 +24,7 @@ export default class ListWalker<T: Timed> {
|
|||
}
|
||||
|
||||
sort(comparator): void {
|
||||
// @ts-ignore
|
||||
this._list.sort((m1,m2) => comparator(m1,m2) || (m1._index - m2._index) ); // indexes for sort stability (TODO: fix types???)
|
||||
}
|
||||
|
||||
|
|
@ -100,10 +99,10 @@ export default class ListWalker<T: Timed> {
|
|||
Assumed that the current message is already handled so
|
||||
if pointer doesn't cahnge <undefined> is returned.
|
||||
*/
|
||||
moveToLast(t: number, index: ?number): ?T {
|
||||
let key = "time"; //TODO
|
||||
moveToLast(t: number, index?: number): T | null {
|
||||
let key: string = "time"; //TODO
|
||||
let val = t;
|
||||
if (index != null) {
|
||||
if (index) {
|
||||
key = "_index";
|
||||
val = index;
|
||||
}
|
||||
|
|
@ -117,7 +116,7 @@ export default class ListWalker<T: Timed> {
|
|||
this._p--;
|
||||
changed = true;
|
||||
}
|
||||
return changed ? this._list[ this._p - 1 ] : undefined;
|
||||
return changed ? this._list[ this._p - 1 ] : null;
|
||||
}
|
||||
|
||||
// moveToLastByIndex(i: number): ?T {
|
||||
|
|
@ -133,7 +132,7 @@ export default class ListWalker<T: Timed> {
|
|||
// return changed ? this._list[ this._p - 1 ] : undefined;
|
||||
// }
|
||||
|
||||
moveApply(t: number, fn: T => void): void {
|
||||
moveApply(t: number, fn: (T) => void): void {
|
||||
// Applying only in increment order for now
|
||||
if (t < this.timeNow) {
|
||||
this.reset();
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
//@flow
|
||||
import type StatedScreen from '../StatedScreen';
|
||||
import type { MouseMove } from '../messages';
|
||||
import type { Timed } from '../Timed';
|
||||
|
|
@ -10,32 +9,30 @@ type MouseMoveTimed = MouseMove & Timed;
|
|||
const HOVER_CLASS = "-openreplay-hover";
|
||||
|
||||
export default class MouseManager extends ListWalker<MouseMoveTimed> {
|
||||
#screen: StatedScreen;
|
||||
#hoverElements: Array<Element> = [];
|
||||
private hoverElements: Array<Element> = [];
|
||||
|
||||
constructor(screen: StatedScreen): void {
|
||||
super();
|
||||
this.#screen = screen;
|
||||
}
|
||||
constructor(private screen: StatedScreen) {super();}
|
||||
|
||||
_updateHover(): void {
|
||||
const curHoverElements = this.#screen.getCursorTargets();
|
||||
const diffAdd = curHoverElements.filter(elem => !this.#hoverElements.includes(elem));
|
||||
const diffRemove = this.#hoverElements.filter(elem => !curHoverElements.includes(elem));
|
||||
this.#hoverElements = curHoverElements;
|
||||
private updateHover(): void {
|
||||
// @ts-ignore TODO
|
||||
const curHoverElements = this.screen.getCursorTargets();
|
||||
const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem));
|
||||
const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem));
|
||||
this.hoverElements = curHoverElements;
|
||||
diffAdd.forEach(elem => elem.classList.add(HOVER_CLASS));
|
||||
diffRemove.forEach(elem => elem.classList.remove(HOVER_CLASS));
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.#hoverElements = [];
|
||||
this.hoverElements = [];
|
||||
}
|
||||
|
||||
move(t: number) {
|
||||
const lastMouseMove = this.moveToLast(t);
|
||||
if (!!lastMouseMove){
|
||||
this.#screen.cursor.move(lastMouseMove);
|
||||
this._updateHover();
|
||||
// @ts-ignore TODO
|
||||
this.screen.cursor.move(lastMouseMove);
|
||||
this.updateHover();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import type { PerformanceTrack, SetPageVisibility } from '../messages';
|
||||
import type { Timed } from '../Timed';
|
||||
|
||||
import ListWalker from './ListWalker';
|
||||
|
||||
type TimedPerformanceTrack = Timed & PerformanceTrack;
|
||||
type TimedSetPageVisibility = Timed & SetPageVisibility;
|
||||
|
||||
type PerformanceChartPoint = {
|
||||
time: number,
|
||||
usedHeap: number,
|
||||
totalHeap: number,
|
||||
fps: ?number,
|
||||
cpu: ?number,
|
||||
nodesCount: number,
|
||||
}
|
||||
|
||||
export default class PerformanceTrackManager extends ListWalker<TimedPerformanceTrack> {
|
||||
#chart: Array<PerformanceChartPoint> = [];
|
||||
#isHidden: boolean = false;
|
||||
#timeCorrection: number = 0;
|
||||
#heapAvaliable: boolean = false;
|
||||
#fpsAvaliable: boolean = false;
|
||||
#cpuAvaliable: boolean = false;
|
||||
#prevTime: ?number = null;
|
||||
#prevNodesCount: number = 0;
|
||||
|
||||
|
||||
add(msg: TimedPerformanceTrack):void {
|
||||
let fps = null;
|
||||
let cpu = null;
|
||||
if (!this.#isHidden && this.#prevTime != null) {
|
||||
let timePassed = msg.time - this.#prevTime + this.#timeCorrection;
|
||||
|
||||
if (timePassed > 0 && msg.frames >= 0) {
|
||||
if (msg.frames > 0) { this.#fpsAvaliable = true; }
|
||||
fps = msg.frames*1e3/timePassed; // Multiply by 1e3 as time in ms;
|
||||
fps = Math.min(fps,60); // What if 120? TODO: alert if more than 60
|
||||
if (this.#chart.length === 1) {
|
||||
this.#chart[0].fps = fps;
|
||||
}
|
||||
}
|
||||
|
||||
if (timePassed > 0 && msg.ticks >= 0) {
|
||||
this.#cpuAvaliable = true;
|
||||
let tickRate = msg.ticks * 30 / timePassed;
|
||||
if (tickRate > 1) {
|
||||
tickRate = 1;
|
||||
}
|
||||
cpu = Math.round(100 - tickRate*100);
|
||||
if (this.#chart.length === 1) {
|
||||
this.#chart[0].cpu = cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.#prevTime = msg.time;
|
||||
this.#timeCorrection = 0
|
||||
|
||||
this.#heapAvaliable = this.#heapAvaliable || msg.usedJSHeapSize > 0;
|
||||
this.#chart.push({
|
||||
usedHeap: msg.usedJSHeapSize,
|
||||
totalHeap: msg.totalJSHeapSize,
|
||||
fps,
|
||||
cpu,
|
||||
time: msg.time,
|
||||
nodesCount: this.#prevNodesCount,
|
||||
});
|
||||
super.add(msg);
|
||||
}
|
||||
|
||||
setCurrentNodesCount(count: number) {
|
||||
this.#prevNodesCount = count;
|
||||
if (this.#chart.length > 0) {
|
||||
this.#chart[ this.#chart.length - 1 ].nodesCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
handleVisibility(msg: TimedSetPageVisibility):void {
|
||||
if (!this.#isHidden && msg.hidden && this.#prevTime != null) {
|
||||
this.#timeCorrection = msg.time - this.#prevTime;
|
||||
}
|
||||
if (this.#isHidden && !msg.hidden) {
|
||||
this.#prevTime = msg.time;
|
||||
}
|
||||
this.#isHidden = msg.hidden;
|
||||
}
|
||||
|
||||
get chartData(): Array<PerformanceChartPoint> {
|
||||
return this.#chart;
|
||||
}
|
||||
|
||||
get avaliability(): { cpu: boolean, fps: boolean, heap: boolean } {
|
||||
return {
|
||||
cpu: this.#cpuAvaliable,
|
||||
fps: this.#fpsAvaliable,
|
||||
heap: this.#heapAvaliable,
|
||||
nodes: true,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import type { PerformanceTrack, SetPageVisibility } from '../messages';
|
||||
import type { Timed } from '../Timed';
|
||||
|
||||
import ListWalker from './ListWalker';
|
||||
|
||||
type TimedPerformanceTrack = Timed & PerformanceTrack;
|
||||
type TimedSetPageVisibility = Timed & SetPageVisibility;
|
||||
|
||||
export type PerformanceChartPoint = {
|
||||
time: number,
|
||||
usedHeap: number,
|
||||
totalHeap: number,
|
||||
fps?: number,
|
||||
cpu?: number,
|
||||
nodesCount: number,
|
||||
}
|
||||
|
||||
export default class PerformanceTrackManager extends ListWalker<TimedPerformanceTrack> {
|
||||
private chart: Array<PerformanceChartPoint> = [];
|
||||
private isHidden: boolean = false;
|
||||
private timeCorrection: number = 0;
|
||||
private heapAvaliable: boolean = false;
|
||||
private fpsAvaliable: boolean = false;
|
||||
private cpuAvaliable: boolean = false;
|
||||
private prevTime: number | null = null;
|
||||
private prevNodesCount: number = 0;
|
||||
|
||||
|
||||
add(msg: TimedPerformanceTrack):void {
|
||||
let fps: number | undefined;
|
||||
let cpu: number | undefined;
|
||||
if (!this.isHidden && this.prevTime != null) {
|
||||
let timePassed = msg.time - this.prevTime + this.timeCorrection;
|
||||
|
||||
if (timePassed > 0 && msg.frames >= 0) {
|
||||
if (msg.frames > 0) { this.fpsAvaliable = true; }
|
||||
fps = msg.frames*1e3/timePassed; // Multiply by 1e3 as time in ms;
|
||||
fps = Math.min(fps,60); // What if 120? TODO: alert if more than 60
|
||||
if (this.chart.length === 1) {
|
||||
this.chart[0].fps = fps;
|
||||
}
|
||||
}
|
||||
|
||||
if (timePassed > 0 && msg.ticks >= 0) {
|
||||
this.cpuAvaliable = true;
|
||||
let tickRate = msg.ticks * 30 / timePassed;
|
||||
if (tickRate > 1) {
|
||||
tickRate = 1;
|
||||
}
|
||||
cpu = Math.round(100 - tickRate*100);
|
||||
if (this.chart.length === 1) {
|
||||
this.chart[0].cpu = cpu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.prevTime = msg.time;
|
||||
this.timeCorrection = 0
|
||||
|
||||
this.heapAvaliable = this.heapAvaliable || msg.usedJSHeapSize > 0;
|
||||
this.chart.push({
|
||||
usedHeap: msg.usedJSHeapSize,
|
||||
totalHeap: msg.totalJSHeapSize,
|
||||
fps,
|
||||
cpu,
|
||||
time: msg.time,
|
||||
nodesCount: this.prevNodesCount,
|
||||
});
|
||||
super.add(msg);
|
||||
}
|
||||
|
||||
setCurrentNodesCount(count: number) {
|
||||
this.prevNodesCount = count;
|
||||
if (this.chart.length > 0) {
|
||||
this.chart[ this.chart.length - 1 ].nodesCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
handleVisibility(msg: TimedSetPageVisibility):void {
|
||||
if (!this.isHidden && msg.hidden && this.prevTime != null) {
|
||||
this.timeCorrection = msg.time - this.prevTime;
|
||||
}
|
||||
if (this.isHidden && !msg.hidden) {
|
||||
this.prevTime = msg.time;
|
||||
}
|
||||
this.isHidden = msg.hidden;
|
||||
}
|
||||
|
||||
get chartData(): Array<PerformanceChartPoint> {
|
||||
return this.chart;
|
||||
}
|
||||
|
||||
get avaliability(): { cpu: boolean, fps: boolean, heap: boolean, nodes: boolean } {
|
||||
return {
|
||||
cpu: this.cpuAvaliable,
|
||||
fps: this.fpsAvaliable,
|
||||
heap: this.heapAvaliable,
|
||||
nodes: true,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,596 +0,0 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import { readUint, readInt, readString, readBoolean } from './readPrimitives'
|
||||
|
||||
|
||||
export type Timestamp = {
|
||||
tp: "timestamp",
|
||||
timestamp: number,
|
||||
}
|
||||
|
||||
export type SessionDisconnect = {
|
||||
tp: "session_disconnect",
|
||||
timestamp: number,
|
||||
}
|
||||
|
||||
export type SetPageLocation = {
|
||||
tp: "set_page_location",
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
}
|
||||
|
||||
export type SetViewportSize = {
|
||||
tp: "set_viewport_size",
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export type SetViewportScroll = {
|
||||
tp: "set_viewport_scroll",
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export type CreateDocument = {
|
||||
tp: "create_document",
|
||||
|
||||
}
|
||||
|
||||
export type CreateElementNode = {
|
||||
tp: "create_element_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
tag: string,
|
||||
svg: boolean,
|
||||
}
|
||||
|
||||
export type CreateTextNode = {
|
||||
tp: "create_text_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export type MoveNode = {
|
||||
tp: "move_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export type RemoveNode = {
|
||||
tp: "remove_node",
|
||||
id: number,
|
||||
}
|
||||
|
||||
export type SetNodeAttribute = {
|
||||
tp: "set_node_attribute",
|
||||
id: number,
|
||||
name: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export type RemoveNodeAttribute = {
|
||||
tp: "remove_node_attribute",
|
||||
id: number,
|
||||
name: string,
|
||||
}
|
||||
|
||||
export type SetNodeData = {
|
||||
tp: "set_node_data",
|
||||
id: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export type SetCssData = {
|
||||
tp: "set_css_data",
|
||||
id: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export type SetNodeScroll = {
|
||||
tp: "set_node_scroll",
|
||||
id: number,
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export type SetInputValue = {
|
||||
tp: "set_input_value",
|
||||
id: number,
|
||||
value: string,
|
||||
mask: number,
|
||||
}
|
||||
|
||||
export type SetInputChecked = {
|
||||
tp: "set_input_checked",
|
||||
id: number,
|
||||
checked: boolean,
|
||||
}
|
||||
|
||||
export type MouseMove = {
|
||||
tp: "mouse_move",
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export type ConsoleLog = {
|
||||
tp: "console_log",
|
||||
level: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export type PerformanceTrack = {
|
||||
tp: "performance_track",
|
||||
frames: number,
|
||||
ticks: number,
|
||||
totalJSHeapSize: number,
|
||||
usedJSHeapSize: number,
|
||||
}
|
||||
|
||||
export type ConnectionInformation = {
|
||||
tp: "connection_information",
|
||||
downlink: number,
|
||||
type: string,
|
||||
}
|
||||
|
||||
export type SetPageVisibility = {
|
||||
tp: "set_page_visibility",
|
||||
hidden: boolean,
|
||||
}
|
||||
|
||||
export type CssInsertRule = {
|
||||
tp: "css_insert_rule",
|
||||
id: number,
|
||||
rule: string,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export type CssDeleteRule = {
|
||||
tp: "css_delete_rule",
|
||||
id: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export type Fetch = {
|
||||
tp: "fetch",
|
||||
method: string,
|
||||
url: string,
|
||||
request: string,
|
||||
response: string,
|
||||
status: number,
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export type Profiler = {
|
||||
tp: "profiler",
|
||||
name: string,
|
||||
duration: number,
|
||||
args: string,
|
||||
result: string,
|
||||
}
|
||||
|
||||
export type OTable = {
|
||||
tp: "o_table",
|
||||
key: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export type Redux = {
|
||||
tp: "redux",
|
||||
action: string,
|
||||
state: string,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export type Vuex = {
|
||||
tp: "vuex",
|
||||
mutation: string,
|
||||
state: string,
|
||||
}
|
||||
|
||||
export type MobX = {
|
||||
tp: "mob_x",
|
||||
type: string,
|
||||
payload: string,
|
||||
}
|
||||
|
||||
export type NgRx = {
|
||||
tp: "ng_rx",
|
||||
action: string,
|
||||
state: string,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export type GraphQl = {
|
||||
tp: "graph_ql",
|
||||
operationKind: string,
|
||||
operationName: string,
|
||||
variables: string,
|
||||
response: string,
|
||||
}
|
||||
|
||||
export type LongTask = {
|
||||
tp: "long_task",
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
context: number,
|
||||
containerType: number,
|
||||
containerSrc: string,
|
||||
containerId: string,
|
||||
containerName: string,
|
||||
}
|
||||
|
||||
export type TechnicalInfo = {
|
||||
tp: "technical_info",
|
||||
type: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export type IosSessionStart = {
|
||||
tp: "ios_session_start",
|
||||
timestamp: number,
|
||||
projectID: number,
|
||||
trackerVersion: string,
|
||||
revID: string,
|
||||
userUUID: string,
|
||||
userOS: string,
|
||||
userOSVersion: string,
|
||||
userDevice: string,
|
||||
userDeviceType: string,
|
||||
userCountry: string,
|
||||
}
|
||||
|
||||
export type IosCustomEvent = {
|
||||
tp: "ios_custom_event",
|
||||
timestamp: number,
|
||||
length: number,
|
||||
name: string,
|
||||
payload: string,
|
||||
}
|
||||
|
||||
export type IosClickEvent = {
|
||||
tp: "ios_click_event",
|
||||
timestamp: number,
|
||||
length: number,
|
||||
label: string,
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export type IosPerformanceEvent = {
|
||||
tp: "ios_performance_event",
|
||||
timestamp: number,
|
||||
length: number,
|
||||
name: string,
|
||||
value: number,
|
||||
}
|
||||
|
||||
export type IosLog = {
|
||||
tp: "ios_log",
|
||||
timestamp: number,
|
||||
length: number,
|
||||
severity: string,
|
||||
content: string,
|
||||
}
|
||||
|
||||
export type IosNetworkCall = {
|
||||
tp: "ios_network_call",
|
||||
timestamp: number,
|
||||
length: number,
|
||||
duration: number,
|
||||
headers: string,
|
||||
body: string,
|
||||
url: string,
|
||||
success: boolean,
|
||||
method: string,
|
||||
status: number,
|
||||
}
|
||||
|
||||
|
||||
export type Message = Timestamp | SessionDisconnect | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetCssData | SetNodeScroll | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | PerformanceTrack | ConnectionInformation | SetPageVisibility | CssInsertRule | CssDeleteRule | Fetch | Profiler | OTable | Redux | Vuex | MobX | NgRx | GraphQl | LongTask | TechnicalInfo | IosSessionStart | IosCustomEvent | IosClickEvent | IosPerformanceEvent | IosLog | IosNetworkCall;
|
||||
|
||||
export default function (buf: Uint8Array, p: number): [Message, number] {
|
||||
const msg = {};
|
||||
let r;
|
||||
switch (buf[p++]) {
|
||||
|
||||
case 0:
|
||||
(msg:Timestamp).tp = "timestamp";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 2:
|
||||
(msg:SessionDisconnect).tp = "session_disconnect";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 4:
|
||||
(msg:SetPageLocation).tp = "set_page_location";
|
||||
r = readString(buf, p); msg.url = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.referrer = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.navigationStart = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 5:
|
||||
(msg:SetViewportSize).tp = "set_viewport_size";
|
||||
r = readUint(buf, p); msg.width = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.height = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 6:
|
||||
(msg:SetViewportScroll).tp = "set_viewport_scroll";
|
||||
r = readInt(buf, p); msg.x = r[0]; p = r[1];
|
||||
r = readInt(buf, p); msg.y = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 7:
|
||||
(msg:CreateDocument).tp = "create_document";
|
||||
|
||||
break;
|
||||
|
||||
case 8:
|
||||
(msg:CreateElementNode).tp = "create_element_node";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.parentID = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.index = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.tag = r[0]; p = r[1];
|
||||
r = readBoolean(buf, p); msg.svg = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 9:
|
||||
(msg:CreateTextNode).tp = "create_text_node";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.parentID = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.index = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 10:
|
||||
(msg:MoveNode).tp = "move_node";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.parentID = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.index = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 11:
|
||||
(msg:RemoveNode).tp = "remove_node";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 12:
|
||||
(msg:SetNodeAttribute).tp = "set_node_attribute";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.name = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.value = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 13:
|
||||
(msg:RemoveNodeAttribute).tp = "remove_node_attribute";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.name = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 14:
|
||||
(msg:SetNodeData).tp = "set_node_data";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.data = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 15:
|
||||
(msg:SetCssData).tp = "set_css_data";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.data = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 16:
|
||||
(msg:SetNodeScroll).tp = "set_node_scroll";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readInt(buf, p); msg.x = r[0]; p = r[1];
|
||||
r = readInt(buf, p); msg.y = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 18:
|
||||
(msg:SetInputValue).tp = "set_input_value";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.value = r[0]; p = r[1];
|
||||
r = readInt(buf, p); msg.mask = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 19:
|
||||
(msg:SetInputChecked).tp = "set_input_checked";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readBoolean(buf, p); msg.checked = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 20:
|
||||
(msg:MouseMove).tp = "mouse_move";
|
||||
r = readUint(buf, p); msg.x = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.y = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 22:
|
||||
(msg:ConsoleLog).tp = "console_log";
|
||||
r = readString(buf, p); msg.level = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.value = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 49:
|
||||
(msg:PerformanceTrack).tp = "performance_track";
|
||||
r = readInt(buf, p); msg.frames = r[0]; p = r[1];
|
||||
r = readInt(buf, p); msg.ticks = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.totalJSHeapSize = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.usedJSHeapSize = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 54:
|
||||
(msg:ConnectionInformation).tp = "connection_information";
|
||||
r = readUint(buf, p); msg.downlink = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.type = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 55:
|
||||
(msg:SetPageVisibility).tp = "set_page_visibility";
|
||||
r = readBoolean(buf, p); msg.hidden = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 37:
|
||||
(msg:CssInsertRule).tp = "css_insert_rule";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.rule = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.index = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 38:
|
||||
(msg:CssDeleteRule).tp = "css_delete_rule";
|
||||
r = readUint(buf, p); msg.id = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.index = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 39:
|
||||
(msg:Fetch).tp = "fetch";
|
||||
r = readString(buf, p); msg.method = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.url = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.request = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.response = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.status = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 40:
|
||||
(msg:Profiler).tp = "profiler";
|
||||
r = readString(buf, p); msg.name = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.args = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.result = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 41:
|
||||
(msg:OTable).tp = "o_table";
|
||||
r = readString(buf, p); msg.key = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.value = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 44:
|
||||
(msg:Redux).tp = "redux";
|
||||
r = readString(buf, p); msg.action = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.state = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 45:
|
||||
(msg:Vuex).tp = "vuex";
|
||||
r = readString(buf, p); msg.mutation = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.state = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 46:
|
||||
(msg:MobX).tp = "mob_x";
|
||||
r = readString(buf, p); msg.type = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.payload = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 47:
|
||||
(msg:NgRx).tp = "ng_rx";
|
||||
r = readString(buf, p); msg.action = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.state = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 48:
|
||||
(msg:GraphQl).tp = "graph_ql";
|
||||
r = readString(buf, p); msg.operationKind = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.operationName = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.variables = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.response = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 59:
|
||||
(msg:LongTask).tp = "long_task";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.context = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.containerType = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.containerSrc = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.containerId = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.containerName = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 63:
|
||||
(msg:TechnicalInfo).tp = "technical_info";
|
||||
r = readString(buf, p); msg.type = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.value = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 90:
|
||||
(msg:IosSessionStart).tp = "ios_session_start";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.projectID = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.trackerVersion = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.revID = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userUUID = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userOS = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userOSVersion = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userDevice = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userDeviceType = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.userCountry = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 93:
|
||||
(msg:IosCustomEvent).tp = "ios_custom_event";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.length = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.name = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.payload = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 100:
|
||||
(msg:IosClickEvent).tp = "ios_click_event";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.length = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.label = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.x = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.y = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 102:
|
||||
(msg:IosPerformanceEvent).tp = "ios_performance_event";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.length = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.name = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.value = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 103:
|
||||
(msg:IosLog).tp = "ios_log";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.length = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.severity = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.content = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
case 105:
|
||||
(msg:IosNetworkCall).tp = "ios_network_call";
|
||||
r = readUint(buf, p); msg.timestamp = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.length = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.duration = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.headers = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.body = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.url = r[0]; p = r[1];
|
||||
r = readBoolean(buf, p); msg.success = r[0]; p = r[1];
|
||||
r = readString(buf, p); msg.method = r[0]; p = r[1];
|
||||
r = readUint(buf, p); msg.status = r[0]; p = r[1];
|
||||
break;
|
||||
|
||||
default:
|
||||
let len;
|
||||
[ _, p ] = readUint(buf, p);
|
||||
[ len, p ] = readUint(buf, p);
|
||||
return [null, p + len] // skip
|
||||
//throw `Unknown type (${buf[p-1]})`;
|
||||
}
|
||||
return [msg, p];
|
||||
}
|
||||
543
frontend/app/player/MessageDistributor/messages.ts
Normal file
|
|
@ -0,0 +1,543 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import PrimitiveReader from './PrimitiveReader';
|
||||
|
||||
export const ID_TP_MAP = {
|
||||
|
||||
0: "timestamp",
|
||||
4: "set_page_location",
|
||||
5: "set_viewport_size",
|
||||
6: "set_viewport_scroll",
|
||||
7: "create_document",
|
||||
8: "create_element_node",
|
||||
9: "create_text_node",
|
||||
10: "move_node",
|
||||
11: "remove_node",
|
||||
12: "set_node_attribute",
|
||||
13: "remove_node_attribute",
|
||||
14: "set_node_data",
|
||||
15: "set_css_data",
|
||||
16: "set_node_scroll",
|
||||
18: "set_input_value",
|
||||
19: "set_input_checked",
|
||||
20: "mouse_move",
|
||||
22: "console_log",
|
||||
37: "css_insert_rule",
|
||||
38: "css_delete_rule",
|
||||
39: "fetch_depricated",
|
||||
40: "profiler",
|
||||
41: "o_table",
|
||||
44: "redux",
|
||||
45: "vuex",
|
||||
46: "mob_x",
|
||||
47: "ng_rx",
|
||||
48: "graph_ql",
|
||||
49: "performance_track",
|
||||
54: "connection_information",
|
||||
55: "set_page_visibility",
|
||||
59: "long_task",
|
||||
68: "fetch",
|
||||
} as const;
|
||||
|
||||
|
||||
export interface Timestamp {
|
||||
tp: "timestamp",
|
||||
timestamp: number,
|
||||
}
|
||||
|
||||
export interface SetPageLocation {
|
||||
tp: "set_page_location",
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
}
|
||||
|
||||
export interface SetViewportSize {
|
||||
tp: "set_viewport_size",
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export interface SetViewportScroll {
|
||||
tp: "set_viewport_scroll",
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export interface CreateDocument {
|
||||
tp: "create_document",
|
||||
|
||||
}
|
||||
|
||||
export interface CreateElementNode {
|
||||
tp: "create_element_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
tag: string,
|
||||
svg: boolean,
|
||||
}
|
||||
|
||||
export interface CreateTextNode {
|
||||
tp: "create_text_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export interface MoveNode {
|
||||
tp: "move_node",
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export interface RemoveNode {
|
||||
tp: "remove_node",
|
||||
id: number,
|
||||
}
|
||||
|
||||
export interface SetNodeAttribute {
|
||||
tp: "set_node_attribute",
|
||||
id: number,
|
||||
name: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface RemoveNodeAttribute {
|
||||
tp: "remove_node_attribute",
|
||||
id: number,
|
||||
name: string,
|
||||
}
|
||||
|
||||
export interface SetNodeData {
|
||||
tp: "set_node_data",
|
||||
id: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export interface SetCssData {
|
||||
tp: "set_css_data",
|
||||
id: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export interface SetNodeScroll {
|
||||
tp: "set_node_scroll",
|
||||
id: number,
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export interface SetInputValue {
|
||||
tp: "set_input_value",
|
||||
id: number,
|
||||
value: string,
|
||||
mask: number,
|
||||
}
|
||||
|
||||
export interface SetInputChecked {
|
||||
tp: "set_input_checked",
|
||||
id: number,
|
||||
checked: boolean,
|
||||
}
|
||||
|
||||
export interface MouseMove {
|
||||
tp: "mouse_move",
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
export interface ConsoleLog {
|
||||
tp: "console_log",
|
||||
level: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface CssInsertRule {
|
||||
tp: "css_insert_rule",
|
||||
id: number,
|
||||
rule: string,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export interface CssDeleteRule {
|
||||
tp: "css_delete_rule",
|
||||
id: number,
|
||||
index: number,
|
||||
}
|
||||
|
||||
export interface FetchDepricated {
|
||||
tp: "fetch_depricated",
|
||||
method: string,
|
||||
url: string,
|
||||
request: string,
|
||||
response: string,
|
||||
status: number,
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export interface Profiler {
|
||||
tp: "profiler",
|
||||
name: string,
|
||||
duration: number,
|
||||
args: string,
|
||||
result: string,
|
||||
}
|
||||
|
||||
export interface OTable {
|
||||
tp: "o_table",
|
||||
key: string,
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface Redux {
|
||||
tp: "redux",
|
||||
action: string,
|
||||
state: string,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export interface Vuex {
|
||||
tp: "vuex",
|
||||
mutation: string,
|
||||
state: string,
|
||||
}
|
||||
|
||||
export interface MobX {
|
||||
tp: "mob_x",
|
||||
type: string,
|
||||
payload: string,
|
||||
}
|
||||
|
||||
export interface NgRx {
|
||||
tp: "ng_rx",
|
||||
action: string,
|
||||
state: string,
|
||||
duration: number,
|
||||
}
|
||||
|
||||
export interface GraphQl {
|
||||
tp: "graph_ql",
|
||||
operationKind: string,
|
||||
operationName: string,
|
||||
variables: string,
|
||||
response: string,
|
||||
}
|
||||
|
||||
export interface PerformanceTrack {
|
||||
tp: "performance_track",
|
||||
frames: number,
|
||||
ticks: number,
|
||||
totalJSHeapSize: number,
|
||||
usedJSHeapSize: number,
|
||||
}
|
||||
|
||||
export interface ConnectionInformation {
|
||||
tp: "connection_information",
|
||||
downlink: number,
|
||||
type: string,
|
||||
}
|
||||
|
||||
export interface SetPageVisibility {
|
||||
tp: "set_page_visibility",
|
||||
hidden: boolean,
|
||||
}
|
||||
|
||||
export interface LongTask {
|
||||
tp: "long_task",
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
context: number,
|
||||
containerType: number,
|
||||
containerSrc: string,
|
||||
containerId: string,
|
||||
containerName: string,
|
||||
}
|
||||
|
||||
export interface Fetch {
|
||||
tp: "fetch",
|
||||
method: string,
|
||||
url: string,
|
||||
request: string,
|
||||
response: string,
|
||||
status: number,
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
headers: string,
|
||||
}
|
||||
|
||||
|
||||
export type Message = Timestamp | SetPageLocation | SetViewportSize | SetViewportScroll | CreateDocument | CreateElementNode | CreateTextNode | MoveNode | RemoveNode | SetNodeAttribute | RemoveNodeAttribute | SetNodeData | SetCssData | SetNodeScroll | SetInputValue | SetInputChecked | MouseMove | ConsoleLog | CssInsertRule | CssDeleteRule | FetchDepricated | Profiler | OTable | Redux | Vuex | MobX | NgRx | GraphQl | PerformanceTrack | ConnectionInformation | SetPageVisibility | LongTask | Fetch;
|
||||
|
||||
export default function (r: PrimitiveReader): Message | null {
|
||||
switch (r.readUint()) {
|
||||
|
||||
case 0:
|
||||
return {
|
||||
tp: ID_TP_MAP[0],
|
||||
timestamp: r.readUint(),
|
||||
};
|
||||
|
||||
case 4:
|
||||
return {
|
||||
tp: ID_TP_MAP[4],
|
||||
url: r.readString(),
|
||||
referrer: r.readString(),
|
||||
navigationStart: r.readUint(),
|
||||
};
|
||||
|
||||
case 5:
|
||||
return {
|
||||
tp: ID_TP_MAP[5],
|
||||
width: r.readUint(),
|
||||
height: r.readUint(),
|
||||
};
|
||||
|
||||
case 6:
|
||||
return {
|
||||
tp: ID_TP_MAP[6],
|
||||
x: r.readInt(),
|
||||
y: r.readInt(),
|
||||
};
|
||||
|
||||
case 7:
|
||||
return {
|
||||
tp: ID_TP_MAP[7],
|
||||
|
||||
};
|
||||
|
||||
case 8:
|
||||
return {
|
||||
tp: ID_TP_MAP[8],
|
||||
id: r.readUint(),
|
||||
parentID: r.readUint(),
|
||||
index: r.readUint(),
|
||||
tag: r.readString(),
|
||||
svg: r.readBoolean(),
|
||||
};
|
||||
|
||||
case 9:
|
||||
return {
|
||||
tp: ID_TP_MAP[9],
|
||||
id: r.readUint(),
|
||||
parentID: r.readUint(),
|
||||
index: r.readUint(),
|
||||
};
|
||||
|
||||
case 10:
|
||||
return {
|
||||
tp: ID_TP_MAP[10],
|
||||
id: r.readUint(),
|
||||
parentID: r.readUint(),
|
||||
index: r.readUint(),
|
||||
};
|
||||
|
||||
case 11:
|
||||
return {
|
||||
tp: ID_TP_MAP[11],
|
||||
id: r.readUint(),
|
||||
};
|
||||
|
||||
case 12:
|
||||
return {
|
||||
tp: ID_TP_MAP[12],
|
||||
id: r.readUint(),
|
||||
name: r.readString(),
|
||||
value: r.readString(),
|
||||
};
|
||||
|
||||
case 13:
|
||||
return {
|
||||
tp: ID_TP_MAP[13],
|
||||
id: r.readUint(),
|
||||
name: r.readString(),
|
||||
};
|
||||
|
||||
case 14:
|
||||
return {
|
||||
tp: ID_TP_MAP[14],
|
||||
id: r.readUint(),
|
||||
data: r.readString(),
|
||||
};
|
||||
|
||||
case 15:
|
||||
return {
|
||||
tp: ID_TP_MAP[15],
|
||||
id: r.readUint(),
|
||||
data: r.readString(),
|
||||
};
|
||||
|
||||
case 16:
|
||||
return {
|
||||
tp: ID_TP_MAP[16],
|
||||
id: r.readUint(),
|
||||
x: r.readInt(),
|
||||
y: r.readInt(),
|
||||
};
|
||||
|
||||
case 18:
|
||||
return {
|
||||
tp: ID_TP_MAP[18],
|
||||
id: r.readUint(),
|
||||
value: r.readString(),
|
||||
mask: r.readInt(),
|
||||
};
|
||||
|
||||
case 19:
|
||||
return {
|
||||
tp: ID_TP_MAP[19],
|
||||
id: r.readUint(),
|
||||
checked: r.readBoolean(),
|
||||
};
|
||||
|
||||
case 20:
|
||||
return {
|
||||
tp: ID_TP_MAP[20],
|
||||
x: r.readUint(),
|
||||
y: r.readUint(),
|
||||
};
|
||||
|
||||
case 22:
|
||||
return {
|
||||
tp: ID_TP_MAP[22],
|
||||
level: r.readString(),
|
||||
value: r.readString(),
|
||||
};
|
||||
|
||||
case 37:
|
||||
return {
|
||||
tp: ID_TP_MAP[37],
|
||||
id: r.readUint(),
|
||||
rule: r.readString(),
|
||||
index: r.readUint(),
|
||||
};
|
||||
|
||||
case 38:
|
||||
return {
|
||||
tp: ID_TP_MAP[38],
|
||||
id: r.readUint(),
|
||||
index: r.readUint(),
|
||||
};
|
||||
|
||||
case 39:
|
||||
return {
|
||||
tp: ID_TP_MAP[39],
|
||||
method: r.readString(),
|
||||
url: r.readString(),
|
||||
request: r.readString(),
|
||||
response: r.readString(),
|
||||
status: r.readUint(),
|
||||
timestamp: r.readUint(),
|
||||
duration: r.readUint(),
|
||||
};
|
||||
|
||||
case 40:
|
||||
return {
|
||||
tp: ID_TP_MAP[40],
|
||||
name: r.readString(),
|
||||
duration: r.readUint(),
|
||||
args: r.readString(),
|
||||
result: r.readString(),
|
||||
};
|
||||
|
||||
case 41:
|
||||
return {
|
||||
tp: ID_TP_MAP[41],
|
||||
key: r.readString(),
|
||||
value: r.readString(),
|
||||
};
|
||||
|
||||
case 44:
|
||||
return {
|
||||
tp: ID_TP_MAP[44],
|
||||
action: r.readString(),
|
||||
state: r.readString(),
|
||||
duration: r.readUint(),
|
||||
};
|
||||
|
||||
case 45:
|
||||
return {
|
||||
tp: ID_TP_MAP[45],
|
||||
mutation: r.readString(),
|
||||
state: r.readString(),
|
||||
};
|
||||
|
||||
case 46:
|
||||
return {
|
||||
tp: ID_TP_MAP[46],
|
||||
type: r.readString(),
|
||||
payload: r.readString(),
|
||||
};
|
||||
|
||||
case 47:
|
||||
return {
|
||||
tp: ID_TP_MAP[47],
|
||||
action: r.readString(),
|
||||
state: r.readString(),
|
||||
duration: r.readUint(),
|
||||
};
|
||||
|
||||
case 48:
|
||||
return {
|
||||
tp: ID_TP_MAP[48],
|
||||
operationKind: r.readString(),
|
||||
operationName: r.readString(),
|
||||
variables: r.readString(),
|
||||
response: r.readString(),
|
||||
};
|
||||
|
||||
case 49:
|
||||
return {
|
||||
tp: ID_TP_MAP[49],
|
||||
frames: r.readInt(),
|
||||
ticks: r.readInt(),
|
||||
totalJSHeapSize: r.readUint(),
|
||||
usedJSHeapSize: r.readUint(),
|
||||
};
|
||||
|
||||
case 54:
|
||||
return {
|
||||
tp: ID_TP_MAP[54],
|
||||
downlink: r.readUint(),
|
||||
type: r.readString(),
|
||||
};
|
||||
|
||||
case 55:
|
||||
return {
|
||||
tp: ID_TP_MAP[55],
|
||||
hidden: r.readBoolean(),
|
||||
};
|
||||
|
||||
case 59:
|
||||
return {
|
||||
tp: ID_TP_MAP[59],
|
||||
timestamp: r.readUint(),
|
||||
duration: r.readUint(),
|
||||
context: r.readUint(),
|
||||
containerType: r.readUint(),
|
||||
containerSrc: r.readString(),
|
||||
containerId: r.readString(),
|
||||
containerName: r.readString(),
|
||||
};
|
||||
|
||||
case 68:
|
||||
return {
|
||||
tp: ID_TP_MAP[68],
|
||||
method: r.readString(),
|
||||
url: r.readString(),
|
||||
request: r.readString(),
|
||||
response: r.readString(),
|
||||
status: r.readUint(),
|
||||
timestamp: r.readUint(),
|
||||
duration: r.readUint(),
|
||||
headers: r.readString(),
|
||||
};
|
||||
|
||||
default:
|
||||
r.readUint(); // IOS skip timestamp
|
||||
r.skip(r.readUint());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
export function readUint(buf, p) {
|
||||
var r = 0, s = 1, b;
|
||||
do {
|
||||
b = buf[p++];
|
||||
r += (b & 0x7F) * s;
|
||||
s *= 128;
|
||||
} while (b >= 0x80)
|
||||
return [r, p];
|
||||
}
|
||||
|
||||
export function readInt(buf, p) {
|
||||
var r = readUint(buf, p);
|
||||
if (r[0] % 2) {
|
||||
r[0] = (r[0] + 1) / -2;
|
||||
} else {
|
||||
r[0] = r[0] / 2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
export function readString(buf, p) {
|
||||
var r = readUint(buf, p);
|
||||
var f = r[1];
|
||||
r[1] += r[0];
|
||||
r[0] = new TextDecoder().decode(buf.subarray(f, r[1]));
|
||||
return r;
|
||||
}
|
||||
|
||||
export function readBoolean(buf, p) {
|
||||
return [!!buf[p], p+1];
|
||||
}
|
||||
|
|
@ -1,35 +1,41 @@
|
|||
import { goTo as listsGoTo } from './lists';
|
||||
import { update, getState } from './store';
|
||||
import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE } from './MessageDistributor';
|
||||
import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './MessageDistributor';
|
||||
|
||||
const fps = 60;
|
||||
const performance = window.performance || { now: Date.now.bind(Date) };
|
||||
const requestAnimationFrame =
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.mozRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.oRequestAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.msRequestAnimationFrame ||
|
||||
(callback => window.setTimeout(() => { callback(performance.now()); }, 1000 / fps));
|
||||
const cancelAnimationFrame =
|
||||
window.cancelAnimationFrame ||
|
||||
// @ts-ignore
|
||||
window.mozCancelAnimationFrame ||
|
||||
window.clearTimeout;
|
||||
|
||||
const HIGHEST_SPEED = 3;
|
||||
const HIGHEST_SPEED = 16;
|
||||
|
||||
|
||||
const SPEED_STORAGE_KEY = "__$player-speed$__";
|
||||
const SKIP_STORAGE_KEY = "__$player-skip$__";
|
||||
const SKIP_TO_ISSUE_STORAGE_KEY = "__$player-skip-to-issue$__";
|
||||
const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__";
|
||||
const storedSpeed = +localStorage.getItem(SPEED_STORAGE_KEY);
|
||||
const initialSpeed = [1,2,3].includes(storedSpeed) ? storedSpeed : 1;
|
||||
const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__";
|
||||
const storedSpeed: number = parseInt(localStorage.getItem(SPEED_STORAGE_KEY) || "") ;
|
||||
const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1;
|
||||
const initialSkip = !!localStorage.getItem(SKIP_STORAGE_KEY);
|
||||
const initialSkipToIssue = !!localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY);
|
||||
const initialAutoplay = !!localStorage.getItem(AUTOPLAY_STORAGE_KEY);
|
||||
const initialShowEvents = !!localStorage.getItem(SHOW_EVENTS_STORAGE_KEY);
|
||||
|
||||
export const INITIAL_STATE = {
|
||||
export const INITIAL_STATE: SuperState = {
|
||||
...SUPER_INITIAL_STATE,
|
||||
time: 0,
|
||||
playing: false,
|
||||
|
|
@ -38,28 +44,30 @@ export const INITIAL_STATE = {
|
|||
inspectorMode: false,
|
||||
live: false,
|
||||
livePlay: false,
|
||||
}
|
||||
} as const;
|
||||
|
||||
|
||||
export const INITIAL_NON_RESETABLE_STATE = {
|
||||
skip: initialSkip,
|
||||
skipToIssue: initialSkipToIssue,
|
||||
autoplay: initialAutoplay,
|
||||
speed: initialSpeed,
|
||||
showEvents: initialShowEvents
|
||||
}
|
||||
|
||||
export default class Player extends MessageDistributor {
|
||||
_animationFrameRequestId = null;
|
||||
private _animationFrameRequestId: number = 0;
|
||||
|
||||
_setTime(time, index) {
|
||||
private _setTime(time: number, index?: number) {
|
||||
update({
|
||||
time,
|
||||
completed: false,
|
||||
});
|
||||
this.move(time, index);
|
||||
super.move(time, index);
|
||||
listsGoTo(time, index);
|
||||
}
|
||||
|
||||
_startAnimation() {
|
||||
private _startAnimation() {
|
||||
let prevTime = getState().time;
|
||||
let animationPrevTime = performance.now();
|
||||
|
||||
|
|
@ -86,10 +94,10 @@ export default class Player extends MessageDistributor {
|
|||
const skipInterval = skip && skipIntervals.find(si => si.contains(time)); // TODO: good skip by messages
|
||||
if (skipInterval) time = skipInterval.end;
|
||||
|
||||
const fmt = this.getFirstMessageTime();
|
||||
const fmt = super.getFirstMessageTime();
|
||||
if (time < fmt) time = fmt; // ?
|
||||
|
||||
const lmt = this.getLastMessageTime();
|
||||
const lmt = super.getLastMessageTime();
|
||||
if (livePlay && time < lmt) time = lmt;
|
||||
if (endTime < lmt) {
|
||||
update({
|
||||
|
|
@ -144,6 +152,9 @@ export default class Player extends MessageDistributor {
|
|||
}
|
||||
|
||||
jump(time = getState().time, index) {
|
||||
const { live } = getState();
|
||||
if (live) return;
|
||||
|
||||
if (getState().playing) {
|
||||
cancelAnimationFrame(this._animationFrameRequestId);
|
||||
// this._animationFrameRequestId = requestAnimationFrame(() => {
|
||||
|
|
@ -161,7 +172,7 @@ export default class Player extends MessageDistributor {
|
|||
|
||||
toggleSkip() {
|
||||
const skip = !getState().skip;
|
||||
localStorage.setItem(SKIP_STORAGE_KEY, skip);
|
||||
localStorage.setItem(SKIP_STORAGE_KEY, `${skip}`);
|
||||
update({ skip });
|
||||
}
|
||||
|
||||
|
|
@ -174,43 +185,49 @@ export default class Player extends MessageDistributor {
|
|||
if (flag) {
|
||||
this.pause();
|
||||
update({ inspectorMode: true });
|
||||
return this.enableInspector(clickCallback);
|
||||
return super.enableInspector(clickCallback);
|
||||
} else {
|
||||
this.disableInspector();
|
||||
super.disableInspector();
|
||||
update({ inspectorMode: false });
|
||||
}
|
||||
}
|
||||
|
||||
toggleSkipToIssue() {
|
||||
const skipToIssue = !getState().skipToIssue;
|
||||
localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, skipToIssue);
|
||||
localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`);
|
||||
update({ skipToIssue });
|
||||
}
|
||||
|
||||
toggleAutoplay() {
|
||||
const autoplay = !getState().autoplay;
|
||||
localStorage.setItem(AUTOPLAY_STORAGE_KEY, autoplay);
|
||||
localStorage.setItem(AUTOPLAY_STORAGE_KEY, `${autoplay}`);
|
||||
update({ autoplay });
|
||||
}
|
||||
|
||||
toggleEvents() {
|
||||
const showEvents = !getState().showEvents;
|
||||
localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`);
|
||||
update({ showEvents });
|
||||
}
|
||||
|
||||
_updateSpeed(speed) {
|
||||
localStorage.setItem(SPEED_STORAGE_KEY, speed);
|
||||
_updateSpeed(speed: number) {
|
||||
localStorage.setItem(SPEED_STORAGE_KEY, `${speed}`);
|
||||
update({ speed });
|
||||
}
|
||||
|
||||
toggleSpeed() {
|
||||
const { speed } = getState();
|
||||
this._updateSpeed(speed < HIGHEST_SPEED ? speed + 1 : 1);
|
||||
this._updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 1);
|
||||
}
|
||||
|
||||
speedUp() {
|
||||
const { speed } = getState();
|
||||
this._updateSpeed(Math.min(HIGHEST_SPEED, speed + 1));
|
||||
this._updateSpeed(Math.min(HIGHEST_SPEED, speed * 2));
|
||||
}
|
||||
|
||||
speedDown() {
|
||||
const { speed } = getState();
|
||||
this._updateSpeed(Math.max(1, speed - 1));
|
||||
this._updateSpeed(Math.max(1, speed/2));
|
||||
}
|
||||
|
||||
clean() {
|
||||
|
|
@ -61,12 +61,14 @@ export const toggleSkip = initCheck((...args) => instance.toggleSkip(...args));
|
|||
export const toggleSkipToIssue = initCheck((...args) => instance.toggleSkipToIssue(...args));
|
||||
export const toggleAutoplay = initCheck((...args) => instance.toggleAutoplay(...args));
|
||||
export const toggleSpeed = initCheck((...args) => instance.toggleSpeed(...args));
|
||||
export const toggleEvents = initCheck((...args) => instance.toggleEvents(...args));
|
||||
export const speedUp = initCheck((...args) => instance.speedUp(...args));
|
||||
export const speedDown = initCheck((...args) => instance.speedDown(...args));
|
||||
export const attach = initCheck((...args) => instance.attach(...args));
|
||||
export const markElement = initCheck((...args) => instance.marker && instance.marker.mark(...args));
|
||||
export const scale = initCheck(() => instance.scale());
|
||||
export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args));
|
||||
export const callPeer = initCheck((...args) => instance.assistManager.call(...args))
|
||||
|
||||
export const Controls = {
|
||||
jump,
|
||||
|
|
@ -75,7 +77,9 @@ export const Controls = {
|
|||
toggleSkip,
|
||||
toggleSkipToIssue,
|
||||
toggleAutoplay,
|
||||
toggleEvents,
|
||||
toggleSpeed,
|
||||
speedUp,
|
||||
speedDown,
|
||||
callPeer
|
||||
}
|
||||
|
|
|
|||
3
frontend/app/svg/icons/camera-video-off.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-camera-video-off" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M10.961 12.365a1.99 1.99 0 0 0 .522-1.103l3.11 1.382A1 1 0 0 0 16 11.731V4.269a1 1 0 0 0-1.406-.913l-3.111 1.382A2 2 0 0 0 9.5 3H4.272l.714 1H9.5a1 1 0 0 1 1 1v6a1 1 0 0 1-.144.518l.605.847zM1.428 4.18A.999.999 0 0 0 1 5v6a1 1 0 0 0 1 1h5.014l.714 1H2a2 2 0 0 1-2-2V5c0-.675.334-1.272.847-1.634l.58.814zM15 11.73l-3.5-1.555v-4.35L15 4.269v7.462zm-4.407 3.56-10-14 .814-.58 10 14-.814.58z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 520 B |
3
frontend/app/svg/icons/camera-video.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-camera-video" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M0 5a2 2 0 0 1 2-2h7.5a2 2 0 0 1 1.983 1.738l3.11-1.382A1 1 0 0 1 16 4.269v7.462a1 1 0 0 1-1.406.913l-3.111-1.382A2 2 0 0 1 9.5 13H2a2 2 0 0 1-2-2V5zm11.5 5.175 3.5 1.556V4.269l-3.5 1.556v4.35zM2 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h7.5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 393 B |
4
frontend/app/svg/icons/chevron-double-left.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-double-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8.354 1.646a.5.5 0 0 1 0 .708L2.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||
<path fill-rule="evenodd" d="M12.354 1.646a.5.5 0 0 1 0 .708L6.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 447 B |
4
frontend/app/svg/icons/chevron-double-right.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-double-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M3.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L9.293 8 3.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||
<path fill-rule="evenodd" d="M7.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L13.293 8 7.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
4
frontend/app/svg/icons/controller.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-controller" viewBox="0 0 16 16">
|
||||
<path d="M11.5 6.027a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-1.5 1.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zm2.5-.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm-1.5 1.5a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zm-6.5-3h1v1h1v1h-1v1h-1v-1h-1v-1h1v-1z"/>
|
||||
<path d="M3.051 3.26a.5.5 0 0 1 .354-.613l1.932-.518a.5.5 0 0 1 .62.39c.655-.079 1.35-.117 2.043-.117.72 0 1.443.041 2.12.126a.5.5 0 0 1 .622-.399l1.932.518a.5.5 0 0 1 .306.729c.14.09.266.19.373.297.408.408.78 1.05 1.095 1.772.32.733.599 1.591.805 2.466.206.875.34 1.78.364 2.606.024.816-.059 1.602-.328 2.21a1.42 1.42 0 0 1-1.445.83c-.636-.067-1.115-.394-1.513-.773-.245-.232-.496-.526-.739-.808-.126-.148-.25-.292-.368-.423-.728-.804-1.597-1.527-3.224-1.527-1.627 0-2.496.723-3.224 1.527-.119.131-.242.275-.368.423-.243.282-.494.575-.739.808-.398.38-.877.706-1.513.773a1.42 1.42 0 0 1-1.445-.83c-.27-.608-.352-1.395-.329-2.21.024-.826.16-1.73.365-2.606.206-.875.486-1.733.805-2.466.315-.722.687-1.364 1.094-1.772a2.34 2.34 0 0 1 .433-.335.504.504 0 0 1-.028-.079zm2.036.412c-.877.185-1.469.443-1.733.708-.276.276-.587.783-.885 1.465a13.748 13.748 0 0 0-.748 2.295 12.351 12.351 0 0 0-.339 2.406c-.022.755.062 1.368.243 1.776a.42.42 0 0 0 .426.24c.327-.034.61-.199.929-.502.212-.202.4-.423.615-.674.133-.156.276-.323.44-.504C4.861 9.969 5.978 9.027 8 9.027s3.139.942 3.965 1.855c.164.181.307.348.44.504.214.251.403.472.615.674.318.303.601.468.929.503a.42.42 0 0 0 .426-.241c.18-.408.265-1.02.243-1.776a12.354 12.354 0 0 0-.339-2.406 13.753 13.753 0 0 0-.748-2.295c-.298-.682-.61-1.19-.885-1.465-.264-.265-.856-.523-1.733-.708-.85-.179-1.877-.27-2.913-.27-1.036 0-2.063.091-2.913.27z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
3
frontend/app/svg/icons/headset.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-headset" viewBox="0 0 16 16">
|
||||
<path d="M8 1a5 5 0 0 0-5 5v1h1a1 1 0 0 1 1 1v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V6a6 6 0 1 1 12 0v6a2.5 2.5 0 0 1-2.5 2.5H9.366a1 1 0 0 1-.866.5h-1a1 1 0 1 1 0-2h1a1 1 0 0 1 .866.5H11.5A1.5 1.5 0 0 0 13 12h-1a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1h1V6a5 5 0 0 0-5-5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
4
frontend/app/svg/icons/mic-mute.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-mic-mute" viewBox="0 0 16 16">
|
||||
<path d="M13 8c0 .564-.094 1.107-.266 1.613l-.814-.814A4.02 4.02 0 0 0 12 8V7a.5.5 0 0 1 1 0v1zm-5 4c.818 0 1.578-.245 2.212-.667l.718.719a4.973 4.973 0 0 1-2.43.923V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 1 0v1a4 4 0 0 0 4 4zm3-9v4.879l-1-1V3a2 2 0 0 0-3.997-.118l-.845-.845A3.001 3.001 0 0 1 11 3z"/>
|
||||
<path d="m9.486 10.607-.748-.748A2 2 0 0 1 6 8v-.878l-1-1V8a3 3 0 0 0 4.486 2.607zm-7.84-9.253 12 12 .708-.708-12-12-.708.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 561 B |
4
frontend/app/svg/icons/mic.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-mic" viewBox="0 0 16 16">
|
||||
<path d="M3.5 6.5A.5.5 0 0 1 4 7v1a4 4 0 0 0 8 0V7a.5.5 0 0 1 1 0v1a5 5 0 0 1-4.5 4.975V15h3a.5.5 0 0 1 0 1h-7a.5.5 0 0 1 0-1h3v-2.025A5 5 0 0 1 3 8V7a.5.5 0 0 1 .5-.5z"/>
|
||||
<path d="M10 8a2 2 0 1 1-4 0V3a2 2 0 1 1 4 0v5zM8 0a3 3 0 0 0-3 3v5a3 3 0 0 0 6 0V3a3 3 0 0 0-3-3z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 363 B |
3
frontend/app/svg/icons/person.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-person" viewBox="0 0 16 16">
|
||||
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0zm4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4zm-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10c-2.29 0-3.516.68-4.168 1.332-.678.678-.83 1.418-.832 1.664h10z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
3
frontend/app/svg/icons/telephone-fill.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-telephone-fill" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.885.511a1.745 1.745 0 0 1 2.61.163L6.29 2.98c.329.423.445.974.315 1.494l-.547 2.19a.678.678 0 0 0 .178.643l2.457 2.457a.678.678 0 0 0 .644.178l2.189-.547a1.745 1.745 0 0 1 1.494.315l2.306 1.794c.829.645.905 1.87.163 2.611l-1.034 1.034c-.74.74-1.846 1.065-2.877.702a18.634 18.634 0 0 1-7.01-4.42 18.634 18.634 0 0 1-4.42-7.009c-.362-1.03-.037-2.137.703-2.877L1.885.511z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 502 B |
3
frontend/app/svg/icons/telephone.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-telephone" viewBox="0 0 16 16">
|
||||
<path d="M3.654 1.328a.678.678 0 0 0-1.015-.063L1.605 2.3c-.483.484-.661 1.169-.45 1.77a17.568 17.568 0 0 0 4.168 6.608 17.569 17.569 0 0 0 6.608 4.168c.601.211 1.286.033 1.77-.45l1.034-1.034a.678.678 0 0 0-.063-1.015l-2.307-1.794a.678.678 0 0 0-.58-.122l-2.19.547a1.745 1.745 0 0 1-1.657-.459L5.482 8.062a1.745 1.745 0 0 1-.46-1.657l.548-2.19a.678.678 0 0 0-.122-.58L3.654 1.328zM1.884.511a1.745 1.745 0 0 1 2.612.163L6.29 2.98c.329.423.445.974.315 1.494l-.547 2.19a.678.678 0 0 0 .178.643l2.457 2.457a.678.678 0 0 0 .644.178l2.189-.547a1.745 1.745 0 0 1 1.494.315l2.306 1.794c.829.645.905 1.87.163 2.611l-1.034 1.034c-.74.74-1.846 1.065-2.877.702a18.634 18.634 0 0 1-7.01-4.42 18.634 18.634 0 0 1-4.42-7.009c-.362-1.03-.037-2.137.703-2.877L1.885.511z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 849 B |
|
|
@ -26,6 +26,8 @@ function hashString(s: string): number {
|
|||
export default Record({
|
||||
sessionId: '',
|
||||
siteId: '',
|
||||
projectKey: '',
|
||||
peerId: '',
|
||||
live: false,
|
||||
startedAt: 0,
|
||||
duration: 0,
|
||||
|
|
|
|||
50
frontend/package-lock.json
generated
|
|
@ -16084,6 +16084,34 @@
|
|||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"peerjs": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.3.2.tgz",
|
||||
"integrity": "sha512-+PHfmsC7QGUU8Ye3OLi6tKQZGPCNy7QatUVNw4JtE8alkguF3+DdO5W0bzepqP2OtE9FqH/ltXt37qyvHw2CqA==",
|
||||
"requires": {
|
||||
"@types/node": "^10.14.33",
|
||||
"eventemitter3": "^3.1.2",
|
||||
"peerjs-js-binarypack": "1.0.1",
|
||||
"webrtc-adapter": "^7.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.17.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
|
||||
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"peerjs-js-binarypack": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz",
|
||||
"integrity": "sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg=="
|
||||
},
|
||||
"performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
|
|
@ -21690,6 +21718,14 @@
|
|||
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
|
||||
"dev": true
|
||||
},
|
||||
"rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"requires": {
|
||||
"sdp": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -21871,6 +21907,11 @@
|
|||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
|
|
@ -25871,6 +25912,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"webrtc-adapter": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz",
|
||||
"integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==",
|
||||
"requires": {
|
||||
"rtcpeerconnection-shim": "^1.2.15",
|
||||
"sdp": "^2.12.0"
|
||||
}
|
||||
},
|
||||
"websocket-driver": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
"mobx-react-lite": "^3.1.6",
|
||||
"moment": "^2.27.0",
|
||||
"moment-range": "^4.0.2",
|
||||
"peerjs": "^1.3.2",
|
||||
"rc-time-picker": "^3.7.3",
|
||||
"react": "^16.13.1",
|
||||
"react-circular-progressbar": "^2.0.3",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const colors = require('./app/theme/colors');
|
||||
|
||||
module.exports = {
|
||||
important: true,
|
||||
purge: [],
|
||||
corePlugins: [
|
||||
'preflight',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node", //?
|
||||
//"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
//"sourceMap": false,
|
||||
"lib": [ "es2020", "dom" ],
|
||||
"jsx": "react",
|
||||
|
|
@ -14,7 +15,16 @@
|
|||
"baseUrl": ".",
|
||||
"paths": { // TODO: one-source truth
|
||||
"App": ["./app"],
|
||||
"UI": ["./app/components/ui"]
|
||||
"App/*": ["./app/*"],
|
||||
"Types": ["./app/types" ],
|
||||
"Types/*": ["./app/types/*"], // Sublime hack
|
||||
"UI": ["./app/components/ui"],
|
||||
"Duck": ["./app/duck"],
|
||||
"Duck/*": ["./app/duck/*"],
|
||||
"Shared": ["./app/components/shared"],
|
||||
"Shared/*": ["./app/components/shared/*"],
|
||||
"Player": ["./app/player"],
|
||||
"Player/*": ["./app/player/*"],
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
|
|
|
|||
114
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"name": "openreplay",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"react-draggable": "^4.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-draggable": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
|
||||
"integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.8.1"
|
||||
}
|
||||
},
|
||||
"react-draggable": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.3.tgz",
|
||||
"integrity": "sha512-jV4TE59MBuWm7gb6Ns3Q1mxX8Azffb7oTtDtBgFkxRvhDp38YAARmRplrj0+XGkhOJB5XziArX+4HUUABtyZ0w==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.5",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
5
package.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"react-draggable": "^4.4.3"
|
||||
}
|
||||
}
|
||||
6
tracker/tracker-assist/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
node_modules
|
||||
npm-debug.log
|
||||
lib
|
||||
cjs
|
||||
.cache
|
||||
*.DS_Store
|
||||
5
tracker/tracker-assist/.npmignore
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
src
|
||||
tsconfig-cjs.json
|
||||
tsconfig.json
|
||||
.prettierrc.json
|
||||
.cache
|
||||
19
tracker/tracker-assist/LICENSE
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2021 OpenReplay.com <support@openreplay.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
0
tracker/tracker-assist/README.md
Normal file
861
tracker/tracker-assist/package-lock.json
generated
Normal file
|
|
@ -0,0 +1,861 @@
|
|||
{
|
||||
"name": "@openreplay/tracker-assist",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.14.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz",
|
||||
"integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
|
||||
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.5",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
"run-parallel": "^1.1.9"
|
||||
}
|
||||
},
|
||||
"@nodelib/fs.stat": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true
|
||||
},
|
||||
"@nodelib/fs.walk": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz",
|
||||
"integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@openreplay/tracker": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@openreplay/tracker/-/tracker-3.0.5.tgz",
|
||||
"integrity": "sha512-hIY7DnQmm7bCe6v+e257WD7OdNuBOWUZ15Q3yUEdyxu7xDNG7brbak9pS97qCt3VY9xGK0RvW/j3ANlRPk8aVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"error-stack-parser": "^2.0.6"
|
||||
}
|
||||
},
|
||||
"@types/minimist": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz",
|
||||
"integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "10.17.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
|
||||
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
|
||||
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"array-union": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
|
||||
"dev": true
|
||||
},
|
||||
"arrify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
|
||||
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase-keys": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
|
||||
"integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.3.1",
|
||||
"map-obj": "^4.0.0",
|
||||
"quick-lru": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
|
||||
"dev": true
|
||||
},
|
||||
"decamelize-keys": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz",
|
||||
"integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"decamelize": "^1.1.0",
|
||||
"map-obj": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"map-obj": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
|
||||
"integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-type": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
"integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"error-stack-parser": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz",
|
||||
"integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"stackframe": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"dev": true
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
|
||||
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
|
||||
"integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"glob-parent": "^5.1.0",
|
||||
"merge2": "^1.3.0",
|
||||
"micromatch": "^4.0.2",
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"fastq": {
|
||||
"version": "1.11.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz",
|
||||
"integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"globby": {
|
||||
"version": "11.0.4",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz",
|
||||
"integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-union": "^2.1.0",
|
||||
"dir-glob": "^3.0.1",
|
||||
"fast-glob": "^3.1.1",
|
||||
"ignore": "^5.1.4",
|
||||
"merge2": "^1.3.0",
|
||||
"slash": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"hard-rejection": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
|
||||
"integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
|
||||
"dev": true
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
|
||||
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
|
||||
"dev": true
|
||||
},
|
||||
"indent-string": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
|
||||
"dev": true
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
|
||||
"integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||
"dev": true
|
||||
},
|
||||
"is-typedarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
|
||||
"dev": true
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
|
||||
"dev": true
|
||||
},
|
||||
"lines-and-columns": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
|
||||
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
|
||||
"dev": true
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"map-obj": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz",
|
||||
"integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"meow": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-7.1.1.tgz",
|
||||
"integrity": "sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/minimist": "^1.2.0",
|
||||
"camelcase-keys": "^6.2.2",
|
||||
"decamelize-keys": "^1.1.0",
|
||||
"hard-rejection": "^2.1.0",
|
||||
"minimist-options": "4.1.0",
|
||||
"normalize-package-data": "^2.5.0",
|
||||
"read-pkg-up": "^7.0.1",
|
||||
"redent": "^3.0.0",
|
||||
"trim-newlines": "^3.0.0",
|
||||
"type-fest": "^0.13.1",
|
||||
"yargs-parser": "^18.1.3"
|
||||
}
|
||||
},
|
||||
"merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"braces": "^3.0.1",
|
||||
"picomatch": "^2.2.3"
|
||||
}
|
||||
},
|
||||
"min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist-options": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
|
||||
"integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arrify": "^1.0.1",
|
||||
"is-plain-obj": "^1.1.0",
|
||||
"kind-of": "^6.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"resolve": "^1.10.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"npm-dragndrop": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-dragndrop/-/npm-dragndrop-1.2.0.tgz",
|
||||
"integrity": "sha1-bgUkAP7Yay8eP0csU4EPkjcRu7U="
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-try": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"dev": true
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
}
|
||||
},
|
||||
"path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-type": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
|
||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||
"dev": true
|
||||
},
|
||||
"peerjs": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.3.2.tgz",
|
||||
"integrity": "sha512-+PHfmsC7QGUU8Ye3OLi6tKQZGPCNy7QatUVNw4JtE8alkguF3+DdO5W0bzepqP2OtE9FqH/ltXt37qyvHw2CqA==",
|
||||
"requires": {
|
||||
"@types/node": "^10.14.33",
|
||||
"eventemitter3": "^3.1.2",
|
||||
"peerjs-js-binarypack": "1.0.1",
|
||||
"webrtc-adapter": "^7.7.1"
|
||||
}
|
||||
},
|
||||
"peerjs-js-binarypack": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-1.0.1.tgz",
|
||||
"integrity": "sha512-N6aeia3NhdpV7kiGxJV5xQiZZCVEEVjRz2T2C6UZQiBkHWHzUv/oWA4myQLcwBwO8LUoR1KWW5oStvwVesmfCg=="
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
|
||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
|
||||
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
|
||||
"dev": true
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true
|
||||
},
|
||||
"quick-lru": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
|
||||
"integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
|
||||
"dev": true
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
|
||||
"integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/normalize-package-data": "^2.4.0",
|
||||
"normalize-package-data": "^2.5.0",
|
||||
"parse-json": "^5.0.0",
|
||||
"type-fest": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"type-fest": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
|
||||
"integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-pkg-up": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
|
||||
"integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-up": "^4.1.0",
|
||||
"read-pkg": "^5.2.0",
|
||||
"type-fest": "^0.8.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"type-fest": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"redent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"indent-string": "^4.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"replace-in-files-cli": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/replace-in-files-cli/-/replace-in-files-cli-1.0.0.tgz",
|
||||
"integrity": "sha512-/HMPLZeCA24CBUQ59ymHji6LyMKM+gEgDZlYsiPvXW6+3PdfOw6SsMCVd9KC2B+KlAEe/8vkJA6gfnexVdF15A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arrify": "^2.0.1",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"globby": "^11.0.1",
|
||||
"meow": "^7.1.1",
|
||||
"normalize-path": "^3.0.0",
|
||||
"write-file-atomic": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-core-module": "^2.2.0",
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"reusify": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||
"dev": true
|
||||
},
|
||||
"rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"requires": {
|
||||
"sdp": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
||||
"dev": true
|
||||
},
|
||||
"slash": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-correct": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
|
||||
"integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"spdx-exceptions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
|
||||
"integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
|
||||
"dev": true
|
||||
},
|
||||
"spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-exceptions": "^2.1.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"spdx-license-ids": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz",
|
||||
"integrity": "sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"stackframe": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.2.0.tgz",
|
||||
"integrity": "sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"min-indent": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"trim-newlines": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
|
||||
"integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
|
||||
"dev": true
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
|
||||
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
|
||||
"dev": true
|
||||
},
|
||||
"typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.10",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz",
|
||||
"integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"webrtc-adapter": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz",
|
||||
"integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==",
|
||||
"requires": {
|
||||
"rtcpeerconnection-shim": "^1.2.15",
|
||||
"sdp": "^2.12.0"
|
||||
}
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
|
||||
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-typedarray": "^1.0.0",
|
||||
"signal-exit": "^3.0.2",
|
||||
"typedarray-to-buffer": "^3.1.5"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
tracker/tracker-assist/package.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "@openreplay/tracker-assist",
|
||||
"description": "Tracker plugin for screen assistance through the WebRTC",
|
||||
"version": "3.0.0",
|
||||
"keywords": [
|
||||
"WebRTC",
|
||||
"assistance",
|
||||
"logging",
|
||||
"replay"
|
||||
],
|
||||
"author": "Aleksandr K <alex@openreplay.com>",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "./lib/index.js",
|
||||
"scripts": {
|
||||
"lint": "prettier --write 'src/**/*.ts' README.md && tsc --noEmit",
|
||||
"build": "npm run build-es && npm run build-cjs",
|
||||
"build-es": "rm -Rf lib && tsc",
|
||||
"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' && replace-in-files cjs/* --string='/lib/' --replacement='/'",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"npm-dragndrop": "^1.2.0",
|
||||
"peerjs": "^1.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@openreplay/tracker": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openreplay/tracker": "^3.0.5",
|
||||
"prettier": "^1.18.2",
|
||||
"replace-in-files-cli": "^1.0.0",
|
||||
"typescript": "^3.6.4"
|
||||
}
|
||||
}
|
||||
153
tracker/tracker-assist/src/CallWindow.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
|
||||
|
||||
export default class CallWindow {
|
||||
private iframe: HTMLIFrameElement;
|
||||
private vRemote: HTMLVideoElement | null = null;
|
||||
private vLocal: HTMLVideoElement | null = null;
|
||||
private audioBtn: HTMLAnchorElement | null = null;
|
||||
private videoBtn: HTMLAnchorElement | null = null;
|
||||
|
||||
private tsInterval: ReturnType<typeof setInterval>;
|
||||
constructor(endCall: () => void) {
|
||||
const iframe = this.iframe = document.createElement('iframe');
|
||||
Object.assign(iframe.style, {
|
||||
position: "absolute",
|
||||
zIndex: 2147483647 - 1,
|
||||
//borderRadius: ".25em .25em .4em .4em",
|
||||
//border: "4px rgba(0, 0, 0, .7)",
|
||||
border: "none",
|
||||
bottom: "10px",
|
||||
right: "10px",
|
||||
});
|
||||
//iframe.src = "//static.openreplay.com/tracker-assist/index.html";
|
||||
iframe.onload = () => {
|
||||
const doc = iframe.contentDocument;
|
||||
if (!doc) {
|
||||
console.error("OpenReplay: CallWindow iframe document is not reachable.")
|
||||
return;
|
||||
}
|
||||
fetch("https://static.openreplay.com/tracker-assist/index.html")
|
||||
//fetch("file:///Users/shikhu/work/asayer-tester/dist/assist/index.html")
|
||||
.then(r => r.text())
|
||||
.then((text) => {
|
||||
iframe.onload = () => {
|
||||
iframe.style.height = doc.body.scrollHeight + 'px';
|
||||
iframe.style.width = doc.body.scrollWidth + 'px';
|
||||
}
|
||||
|
||||
text = text.replace(/href="css/g, "href=\"https://static.openreplay.com/tracker-assist/css")
|
||||
doc.open();
|
||||
doc.write(text);
|
||||
doc.close();
|
||||
|
||||
|
||||
this.vLocal = doc.getElementById("video-local") as HTMLVideoElement;
|
||||
this.vRemote = doc.getElementById("video-remote") as HTMLVideoElement;
|
||||
this._trySetStreams();
|
||||
this.vLocal.parentElement && this.vLocal.parentElement.classList.add("d-none");
|
||||
|
||||
this.audioBtn = doc.getElementById("audio-btn") as HTMLAnchorElement;
|
||||
this.audioBtn.onclick = () => this.toggleAudio();
|
||||
this.videoBtn = doc.getElementById("video-btn") as HTMLAnchorElement;
|
||||
this.videoBtn.onclick = () => this.toggleVideo();
|
||||
|
||||
const endCallBtn = doc.getElementById("end-call-btn") as HTMLAnchorElement;
|
||||
endCallBtn.onclick = endCall;
|
||||
|
||||
const tsText = doc.getElementById("time-stamp");
|
||||
const startTs = Date.now();
|
||||
if (tsText) {
|
||||
this.tsInterval = setInterval(() => {
|
||||
const ellapsed = Date.now() - startTs;
|
||||
const secsFull = ~~(ellapsed / 1000);
|
||||
const mins = ~~(secsFull / 60);
|
||||
const secs = secsFull - mins * 60
|
||||
tsText.innerText = `${mins}:${secs < 10 ? 0 : ''}${secs}`;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// TODO: better D'n'D
|
||||
doc.body.setAttribute("draggable", "true");
|
||||
doc.body.ondragstart = (e) => {
|
||||
if (!e.dataTransfer || !e.target) { return; }
|
||||
e.dataTransfer.setDragImage(doc.body, e.clientX, e.clientY);
|
||||
};
|
||||
doc.body.ondragend = e => {
|
||||
Object.assign(iframe.style, {
|
||||
left: `${e.clientX}px`,
|
||||
top: `${e.clientY}px`,
|
||||
bottom: 'auto',
|
||||
right: 'auto',
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
}
|
||||
|
||||
private localStream: MediaStream | null = null;
|
||||
private remoteStream: MediaStream | null = null;
|
||||
private _trySetStreams() {
|
||||
if (this.vRemote && this.remoteStream) {
|
||||
this.vRemote.srcObject = this.remoteStream;
|
||||
}
|
||||
if (this.vLocal && this.localStream) {
|
||||
this.vLocal.srcObject = this.localStream;
|
||||
}
|
||||
}
|
||||
setRemoteStream(rStream: MediaStream) {
|
||||
this.remoteStream = rStream;
|
||||
this._trySetStreams();
|
||||
}
|
||||
setLocalStream(lStream: MediaStream) {
|
||||
this.localStream = lStream;
|
||||
lStream.getVideoTracks().forEach(track => {
|
||||
track.enabled = false;
|
||||
});
|
||||
this._trySetStreams();
|
||||
}
|
||||
|
||||
toggleAudio() {
|
||||
let enabled = true;
|
||||
this.localStream?.getAudioTracks().forEach(track => {
|
||||
enabled = enabled && !track.enabled;
|
||||
track.enabled = enabled;
|
||||
});
|
||||
const cList = this.audioBtn?.classList;
|
||||
if (!this.audioBtn) { return; }
|
||||
if (enabled) {
|
||||
this.audioBtn.classList.remove("muted");
|
||||
this.audioBtn.childNodes[1].textContent = "Mute";
|
||||
} else {
|
||||
this.audioBtn.classList.add("muted");
|
||||
this.audioBtn.childNodes[1].textContent = "Unmute";
|
||||
}
|
||||
}
|
||||
toggleVideo() {
|
||||
let enabled = true;
|
||||
this.localStream?.getVideoTracks().forEach(track => {
|
||||
enabled = enabled && !track.enabled;
|
||||
track.enabled = enabled;
|
||||
});
|
||||
if (!this.videoBtn || !this.vLocal || !this.vLocal.parentElement) { return; }
|
||||
if (enabled) {
|
||||
this.vLocal.parentElement.classList.remove("d-none");
|
||||
this.videoBtn.classList.remove("off");
|
||||
this.videoBtn.childNodes[1].textContent = "Stop Video";
|
||||
} else {
|
||||
this.vLocal.parentElement.classList.add("d-none");
|
||||
this.videoBtn.classList.add("off");
|
||||
this.videoBtn.childNodes[1].textContent = "Start Video";
|
||||
}
|
||||
}
|
||||
|
||||
remove() {
|
||||
clearInterval(this.tsInterval);
|
||||
if (this.iframe.parentElement) {
|
||||
document.body.removeChild(this.iframe);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
91
tracker/tracker-assist/src/Confirm.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
|
||||
const declineIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128" ><g id="Circle_Grid" data-name="Circle Grid"><circle cx="64" cy="64" fill="#ef5261" r="64"/></g><g id="icon"><path d="m57.831 70.1c8.79 8.79 17.405 12.356 20.508 9.253l4.261-4.26a7.516 7.516 0 0 1 10.629 0l9.566 9.566a7.516 7.516 0 0 1 0 10.629l-7.453 7.453c-7.042 7.042-27.87-2.358-47.832-22.319-9.976-9.981-16.519-19.382-20.748-28.222s-5.086-16.091-1.567-19.61l7.453-7.453a7.516 7.516 0 0 1 10.629 0l9.566 9.563a7.516 7.516 0 0 1 0 10.629l-4.264 4.271c-3.103 3.1.462 11.714 9.252 20.5z" fill="#eeefee"/></g></svg>`;
|
||||
|
||||
export default class Confirm {
|
||||
private wrapper: HTMLDivElement;
|
||||
|
||||
constructor(text: string, styles?: Object) {
|
||||
const wrapper = document.createElement('div');
|
||||
const popup = document.createElement('div');
|
||||
const p = document.createElement('p');
|
||||
p.innerText = text;
|
||||
const buttons = document.createElement('div');
|
||||
const answerBtn = document.createElement('button');
|
||||
answerBtn.innerHTML = declineIcon.replace('fill="#ef5261"', 'fill="green"');
|
||||
const declineBtn = document.createElement('button');
|
||||
declineBtn.innerHTML = declineIcon;
|
||||
buttons.appendChild(answerBtn);
|
||||
buttons.appendChild(declineBtn);
|
||||
popup.appendChild(p);
|
||||
popup.appendChild(buttons);
|
||||
|
||||
const btnStyles = {
|
||||
borderRadius: "50%",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
background: "transparent",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
border: 0,
|
||||
cursor: "pointer",
|
||||
}
|
||||
Object.assign(answerBtn.style, btnStyles);
|
||||
Object.assign(declineBtn.style, btnStyles);
|
||||
Object.assign(buttons.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
});
|
||||
|
||||
Object.assign(popup.style, {
|
||||
position: "relative",
|
||||
pointerEvents: "auto",
|
||||
margin: "4em auto",
|
||||
width: "90%",
|
||||
maxWidth: "400px",
|
||||
padding: "25px 30px",
|
||||
background: "black",
|
||||
opacity: ".75",
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
borderRadius: ".25em .25em .4em .4em",
|
||||
boxShadow: "0 0 20px rgb(0 0 0 / 20%)",
|
||||
}, styles);
|
||||
|
||||
Object.assign(wrapper.style, {
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
pointerEvents: "none",
|
||||
zIndex: 2147483647 - 1,
|
||||
})
|
||||
|
||||
wrapper.appendChild(popup);
|
||||
this.wrapper = wrapper;
|
||||
|
||||
answerBtn.onclick = () => {
|
||||
this.remove();
|
||||
this.callback(true);
|
||||
}
|
||||
declineBtn.onclick = () => {
|
||||
this.remove();
|
||||
this.callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
mount() {
|
||||
document.body.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
private callback: (result: boolean) => void = ()=>{};
|
||||
onAnswer(callback: (result: boolean) => void) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.wrapper.parentElement) { return; }
|
||||
document.body.removeChild(this.wrapper);
|
||||
}
|
||||
}
|
||||
32
tracker/tracker-assist/src/Mouse.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
|
||||
|
||||
export default class Mouse {
|
||||
private mouse: HTMLDivElement
|
||||
constructor() {
|
||||
this.mouse = document.createElement('div');
|
||||
Object.assign(this.mouse.style, {
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
opacity: ".4",
|
||||
borderRadius: "50%",
|
||||
position: "absolute",
|
||||
zIndex: "999998",
|
||||
background: "radial-gradient(red, transparent)",
|
||||
});
|
||||
document.body.appendChild(this.mouse);
|
||||
}
|
||||
|
||||
move({x, y}: {x?: number, y?: number}) {
|
||||
Object.assign(this.mouse.style, {
|
||||
left: `${x || 0}px`,
|
||||
top: `${y || 0}px`
|
||||
})
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (this.mouse.parentElement) {
|
||||
document.body.removeChild(this.mouse);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
tracker/tracker-assist/src/confirm.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
|
||||
const declineIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 128 128" ><g id="Circle_Grid" data-name="Circle Grid"><circle cx="64" cy="64" fill="#ef5261" r="64"/></g><g id="icon"><path d="m57.831 70.1c8.79 8.79 17.405 12.356 20.508 9.253l4.261-4.26a7.516 7.516 0 0 1 10.629 0l9.566 9.566a7.516 7.516 0 0 1 0 10.629l-7.453 7.453c-7.042 7.042-27.87-2.358-47.832-22.319-9.976-9.981-16.519-19.382-20.748-28.222s-5.086-16.091-1.567-19.61l7.453-7.453a7.516 7.516 0 0 1 10.629 0l9.566 9.563a7.516 7.516 0 0 1 0 10.629l-4.264 4.271c-3.103 3.1.462 11.714 9.252 20.5z" fill="#eeefee"/></g></svg>`;
|
||||
|
||||
export default class Confirm {
|
||||
private wrapper: HTMLDivElement;
|
||||
|
||||
constructor(text: string, styles?: Object) {
|
||||
const wrapper = document.createElement('div');
|
||||
const popup = document.createElement('div');
|
||||
const p = document.createElement('p');
|
||||
p.innerText = text;
|
||||
const buttons = document.createElement('div');
|
||||
const answerBtn = document.createElement('button');
|
||||
answerBtn.innerHTML = declineIcon.replace('fill="#ef5261"', 'fill="green"');
|
||||
const declineBtn = document.createElement('button');
|
||||
declineBtn.innerHTML = declineIcon;
|
||||
buttons.appendChild(answerBtn);
|
||||
buttons.appendChild(declineBtn);
|
||||
popup.appendChild(p);
|
||||
popup.appendChild(buttons);
|
||||
|
||||
const btnStyles = {
|
||||
borderRadius: "50%",
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
background: "transparent",
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
border: 0,
|
||||
cursor: "pointer",
|
||||
}
|
||||
Object.assign(answerBtn.style, btnStyles);
|
||||
Object.assign(declineBtn.style, btnStyles);
|
||||
Object.assign(buttons.style, {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-evenly",
|
||||
});
|
||||
|
||||
Object.assign(popup.style, {
|
||||
position: "relative",
|
||||
pointerEvents: "auto",
|
||||
margin: "4em auto",
|
||||
width: "90%",
|
||||
maxWidth: "400px",
|
||||
padding: "25px 30px",
|
||||
background: "black",
|
||||
opacity: ".75",
|
||||
color: "white",
|
||||
textAlign: "center",
|
||||
borderRadius: ".25em .25em .4em .4em",
|
||||
boxShadow: "0 0 20px rgb(0 0 0 / 20%)",
|
||||
}, styles);
|
||||
|
||||
Object.assign(wrapper.style, {
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
top: 0,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
pointerEvents: "none",
|
||||
zIndex: 2147483647 - 1,
|
||||
})
|
||||
|
||||
wrapper.appendChild(popup);
|
||||
this.wrapper = wrapper;
|
||||
|
||||
answerBtn.onclick = () => {
|
||||
this.remove();
|
||||
this.callback(true);
|
||||
}
|
||||
declineBtn.onclick = () => {
|
||||
this.remove();
|
||||
this.callback(false);
|
||||
}
|
||||
}
|
||||
|
||||
mount() {
|
||||
document.body.appendChild(this.wrapper);
|
||||
}
|
||||
|
||||
private callback: (result: boolean) => void = ()=>{};
|
||||
onAnswer(callback: (result: boolean) => void) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
remove() {
|
||||
if (!this.wrapper.parentElement) { return; }
|
||||
document.body.removeChild(this.wrapper);
|
||||
}
|
||||
}
|
||||
156
tracker/tracker-assist/src/index.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import Peer, { MediaConnection } from 'peerjs';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { App, Messages } from '@openreplay/tracker';
|
||||
import type Message from '@openreplay/tracker';
|
||||
|
||||
import Mouse from './Mouse';
|
||||
import CallWindow from './CallWindow';
|
||||
import Confirm from './Confirm';
|
||||
|
||||
|
||||
export interface Options {
|
||||
confirmText: string,
|
||||
confirmStyle: Object, // Styles object
|
||||
}
|
||||
|
||||
|
||||
export default function(opts: Partial<Options> = {}) {
|
||||
const options: Options = Object.assign(
|
||||
{
|
||||
confirmText: "You have a call. Do you want to answer?",
|
||||
confirmStyle: {},
|
||||
},
|
||||
opts,
|
||||
);
|
||||
return function(app: App | null, appOptions: { __DISABLE_SECURE_MODE?: boolean } = {}) {
|
||||
// @ts-ignore
|
||||
if (app === null || !navigator?.mediaDevices?.getUserMedia) { // 93.04% browsers
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let callingPeerDataConn
|
||||
app.attachStartCallback(function() {
|
||||
// new CallWindow(()=>{console.log('endcall')});
|
||||
|
||||
// @ts-ignore
|
||||
const peerID = `${app.projectKey}-${app.getSessionID()}`
|
||||
const peer = new Peer(peerID, {
|
||||
// @ts-ignore
|
||||
host: app.getHost(),
|
||||
path: '/assist',
|
||||
port: location.protocol === 'http:' && appOptions.__DISABLE_SECURE_MODE ? 80 : 443,
|
||||
});
|
||||
console.log(peerID)
|
||||
peer.on('connection', function(conn) {
|
||||
console.log('connection')
|
||||
conn.on('open', function() {
|
||||
|
||||
console.log('connection open')
|
||||
|
||||
// TODO: onClose
|
||||
const buffer: Message[][] = [];
|
||||
let buffering = false;
|
||||
function sendNext() {
|
||||
setTimeout(() => {
|
||||
if (buffer.length) {
|
||||
conn.send(buffer.shift());
|
||||
sendNext();
|
||||
} else {
|
||||
buffering = false;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
app.stop();
|
||||
//@ts-ignore (should update tracker dependency)
|
||||
app.addCommitCallback((messages: Array<Message>): void => {
|
||||
let i = 0;
|
||||
while (i < messages.length) {
|
||||
buffer.push(messages.slice(i, i+=1000));
|
||||
}
|
||||
if (!buffering) {
|
||||
buffering = true;
|
||||
sendNext();
|
||||
}
|
||||
});
|
||||
app.start();
|
||||
});
|
||||
});
|
||||
let calling = false;
|
||||
peer.on('call', function(call) {
|
||||
const dataConn: DataConnection | undefined = peer
|
||||
.connections[call.peer].find(c => c.type === 'data');
|
||||
if (calling || !dataConn) {
|
||||
call.close();
|
||||
dataConn?.send("call_error");
|
||||
return;
|
||||
}
|
||||
calling = true;
|
||||
window.addEventListener("beforeunload", () => {
|
||||
dataConn.open && dataConn.send("call_end");
|
||||
});
|
||||
dataConn.on('data', (data) => { // if call closed be a caller before confirm
|
||||
if (data === "call_end") {
|
||||
calling = false;
|
||||
confirm.remove();
|
||||
}
|
||||
});
|
||||
const confirm = new Confirm(options.confirmText, options.confirmStyle);
|
||||
confirm.mount();
|
||||
confirm.onAnswer(conf => {
|
||||
if (!conf || !dataConn.open) {
|
||||
call.close();
|
||||
dataConn.open && dataConn.send("call_end");
|
||||
calling = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const mouse = new Mouse();
|
||||
let callUI;
|
||||
|
||||
navigator.mediaDevices.getUserMedia({video:true, audio:true})
|
||||
.then(oStream => {
|
||||
const onClose = () => {
|
||||
console.log("close call...")
|
||||
if (call.open) { call.close(); }
|
||||
mouse.remove();
|
||||
callUI?.remove();
|
||||
oStream.getTracks().forEach(t => t.stop());
|
||||
|
||||
calling = false;
|
||||
if (dataConn.open) {
|
||||
dataConn.send("call_end");
|
||||
}
|
||||
}
|
||||
dataConn.on("close", onClose);
|
||||
|
||||
call.answer(oStream);
|
||||
call.on('close', onClose); // Works from time to time (peerjs bug)
|
||||
const intervalID = setInterval(() => {
|
||||
if (!call.open) {
|
||||
onClose();
|
||||
clearInterval(intervalID);
|
||||
}
|
||||
}, 5000);
|
||||
call.on('error', onClose); // notify about error?
|
||||
|
||||
callUI = new CallWindow(onClose);
|
||||
callUI.setLocalStream(oStream);
|
||||
call.on('stream', function(iStream) {
|
||||
callUI.setRemoteStream(iStream);
|
||||
dataConn.on('data', (data: any) => {
|
||||
if (data === "call_end") {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
if (call.open && data && typeof data.x === 'number' && typeof data.y === 'number') {
|
||||
mouse.move(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
7
tracker/tracker-assist/tsconfig-cjs.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"outDir": "./cjs"
|
||||
},
|
||||
}
|
||||