-
- {renderItem()}
-
+ return (
+
+ )
})
export default CustomDragLayer;
diff --git a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx
index e1be98622..e47593b97 100644
--- a/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx
+++ b/frontend/app/components/Session_/Player/Controls/components/TimeTooltip.tsx
@@ -8,31 +8,39 @@ interface Props {
time: number;
offset: number;
isVisible: boolean;
- liveTimeTravel: boolean;
+ timeStr: string;
}
function TimeTooltip({
time,
offset,
isVisible,
- liveTimeTravel,
+ timeStr,
}: Props) {
- const duration = Duration.fromMillis(time).toFormat(`${liveTimeTravel ? '-' : ''}mm:ss`);
return (
- {!time ? 'Loading' : duration}
+ {!time ? 'Loading' : time}
+ {timeStr ? (
+ <>
+
+ ({timeStr})
+ >
+ ) : null}
);
}
export default connect((state) => {
- const { time = 0, offset = 0, isVisible } = state.getIn(['sessions', 'timeLineTooltip']);
- return { time, offset, isVisible };
+ const { time = 0, offset = 0, isVisible, timeStr } = state.getIn(['sessions', 'timeLineTooltip']);
+ return { time, offset, isVisible, timeStr };
})(TimeTooltip);
diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx
index 99499cac6..7be45a432 100644
--- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx
+++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx
@@ -1,9 +1,7 @@
import React from 'react';
import Marker from './ElementsMarker/Marker';
+import type { MarkedTarget } from 'Player';
-export default function ElementsMarker({ targets, activeIndex }) {
- return targets && targets.map(t =>
)
+export default function ElementsMarker({ targets, activeIndex }: { targets: MarkedTarget[], activeIndex: number }) {
+ return targets && targets.map(t =>
)
}
-
-
-
diff --git a/frontend/app/components/Session_/PlayerBlockHeader.tsx b/frontend/app/components/Session_/PlayerBlockHeader.tsx
index 1932ccb85..744db22cc 100644
--- a/frontend/app/components/Session_/PlayerBlockHeader.tsx
+++ b/frontend/app/components/Session_/PlayerBlockHeader.tsx
@@ -31,7 +31,6 @@ function PlayerBlockHeader(props: any) {
const { assistMultiviewStore } = useStore();
const { width, height, showEvents } = store.get();
- const toggleEvents = player.toggleEvents;
const {
session,
@@ -147,10 +146,10 @@ function PlayerBlockHeader(props: any) {
onClick={(tab) => {
if (activeTab === tab) {
setActiveTab('');
- toggleEvents();
+ player.toggleEvents();
} else {
setActiveTab(tab);
- !showEvents && toggleEvents();
+ !showEvents && player.toggleEvents();
}
}}
border={false}
diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js
index be7679fd5..72ae28e3d 100644
--- a/frontend/app/components/Session_/Subheader.js
+++ b/frontend/app/components/Session_/Subheader.js
@@ -11,6 +11,7 @@ import { useModal } from 'App/components/Modal';
import BugReportModal from './BugReport/BugReportModal';
import { PlayerContext } from 'App/components/Session/playerContext';
import { observer } from 'mobx-react-lite';
+import { useStore } from 'App/mstore';
import AutoplayToggle from 'Shared/AutoplayToggle';
function SubHeader(props) {
@@ -55,6 +56,7 @@ function SubHeader(props) {
return (
+
{location && (
{
setIsDetailsModalActive(true)
showModal(
-
0} />,
+ 0} />,
{
right: true,
onClose: () => {
@@ -369,7 +369,7 @@ function NetworkPanel() {
hidden: activeTab === XHR,
},
{
- label: 'Time',
+ label: 'Duration',
width: 80,
dataKey: 'duration',
render: renderDuration,
@@ -383,4 +383,6 @@ function NetworkPanel() {
);
}
-export default observer(NetworkPanel);
+export default connect((state: any) => ({
+ startedAt: state.getIn(['sessions', 'current', 'startedAt']),
+}))(observer(NetworkPanel));
diff --git a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx
index a9f90e723..183294788 100644
--- a/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx
+++ b/frontend/app/components/shared/FetchDetailsModal/FetchDetailsModal.tsx
@@ -5,9 +5,11 @@ import FetchPluginMessage from './components/FetchPluginMessage';
import { TYPES } from 'Types/session/resource';
import FetchTabs from './components/FetchTabs/FetchTabs';
import { useStore } from 'App/mstore';
+import { DateTime } from 'luxon';
interface Props {
resource: any;
+ time?: number;
rows?: any;
fetchPresented?: boolean;
}
@@ -19,6 +21,7 @@ function FetchDetailsModal(props: Props) {
const isXHR = resource.type === TYPES.XHR
const {
sessionStore: { devTools },
+ settingsStore: { sessionSettings: { timezone }},
} = useStore();
useEffect(() => {
@@ -47,7 +50,7 @@ function FetchDetailsModal(props: Props) {
return (
Network Request
-
+
{isXHR && !fetchPresented &&
}
{isXHR &&
}
diff --git a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx
index 49e16c00f..94aadfb1d 100644
--- a/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx
+++ b/frontend/app/components/shared/FetchDetailsModal/components/FetchBasicDetails/FetchBasicDetails.tsx
@@ -5,8 +5,9 @@ import cn from 'classnames';
interface Props {
resource: any;
+ timestamp?: string;
}
-function FetchBasicDetails({ resource }: Props) {
+function FetchBasicDetails({ resource, timestamp }: Props) {
const _duration = parseInt(resource.duration);
const text = useMemo(() => {
if (resource.url.length > 50) {
@@ -69,12 +70,22 @@ function FetchBasicDetails({ resource }: Props) {
{!!_duration && (
-
Time
+
Duration
{_duration} ms
)}
+
+ {timestamp && (
+
+
Time
+
+ {timestamp}
+
+
+
+ )}
);
}
diff --git a/frontend/app/duck/index.js b/frontend/app/duck/index.js
index 5ad487c93..051ec6933 100644
--- a/frontend/app/duck/index.js
+++ b/frontend/app/duck/index.js
@@ -7,12 +7,8 @@ import issues from './issues';
import assignments from './assignments';
import target from './target';
import targetCustom from './targetCustom';
-import runs from './runs';
import filters from './filters';
import funnelFilters from './funnelFilters';
-import tests from './tests';
-import steps from './steps';
-import schedules from './schedules';
import events from './events';
import environments from './environments';
import variables from './variables';
@@ -46,12 +42,9 @@ export default combineReducers({
assignments,
target,
targetCustom,
- runs,
filters,
funnelFilters,
- tests,
- steps,
- schedules,
+
events,
environments,
variables,
diff --git a/frontend/app/duck/runs.js b/frontend/app/duck/runs.js
deleted file mode 100644
index 30b8051ac..000000000
--- a/frontend/app/duck/runs.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import Run from 'Types/run';
-import crudDuckGenerator from './tools/crudDuck';
-
-const crudDuck = crudDuckGenerator('run', Run);
-export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions;
-
-export default crudDuck.reducer;
diff --git a/frontend/app/duck/schedules.js b/frontend/app/duck/schedules.js
deleted file mode 100644
index 3f13e9188..000000000
--- a/frontend/app/duck/schedules.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Schedule from 'Types/schedule';
-import crudDuckGenerator from './tools/crudDuck';
-
-const crudDuck = crudDuckGenerator('scheduler', Schedule);
-export const { fetchList, fetch, init, edit, remove } = crudDuck.actions;
-
-export function save(instance) { // TODO: fix the crudDuckGenerator
- return {
- types: crudDuck.actionTypes.SAVE.toArray(),
- call: client => client.post(`/schedulers${!!instance.schedulerId ? '/' + instance.schedulerId : '' }`, instance),
- };
-}
-
-export default crudDuck.reducer;
diff --git a/frontend/app/duck/sessions.js b/frontend/app/duck/sessions.js
index ecce3d713..8afb6d073 100644
--- a/frontend/app/duck/sessions.js
+++ b/frontend/app/duck/sessions.js
@@ -70,7 +70,7 @@ const initialState = Map({
timelinePointer: null,
sessionPath: {},
lastPlayedSessionId: null,
- timeLineTooltip: { time: 0, offset: 0, isVisible: false },
+ timeLineTooltip: { time: 0, offset: 0, isVisible: false, timeStr: '' },
createNoteTooltip: { time: 0, isVisible: false, isEdit: false, note: null },
});
@@ -454,4 +454,4 @@ export function updateLastPlayedSession(sessionId) {
type: LAST_PLAYED_SESSION_ID,
sessionId,
};
-}
\ No newline at end of file
+}
diff --git a/frontend/app/duck/steps.js b/frontend/app/duck/steps.js
deleted file mode 100644
index 02bfdbb90..000000000
--- a/frontend/app/duck/steps.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import { List, Map } from 'immutable';
-import { RequestTypes } from 'Duck/requestStateCreator';
-import Step from 'Types/step';
-import Event from 'Types/filter/event';
-import { getRE } from 'App/utils';
-import Test from 'Types/appTest';
-import { countries } from 'App/constants';
-import { KEYS } from 'Types/filter/customFilter';
-
-const countryOptions = Object.keys(countries).map(c => ({filterKey: KEYS.USER_COUNTRY, label: KEYS.USER_COUNTRY, type: KEYS.USER_COUNTRY, value: c, actualValue: countries[c], isFilter: true }));
-
-const INIT = 'steps/INIT';
-const EDIT = 'steps/EDIT';
-
-const SET_TEST = 'steps/SET_TEST';
-const FETCH_LIST = new RequestTypes('steps/FETCH_LIST');
-
-const initialState = Map({
- list: List(),
- test: Test(),
- instance: Step(),
- editingIndex: null,
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case FETCH_LIST.SUCCESS: {
- return state.set('list', List(action.data).map(i => {
- const type = i.type === 'navigate' ? i.type : 'location';
- return {...i, type: type.toUpperCase()}
- }))
- }
- case INIT:
- return state
- .set('instance', Step(action.instance))
- .set('editingIndex', action.index)
- .set('test', Test());
- case EDIT:
- return state.mergeIn([ 'instance' ], action.instance);
- case SET_TEST:
- return state.set('test', Test(action.test));
- }
- return state;
-};
-
-export default reducer;
-
-export function init(instance, index) {
- return {
- type: INIT,
- instance,
- index,
- };
-}
-
-export function edit(instance) {
- return {
- type: EDIT,
- instance,
- };
-}
-
-export function setTest(test) {
- return {
- type: SET_TEST,
- test,
- };
-}
-
-
-export function fetchList(params) {
- return {
- types: FETCH_LIST.toArray(),
- call: client => client.get('/tests/steps/search', params),
- params,
- };
-}
diff --git a/frontend/app/duck/tests/index.js b/frontend/app/duck/tests/index.js
deleted file mode 100644
index eda7edc43..000000000
--- a/frontend/app/duck/tests/index.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import { List, Map, Set } from 'immutable';
-import Test from 'Types/appTest';
-import stepFromJS from 'Types/step';
-import crudDuckGenerator from 'Duck/tools/crudDuck';
-import { reduceDucks } from 'Duck/tools';
-import runsDuck from './runs';
-import Run from 'Types/run';
-
-const sampleRun = Run({"runId":8,"testId":7,"name":"test import","createdAt":1601481986264,"createdBy":283,"starter":"on-demand","state":"failed","steps":[{"label":"Open URL","order":0,"title":"navigate","status":"passed","startedAt":1601647536513,"finishedAt":1601647546211,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647546211.jpg","executionTime":9698},{"label":"Open URL","order":1,"title":"Visit OpenReplay","status":"passed","startedAt":1601647548354,"finishedAt":1601647556991,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647556991.jpg","executionTime":8637},{"info":"Unhandled promise rejection: TimeoutError: waiting for selector \"[name=\"email\"]\" failed: timeout 30000ms exceeded","input":"failed","label":"Send Keys to Element","order":2,"title":"input","status":"failed","startedAt":1601647559091,"finishedAt":1601647589099,"screenshot":"https://parrot-tests.s3.eu-central-1.amazonaws.com/115/7/8/screenshots/1601647589099.jpg","executionTime":30008}],"browser":"chrome","meta":{"startedAt":1601487715818},"location":"FR","startedAt":1601647524205,"finishedAt":1601647591217,"network":[{"url":"http://yahoo.fr/","method":"GET","duration":1760,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647537533,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"response":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"http://fr.yahoo.com/","method":"GET","duration":1112,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647539293,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null,"payload":{"data":[{"key":"user_id","index":1},{"key":"virtual_number","index":2}]}},{"url":"https://fr.yahoo.com/","method":"GET","duration":1204,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647540405,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=bxFB6Ac&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1173,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647541609,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_fab600d0-8323-4b52-88c1-5698e6288f48","method":"GET","duration":1169,"requestID":"769C483871CB3D35DF4BB1CD7D3258C4","timestamp":1601647542782,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64)AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1179,"requestID":"56.2","timestamp":1601647543958,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.3","timestamp":1601647543959,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1194,"requestID":"56.5","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1189,"requestID":"56.4","timestamp":1601647543961,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1068,"requestID":"56.7","timestamp":1601647545141,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"http://yahoo.fr/","method":"GET","duration":1312,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647549363,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"http://fr.yahoo.com/","method":"GET","duration":1005,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647550675,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://fr.yahoo.com/","method":"GET","duration":1037,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647551680,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://guce.yahoo.com/consent?brandType=eu&gcrumb=ESjhlqw&lang=fr-FR&done=https%3A%2F%2Ffr.yahoo.com%2F","method":"GET","duration":1045,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647552717,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://consent.yahoo.com/v2/collectConsent?sessionId=3_cc-session_3b367c91-9f88-498b-96a5-728947dda245","method":"GET","duration":1115,"requestID":"7CA8FE6239B07643872BF48C30D639D3","timestamp":1601647553762,"requestHeaders":{"cookie":"key1=myvalue1","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36","upgrade-insecure-requests":"1"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","method":"GET","duration":1052,"requestID":"56.14","timestamp":1601647554885,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_p_bestfit_frontpage.png","method":"GET","duration":1060,"requestID":"56.15","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/js/site-ee81be05.js","method":"GET","duration":1065,"requestID":"56.17","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/rz/p/yahoo_frontpage_en-US_s_f_w_bestfit_frontpage.png","method":"GET","duration":1063,"requestID":"56.16","timestamp":1601647554886,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://consent.yahoo.com/","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null},{"url":"https://s.yimg.com/oa/build/images/fr-FR-home_11f60c18d02223c8.jpeg","method":"GET","duration":1046,"requestID":"56.19","timestamp":1601647555944,"requestHeaders":{"cookie":"key1=myvalue1","referer":"https://s.yimg.com/oa/build/css/site-ltr-b1aa14b0.css","user-agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/83.0.4103.0 Safari/537.36"},"responseHeaders":null}],"environmentId":null,"tenantId":115,"consoleLogs":[{"_type":"warning","_text":"A cookie associated with a resource at http://openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840909},{"_type":"warning","_text":"A cookie associated with a resource at http://app.openreplay.com/ was set with `SameSite=None` but without `Secure`. It has been blocked, as Chrome now only delivers cookies marked `SameSite=None` if they are also marked `Secure`. You can review cookies in developer tools under Application>Storage>Cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.","_args":[],"_location":{"url":"https://app.openreplay.com/"},"timestamp":1602089840918}]});
-
-const ADD_STEPS = 'tests/ADD_STEPS';
-const MOVE_STEP = 'tests/MOVE_STEP';
-const REMOVE_STEP = 'tests/REMOVE_STEP';
-const COPY_STEP = 'tests/COPY_STEP';
-const EDIT_STEP = 'tests/EDIT_STEP';
-const TOGGLE_STEP = 'tests/TOGGLE_STEP';
-const ADD_TAG = 'tests/ADD_TAG';
-const REMOVE_TAG = 'tests/REMOVE_TAG';
-const TOGGLE_TAG = 'tests/TOGGLE_TAG';
-const SET_MODIFIED = 'tests/SET_MODIFIED';
-const SET_QUERY = 'tests/SET_QUERY';
-
-const MOVE_TEST = 'tests/MOVE_TEST';
-
-const initialState = Map({
- tags: Set(),
- query: '',
- modified: false,
- sampleRun: sampleRun,
-});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case SET_MODIFIED:
- return state.set('modified', action.state);
- case SET_QUERY:
- return state.set('query', action.query);
- case ADD_STEPS:
- // TODO check frameworks
- return state
- .updateIn([ 'instance', 'steps' ], list => list.concat(action.steps.map(stepFromJS))).set('modified', true);
- case MOVE_STEP: {
- const { fromI, toI } = action;
- return state
- .updateIn([ 'instance', 'steps' ], list =>
- list.remove(fromI).insert(toI, list.get(fromI))).set('modified', true);
- }
- case REMOVE_STEP:
- return state.removeIn([ 'instance', 'steps', action.index ]).set('modified', true);
- case COPY_STEP: {
- // Use fromJS to make another key.
- const copiedStep = stepFromJS(state
- .getIn([ 'instance', 'steps', action.index ])
- .set('imported', false));
- return state
- .updateIn([ 'instance', 'steps' ], steps =>
- steps.insert(action.index + 1, copiedStep)).set('modified', true);
- }
- case EDIT_STEP:
- return state.mergeIn([ 'instance', 'steps', action.index ], action.step).set('modified', true);
- case TOGGLE_STEP:
- return state.updateIn([ 'instance', 'steps', action.index, 'isDisabled' ], isDisabled => !isDisabled).set('modified', true);
- case ADD_TAG:
- return state.updateIn([ 'instance', 'tags' ], tags => tags.add(action.tag)).set('modified', true);
- case REMOVE_TAG:
- return state.updateIn([ 'instance', 'tags' ], tags => tags.remove(action.tag)).set('modified', true);
- case TOGGLE_TAG: {
- const { tag, flag } = action;
- const adding = typeof flag === 'boolean'
- ? flag
- : !state.hasIn([ 'tags', tag ]);
- return state.update('tags', tags => (adding
- ? tags.add(tag)
- : tags.remove(tag)));
- }
- case MOVE_TEST: {
- const { fromI, toI } = action;
- return state
- .updateIn([ 'list' ], list =>
- list.remove(fromI).insert(toI, list.get(fromI)));
- }
- }
- return state;
-};
-
-const crudDuck = crudDuckGenerator('test', Test);
-export const { fetchList, fetch, init, edit, save, remove } = crudDuck.actions;
-export { runTest, stopRun, checkRun, generateTest, stopAllRuns, resetErrors } from './runs';
-export default reduceDucks(crudDuck, { reducer, initialState }, runsDuck).reducer;
-
-export function addSteps(stepOrSteps) {
- const steps = Array.isArray(stepOrSteps) || List.isList(stepOrSteps)
- ? stepOrSteps
- : [ stepOrSteps ];
- return {
- type: ADD_STEPS,
- steps,
- };
-}
-
-export function moveStep(fromI, toI) {
- return {
- type: MOVE_STEP,
- fromI,
- toI,
- };
-}
-
-export function removeStep(index) {
- return {
- type: REMOVE_STEP,
- index,
- };
-}
-
-export function copyStep(index) {
- return {
- type: COPY_STEP,
- index,
- };
-}
-
-export function editStep(index, step) {
- return {
- type: EDIT_STEP,
- index,
- step,
- };
-}
-
-export function setModified(state) {
- return {
- type: SET_MODIFIED,
- state,
- };
-}
-
-export function toggleStep(index) {
- return {
- type: TOGGLE_STEP,
- index,
- };
-}
-
-export const addTag = (tag) => (dispatch) => {
- return new Promise((resolve) => {
- dispatch({
- type: ADD_TAG,
- tag,
- })
- resolve()
- })
-}
-
-export const removeTag = (tag) => (dispatch) => {
- return new Promise((resolve) => {
- dispatch({
- type: REMOVE_TAG,
- tag,
- });
- resolve()
- })
-}
-
-export function toggleTag(tag, flag) {
- return {
- type: TOGGLE_TAG,
- tag,
- flag,
- };
-}
-
-export function setQuery(query) {
- return {
- type: SET_QUERY,
- query
- };
-}
-
-export function moveTest(fromI, toI) {
- return {
- type: MOVE_TEST,
- fromI,
- toI,
- };
-}
diff --git a/frontend/app/duck/tests/runs.js b/frontend/app/duck/tests/runs.js
deleted file mode 100644
index e5f1ecc3d..000000000
--- a/frontend/app/duck/tests/runs.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Map } from 'immutable';
-import Test from 'Types/appTest';
-import Run, { RUNNING, STOPPED } from 'Types/run';
-import requestDuckGenerator, { RequestTypes } from 'Duck/tools/requestDuck';
-import { reduceDucks } from 'Duck/tools';
-
-const GEN_TEST = new RequestTypes('tests/GEN_TEST');
-const RUN_TEST = new RequestTypes('tests/RUN_TEST');
-const STOP_RUN = new RequestTypes('tests/STOP_RUN');
-const STOP_ALL_RUNS = new RequestTypes('tests/STOP_ALL_RUNS');
-const CHECK_RUN = new RequestTypes('tests/CHECK_RUN');
-const RESET_ERRORS = 'tests/RESET_ERRORS';
-
-const updateRunInTest = run => (test) => {
- const runIndex = test.runHistory
- .findLastIndex(({ runId }) => run.runId === runId);
- return runIndex === -1
- ? test.update('runHistory', list => list.push(run))
- : test.mergeIn([ 'runHistory', runIndex ], run);
-};
-
-const updateRun = (state, testId, run) => {
- const testIndex = state.get('list').findIndex(test => test.testId === testId);
- if (testIndex === -1) return state;
- const updater = updateRunInTest(run);
- return state
- .updateIn([ 'list', testIndex ], updater)
- .updateIn([ 'instance' ], test => (test.testId === testId
- ? updater(test)
- : test));
-};
-
-const initialState = Map({});
-
-const reducer = (state = initialState, action = {}) => {
- switch (action.type) {
- case GEN_TEST.SUCCESS:
- return state.set('instance', Test(action.data).set('generated', true));
- case RUN_TEST.SUCCESS: {
- const test = state.get('list').find(({ testId }) => testId === action.testId);
- const run = Run({
- runId: action.data.id, state: RUNNING, testId: action.testId, name: test.name
- });
- return updateRun(state, action.testId, run);
- }
- case STOP_RUN.SUCCESS: {
- const { testId, runId } = action;
- return updateRun(state, testId, { runId, state: STOPPED });
- }
- case STOP_ALL_RUNS.SUCCESS:
- return state.update('list', list => list.map(test => {
- test.runHistory.map(run => run.state === RUNNING ? run.set('state', STOPPED) : run.state);
- return test;
- })).setIn(['runRequest', 'errors'], null);
- case CHECK_RUN.SUCCESS:
- return updateRun(state, action.testId, Run(action.data));
- case RESET_ERRORS:
- return state.setIn(['runRequest', 'errors'], null);
- }
- return state;
-};
-
-const requestDuck = requestDuckGenerator({
- runRequest: RUN_TEST,
- stopRunRequest: STOP_RUN,
- stopAllRunsRequest: STOP_ALL_RUNS,
- genTestRequest: GEN_TEST,
-});
-
-export default reduceDucks({ reducer, initialState }, requestDuck);
-
-
-export function generateTest(sessionId, params) {
- return {
- types: GEN_TEST.toArray(),
- call: client => client.post(`/sessions/${ sessionId }/gentest`, params),
- };
-}
-
-
-export function runTest(testId, params) {
- return {
- testId,
- types: RUN_TEST.toArray(),
- call: client => client.post(`/tests/${ testId }/execute`, params),
- };
-}
-
-export function stopRun(testId, runId) {
- return {
- runId,
- testId,
- types: STOP_RUN.toArray(),
- call: client => client.get(`/runs/${ runId }/stop`),
- };
-}
-
-export function stopAllRuns() {
- return {
- types: STOP_ALL_RUNS.toArray(),
- call: client => client.get(`/runs/all/stop`),
- };
-}
-
-export function resetErrors() {
- return {
- type: RESET_ERRORS,
- }
-}
-
-export function checkRun(testId, runId) {
- return {
- runId,
- testId,
- types: CHECK_RUN.toArray(),
- call: client => client.get(`/runs/${ runId }`),
- };
-}
diff --git a/frontend/app/hooks/useToggle.ts b/frontend/app/hooks/useToggle.ts
index d4e820388..426e19b41 100644
--- a/frontend/app/hooks/useToggle.ts
+++ b/frontend/app/hooks/useToggle.ts
@@ -1,9 +1,9 @@
-import { useState, useCallback } from 'react';
+import { useState } from 'react';
export default function useToggle(defaultValue: boolean = false): [ boolean, () => void, () => void, () => void ] {
const [ value, setValue ] = useState(defaultValue);
- const toggle = useCallback(() => setValue(d => !d), []);
- const setFalse = useCallback(() => setValue(false), []);
- const setTrue = useCallback(() => setValue(true), []);
+ const toggle = () => setValue(d => !d)
+ const setFalse = () => setValue(false)
+ const setTrue = () => setValue(true)
return [ value, toggle, setFalse, setTrue ];
}
\ No newline at end of file
diff --git a/frontend/app/mstore/notesStore.ts b/frontend/app/mstore/notesStore.ts
index 1cb041049..ea52cb2e7 100644
--- a/frontend/app/mstore/notesStore.ts
+++ b/frontend/app/mstore/notesStore.ts
@@ -44,7 +44,7 @@ export default class NotesStore {
this.loading = true
try {
const notes = await notesService.getNotesBySessionId(sessionId)
- this.sessionNotes = notes
+ this.setNotes(notes)
return notes;
} catch (e) {
console.error(e)
@@ -53,6 +53,10 @@ export default class NotesStore {
}
}
+ setNotes(notes: Note[]) {
+ this.sessionNotes = notes
+ }
+
async addNote(sessionId: string, note: WriteNote) {
this.loading = true
try {
diff --git a/frontend/app/mstore/types/sessionSettings.ts b/frontend/app/mstore/types/sessionSettings.ts
index 95005b85d..bde66cdaf 100644
--- a/frontend/app/mstore/types/sessionSettings.ts
+++ b/frontend/app/mstore/types/sessionSettings.ts
@@ -13,27 +13,52 @@ const defaultDurationFilter = {
countType: 'sec'
}
+const negativeExceptions = {
+ 4: ['-04:30'],
+ 3: ['-03:30'],
+
+}
+const exceptions = {
+ 3: ['+03:30'],
+ 4: ['+04:30'],
+ 5: ['+05:30', '+05:45'],
+ 6: ['+06:30'],
+ 9: ['+09:30']
+}
+
export const generateGMTZones = (): Timezone[] => {
const timezones: Timezone[] = [];
- const positiveNumbers = [...Array(12).keys()];
- const negativeNumbers = [...Array(12).keys()].reverse();
+ const positiveNumbers = [...Array(13).keys()];
+ const negativeNumbers = [...Array(13).keys()].reverse();
negativeNumbers.pop(); // remove trailing zero since we have one in positive numbers array
const combinedArray = [...negativeNumbers, ...positiveNumbers];
for (let i = 0; i < combinedArray.length; i++) {
- let symbol = i < 11 ? '-' : '+';
- let isUTC = i === 11;
- let value = String(combinedArray[i]).padStart(2, '0');
+ let symbol = i < 12 ? '-' : '+';
+ let isUTC = i === 12;
+ const item = combinedArray[i]
+ let value = String(item).padStart(2, '0');
- let tz = `UTC ${symbol}${String(combinedArray[i]).padStart(2, '0')}:00`;
+ let tz = `UTC ${symbol}${String(item).padStart(2, '0')}:00`;
let dropdownValue = `UTC${symbol}${value}`;
timezones.push({ label: tz, value: isUTC ? 'UTC' : dropdownValue });
+
+ // @ts-ignore
+ const negativeMatch = negativeExceptions[item], positiveMatch = exceptions[item]
+ if (i < 11 && negativeMatch) {
+ negativeMatch.forEach((str: string) => {
+ timezones.push({ label: `UTC ${str}`, value: `UTC${str}`})
+ })
+ } else if (i > 11 && positiveMatch) {
+ positiveMatch.forEach((str: string) => {
+ timezones.push({ label: `UTC ${str}`, value: `UTC${str}`})
+ })
+ }
}
- timezones.splice(17, 0, { label: 'GMT +05:30', value: 'UTC+05:30' });
return timezones;
};
diff --git a/frontend/app/player/web/Screen/Cursor.ts b/frontend/app/player/web/Screen/Cursor.ts
index 4d8094b4e..f2d371062 100644
--- a/frontend/app/player/web/Screen/Cursor.ts
+++ b/frontend/app/player/web/Screen/Cursor.ts
@@ -5,10 +5,14 @@ import styles from './cursor.module.css';
export default class Cursor {
private readonly cursor: HTMLDivElement;
private tagElement: HTMLDivElement;
- constructor(overlay: HTMLDivElement) {
+ private isMobile: boolean;
+
+ constructor(overlay: HTMLDivElement, isMobile: boolean) {
this.cursor = document.createElement('div');
this.cursor.className = styles.cursor;
+ if (isMobile) this.cursor.style.backgroundImage = 'unset'
overlay.appendChild(this.cursor);
+ this.isMobile = isMobile;
}
toggle(flag: boolean) {
@@ -51,9 +55,10 @@ export default class Cursor {
}
click() {
- this.cursor.classList.add(styles.clicked)
+ const styleList = this.isMobile ? styles.clickedMobile : styles.clicked
+ this.cursor.classList.add(styleList)
setTimeout(() => {
- this.cursor.classList.remove(styles.clicked)
+ this.cursor.classList.remove(styleList)
}, 600)
}
diff --git a/frontend/app/player/web/Screen/Screen.ts b/frontend/app/player/web/Screen/Screen.ts
index b1ceff509..043be5357 100644
--- a/frontend/app/player/web/Screen/Screen.ts
+++ b/frontend/app/player/web/Screen/Screen.ts
@@ -57,7 +57,7 @@ export default class Screen {
private readonly screen: HTMLDivElement;
private parentElement: HTMLElement | null = null;
- constructor() {
+ constructor(isMobile: boolean) {
const iframe = document.createElement('iframe');
iframe.className = styles.iframe;
this.iframe = iframe;
@@ -73,7 +73,7 @@ export default class Screen {
screen.appendChild(overlay);
this.screen = screen;
- this.cursor = new Cursor(this.overlay) // TODO: move outside
+ this.cursor = new Cursor(this.overlay, isMobile) // TODO: move outside
}
attach(parentElement: HTMLElement) {
diff --git a/frontend/app/player/web/Screen/cursor.module.css b/frontend/app/player/web/Screen/cursor.module.css
index 7a94c99b8..93f3d05ff 100644
--- a/frontend/app/player/web/Screen/cursor.module.css
+++ b/frontend/app/player/web/Screen/cursor.module.css
@@ -67,3 +67,44 @@
transform: scale3d(1.2, 1.2, 1);
}
}
+
+.cursor.clickedMobile::after {
+ -webkit-animation: anim-effect-sanja 1s ease-out forwards;
+ animation: anim-effect-sanja 1s ease-out forwards;
+}
+
+@-webkit-keyframes anim-effect-sanja {
+ 0% {
+ opacity: 1;
+ -webkit-transform: scale3d(0.5, 0.5, 1);
+ transform: scale3d(0.5, 0.5, 1);
+ }
+ 25% {
+ opacity: 1;
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+ 100% {
+ opacity: 0;
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+}
+
+@keyframes anim-effect-sanja {
+ 0% {
+ opacity: 1;
+ -webkit-transform: scale3d(0.5, 0.5, 1);
+ transform: scale3d(0.5, 0.5, 1);
+ }
+ 25% {
+ opacity: 1;
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+ 100% {
+ opacity: 0;
+ -webkit-transform: scale3d(1, 1, 1);
+ transform: scale3d(1, 1, 1);
+ }
+}
diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts
index 8df6214d4..bd80c1189 100644
--- a/frontend/app/player/web/WebPlayer.ts
+++ b/frontend/app/player/web/WebPlayer.ts
@@ -31,7 +31,6 @@ export default class WebPlayer extends Player {
private targetMarker: TargetMarker
constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) {
-
let initialLists = live ? {} : {
event: session.events.toJSON(),
stack: session.stackEvents.toJSON(),
@@ -46,7 +45,7 @@ export default class WebPlayer extends Player {
),
}
- const screen = new Screen()
+ const screen = new Screen(session.isMobile)
const messageManager = new MessageManager(session, wpState, screen, initialLists)
super(wpState, messageManager)
this.screen = screen
diff --git a/frontend/app/player/web/messages/JSONRawMessageReader.ts b/frontend/app/player/web/messages/JSONRawMessageReader.ts
index 04f622ecb..6a9cfb4f9 100644
--- a/frontend/app/player/web/messages/JSONRawMessageReader.ts
+++ b/frontend/app/player/web/messages/JSONRawMessageReader.ts
@@ -1,21 +1,9 @@
-import type {
- RawMessage,
- RawSetNodeAttributeURLBased,
- RawSetNodeAttribute,
- RawSetCssDataURLBased,
- RawSetCssData,
- RawCssInsertRuleURLBased,
- RawCssInsertRule,
- RawAdoptedSsInsertRuleURLBased,
- RawAdoptedSsInsertRule,
- RawAdoptedSsReplaceURLBased,
- RawAdoptedSsReplace,
-} from './raw.gen'
+import type { RawMessage } from './raw.gen'
import type { TrackerMessage } from './tracker.gen'
import { MType } from './raw.gen'
import translate from './tracker.gen'
import { TP_MAP } from './tracker-legacy.gen'
-import { resolveURL, resolveCSS } from './urlResolve'
+import resolveURL from './urlBasedResolver'
function legacyTranslate(msg: any): RawMessage | null {
@@ -29,54 +17,6 @@ function legacyTranslate(msg: any): RawMessage | null {
}
-// TODO: commonURLBased logic for feilds
-const resolvers = {
- [MType.SetNodeAttributeURLBased]: (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute =>
- ({
- ...msg,
- value: msg.name === 'src' || msg.name === 'href'
- ? resolveURL(msg.baseURL, msg.value)
- : (msg.name === 'style'
- ? resolveCSS(msg.baseURL, msg.value)
- : msg.value
- ),
- tp: MType.SetNodeAttribute,
- }),
- [MType.SetCssDataURLBased]: (msg: RawSetCssDataURLBased): RawSetCssData =>
- ({
- ...msg,
- data: resolveCSS(msg.baseURL, msg.data),
- tp: MType.SetCssData,
- }),
- [MType.CssInsertRuleURLBased]: (msg: RawCssInsertRuleURLBased): RawCssInsertRule =>
- ({
- ...msg,
- rule: resolveCSS(msg.baseURL, msg.rule),
- tp: MType.CssInsertRule,
- }),
- [MType.AdoptedSsInsertRuleURLBased]: (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule =>
- ({
- ...msg,
- rule: resolveCSS(msg.baseURL, msg.rule),
- tp: MType.AdoptedSsInsertRule,
- }),
- [MType.AdoptedSsReplaceURLBased]: (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace =>
- ({
- ...msg,
- text: resolveCSS(msg.baseURL, msg.text),
- tp: MType.AdoptedSsReplace,
- }),
-} as const
-
-type ResolvableType = keyof typeof resolvers
-type ResolvableRawMessage = RawMessage & { tp: ResolvableType }
-
-function isResolvable(msg: RawMessage): msg is ResolvableRawMessage {
- //@ts-ignore
- return resolvers[msg.tp] !== undefined
-}
-
-
export default class JSONRawMessageReader {
constructor(private messages: TrackerMessage[] = []){}
append(messages: TrackerMessage[]) {
@@ -91,11 +31,7 @@ export default class JSONRawMessageReader {
if (!rawMsg) {
return this.readMessage()
}
- if (isResolvable(rawMsg)) {
- //@ts-ignore ??? too complex typscript...
- return resolvers[rawMsg.tp](rawMsg)
- }
- return rawMsg
+ return resolveURL(rawMsg)
}
}
diff --git a/frontend/app/player/web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts
index e6fad8c63..b91080b4f 100644
--- a/frontend/app/player/web/messages/MFileReader.ts
+++ b/frontend/app/player/web/messages/MFileReader.ts
@@ -3,6 +3,8 @@ import type { RawMessage } from './raw.gen';
import { MType } from './raw.gen';
import logger from 'App/logger';
import RawMessageReader from './RawMessageReader.gen';
+import resolveURL from './urlBasedResolver'
+
// TODO: composition instead of inheritance
// needSkipMessage() and next() methods here use buf and p protected properties,
@@ -77,7 +79,7 @@ export default class MFileReader extends RawMessageReader {
}
const index = this.getLastMessageID()
- const msg = Object.assign(rMsg, {
+ const msg = Object.assign(resolveURL(rMsg), {
time: this.currentTime,
_index: index,
})
diff --git a/frontend/app/player/web/messages/MStreamReader.ts b/frontend/app/player/web/messages/MStreamReader.ts
index 6446112a3..a37e43c46 100644
--- a/frontend/app/player/web/messages/MStreamReader.ts
+++ b/frontend/app/player/web/messages/MStreamReader.ts
@@ -8,7 +8,7 @@ interface RawMessageReaderI {
}
export default class MStreamReader {
- constructor(private readonly r: RawMessageReaderI = new RawMessageReader(), private startTs: number = 0){}
+ constructor(private readonly r: RawMessageReaderI, private startTs: number = 0){}
private t: number = 0
private idx: number = 0
diff --git a/frontend/app/player/web/messages/urlBasedResolver.ts b/frontend/app/player/web/messages/urlBasedResolver.ts
new file mode 100644
index 000000000..53bf1ed81
--- /dev/null
+++ b/frontend/app/player/web/messages/urlBasedResolver.ts
@@ -0,0 +1,69 @@
+import type {
+ RawMessage,
+ RawSetNodeAttributeURLBased,
+ RawSetNodeAttribute,
+ RawSetCssDataURLBased,
+ RawSetCssData,
+ RawCssInsertRuleURLBased,
+ RawCssInsertRule,
+ RawAdoptedSsInsertRuleURLBased,
+ RawAdoptedSsInsertRule,
+ RawAdoptedSsReplaceURLBased,
+ RawAdoptedSsReplace,
+} from './raw.gen'
+import { MType } from './raw.gen'
+import { resolveURL, resolveCSS } from './urlResolve'
+
+// type PickMessage = Extract;
+// type ResolversMap = {
+// [Key in MType]: (event: PickMessage) => RawMessage
+// }
+
+// TODO: commonURLBased logic for feilds
+const resolvers = {
+ [MType.SetNodeAttributeURLBased]: (msg: RawSetNodeAttributeURLBased): RawSetNodeAttribute =>
+ ({
+ ...msg,
+ value: msg.name === 'src' || msg.name === 'href'
+ ? resolveURL(msg.baseURL, msg.value)
+ : (msg.name === 'style'
+ ? resolveCSS(msg.baseURL, msg.value)
+ : msg.value
+ ),
+ tp: MType.SetNodeAttribute,
+ }),
+ [MType.SetCssDataURLBased]: (msg: RawSetCssDataURLBased): RawSetCssData =>
+ ({
+ ...msg,
+ data: resolveCSS(msg.baseURL, msg.data),
+ tp: MType.SetCssData,
+ }),
+ [MType.CssInsertRuleURLBased]: (msg: RawCssInsertRuleURLBased): RawCssInsertRule =>
+ ({
+ ...msg,
+ rule: resolveCSS(msg.baseURL, msg.rule),
+ tp: MType.CssInsertRule,
+ }),
+ [MType.AdoptedSsInsertRuleURLBased]: (msg: RawAdoptedSsInsertRuleURLBased): RawAdoptedSsInsertRule =>
+ ({
+ ...msg,
+ rule: resolveCSS(msg.baseURL, msg.rule),
+ tp: MType.AdoptedSsInsertRule,
+ }),
+ [MType.AdoptedSsReplaceURLBased]: (msg: RawAdoptedSsReplaceURLBased): RawAdoptedSsReplace =>
+ ({
+ ...msg,
+ text: resolveCSS(msg.baseURL, msg.text),
+ tp: MType.AdoptedSsReplace,
+ }),
+} as const
+
+
+export default function resolve(msg: RawMessage): RawMessage {
+ // @ts-ignore --- any idea?
+ if (resolvers[msg.tp]) {
+ // @ts-ignore
+ return resolvers[msg.tp](msg)
+ }
+ return msg
+}
\ No newline at end of file
diff --git a/frontend/app/types/run/index.js b/frontend/app/types/run/index.js
deleted file mode 100644
index 658043461..000000000
--- a/frontend/app/types/run/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import fromJS from './run';
-
-export * from './run';
-export default fromJS;
\ No newline at end of file
diff --git a/frontend/app/types/run/run.js b/frontend/app/types/run/run.js
deleted file mode 100644
index cfe35488e..000000000
--- a/frontend/app/types/run/run.js
+++ /dev/null
@@ -1,183 +0,0 @@
-import { Record, List, Map } from 'immutable';
-import { DateTime } from 'luxon';
-import Environment from 'Types/environment';
-import stepFromJS from './step';
-import seleniumStepFromJS from './seleniumStep';
-import Resource from '../session/resource';
-
-export const NOT_FETCHED = undefined;
-export const QUEUED = 'queued';
-export const INITIALIZING = 'initializing';
-export const RUNNING = 'running';
-export const COMPLETED = 'completed';
-export const PASSED = 'passed';
-export const FAILED = 'failed';
-export const STOPPED = 'stopped';
-export const CRASHED = 'crashed';
-export const EXPIRED = 'expired';
-
-export const STATUS = {
- NOT_FETCHED,
- QUEUED,
- INITIALIZING,
- RUNNING,
- COMPLETED,
- PASSED,
- FAILED,
- STOPPED,
- CRASHED,
- EXPIRED,
-}
-
-class Run extends Record({
- runId: undefined,
- testId: undefined,
- name: '',
- tags: List(),
- environment: Environment(),
- scheduled: false,
- schedulerId: undefined,
- browser: undefined,
- sessionId: undefined,
- startedAt: undefined,
- url_video: undefined,
- finishedAt: undefined,
- steps: List(),
- resources: [],
- seleniumSteps: List(),
- url_browser_logs: undefined,
- url_logs: undefined,
- url_selenium_project: undefined,
- sourceCode: undefined,
- screenshotUrl: undefined,
- clientId: undefined,
- state: NOT_FETCHED,
- baseRunId: undefined,
- lastExecutedString: undefined,
- durationString: undefined,
- hour: undefined, // TODO: fine API
- day: undefined,
- location: undefined,
- deviceType: undefined,
- advancedOptions: undefined,
- harfile: undefined,
- lighthouseHtmlFile: undefined,
- resultsFile: undefined,
- lighthouseJsonFile: undefined,
- totalStepsCount: undefined,
- auditsPerformance: Map(),
- auditsAd: Map(),
- transferredSize: undefined,
- resourcesSize: undefined,
- domBuildingTime: undefined,
- domContentLoadedTime: undefined,
- loadTime: undefined,
- starter: undefined,
- // {
- // "id": '',
- // "title": '',
- // "description": '',
- // "score": 0,
- // "scoreDisplayMode": '',
- // "numericValue": 0,
- // "numericUnit": '',
- // "displayValue": ''
- // }
-}) {
- idKey = 'runId';
- isRunning() {
- return this.state === RUNNING;
- }
- isQueued() {
- return this.state === QUEUED;
- }
- isPassed() {
- return this.state === PASSED;
- }
-}
-
-// eslint-disable-next-line complexity
-function fromJS(run = {}) {
- if (run instanceof Run) return run;
-
- const startedAt = run.startedAt && DateTime.fromMillis(run.startedAt);
- const finishedAt = run.finishedAt && DateTime.fromMillis(run.finishedAt);
- let durationString;
- let lastExecutedString;
- if (run.state === 'running') {
- durationString = 'Running...';
- lastExecutedString = 'Now';
- } else if (startedAt && finishedAt) {
- const _duration = Math.floor(finishedAt - startedAt);
- if (_duration > 10000) {
- const min = Math.floor(_duration / 60000);
- durationString = `${ min < 1 ? 1 : min } min`;
- } else {
- durationString = `${ Math.floor(_duration / 1000) } secs`;
- }
- const diff = startedAt.diffNow([ 'days', 'hours', 'minutes', 'seconds' ]).negate();
- if (diff.days > 0) {
- lastExecutedString = `${ Math.round(diff.days) } day${ diff.days > 1 ? 's' : '' } ago`;
- } else if (diff.hours > 0) {
- lastExecutedString = `${ Math.round(diff.hours) } hrs ago`;
- } else if (diff.minutes > 0) {
- lastExecutedString = `${ Math.round(diff.minutes) } min ago`;
- } else {
- lastExecutedString = `${ Math.round(diff.seconds) } sec ago`;
- }
- }
-
- const steps = List(run.steps).map(stepFromJS);
- const seleniumSteps = List(run.seleniumSteps).map(seleniumStepFromJS);
- const tags = List(run.tags);
- const environment = Environment(run.environment);
-
- let resources = List(run.network)
- .map(i => Resource({
- ...i,
- // success: 1,
- // time: i.timestamp,
- // type: 'xhr',
- // headerSize: 1200,
- // timings: {},
- }));
- const firstResourceTime = resources.map(r => r.time).reduce((a,b)=>Math.min(a,b), Number.MAX_SAFE_INTEGER);
- resources = resources
- .map(r => r.set("time", r.time - firstResourceTime))
- .sort((r1, r2) => r1.time - r2.time).toArray()
-
- const screenshotUrl = run.screenshot_url ||
- seleniumSteps.find(({ screenshotUrl }) => !!screenshotUrl, null, {}).screenshotUrl;
-
- const state = run.state === 'completed' ? PASSED : run.state;
- const networkOverview = run.networkOverview || {};
-
- return new Run({
- ...run,
- startedAt,
- finishedAt,
- durationString,
- lastExecutedString,
- steps,
- resources,
- seleniumSteps,
- tags,
- environment,
- screenshotUrl,
- state,
- deviceType: run.device || run.deviceType,
- auditsPerformance: run.lighthouseJson && run.lighthouseJson.performance,
- auditsAd: run.lighthouseJson && run.lighthouseJson.ad,
- transferredSize: networkOverview.transferredSize,
- resourcesSize: networkOverview.resourcesSize,
- domBuildingTime: networkOverview.domBuildingTime,
- domContentLoadedTime: networkOverview.domContentLoadedTime,
- loadTime: networkOverview.loadTime,
- });
-}
-
-Run.prototype.exists = function () {
- return this.runId !== undefined;
-};
-
-export default fromJS;
diff --git a/frontend/app/types/run/seleniumStep.js b/frontend/app/types/run/seleniumStep.js
deleted file mode 100644
index 5178240c7..000000000
--- a/frontend/app/types/run/seleniumStep.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Record, List } from 'immutable';
-import { DateTime, Duration } from 'luxon';
-
-const Step = Record({
- duration: undefined,
- startedAt: undefined,
- label: undefined,
- input: undefined,
- info: undefined,
- order: undefined,
- screenshotUrl: undefined,
- steps: List(),
-});
-
-function fromJS(step = {}) {
- const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt * 1000);
- const duration = step.executionTime && Duration.fromMillis(step.executionTime);
- const steps = List(step.steps).map(Step);
- const screenshotUrl = step.screenshot_url;
- return new Step({
- ...step,
- steps,
- startedAt,
- duration,
- screenshotUrl,
- });
-};
-
-export default fromJS;
\ No newline at end of file
diff --git a/frontend/app/types/run/step.js b/frontend/app/types/run/step.js
deleted file mode 100644
index 5358c0985..000000000
--- a/frontend/app/types/run/step.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Record, List } from 'immutable';
-import { DateTime, Duration } from 'luxon';
-
-const Step = Record({
- duration: undefined,
- startedAt: undefined,
- label: undefined,
- input: undefined,
- info: undefined,
- order: undefined,
- status: undefined,
- title: undefined,
- screenshotUrl: undefined,
- steps: List(),
-});
-
-function fromJS(step = {}) {
- const startedAt = step.startedAt && DateTime.fromMillis(step.startedAt);
- const duration = step.executionTime && Duration.fromMillis(step.executionTime);
- const steps = List(step.steps).map(Step);
- const screenshotUrl = step.screenshot;
- return new Step({
- ...step,
- steps,
- startedAt,
- duration,
- screenshotUrl,
- });
-};
-
-export default fromJS;
\ No newline at end of file
diff --git a/frontend/app/types/schedule.js b/frontend/app/types/schedule.js
deleted file mode 100644
index 87b38eb6d..000000000
--- a/frontend/app/types/schedule.js
+++ /dev/null
@@ -1,228 +0,0 @@
-import { Record, List, Map } from 'immutable';
-import { DateTime } from 'luxon';
-import {
- CHANNEL,
- DAYS,
- HOURS,
- EMAIL,
- SLACK,
- WEBHOOK
-} from 'App/constants/schedule';
-// import runFromJS from './run';
-import { validateEmail } from 'App/validate';
-
-export const DEFAULT_ENV_VALUE = '_';
-const Schedule = Record({
- minutes: 30,
- hour: 0,
- day: -2,
- testId: '',
- sourceCode: '',
- name: '',
- nextExecutionTime: undefined,
- numberOFExecutions: undefined,
- schedulerId: undefined,
- environmentId: DEFAULT_ENV_VALUE,
- device: 'desktop',
- locations: [],
-
- advancedOptions: false,
- headers: [{}],
- cookies: [{}],
- basicAuth: {},
- network: 'wifi',
- bypassCSP: false,
-
- slack: false,
- slackInput: [],
- webhook: false,
- webhookInput: [],
- email: false,
- emailInput: [],
- hasNotification: false,
- options: Map({ message: [], device: 'desktop' }),
-
- extraCaps: {},
-
- validateEvery() {
- if (this.day > -2) return true;
- return this.minutes >= 5 && this.minutes <= 1440;
- },
- validateWebhookEmail() {
- if (this.channel !== EMAIL) return true;
- return validateEmail(this.webhookEmail);
- },
- validateWebhook() {
- if (this.channel !== WEBHOOK) return true;
- return this.webhookId !== '';
- }
-});
-
-function fromJS(schedule = {}) {
- if (schedule instanceof Schedule) return schedule;
- const options = schedule.options || { message: [] };
- const extraCaps = options.extraCaps || { };
-
- let channel = '';
- if (schedule.webhookEmail) {
- channel = EMAIL;
- } else if (schedule.webhookId && schedule.webhook) {
- channel = schedule.webhook.type === 'slack' ? SLACK : WEBHOOK;
- }
-
- const nextExecutionTime = schedule.nextExecutionTime ?
- DateTime.fromMillis(schedule.nextExecutionTime) : undefined;
-
-
- let { day, minutes } = schedule;
- let hour;
- if (day !== -2) {
- const utcOffset = new Date().getTimezoneOffset();
- minutes = minutes - utcOffset
- minutes = minutes >= 1440 ? (minutes - 1440) : minutes;
- hour = Math.floor(minutes / 60);
- }
- // if (day !== -2) {
- // const utcOffset = new Date().getTimezoneOffset();
- // const hourOffset = Math.floor(utcOffset / 60);
- // const minuteOffset = utcOffset - 60*hourOffset;
-
- // minutes -= minuteOffset;
- // hour -= hourOffset;
- // if (day !== -1) {
- // const dayOffset = Math.floor(hour/24); // +/-1
- // day = (day + dayOffset + 7) % 7;
- // }
- // hour = (hour + 24) % 24;
- // }
-
- const slack = List(options.message).filter(i => i.type === 'slack');
- const email = List(options.message).filter(i => i.type === 'email');
- const webhook = List(options.message).filter(i => i.type === 'webhook');
-
- const headers = extraCaps.headers ? Object.keys(extraCaps.headers).map(k => ({ name: k, value: extraCaps.headers[k] })) : [{}];
- const cookies = extraCaps.cookies ? Object.keys(extraCaps.cookies).map(k => ({ name: k, value: extraCaps.cookies[k] })) : [{}];
-
- return new Schedule({
- ...schedule,
- day,
- minutes,
- hour,
- channel,
- nextExecutionTime,
- device: options.device,
- options,
- advancedOptions: !!options.extraCaps,
- bypassCSP: options.bypassCSP,
- network: options.network,
- headers,
- cookies,
- basicAuth: extraCaps.basicAuth,
-
- slack: slack.size > 0,
- slackInput: slack.map(i => parseInt(i.value)).toJS(),
-
- email: email.size > 0,
- emailInput: email.map(i => i.value).toJS(),
-
- webhook: webhook.size > 0,
- webhookInput: webhook.map(i => parseInt(i.value)).toJS(),
-
- hasNotification: !!slack || !!email || !!webhook
- });
-}
-
-function getObjetctFromArr(arr) {
- const obj = {}
- for (var i = 0; i < arr.length; i++) {
- const temp = arr[i];
- obj[temp.name] = temp.value
- }
- return obj;
-}
-
-Schedule.prototype.toData = function toData() {
- const {
- name, schedulerId, environmentId, device, options, bypassCSP, network, headers, cookies, basicAuth
- } = this;
-
- const js = this.toJS();
- options.device = device;
- options.bypassCSP = bypassCSP;
- options.network = network;
-
- options.extraCaps = {
- headers: getObjetctFromArr(headers),
- cookies: getObjetctFromArr(cookies),
- basicAuth
- };
-
- if (js.slack && js.slackInput)
- options.message = js.slackInput.map(i => ({ type: 'slack', value: i }))
- if (js.email && js.emailInput)
- options.message = options.message.concat(js.emailInput.map(i => ({ type: 'email', value: i })))
- if (js.webhook && js.webhookInput)
- options.message = options.message.concat(js.webhookInput.map(i => ({ type: 'webhook', value: i })))
-
- let day = this.day;
- let hour = undefined;
- let minutes = this.minutes;
- if (day !== -2) {
- const utcOffset = new Date().getTimezoneOffset();
- minutes = (this.hour * 60) + utcOffset;
- // minutes += utcOffset;
- minutes = minutes < 0 ? minutes + 1440 : minutes;
- }
- // if (day !== -2) {
- // const utcOffset = new Date().getTimezoneOffset();
- // const hourOffset = Math.floor(utcOffset / 60);
- // const minuteOffset = utcOffset - 60*hourOffset;
-
- // minutes = minuteOffset;
- // hour = this.hour + hourOffset;
- // if (day !== -1) {
- // const dayOffset = Math.floor(hour/24); // +/-1
- // day = (day + dayOffset + 7) % 7;
- // }
- // hour = (hour + 24) % 24;
- // }
-
- delete js.slack;
- delete js.webhook;
- delete js.email;
- delete js.slackInput;
- delete js.webhookInput;
- delete js.emailInput;
- delete js.hasNotification;
- delete js.headers;
- delete js.cookies;
-
- delete js.device;
- delete js.extraCaps;
-
- // return {
- // day, hour, name, minutes, schedulerId, environment,
- // };
- return { ...js, day, hour, name, minutes, schedulerId, environmentId, options: options };
-};
-
-Schedule.prototype.exists = function exists() {
- return this.schedulerId !== undefined;
-};
-
-Schedule.prototype.valid = function validate() {
- return this.validateEvery;
-};
-
-Schedule.prototype.getInterval = function getInterval() {
- const DAY = List(DAYS).filter(item => item.value === this.day).first();
-
- if (DAY.value === -2) {
- return DAY.text + ' ' + this.minutes + ' Minutes'; // Every 30 minutes
- }
-
- const HOUR = List(HOURS).filter(item => item.value === this.hour).first();
- return DAY.text + ' ' + HOUR.text; // Everyday/Sunday 2 AM;
-};
-
-export default fromJS;
diff --git a/frontend/app/types/step.js b/frontend/app/types/step.js
deleted file mode 100644
index 438b403d8..000000000
--- a/frontend/app/types/step.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import { Record, List, Set, isImmutable } from 'immutable';
-import { TYPES as EVENT_TYPES } from 'Types/session/event';
-
-export const CUSTOM = 'custom';
-export const CLICK = 'click';
-export const INPUT = 'input';
-export const NAVIGATE = 'navigate';
-export const TEST = 'test';
-
-export const TYPES = {
- CLICK,
- INPUT,
- CUSTOM,
- NAVIGATE,
- TEST,
-};
-
-
-const Step = defaultValues => class extends Record({
- key: undefined,
- name: '',
- imported: false,
- isDisabled: false,
- importTestId: undefined,
- ...defaultValues,
-}) {
- hasTarget() {
- return this.type === CLICK || this.type === INPUT;
- }
-
- isTest() {
- return this.type === TEST;
- }
-
- getEventType() {
- switch (this.type) {
- case INPUT:
- return EVENT_TYPES.INPUT;
- case CLICK:
- return EVENT_TYPES.CLICK;
- case NAVIGATE:
- return EVENT_TYPES.LOCATION;
- default:
- return null;
- }
- }
-
- validate() {
- const selectorsOK = this.selectors && this.selectors.size > 0;
- const valueOK = this.value && this.value.trim().length > 0;
- switch (this.type) {
- case INPUT:
- return selectorsOK;
- case CLICK:
- return selectorsOK;
- case NAVIGATE:
- return valueOK;
- case CUSTOM:
- // if (this.name.length === 0) return false;
- /* if (window.JSHINT) {
- window.JSHINT(this.code, { esversion: 6 });
- const noErrors = window.JSHINT.errors.every(({ code }) => code && code.startsWith('W'));
- return noErrors;
- } */
- return this.code && this.code.length > 0;
- default:
- return true;
- }
- }
-
- toData() {
- const {
- value,
- ...step
- } = this.toJS();
- delete step.key;
- return {
- values: value && [ value ],
- ...step,
- };
- }
-};
-
-const Custom = Step({
- type: CUSTOM,
- code: '',
- framework: 'any',
- template: '',
-});
-
-const Click = Step({
- type: CLICK,
- selectors: List(),
- customSelector: true,
-});
-
-const Input = Step({
- type: INPUT,
- selectors: List(),
- value: '',
- customSelector: true,
-});
-
-const Navigate = Step({
- type: NAVIGATE,
- value: '',
-});
-
-const TestAsStep = Step({
- type: TEST,
- testId: '',
- name: '',
- stepsCount: '',
- steps: List(),
-});
-
-const EmptyStep = Step();
-
-let uniqueKey = 0xff;
-function nextKey() {
- uniqueKey += 1;
- return `${ uniqueKey }`;
-}
-
-function fromJS(initStep = {}) {
- // TODO: more clear
- if (initStep.importTestId) return new TestAsStep(initStep).set('steps', List(initStep.steps ? initStep.steps : initStep.test.steps).map(fromJS));
- // todo: ?
- if (isImmutable(initStep)) return initStep.set('key', nextKey());
-
- const values = initStep.values && initStep.values.length > 0 && initStep.values[ 0 ];
-
- // bad code
- const step = {
- ...initStep,
- selectors: Set(initStep.selectors).toList(), // to List not nrcrssary. TODO: check
- value: initStep.value ? [initStep.value] : values,
- key: nextKey(),
- isDisabled: initStep.disabled
- };
- // bad code
-
- if (step.type === CUSTOM) return new Custom(step);
- if (step.type === CLICK) return new Click(step);
- if (step.type === INPUT) return new Input(step);
- if (step.type === NAVIGATE) return new Navigate(step);
-
- return new EmptyStep();
- // throw new Error(`Unknown step type: ${step.type}`);
-}
-
-export default fromJS;
diff --git a/third-party.md b/third-party.md
index 186ad8817..1c003748b 100644
--- a/third-party.md
+++ b/third-party.md
@@ -1,4 +1,4 @@
-## Licenses (as of November 04, 2022)
+## Licenses (as of December 12, 2022)
Below is the list of dependencies used in OpenReplay software. Licenses may change between versions, so please keep this up to date with every new library you use.
@@ -105,9 +105,8 @@ Below is the list of dependencies used in OpenReplay software. Licenses may chan
| kafka | Apache2 | Infrastructure |
| stern | Apache2 | Infrastructure |
| k9s | Apache2 | Infrastructure |
-| minio | GPLv3 | Infrastructure |
+| minio | [AGPLv3](https://github.com/minio/minio/blob/master/LICENSE) | Infrastructure |
| postgreSQL | PostgreSQL License | Infrastructure |
-| ansible | GPLv3 | Infrastructure |
| k3s | Apache2 | Infrastructure |
| nginx | BSD2 | Infrastructure |
| clickhouse | Apache2 | Infrastructure |
diff --git a/tracker/tracker-assist/README.md b/tracker/tracker-assist/README.md
index 4897477b3..662e1e084 100644
--- a/tracker/tracker-assist/README.md
+++ b/tracker/tracker-assist/README.md
@@ -2,6 +2,10 @@
OpenReplay Assist Plugin allows you to support your users by seeing their live screen and instantly hopping on call (WebRTC) with them without requiring any 3rd-party screen sharing software.
+## Documentation
+
+For launch options and available public methods, [refer to the documentation](https://docs.openreplay.com/plugins/assist)
+
## Installation
```bash
@@ -72,7 +76,7 @@ trackerAssist({
type ConfirmOptions = {
text?:string,
style?: StyleObject, // style object (i.e {color: 'red', borderRadius: '10px'})
- confirmBtn?: ButtonOptions,
+ confirmBtn?: ButtonOptions,
declineBtn?: ButtonOptions
}
@@ -82,7 +86,7 @@ type ButtonOptions = HTMLButtonElement | string | {
}
```
-- `callConfirm`: Customize the text and/or layout of the call request popup.
+- `callConfirm`: Customize the text and/or layout of the call request popup.
- `controlConfirm`: Customize the text and/or layout of the remote control request popup.
- `config`: Contains any custom ICE/TURN server configuration. Defaults to `{ 'iceServers': [{ 'urls': 'stun:stun.l.google.com:19302' }], 'sdpSemantics': 'unified-plan' }`.
- `onAgentConnect: () => (()=>void | void)`: This callback function is fired when someone from OpenReplay UI connects to the current live session. It can return another function. In this case, returned callback will be called when the same agent connection gets closed.
diff --git a/tracker/tracker/README.md b/tracker/tracker/README.md
index c47f301dc..b1daf6d4d 100644
--- a/tracker/tracker/README.md
+++ b/tracker/tracker/README.md
@@ -2,10 +2,14 @@
The main package of the [OpenReplay](https://openreplay.com/) tracker.
+## Documentation
+
+For launch options and available public methods, [refer to the documentation](https://docs.openreplay.com/installation/javascript-sdk#options)
+
## Installation
```bash
-npm i @openreplay/tracker
+npm i @openreplay/tracker
```
## Usage
@@ -13,30 +17,30 @@ npm i @openreplay/tracker
Initialize the package from your codebase entry point and start the tracker. You must set the `projectKey` option in the constructor. Its value can can be found in your OpenReplay dashboard under [Preferences -> Projects](https://app.openreplay.com/client/projects).
```js
-import Tracker from '@openreplay/tracker';
+import Tracker from '@openreplay/tracker'
const tracker = new Tracker({
projectKey: YOUR_PROJECT_KEY,
-});
-tracker.start({
- userID: "Mr.Smith",
- metadata: {
- version: "3.5.0",
- balance: "10M",
- role: "admin",
- }
-}).then(startedSession => {
- if (startedSession.success) {
- console.log(startedSession)
- }
})
+tracker
+ .start({
+ userID: 'Mr.Smith',
+ metadata: {
+ version: '3.5.0',
+ balance: '10M',
+ role: 'admin',
+ },
+ })
+ .then((startedSession) => {
+ if (startedSession.success) {
+ console.log(startedSession)
+ }
+ })
```
Then you can use OpenReplay JavaScript API anywhere in your code.
```js
-tracker.setUserID('my_user_id');
-tracker.setMetadata('env', 'prod');
+tracker.setUserID('my_user_id')
+tracker.setMetadata('env', 'prod')
```
-
-Read [our docs](https://docs.openreplay.com/) for more information.
diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts
index dc4caa143..5e33b84bc 100644
--- a/tracker/tracker/src/main/app/index.ts
+++ b/tracker/tracker/src/main/app/index.ts
@@ -464,7 +464,7 @@ export default class App {
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')
+ return Promise.reject('Tracker stopped during authorization')
}
const {
token,
diff --git a/tracker/tracker/src/webworker/QueueSender.ts b/tracker/tracker/src/webworker/QueueSender.ts
index aa1ff4589..2a2863192 100644
--- a/tracker/tracker/src/webworker/QueueSender.ts
+++ b/tracker/tracker/src/webworker/QueueSender.ts
@@ -2,24 +2,6 @@ const INGEST_PATH = '/v1/web/i'
const KEEPALIVE_SIZE_LIMIT = 64 << 10 // 64 kB
-// function sendXHR(url: string, token: string, batch: Uint8Array): Promise {
-// const req = new XMLHttpRequest()
-// req.open("POST", url)
-// req.setRequestHeader("Authorization", "Bearer " + token)
-// return new Promise((res, rej) => {
-// req.onreadystatechange = function() {
-// if (this.readyState === 4) {
-// if (this.status == 0) {
-// return; // happens simultaneously with onerror
-// }
-// res(this)
-// }
-// }
-// req.onerror = rej
-// req.send(batch.buffer)
-// })
-// }
-
export default class QueueSender {
private attemptsCount = 0
private busy = false
@@ -38,6 +20,10 @@ export default class QueueSender {
authorise(token: string): void {
this.token = token
+ if (!this.busy) {
+ // TODO: transparent busy/send logic
+ this.sendNext()
+ }
}
push(batch: Uint8Array): void {
@@ -48,9 +34,19 @@ export default class QueueSender {
}
}
+ private sendNext() {
+ const nextBatch = this.queue.shift()
+ if (nextBatch) {
+ this.sendBatch(nextBatch)
+ } else {
+ this.busy = false
+ }
+ }
+
private retry(batch: Uint8Array): void {
if (this.attemptsCount >= this.MAX_ATTEMPTS_COUNT) {
this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`)
+ // remains this.busy === true
return
}
this.attemptsCount++
@@ -83,12 +79,7 @@ export default class QueueSender {
// Success
this.attemptsCount = 0
- const nextBatch = this.queue.shift()
- if (nextBatch) {
- this.sendBatch(nextBatch)
- } else {
- this.busy = false
- }
+ this.sendNext()
})
.catch((e) => {
console.warn('OpenReplay:', e)
@@ -98,5 +89,6 @@ export default class QueueSender {
clean() {
this.queue.length = 0
+ this.token = null
}
}