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/core
folder, - A recorder in
frontend/ui/recorder
, which is a simple UI showcasing how to record. - A naive API server in
server
which 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 dev
which opens up two UIs, the recorder and the player - Spin up a local server
yarn server
- Go to the
recorder
page and click thestart
button and then play with the UI, clickstop
once you are done - Go the the
player
page and input the id(default value 123), andload
it. - 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
# 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
onRootEmitted
is 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
bufferSize
events or the recorder is stopped, the stashed data will be emitted viaonRecordItemsEmitted
, note that at maxmaxEmitSize
will be emitted. For example, ifbufferSize
is 200 andmaxEmitSie
is 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 fiber
and its new interruptiblestartTransition
API. - I need to add more details as to how to build your own server