This tool "records" users' behavior and store data as json files. Later, the "player" can simply load json data and can replay what happened.
It is similar to rrweb and hotjar
It might not be that omnipotent but still allows you to track user's real behavior in a very granular way, compared to normal Tracking libraries which only send aggregated user behaviors as tracking events.
This Repo is composed of 4 parts:
- The Typescript lib itself, which is in the
frontend/corefolder, - A recorder in
frontend/ui/recorder, which is a simple UI showcasing how to record. - A naive API server in
serverwhich receives the data sent from the recorder, if you build your server logic, be sure to reference this file. - A player in
frontend/ui/player, which is a simple UI showcasing how to play users' real bahvior.
Note that the recorder,naive api and player components in this repo are just for demo/reference during development, you need to check the How it works below to see how you should send data back to your own server.
Be sure to check out the demo, the page is recorded first and then played just like a "video".
1c7dcaf15cbd4596e760252724008a21.mp4
- Install dependencies
yarn - Open two terminals,
yarn devwhich opens up two UIs, the recorder and the player - Spin up a local server
yarn server - Go to the
recorderpage and click thestartbutton and then play with the UI, clickstoponce you are done alt text - Go the the
playerpage and input the id(default value 123), andloadit. alt text - Finally click
play, and voila you see the magic - Note that you can drag the progress bar and can set play speed via the settings button alt text
# example for how to record
const sessionId = <your_unique_session_id:number>;
recorder.init({
// once 10 records are accumulated, data get flushed via onRecordItemsEmitted
bufferSize: 10,
// how many runs the onRecordItemsEmitted gets called
// for example = 10 = 3+3+3+1, 4 runs
maxEmitSize: 3,
// called when snapshot is taken on page load
onRootEmitted: async (root, metaInfo) => {
// write to your own server
await fetch(domain + '/ubm', {
method: 'post',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
// the current url
location: location.href,
sessionId,
root,
metaInfo,
}),
});
},
// called in a streaming way
// note that not only the events during the normal user
// interactions but also the final stop event are all
// sent via this callback
onRecordItemsEmitted: async (items: RecordItem[]) => {
await fetch(domain + '/ubm/records', {
method: 'post',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
sessionId,
records: items,
}),
});
},
// you are suggested to send a stop signal to your remote server
onStopped: async () => {
await fetch(domain + '/ubm/stop', {
method: 'post',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
sessionId,
})
});
},
debugMode: false,
});
recorder.record();
// Once you determine you are done with the page, call this
// this will make sure all buffered data get flushed
// and all states are stored back to its original state,
// which allows a new record session to be triggered.
recorder.stop();
# How to play
UI wise just use the player we provide for now, and provide the url of your server.
But still you need to implement your own server by referencing `server/ubm-sample-server.ts` to implement the data capture and data fetch(see `router.get('/:sessionId')`)

- Take a global snapshot of the page by recursively walk all the Dom Nodes and all nodes are transformed into a nested JSON tree.
- The
onRootEmittedis triggered with the nested json data, you may listen to this event and send this data to your remote server to store. - Register a MutationObserver for Dom changes, and register scroll/mousemove/click handlers for users' interaction
- When any event is triggered by the user, the event is stashed locally
- When there are
bufferSizeevents or the recorder is stopped, the stashed data will be emitted viaonRecordItemsEmitted, note that at maxmaxEmitSizewill be emitted. For example, ifbufferSizeis 200 andmaxEmitSieis 60, then the onRecordItemsEmitted will be triggered 4 times, 60,60,60 and 20 for each run. You may listen to this event and send the emitted data to your remote server.
- Password input should be pixelated or replaced with xxxxx, this is easily achivable by storing the value as xxxxx
- This tool has been tested on some very complex pages. For example, I used to be a main contributor to the Tiktok Creative Center site, and this tool can record all the complex UIs for this site.
- The logic still occupies a lot of CPU usage for handling normal users' eventloop, moving on I consider either offloading some computations to a WebWorker or break things down and queue it using requestIdleCallback, which is inspired by
React fiberand its new interruptiblestartTransitionAPI. - I need to add more details as to how to build your own server