/**
 * this file is used to serialize log message to string
 *
 */ import { StringifyOptions } from './index' ; param node function pathToSelector ( node ) { if ( ! node || ! node . outerHTML ) { return '' ; } let path = '' ; while ( node . parentElement ) { let name = node . localName ; if ( ! name ) { break ; } name = name . toLowerCase ( ) ; let parent = node . parentElement ; let domSiblings = [ ] ; if ( parent . children && parent . children . length > 0 ) { for ( let i = 0 ; i < parent . children . length ; i ++ ) { let sibling = parent . children [ i ] ; if ( sibling . localName && sibling . localName . toLowerCase ) { if ( sibling . localName . toLowerCase ( ) === name ) { domSiblings . push ( sibling ) ; } } } } if ( domSiblings . length > 1 ) { name += ':eq(' + domSiblings . indexOf ( node ) + ')' ; } path = name + ( path ? '>' + path : '' ) ; node = parent ; } return path ; } /**
 * judge is object
 */ function isObject ( obj ) { return Object . prototype . toString . call ( obj ) === '[object Object]' ; } /**
 * judge the object's depth
 */ function isObjTooDeep ( obj , limit ) { if ( limit === 0 ) { return true ; } const keys = Object . keys ( obj ) ; for ( const key of keys ) { if ( isObject ( obj [ key ] ) && isObjTooDeep ( obj [ key ] , limit - 1 ) ) { return true ; } } return false ; } param obj export function stringify ( obj , stringifyOptions ? ) { const options = { numOfKeysLimit : 50 , depthOfLimit : 4 , } ; Object . assign ( options , stringifyOptions ) ; const stack = [ ] ; const keys = [ ] ; return JSON . stringify ( obj , function ( key , value ) { /**
         * forked from https://github.com/moll/json-stringify-safe/blob/master/stringify.js
         * to deCycle the object
         */ if ( stack . length > 0 ) { const thisPos = stack . indexOf ( this ) ; ~ thisPos ? stack . splice ( thisPos + 1 ) : stack . push ( this ) ; ~ thisPos ? keys . splice ( thisPos , Infinity , key ) : keys . push ( key ) ; if ( ~ stack . indexOf ( value ) ) { if ( stack [ 0 ] === value ) { value = '[Circular ~]' ; } else { value = '[Circular ~.' + keys . slice ( 0 , stack . indexOf ( value ) ) . join ( '.' ) + ']' ; } } } else { stack . push ( value ) ; } if ( value === null || value === undefined ) { return value ; } if ( shouldIgnore ( value ) ) { return toString ( value ) ; } if ( value instanceof Event ) { const eventResult = { } ; for ( const eventKey in value ) { const eventValue = ( value as any ) [ eventKey ] ; if ( Array . isArray ( eventValue ) ) { eventResult [ eventKey ] = pathToSelector ( eventValue . length ? eventValue [ 0 ] : null ) ; } else { eventResult [ eventKey ] = eventValue ; } } return eventResult ; } else if ( value instanceof Node ) { if ( value instanceof HTMLElement ) { return value ? value . outerHTML : '' ; } return value . nodeName ; } return value ; } ) ; /**
     * whether we should ignore obj's info and call toString() function instead
     */ function shouldIgnore ( _obj ) { if ( isObject ( _obj ) && Object . keys ( _obj ) . length > options . numOfKeysLimit ) { return true ; } if ( typeof _obj === 'function' ) { return true ; } /**
         * judge object's depth to avoid browser's OOM
         *
         * issues: https://github.com/rrweb-io/rrweb/issues/653
         */ if ( isObject ( _obj ) && isObjTooDeep ( _obj , options . depthOfLimit ) ) { return true ; } return false ; } /**
     * limit the toString() result according to option
     */ function toString ( _obj ) { let str = _obj . toString ( ) ; if ( options . stringLengthLimit && str . length > options . stringLengthLimit ) { str = `${ str . slice ( 0 , options . stringLengthLimit ) }...` ; } return str ; } }