import { DeesElement, html, css, customElement, property, query, } from '@design.estate/dees-element'; import * as rrwebMod from 'rrweb'; import rrwebPlayerMod from 'rrweb-player'; const rrweb: any = rrwebMod; const rrwebPlayer: typeof rrwebPlayerMod.default = rrwebPlayerMod as any; /** * Use rrweb's eventWithTime if you like strict typing: * * import { eventWithTime } from 'rrweb'; * export interface IRecordingEvent extends eventWithTime {} * * Here, for brevity, we define an empty interface * and cast all events to any. */ export interface IRecordingEvent {} @customElement('sio-recorder') export class SioRecorder extends DeesElement { public static demo = () => html``; /** * Holds all recorded events from rrweb. */ private events: IRecordingEvent[] = []; /** * A reference to rrweb's stop recording function. * We'll store it when we begin a record session so we can call it later. */ private stopFn: (() => void) | null = null; /** * Query for the div in our template that will be used for playback. */ @query('#playback') private playbackDiv!: HTMLDivElement; static styles = css` :host { display: block; } /* The playback container: set a fixed size for demonstration. */ #playback { width: 800px; height: 600px; border: 1px solid #ccc; background-color: #fff; position: relative; } `; render() { return html`
`; } /** * Lifecycle: Called when the element is inserted into the DOM. */ async connectedCallback(): Promise { super.connectedCallback(); console.log('sio-recorder connectedCallback'); // Start recording immediately this.startRecording(); // Use the domtools-based approach you have (Ctrl+H): const domtools = await this.domtoolsPromise; domtools.keyboard .on([domtools.keyboard.keyEnum.Ctrl, domtools.keyboard.keyEnum.H]) .subscribe(this.handleKeydown); await this.domtoolsPromise; this.domtools.convenience.smartdelay.delayFor(2000).then(() => { // this.handleKeydown(); }); } /** * Lifecycle: Called when the element is removed from the DOM. */ public async disconnectedCallback(): Promise { super.disconnectedCallback(); // Stop recording if it's still running this.stopRecording(); // Clean up your subscription or any global listeners if needed } /** * Starts an rrweb recording session that tracks the entire DOM, * including canvases and cross-origin iframes (if permissible). */ private startRecording(): void { this.events = []; // For capturing "everything," enable advanced flags: this.stopFn = rrweb.record({ emit: (event: any) => { // If you have a stricter type: // this.events.push(event as IRecordingEvent); // else store as any: this.events.push(event); }, // Some recommended settings to capture the "complete" page: recordCanvas: true, // record canvas elements recordCrossOriginIframes: true, // attempt capturing cross-origin iframes // checkoutEveryNms: 1000, // check every N milliseconds }); console.log('Recording has started...'); } /** * Stops the rrweb recording session */ private stopRecording(): void { if (this.stopFn) { this.stopFn(); this.stopFn = null; console.log('Recording has stopped.'); } } /** * Plays back the recorded events in the `playbackDiv` */ private async playRecording(): Promise { await this.domtoolsPromise; if (!this.playbackDiv) return; const replayer =new rrwebPlayer({ target: this.playbackDiv, // customizable root element props: { events: this.events as any, root: this.playbackDiv, showController: false, width: this.playbackDiv.offsetWidth, height: this.playbackDiv.offsetHeight, }, }); this.domtools.convenience.smartdelay.delayFor(0).then(async () => { while (true) { await this.domtools.convenience.smartdelay.delayFor(30000); await replayer.play(); await this.domtools.convenience.smartdelay.delayFor(0); // this.fixPosition(); } }); this.fixPosition(); } public async fixPosition() { await this.domtoolsPromise; await this.domtools.convenience.smartdelay.delayFor(0); const iframe = this.shadowRoot.querySelector('iframe'); const replayerWrapper = this.shadowRoot.querySelector('.replayer-wrapper') as HTMLElement; const replayerMouse = this.shadowRoot.querySelector('.replayer-mouse') as HTMLElement; const replayerMouseTail = this.shadowRoot.querySelector('.replayer-mouse-tail') as HTMLElement; replayerWrapper.style.position = 'absolute'; replayerWrapper.style.top = '0px'; replayerWrapper.style.left = '0px'; replayerWrapper.style.transformOrigin = 'center center'; replayerMouse.style.position = 'absolute'; replayerMouseTail.style.position = 'absolute'; iframe.style.position = 'absolute'; iframe.style.top = '0px'; iframe.style.left = '0px'; } /** * Keydown handler. If Ctrl + H is pressed, stop the recording and replay the session */ private handleKeydown = (): void => { this.stopRecording(); this.playRecording(); }; }