Frontend Development 18 min read

Implementing Page-Level User Interaction Recording and Replay for Insurance Compliance Using rrweb, Puppeteer, and FFmpeg

This article details a compliance‑driven solution that records, replays, and merges user interaction videos across multiple insurance web pages by leveraging rrweb for event capture, Puppeteer for automated playback, and FFmpeg for video stitching, while addressing performance, storage, and privacy challenges.

Yang Money Pot Technology Team
Yang Money Pot Technology Team
Yang Money Pot Technology Team
Implementing Page-Level User Interaction Recording and Replay for Insurance Compliance Using rrweb, Puppeteer, and FFmpeg

On June 30, 2020, the China Banking and Insurance Regulatory Commission issued a notice requiring internet insurance sales to be fully traceable, meaning every page view and user action must be recorded and stored for later verification, preferably as video evidence.

Implementation Challenges

The requirement calls for page‑level recording of the entire user journey, which cannot be satisfied by simple button‑level event tracking. Traditional click‑stream analytics are insufficient because regulators demand a complete visual replay of the page interactions.

Technical Components

rrweb – a library that records DOM mutations, mouse movements, scrolls, inputs, and other interactions as a stream of events that can later be replayed.

Puppeteer – a Node.js API for controlling Chrome, used to automate the replay of rrweb events and capture the resulting video.

FFmpeg – a multimedia toolkit employed to concatenate the individual video fragments generated from each page into a single compliance video.

Technical Implementation

Data Collection

rrweb records user behavior on each visited page and stores the JSON payload on the server at a configurable frequency.

Multiple pages generate multiple data fragments, all of which are linked to the insurance order.

When the order is successfully created, the fragments are associated with that order for later processing.

Video Recording

On the server, Puppeteer launches a headless (or visible) browser, replays the rrweb event stream for each fragment, and records the playback.

After each fragment finishes, the video file is downloaded and stored.

When all fragments are recorded, FFmpeg merges them and transcodes the result into a single compliance video.

Video Upload

The final video is uploaded to Alibaba Cloud OSS.

A callback notifies the insurer to download and archive the video.

Core Code Snippets

Lock‑free High‑Concurrency Storage

To handle near‑real‑time data ingestion, the solution stores large JSON fragments in MongoDB while keeping indexing information in MySQL. The pseudo‑code below shows a double‑check pattern with a distributed lock to avoid duplicate inserts.

fetch mysql data
if not exist
  // avoid duplicate initialization, compete for distributed lock
  lock userId
    fetch mysql data
    // double check
    if not exist
      insert mongo document generate objectId
      insert mysql data
      return
// mysql index exists, update mongo document
fetch mongo document with objectId
document add data
update mongo document with objectId

Improvements include reducing lock contention, separating MongoDB and MySQL writes, and using MongoDB $push to make the update atomic.

Puppeteer‑Driven Recording

The following snippet launches Chrome, navigates to the playback page, waits for a custom finish flag, triggers rrweb‑player to stop recording, and finally downloads the video.

const options = {
    ignoreHTTPSErrors: true,
    headless: false,
    defaultViewport: null,
    args: [
        '--enable-usermedia-screen-capturing',
        '--allow-http-screen-capture',
        '--auto-select-desktop-capture-source=yqg-puppetcam',
        '--load-extension=' + __dirname,
        '--disable-extensions-except=' + __dirname,
        '--disable-infobars',
        `--window-size=${width},${height}`,
    ],
}
const browser = await puppeteer.launch(options);
const [page] = await browser.pages();
await page._client.send('Emulation.clearDeviceMetricsOverride');
await page.goto(playUrl, {waitUntil: ['load', 'domcontentloaded']});
await page.setBypassCSP(true);
await page.waitForSelector('.finish-flag', {timeout: 0});
await page.evaluate(filename => {
  window.postMessage({type: 'SET_EXPORT_PATH', filename}, '*');
  window.postMessage({type: 'REC_STOP'}, '*');
}, exportname);
await page.waitForSelector('html.downloadComplete', {timeout: 0});
await page.close();
await browser.close();

Encountered Issues

rrweb does not capture iframes or PDFs, which are common for insurance agreements. The workaround converts PDFs to image sequences and treats iframe content as plain text.

When navigating between pages, rrweb must be re‑initialized to avoid missing input values; this is currently handled in a Vue‑Router setup.

Missing event data can cause playback stalls, so error‑compensation mechanisms are added to keep the pipeline robust.

Performance Impact

On the most complex insurance page, rrweb’s emit method takes less than 100 ms on initial load and only a few milliseconds for subsequent DOM changes, which is well within acceptable limits and does not noticeably affect user experience.

Extending to Live Streaming

By continuously feeding rrweb data to the player, a near‑real‑time “live” view of the user’s actions can be provided to insurance advisors, enabling them to guide users during the purchase process. Sensitive fields are masked using the blockClass and custom insertStyleRules options.

new rrwebPlayer({
  target: vm.$refs['rrweb-player'],
  props: {
    events: data,
    insertStyleRules: [
      `.hide-info-in-live {${this.hideClass()}}`
    ],
  }
});

hideClass() {
  const input = document.createElement('input');
  const style = window.getComputedStyle(input);
  if (style.webkitTextSecurity !== undefined) {
    return 'text-security: disc!important;-webkit-text-security: disc!important;-moz-text-security: disc!important;';
  } else {
    return 'opacity: 0!important;';
  }
}

Conclusion

After evaluating several alternatives, the team chose rrweb‑based event capture combined with Puppeteer‑driven replay and FFmpeg stitching because it balances performance, video quality, and regulatory compliance. The solution not only satisfies the new traceability mandate but also provides valuable capabilities for debugging, process replication, and real‑time assistance.

The open‑source community around rrweb has already seen broad adoption in the insurance sector, and the authors hope the project continues to grow, driving further innovation at the intersection of insurance and technology.

frontendPuppeteercomplianceFFmpegrrwebUser Interactionweb recording
Yang Money Pot Technology Team
Written by

Yang Money Pot Technology Team

Enhancing service efficiency with technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.