Merge branch 'dev' into dynamic-css-support

This commit is contained in:
Alex K 2022-09-14 13:14:21 +02:00 committed by GitHub
commit 90a77f739f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 636 additions and 473 deletions

View file

@ -80,12 +80,7 @@ def get_traces_group(project_id, payload):
payloads = {}
all_exists = True
for i, u in enumerate(frames):
print("===============================")
print(u["absPath"])
print("converted to:")
key = __get_key(project_id, u["absPath"]) # use filename instead?
print(key)
print("===============================")
if key not in payloads:
file_exists = s3.exists(config('sourcemaps_bucket'), key)
all_exists = all_exists and file_exists

View file

@ -9,13 +9,21 @@ def get_original_trace(key, positions):
"positions": positions,
"padding": 5,
"bucket": config('sourcemaps_bucket'),
"S3_HOST": config('S3_HOST'),
"S3_KEY": config('S3_KEY'),
"S3_SECRET": config('S3_SECRET'),
"region": config('sessions_region')
"S3_KEY": config('S3_KEY', default=config('AWS_ACCESS_KEY_ID')),
"S3_SECRET": config('S3_SECRET', default=config('AWS_SECRET_ACCESS_KEY')),
"region": config('sessions_region', default=config('AWS_DEFAULT_REGION'))
}
r = requests.post(config("sourcemaps_reader"), json=payload)
if r.status_code != 200:
if len(config('S3_HOST', default="")) > 0:
payload["S3_HOST"] = config('S3_HOST')
try:
r = requests.post(config("sourcemaps_reader"), json=payload,
timeout=config("sourcemapTimeout", cast=int, default=5))
if r.status_code != 200:
return {}
return r.json()
except requests.exceptions.Timeout:
print("Timeout getting sourcemap")
return {}
except Exception as e:
print("issue getting sourcemap")
return {}
return r.json()

View file

