feat(elements): Added sio-recorder element for recording and replaying sessions
This commit is contained in:
parent
39fac4317d
commit
8603b7876f
@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-01-25 - 1.2.0 - feat(elements)
|
||||
Added sio-recorder element for recording and replaying sessions
|
||||
|
||||
- Introduced a new 'sio-recorder' custom element that allows for recording and replaying DOM events.
|
||||
- Integrated rrweb and rrweb-player dependencies for session recording and playback.
|
||||
- Updated import/export in ts_web/elements/index.ts to include sio-recorder.
|
||||
- Fixed assetbroker URL in the html index.html file.
|
||||
|
||||
## 2024-12-27 - 1.1.0 - feat(ci)
|
||||
Add Gitea workflows for CI/CD process.
|
||||
|
||||
|
@ -8,9 +8,9 @@
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<!--Lets load standard fonts-->
|
||||
<link rel="preconnect" href="https://" crossorigin>
|
||||
<link rel="stylesheet" href="https:///fonts/fonts.css">
|
||||
|
||||
<link rel="preconnect" href="https://assetbroker.lossless.one" crossorigin>
|
||||
<link rel="stylesheet" href="https://assetbroker.lossless.one/fonts/fonts.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0px;
|
||||
|
@ -20,7 +20,10 @@
|
||||
"@design.estate/dees-element": "^2.0.39",
|
||||
"@design.estate/dees-wcctools": "^1.0.90",
|
||||
"@losslessone_private/loint-pubapi": "^1.0.14",
|
||||
"@social.io/interfaces": "^1.0.5"
|
||||
"@social.io/interfaces": "^1.0.5",
|
||||
"rrweb": "2.0.0-alpha.4",
|
||||
"rrweb-player": "1.0.0-alpha.4",
|
||||
"rrweb-snapshot": "2.0.0-alpha.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.84",
|
||||
|
79
pnpm-lock.yaml
generated
79
pnpm-lock.yaml
generated
@ -26,6 +26,15 @@ importers:
|
||||
'@social.io/interfaces':
|
||||
specifier: ^1.0.5
|
||||
version: 1.2.1
|
||||
rrweb:
|
||||
specifier: 2.0.0-alpha.4
|
||||
version: 2.0.0-alpha.4
|
||||
rrweb-player:
|
||||
specifier: 1.0.0-alpha.4
|
||||
version: 1.0.0-alpha.4
|
||||
rrweb-snapshot:
|
||||
specifier: 2.0.0-alpha.4
|
||||
version: 2.0.0-alpha.4
|
||||
devDependencies:
|
||||
'@git.zone/tsbuild':
|
||||
specifier: ^2.1.84
|
||||
@ -690,6 +699,9 @@ packages:
|
||||
resolution: {integrity: sha512-5SpUqD3X/2IZCTezCpk48Ss7cDc9QOuQAkeAYnJrRjDL4UCLakA3lBeHXRD/rsIB7S1smtXlayQ/vizfYzdbfw==}
|
||||
deprecated: This package has been deprecated in favour of the new package at @push.rocks/smarttime
|
||||
|
||||
'@rrweb/types@2.0.0-alpha.18':
|
||||
resolution: {integrity: sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1':
|
||||
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
|
||||
|
||||
@ -734,6 +746,9 @@ packages:
|
||||
'@tsconfig/node16@1.0.4':
|
||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
|
||||
'@tsconfig/svelte@1.0.13':
|
||||
resolution: {integrity: sha512-5lYJP45Xllo4yE/RUBccBT32eBlRDbqN8r1/MIvQbKxW3aFqaYPCNgm8D5V20X4ShHcwvYWNlKg3liDh1MlBoA==}
|
||||
|
||||
'@types/accepts@1.3.7':
|
||||
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
|
||||
|
||||
@ -779,6 +794,9 @@ packages:
|
||||
'@types/cors@2.8.17':
|
||||
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
|
||||
|
||||
'@types/css-font-loading-module@0.0.7':
|
||||
resolution: {integrity: sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==}
|
||||
|
||||
'@types/debounce@1.2.4':
|
||||
resolution: {integrity: sha512-jBqiORIzKDOToaF63Fm//haOCHuwQuLa2202RK4MozpA6lh93eCBc+/8+wZn5OzjJt3ySdc+74SXWXB55Ewtyw==}
|
||||
|
||||
@ -991,6 +1009,9 @@ packages:
|
||||
'@webcontainer/api@1.2.0':
|
||||
resolution: {integrity: sha512-tzoKBd4lLdhHy5GHFpUkl+ndoSba8JqmB7x0ZQFnWfjbcbQOvKQfxA8MEMUYhgqjWHnbrWdAfnBEHz5f5lYG5A==}
|
||||
|
||||
'@xstate/fsm@1.6.5':
|
||||
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
|
||||
|
||||
'@yr/monotone-cubic-spline@1.0.3':
|
||||
resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
|
||||
|
||||
@ -1103,6 +1124,10 @@ packages:
|
||||
resolution: {integrity: sha512-ESyU/U1CFZDJUdr+neHRhNozeCv72Y7Vm0m1DCbjX3KBjT6eYocvAJlSk6+8+HkVwXlT1FNxhGW6q3UKAlCvvw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
base64-arraybuffer@1.0.2:
|
||||
resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==}
|
||||
engines: {node: '>= 0.6.0'}
|
||||
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
@ -1605,6 +1630,9 @@ packages:
|
||||
resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
fflate@0.4.8:
|
||||
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
|
||||
|
||||
figures@6.1.0:
|
||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||
engines: {node: '>=18'}
|
||||
@ -2393,6 +2421,9 @@ packages:
|
||||
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
mkdirp-classic@0.5.3:
|
||||
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||
|
||||
@ -2785,6 +2816,18 @@ packages:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
|
||||
rrdom@0.1.7:
|
||||
resolution: {integrity: sha512-ZLd8f14z9pUy2Hk9y636cNv5Y2BMnNEY99wxzW9tD2BLDfe1xFxtLjB4q/xCBYo6HRe0wofzKzjm4JojmpBfFw==}
|
||||
|
||||
rrweb-player@1.0.0-alpha.4:
|
||||
resolution: {integrity: sha512-Wlmn9GZ5Fdqa37vd3TzsYdLl/JWEvXNUrLCrYpnOwEgmY409HwVIvvA5aIo7k582LoKgdRCsB87N+f0oWAR0Kg==}
|
||||
|
||||
rrweb-snapshot@2.0.0-alpha.4:
|
||||
resolution: {integrity: sha512-KQ2OtPpXO5jLYqg1OnXS/Hf+EzqnZyP5A+XPqBCjYpj3XIje/Od4gdUwjbFo3cVuWq5Cw5Y1d3/xwgIS7/XpQQ==}
|
||||
|
||||
rrweb@2.0.0-alpha.4:
|
||||
resolution: {integrity: sha512-wEHUILbxDPcNwkM3m4qgPgXAiBJyqCbbOHyVoNEVBJzHszWEFYyTbrZqUdeb1EfmTRC2PsumCIkVcomJ/xcOzA==}
|
||||
|
||||
rss-parser@3.13.0:
|
||||
resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==}
|
||||
|
||||
@ -4549,6 +4592,8 @@ snapshots:
|
||||
is-nan: 1.3.2
|
||||
pretty-ms: 8.0.0
|
||||
|
||||
'@rrweb/types@2.0.0-alpha.18': {}
|
||||
|
||||
'@sec-ant/readable-stream@0.4.1': {}
|
||||
|
||||
'@sindresorhus/is@5.6.0': {}
|
||||
@ -4587,6 +4632,8 @@ snapshots:
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@tsconfig/svelte@1.0.13': {}
|
||||
|
||||
'@types/accepts@1.3.7':
|
||||
dependencies:
|
||||
'@types/node': 22.7.5
|
||||
@ -4639,6 +4686,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.7.5
|
||||
|
||||
'@types/css-font-loading-module@0.0.7': {}
|
||||
|
||||
'@types/debounce@1.2.4': {}
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
@ -4932,6 +4981,8 @@ snapshots:
|
||||
|
||||
'@webcontainer/api@1.2.0': {}
|
||||
|
||||
'@xstate/fsm@1.6.5': {}
|
||||
|
||||
'@yr/monotone-cubic-spline@1.0.3': {}
|
||||
|
||||
abbrev@1.1.1:
|
||||
@ -5030,6 +5081,8 @@ snapshots:
|
||||
|
||||
base64-arraybuffer-es6@0.7.0: {}
|
||||
|
||||
base64-arraybuffer@1.0.2: {}
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
base64id@2.0.0: {}
|
||||
@ -5578,6 +5631,8 @@ snapshots:
|
||||
dependencies:
|
||||
xml-js: 1.6.11
|
||||
|
||||
fflate@0.4.8: {}
|
||||
|
||||
figures@6.1.0:
|
||||
dependencies:
|
||||
is-unicode-supported: 2.1.0
|
||||
@ -6635,6 +6690,8 @@ snapshots:
|
||||
yallist: 4.0.0
|
||||
optional: true
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
mkdirp-classic@0.5.3: {}
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
@ -7013,6 +7070,28 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rrdom@0.1.7:
|
||||
dependencies:
|
||||
rrweb-snapshot: 2.0.0-alpha.4
|
||||
|
||||
rrweb-player@1.0.0-alpha.4:
|
||||
dependencies:
|
||||
'@tsconfig/svelte': 1.0.13
|
||||
rrweb: 2.0.0-alpha.4
|
||||
|
||||
rrweb-snapshot@2.0.0-alpha.4: {}
|
||||
|
||||
rrweb@2.0.0-alpha.4:
|
||||
dependencies:
|
||||
'@rrweb/types': 2.0.0-alpha.18
|
||||
'@types/css-font-loading-module': 0.0.7
|
||||
'@xstate/fsm': 1.6.5
|
||||
base64-arraybuffer: 1.0.2
|
||||
fflate: 0.4.8
|
||||
mitt: 3.0.1
|
||||
rrdom: 0.1.7
|
||||
rrweb-snapshot: 2.0.0-alpha.4
|
||||
|
||||
rss-parser@3.13.0:
|
||||
dependencies:
|
||||
entities: 2.2.0
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@social.io/catalog',
|
||||
version: '1.1.0',
|
||||
version: '1.2.0',
|
||||
description: 'catalog for social.io'
|
||||
}
|
||||
|
@ -2,3 +2,4 @@ export * from './sio-fab.js';
|
||||
export * from './sio-combox.js';
|
||||
export * from './sio-subwidget-onboardme.js';
|
||||
export * from './sio-subwidget-conversations.js';
|
||||
export * from './sio-recorder.js';
|
||||
|
188
ts_web/elements/sio-recorder.ts
Normal file
188
ts_web/elements/sio-recorder.ts
Normal file
@ -0,0 +1,188 @@
|
||||
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();
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user