Merge pull request #336 from openreplay/dev
v1.5.0: UI and tracker fixes
This commit is contained in:
commit
fc1d0634d4
28 changed files with 841 additions and 218 deletions
|
|
@ -102,7 +102,7 @@ class Router extends React.Component {
|
|||
this.props.fetchTenants();
|
||||
}
|
||||
|
||||
if (!prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') {
|
||||
if (this.state.destinationPath && !prevProps.isLoggedIn && this.props.isLoggedIn && this.state.destinationPath !== routes.login() && this.state.destinationPath !== '/') {
|
||||
this.props.history.push(this.state.destinationPath);
|
||||
this.setState({ destinationPath: null });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const LIMIT_WARNING = 'You have reached users limit.';
|
|||
generateInviteLink,
|
||||
fetchRoles
|
||||
})
|
||||
@withPageTitle('Users - OpenReplay Preferences')
|
||||
@withPageTitle('Team - OpenReplay Preferences')
|
||||
class ManageUsers extends React.PureComponent {
|
||||
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Checkbox } from 'UI'
|
|||
import { connect } from 'react-redux'
|
||||
import { withRequest } from 'HOCs'
|
||||
import { fetch as fetchConfig, edit as editConfig, save as saveConfig } from 'Duck/config'
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
function Notifications(props) {
|
||||
const { config } = props;
|
||||
|
|
@ -42,4 +43,4 @@ function Notifications(props) {
|
|||
|
||||
export default connect(state => ({
|
||||
config: state.getIn(['config', 'options'])
|
||||
}), { fetchConfig, editConfig, saveConfig })(Notifications)
|
||||
}), { fetchConfig, editConfig, saveConfig })(withPageTitle('Notifications - OpenReplay Preferences')(Notifications));
|
||||
|
|
|
|||
|
|
@ -68,25 +68,25 @@ function PreferencesMenu({ activeTab, appearance, history, isEnterprise }) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_USERS }
|
||||
title="Users"
|
||||
iconName="users"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
{ isEnterprise && (
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_ROLES }
|
||||
title="Roles"
|
||||
iconName="shield-lock"
|
||||
title="Roles & Access"
|
||||
iconName="diagram-3"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_ROLES) }
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
active={ activeTab === CLIENT_TABS.MANAGE_USERS }
|
||||
title="Team"
|
||||
iconName="users"
|
||||
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS) }
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<SideMenuitem
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { init, edit, fetchList, remove as deleteRole, resetErrors } from 'Duck/r
|
|||
import RoleItem from './components/RoleItem'
|
||||
import { confirm } from 'UI/Confirmation';
|
||||
import { toast } from 'react-toastify';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
interface Props {
|
||||
loading: boolean
|
||||
|
|
@ -20,11 +21,12 @@ interface Props {
|
|||
account: any,
|
||||
permissionsMap: any,
|
||||
removeErrors: any,
|
||||
resetErrors: () => void
|
||||
resetErrors: () => void,
|
||||
projectsMap: any,
|
||||
}
|
||||
|
||||
function Roles(props: Props) {
|
||||
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, removeErrors } = props
|
||||
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, projectsMap, removeErrors } = props
|
||||
const [showModal, setShowmModal] = useState(false)
|
||||
const isAdmin = account.admin || account.superAdmin;
|
||||
|
||||
|
|
@ -72,16 +74,16 @@ function Roles(props: Props) {
|
|||
<React.Fragment>
|
||||
<Loader loading={ loading }>
|
||||
<SlideModal
|
||||
title={ instance.exists() ? "Edit Role" : "Add Role" }
|
||||
title={ instance.exists() ? "Edit Role" : "Create Role" }
|
||||
size="small"
|
||||
isDisplayed={showModal }
|
||||
content={ showModal && <RoleForm closeModal={closeModal}/> }
|
||||
content={ showModal && <RoleForm closeModal={closeModal} permissionsMap={permissionsMap} deleteHandler={deleteHandler} /> }
|
||||
onClose={ closeModal }
|
||||
/>
|
||||
<div className={ stl.wrapper }>
|
||||
<div className={ cn(stl.tabHeader, 'flex items-center') }>
|
||||
<div className="flex items-center mr-auto">
|
||||
<h3 className={ cn(stl.tabTitle, "text-2xl") }>Manage Roles and Permissions</h3>
|
||||
<h3 className={ cn(stl.tabTitle, "text-2xl") }>Roles and Access</h3>
|
||||
<Popup
|
||||
trigger={
|
||||
<div>
|
||||
|
|
@ -111,11 +113,17 @@ function Roles(props: Props) {
|
|||
icon
|
||||
>
|
||||
<div className={''}>
|
||||
<div className={cn(stl.wrapper, 'flex items-start py-3 border-b px-3 pr-20')}>
|
||||
<div className="flex" style={{ width: '20%'}}>Title</div>
|
||||
<div className="flex" style={{ width: '30%'}}>Project Access</div>
|
||||
<div className="flex" style={{ width: '50%'}}>Feature Access</div>
|
||||
</div>
|
||||
{roles.map(role => (
|
||||
<RoleItem
|
||||
role={role}
|
||||
isAdmin={isAdmin}
|
||||
permissions={permissionsMap}
|
||||
projects={projectsMap}
|
||||
editHandler={editHandler}
|
||||
deleteHandler={deleteHandler}
|
||||
/>
|
||||
|
|
@ -132,14 +140,20 @@ export default connect(state => {
|
|||
const permissions = state.getIn(['roles', 'permissions'])
|
||||
const permissionsMap = {}
|
||||
permissions.forEach(p => {
|
||||
permissionsMap[p.value] = p.name
|
||||
permissionsMap[p.value] = p.text
|
||||
});
|
||||
const projects = state.getIn([ 'site', 'list' ])
|
||||
return {
|
||||
instance: state.getIn(['roles', 'instance']) || null,
|
||||
permissionsMap: permissionsMap,
|
||||
roles: state.getIn(['roles', 'list']),
|
||||
removeErrors: state.getIn(['roles', 'removeRequest', 'errors']),
|
||||
loading: state.getIn(['roles', 'fetchRequest', 'loading']),
|
||||
account: state.getIn([ 'user', 'account' ])
|
||||
account: state.getIn([ 'user', 'account' ]),
|
||||
projectsMap: projects.reduce((acc, p) => {
|
||||
acc[ p.get('id') ] = p.get('name')
|
||||
return acc
|
||||
}
|
||||
, {}),
|
||||
}
|
||||
}, { init, edit, fetchList, deleteRole, resetErrors })(Roles)
|
||||
}, { init, edit, fetchList, deleteRole, resetErrors })(withPageTitle('Roles & Access - OpenReplay Preferences')(Roles))
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useRef, useEffect } from 'react'
|
|||
import { connect } from 'react-redux'
|
||||
import stl from './roleForm.css'
|
||||
import { save, edit } from 'Duck/roles'
|
||||
import { Input, Button, Checkbox } from 'UI'
|
||||
import { Input, Button, Checkbox, Dropdown, Icon } from 'UI'
|
||||
|
||||
interface Permission {
|
||||
name: string,
|
||||
|
|
@ -16,9 +16,14 @@ interface Props {
|
|||
closeModal: (toastMessage?: string) => void,
|
||||
saving: boolean,
|
||||
permissions: Array<Permission>[]
|
||||
projectOptions: Array<any>[],
|
||||
permissionsMap: any,
|
||||
projectsMap: any,
|
||||
deleteHandler: (id: any) => Promise<void>,
|
||||
}
|
||||
|
||||
const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props) => {
|
||||
const RoleForm = (props: Props) => {
|
||||
const { role, edit, save, closeModal, saving, permissions, projectOptions, permissionsMap, projectsMap } = props
|
||||
let focusElement = useRef<any>(null)
|
||||
const _save = () => {
|
||||
save(role).then(() => {
|
||||
|
|
@ -28,13 +33,33 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
|
||||
const write = ({ target: { value, name } }) => edit({ [ name ]: value })
|
||||
|
||||
const onChangeOption = (e) => {
|
||||
const onChangePermissions = (e) => {
|
||||
const { permissions } = role
|
||||
const index = permissions.indexOf(e)
|
||||
const _perms = permissions.contains(e) ? permissions.remove(index) : permissions.push(e)
|
||||
edit({ permissions: _perms })
|
||||
}
|
||||
|
||||
const onChangeProjects = (e) => {
|
||||
const { projects } = role
|
||||
const index = projects.indexOf(e)
|
||||
const _projects = index === -1 ? projects.push(e) : projects.remove(index)
|
||||
edit({ projects: _projects })
|
||||
}
|
||||
|
||||
const writeOption = (e, { name, value }) => {
|
||||
if (name === 'permissions') {
|
||||
onChangePermissions(value)
|
||||
} else if (name === 'projects') {
|
||||
onChangeProjects(value)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAllProjects = () => {
|
||||
const { allProjects } = role
|
||||
edit({ allProjects: !allProjects })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
focusElement && focusElement.current && focusElement.current.focus()
|
||||
}, [])
|
||||
|
|
@ -42,8 +67,8 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
return (
|
||||
<div className={ stl.form }>
|
||||
<form onSubmit={ _save } >
|
||||
<div className={ stl.formGroup }>
|
||||
<label>{ 'Name' }</label>
|
||||
<div className="form-group">
|
||||
<label>{ 'Title' }</label>
|
||||
<Input
|
||||
ref={ focusElement }
|
||||
name="name"
|
||||
|
|
@ -51,22 +76,77 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
onChange={ write }
|
||||
className={ stl.input }
|
||||
id="name-field"
|
||||
placeholder="Ex. Admin"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{ permissions.map((permission: any, index) => (
|
||||
<div key={ index } className={ stl.formGroup }>
|
||||
<Checkbox
|
||||
name="permissions"
|
||||
className="font-medium"
|
||||
type="checkbox"
|
||||
checked={ role.permissions.contains(permission.value) }
|
||||
onClick={ () => onChangeOption(permission.value) }
|
||||
label={permission.name}
|
||||
/>
|
||||
<div className="form-group flex flex-col">
|
||||
<label>{ 'Project Access' }</label>
|
||||
|
||||
<div className="flex my-3">
|
||||
<Checkbox
|
||||
name="allProjects"
|
||||
className="font-medium"
|
||||
type="checkbox"
|
||||
checked={ role.allProjects }
|
||||
onClick={toggleAllProjects}
|
||||
label={''}
|
||||
/>
|
||||
<div className="cursor-pointer" onClick={toggleAllProjects}>
|
||||
<div>All Projects</div>
|
||||
<span className="text-xs text-gray-600">
|
||||
(Uncheck to select specific projects)
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{ !role.allProjects && (
|
||||
<>
|
||||
<Dropdown
|
||||
search
|
||||
className="fluid"
|
||||
placeholder="Select"
|
||||
selection
|
||||
options={ projectOptions }
|
||||
name="projects"
|
||||
value={null}
|
||||
onChange={ writeOption }
|
||||
id="change-dropdown"
|
||||
selectOnBlur={false}
|
||||
selectOnNavigation={false}
|
||||
/>
|
||||
{ role.projects.size > 0 && (
|
||||
<div className="flex flex-row items-start flex-wrap mt-4">
|
||||
{ role.projects.map(p => (
|
||||
OptionLabel(projectsMap, p, onChangeProjects)
|
||||
)) }
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group flex flex-col">
|
||||
<label>{ 'Capability Access' }</label>
|
||||
<Dropdown
|
||||
search
|
||||
className="fluid"
|
||||
placeholder="Select"
|
||||
selection
|
||||
options={ permissions }
|
||||
name="permissions"
|
||||
value={null}
|
||||
onChange={ writeOption }
|
||||
id="change-dropdown"
|
||||
selectOnBlur={false}
|
||||
selectOnNavigation={false}
|
||||
/>
|
||||
{ role.permissions.size > 0 && (
|
||||
<div className="flex flex-row items-start flex-wrap mt-4">
|
||||
{ role.permissions.map(p => (
|
||||
OptionLabel(permissionsMap, p, onChangePermissions)
|
||||
)) }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
@ -89,13 +169,50 @@ const RoleForm = ({ role, closeModal, edit, save, saving, permissions }: Props)
|
|||
{ 'Cancel' }
|
||||
</Button>
|
||||
</div>
|
||||
{ role.exists() && (
|
||||
<div>
|
||||
<Button
|
||||
data-hidden={ !role.exists() }
|
||||
onClick={ () => props.deleteHandler(role) }
|
||||
hover
|
||||
noPadding
|
||||
>
|
||||
<Icon name="trash" size="18"/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
role: state.getIn(['roles', 'instance']),
|
||||
permissions: state.getIn(['roles', 'permissions']),
|
||||
saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]),
|
||||
}), { edit, save })(RoleForm);
|
||||
export default connect(state => {
|
||||
const role = state.getIn(['roles', 'instance'])
|
||||
const projects = state.getIn([ 'site', 'list' ])
|
||||
return {
|
||||
role,
|
||||
projectOptions: projects.map(p => ({
|
||||
key: p.get('id'),
|
||||
value: p.get('id'),
|
||||
text: p.get('name'),
|
||||
disabled: role.projects.includes(p.get('id')),
|
||||
})).toJS(),
|
||||
permissions: state.getIn(['roles', 'permissions'])
|
||||
.map(({ text, value }) => ({ text, value, disabled: role.permissions.includes(value) })).toJS(),
|
||||
saving: state.getIn([ 'roles', 'saveRequest', 'loading' ]),
|
||||
projectsMap: projects.reduce((acc, p) => {
|
||||
acc[ p.get('id') ] = p.get('name')
|
||||
return acc
|
||||
}
|
||||
, {}),
|
||||
}
|
||||
}, { edit, save })(RoleForm);
|
||||
|
||||
function OptionLabel(nameMap: any, p: any, onChangeOption: (e: any) => void) {
|
||||
return <div className="px-2 py-1 rounded bg-gray-lightest mr-2 mb-2 border flex items-center justify-between">
|
||||
<div>{nameMap[p]}</div>
|
||||
<div className="cursor-pointer ml-2" onClick={() => onChangeOption(p)}>
|
||||
<Icon name="close" size="12" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
.form {
|
||||
padding: 0 20px;
|
||||
|
||||
& .formGroup {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
& label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
import React from 'react'
|
||||
import { Icon } from 'UI'
|
||||
import { Icon, Link } from 'UI'
|
||||
import stl from './roleItem.css'
|
||||
import cn from 'classnames'
|
||||
import { CLIENT_TABS, client as clientRoute } from 'App/routes';
|
||||
|
||||
function PermisionLabel({ permission }: any) {
|
||||
|
||||
function PermisionLabel({ label }: any) {
|
||||
return (
|
||||
<div className={cn(stl.label)}>{ permission }</div>
|
||||
<div className={cn(stl.label, 'mb-2')}>{ label }</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PermisionLabelLinked({ label, route }: any) {
|
||||
return (
|
||||
<Link to={route}><div className={cn(stl.label, 'mb-2 bg-active-blue color-teal')}>{ label }</div></Link>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -14,28 +22,33 @@ interface Props {
|
|||
deleteHandler?: (role: any) => void,
|
||||
editHandler?: (role: any) => void,
|
||||
permissions: any,
|
||||
isAdmin: boolean
|
||||
isAdmin: boolean,
|
||||
projects: any,
|
||||
}
|
||||
function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Props) {
|
||||
function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions, projects }: Props) {
|
||||
return (
|
||||
<div className={cn(stl.wrapper)}>
|
||||
<Icon name="user-alt" size="16" marginRight="10" />
|
||||
<div className="flex items-center">
|
||||
<div className="mr-4">{ role.name }</div>
|
||||
<div className="grid grid-flow-col auto-cols-max">
|
||||
{role.permissions.map((permission: any) => (
|
||||
<PermisionLabel permission={permissions[permission]} key={permission.id} />
|
||||
// <span key={permission.id} className={cn(stl.permission)}>{ permissions[permission].name }</span>
|
||||
))}
|
||||
</div>
|
||||
<div className={cn('flex items-start relative py-4 hover border-b px-3 pr-20')}>
|
||||
<div className="flex" style={{ width: '20%'}}>
|
||||
<Icon name="user-alt" size="16" marginRight="10" />
|
||||
{ role.name }
|
||||
</div>
|
||||
<div className="flex items-start flex-wrap" style={{ width: '30%'}}>
|
||||
{role.allProjects ? (
|
||||
<PermisionLabelLinked label="All projects" route={clientRoute(CLIENT_TABS.SITES)}/>
|
||||
) : (
|
||||
role.projects.map(p => (
|
||||
<PermisionLabel label={projects[p]} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-start flex-wrap" style={{ width: '50%'}}>
|
||||
{role.permissions.map((permission: any) => (
|
||||
<PermisionLabel label={permissions[permission]} key={permission.id} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{ isAdmin && (
|
||||
<div className={ stl.actions }>
|
||||
{ !!deleteHandler &&
|
||||
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => deleteHandler(role) } id="trash">
|
||||
<Icon name="trash" size="16" color="teal"/>
|
||||
</div>
|
||||
}
|
||||
<div className={ cn(stl.actions, 'absolute right-0 top-0 bottom-0 mr-8') }>
|
||||
{ !!editHandler &&
|
||||
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => editHandler(role) }>
|
||||
<Icon name="edit" size="16" color="teal"/>
|
||||
|
|
@ -43,7 +56,6 @@ function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Pr
|
|||
}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,5 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-bottom: solid thin #e6e6e6;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-left: auto;
|
||||
/* margin-left: auto; */
|
||||
/* opacity: 0; */
|
||||
transition: all 0.4s;
|
||||
display: flex;
|
||||
|
|
@ -37,11 +29,11 @@
|
|||
}
|
||||
|
||||
.label {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 0 5px;
|
||||
border-radius: 3px;
|
||||
background-color: $gray-lightest;
|
||||
font-size: 10px;
|
||||
font-size: 12px;
|
||||
border: solid thin $gray-light;
|
||||
width: fit-content;
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ const GDPR_FORM = 'GDPR_FORM';
|
|||
remove,
|
||||
fetchGDPR
|
||||
})
|
||||
@withPageTitle('Sites - OpenReplay Preferences')
|
||||
@withPageTitle('Projects - OpenReplay Preferences')
|
||||
class Sites extends React.PureComponent {
|
||||
state = {
|
||||
showTrackingCode: false,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
|
|||
import { errors as errorsRoute, isRoute } from "App/routes";
|
||||
import EventFilter from 'Components/BugFinder/EventFilter';
|
||||
import DateRange from 'Components/BugFinder/DateRange';
|
||||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
|
||||
import { SavedSearchList } from 'UI';
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ function getStatusLabel(status) {
|
|||
applyFilter,
|
||||
fetchSlackList,
|
||||
})
|
||||
@withPageTitle("Errors - OpenReplay")
|
||||
export default class Errors extends React.PureComponent {
|
||||
state = {
|
||||
status: UNRESOLVED,
|
||||
|
|
|
|||
|
|
@ -127,10 +127,6 @@ export default class Fetch extends React.PureComponent {
|
|||
<BottomBlock.Header>
|
||||
<h4 className="text-lg">Fetch</h4>
|
||||
<div className="flex items-center">
|
||||
{/* <div className="flex items-center mr-3 text-sm uppercase">
|
||||
<div className="p-2 cursor-pointer" onClick={this.goToPrevError}>Prev</div>
|
||||
<div className="p-2 cursor-pointer" onClick={this.goToNextError}>Next</div>
|
||||
</div> */}
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { JSONTree } from 'UI'
|
||||
import { JSONTree, Button } from 'UI'
|
||||
import cn from 'classnames';
|
||||
|
||||
export default class GQLDetails extends React.PureComponent {
|
||||
|
|
@ -7,7 +7,14 @@ export default class GQLDetails extends React.PureComponent {
|
|||
gql: {
|
||||
variables,
|
||||
response,
|
||||
duration,
|
||||
operationKind,
|
||||
operationName,
|
||||
},
|
||||
nextClick,
|
||||
prevClick,
|
||||
first = false,
|
||||
last = false,
|
||||
} = this.props;
|
||||
|
||||
let jsonVars = undefined;
|
||||
|
|
@ -19,28 +26,52 @@ export default class GQLDetails extends React.PureComponent {
|
|||
jsonResponse = JSON.parse(response);
|
||||
} catch (e) {}
|
||||
return (
|
||||
<div className="ph-20" >
|
||||
<div className="divider"/>
|
||||
{ variables && variables !== "{}" &&
|
||||
<div>
|
||||
<div className="mt-6">
|
||||
<h5>{ 'Variables'}</h5>
|
||||
{ jsonVars === undefined
|
||||
? <div className="ml-3"> { variables } </div>
|
||||
: <JSONTree src={ jsonVars } />
|
||||
}
|
||||
<div className="px-4 pb-16">
|
||||
<h5 className="mb-2">{ 'Operation Name'}</h5>
|
||||
<div className={ cn('p-2 bg-gray-lightest rounded color-gray-darkest')}>{ operationName }</div>
|
||||
|
||||
<div className="flex items-center mt-4">
|
||||
<div className="w-4/12">
|
||||
<div className="font-medium mb-2">Operation Kind</div>
|
||||
<div>{operationKind}</div>
|
||||
</div>
|
||||
<div className="w-4/12">
|
||||
<div className="font-medium mb-2">Duration</div>
|
||||
<div>{parseInt(duration)} ms</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-start mt-6">
|
||||
<h5 className="mt-1 mr-1">{ 'Response' }</h5>
|
||||
</div>
|
||||
<div style={{ height: 'calc(100vh - 314px)', overflowY: 'auto' }}>
|
||||
{ variables && variables !== "{}" &&
|
||||
<div>
|
||||
<div className="mt-2">
|
||||
<h5>{ 'Variables'}</h5>
|
||||
{ jsonVars === undefined
|
||||
? <div className="ml-3"> { variables } </div>
|
||||
: <JSONTree src={ jsonVars } />
|
||||
}
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
<div className="divider"/>
|
||||
</div>
|
||||
}
|
||||
<div className="mt-6">
|
||||
<div className="flex justify-between items-start">
|
||||
<h5 className="mt-1 mr-1">{ 'Response' }</h5>
|
||||
</div>
|
||||
{ jsonResponse === undefined
|
||||
? <div className="ml-3"> { response } </div>
|
||||
: <JSONTree src={ jsonResponse } />
|
||||
}
|
||||
<div className="mt-3">
|
||||
{ jsonResponse === undefined
|
||||
? <div className="ml-3"> { response } </div>
|
||||
: <JSONTree src={ jsonResponse } />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between absolute bottom-0 left-0 right-0 p-3 border-t bg-white">
|
||||
<Button primary plain onClick={prevClick} disabled={first}>
|
||||
Prev
|
||||
</Button>
|
||||
<Button primary plain onClick={nextClick} disabled={last}>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
//import cn from 'classnames';
|
||||
import { Icon, NoContent, Input, SlideModal } from 'UI';
|
||||
import { Label, Icon, NoContent, Input, SlideModal, CloseButton } from 'UI';
|
||||
import { getRE } from 'App/utils';
|
||||
import { connectPlayer } from 'Player';
|
||||
import Autoscroll from '../Autoscroll';
|
||||
import { connectPlayer, pause, jump } from 'Player';
|
||||
import BottomBlock from '../BottomBlock';
|
||||
import TimeTable from '../TimeTable';
|
||||
import GQLDetails from './GQLDetails';
|
||||
|
|
@ -10,60 +8,105 @@ import GQLDetails from './GQLDetails';
|
|||
function renderDefaultStatus() {
|
||||
return "2xx-3xx";
|
||||
}
|
||||
|
||||
@connectPlayer(state => ({
|
||||
list: state.graphqlListNow,
|
||||
livePlay: state.livePlay,
|
||||
}))
|
||||
export default class GraphQL extends React.PureComponent {
|
||||
state = {
|
||||
filter: "",
|
||||
filter: "",
|
||||
filteredList: this.props.list,
|
||||
current: null,
|
||||
currentIndex: 0,
|
||||
showFetchDetails: false,
|
||||
hasNextError: false,
|
||||
hasPreviousError: false,
|
||||
}
|
||||
onFilterChange = (e, { value }) => this.setState({ filter: value })
|
||||
|
||||
setCurrent = (item) => {
|
||||
this.setState({ current: item });
|
||||
onFilterChange = (e, { value }) => {
|
||||
const { list } = this.props;
|
||||
const filterRE = getRE(value, 'i');
|
||||
const filtered = list
|
||||
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
|
||||
this.setState({ filter: value, filteredList: value ? filtered : list, currentIndex: 0 });
|
||||
}
|
||||
|
||||
setCurrent = (item, index) => {
|
||||
if (!this.props.livePlay) {
|
||||
pause();
|
||||
jump(item.time)
|
||||
}
|
||||
this.setState({ current: item, currentIndex: index });
|
||||
}
|
||||
|
||||
closeModal = () => this.setState({ current: null, showFetchDetails: false });
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
const { filteredList } = prevState;
|
||||
if (nextProps.timelinePointer) {
|
||||
let activeItem = filteredList.find((r) => r.time >= nextProps.timelinePointer.time);
|
||||
activeItem = activeItem || filteredList[filteredList.length - 1];
|
||||
return {
|
||||
current: activeItem,
|
||||
currentIndex: filteredList.indexOf(activeItem),
|
||||
};
|
||||
}
|
||||
}
|
||||
closeModal = () => this.setState({ current: null})
|
||||
|
||||
render() {
|
||||
const { list } = this.props;
|
||||
const { filter, current } = this.state;
|
||||
const filterRE = getRE(filter, 'i');
|
||||
const filtered = list
|
||||
.filter(({ operationName = "", operationKind = "" }) => filterRE.test(operationName) || filterRE.test(operationKind));
|
||||
|
||||
const { current, currentIndex, filteredList } = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SlideModal
|
||||
size="middle"
|
||||
title={ current && <span><i className="color-gray-medium">{current.operationKind}</i> {current.operationName}</span> }
|
||||
right
|
||||
title = {
|
||||
<div className="flex justify-between">
|
||||
<h1>GraphQL</h1>
|
||||
<div className="flex items-center">
|
||||
<CloseButton onClick={ this.closeModal } size="18" className="ml-2" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
isDisplayed={ current != null }
|
||||
content={ current &&
|
||||
<GQLDetails gql={ current }/>
|
||||
<GQLDetails
|
||||
gql={ current }
|
||||
nextClick={this.nextClickHander}
|
||||
prevClick={this.prevClickHander}
|
||||
first={currentIndex === 0}
|
||||
last={currentIndex === filteredList.length - 1}
|
||||
/>
|
||||
}
|
||||
onClose={ this.closeModal }
|
||||
/>
|
||||
<BottomBlock>
|
||||
<BottomBlock.Header>
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter by Name or Type"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onFilterChange }
|
||||
/>
|
||||
<h4 className="text-lg">GraphQL</h4>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
className="input-small"
|
||||
placeholder="Filter by Name or Type"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
name="filter"
|
||||
onChange={ this.onFilterChange }
|
||||
/>
|
||||
</div>
|
||||
</BottomBlock.Header>
|
||||
<BottomBlock.Content>
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ filtered.length === 0}
|
||||
show={ filteredList.length === 0}
|
||||
>
|
||||
<TimeTable
|
||||
rows={ filtered }
|
||||
rows={ filteredList }
|
||||
onRowClick={ this.setCurrent }
|
||||
hoverable
|
||||
navigation
|
||||
activeIndex={currentIndex}
|
||||
>
|
||||
{[
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { List, Map } from 'immutable';
|
|||
import Role from 'Types/role';
|
||||
import crudDuckGenerator from './tools/crudDuck';
|
||||
import { reduceDucks } from 'Duck/tools';
|
||||
import { array, request, success, failure, createListUpdater, mergeReducers } from './funcTools/tools';
|
||||
|
||||
const crudDuck = crudDuckGenerator('client/role', Role, { idKey: 'roleId' });
|
||||
export const { fetchList, init, edit, remove, } = crudDuck.actions;
|
||||
|
|
@ -11,19 +12,29 @@ const RESET_ERRORS = 'roles/RESET_ERRORS';
|
|||
const initialState = Map({
|
||||
list: List(),
|
||||
permissions: List([
|
||||
{ name: 'Session Replay', value: 'SESSION_REPLAY' },
|
||||
{ name: 'Developer Tools', value: 'DEV_TOOLS' },
|
||||
{ name: 'Errors', value: 'ERRORS' },
|
||||
{ name: 'Metrics', value: 'METRICS' },
|
||||
{ name: 'Assist (Live)', value: 'ASSIST_LIVE' },
|
||||
{ name: 'Assist (Call)', value: 'ASSIST_CALL' },
|
||||
{ text: 'Session Replay', value: 'SESSION_REPLAY' },
|
||||
{ text: 'Developer Tools', value: 'DEV_TOOLS' },
|
||||
{ text: 'Errors', value: 'ERRORS' },
|
||||
{ text: 'Metrics', value: 'METRICS' },
|
||||
{ text: 'Assist (Live)', value: 'ASSIST_LIVE' },
|
||||
{ text: 'Assist (Call)', value: 'ASSIST_CALL' },
|
||||
])
|
||||
});
|
||||
|
||||
const name = "role";
|
||||
const idKey = "roleId";
|
||||
|
||||
const updateItemInList = createListUpdater(idKey);
|
||||
const updateInstance = (state, instance) => state.getIn([ "instance", idKey ]) === instance[ idKey ]
|
||||
? state.mergeIn([ "instance" ], instance)
|
||||
: state;
|
||||
|
||||
const reducer = (state = initialState, action = {}) => {
|
||||
switch (action.type) {
|
||||
case RESET_ERRORS:
|
||||
return state.setIn(['removeRequest', 'errors'], null);
|
||||
case crudDuck.actionTypes.SAVE.SUCCESS:
|
||||
return updateItemInList(updateInstance(state, action.data), action.data);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ export const forgotPassword = () => '/reset-password';
|
|||
export const CLIENT_TABS = {
|
||||
INTEGRATIONS: 'integrations',
|
||||
PROFILE: 'account',
|
||||
MANAGE_USERS: 'manage-users',
|
||||
MANAGE_ROLES: 'manage-roles',
|
||||
MANAGE_USERS: 'team',
|
||||
MANAGE_ROLES: 'roles',
|
||||
SITES: 'projects',
|
||||
CUSTOM_FIELDS: 'metadata',
|
||||
WEBHOOKS: 'webhooks',
|
||||
|
|
@ -73,7 +73,7 @@ export const client = (tab = routerClientTabString) => `/client/${ tab }`;
|
|||
export const OB_TABS = {
|
||||
INSTALLING: 'installing',
|
||||
IDENTIFY_USERS: 'identify-users',
|
||||
MANAGE_USERS: 'manage-users',
|
||||
MANAGE_USERS: 'team',
|
||||
INTEGRATIONS: 'integrations',
|
||||
};
|
||||
export const OB_DEFAULT_TAB = OB_TABS.INSTALLING;
|
||||
|
|
|
|||
|
|
@ -117,4 +117,10 @@
|
|||
.disabled {
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hover {
|
||||
&:hover {
|
||||
background-color: $active-blue;
|
||||
}
|
||||
}
|
||||
3
frontend/app/svg/icons/diagram-3.svg
Normal file
3
frontend/app/svg/icons/diagram-3.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" class="bi bi-diagram-3" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5v-1zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5v-1zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 973 B |
|
|
@ -1,18 +1,21 @@
|
|||
import Record from 'Types/Record';
|
||||
import { validateName } from 'App/validate';
|
||||
import { notEmptyString, validateName } from 'App/validate';
|
||||
import { List } from 'immutable';
|
||||
|
||||
export default Record({
|
||||
roleId: undefined,
|
||||
name: '',
|
||||
allProjects: true,
|
||||
permissions: List(),
|
||||
projects: List(),
|
||||
protected: false,
|
||||
description: ''
|
||||
description: '',
|
||||
permissionOptions: List(),
|
||||
}, {
|
||||
idKey: 'roleId',
|
||||
methods: {
|
||||
validate() {
|
||||
return validateName(this.name, { diacritics: true });
|
||||
return notEmptyString(this.name) && validateName(this.name, { diacritics: true }) && (this.allProjects || this.projects.size > 0);
|
||||
},
|
||||
toData() {
|
||||
const js = this.toJS();
|
||||
|
|
@ -21,10 +24,11 @@ export default Record({
|
|||
return js;
|
||||
},
|
||||
},
|
||||
fromJS({ permissions, ...rest }) {
|
||||
fromJS({ projects = [], permissions = [], ...rest }) {
|
||||
return {
|
||||
...rest,
|
||||
permissions: List(permissions)
|
||||
permissions: List(permissions),
|
||||
projects: List(projects),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@openreplay/tracker",
|
||||
"description": "The OpenReplay tracker main package",
|
||||
"version": "3.5.0",
|
||||
"version": "3.5.1",
|
||||
"keywords": [
|
||||
"logging",
|
||||
"replay"
|
||||
|
|
@ -41,6 +41,6 @@
|
|||
"error-stack-parser": "^2.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,10 +171,18 @@ export default class App {
|
|||
this.debug.error("OpenReplay error: ", context, e)
|
||||
}
|
||||
|
||||
private readonly preStartMessages: Message[] = []
|
||||
send(message: Message, urgent = false): void {
|
||||
if (this.activityState !== ActivityState.Active) {
|
||||
if (this.activityState === ActivityState.NotActive) {
|
||||
return;
|
||||
}
|
||||
if (this.activityState === ActivityState.Starting) {
|
||||
this.preStartMessages.push(message);
|
||||
}
|
||||
if (this.preStartMessages.length) {
|
||||
this.messages.push(...this.preStartMessages);
|
||||
this.preStartMessages.length = 0
|
||||
}
|
||||
this.messages.push(message);
|
||||
if (urgent) {
|
||||
this.commit();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:12.22-stretch
|
||||
FROM node:17-stretch
|
||||
WORKDIR /work
|
||||
COPY . .
|
||||
RUN npm install
|
||||
|
|
|
|||
11
utilities/package-lock.json
generated
11
utilities/package-lock.json
generated
|
|
@ -15,7 +15,8 @@
|
|||
"peer": "^0.6.1",
|
||||
"socket.io": "^4.4.1",
|
||||
"source-map": "^0.7.3",
|
||||
"ua-parser-js": "^1.0.2"
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@maxmind/geoip2-node": {
|
||||
|
|
@ -1287,6 +1288,10 @@
|
|||
"uuid": "bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/uWebSockets.js": {
|
||||
"version": "20.6.0",
|
||||
"resolved": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5"
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
@ -2350,6 +2355,10 @@
|
|||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
},
|
||||
"uWebSockets.js": {
|
||||
"version": "git+ssh://git@github.com/uNetworking/uWebSockets.js.git#a58e810e47a23696410f6073c8c905dc38f75da5",
|
||||
"from": "uWebSockets.js@github:uNetworking/uWebSockets.js#v20.6.0"
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"peer": "^0.6.1",
|
||||
"socket.io": "^4.4.1",
|
||||
"source-map": "^0.7.3",
|
||||
"ua-parser-js": "^1.0.2"
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,32 +8,29 @@ const HOST = '0.0.0.0';
|
|||
const PORT = 9000;
|
||||
|
||||
var app = express();
|
||||
var wsapp = express();
|
||||
let debug = process.env.debug === "1" || false;
|
||||
const request_logger = (identity) => {
|
||||
return (req, res, next) => {
|
||||
console.log(identity,new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl);
|
||||
debug && console.log(identity, new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl);
|
||||
res.on('finish', function () {
|
||||
console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode);
|
||||
if (this.statusCode !== 200 || debug) {
|
||||
console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode);
|
||||
}
|
||||
})
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
app.use(request_logger("[app]"));
|
||||
wsapp.use(request_logger("[wsapp]"));
|
||||
|
||||
app.use('/sourcemaps', sourcemapsReaderServer);
|
||||
app.use('/assist', peerRouter);
|
||||
wsapp.use('/assist', socket.wsRouter);
|
||||
|
||||
const server = app.listen(PORT, HOST, () => {
|
||||
console.log(`App listening on http://${HOST}:${PORT}`);
|
||||
console.log('Press Ctrl+C to quit.');
|
||||
});
|
||||
const wsserver = wsapp.listen(PORT + 1, HOST, () => {
|
||||
console.log(`WS App listening on http://${HOST}:${PORT + 1}`);
|
||||
console.log('Press Ctrl+C to quit.');
|
||||
});
|
||||
|
||||
const peerServer = ExpressPeerServer(server, {
|
||||
debug: true,
|
||||
path: '/',
|
||||
|
|
@ -45,6 +42,38 @@ peerServer.on('disconnect', peerDisconnect);
|
|||
peerServer.on('error', peerError);
|
||||
app.use('/', peerServer);
|
||||
app.enable('trust proxy');
|
||||
wsapp.enable('trust proxy');
|
||||
socket.start(wsserver);
|
||||
module.exports = {wsserver, server};
|
||||
|
||||
|
||||
const {App} = require("uWebSockets.js");
|
||||
const PREFIX = process.env.prefix || '/assist'
|
||||
|
||||
const uapp = new App();
|
||||
|
||||
const healthFn = (res, req) => {
|
||||
res.writeStatus('200 OK').end('ok!');
|
||||
}
|
||||
uapp.get(PREFIX, healthFn);
|
||||
uapp.get(`${PREFIX}/`, healthFn);
|
||||
|
||||
const uWrapper = function (fn) {
|
||||
return (res, req) => fn(req, res);
|
||||
}
|
||||
uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-list`, uWrapper(socket.handlers.socketsList));
|
||||
uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-list/:projectKey`, uWrapper(socket.handlers.socketsListByProject));
|
||||
|
||||
uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-live`, uWrapper(socket.handlers.socketsLive));
|
||||
uapp.get(`${PREFIX}/${process.env.S3_KEY}/sockets-live/:projectKey`, uWrapper(socket.handlers.socketsLiveByProject));
|
||||
|
||||
|
||||
socket.start(uapp);
|
||||
|
||||
uapp.listen(HOST, PORT + 1, (token) => {
|
||||
if (!token) {
|
||||
console.warn("port already in use");
|
||||
}
|
||||
console.log(`WS App listening on http://${HOST}:${PORT + 1}`);
|
||||
console.log('Press Ctrl+C to quit.');
|
||||
});
|
||||
|
||||
|
||||
module.exports = {uapp, server};
|
||||
|
|
|
|||
53
utilities/server_back.js
Normal file
53
utilities/server_back.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
var sourcemapsReaderServer = require('./servers/sourcemaps-server');
|
||||
var {peerRouter, peerConnection, peerDisconnect, peerError} = require('./servers/peerjs-server');
|
||||
var express = require('express');
|
||||
const {ExpressPeerServer} = require('peer');
|
||||
const socket = require("./servers/websocket");
|
||||
|
||||
const HOST = '0.0.0.0';
|
||||
const PORT = 9000;
|
||||
|
||||
var app = express();
|
||||
var wsapp = express();
|
||||
let debug = process.env.debug === "1" || false;
|
||||
const request_logger = (identity) => {
|
||||
return (req, res, next) => {
|
||||
debug && console.log(identity, new Date().toTimeString(), 'REQUEST', req.method, req.originalUrl);
|
||||
res.on('finish', function () {
|
||||
if (this.statusCode !== 200 || debug) {
|
||||
console.log(new Date().toTimeString(), 'RESPONSE', req.method, req.originalUrl, this.statusCode);
|
||||
}
|
||||
})
|
||||
|
||||
next();
|
||||
}
|
||||
};
|
||||
app.use(request_logger("[app]"));
|
||||
wsapp.use(request_logger("[wsapp]"));
|
||||
|
||||
app.use('/sourcemaps', sourcemapsReaderServer);
|
||||
app.use('/assist', peerRouter);
|
||||
wsapp.use('/assist', socket.wsRouter);
|
||||
|
||||
const server = app.listen(PORT, HOST, () => {
|
||||
console.log(`App listening on http://${HOST}:${PORT}`);
|
||||
console.log('Press Ctrl+C to quit.');
|
||||
});
|
||||
const wsserver = wsapp.listen(PORT + 1, HOST, () => {
|
||||
console.log(`WS App listening on http://${HOST}:${PORT + 1}`);
|
||||
console.log('Press Ctrl+C to quit.');
|
||||
});
|
||||
const peerServer = ExpressPeerServer(server, {
|
||||
debug: true,
|
||||
path: '/',
|
||||
proxied: true,
|
||||
allow_discovery: false
|
||||
});
|
||||
peerServer.on('connection', peerConnection);
|
||||
peerServer.on('disconnect', peerDisconnect);
|
||||
peerServer.on('error', peerError);
|
||||
app.use('/', peerServer);
|
||||
app.enable('trust proxy');
|
||||
wsapp.enable('trust proxy');
|
||||
socket.start(wsserver);
|
||||
module.exports = {wsserver, server};
|
||||
|
|
@ -1,9 +1,7 @@
|
|||
const _io = require('socket.io');
|
||||
const express = require('express');
|
||||
const uaParser = require('ua-parser-js');
|
||||
const geoip2Reader = require('@maxmind/geoip2-node').Reader;
|
||||
var {extractPeerId} = require('./peerjs-server');
|
||||
var wsRouter = express.Router();
|
||||
const IDENTITIES = {agent: 'agent', session: 'session'};
|
||||
const NEW_AGENT = "NEW_AGENT";
|
||||
const NO_AGENTS = "NO_AGENT";
|
||||
|
|
@ -11,12 +9,12 @@ const AGENT_DISCONNECT = "AGENT_DISCONNECTED";
|
|||
const AGENTS_CONNECTED = "AGENTS_CONNECTED";
|
||||
const NO_SESSIONS = "SESSION_DISCONNECTED";
|
||||
const SESSION_ALREADY_CONNECTED = "SESSION_ALREADY_CONNECTED";
|
||||
// const wsReconnectionTimeout = process.env.wsReconnectionTimeout | 10 * 1000;
|
||||
|
||||
let io;
|
||||
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) {
|
||||
console.log("[WS]looking for all available sessions");
|
||||
let debug = process.env.debug === "1" || false;
|
||||
const socketsList = function (req, res) {
|
||||
debug && console.log("[WS]looking for all available sessions");
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
|
|
@ -25,12 +23,11 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) {
|
|||
liveSessions[projectKey].push(sessionId);
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions}));
|
||||
});
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, res) {
|
||||
console.log(`[WS]looking for available sessions for ${req.params.projectKey}`);
|
||||
res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions}));
|
||||
}
|
||||
const socketsListByProject = function (req, res) {
|
||||
req.params = {projectKey: req.getParameter(0)};
|
||||
debug && console.log(`[WS]looking for available sessions for ${req.params.projectKey}`);
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
|
|
@ -39,13 +36,10 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, r
|
|||
liveSessions[projectKey].push(sessionId);
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
});
|
||||
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) {
|
||||
console.log("[WS]looking for all available LIVE sessions");
|
||||
res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
}
|
||||
const socketsLive = async function (req, res) {
|
||||
debug && console.log("[WS]looking for all available LIVE sessions");
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
|
|
@ -59,13 +53,11 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions}));
|
||||
});
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (req, res) {
|
||||
console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`);
|
||||
res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions}));
|
||||
}
|
||||
const socketsLiveByProject = async function (req, res) {
|
||||
req.params = {projectKey: req.getParameter(0)};
|
||||
debug && console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`);
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
|
|
@ -79,10 +71,8 @@ wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (
|
|||
}
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
});
|
||||
res.writeStatus('200 OK').writeHeader('Content-Type', 'application/json').end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
}
|
||||
|
||||
const findSessionSocketId = async (io, peerId) => {
|
||||
const connected_sockets = await io.in(peerId).fetchSockets();
|
||||
|
|
@ -128,8 +118,8 @@ async function get_all_agents_ids(io, socket) {
|
|||
|
||||
function extractSessionInfo(socket) {
|
||||
if (socket.handshake.query.sessionInfo !== undefined) {
|
||||
console.log("received headers");
|
||||
console.log(socket.handshake.headers);
|
||||
debug && console.log("received headers");
|
||||
debug && console.log(socket.handshake.headers);
|
||||
socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo);
|
||||
|
||||
let ua = uaParser(socket.handshake.headers['user-agent']);
|
||||
|
|
@ -146,8 +136,8 @@ function extractSessionInfo(socket) {
|
|||
// console.log("Looking for MMDB file in " + process.env.MAXMINDDB_FILE);
|
||||
geoip2Reader.open(process.env.MAXMINDDB_FILE, options)
|
||||
.then(reader => {
|
||||
console.log("looking for location of ");
|
||||
console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address);
|
||||
debug && console.log("looking for location of ");
|
||||
debug && console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address);
|
||||
let country = reader.country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address);
|
||||
socket.handshake.query.sessionInfo.userCountry = country.country.isoCode;
|
||||
})
|
||||
|
|
@ -158,19 +148,22 @@ function extractSessionInfo(socket) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
wsRouter,
|
||||
start: (server) => {
|
||||
io = _io(server, {
|
||||
maxHttpBufferSize: 7e6,
|
||||
io = new _io.Server({
|
||||
maxHttpBufferSize: 1e6,
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST", "PUT"]
|
||||
},
|
||||
path: '/socket'
|
||||
path: '/ws-assist/socket',
|
||||
transports: ['websocket'],
|
||||
// upgrade: false
|
||||
});
|
||||
io.attachApp(server);
|
||||
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
|
||||
debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
|
||||
socket.peerId = socket.handshake.query.peerId;
|
||||
socket.identity = socket.handshake.query.identity;
|
||||
const {projectKey, sessionId} = extractPeerId(socket.peerId);
|
||||
|
|
@ -180,24 +173,24 @@ module.exports = {
|
|||
let {c_sessions, c_agents} = await sessions_agents_count(io, socket);
|
||||
if (socket.identity === IDENTITIES.session) {
|
||||
if (c_sessions > 0) {
|
||||
console.log(`session already connected, refusing new connexion`);
|
||||
debug && console.log(`session already connected, refusing new connexion`);
|
||||
io.to(socket.id).emit(SESSION_ALREADY_CONNECTED);
|
||||
return socket.disconnect();
|
||||
}
|
||||
extractSessionInfo(socket);
|
||||
if (c_agents > 0) {
|
||||
console.log(`notifying new session about agent-existence`);
|
||||
debug && console.log(`notifying new session about agent-existence`);
|
||||
let agents_ids = await get_all_agents_ids(io, socket);
|
||||
io.to(socket.id).emit(AGENTS_CONNECTED, agents_ids);
|
||||
}
|
||||
|
||||
} else if (c_sessions <= 0) {
|
||||
console.log(`notifying new agent about no SESSIONS`);
|
||||
debug && console.log(`notifying new agent about no SESSIONS`);
|
||||
io.to(socket.id).emit(NO_SESSIONS);
|
||||
}
|
||||
socket.join(socket.peerId);
|
||||
if (io.sockets.adapter.rooms.get(socket.peerId)) {
|
||||
console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
}
|
||||
if (socket.identity === IDENTITIES.agent) {
|
||||
if (socket.handshake.query.agentInfo !== undefined) {
|
||||
|
|
@ -208,23 +201,23 @@ module.exports = {
|
|||
|
||||
socket.on('disconnect', async () => {
|
||||
// console.log(`${socket.id} disconnected from ${socket.peerId}, waiting ${wsReconnectionTimeout / 1000}s before checking remaining`);
|
||||
console.log(`${socket.id} disconnected from ${socket.peerId}`);
|
||||
debug && console.log(`${socket.id} disconnected from ${socket.peerId}`);
|
||||
if (socket.identity === IDENTITIES.agent) {
|
||||
socket.to(socket.peerId).emit(AGENT_DISCONNECT, socket.id);
|
||||
}
|
||||
// wait a little bit before notifying everyone
|
||||
// setTimeout(async () => {
|
||||
console.log("checking for number of connected agents and sessions");
|
||||
debug && console.log("checking for number of connected agents and sessions");
|
||||
let {c_sessions, c_agents} = await sessions_agents_count(io, socket);
|
||||
if (c_sessions === -1 && c_agents === -1) {
|
||||
console.log(`room not found: ${socket.peerId}`);
|
||||
debug && console.log(`room not found: ${socket.peerId}`);
|
||||
}
|
||||
if (c_sessions === 0) {
|
||||
console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`);
|
||||
debug && console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`);
|
||||
socket.to(socket.peerId).emit(NO_SESSIONS);
|
||||
}
|
||||
if (c_agents === 0) {
|
||||
console.log(`notifying everyone in ${socket.peerId} about no AGENTS`);
|
||||
debug && console.log(`notifying everyone in ${socket.peerId} about no AGENTS`);
|
||||
socket.to(socket.peerId).emit(NO_AGENTS);
|
||||
}
|
||||
|
||||
|
|
@ -235,22 +228,52 @@ module.exports = {
|
|||
socket.onAny(async (eventName, ...args) => {
|
||||
socket.lastMessageReceivedAt = Date.now();
|
||||
if (socket.identity === IDENTITIES.session) {
|
||||
console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
socket.to(socket.peerId).emit(eventName, args[0]);
|
||||
} else {
|
||||
console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
let socketId = await findSessionSocketId(io, socket.peerId);
|
||||
if (socketId === null) {
|
||||
console.log(`session not found for:${socket.peerId}`);
|
||||
debug && console.log(`session not found for:${socket.peerId}`);
|
||||
io.to(socket.id).emit(NO_SESSIONS);
|
||||
} else {
|
||||
console.log("message sent");
|
||||
debug && console.log("message sent");
|
||||
io.to(socketId).emit(eventName, socket.id, args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
console.log("WS server started")
|
||||
console.log("WS server started");
|
||||
debug ? console.log("Debugging enabled.") : console.log("Debugging disabled, set debug=\"1\" to enable debugging.");
|
||||
|
||||
setInterval((io) => {
|
||||
try {
|
||||
let count = 0;
|
||||
console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `);
|
||||
const arr = Array.from(io.sockets.adapter.rooms)
|
||||
const filtered = arr.filter(room => !room[1].has(room[0]))
|
||||
for (let i of filtered) {
|
||||
let {projectKey, sessionId} = extractPeerId(i[0]);
|
||||
if (projectKey !== null && sessionId !== null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
console.log(` ====== Valid Rooms: ${count} ====== `);
|
||||
if (debug) {
|
||||
for (let item of filtered) {
|
||||
console.log(`Room: ${item[0]} connected: ${item[1].size}`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 20000, io);
|
||||
},
|
||||
handlers: {
|
||||
socketsList,
|
||||
socketsListByProject,
|
||||
socketsLive,
|
||||
socketsLiveByProject
|
||||
}
|
||||
};
|
||||
272
utilities/servers/websocket_back.js
Normal file
272
utilities/servers/websocket_back.js
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
const _io = require('socket.io');
|
||||
const express = require('express');
|
||||
const uaParser = require('ua-parser-js');
|
||||
const geoip2Reader = require('@maxmind/geoip2-node').Reader;
|
||||
var {extractPeerId} = require('./peerjs-server');
|
||||
var wsRouter = express.Router();
|
||||
const IDENTITIES = {agent: 'agent', session: 'session'};
|
||||
const NEW_AGENT = "NEW_AGENT";
|
||||
const NO_AGENTS = "NO_AGENT";
|
||||
const AGENT_DISCONNECT = "AGENT_DISCONNECTED";
|
||||
const AGENTS_CONNECTED = "AGENTS_CONNECTED";
|
||||
const NO_SESSIONS = "SESSION_DISCONNECTED";
|
||||
const SESSION_ALREADY_CONNECTED = "SESSION_ALREADY_CONNECTED";
|
||||
// const wsReconnectionTimeout = process.env.wsReconnectionTimeout | 10 * 1000;
|
||||
|
||||
let io;
|
||||
let debug = process.env.debug === "1" || false;
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-list`, function (req, res) {
|
||||
debug && console.log("[WS]looking for all available sessions");
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
if (projectKey !== undefined) {
|
||||
liveSessions[projectKey] = liveSessions[projectKey] || [];
|
||||
liveSessions[projectKey].push(sessionId);
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions}));
|
||||
});
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-list/:projectKey`, function (req, res) {
|
||||
debug && console.log(`[WS]looking for available sessions for ${req.params.projectKey}`);
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
if (projectKey === req.params.projectKey) {
|
||||
liveSessions[projectKey] = liveSessions[projectKey] || [];
|
||||
liveSessions[projectKey].push(sessionId);
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
});
|
||||
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-live`, async function (req, res) {
|
||||
debug && console.log("[WS]looking for all available LIVE sessions");
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
if (projectKey !== undefined) {
|
||||
let connected_sockets = await io.in(peerId).fetchSockets();
|
||||
for (let item of connected_sockets) {
|
||||
if (item.handshake.query.identity === IDENTITIES.session) {
|
||||
liveSessions[projectKey] = liveSessions[projectKey] || [];
|
||||
liveSessions[projectKey].push(item.handshake.query.sessionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions}));
|
||||
});
|
||||
wsRouter.get(`/${process.env.S3_KEY}/sockets-live/:projectKey`, async function (req, res) {
|
||||
debug && console.log(`[WS]looking for available LIVE sessions for ${req.params.projectKey}`);
|
||||
let liveSessions = {};
|
||||
for (let peerId of io.sockets.adapter.rooms.keys()) {
|
||||
let {projectKey, sessionId} = extractPeerId(peerId);
|
||||
if (projectKey === req.params.projectKey) {
|
||||
let connected_sockets = await io.in(peerId).fetchSockets();
|
||||
for (let item of connected_sockets) {
|
||||
if (item.handshake.query.identity === IDENTITIES.session) {
|
||||
liveSessions[projectKey] = liveSessions[projectKey] || [];
|
||||
liveSessions[projectKey].push(item.handshake.query.sessionInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({"data": liveSessions[req.params.projectKey] || []}));
|
||||
});
|
||||
|
||||
const findSessionSocketId = async (io, peerId) => {
|
||||
const connected_sockets = await io.in(peerId).fetchSockets();
|
||||
for (let item of connected_sockets) {
|
||||
if (item.handshake.query.identity === IDENTITIES.session) {
|
||||
return item.id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
async function sessions_agents_count(io, socket) {
|
||||
let c_sessions = 0, c_agents = 0;
|
||||
if (io.sockets.adapter.rooms.get(socket.peerId)) {
|
||||
const connected_sockets = await io.in(socket.peerId).fetchSockets();
|
||||
|
||||
for (let item of connected_sockets) {
|
||||
if (item.handshake.query.identity === IDENTITIES.session) {
|
||||
c_sessions++;
|
||||
} else {
|
||||
c_agents++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c_agents = -1;
|
||||
c_sessions = -1;
|
||||
}
|
||||
return {c_sessions, c_agents};
|
||||
}
|
||||
|
||||
async function get_all_agents_ids(io, socket) {
|
||||
let agents = [];
|
||||
if (io.sockets.adapter.rooms.get(socket.peerId)) {
|
||||
const connected_sockets = await io.in(socket.peerId).fetchSockets();
|
||||
for (let item of connected_sockets) {
|
||||
if (item.handshake.query.identity === IDENTITIES.agent) {
|
||||
agents.push(item.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return agents;
|
||||
}
|
||||
|
||||
function extractSessionInfo(socket) {
|
||||
if (socket.handshake.query.sessionInfo !== undefined) {
|
||||
debug && console.log("received headers");
|
||||
debug && console.log(socket.handshake.headers);
|
||||
socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo);
|
||||
|
||||
let ua = uaParser(socket.handshake.headers['user-agent']);
|
||||
socket.handshake.query.sessionInfo.userOs = ua.os.name || null;
|
||||
socket.handshake.query.sessionInfo.userBrowser = ua.browser.name || null;
|
||||
socket.handshake.query.sessionInfo.userBrowserVersion = ua.browser.version || null;
|
||||
socket.handshake.query.sessionInfo.userDevice = ua.device.model || null;
|
||||
socket.handshake.query.sessionInfo.userDeviceType = ua.device.type || 'desktop';
|
||||
socket.handshake.query.sessionInfo.userCountry = null;
|
||||
|
||||
const options = {
|
||||
// you can use options like `cache` or `watchForUpdates`
|
||||
};
|
||||
// console.log("Looking for MMDB file in " + process.env.MAXMINDDB_FILE);
|
||||
geoip2Reader.open(process.env.MAXMINDDB_FILE, options)
|
||||
.then(reader => {
|
||||
debug && console.log("looking for location of ");
|
||||
debug && console.log(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address);
|
||||
let country = reader.country(socket.handshake.headers['x-forwarded-for'] || socket.handshake.address);
|
||||
socket.handshake.query.sessionInfo.userCountry = country.country.isoCode;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
wsRouter,
|
||||
start: (server) => {
|
||||
io = _io(server, {
|
||||
maxHttpBufferSize: 1e6,
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST", "PUT"]
|
||||
},
|
||||
path: '/socket'
|
||||
});
|
||||
|
||||
io.on('connection', async (socket) => {
|
||||
debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
|
||||
socket.peerId = socket.handshake.query.peerId;
|
||||
socket.identity = socket.handshake.query.identity;
|
||||
const {projectKey, sessionId} = extractPeerId(socket.peerId);
|
||||
socket.sessionId = sessionId;
|
||||
socket.projectKey = projectKey;
|
||||
socket.lastMessageReceivedAt = Date.now();
|
||||
let {c_sessions, c_agents} = await sessions_agents_count(io, socket);
|
||||
if (socket.identity === IDENTITIES.session) {
|
||||
if (c_sessions > 0) {
|
||||
debug && console.log(`session already connected, refusing new connexion`);
|
||||
io.to(socket.id).emit(SESSION_ALREADY_CONNECTED);
|
||||
return socket.disconnect();
|
||||
}
|
||||
extractSessionInfo(socket);
|
||||
if (c_agents > 0) {
|
||||
debug && console.log(`notifying new session about agent-existence`);
|
||||
let agents_ids = await get_all_agents_ids(io, socket);
|
||||
io.to(socket.id).emit(AGENTS_CONNECTED, agents_ids);
|
||||
}
|
||||
|
||||
} else if (c_sessions <= 0) {
|
||||
debug && console.log(`notifying new agent about no SESSIONS`);
|
||||
io.to(socket.id).emit(NO_SESSIONS);
|
||||
}
|
||||
socket.join(socket.peerId);
|
||||
if (io.sockets.adapter.rooms.get(socket.peerId)) {
|
||||
debug && console.log(`${socket.id} joined room:${socket.peerId}, as:${socket.identity}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
}
|
||||
if (socket.identity === IDENTITIES.agent) {
|
||||
if (socket.handshake.query.agentInfo !== undefined) {
|
||||
socket.handshake.query.agentInfo = JSON.parse(socket.handshake.query.agentInfo);
|
||||
}
|
||||
socket.to(socket.peerId).emit(NEW_AGENT, socket.id, socket.handshake.query.agentInfo);
|
||||
}
|
||||
|
||||
socket.on('disconnect', async () => {
|
||||
debug && console.log(`${socket.id} disconnected from ${socket.peerId}`);
|
||||
if (socket.identity === IDENTITIES.agent) {
|
||||
socket.to(socket.peerId).emit(AGENT_DISCONNECT, socket.id);
|
||||
}
|
||||
debug && console.log("checking for number of connected agents and sessions");
|
||||
let {c_sessions, c_agents} = await sessions_agents_count(io, socket);
|
||||
if (c_sessions === -1 && c_agents === -1) {
|
||||
debug && console.log(`room not found: ${socket.peerId}`);
|
||||
}
|
||||
if (c_sessions === 0) {
|
||||
debug && console.log(`notifying everyone in ${socket.peerId} about no SESSIONS`);
|
||||
socket.to(socket.peerId).emit(NO_SESSIONS);
|
||||
}
|
||||
if (c_agents === 0) {
|
||||
debug && console.log(`notifying everyone in ${socket.peerId} about no AGENTS`);
|
||||
socket.to(socket.peerId).emit(NO_AGENTS);
|
||||
}
|
||||
});
|
||||
|
||||
socket.onAny(async (eventName, ...args) => {
|
||||
socket.lastMessageReceivedAt = Date.now();
|
||||
if (socket.identity === IDENTITIES.session) {
|
||||
debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to room:${socket.peerId}, members: ${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
socket.to(socket.peerId).emit(eventName, args[0]);
|
||||
} else {
|
||||
debug && console.log(`received event:${eventName}, from:${socket.identity}, sending message to session of room:${socket.peerId}, members:${io.sockets.adapter.rooms.get(socket.peerId).size}`);
|
||||
let socketId = await findSessionSocketId(io, socket.peerId);
|
||||
if (socketId === null) {
|
||||
debug && console.log(`session not found for:${socket.peerId}`);
|
||||
io.to(socket.id).emit(NO_SESSIONS);
|
||||
} else {
|
||||
debug && console.log("message sent");
|
||||
io.to(socketId).emit(eventName, socket.id, args[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
console.log("WS server started")
|
||||
setInterval((io) => {
|
||||
try {
|
||||
let count = 0;
|
||||
console.log(` ====== Rooms: ${io.sockets.adapter.rooms.size} ====== `);
|
||||
const arr = Array.from(io.sockets.adapter.rooms)
|
||||
const filtered = arr.filter(room => !room[1].has(room[0]))
|
||||
for (let i of filtered) {
|
||||
let {projectKey, sessionId} = extractPeerId(i[0]);
|
||||
if (projectKey !== null && sessionId !== null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
console.log(` ====== Valid Rooms: ${count} ====== `);
|
||||
if (debug) {
|
||||
for (let item of filtered) {
|
||||
console.log(`Room: ${item[0]} connected: ${item[1].size}`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, 20000, io);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue