import { snapshot , MaskInputOptions , SlimDOMOptions } from 'rrweb-snapshot' ; import { initObservers , mutationBuffers } from './observer' ; import { on , getWindowWidth , getWindowHeight , polyfill , isIframeINode , hasShadowRoot , createMirror , } from '../utils' ; import { EventType , event , eventWithTime , recordOptions , IncrementalSource , listenerHandler , mutationCallbackParam , scrollCallback , } from '../types' ; import { IframeManager } from './iframe-manager' ; import { ShadowDomManager } from './shadow-dom-manager' ; function wrapEvent ( e ) { return { ... e , timestamp : Date . now ( ) , } ; } let wrappedEmit ! ; let takeFullSnapshot ! ; const mirror = createMirror ( ) ; function record < T = eventWithTime > ( options = { } ) { const { emit , checkoutEveryNms , checkoutEveryNth , blockClass = 'rr-block' , blockSelector = null , ignoreClass = 'rr-ignore' , maskTextClass = 'rr-mask' , maskTextSelector = null , inlineStylesheet = true , maskAllInputs , maskInputOptions : _maskInputOptions , slimDOMOptions : _slimDOMOptions , maskInputFn , maskTextFn , hooks , packFn , sampling = { } , mousemoveWait , recordCanvas = false , userTriggeredOnInput = false , collectFonts = false , plugins , keepIframeSrcFn = ( ) => false , } = options ; if ( ! emit ) { throw new Error ( 'emit function is required' ) ; } if ( mousemoveWait !== undefined && sampling . mousemove === undefined ) { sampling . mousemove = mousemoveWait ; } const maskInputOptions = maskAllInputs === true ? { color : true , date : true , 'datetime-local' : true , email : true , month : true , number : true , range : true , search : true , tel : true , text : true , time : true , url : true , week : true , textarea : true , select : true , password : true , } : _maskInputOptions !== undefined ? _maskInputOptions : { password : true } ; const slimDOMOptions = _slimDOMOptions === true || _slimDOMOptions === 'all' ? { script : true , comment : true , headFavicon : true , headWhitespace : true , headMetaSocial : true , headMetaRobots : true , headMetaHttpEquiv : true , headMetaVerification : true , headMetaAuthorship : _slimDOMOptions === 'all' , headMetaDescKeywords : _slimDOMOptions === 'all' , } : _slimDOMOptions ? _slimDOMOptions : { } ; polyfill ( ) ; let lastFullSnapshotEvent ; let incrementalSnapshotCount = 0 ; wrappedEmit = ( e , isCheckout ? ) => { if ( mutationBuffers [ 0 ] ?. isFrozen ( ) && e . type !== EventType . FullSnapshot && ! ( e . type === EventType . IncrementalSnapshot && e . data . source === IncrementalSource . Mutation ) ) { mutationBuffers . forEach ( ( buf ) => buf . unfreeze ( ) ) ; } emit ( ( ( packFn ? packFn ( e ) : e ) as unknown ) as T , isCheckout ) ; if ( e . type === EventType . FullSnapshot ) { lastFullSnapshotEvent = e ; incrementalSnapshotCount = 0 ; } else if ( e . type === EventType . IncrementalSnapshot ) { if ( e . data . source === IncrementalSource . Mutation && e . data . isAttachIframe ) { return ; } incrementalSnapshotCount ++ ; const exceedCount = checkoutEveryNth && incrementalSnapshotCount >= checkoutEveryNth ; const exceedTime = checkoutEveryNms && e . timestamp - lastFullSnapshotEvent . timestamp > checkoutEveryNms ; if ( exceedCount || exceedTime ) { takeFullSnapshot ( true ) ; } } } ; const wrappedMutationEmit = ( m ) => { wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . Mutation , ... m , } , } ) ) ; } ; const wrappedScrollEmit = ( p ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . Scroll , ... p , } , } ) ) ; const iframeManager = new IframeManager ( { mutationCb : wrappedMutationEmit , } ) ; const shadowDomManager = new ShadowDomManager ( { mutationCb : wrappedMutationEmit , scrollCb : wrappedScrollEmit , bypassOptions : { blockClass , blockSelector , maskTextClass , maskTextSelector , inlineStylesheet , maskInputOptions , maskTextFn , maskInputFn , recordCanvas , sampling , slimDOMOptions , iframeManager , } , mirror , } ) ; takeFullSnapshot = ( isCheckout = false ) => { wrappedEmit ( wrapEvent ( { type : EventType . Meta , data : { href : window . location . href , width : getWindowWidth ( ) , height : getWindowHeight ( ) , } , } ) , isCheckout ) ; mutationBuffers . forEach ( ( buf ) => buf . lock ( ) ) ; const [ node , idNodeMap ] = snapshot ( document , { blockClass , blockSelector , maskTextClass , maskTextSelector , inlineStylesheet , maskAllInputs : maskInputOptions , maskTextFn , slimDOM : slimDOMOptions , recordCanvas , onSerialize : ( n ) => { if ( isIframeINode ( n ) ) { iframeManager . addIframe ( n ) ; } if ( hasShadowRoot ( n ) ) { shadowDomManager . addShadowRoot ( n . shadowRoot , document ) ; } } , onIframeLoad : ( iframe , childSn ) => { iframeManager . attachIframe ( iframe , childSn ) ; } , keepIframeSrcFn , } ) ; if ( ! node ) { return console . warn ( 'Failed to snapshot the document' ) ; } mirror . map = idNodeMap ; wrappedEmit ( wrapEvent ( { type : EventType . FullSnapshot , data : { node , initialOffset : { left : window . pageXOffset !== undefined ? window . pageXOffset : document ?. documentElement . scrollLeft || document ?. body ?. parentElement ?. scrollLeft || document ?. body . scrollLeft || 0 , top : window . pageYOffset !== undefined ? window . pageYOffset : document ?. documentElement . scrollTop || document ?. body ?. parentElement ?. scrollTop || document ?. body . scrollTop || 0 , } , } , } ) ) ; mutationBuffers . forEach ( ( buf ) => buf . unlock ( ) ) ; } ; try { const handlers = [ ] ; handlers . push ( on ( 'DOMContentLoaded' , ( ) => { wrappedEmit ( wrapEvent ( { type : EventType . DomContentLoaded , data : { } , } ) ) ; } ) ) ; const observe = ( doc ) => { return initObservers ( { mutationCb : wrappedMutationEmit , mousemoveCb : ( positions , source ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source , positions , } , } ) ) , mouseInteractionCb : ( d ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . MouseInteraction , ... d , } , } ) ) , scrollCb : wrappedScrollEmit , viewportResizeCb : ( d ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . ViewportResize , ... d , } , } ) ) , inputCb : ( v ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . Input , ... v , } , } ) ) , mediaInteractionCb : ( p ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . MediaInteraction , ... p , } , } ) ) , styleSheetRuleCb : ( r ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . StyleSheetRule , ... r , } , } ) ) , styleDeclarationCb : ( r ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . StyleDeclaration , ... r , } , } ) ) , canvasMutationCb : ( p ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . CanvasMutation , ... p , } , } ) ) , fontCb : ( p ) => wrappedEmit ( wrapEvent ( { type : EventType . IncrementalSnapshot , data : { source : IncrementalSource . Font , ... p , } , } ) ) , blockClass , ignoreClass , maskTextClass , maskTextSelector , maskInputOptions , inlineStylesheet , sampling , recordCanvas , userTriggeredOnInput , collectFonts , doc , maskInputFn , maskTextFn , blockSelector , slimDOMOptions , mirror , iframeManager , shadowDomManager , plugins : plugins ?. map ( ( p ) => ( { observer : p . observer , options : p . options , callback : ( payload ) => wrappedEmit ( wrapEvent ( { type : EventType . Plugin , data : { plugin : p . name , payload , } , } ) ) , } ) ) || [ ] , } , hooks ) ; } ; iframeManager . addLoadListener ( ( iframeEl ) => { handlers . push ( observe ( iframeEl . contentDocument ! ) ) ; } ) ; const init = ( ) => { takeFullSnapshot ( ) ; handlers . push ( observe ( document ) ) ; } ; if ( document . readyState === 'interactive' || document . readyState === 'complete' ) { init ( ) ; } else { handlers . push ( on ( 'load' , ( ) => { wrappedEmit ( wrapEvent ( { type : EventType . Load , data : { } , } ) ) ; init ( ) ; } , window ) ) ; } return ( ) => { handlers . forEach ( ( h ) => h ( ) ) ; } ; } catch ( error ) { console . warn ( error ) ; } } record . addCustomEvent = < T > ( tag , payload ) => { if ( ! wrappedEmit ) { throw new Error ( 'please add custom event after start recording' ) ; } wrappedEmit ( wrapEvent ( { type : EventType . Custom , data : { tag , payload , } , } ) ) ; } ; record . freezePage = ( ) => { mutationBuffers . forEach ( ( buf ) => buf . freeze ( ) ) ; } ; record . takeFullSnapshot = ( isCheckout ? ) => { if ( ! takeFullSnapshot ) { throw new Error ( 'please take full snapshot after start recording' ) ; } takeFullSnapshot ( isCheckout ) ; } ; record . mirror = mirror ; export default record ;