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 (