diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 4382ae0b9..8ff9af3f3 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -18,6 +18,7 @@ function OverviewPanel({ issuesList }: { issuesList: Record[] }) { const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState([ 'PERFORMANCE', + 'FRUSTRATIONS', 'ERRORS', 'NETWORK', ]); diff --git a/frontend/app/player/web/Screen/Cursor.ts b/frontend/app/player/web/Screen/Cursor.ts index dc3637c1b..19283d9d4 100644 --- a/frontend/app/player/web/Screen/Cursor.ts +++ b/frontend/app/player/web/Screen/Cursor.ts @@ -8,6 +8,7 @@ export default class Cursor { private tagElement: HTMLDivElement; private coords = { x: 0, y: 0 }; private isMoving = false; + private onClick: () => void; constructor(overlay: HTMLDivElement, isMobile: boolean) { this.cursor = document.createElement('div'); @@ -75,13 +76,14 @@ export default class Cursor { click() { const styleList = this.isMobile ? styles.clickedMobile : styles.clicked this.cursor.classList.add(styleList) + this.onClick?.() setTimeout(() => { this.cursor.classList.remove(styleList) }, 600) } - // TODO (to keep on a different playing speed): - // transition - // setTransitionSpeed() + setOnClickHook(callback: () => void) { + this.onClick = callback + } } diff --git a/frontend/app/player/web/Screen/Screen.ts b/frontend/app/player/web/Screen/Screen.ts index 185b7c769..52d309873 100644 --- a/frontend/app/player/web/Screen/Screen.ts +++ b/frontend/app/player/web/Screen/Screen.ts @@ -58,6 +58,7 @@ function isIframe(el: Element): el is HTMLIFrameElement { export default class Screen { readonly overlay: HTMLDivElement readonly cursor: Cursor + private selectionTargets: { start?: HTMLDivElement, end?: HTMLDivElement } = { start: undefined, end: undefined } private readonly iframe: HTMLIFrameElement; private readonly screen: HTMLDivElement; @@ -253,4 +254,27 @@ export default class Screen { setOnUpdate(cb: any) { this.onUpdateHook = cb } + + public createSelection(start: HTMLDivElement, end: HTMLDivElement) { + this.selectionTargets = { start, end } + + this.overlay.appendChild(start); + this.overlay.appendChild(end); + + setTimeout(() => { + start.className = styles.highlightoff + end.className = styles.highlightoff + }, 750) + } + + public clearSelection() { + if (this.selectionTargets.start && this.selectionTargets.end) { + this.overlay.removeChild(this.selectionTargets.start); + this.overlay.removeChild(this.selectionTargets.end); + this.selectionTargets.start.remove() + this.selectionTargets.end.remove() + this.selectionTargets = { start: undefined, end: undefined } + } + + } } diff --git a/frontend/app/player/web/Screen/screen.module.css b/frontend/app/player/web/Screen/screen.module.css index f9a2d2d8a..b69579935 100644 --- a/frontend/app/player/web/Screen/screen.module.css +++ b/frontend/app/player/web/Screen/screen.module.css @@ -20,3 +20,8 @@ bottom: 0; z-index: 10; } + +.highlightoff { + opacity: 0; + transition: all 0.25s cubic-bezier(0, 0, 0.4, 1.0); +} \ No newline at end of file diff --git a/frontend/app/player/web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts index 85d21e85d..2b98a8cfe 100644 --- a/frontend/app/player/web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -61,6 +61,10 @@ export default class DOMManager extends ListWalker { this.stringDict = stringDict } + public clearSelectionManager() { + this.selectionManager.clearSelection() + } + append(m: Message): void { if (m.tp === MType.SetNodeScroll) { let scrollManager = this.nodeScrollManagers.get(m.id) diff --git a/frontend/app/player/web/managers/DOM/SelectionManager.ts b/frontend/app/player/web/managers/DOM/SelectionManager.ts index c457ec3b8..8f44d7362 100644 --- a/frontend/app/player/web/managers/DOM/SelectionManager.ts +++ b/frontend/app/player/web/managers/DOM/SelectionManager.ts @@ -9,15 +9,12 @@ export default class SelectionManager extends ListWalker { } private selected: [{ id: number, node: Element } | null, { id: number, node: Element } | null] = [null, null]; - private markers: Element[] = [] - clearSelection() { - this.selected[0] && this.screen.overlay.removeChild(this.selected[0].node) && this.selected[0].node.remove(); - this.selected[1] && this.screen.overlay.removeChild(this.selected[1].node) && this.selected[1].node.remove(); - this.markers.forEach(marker => marker.remove()) + public clearSelection = () => { + if (this.selected[0] === null && this.selected[1] === null) return; + this.screen.clearSelection() this.selected = [null, null]; - this.markers = []; } move(t: number) { @@ -25,6 +22,7 @@ export default class SelectionManager extends ListWalker { if (!msg) { return; } + // in theory: empty selection or selection removed if (msg.selectionStart <= 0) { this.clearSelection() @@ -48,26 +46,22 @@ export default class SelectionManager extends ListWalker { Object.assign(endPointer.style, { top: endCoords.top + 'px', - left: (endCoords.left + (endCoords.width / 2) + 3) + 'px', - width: (endCoords.width / 2) + 'px', + right: (endCoords.right) + 'px', + width: (endCoords.width) + 'px', height: endCoords.height + 'px', - borderRight: '2px solid blue', position: 'absolute', boxShadow: '1px 4px 1px -2px blue', }); Object.assign(startPointer.style, { top: startCoords.top + 'px', - left: (startCoords.left - 3) + 'px', - width: (startCoords.width / 2 ) + 'px', + left: (startCoords.left) + 'px', + width: (startCoords.width) + 'px', height: startCoords.height + 'px', - borderLeft: '2px solid blue', position: 'absolute', boxShadow: '1px 4px 1px -2px blue', }); - this.markers.push(startPointer, endPointer); - this.screen.overlay.appendChild(startPointer); - this.screen.overlay.appendChild(endPointer); + this.screen.createSelection(startPointer, endPointer); this.selected = [{ id: msg.selectionStart, node: startPointer }, { id: msg.selectionEnd, node: endPointer }]; } diff --git a/frontend/app/player/web/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts index 4ab78a0ac..d6c0f0467 100644 --- a/frontend/app/player/web/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -59,6 +59,7 @@ export default class PagesManager extends ListWalker { moveReady(t: number): Promise { const requiredPage = this.moveGetLast(t) if (requiredPage != null) { + this.currentPage?.clearSelectionManager() this.currentPage = requiredPage this.currentPage.reset() // Otherwise it won't apply create_document } diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index dd9840daf..803db28a1 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,8 @@ +# 7.0.1 + +- fix time inputs capturing +- add option to disable network tracking inside iframes + # 7.0.0 - **[breaking]** added gzip compression to large messages diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 4eada9868..0f4a1a4f5 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "7.0.0", + "version": "7.0.1", "keywords": [ "logging", "replay" @@ -40,7 +40,7 @@ "lint-staged": "^13.0.3", "prettier": "^2.7.1", "replace-in-files": "^2.0.3", - "rollup": "^2.17.0", + "rollup": "^2.59.0", "rollup-plugin-terser": "^6.1.0", "semver": "^6.3.0", "ts-jest": "^29.0.3", diff --git a/tracker/tracker/src/main/modules/input.ts b/tracker/tracker/src/main/modules/input.ts index 62c059748..cb3c67629 100644 --- a/tracker/tracker/src/main/modules/input.ts +++ b/tracker/tracker/src/main/modules/input.ts @@ -3,7 +3,17 @@ import { normSpaces, IN_BROWSER, getLabelAttribute, now } from '../utils.js' import { hasTag } from '../app/guards.js' import { InputChange, SetInputValue, SetInputChecked } from '../app/messages.gen.js' -const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date', 'tel'] +const INPUT_TYPES = [ + 'text', + 'password', + 'email', + 'search', + 'number', + 'range', + 'date', + 'tel', + 'time', +] // TODO: take into consideration "contenteditable" attribute type TextFieldElement = HTMLInputElement | HTMLTextAreaElement diff --git a/tracker/tracker/src/main/modules/network.ts b/tracker/tracker/src/main/modules/network.ts index de2ea9789..a0c2eedbe 100644 --- a/tracker/tracker/src/main/modules/network.ts +++ b/tracker/tracker/src/main/modules/network.ts @@ -79,6 +79,7 @@ export interface Options { failuresOnly: boolean ignoreHeaders: Array | boolean capturePayload: boolean + captureInIframes: boolean sanitizer?: Sanitizer } @@ -89,6 +90,7 @@ export default function (app: App, opts: Partial = {}) { ignoreHeaders: ['Cookie', 'Set-Cookie', 'Authorization'], capturePayload: false, sessionTokenHeader: false, + captureInIframes: true, }, opts, ) @@ -312,7 +314,6 @@ export default function (app: App, opts: Partial = {}) { ) { const rdo = getXHRRequestDataObject(this) rdo.body = body - // @ts-ignore ??? this -> XMLHttpRequest return nativeSend.apply(this, arguments) } @@ -336,5 +337,7 @@ export default function (app: App, opts: Partial = {}) { patchWindow(window) - app.observer.attachContextCallback(app.safe(patchWindow)) + if (options.captureInIframes) { + app.observer.attachContextCallback(app.safe(patchWindow)) + } }