@@ -69,7 +69,11 @@ class TagLinkInternal {
6969 if ( ! this . jstagExist ( ) ) return ;
7070
7171 ( window as any ) . jstag . call ( 'entityReady' , ( ) => {
72- this . dispatchEvent ( 'config' , ( window as any ) . jstag . config ) ;
72+ try {
73+ this . dispatchEvent ( 'config' , ( window as any ) . jstag . config ) ;
74+ } catch ( err ) {
75+ this . emitLog ( 'tag' , { msg : 'failed to read config' , error : String ( err ) } ) ;
76+ }
7377 } ) ;
7478 }
7579
@@ -78,9 +82,12 @@ class TagLinkInternal {
7882
7983 ( window as any ) . jstag . call ( 'entityReady' , entity => {
8084 ( window as any ) . jstag . getid ( id => {
81- // this.emitLog('tag', 'got entity');
82- entity . data . _uid = id ;
83- this . dispatchEvent ( 'entity' , entity ) ;
85+ try {
86+ entity . data . _uid = id ;
87+ this . dispatchEvent ( 'entity' , entity ) ;
88+ } catch ( err ) {
89+ this . emitLog ( 'tag' , { msg : 'failed to read entity' , error : String ( err ) } ) ;
90+ }
8491 } ) ;
8592 } ) ;
8693 }
@@ -100,27 +107,54 @@ class TagLinkInternal {
100107 ` ) ;
101108 }
102109
110+ safeClone ( value : any , seen = new WeakSet ( ) ) : any {
111+ if ( value === null || typeof value !== 'object' ) {
112+ return typeof value === 'bigint' ? value . toString ( ) : value ;
113+ }
114+
115+ try {
116+ if ( value === value . window || value . self === value ) return undefined ;
117+ } catch {
118+ return undefined ;
119+ }
120+
121+ // Only walk plain data (object literals / arrays). Anything else - DOM nodes, Window,
122+ // class instances, Map/Set, etc. - can reach huge or exotic graphs (e.g. a DOM element's
123+ // ownerDocument/parentNode chain), so skip it rather than cloning it.
124+ const proto = Object . getPrototypeOf ( value ) ;
125+ const isPlainObject = proto === Object . prototype || proto === null ;
126+ if ( ! Array . isArray ( value ) && ! isPlainObject ) {
127+ return undefined ;
128+ }
129+
130+ if ( seen . has ( value ) ) return undefined ;
131+ seen . add ( value ) ;
132+
133+ if ( Array . isArray ( value ) ) return value . map ( v => this . safeClone ( v , seen ) ) ;
134+
135+ const out : Record < string , any > = { } ;
136+ for ( const key in value ) {
137+ try {
138+ const v = value [ key ] ;
139+ if ( typeof v !== 'function' ) out [ key ] = this . safeClone ( v , seen ) ;
140+ } catch {
141+ // skip unreadable (cross-origin) property
142+ }
143+ }
144+ return out ;
145+ }
146+
103147 dispatchEvent ( name : string , payload : any ) {
104- const safeStringify = ( obj : any ) => {
105- const seen = new WeakSet ( ) ;
106-
107- return JSON . stringify ( obj , ( key , value ) => {
108- if ( value !== null && typeof value === 'object' ) {
109- if ( seen . has ( value ) ) {
110- return undefined ;
111- }
112- seen . add ( value ) ;
113- }
114- return value ;
148+ try {
149+ const customEvent = new CustomEvent ( name , {
150+ detail : {
151+ data : JSON . stringify ( this . safeClone ( payload ) ) ,
152+ } ,
115153 } ) ;
116- } ;
117-
118- const customEvent = new CustomEvent ( name , {
119- detail : {
120- data : safeStringify ( payload ) ,
121- } ,
122- } ) ;
123- document . dispatchEvent ( customEvent ) ;
154+ document . dispatchEvent ( customEvent ) ;
155+ } catch ( err ) {
156+ this . emitLog ( 'tag' , { msg : 'failed to serialize payload' , name, error : String ( err ) } ) ;
157+ }
124158 }
125159}
126160
0 commit comments