catalog/ts_web/elements/sio-recorder.ts

188 lines
5.3 KiB
TypeScript

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`<sio-recorder></sio-recorder>`;
/**
* 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`
<div id="playback"></div>
`;
}
/**
* Lifecycle: Called when the element is inserted into the DOM.
*/
async connectedCallback(): Promise<void> {
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<void> {
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<void> {
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();
};
}