Merge branch 'dev' into network-request-messahe

This commit is contained in:
Alex Kaminskii 2022-12-12 16:08:00 +01:00
commit 18cdde05fd
11 changed files with 367 additions and 278 deletions

View file

@ -42,7 +42,7 @@ function UserListItem(props: Props) {
onClick={editHandler}
>
<div className="col-span-5">
<span className="mr-2">{user.name}</span>
<span className="mr-2 capitalize">{user.name}</span>
{/* {isEnterprise && <AdminPrivilegeLabel user={user} />} */}
</div>
<div className="col-span-3">

View file

@ -19,7 +19,7 @@ export default (BaseComponent) => {
}
componentDidUpdate(prevProps) {
const { urlSiteId, siteId, location: { pathname }, history } = this.props;
const shouldUrlUpdate = urlSiteId && urlSiteId !== siteId;
const shouldUrlUpdate = urlSiteId && parseInt(urlSiteId, 10) !== parseInt(siteId, 10);
if (shouldUrlUpdate) {
const path = ['', siteId].concat(pathname.split('/').slice(2)).join('/');
history.push(path);

View file

@ -1,5 +1,6 @@
import { List, Map, Record } from 'immutable';
import { List, Map } from 'immutable';
import Client from 'Types/client';
import { deleteCookie } from 'App/utils';
import Account from 'Types/account';
import { DELETE } from './jwt';
import withRequestState, { RequestTypes } from './requestStateCreator';
@ -55,6 +56,8 @@ const reducer = (state = initialState, action = {}) => {
case UPDATE_PASSWORD.FAILURE:
return state.set('passwordErrors', List(action.errors))
case DELETE:
deleteCookie('jwt', '/', '.openreplay.com')
console.log('called')
return initialState;
case PUT_CLIENT.REQUEST:
return state.mergeIn([ 'account' ], action.params);
@ -69,6 +72,7 @@ const reducer = (state = initialState, action = {}) => {
return state;
};
export default withRequestState({
loginRequest: LOGIN,
signupRequest: SIGNUP,
@ -164,4 +168,3 @@ export function setOnboarding(state = false) {
state
};
}

View file

@ -5,46 +5,48 @@ import { SESSION_FILTER } from 'App/constants/storageKeys';
import { useEffect, useRef, useState } from 'react';
export function debounce(callback, wait, context = this) {
let timeout = null;
let callbackArgs = null;
let timeout = null;
let callbackArgs = null;
const later = () => callback.apply(context, callbackArgs);
const later = () => callback.apply(context, callbackArgs);
return function (...args) {
callbackArgs = args;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
return function (...args) {
callbackArgs = args;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
export function getResourceName(url: string) {
return url
.split('/')
.filter((s) => s !== '')
.pop();
return url
.split('/')
.filter((s) => s !== '')
.pop();
}
/* eslint-disable no-mixed-operators */
export function randomInt(a, b) {
const min = (b ? a : 0) - 0.5;
const max = b || a || Number.MAX_SAFE_INTEGER;
const rand = min - 0.5 + Math.random() * (max - min + 1);
return Math.round(rand);
const min = (b ? a : 0) - 0.5;
const max = b || a || Number.MAX_SAFE_INTEGER;
const rand = min - 0.5 + Math.random() * (max - min + 1);
return Math.round(rand);
}
export const fileNameFormat = (str = '', ext = '') => {
const name = str.replace(/[^a-zA-Z0-9]/g, '_');
return `${name}${ext}`;
const name = str.replace(/[^a-zA-Z0-9]/g, '_');
return `${name}${ext}`;
};
export const toUnderscore = (s) =>
s
.split(/(?=[A-Z])/)
.join('_')
.toLowerCase();
s
.split(/(?=[A-Z])/)
.join('_')
.toLowerCase();
export const getUniqueFilter = (keys) => (item, i, list) =>
!list.some((item2, j) => j < i && keys.every((key) => item[key] === item2[key] && item[key] !== undefined));
!list.some(
(item2, j) => j < i && keys.every((key) => item[key] === item2[key] && item[key] !== undefined)
);
export const numberWithCommas = (x) => (x ? x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : 0);
@ -55,158 +57,161 @@ export const cutURL = (url, prefix = '.../') => `${prefix + url.split('/').slice
export const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export function getRE(string: string, options: string) {
let re;
try {
re = new RegExp(string, options);
} catch (e) {
re = new RegExp(escapeRegExp(string), options);
}
return re;
let re;
try {
re = new RegExp(string, options);
} catch (e) {
re = new RegExp(escapeRegExp(string), options);
}
return re;
}
export const filterList = <T extends Record<string, any>>(
list: T[],
searchQuery: string,
testKeys: string[],
searchCb?: (listItem: T, query: RegExp
) => boolean): T[] => {
if (searchQuery === '') return list;
const filterRE = getRE(searchQuery, 'i');
let _list = list.filter((listItem: T) => {
return testKeys.some((key) => filterRE.test(listItem[key])) || searchCb?.(listItem, filterRE);
});
return _list;
}
list: T[],
searchQuery: string,
testKeys: string[],
searchCb?: (listItem: T, query: RegExp) => boolean
): T[] => {
if (searchQuery === '') return list;
const filterRE = getRE(searchQuery, 'i');
let _list = list.filter((listItem: T) => {
return testKeys.some((key) => filterRE.test(listItem[key])) || searchCb?.(listItem, filterRE);
});
return _list;
};
export const getStateColor = (state) => {
switch (state) {
case 'passed':
return 'green';
case 'failed':
return 'red';
default:
return 'gray-medium';
}
switch (state) {
case 'passed':
return 'green';
case 'failed':
return 'red';
default:
return 'gray-medium';
}
};
export const convertNumberRange = (oldMax, oldMin, newMin, newMax, currentValue) => {
let newValue;
let newRange;
const oldRange = oldMax - oldMin;
let newValue;
let newRange;
const oldRange = oldMax - oldMin;
if (oldRange === 0) {
newValue = newMin;
} else {
newRange = newMax - newMin;
newValue = ((currentValue - oldMin) * newRange) / oldRange + newMin;
}
return newValue;
if (oldRange === 0) {
newValue = newMin;
} else {
newRange = newMax - newMin;
newValue = ((currentValue - oldMin) * newRange) / oldRange + newMin;
}
return newValue;
};
export const prorata = ({ parts, elements, startDivisorFn, divisorFn }) => {
const byElement = Object.entries(elements).reduce(
(ack, [element, numElements]) => ({
...ack,
[element]: { parts: 0, elements: numElements, divisor: startDivisorFn(numElements) },
}),
{}
);
const byElement = Object.entries(elements).reduce(
(ack, [element, numElements]) => ({
...ack,
[element]: { parts: 0, elements: numElements, divisor: startDivisorFn(numElements) },
}),
{}
);
while (parts > 0) {
const element = Object.entries(byElement).reduce((a, [k, v]) => (a.divisor > v.divisor ? a : v), { divisor: 0 });
// eslint-disable-next-line no-plusplus
element.parts++;
element.divisor = divisorFn(element.elements, element.parts);
// eslint-disable-next-line no-plusplus
parts--;
}
return Object.entries(byElement).reduce((a, [k, v]) => ({ ...a, [k]: v.parts }), {});
while (parts > 0) {
const element = Object.entries(byElement).reduce(
(a, [k, v]) => (a.divisor > v.divisor ? a : v),
{ divisor: 0 }
);
// eslint-disable-next-line no-plusplus
element.parts++;
element.divisor = divisorFn(element.elements, element.parts);
// eslint-disable-next-line no-plusplus
parts--;
}
return Object.entries(byElement).reduce((a, [k, v]) => ({ ...a, [k]: v.parts }), {});
};
export const titleCase = (str) => {
str = str.toLowerCase();
str = str.split('_');
for (var i = 0; i < str.length; i++) {
str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1);
}
str = str.toLowerCase();
str = str.split('_');
for (var i = 0; i < str.length; i++) {
str[i] = str[i].charAt(0).toUpperCase() + str[i].slice(1);
}
return str.join(' ');
return str.join(' ');
};
export const confirm = (message, callback) => {
const sure = window.confirm(message);
if (!sure) return;
callback();
const sure = window.confirm(message);
if (!sure) return;
callback();
};
const KB = 1 << 10;
const MB = KB << 10;
const GB = MB << 10;
export function formatBytes(bt: number): string {
if (bt > GB) {
return `${Math.trunc((bt / GB) * 1e2) / 1e2}GB`;
}
if (bt > MB) {
return `${Math.trunc((bt / MB) * 1e2) / 1e2}MB`;
}
if (bt > KB) {
return `${Math.trunc((bt / KB) * 1e2) / 1e2}KB`;
}
return `${bt}B`;
if (bt > GB) {
return `${Math.trunc((bt / GB) * 1e2) / 1e2}GB`;
}
if (bt > MB) {
return `${Math.trunc((bt / MB) * 1e2) / 1e2}MB`;
}
if (bt > KB) {
return `${Math.trunc((bt / KB) * 1e2) / 1e2}KB`;
}
return `${bt}B`;
}
export function percentOf(part: number, whole: number): number {
return whole > 0 ? (part * 100) / whole : 0;
return whole > 0 ? (part * 100) / whole : 0;
}
export function fileType(url: string) {
const filename = url.split(/[#?]/)
if (!filename || filename.length == 0) return ''
const parts = filename[0].split('.')
if (!parts || parts.length == 0) return ''
return parts.pop().trim();
const filename = url.split(/[#?]/);
if (!filename || filename.length == 0) return '';
const parts = filename[0].split('.');
if (!parts || parts.length == 0) return '';
return parts.pop().trim();
}
export function fileName(url: string) {
if (url) {
var m = url.toString().match(/.*\/(.+?)\./);
if (m && m.length > 1) {
return `${m[1]}.${fileType(url)}`;
}
if (url) {
var m = url.toString().match(/.*\/(.+?)\./);
if (m && m.length > 1) {
return `${m[1]}.${fileType(url)}`;
}
return '';
}
return '';
}
export const camelCased = (val) =>
val.replace(/_([a-z])/g, function (g) {
return g[1].toUpperCase();
});
val.replace(/_([a-z])/g, function (g) {
return g[1].toUpperCase();
});
export function capitalize(s: string) {
if (s.length === 0) return s;
return s[0].toUpperCase() + s.slice(1);
if (s.length === 0) return s;
return s[0].toUpperCase() + s.slice(1);
}
export const titleize = (str) => {
let upper = true;
let newStr = '';
for (let i = 0, l = str.length; i < l; i++) {
// Note that you can also check for all kinds of spaces with
// str[i].match(/\s/)
if (str[i] == ' ') {
upper = true;
newStr += str[i];
continue;
}
if (str[i] == '_') {
upper = true;
newStr += ' ';
continue;
}
newStr += upper ? str[i].toUpperCase() : str[i].toLowerCase();
upper = false;
let upper = true;
let newStr = '';
for (let i = 0, l = str.length; i < l; i++) {
// Note that you can also check for all kinds of spaces with
// str[i].match(/\s/)
if (str[i] == ' ') {
upper = true;
newStr += str[i];
continue;
}
return newStr;
if (str[i] == '_') {
upper = true;
newStr += ' ';
continue;
}
newStr += upper ? str[i].toUpperCase() : str[i].toLowerCase();
upper = false;
}
return newStr;
};
/**
@ -214,202 +219,224 @@ export const titleize = (str) => {
* Replacing the above line of BigInt with JSBI since the BigInt not supportiing the some of the browser (i.e Safari (< 14), Opera).
*/
export const hashProjectID = (id) => {
if (!!id) {
return JSBI.add(
JSBI.remainder(JSBI.multiply(JSBI.BigInt('2783377641436327'), JSBI.BigInt(id)), JSBI.BigInt('4503599627370496')),
JSBI.BigInt('4503599627370496')
).toString();
}
return '';
if (!!id) {
return JSBI.add(
JSBI.remainder(
JSBI.multiply(JSBI.BigInt('2783377641436327'), JSBI.BigInt(id)),
JSBI.BigInt('4503599627370496')
),
JSBI.BigInt('4503599627370496')
).toString();
}
return '';
};
export const colorScale = (values, colors) => {
return chroma.scale(colors);
return chroma.scale(colors);
};
export const truncate = (input, max = 10) => (input.length > max ? `${input.substring(0, max)}...` : input);
export const truncate = (input, max = 10) =>
input.length > max ? `${input.substring(0, max)}...` : input;
export const iceServerConfigFromString = (str) => {
if (!str || typeof str !== 'string' || str.length === 0) {
return null;
}
if (!str || typeof str !== 'string' || str.length === 0) {
return null;
}
return str.split('|').map(function (c) {
let server = null;
const arr = c.split(',');
return str.split('|').map(function (c) {
let server = null;
const arr = c.split(',');
if (!!arr[0] !== '') {
server = {};
server.urls = arr[0];
if (!!arr[1]) {
server.username = arr[1];
if (!!arr[2]) {
server.credential = arr[2];
}
}
return server;
if (!!arr[0] !== '') {
server = {};
server.urls = arr[0];
if (!!arr[1]) {
server.username = arr[1];
if (!!arr[2]) {
server.credential = arr[2];
}
});
}
return server;
}
});
};
export const isGreaterOrEqualVersion = (version, compareTo) => {
const [major, minor, patch] = version.split('-')[0].split('.');
const [majorC, minorC, patchC] = compareTo.split('-')[0].split('.');
return major > majorC || (major === majorC && minor > minorC) || (major === majorC && minor === minorC && patch >= patchC);
const [major, minor, patch] = version.split('-')[0].split('.');
const [majorC, minorC, patchC] = compareTo.split('-')[0].split('.');
return (
major > majorC ||
(major === majorC && minor > minorC) ||
(major === majorC && minor === minorC && patch >= patchC)
);
};
export const sliceListPerPage = <T extends Array<any>>(list: T, page: number, perPage = 10): T => {
const start = page * perPage;
const end = start + perPage;
return list.slice(start, end) as T;
const start = page * perPage;
const end = start + perPage;
return list.slice(start, end) as T;
};
export const positionOfTheNumber = (min, max, value, length) => {
const interval = (max - min) / length;
const position = Math.round((value - min) / interval);
return position;
const interval = (max - min) / length;
const position = Math.round((value - min) / interval);
return position;
};
export const convertElementToImage = async (el: HTMLElement) => {
// const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el);
const image = await htmlToImage.toJpeg(el, {
pixelRatio: 2,
// fontEmbedCss,
filter: function (node) {
return node.id !== 'no-print';
},
});
return image;
// const fontEmbedCss = await htmlToImage.getFontEmbedCSS(el);
const image = await htmlToImage.toJpeg(el, {
pixelRatio: 2,
// fontEmbedCss,
filter: function (node) {
return node.id !== 'no-print';
},
});
return image;
};
export const unserscoreToSpaceAndCapitalize = (str) => {
return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
return str.replace(/_/g, ' ').replace(/\w\S*/g, (txt) => {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
};
export const convertToCSV = (headers, objArray) => {
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
var str = '';
const headersMap = headers.reduce((acc, curr) => {
acc[curr.key] = curr;
return acc;
}, {});
var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
var str = '';
const headersMap = headers.reduce((acc, curr) => {
acc[curr.key] = curr;
return acc;
}, {});
str += headers.map((h) => h.label).join(',') + '\r\n';
str += headers.map((h) => h.label).join(',') + '\r\n';
for (var i = 0; i < array.length; i++) {
var line = '';
for (var index in headersMap) {
if (line !== '') line += ',';
line += array[i][index];
}
str += line + '\r\n';
for (var i = 0; i < array.length; i++) {
var line = '';
for (var index in headersMap) {
if (line !== '') line += ',';
line += array[i][index];
}
str += line + '\r\n';
}
return str;
return str;
};
export const exportCSVFile = (headers, items, fileTitle) => {
var jsonObject = JSON.stringify(items);
var csv = convertToCSV(headers, jsonObject);
var exportedFilenmae = fileTitle + '.csv' || 'export.csv';
var jsonObject = JSON.stringify(items);
var csv = convertToCSV(headers, jsonObject);
var exportedFilenmae = fileTitle + '.csv' || 'export.csv';
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, exportedFilenmae);
} else {
var link = document.createElement('a');
if (link.download !== undefined) {
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', exportedFilenmae);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
if (navigator.msSaveBlob) {
// IE 10+
navigator.msSaveBlob(blob, exportedFilenmae);
} else {
var link = document.createElement('a');
if (link.download !== undefined) {
var url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', exportedFilenmae);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
};
export const fetchErrorCheck = async (response: any) => {
if (!response.ok) {
return Promise.reject(response);
}
return response.json();
if (!response.ok) {
return Promise.reject(response);
}
return response.json();
};
export const cleanSessionFilters = (data: any) => {
const { filters, ...rest } = data;
const _fitlers = filters.filter((f: any) => {
if (f.operator === 'isAny' || f.operator === 'onAny') {
return true;
} // ignore filter with isAny/onAny operator
if (Array.isArray(f.filters) && f.filters.length > 0) {
return true;
} // ignore subfilters
const { filters, ...rest } = data;
const _fitlers = filters.filter((f: any) => {
if (f.operator === 'isAny' || f.operator === 'onAny') {
return true;
} // ignore filter with isAny/onAny operator
if (Array.isArray(f.filters) && f.filters.length > 0) {
return true;
} // ignore subfilters
return f.value !== '' && Array.isArray(f.value) && f.value.length > 0;
});
return { ...rest, filters: _fitlers };
return f.value !== '' && Array.isArray(f.value) && f.value.length > 0;
});
return { ...rest, filters: _fitlers };
};
export const getSessionFilter = () => {
return JSON.parse(localStorage.getItem(SESSION_FILTER));
return JSON.parse(localStorage.getItem(SESSION_FILTER));
};
export const setSessionFilter = (filter: any) => {
localStorage.setItem(SESSION_FILTER, JSON.stringify(filter));
localStorage.setItem(SESSION_FILTER, JSON.stringify(filter));
};
export const compareJsonObjects = (obj1: any, obj2: any) => {
return JSON.stringify(obj1) === JSON.stringify(obj2);
return JSON.stringify(obj1) === JSON.stringify(obj2);
};
export const getInitials = (name: any) => {
const names = name.split(' ');
return names.slice(0, 2).map((n: any) => n[0]).join('');
}
const names = name.split(' ');
return (
names
.slice(0, 2)
.map((n: any) => n[0]?.toUpperCase())
.join('') || ''
);
};
export function getTimelinePosition(value: any, scale: any) {
const pos = value * scale;
return pos > 100 ? 100 : pos;
const pos = value * scale;
return pos > 100 ? 100 : pos;
}
export function millisToMinutesAndSeconds(millis: any) {
const minutes = Math.floor(millis / 60000);
const seconds: any = ((millis % 60000) / 1000).toFixed(0);
return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's';
const minutes = Math.floor(millis / 60000);
const seconds: any = ((millis % 60000) / 1000).toFixed(0);
return minutes + 'm' + (seconds < 10 ? '0' : '') + seconds + 's';
}
export function throttle(func, wait, options) {
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
var context, args, result;
var timeout = null;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function () {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
var now = Date.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
}
export function deleteCookie(name: string, path: string, domain: string) {
document.cookie =
name +
'=' +
(path ? ';path=' + path : '') +
(domain ? ';domain=' + domain : '') +
';expires=Thu, 01 Jan 1970 00:00:01 GMT';
}

View file

@ -4,6 +4,8 @@ kind: CronJob
metadata:
name: clickhouse-backup
namespace: {{ .Release.Namespace }}
labels:
{{- include "clickhouse.labels" . | nindent 4 }}
spec:
schedule: "5 11 * * */4"
jobTemplate:
@ -21,15 +23,15 @@ spec:
- |
set -x
BACKUP_NAME=openreplay_clickhouse_backup$(date -u +%Y-%m-%dT%H-%M-%S)
curl -XPOST http://localhost:7171/backup/create?name=$BACKUP_NAME
curl -XPOST http://clickhouse-openreplay-clickhouse:7171/backup/create?name=$BACKUP_NAME
sleep 10
# Upload backup
# curl -XPOST http://localhost:7171/backup/upload/$BACKUP_NAME
# curl -XPOST http://clickhouse-openreplay-clickhouse:7171/backup/upload/$BACKUP_NAME
# Get the backup status
echo "Backup Status:"
curl http://localhost:7171/backup/status?name=$BACKUP_NAME
curl http://clickhouse-openreplay-clickhouse:7171/backup/status?name=$BACKUP_NAME
# List active backups
echo "Active backup in machine"
curl http://localhost:7171/backup/list
curl http://clickhouse-openreplay-clickhouse:7171/backup/list
restartPolicy: Never

View file

@ -37,4 +37,12 @@ spec:
port:
number: {{ $svcPort }}
path: /(.*)
- backend:
service:
name: frontend-openreplay
port:
number: 8080
path: /(api|assist|ws-assist)/(private|sockets-list|sockets-live|peers)(.*)
pathType: Prefix
{{- end }}

View file

@ -0,0 +1,26 @@
## 4.1.5
- fixed peerjs hack that caused ts compile issues
## 4.1.4
- added peerjs hack `Peer = Peer.default || Peer` to prevent webpack 5 import error
## 4.1.3
- fixed issue with agents reconnecting on new page (setting array without agentinfo)
## 4.1.2
- added agentInfo object to most assist actions callbacks
- added relative path (with port support) for assist connection
- ensure releaseControl is called when session is stopped
- fixed videofeed window sizes
- fixed "black squares" in videofeed when one of the users turn off camera
- fixed html chat snippet layout
## 4.1.x-4.1.1
- mice name tags
- smaller font for cursor
- remote control status for end user

View file

@ -0,0 +1,16 @@
## 4.1.10
- Added "tel" to supported input types
- Added `{ withCurrentTime: true }` to `tracker.getSessionURL` method which will return sessionURL with current session's timestamp
## 4.1.8
- recalculate timeOrigin on start to prevent wrong timestamps on "sleeping" sessions
## 4.1.7
- resend metadata on start
## 4.1.6
- remove log that potentially caused crashed during slow initial render

View file

@ -324,8 +324,8 @@ export default class App {
return this.session.getInfo().sessionID || undefined
}
getSessionURL(): string | undefined {
const { projectID, sessionID } = this.session.getInfo()
getSessionURL(options?: { withCurrentTime?: boolean }): string | undefined {
const { projectID, sessionID, timestamp } = this.session.getInfo()
if (!projectID || !sessionID) {
this.debug.error('OpenReplay error: Unable to build session URL')
return undefined
@ -335,7 +335,14 @@ export default class App {
const projectPath = isSaas ? ingest.replace('api', 'app') : ingest
return projectPath.replace(/ingest$/, `${projectID}/session/${sessionID}`)
const url = projectPath.replace(/ingest$/, `${projectID}/session/${sessionID}`)
if (options?.withCurrentTime) {
const jumpTo = now() - timestamp
return `${url}?jumpto=${jumpTo}`
}
return url
}
getHost(): string {

View file

@ -216,11 +216,11 @@ export default class API {
return this.getSessionID()
}
getSessionURL(): string | undefined {
getSessionURL(options?: { withCurrentTime?: boolean }): string | undefined {
if (this.app === null) {
return undefined
}
return this.app.getSessionURL()
return this.app.getSessionURL(options)
}
setUserID(id: string): void {

View file

@ -3,7 +3,7 @@ import { normSpaces, IN_BROWSER, getLabelAttribute } from '../utils.js'
import { hasTag } from '../app/guards.js'
import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.gen.js'
const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date']
const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date', 'tel']
// TODO: take into consideration "contenteditable" attribute
type TextEditableElement = HTMLInputElement | HTMLTextAreaElement