From 0ba1382c165bb7143d8ad826d5e28cea7cecf8fc Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Tue, 4 Mar 2025 16:37:40 +0100 Subject: [PATCH] tracker: fix spritemap parser, add svgdoc cache --- tracker/tracker/CHANGELOG.md | 8 ++++ tracker/tracker/src/main/app/index.ts | 1 + .../tracker/src/main/app/observer/observer.ts | 41 ++++++++++++++++--- .../src/main/app/observer/top_observer.ts | 9 ++-- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index c291f39d8..216f791df 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -8,6 +8,14 @@ - **[breaking]** new string dictionary message format +## 15.0.7 + +- fix for svg sprite handling + +## 15.0.6 + +- fix for batch sending to prevent proxy wrappers + ## 15.0.5 - update medv/finder to 4.0.2 for better support of css-in-js libs diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index a016bab2e..4a4684f9f 100644 --- a/tracker/tracker/src/main/app/index.ts +++ b/tracker/tracker/src/main/app/index.ts @@ -324,6 +324,7 @@ export default class App { fixedCanvasScaling: false, disableCanvas: false, captureIFrames: true, + disableSprites: false, obscureTextEmails: true, obscureTextNumbers: false, crossdomain: { diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index 006c3c7b6..7c13c0483 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -23,6 +23,7 @@ import { } from '../guards.js' const iconCache = {} +const svgUrlCache = {} const domParser = new DOMParser() async function parseUseEl(useElement: SVGUseElement, mode: 'inline' | 'dataurl' | 'svgtext') { @@ -43,15 +44,42 @@ async function parseUseEl(useElement: SVGUseElement, mode: 'inline' | 'dataurl' return iconCache[symbolId] } - const response = await fetch(url) - const svgText = await response.text() + let svgDoc: Document + if (svgUrlCache[url]) { + if (svgUrlCache[url] === 1) { + await new Promise((resolve) => { + let tries = 0 + const interval = setInterval(() => { + if (tries > 100) { + clearInterval(interval) + resolve(false) + } + if (svgUrlCache[url] !== 1) { + svgDoc = svgUrlCache[url] + clearInterval(interval) + resolve(true) + } else { + tries++ + } + }, 100) + }) + } else { + svgDoc = svgUrlCache[url] ?? `` + } + } else { + svgUrlCache[url] = 1 + const response = await fetch(url) + const svgText = await response.text() + svgDoc = domParser.parseFromString(svgText, 'image/svg+xml') + svgUrlCache[url] = svgDoc + } - const svgDoc = domParser.parseFromString(svgText, 'image/svg+xml') + // @ts-ignore const symbol = svgDoc.getElementById(symbolId) if (!symbol) { console.debug('Openreplay: Symbol not found in SVG.') - return + return '' } if (mode === 'inline') { @@ -136,10 +164,13 @@ export default abstract class Observer { private readonly indexes: Array = [] private readonly attributesMap: Map> = new Map() private readonly textSet: Set = new Set() + private readonly disableSprites: boolean = false constructor( protected readonly app: App, protected readonly isTopContext = false, + options: { disableSprites: boolean } = { disableSprites: false }, ) { + this.disableSprites = options.disableSprites this.observer = createMutationObserver( this.app.safe((mutations) => { for (const mutation of mutations) { @@ -249,7 +280,7 @@ export default abstract class Observer { this.app.send(RemoveNodeAttribute(id, name)) } - if (isUseElement(node) && name === 'href') { + if (isUseElement(node) && name === 'href' && !this.disableSprites) { parseUseEl(node, 'svgtext') .then((svgData) => { if (svgData) { diff --git a/tracker/tracker/src/main/app/observer/top_observer.ts b/tracker/tracker/src/main/app/observer/top_observer.ts index 6c14e8083..c5f623170 100644 --- a/tracker/tracker/src/main/app/observer/top_observer.ts +++ b/tracker/tracker/src/main/app/observer/top_observer.ts @@ -11,6 +11,7 @@ import { IN_BROWSER, hasOpenreplayAttribute, canAccessIframe } from '../../utils export interface Options { captureIFrames: boolean + disableSprites: boolean } type Context = Window & typeof globalThis @@ -24,14 +25,16 @@ export default class TopObserver extends Observer { readonly app: App constructor(params: { app: App; options: Partial }) { - super(params.app, true) - this.app = params.app - this.options = Object.assign( + const opts = Object.assign( { captureIFrames: true, + disableSprites: false, }, params.options, ) + super(params.app, true, opts) + this.app = params.app + this.options = opts // IFrames this.app.nodes.attachNodeCallback((node) => { if (