From 4da58f3a43585fa6bfdfe3df7d8e922bf41b85d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnup?= Date: Thu, 2 Jul 2026 12:57:37 +0530 Subject: [PATCH 1/2] chore: fixed loading issue for lytics domain --- package.json | 2 +- src/pages/tagLink/tagLink.tsx | 54 +++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 32741f1..a464bec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "lytics-dev-tools", "version": "2.0.0", - "title": "Contentstack Data & Insights Dev Tools", + "title": "Lytics Dev Tools", "description": "Advanced Chrome extension v2 for exploring, debugging, and enhancing the Lytics web SDK and APIs with powerful new features.", "license": "MIT", "repository": { diff --git a/src/pages/tagLink/tagLink.tsx b/src/pages/tagLink/tagLink.tsx index d3cb8e3..f49320c 100644 --- a/src/pages/tagLink/tagLink.tsx +++ b/src/pages/tagLink/tagLink.tsx @@ -100,27 +100,43 @@ class TagLinkInternal { `); } + safeClone(value: any, seen = new WeakSet()): any { + if (value === null || typeof value !== 'object') return value; + + try { + if (value === value.window || value.self === value) return undefined; + } catch { + return undefined; + } + + if (seen.has(value)) return undefined; + seen.add(value); + + if (Array.isArray(value)) return value.map(v => this.safeClone(v, seen)); + + const out: Record = {}; + for (const key in value) { + try { + const v = value[key]; + if (typeof v !== 'function') out[key] = this.safeClone(v, seen); + } catch { + // skip unreadable (cross-origin) property + } + } + return out; + } + dispatchEvent(name: string, payload: any) { - const safeStringify = (obj: any) => { - const seen = new WeakSet(); - - return JSON.stringify(obj, (key, value) => { - if (value !== null && typeof value === 'object') { - if (seen.has(value)) { - return undefined; - } - seen.add(value); - } - return value; + try { + const customEvent = new CustomEvent(name, { + detail: { + data: JSON.stringify(this.safeClone(payload)), + }, }); - }; - - const customEvent = new CustomEvent(name, { - detail: { - data: safeStringify(payload), - }, - }); - document.dispatchEvent(customEvent); + document.dispatchEvent(customEvent); + } catch (err) { + this.emitLog('tag', { msg: 'failed to serialize payload', name, error: String(err) }); + } } } From adac71217212cf8bcf41af00eb75388aef7d1e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CAnup?= Date: Thu, 2 Jul 2026 15:54:35 +0530 Subject: [PATCH 2/2] chore: minor memory issues and iframe fix --- src/global.d.ts | 2 + .../content/modules/scriptInjection.test.ts | 2 +- src/pages/content/modules/scriptInjection.ts | 5 +- src/pages/sidepanel/sections/TagActivity.tsx | 2 +- src/pages/tagLink/tagLink.js | 79 +++++++++++++------ src/pages/tagLink/tagLink.tsx | 28 +++++-- src/shared/storages/tabStateStorage.ts | 5 +- vite.config.ts | 3 + 8 files changed, 95 insertions(+), 31 deletions(-) diff --git a/src/global.d.ts b/src/global.d.ts index 4ac64dc..d5e8124 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,3 +1,5 @@ +declare const __TAGLINK_BUILD_ID__: string; + declare module 'virtual:reload-on-update-in-background-script' { export const reloadOnUpdate: (watchPath: string) => void; export default reloadOnUpdate; diff --git a/src/pages/content/modules/scriptInjection.test.ts b/src/pages/content/modules/scriptInjection.test.ts index bceceae..faf57b9 100644 --- a/src/pages/content/modules/scriptInjection.test.ts +++ b/src/pages/content/modules/scriptInjection.test.ts @@ -45,7 +45,7 @@ describe('Script Injection Module', () => { injectScript(); expect(document.createElement).toHaveBeenCalledWith('script'); - expect(mockScript.src).toBe('chrome-extension://test/tagLink.js'); + expect(mockScript.src).toMatch(/^chrome-extension:\/\/test\/tagLink\.js\?v=/); expect(document.documentElement.appendChild).toHaveBeenCalledWith(mockScript); }); diff --git a/src/pages/content/modules/scriptInjection.ts b/src/pages/content/modules/scriptInjection.ts index 1e1db25..1511668 100644 --- a/src/pages/content/modules/scriptInjection.ts +++ b/src/pages/content/modules/scriptInjection.ts @@ -10,7 +10,10 @@ export const injectScript = (scriptInjectedRef?: MutableRefObject) => { const script = document.createElement('script'); - script.src = chrome.runtime.getURL('/tagLink.js'); + // Cache-bust: tagLink.js keeps a stable filename (required by manifest.web_accessible_resources), + // so without a query param the browser can keep serving an old cached copy after an extension update. + const buildId = typeof __TAGLINK_BUILD_ID__ !== 'undefined' ? __TAGLINK_BUILD_ID__ : ''; + script.src = `${chrome.runtime.getURL('/tagLink.js')}?v=${buildId}`; script.onload = () => { EmitLog({ name: 'content', payload: { msg: 'TagLink script loaded successfully' } }); if (scriptInjectedRef) { diff --git a/src/pages/sidepanel/sections/TagActivity.tsx b/src/pages/sidepanel/sections/TagActivity.tsx index dae4766..f64c28f 100644 --- a/src/pages/sidepanel/sections/TagActivity.tsx +++ b/src/pages/sidepanel/sections/TagActivity.tsx @@ -92,7 +92,7 @@ const TagActivity = ({ textContent = appContent.tagActivity }: TagActivityProps) {tagActivity.map((item, index) => ( <> {item && ( - + } aria-controls="panel1a-content" id={index.toString()}> { - this.dispatchEvent('config', window.jstag.config); + try { + this.dispatchEvent('config', window.jstag.config); + } catch (err) { + this.emitLog('tag', { msg: 'failed to read config', error: String(err) }); + } }); } @@ -79,8 +83,12 @@ class TagLinkInternal { window.jstag.call('entityReady', entity => { window.jstag.getid(id => { - entity.data._uid = id; - this.dispatchEvent('entity', entity); + try { + entity.data._uid = id; + this.dispatchEvent('entity', entity); + } catch (err) { + this.emitLog('tag', { msg: 'failed to read entity', error: String(err) }); + } }); }); } @@ -100,27 +108,54 @@ class TagLinkInternal { `); } + safeClone(value, seen = new WeakSet()) { + if (value === null || typeof value !== 'object') { + return typeof value === 'bigint' ? value.toString() : value; + } + + try { + if (value === value.window || value.self === value) return undefined; + } catch { + return undefined; + } + + // Only walk plain data (object literals / arrays). Anything else - DOM nodes, Window, + // class instances, Map/Set, etc. - can reach huge or exotic graphs (e.g. a DOM element's + // ownerDocument/parentNode chain), so skip it rather than cloning it. + const proto = Object.getPrototypeOf(value); + const isPlainObject = proto === Object.prototype || proto === null; + if (!Array.isArray(value) && !isPlainObject) { + return undefined; + } + + if (seen.has(value)) return undefined; + seen.add(value); + + if (Array.isArray(value)) return value.map(v => this.safeClone(v, seen)); + + const out = {}; + for (const key in value) { + try { + const v = value[key]; + if (typeof v !== 'function') out[key] = this.safeClone(v, seen); + } catch { + // skip unreadable (cross-origin) property + } + } + return out; + } + dispatchEvent(name, payload) { - const safeStringify = obj => { - const seen = new WeakSet(); - - return JSON.stringify(obj, (key, value) => { - if (value !== null && typeof value === 'object') { - if (seen.has(value)) { - return undefined; - } - seen.add(value); - } - return value; + try { + const customEvent = new CustomEvent(name, { + detail: { + data: JSON.stringify(this.safeClone(payload)), + }, }); - }; - - const customEvent = new CustomEvent(name, { - detail: { - data: safeStringify(payload), - }, - }); - document.dispatchEvent(customEvent); + document.dispatchEvent(customEvent); + } catch (err) { + this.emitLog('tag', { msg: 'failed to serialize payload', name, error: String(err) }); + } } } diff --git a/src/pages/tagLink/tagLink.tsx b/src/pages/tagLink/tagLink.tsx index f49320c..a30ebc4 100644 --- a/src/pages/tagLink/tagLink.tsx +++ b/src/pages/tagLink/tagLink.tsx @@ -69,7 +69,11 @@ class TagLinkInternal { if (!this.jstagExist()) return; (window as any).jstag.call('entityReady', () => { - this.dispatchEvent('config', (window as any).jstag.config); + try { + this.dispatchEvent('config', (window as any).jstag.config); + } catch (err) { + this.emitLog('tag', { msg: 'failed to read config', error: String(err) }); + } }); } @@ -78,9 +82,12 @@ class TagLinkInternal { (window as any).jstag.call('entityReady', entity => { (window as any).jstag.getid(id => { - // this.emitLog('tag', 'got entity'); - entity.data._uid = id; - this.dispatchEvent('entity', entity); + try { + entity.data._uid = id; + this.dispatchEvent('entity', entity); + } catch (err) { + this.emitLog('tag', { msg: 'failed to read entity', error: String(err) }); + } }); }); } @@ -101,7 +108,9 @@ class TagLinkInternal { } safeClone(value: any, seen = new WeakSet()): any { - if (value === null || typeof value !== 'object') return value; + if (value === null || typeof value !== 'object') { + return typeof value === 'bigint' ? value.toString() : value; + } try { if (value === value.window || value.self === value) return undefined; @@ -109,6 +118,15 @@ class TagLinkInternal { return undefined; } + // Only walk plain data (object literals / arrays). Anything else - DOM nodes, Window, + // class instances, Map/Set, etc. - can reach huge or exotic graphs (e.g. a DOM element's + // ownerDocument/parentNode chain), so skip it rather than cloning it. + const proto = Object.getPrototypeOf(value); + const isPlainObject = proto === Object.prototype || proto === null; + if (!Array.isArray(value) && !isPlainObject) { + return undefined; + } + if (seen.has(value)) return undefined; seen.add(value); diff --git a/src/shared/storages/tabStateStorage.ts b/src/shared/storages/tabStateStorage.ts index 48a2212..ca01219 100644 --- a/src/shared/storages/tabStateStorage.ts +++ b/src/shared/storages/tabStateStorage.ts @@ -3,6 +3,7 @@ import { EmitLog } from '@src/shared/components/EmitLog'; import { EventModel } from '@src/shared/models/eventModel'; const TabStateStorageKey = 'domainStates'; +const MAX_TAG_ACTIVITY_EVENTS = 200; export interface DomainState { domain: string; @@ -129,7 +130,9 @@ const domainStateStore: DomainStateStorage = { allStates[domain] = createDefaultDomainState(domain); } - allStates[domain].tagActivity = [...(allStates[domain].tagActivity || []), event]; + const nextActivity = [...(allStates[domain].tagActivity || []), event]; + allStates[domain].tagActivity = + nextActivity.length > MAX_TAG_ACTIVITY_EVENTS ? nextActivity.slice(-MAX_TAG_ACTIVITY_EVENTS) : nextActivity; allStates[domain].lastUpdated = Date.now(); await storage.set(allStates); diff --git a/vite.config.ts b/vite.config.ts index d8e7946..eca2050 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -17,6 +17,9 @@ const assetsDir = resolve(srcDir, 'assets'); const publicDir = resolve(rootDir, 'public'); export default defineConfig({ + define: { + __TAGLINK_BUILD_ID__: JSON.stringify(Date.now()), + }, resolve: { alias: { '@root': rootDir,