diff --git a/tracker/tracker-assist/.yarn/install-state.gz b/tracker/tracker-assist/.yarn/install-state.gz index e4a338e9c..1ece5d84d 100644 Binary files a/tracker/tracker-assist/.yarn/install-state.gz and b/tracker/tracker-assist/.yarn/install-state.gz differ diff --git a/tracker/tracker/CHANGELOG.md b/tracker/tracker/CHANGELOG.md index 0491100e1..fe35289de 100644 --- a/tracker/tracker/CHANGELOG.md +++ b/tracker/tracker/CHANGELOG.md @@ -1,3 +1,7 @@ +## 15.0.7 + +- fix for svg sprite handling + ## 15.0.6 - fix for batch sending to prevent proxy wrappers diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index e9b0b8a74..6402047db 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": "15.0.6", + "version": "15.0.7", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/index.ts b/tracker/tracker/src/main/app/index.ts index b29ca1254..125358cd4 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 d386b5b81..b2163dd26 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 (