@ -145,7 +145,7 @@ func main() {
pgDur := time.Now().Sub(start).Milliseconds()
start = time.Now()
if err := saver.CommitStats(); err != nil {
if err := saver.CommitStats(consumer.HasFirstPartition()); err != nil {
log.Printf("Error on stats commit: %v", err)
}
chDur := time.Now().Sub(start).Milliseconds()

View file

@ -22,6 +22,6 @@ func (si *Saver) InsertStats(session *Session, msg Message) error {
return nil
}
func (si *Saver) CommitStats() error {
func (si *Saver) CommitStats(optimize bool) error {
return nil
}

View file

@ -9,6 +9,7 @@ type Consumer interface {
Commit() error
CommitBack(gap int64) error
Close()
HasFirstPartition() bool
}
type Producer interface {

View file

@ -161,3 +161,7 @@ func (c *Consumer) CommitBack(gap int64) error {
func (c *Consumer) Close() {
// noop
}
func (c *Consumer) HasFirstPartition() bool {
return false
}

View file

@ -42,3 +42,6 @@ if config("EXP_FUNNELS", cast=bool, default=False):
from . import significance_exp as significance
else:
from . import significance as significance
if config("EXP_RESOURCES", cast=bool, default=False):
print(">>> Using experimental resources for session-replay")

View file

@ -1,4 +1,4 @@
from chalicelib.utils import helper
from chalicelib.utils import helper, exp_ch_helper
from chalicelib.utils import ch_client
from chalicelib.utils.TimeUTC import TimeUTC
from decouple import config
@ -9,14 +9,26 @@ def get_by_session_id(session_id, project_id, start_ts, duration):
if duration is None or (type(duration) != 'int' and type(duration) != 'float') or duration < 0:
duration = 0
delta = config("events_ts_delta", cast=int, default=60 * 60) * 1000
ch_query = """\
SELECT
datetime,url,type,duration,ttfb,header_size,encoded_body_size,decoded_body_size,success,coalesce(status,if(success, 200, status)) AS status
FROM resources
WHERE session_id = toUInt64(%(session_id)s)
AND project_id=%(project_id)s
AND datetime >= toDateTime(%(res_start_ts)s / 1000)
AND datetime <= toDateTime(%(res_end_ts)s / 1000);"""
if config("EXP_RESOURCES", cast=bool, default=False):
ch_query = f"""SELECT
datetime,url,type,duration,ttfb,header_size,
encoded_body_size,decoded_body_size,success,
if(success, 200, 400) AS status
FROM {exp_ch_helper.get_main_resources_table(start_ts)}
WHERE session_id = toUInt64(%(session_id)s)
AND project_id = toUInt16(%(project_id)s)
AND datetime >= toDateTime(%(res_start_ts)s / 1000)
AND datetime <= toDateTime(%(res_end_ts)s / 1000);"""
else:
ch_query = """SELECT
datetime,url,type,duration,ttfb,header_size,
encoded_body_size,decoded_body_size,success,
coalesce(status,if(success, 200, status)) AS status
FROM resources
WHERE session_id = toUInt64(%(session_id)s)
AND project_id = toUInt64(%(project_id)s)
AND datetime >= toDateTime(%(res_start_ts)s / 1000)
AND datetime <= toDateTime(%(res_end_ts)s / 1000);"""
params = {"session_id": session_id, "project_id": project_id, "start_ts": start_ts, "duration": duration,
"res_start_ts": start_ts - delta, "res_end_ts": start_ts + duration + delta, }
rows = ch.execute(query=ch_query, params=params)

View file

@ -66,4 +66,5 @@ EXP_ERRORS_SEARCH=false
EXP_METRICS=false
EXP_7D_MV=false
EXP_ALERTS=false
EXP_FUNNELS=false
EXP_FUNNELS=false
EXP_RESOURCES=true

View file

@ -43,7 +43,10 @@ func (si *Saver) InsertStats(session *types.Session, msg messages.Message) error
return nil
}
func (si *Saver) CommitStats() error {
func (si *Saver) CommitStats(optimize bool) error {
if !optimize {
return si.ch.Commit()
}
select {
case <-finalizeTicker:
if err := si.ch.FinaliseSessionsTable(); err != nil {

View file

@ -194,3 +194,16 @@ func (consumer *Consumer) Close() {
log.Printf("Kafka consumer close error: %v", err)
}
}
func (consumer *Consumer) HasFirstPartition() bool {
assigned, err := consumer.c.Assignment()
if err != nil {
return false
}
for _, p := range assigned {
if p.Partition == 1 {
return true
}
}
return false
}

View file

@ -8,7 +8,7 @@ if (process.env.redis === "true") {
socket = require("./servers/websocket");
}
const HOST = '0.0.0.0';
const HOST = process.env.LISTEN_HOST || '0.0.0.0';
const PORT = process.env.LISTEN_PORT || 9001;
let debug = process.env.debug === "1" || false;

View file

@ -283,6 +283,7 @@ module.exports = {
start: (server, prefix) => {
createSocketIOServer(server, prefix);
io.on('connection', async (socket) => {
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
socket._connectedAt = new Date();
socket.peerId = socket.handshake.query.peerId;
@ -351,7 +352,6 @@ module.exports = {
socket.on(EVENTS_DEFINITION.listen.CONNECT_ERROR, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_ERROR, err));
socket.on(EVENTS_DEFINITION.listen.CONNECT_FAILED, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_FAILED, err));
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
socket.onAny(async (eventName, ...args) => {
if (Object.values(EVENTS_DEFINITION.listen).indexOf(eventName) >= 0) {

View file

@ -261,6 +261,7 @@ module.exports = {
start: (server, prefix) => {
createSocketIOServer(server, prefix);
io.on('connection', async (socket) => {
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
socket._connectedAt = new Date();
socket.peerId = socket.handshake.query.peerId;
@ -327,7 +328,6 @@ module.exports = {
socket.on(EVENTS_DEFINITION.listen.CONNECT_ERROR, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_ERROR, err));
socket.on(EVENTS_DEFINITION.listen.CONNECT_FAILED, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_FAILED, err));
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
socket.onAny(async (eventName, ...args) => {
if (Object.values(EVENTS_DEFINITION.listen).indexOf(eventName) >= 0) {

View file

@ -2,7 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { Input, Form, Button, Checkbox, Loader } from 'UI';
import SiteDropdown from 'Shared/SiteDropdown';
import { save, init, edit, remove, fetchList } from 'Duck/integrations/actions';
import { save, init, edit, remove } from 'Duck/integrations/actions';
import { fetchIntegrationList } from 'Duck/integrations/integrations';
@connect(
@ -21,7 +21,7 @@ import { fetchIntegrationList } from 'Duck/integrations/integrations';
init,
edit,
remove,
fetchList,
// fetchList,
fetchIntegrationList,
}
)
@ -33,6 +33,16 @@ export default class IntegrationForm extends React.PureComponent {
// this.init(currentSiteId);
}
fetchList = () => {
alert('calling...');
const { siteId, initialSiteId } = this.props;
if (!siteId) {
this.props.fetchIntegrationList(initialSiteId);
} else {
this.props.fetchIntegrationList(siteId);
}
}
write = ({ target: { value, name: key, type, checked } }) => {
if (type === 'checkbox') this.props.edit(this.props.name, { [key]: checked });
else this.props.edit(this.props.name, { [key]: value });
@ -57,6 +67,7 @@ export default class IntegrationForm extends React.PureComponent {
// const { currentSiteId } = this.state;
this.props.save(customPath || name, !ignoreProject ? this.props.siteId : null, config).then(() => {
// this.props.fetchList(name);
this.fetchList();
this.props.onClose();
if (isExists) return;
});
@ -67,7 +78,7 @@ export default class IntegrationForm extends React.PureComponent {
this.props.remove(name, !ignoreProject ? config.projectId : null).then(
function () {
this.props.onClose();
this.props.fetchList(name);
this.fetchList();
}.bind(this)
);
};

View file

@ -7,112 +7,126 @@ import { CLIENT_TABS, client as clientRoute } from 'App/routes';
import { withRouter } from 'react-router-dom';
function PreferencesMenu({ account, activeTab, history, isEnterprise }) {
const isAdmin = account.admin || account.superAdmin;
const setTab = (tab) => {
history.push(clientRoute(tab));
};
const isAdmin = account.admin || account.superAdmin;
const setTab = (tab) => {
history.push(clientRoute(tab));
};
return (
<div className={cn(stl.wrapper, 'h-full overflow-y-auto pb-24')}>
<div className={cn(stl.header, 'flex items-end')}>
<div className={stl.label}>
<span>Preferences</span>
</div>
</div>
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.PROFILE}
title="Account"
iconName="user-circle"
onClick={() => setTab(CLIENT_TABS.PROFILE)}
/>
</div>
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.INTEGRATIONS}
title="Integrations"
iconName="puzzle-piece"
onClick={() => setTab(CLIENT_TABS.INTEGRATIONS)}
/>
</div>
<div className="mb-2">
<SideMenuitem
iconName="tags"
active={activeTab === CLIENT_TABS.CUSTOM_FIELDS}
onClick={() => setTab(CLIENT_TABS.CUSTOM_FIELDS)}
title="Metadata"
/>
</div>
{
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.WEBHOOKS}
title="Webhooks"
iconName="anchor"
onClick={() => setTab(CLIENT_TABS.WEBHOOKS)}
/>
</div>
}
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.SITES}
title="Projects"
iconName="window-restore"
onClick={() => setTab(CLIENT_TABS.SITES)}
/>
</div>
{isEnterprise && isAdmin && (
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.MANAGE_ROLES}
title="Roles & Access"
iconName="diagram-3"
onClick={() => setTab(CLIENT_TABS.MANAGE_ROLES)}
/>
</div>
)}
{isEnterprise && isAdmin && (
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.AUDIT}
title="Audit"
iconName="list-ul"
onClick={() => setTab(CLIENT_TABS.AUDIT)}
/>
</div>
)}
{isAdmin && (
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.MANAGE_USERS}
title="Team"
iconName="users"
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS)}
/>
</div>
)}
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.NOTIFICATIONS}
title="Notifications"
iconName="bell"
onClick={() => setTab(CLIENT_TABS.NOTIFICATIONS)}
/>
</div>
return (
<div className={cn(stl.wrapper, 'h-full overflow-y-auto pb-24')}>
<div className={cn(stl.header, 'flex items-end')}>
<div className={stl.label}>
<span>Preferences</span>
</div>
);
</div>
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.PROFILE}
title="Account"
iconName="user-circle"
onClick={() => setTab(CLIENT_TABS.PROFILE)}
/>
</div>
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.INTEGRATIONS}
title="Integrations"
iconName="puzzle-piece"
onClick={() => setTab(CLIENT_TABS.INTEGRATIONS)}
/>
</div>
<div className="mb-2">
<SideMenuitem
iconName="tags"
active={activeTab === CLIENT_TABS.CUSTOM_FIELDS}
onClick={() => setTab(CLIENT_TABS.CUSTOM_FIELDS)}
title="Metadata"
/>
</div>
{
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.WEBHOOKS}
title="Webhooks"
iconName="anchor"
onClick={() => setTab(CLIENT_TABS.WEBHOOKS)}
/>
</div>
}
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.SITES}
title="Projects"
iconName="window-restore"
onClick={() => setTab(CLIENT_TABS.SITES)}
/>
</div>
{isEnterprise && isAdmin && (
<div className="mb-2 relative">
<SideMenuitem
active={activeTab === CLIENT_TABS.MANAGE_ROLES}
title="Roles & Access"
iconName="diagram-3"
onClick={() => setTab(CLIENT_TABS.MANAGE_ROLES)}
leading={<AdminOnlyBadge />}
/>
</div>
)}
{isEnterprise && isAdmin && (
<div className="mb-2 relative">
<SideMenuitem
active={activeTab === CLIENT_TABS.AUDIT}
title="Audit"
iconName="list-ul"
onClick={() => setTab(CLIENT_TABS.AUDIT)}
leading={<AdminOnlyBadge />}
/>
</div>
)}
{isAdmin && (
<div className="mb-2 relative">
<SideMenuitem
active={activeTab === CLIENT_TABS.MANAGE_USERS}
title="Team"
iconName="users"
onClick={() => setTab(CLIENT_TABS.MANAGE_USERS)}
leading={<AdminOnlyBadge />}
/>
</div>
)}
<div className="mb-2">
<SideMenuitem
active={activeTab === CLIENT_TABS.NOTIFICATIONS}
title="Notifications"
iconName="bell"
onClick={() => setTab(CLIENT_TABS.NOTIFICATIONS)}
/>
</div>
</div>
);
}
export default connect((state) => ({
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
account: state.getIn(['user', 'account']),
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
account: state.getIn(['user', 'account']),
}))(withRouter(PreferencesMenu));
function AdminOnlyBadge() {
return (
<div
className="ml-1 rounded-full bg-gray-light text-xs flex items-center px-2 color-gray-medium"
style={{ marginTop: '', height: '20px', whiteSpace: 'nowrap' }}
>
Admin Only
</div>
);
}

View file

@ -1,7 +1,7 @@
.wrapper {
position: fixed;
top: 81px;
width: 200px;
width: 210px;
}
.header {

View file

@ -38,7 +38,7 @@ function Roles(props: Props) {
useEffect(() => {
if (removeErrors && removeErrors.size > 0) {
removeErrors.forEach((e) => {
removeErrors.forEach((e: any) => {
toast.error(e);
});
}
@ -47,21 +47,20 @@ function Roles(props: Props) {
};
}, [removeErrors]);
const closeModal = (showToastMessage) => {
if (showToastMessage) {
toast.success(showToastMessage);
props.fetchList();
}
setShowmModal(false);
setTimeout(() => {
init();
}, 100);
};
// const closeModal = (showToastMessage: boolean) => {
// if (showToastMessage) {
// toast.success(showToastMessage);
// props.fetchList();
// }
// // setShowmModal(false);
// setTimeout(() => {
// init();
// }, 100);
// };
const editHandler = (role: any) => {
init(role);
showModal(<RoleForm closeModal={hideModal} permissionsMap={permissionsMap} deleteHandler={deleteHandler} />, { right: true });
// setShowmModal(true);
};
const deleteHandler = async (role: any) => {
@ -71,7 +70,7 @@ function Roles(props: Props) {
confirmation: `Are you sure you want to remove this role?`,
})
) {
deleteRole(role.roleId);
deleteRole(role.roleId).then(hideModal);
}
};
@ -83,7 +82,7 @@ function Roles(props: Props) {
<div className="flex items-center mr-auto px-5 pt-5">
<h3 className={cn(stl.tabTitle, 'text-2xl')}>Roles and Access</h3>
<Popup content="You dont have the permissions to perform this action." disabled={isAdmin}>
<Button variant="primary" onClick={() => setShowmModal(true)}>Add</Button>
<Button variant="primary" onClick={() => editHandler({})}>Add</Button>
</Popup>
</div>
</div>
@ -123,7 +122,7 @@ function Roles(props: Props) {
export default connect(
(state: any) => {
const permissions = state.getIn(['roles', 'permissions']);
const permissionsMap = {};
const permissionsMap: any = {};
permissions.forEach((p: any) => {
permissionsMap[p.value] = p.text;
});

View file

@ -27,11 +27,15 @@ function UserListItem(props: Props) {
<div className="grid grid-cols-12 py-4 px-5 border-t items-center select-none hover:bg-active-blue group cursor-pointer" onClick={editHandler}>
<div className="col-span-5">
<span className="mr-2">{user.name}</span>
{isEnterprise && <AdminPrivilegeLabel user={user} />}
{/* {isEnterprise && <AdminPrivilegeLabel user={user} />} */}
</div>
<div className="col-span-3">
{!isEnterprise && <AdminPrivilegeLabel user={user} />}
{isEnterprise && <span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">{user.roleName}</span>}
{isEnterprise && (
<>
<span className="px-2 py-1 bg-gray-lightest rounded border text-sm capitalize">{user.roleName}</span>
{ user.isSuperAdmin || user.isAdmin && <><span className="ml-2" /><AdminPrivilegeLabel user={user} /></> }
</>)}
</div>
{!isOnboarding && (
<div className="col-span-2">

View file

@ -10,7 +10,7 @@
/* min-height: calc(100vh - 81px); */
& .tabMenu {
width: 240px;
width: 250px;
margin: 0;
background-color: $gray-lightest;
}

View file

@ -18,6 +18,7 @@ import SelectDateRange from 'Shared/SelectDateRange';
import { Tooltip } from 'react-tippy';
import Breadcrumb from 'Shared/Breadcrumb';
import AddMetricContainer from '../DashboardWidgetGrid/AddMetricContainer';
import OutsideClickDetectingDiv from 'Shared/OutsideClickDetectingDiv';
interface IProps {
siteId: string;
@ -32,6 +33,7 @@ function DashboardView(props: Props) {
const { dashboardStore } = useStore();
const { showModal } = useModal();
const [showTooltip, setShowTooltip] = React.useState(false);
const [focusTitle, setFocusedInput] = React.useState(true);
const [showEditModal, setShowEditModal] = React.useState(false);
@ -125,7 +127,8 @@ function DashboardView(props: Props) {
className="mr-3 select-none border-b border-b-borderColor-transparent hover:border-dotted hover:border-gray-medium cursor-pointer"
actionButton={
/* @ts-ignore */
<Tooltip
<Tooltip
open={showTooltip}
interactive
useContext
// @ts-ignore
@ -134,9 +137,15 @@ function DashboardView(props: Props) {
hideDelay={200}
duration={0}
distance={20}
html={<div style={{ padding: 0 }}><AddMetricContainer isPopup siteId={siteId} /></div>}
html={
<div style={{ padding: 0 }}>
<OutsideClickDetectingDiv onClickOutside={() => setShowTooltip(false)}>
<AddMetricContainer onAction={() => setShowTooltip(false)} isPopup siteId={siteId} />
</OutsideClickDetectingDiv>
</div>
}
>
<Button variant="primary">
<Button variant="primary" onClick={() => setShowTooltip(true)}>
Add Metric
</Button>
</Tooltip>

View file

@ -47,11 +47,18 @@ function AddMetricButton({ iconName, title, description, onClick, isPremade, isP
);
}
function AddMetricContainer({ siteId, isPopup }: any) {
interface Props {
siteId: string
isPopup: boolean
onAction: () => void
}
function AddMetricContainer({ siteId, isPopup, onAction }: Props) {
const { showModal } = useModal();
const { dashboardStore } = useStore();
const onAddCustomMetrics = () => {
onAction()
dashboardStore.initDashboard(dashboardStore.selectedDashboard);
showModal(
<AddMetric
@ -64,6 +71,7 @@ function AddMetricContainer({ siteId, isPopup }: any) {
};
const onAddPredefinedMetrics = () => {
onAction()
dashboardStore.initDashboard(dashboardStore.selectedDashboard);
showModal(
<AddPredefinedMetric

View file

@ -10,7 +10,7 @@ function ErrorDetailsModal(props: Props) {
style={{ width: '85vw', maxWidth: '1200px' }}
className="bg-white h-screen p-4 overflow-y-auto"
>
<ErrorInfo errorId={props.errorId} list={[]} />
<ErrorInfo errorId={props.errorId} />
</div>
);
}

View file

@ -11,6 +11,7 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
@connect(
(state) => ({
errorIdInStore: state.getIn(['errors', 'instance']).errorId,
list: state.getIn(['errors', 'instanceTrace']),
loading: state.getIn(['errors', 'fetch', 'loading']) || state.getIn(['errors', 'fetchTrace', 'loading']),
errorOnFetch: state.getIn(['errors', 'fetch', 'errors']) || state.getIn(['errors', 'fetchTrace', 'errors']),
}),

View file

@ -63,6 +63,7 @@ export default class MainSection extends React.PureComponent {
render() {
const { error, trace, sourcemapUploaded, ignoreLoading, resolveToggleLoading, toggleFavoriteLoading, className, traceLoading } = this.props;
const isPlayer = window.location.pathname.includes('/session/')
return (
<div className={cn(className, 'bg-white border-radius-3 thin-gray-border mb-6')}>
@ -143,15 +144,17 @@ export default class MainSection extends React.PureComponent {
/>
</div> */}
<Divider />
<div className="m-4">
<h3 className="text-xl inline-block mr-2">Last session with this error</h3>
<span className="font-thin text-sm">{resentOrDate(error.lastOccurrence)}</span>
<SessionBar className="my-4" session={error.lastHydratedSession} />
<Button variant="text-primary" onClick={this.findSessions}>
Find all sessions with this error
<Icon className="ml-1" name="next1" color="teal" />
</Button>
</div>
{!isPlayer && (
<div className="m-4">
<h3 className="text-xl inline-block mr-2">Last session with this error</h3>
<span className="font-thin text-sm">{resentOrDate(error.lastOccurrence)}</span>
<SessionBar className="my-4" session={error.lastHydratedSession} />
<Button variant="text-primary" onClick={this.findSessions}>
Find all sessions with this error
<Icon className="ml-1" name="next1" color="teal" />
</Button>
</div>
)}
<Divider />
<div className="m-4">
<Loader loading={traceLoading}>

View file

@ -113,7 +113,7 @@ export default class Autoscroll extends React.PureComponent<Props, {
</div>
<div className={stl.navButtons}>
<label><input type={'checkbox'} checked={this.state.autoScroll} onChange={(e) => this.setState({ autoScroll: !this.state.autoScroll })} /> Autoscroll</label>
{/* <label><input type={'checkbox'} checked={this.state.autoScroll} onChange={(e) => this.setState({ autoScroll: !this.state.autoScroll })} /> Autoscroll</label> */}
{navigation && (
<>
<IconButton size="small" icon="chevron-up" onClick={this.onPrevClick} />

View file

@ -140,7 +140,7 @@ export default class Exceptions extends React.PureComponent {
error={e}
key={e.key}
selected={lastIndex === index}
inactive={index > lastIndex}
// inactive={index > lastIndex}
onErrorClick={(jsEvent) => {
jsEvent.stopPropagation();
jsEvent.preventDefault();

View file

@ -141,7 +141,6 @@ export default connect(
stackEventList: state.stackList,
performanceChartData: state.performanceChartData,
endTime: state.endTime,
// endTime: 30000000,
}))(OverviewPanel)
);

View file

@ -147,7 +147,7 @@ const VERY_LOW_FPS = 20;
const LOW_FPS_MARKER_VALUE = 5;
const HIDDEN_SCREEN_MARKER_VALUE = 20;
function addFpsMetadata(data) {
return data.map((point, i) => {
return [...data].map((point, i) => {
let fpsVeryLowMarker = null;
let fpsLowMarker = null;
let hiddenScreenMarker = 0;

View file

@ -11,9 +11,14 @@ export default class JsonViewer extends React.PureComponent {
<Icon name={icon} size="24" />
<h4 className="my-5 mx-2 font-semibold text-xl"> {title}</h4>
</div>
{isObjectData ? (
<JSONTree src={data} collapsed={false} />
) : (
{isObjectData && <JSONTree src={data} collapsed={false} />}
{!isObjectData && Array.isArray(data) && (
<div>
<div className="text-lg">{data[0]}</div>
<JSONTree src={data[1]} collapsed={false} />
</div>
)}
{typeof data === 'string' && (
<>
<div className="-ml-2 text-disabled-text">Payload: </div>
<div className="mx-2">{data}</div>

View file

@ -41,7 +41,7 @@ export default class UserEvent extends React.PureComponent {
// onClick={ this.props.switchOpen } //
onClick={this.props.onJump} //
className={cn('group flex py-2 px-4 ', stl.userEvent, this.getLevelClassname(), {
[stl.inactive]: inactive,
// [stl.inactive]: inactive,
[stl.selected]: selected,
})}
>

View file

@ -156,7 +156,7 @@ const ProjectCodeSnippet = props => {
export default connect(state => ({
// siteId: state.getIn([ 'site', 'siteId' ]),
site: state.getIn([ 'site', 'instance' ]),
// site: state.getIn([ 'site', 'instance' ]),
gdpr: state.getIn([ 'site', 'instance', 'gdpr' ]),
saving: state.getIn([ 'site', 'saveGDPR', 'loading' ])
}), { editGDPR, saveGDPR })(ProjectCodeSnippet)

View file

@ -25,7 +25,7 @@ class TrackingCodeModal extends React.PureComponent {
const { site } = this.props;
switch (this.state.activeTab) {
case PROJECT:
return <ProjectCodeSnippet />;
return <ProjectCodeSnippet site={site} />;
case DOCUMENTATION:
return <InstallDocs site={site} />;
}
@ -46,32 +46,14 @@ class TrackingCodeModal extends React.PureComponent {
<div className="p-5">{this.renderActiveTab()}</div>
</div>
</div>
// displayed &&
// <Modal size="large" onClose={ onClose } open={ displayed } style={{ top: "85px" }} >
// <Modal.Header className={ styles.modalHeader }>
// <div>{ title } { subTitle && <span className="text-sm color-gray-dark">{subTitle}</span>}</div>
// <div className={ cn(styles.closeButton, { 'hidden' : !onClose }) } role="button" tabIndex="-1" onClick={ onClose }>
// <Icon name="close" size="14" />
// </div>
// </Modal.Header>
// <Modal.Content className={ cn(styles.content, 'overflow-y-auto') }>
// <Tabs
// className="px-5"
// tabs={ TABS }
// active={ activeTab } onClick={ this.setActiveTab } />
// <div className="p-5">
// { this.renderActiveTab() }
// </div>
// </Modal.Content>
// </Modal>
);
}
}
export default connect(
(state) => ({
site: state.getIn(['site', 'instance']),
gdpr: state.getIn(['site', 'instance', 'gdpr']),
// site: state.getIn(['site', 'instance']),
// gdpr: state.getIn(['site', 'instance', 'gdpr']),
saving: state.getIn(['site', 'saveGDPR', 'loading']),
}),
{

View file

@ -18,6 +18,7 @@ function ErrorDetails(props: Props) {
const { error, sessionId, message = '', errorStack = [], sourcemapUploaded = false } = props;
const [showRaw, setShowRaw] = useState(false);
const firstFunc = errorStack.first() && errorStack.first().function;
const openDocs = () => {
window.open(docLink, '_blank');
@ -77,7 +78,8 @@ function ErrorDetails(props: Props) {
ErrorDetails.displayName = 'ErrorDetails';
export default connect(
(state: any) => ({
errorStack: state.getIn(['sessions', 'errorStack']),
// errorStack: state.getIn(['sessions', 'errorStack']),
errorStack: state.getIn(['errors', 'instanceTrace']),
sessionId: state.getIn(['sessions', 'current', 'sessionId']),
}),
{ fetchErrorStackList }

View file

@ -3,8 +3,15 @@ import cn from 'classnames';
import { IconButton } from 'UI';
import stl from './errorItem.module.css';
import { Duration } from 'luxon';
import { useModal } from 'App/components/Modal';
import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal';
function ErrorItem({ error = {}, onErrorClick, onJump, inactive, selected }) {
function ErrorItem({ error = {}, onJump, inactive, selected }) {
const { showModal } = useModal();
const onErrorClick = () => {
showModal(<ErrorDetailsModal errorId={error.errorId} />, { right: true });
}
return (
<div
className={cn(stl.wrapper, 'py-2 px-4 flex cursor-pointer', {

View file

@ -29,7 +29,7 @@ function SideMenuitem({
className={ cn(
className,
stl.menuItem,
"flex items-center py-2 justify-between",
"flex items-center py-2 justify-between shrink-0",
{ [stl.active] : active }
)}
onClick={disabled ? null : onClick}

View file

@ -49,6 +49,7 @@ export default class DOMManager extends ListWalker<Message> {
private vTexts: Map<number, VText> = new Map() // map vs object here?
private vElements: Map<number, VElement> = new Map()
private vRoots: Map<number, VShadowRoot | VDocument> = new Map()
private activeIframeRoots: Map<number, number> = new Map()
private styleSheets: Map<number, CSSStyleSheet> = new Map()
@ -145,7 +146,7 @@ export default class DOMManager extends ListWalker<Message> {
case "create_document":
doc = this.screen.document;
if (!doc) {
logger.error("No iframe document found", msg)
logger.error("No root iframe document found", msg)
return;
}
doc.open();
@ -162,6 +163,7 @@ export default class DOMManager extends ListWalker<Message> {
// this is done for the AdoptedCSS logic
// todo: start from 0 (sync logic with tracker)
this.stylesManager.reset()
this.activeIframeRoots.clear()
return
case "create_text_node":
vn = new VText()
@ -293,14 +295,18 @@ export default class DOMManager extends ListWalker<Message> {
vn.enforceInsertion()
const host = vn.node
if (host instanceof HTMLIFrameElement) {
const vDoc = new VDocument()
this.vRoots.set(msg.id, vDoc)
const doc = host.contentDocument
if (!doc) {
logger.warn("No iframe doc onload", msg, host)
logger.warn("No default iframe doc", msg, host)
return
}
vDoc.setDocument(doc)
// remove old root of the same iframe if present
const oldRootId = this.activeIframeRoots.get(msg.frameID)
oldRootId != null && this.vRoots.delete(oldRootId)
const vDoc = new VDocument(doc)
this.activeIframeRoots.set(msg.frameID, msg.id)
this.vRoots.set(msg.id, vDoc)
return;
} else if (host instanceof Element) { // shadow DOM
try {
@ -394,10 +400,12 @@ export default class DOMManager extends ListWalker<Message> {
this.nodeScrollManagers.forEach(manager => {
const msg = manager.moveGetLast(t)
if (msg) {
const vElm = this.vElements.get(msg.id)
if (vElm) {
vElm.node.scrollLeft = msg.x
vElm.node.scrollTop = msg.y
let vNode: VNode
if (vNode = this.vElements.get(msg.id)) {
vNode.node.scrollLeft = msg.x
vNode.node.scrollTop = msg.y
} else if ((vNode = this.vRoots.get(msg.id)) && vNode instanceof VDocument){
vNode.node.defaultView?.scrollTo(msg.x, msg.y)
}
}
})

View file

@ -54,10 +54,7 @@ abstract class VParent {
}
export class VDocument extends VParent {
constructor(public node: Document | null = null) { super() }
setDocument(doc: Document) {
this.node = doc
}
constructor(public readonly node: Document) { super() }
applyChanges() {
if (this.children.length > 1) {
// log err

View file

@ -36,7 +36,7 @@
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 4px;
}
}
}
.grecaptcha-badge{
@ -190,7 +190,7 @@
/* font-family: 'FontAwesome'; */
top: 10px;
left: 0;
content: "\201C";
font-size: 140px;
color: rgba(0,0,0,0.1);
@ -201,7 +201,7 @@
/* font-family: 'FontAwesome'; */
bottom: 10px;
right: 0;
content: "\201E";
font-size: 140px;
color: rgba(0,0,0,0.1);
@ -226,7 +226,7 @@
.blink-border {
/* border: 1px #ff0000 solid; */
border-color: #CC0000;
animation: blink 1s;
animation-iteration-count: 3;
}
@ -282,8 +282,18 @@ p {
padding: 0!important;
transition: none!important;
}
.tippy-notransition {
.tippy-tooltip[data-theme~='nopadding'] > .enter {
background-color: transparent!important;
}
.tippy-notransition, .tippy-notransition > * {
transition: none!important;
background-color: transparent!important;
will-change: unset!important;
}
.tippy-notransition > *[x-circle] {
display: none
}
@media print {

206
peers/package-lock.json generated
View file

@ -47,9 +47,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.28",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz",
"integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==",
"version": "4.17.30",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz",
"integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@ -57,14 +57,14 @@
}
},
"node_modules/@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"node_modules/@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
"version": "18.7.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
"integrity": "sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg=="
},
"node_modules/@types/qs": {
"version": "6.9.7",
@ -77,11 +77,11 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"node_modules/@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==",
"dependencies": {
"@types/mime": "^1",
"@types/mime": "*",
"@types/node": "*"
}
},
@ -130,7 +130,7 @@
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"node_modules/body-parser": {
"version": "1.20.0",
@ -175,6 +175,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -231,7 +239,7 @@
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/cors": {
"version": "2.8.5",
@ -256,7 +264,7 @@
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"engines": {
"node": ">=0.10.0"
}
@ -504,7 +512,7 @@
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"engines": {
"node": ">= 0.6"
}
@ -512,12 +520,12 @@
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"engines": {
"node": ">= 0.6"
}
@ -534,19 +542,19 @@
}
},
"node_modules/mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.51.0"
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
@ -555,7 +563,7 @@
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/negotiator": {
"version": "0.6.3",
@ -568,7 +576,7 @@
"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=",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
@ -644,7 +652,7 @@
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"node_modules/peer": {
"version": "0.6.1",
@ -668,15 +676,6 @@
"node": ">=10"
}
},
"node_modules/peer/node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -728,7 +727,7 @@
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"engines": {
"node": ">=0.10.0"
}
@ -807,7 +806,7 @@
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/setprototypeof": {
"version": "1.2.0",
@ -890,15 +889,24 @@
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
@ -906,7 +914,7 @@
"node_modules/which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
@ -922,9 +930,9 @@
}
},
"node_modules/ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"engines": {
"node": ">=8.3.0"
},
@ -978,14 +986,6 @@
"engines": {
"node": ">=6"
}
},
"node_modules/yargs-parser/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
}
},
"dependencies": {
@ -1023,9 +1023,9 @@
}
},
"@types/express-serve-static-core": {
"version": "4.17.28",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz",
"integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==",
"version": "4.17.30",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.30.tgz",
"integrity": "sha512-gstzbTWro2/nFed1WXtf+TtrpwxH7Ggs4RLYTLbeVgIkUQOI3WG/JKjgeOU1zXDvezllupjrf8OPIdvTbIaVOQ==",
"requires": {
"@types/node": "*",
"@types/qs": "*",
@ -1033,14 +1033,14 @@
}
},
"@types/mime": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
"integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA=="
},
"@types/node": {
"version": "17.0.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
"integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ=="
"version": "18.7.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz",
"integrity": "sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg=="
},
"@types/qs": {
"version": "6.9.7",
@ -1053,11 +1053,11 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
"integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==",
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==",
"requires": {
"@types/mime": "^1",
"@types/mime": "*",
"@types/node": "*"
}
},
@ -1094,7 +1094,7 @@
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
},
"body-parser": {
"version": "1.20.0",
@ -1129,6 +1129,11 @@
"get-intrinsic": "^1.0.2"
}
},
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -1173,7 +1178,7 @@
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"cors": {
"version": "2.8.5",
@ -1195,7 +1200,7 @@
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="
},
"depd": {
"version": "2.0.0",
@ -1382,17 +1387,17 @@
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
},
"merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="
},
"mime": {
"version": "1.6.0",
@ -1400,22 +1405,22 @@
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
},
"mime-db": {
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g=="
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.51.0"
"mime-db": "1.52.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"negotiator": {
"version": "0.6.3",
@ -1425,7 +1430,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="
},
"object-inspect": {
"version": "1.12.2",
@ -1474,7 +1479,7 @@
"path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
},
"peer": {
"version": "0.6.1",
@ -1490,13 +1495,6 @@
"uuid": "^3.4.0",
"ws": "^7.2.3",
"yargs": "^15.3.1"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
}
}
},
"proxy-addr": {
@ -1535,7 +1533,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
},
"require-main-filename": {
"version": "2.0.0",
@ -1593,7 +1591,7 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"setprototypeof": {
"version": "1.2.0",
@ -1655,17 +1653,22 @@
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
},
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
"integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q=="
},
"wrap-ansi": {
"version": "6.2.0",
@ -1678,9 +1681,9 @@
}
},
"ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
},
"y18n": {
@ -1713,13 +1716,6 @@
"requires": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"dependencies": {
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
}
}
}
}

View file

@ -4,8 +4,9 @@ const {peerRouter, peerConnection, peerDisconnect, peerError} = require('./serve
const express = require('express');
const {ExpressPeerServer} = require('peer');
const HOST = '0.0.0.0';
const PORT = 9000;
const debug = process.env.debug === "1" || false;
const HOST = process.env.LISTEN_HOST || '0.0.0.0';
const PORT = process.env.LISTEN_PORT || 9000;
const app = express();
@ -30,4 +31,10 @@ peerServer.on('disconnect', peerDisconnect);
peerServer.on('error', peerError);
app.use('/', peerServer);
app.enable('trust proxy');
module.exports = {server};
module.exports = {server};
process.on('uncaughtException', err => {
console.log(`Uncaught Exception: ${err.message}`);
debug && console.log(err.stack);
// process.exit(1);
});

View file

@ -37,7 +37,11 @@ const peerDisconnect = (client) => {
}
const peerError = (error) => {
console.error('error fired');
//https://peerjs.com/docs/#peeron-error
console.error('Error detected in Peers');
console.error('Error type:');
console.error(error.type);
console.error('Error message:');
console.error(error);
}

View file

@ -3,17 +3,17 @@ const sourcemapsReaderServer = require('./servers/sourcemaps-server');
const express = require('express');
const {request_logger} = require("./utils/helper");
const HOST = '0.0.0.0';
const PORT = 9000;
const HOST = process.env.SR_HOST || '127.0.0.1';
const PORT = process.env.SR_PORT || 9000;
const app = express();
app.use(request_logger("[wsapp]"));
app.use(request_logger("[SR]"));
app.use('/sourcemaps', sourcemapsReaderServer);
app.use('/heapdump', dumps.router);
const server = app.listen(PORT, HOST, () => {
console.log(`WS App listening on http://${HOST}:${PORT}`);
console.log(`SR App listening on http://${HOST}:${PORT}`);
console.log('Press Ctrl+C to quit.');
});
module.exports = {server};

View file

@ -42,8 +42,8 @@ module.exports.sourcemapReader = async event => {
return new Promise(function (resolve, reject) {
s3.getObject(options, (err, data) => {
if (err) {
console.log("Get S3 object failed");
console.log(err);
console.error("[SR] Get S3 object failed");
console.error(err);
return reject(err);
}
let sourcemap = data.Body.toString();
@ -68,13 +68,13 @@ module.exports.sourcemapReader = async event => {
preview = preview.slice(start, original.line + event.padding);
}
} else {
console.log("source not found, null preview for:");
console.log("[SR] source not found, null preview for:");
console.log(original.source);
preview = []
}
url = URL.parse(original.source);
} else {
console.log("couldn't find original position of:");
console.log("[SR] couldn't find original position of:");
console.log({
line: event.positions[i].line,
column: event.positions[i].column

View file

@ -9,7 +9,7 @@ router.post('/', (req, res) => {
});
req.on('end', function () {
data = JSON.parse(data);
console.log("Starting parser for: " + data.key);
console.log("[SR] Starting parser for: " + data.key);
// process.env = {...process.env, ...data.bucket_config};
handler.sourcemapReader(data)
.then((results) => {
@ -18,7 +18,7 @@ router.post('/', (req, res) => {
res.end(JSON.stringify(results));
})
.catch((e) => {
console.error("Something went wrong");
console.error("[SR] Something went wrong");
console.error(e);
res.statusCode(500);
res.end(e);

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-assist",
"description": "Tracker plugin for screen assistance through the WebRTC",
"version": "4.0.0",
"version": "4.0.1",
"keywords": [
"WebRTC",
"assistance",
@ -31,7 +31,7 @@
"socket.io-client": "^4.4.1"
},
"peerDependencies": {
"@openreplay/tracker": "^3.6.0"
"@openreplay/tracker": ">=3.6.0"
},
"devDependencies": {
"@openreplay/tracker": "file:../tracker",

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-axios",
"description": "Tracker plugin for axios requests recording",
"version": "3.6.0",
"version": "3.6.1",
"keywords": [
"axios",
"logging",
@ -20,7 +20,7 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.6.0",
"@openreplay/tracker": ">=3.6.0",
"axios": "0.x"
},
"devDependencies": {

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-fetch",
"description": "Tracker plugin for fetch requests recording ",
"version": "3.6.0",
"version": "3.6.1",
"keywords": [
"fetch",
"logging",
@ -23,7 +23,7 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.6.0"
"@openreplay/tracker": ">=3.6.0"
},
"devDependencies": {
"@openreplay/tracker": "file:../tracker",

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-graphql",
"description": "Tracker plugin for GraphQL requests recording",
"version": "3.0.0",
"version": "3.0.1",
"keywords": [
"graphql",
"logging",
@ -22,10 +22,10 @@
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"@openreplay/tracker": "^3.0.0"
"@openreplay/tracker": ">=3.0.0"
},
"devDependencies": {
"@openreplay/tracker": "^3.0.0",
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^3.6.4"

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-mobx",
"description": "Tracker plugin for MobX events recording",
"version": "3.0.0",
"version": "3.0.1",
"keywords": [
"mobx",
"logging",
@ -23,11 +23,11 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.0.0",
"@openreplay/tracker": ">=3.0.0",
"mobx": "^4.15.7"
},
"devDependencies": {
"@openreplay/tracker": "^3.0.3",
"@openreplay/tracker": "file:../tracker",
"mobx": "^4.15.7",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-ngrx",
"description": "Tracker plugin for NgRx state recording",
"version": "3.4.8",
"version": "3.4.9",
"keywords": [
"ngrx",
"logging",
@ -23,11 +23,11 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.4.8",
"@openreplay/tracker": ">=3.4.8",
"@ngrx/store": ">=4"
},
"devDependencies": {
"@openreplay/tracker": "^3.4.8",
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^4.6.0-dev.20211126"

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-profiler",
"description": "Tracker plugin for profiling JS function calls",
"version": "3.0.0",
"version": "3.0.1",
"keywords": [
"debugging",
"logging",
@ -22,10 +22,10 @@
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"@openreplay/tracker": "^3.0.0"
"@openreplay/tracker": ">=3.0.0"
},
"devDependencies": {
"@openreplay/tracker": "^3.0.0",
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^3.6.4"

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-redux",
"description": "Tracker plugin for Redux state recording",
"version": "3.5.0",
"version": "3.5.1",
"keywords": [
"redux",
"logging",
@ -23,11 +23,11 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.5.0",
"@openreplay/tracker": ">=3.5.0",
"redux": "^4.0.0"
},
"devDependencies": {
"@openreplay/tracker": "^3.5.0",
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^4.6.0-dev.20211126"

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-vuex",
"description": "Tracker plugin for Vuex state recording",
"version": "4.0.1",
"version": "4.0.2",
"keywords": [
"vuex",
"logging",
@ -23,10 +23,10 @@
},
"dependencies": {},
"peerDependencies": {
"@openreplay/tracker": "^3.4.8"
"@openreplay/tracker": ">=3.4.8"
},
"devDependencies": {
"@openreplay/tracker": "^3.4.8",
"@openreplay/tracker": "file:../tracker",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^4.6.0-dev.20211126"

View file

@ -21,6 +21,8 @@ const tracker = new Tracker({
});
const zustandPlugin = tracker.use(trackerZustand())
// store name, optional
// randomly generated if undefined
const bearStoreLogger = zustandPlugin('bear_store')

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-zustand",
"description": "Tracker plugin for Zustand state recording",
"version": "1.0.1",
"version": "1.0.2",
"keywords": [
"zustand",
"state",

View file

@ -1,3 +1,8 @@
//@ts-ignore
export function isNode(sth: any): sth is Node {
return !!sth && sth.nodeType != null
}
export function isSVGElement(node: Element): node is SVGElement {
return node.namespaceURI === 'http://www.w3.org/2000/svg'
}

View file

@ -213,9 +213,9 @@ export default class App {
}
}
safe<T extends (...args: any[]) => void>(fn: T): T {
safe<T extends (this: any, ...args: any[]) => void>(fn: T): T {
const app = this
return function (this: any, ...args: any) {
return function (this: any, ...args: any[]) {
try {
fn.apply(this, args)
} catch (e) {
@ -225,12 +225,10 @@ export default class App {
// message: e.message,
// stack: e.stack
}
} as any // TODO: correct typing
} as T // TODO: correct typing
}
attachCommitCallback(cb: CommitCallback): void {
// TODO!: what if start callback added when activityState === Active ?
// For example - attachEventListener() called during dynamic <iframe> appearance
this.commitCallbacks.push(cb)
}
attachStartCallback(cb: StartCallback, useSafe = false): void {
@ -245,6 +243,7 @@ export default class App {
}
this.stopCallbacks.push(cb)
}
// Use app.nodes.attachNodeListener for registered nodes instead
attachEventListener(
target: EventTarget,
type: string,
@ -390,6 +389,7 @@ export default class App {
const sReset = this.sessionStorage.getItem(this.options.session_reset_key)
this.sessionStorage.removeItem(this.options.session_reset_key)
const shouldReset = startOpts.forceNew || sReset !== null
return window
.fetch(this.options.ingestPoint + '/v1/web/start', {
@ -401,10 +401,9 @@ export default class App {
...this.getTrackerInfo(),
timestamp,
userID: this.session.getInfo().userID,
token: this.session.getSessionToken(),
token: shouldReset ? undefined : this.session.getSessionToken(),
deviceMemory,
jsHeapSizeLimit,
reset: startOpts.forceNew || sReset !== null,
}),
})
.then((r) => {
@ -424,6 +423,9 @@ export default class App {
if (!this.worker) {
return Promise.reject('no worker found after start request (this might not happen)')
}
if (this.activityState === ActivityState.NotActive) {
return Promise.reject('Tracker stopped during authorisation')
}
const {
token,
userUUID,
@ -441,9 +443,12 @@ export default class App {
) {
return Promise.reject(`Incorrect server response: ${JSON.stringify(r)}`)
}
if (sessionID !== this.session.getInfo().sessionID) {
this.session.reset()
}
this.session.setSessionToken(token)
this.localStorage.setItem(this.options.local_uuid_key, userUUID)
this.session.update({ sessionID, timestamp: startTimestamp || timestamp, projectID }) // TODO: no no-explicit 'any'
this.localStorage.setItem(this.options.local_uuid_key, userUUID)
const startWorkerMsg: WorkerMessageData = {
type: 'auth',

View file

@ -12,20 +12,18 @@ export default class Nodes {
attachNodeCallback(nodeCallback: NodeCallback): void {
this.nodeCallbacks.push(nodeCallback)
}
// TODO: what is the difference with app.attachEventListener. can we use only one of those?
attachElementListener(type: string, node: Element, elementListener: EventListener): void {
attachNodeListener(node: Node, type: string, listener: EventListener): void {
const id = this.getID(node)
if (id === undefined) {
return
}
node.addEventListener(type, elementListener)
node.addEventListener(type, listener)
let listeners = this.elementListeners.get(id)
if (listeners === undefined) {
listeners = []
this.elementListeners.set(id, listeners)
return
}
listeners.push([type, elementListener])
listeners.push([type, listener])
}
registerNode(node: Node): [/*id:*/ number, /*isNew:*/ boolean] {

View file

@ -10,6 +10,7 @@ export default class IFrameObserver extends Observer {
} //log TODO common app.logger
// Have to observe document, because the inner <html> might be changed
this.observeRoot(doc, (docID) => {
//MBTODO: do not send if empty (send on load? it might be in-place iframe, like our replayer, which does not get loaded)
if (docID === undefined) {
console.log('OpenReplay: Iframe document not bound')
return

View file

@ -259,7 +259,7 @@ export default abstract class Observer {
return false
}
this.app.sanitizer.handleNode(id, parentID, node)
if (this.app.sanitizer.isMaskedContainer(parentID)) {
if (this.app.sanitizer.isHidden(parentID)) {
return false
}
}
@ -287,13 +287,13 @@ export default abstract class Observer {
if (isElementNode(node)) {
let el: Element = node
if (parentID !== undefined) {
if (this.app.sanitizer.isMaskedContainer(id)) {
if (this.app.sanitizer.isHidden(id)) {
const width = el.clientWidth
const height = el.clientHeight
el = node.cloneNode() as Element
// TODO: use ResizeObserver
;(el as HTMLElement | SVGElement).style.width = width + 'px'
;(el as HTMLElement | SVGElement).style.height = height + 'px'
;(el as HTMLElement | SVGElement).style.width = `${width}px`
;(el as HTMLElement | SVGElement).style.height = `${height}px`
}
this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)))

View file

@ -78,41 +78,45 @@ export default class TopObserver extends Observer {
private handleIframe(iframe: HTMLIFrameElement): void {
let doc: Document | null = null
let win: Window | null = null
const handle = this.app.safe(() => {
const id = this.app.nodes.getID(iframe)
if (id === undefined) {
//log
return
}
const currentWin = iframe.contentWindow
const currentDoc = iframe.contentDocument
if (currentDoc && currentDoc !== doc) {
const observer = new IFrameObserver(this.app)
this.iframeObservers.push(observer)
observer.observe(iframe)
doc = currentDoc
// setTimeout is required. Otherwise some event listeners (scroll, mousemove) applied in modules
// do not work on the iframe document when it 've been loaded dynamically ((why?))
const handle = this.app.safe(() =>
setTimeout(() => {
const id = this.app.nodes.getID(iframe)
if (id === undefined) {
//log
return
}
const currentWin = iframe.contentWindow
const currentDoc = iframe.contentDocument
if (currentDoc && currentDoc !== doc) {
const observer = new IFrameObserver(this.app)
this.iframeObservers.push(observer)
observer.observe(iframe) // TODO: call unregisterNode for the previous doc if present (incapsulate: one iframe - one observer)
doc = currentDoc
// Le truc
;(doc as PatchedDocument).__openreplay__getOffset = () => {
const { top, left } = this.getDocumentOffset(iframe.ownerDocument)
return {
top: iframe.offsetTop + top,
left: iframe.offsetLeft + left,
// Le truc
;(doc as PatchedDocument).__openreplay__getOffset = () => {
const { top, left } = this.getDocumentOffset(iframe.ownerDocument)
return {
top: iframe.offsetTop + top,
left: iframe.offsetLeft + left,
}
}
}
}
if (
currentWin &&
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
currentWin === currentWin.window &&
!this.contextsSet.has(currentWin) // for each context callbacks called once per Tracker (TopObserver) instance
) {
this.contextsSet.add(currentWin)
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
this.contextCallbacks.forEach((cb) => cb(currentWin))
win = currentWin
}
})
if (
currentWin &&
// Sometimes currentWin.window is null (not in specification). Such window object is not functional
currentWin === currentWin.window &&
!this.contextsSet.has(currentWin) // for each context callbacks called once per Tracker (TopObserver) instance
) {
this.contextsSet.add(currentWin)
//@ts-ignore https://github.com/microsoft/TypeScript/issues/41684
this.contextCallbacks.forEach((cb) => cb(currentWin))
win = currentWin
}
}, 0),
)
iframe.addEventListener('load', handle) // why app.attachEventListener not working?
handle()
}

View file

@ -2,14 +2,21 @@ import type App from './index.js'
import { stars, hasOpenreplayAttribute } from '../utils.js'
import { isElementNode } from './guards.js'
export enum SanitizeLevel {
Plain,
Obscured,
Hidden,
}
export interface Options {
obscureTextEmails: boolean
obscureTextNumbers: boolean
domSanitizer?: (node: Element) => SanitizeLevel
}
export default class Sanitizer {
private readonly masked: Set<number> = new Set()
private readonly maskedContainers: Set<number> = new Set()
private readonly obscured: Set<number> = new Set()
private readonly hidden: Set<number> = new Set()
private readonly options: Options
constructor(private readonly app: App, options: Partial<Options>) {
@ -24,21 +31,33 @@ export default class Sanitizer {
handleNode(id: number, parentID: number, node: Node) {
if (
this.masked.has(parentID) ||
(isElementNode(node) && hasOpenreplayAttribute(node, 'masked'))
this.obscured.has(parentID) ||
(isElementNode(node) &&
(hasOpenreplayAttribute(node, 'masked') || hasOpenreplayAttribute(node, 'obscured')))
) {
this.masked.add(id)
this.obscured.add(id)
}
if (
this.maskedContainers.has(parentID) ||
(isElementNode(node) && hasOpenreplayAttribute(node, 'htmlmasked'))
this.hidden.has(parentID) ||
(isElementNode(node) &&
(hasOpenreplayAttribute(node, 'htmlmasked') || hasOpenreplayAttribute(node, 'hidden')))
) {
this.maskedContainers.add(id)
this.hidden.add(id)
}
if (this.options.domSanitizer !== undefined && isElementNode(node)) {
const sanitizeLevel = this.options.domSanitizer(node)
if (sanitizeLevel === SanitizeLevel.Obscured) {
this.obscured.add(id)
}
if (sanitizeLevel === SanitizeLevel.Hidden) {
this.hidden.add(id)
}
}
}
sanitize(id: number, data: string): string {
if (this.masked.has(id)) {
if (this.obscured.has(id)) {
// TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
return data
.trim()
@ -56,11 +75,12 @@ export default class Sanitizer {
return data
}
isMasked(id: number): boolean {
return this.masked.has(id)
isObscured(id: number): boolean {
return this.obscured.has(id)
}
isMaskedContainer(id: number) {
return this.maskedContainers.has(id)
isHidden(id: number) {
return this.hidden.has(id)
}
getInnerTextSecure(el: HTMLElement): string {
@ -72,7 +92,7 @@ export default class Sanitizer {
}
clear(): void {
this.masked.clear()
this.maskedContainers.clear()
this.obscured.clear()
this.hidden.clear()
}
}

View file

@ -97,7 +97,7 @@ export default class Session {
let token = hash
let pageNoStr = '100500' // back-compat for sessionToken
if (hashParts.length == 2) {
;[token, pageNoStr] = hashParts
;[pageNoStr, token] = hashParts
}
if (!pageNoStr || !token) {
return

View file

@ -1,9 +1,10 @@
import App, { DEFAULT_INGEST_POINT } from './app/index.js'
export { default as App } from './app/index.js'
import { UserID, UserAnonymousID, RawCustomEvent, CustomIssue } from './app/messages.gen.js'
import { UserAnonymousID, RawCustomEvent, CustomIssue } from './app/messages.gen.js'
import * as _Messages from './app/messages.gen.js'
export const Messages = _Messages
export { SanitizeLevel } from './app/sanitizer.js'
import Connection from './modules/connection.js'
import Console from './modules/console.js'
@ -187,9 +188,7 @@ export default class API {
return
}
this.app.stop()
const sessionHash = this.app.session.getSessionHash()
this.app.session.reset()
return sessionHash
return this.app.session.getSessionHash()
}
getSessionToken(): string | null | undefined {

View file

@ -54,7 +54,7 @@ export default function (app: App): void {
if (id === undefined) {
return
}
const { src, complete, naturalWidth, naturalHeight, srcset } = this
const { src, complete, naturalWidth, naturalHeight } = this
if (!complete) {
return
}
@ -63,7 +63,11 @@ export default function (app: App): void {
if (isURL(resolvedSrc)) {
app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'))
}
} else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) {
} else if (
resolvedSrc.length >= 1e5 ||
app.sanitizer.isHidden(id) ||
app.sanitizer.isObscured(id)
) {
sendPlaceholder(id, this)
} else {
sendSrc(id, this)
@ -97,8 +101,8 @@ export default function (app: App): void {
if (!hasTag(node, 'IMG')) {
return
}
app.nodes.attachElementListener('error', node, sendImgAttrs.bind(node))
app.nodes.attachElementListener('load', node, sendImgAttrs.bind(node))
app.nodes.attachNodeListener(node, 'error', sendImgAttrs.bind(node))
app.nodes.attachNodeListener(node, 'load', sendImgAttrs.bind(node))
sendImgAttrs.call(node)
observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] })
})

View file

@ -1,5 +1,5 @@
import type App from '../app/index.js'
import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from '../utils.js'
import { normSpaces, IN_BROWSER, getLabelAttribute } from '../utils.js'
import { hasTag } from '../app/guards.js'
import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js'
@ -103,10 +103,11 @@ export default function (app: App, opts: Partial<Options>): void {
function sendInputValue(id: number, node: TextEditableElement | HTMLSelectElement): void {
let value = node.value
let inputMode: InputMode = options.defaultInputMode
if (node.type === 'password' || hasOpenreplayAttribute(node, 'hidden')) {
if (node.type === 'password' || app.sanitizer.isHidden(id)) {
inputMode = InputMode.Hidden
} else if (
hasOpenreplayAttribute(node, 'obscured') ||
app.sanitizer.isObscured(id) ||
(inputMode === InputMode.Plain &&
((options.obscureInputNumbers && node.type !== 'date' && /\d\d\d\d/.test(value)) ||
(options.obscureInputDates && node.type === 'date') ||

View file

@ -1,30 +1,39 @@
import type App from '../app/index.js'
import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js'
import { isElementNode, isRootNode } from '../app/guards.js'
import { isNode, isElementNode, isRootNode, isDocument } from '../app/guards.js'
function getDocumentScroll(doc: Document): [number, number] {
const win = doc.defaultView
return [
(win && win.pageXOffset) ||
(doc.documentElement && doc.documentElement.scrollLeft) ||
(doc.body && doc.body.scrollLeft) ||
0,
(win && win.pageYOffset) ||
(doc.documentElement && doc.documentElement.scrollTop) ||
(doc.body && doc.body.scrollTop) ||
0,
]
}
export default function (app: App): void {
let documentScroll = false
const nodeScroll: Map<Node, [number, number]> = new Map()
function setNodeScroll(target: EventTarget | null) {
if (target instanceof Element) {
if (!isNode(target)) {
return
}
if (isElementNode(target)) {
nodeScroll.set(target, [target.scrollLeft, target.scrollTop])
}
if (isDocument(target)) {
nodeScroll.set(target, getDocumentScroll(target))
}
}
const sendSetViewportScroll = app.safe((): void =>
app.send(
SetViewportScroll(
window.pageXOffset ||
(document.documentElement && document.documentElement.scrollLeft) ||
(document.body && document.body.scrollLeft) ||
0,
window.pageYOffset ||
(document.documentElement && document.documentElement.scrollTop) ||
(document.body && document.body.scrollTop) ||
0,
),
),
app.send(SetViewportScroll(...getDocumentScroll(document))),
)
const sendSetNodeScroll = app.safe((s: [number, number], node: Node): void => {
@ -42,11 +51,12 @@ export default function (app: App): void {
})
app.nodes.attachNodeCallback((node, isStart) => {
// MBTODO: iterate over all the nodes on start instead of using isStart hack
if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
nodeScroll.set(node, [node.scrollLeft, node.scrollTop])
} else if (isRootNode(node)) {
// scroll is not-composed event (https://javascript.info/shadow-dom-events)
app.attachEventListener(node, 'scroll', (e: Event): void => {
app.nodes.attachNodeListener(node, 'scroll', (e: Event): void => {
setNodeScroll(e.target)
})
}

View file

@ -1,6 +1,14 @@
export function timestamp(): number {
return Math.round(performance.now()) + performance.timing.navigationStart
}
const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' }
export const IN_BROWSER = !(typeof window === 'undefined')
const navigationStart: number | false =
(IN_BROWSER && performance.timing.navigationStart) || performance.timeOrigin
// performance.now() is buggy in some browsers
export const timestamp: () => number =
IN_BROWSER && performance.now() && navigationStart
? () => Math.round(performance.now() + navigationStart)
: () => Date.now()
export const stars: (str: string) => string =
'repeat' in String.prototype
@ -16,8 +24,6 @@ export function isURL(s: string): boolean {
return s.startsWith('https://') || s.startsWith('http://')
}
export const IN_BROWSER = !(typeof window === 'undefined')
// TODO: JOIN IT WITH LOGGER somehow (use logging decorators?); Don't forget about index.js loggin when there is no logger instance.
export const DOCS_HOST = 'https://docs.openreplay.com'
@ -47,19 +53,20 @@ export function getLabelAttribute(e: Element): string | null {
return value
}
export function hasOpenreplayAttribute(e: Element, name: string): boolean {
const newName = `data-openreplay-${name}`
export function hasOpenreplayAttribute(e: Element, attr: string): boolean {
const newName = `data-openreplay-${attr}`
if (e.hasAttribute(newName)) {
// @ts-ignore
if (DEPRECATED_ATTRS[attr]) {
deprecationWarn(
`"${newName}" attribute`,
// @ts-ignore
`"${DEPRECATED_ATTRS[attr] as string}" attribute`,
'/installation/sanitize-data',
)
}
return true
}
const oldName = `data-asayer-${name}`
if (e.hasAttribute(oldName)) {
deprecationWarn(
`"${oldName}" attribute`,
`"${newName}" attribute`,
'/installation/sanitize-data',
)
return true
}
return false
}

View file

@ -40,7 +40,7 @@ export default class BatchWriter {
return
}
// MBTODO: move service-messages creation to webworker
// MBTODO: move service-messages creation methods to webworker
const batchMetadata: Messages.BatchMetadata = [
Messages.Type.BatchMetadata,
1,

View file

@ -3,7 +3,8 @@ const express = require('express');
const socket = require("./servers/websocket");
const {request_logger} = require("./utils/helper");
const HOST = '0.0.0.0';
const debug = process.env.debug === "1" || false;
const HOST = process.env.LISTEN_HOST || '0.0.0.0';
const PORT = process.env.LISTEN_PORT || 9001;
const wsapp = express();

View file

@ -242,6 +242,7 @@ module.exports = {
start: (server, prefix) => {
createSocketIOServer(server, prefix);
io.on('connection', async (socket) => {
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
debug && console.log(`WS started:${socket.id}, Query:${JSON.stringify(socket.handshake.query)}`);
socket._connectedAt = new Date();
socket.peerId = socket.handshake.query.peerId;
@ -308,7 +309,6 @@ module.exports = {
socket.on(EVENTS_DEFINITION.listen.CONNECT_ERROR, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_ERROR, err));
socket.on(EVENTS_DEFINITION.listen.CONNECT_FAILED, err => errorHandler(EVENTS_DEFINITION.listen.CONNECT_FAILED, err));
socket.on(EVENTS_DEFINITION.listen.ERROR, err => errorHandler(EVENTS_DEFINITION.listen.ERROR, err));
socket.onAny(async (eventName, ...args) => {
if (Object.values(EVENTS_DEFINITION.listen).indexOf(eventName) >= 0) {