Session Replay

Lessons Learned from Building

a DOM capturing product

Wey Wey Web 2023

Francesco Novy

Francesco Novy

www.fnovy.com

hello@fnovy.com

mydea

  • Living in Vienna, Austria
  • 8+ years of building web UIs
  • At Sentry since 2022
  • Working on the JavaScript SDKs
  • Sentry & Session Replay
  • How does Session Replay work?
  • Lessons Learned:
    • Know your Use Case
    • Expect the Unexpected
    • How to Optimize Bundle Size
    • How to Compress Data

Overview

  • Error Monitoring
  • Performance Monitoring
  • Profiling
  • Session Replay

What does Sentry do?

  • Capture what's happening in the user's browser
     
  • See what happened leading up to an error
     
  • In-depth debugging similar to the Browser DevTools

Session Replay

Session Replay

Session Replay

import * as Sentry from '@sentry/browser';

Sentry.init({
  integrations: [
    new Sentry.Replay({
      unmask: ['.show-this-class']
    })
  ],
  // Always capture when an error happens
  replaysOnErrorSampleRate: 1.0,
  // Capture 10% of sessions generally
  replaysSessionSampleRate: 0.1,
});

How to use Session Replay?

  • We use a fork of rrweb
  • Mutation Observers
  • Monkey Patching* Stylesheet APIs
  • Monkey Patching* Canvas APIs

How does it work?

* What is Monkey Patching?

const originalFetch = window.fetch;
window.fetch = function() {
  console.log("A fetch happened!");
  return originalFetch.apply(window, arguments);
}

// Later somewhere in the application
window.fetch('https://example.com');
// Will show the console log

"Monkey patching is a technique used to dynamically update the behavior of a piece of code at run-time."

  • Know your Use Case
  • Expect the Unexpected
  • How to Optimize Bundle Size
  • How to Compress Data

Lessons Learned

Know your Use Case

Do we really need that?

Know your Use Case

  • What do you really need?
  • Hide text & user input by default
  • Opt-in to show certain text
  • Defaults matter: Make the best way the easy way
  • Make the worst things impossible
     

Expect the Unexpected

Everything that can go wrong, will go wrong.

Expect the Unexpected

  • Low Level APIs are dangerous to tinker with
  • Global APIs may not be available
  • Browser Extensions can do anything
  • try-catch everything

How to Optimize Bundle Size

Ship as little code as necessary.

Session Replay Bundle Size

  • Bundle Size v7.73.0: ~53 KB
  • Bundle Size v7.78.0: ~35 KB
  • Still large 😱
  • ... but how?

Optimizing Bundle Size

  • Remove unused code from rrweb
  • Audit dependencies
  • Make certain recording features opt-in
    • Optimize for Tree Shaking!

What is Tree Shaking?

  • Tree Shaking describes the ability to automatically remove unused code from your build.
     
  • When code is written in a tree-shakeable way, bundlers like Webpack can optimize your application based on what is actually used.

Tree Shaking: Simple Example

// SDK
import { large, small } from './my-code';

export function largeOrSmall(config) {
  return config.useLarge ? large() : small();
} 

// Application
import { largeOrSmall } from 'sdk';

largeOrSmall({ useLarge: false });

❌ Not tree shakeable

// SDK
export { large, small } from './my-code';

// Application
import { small } from 'sdk';

small();

✅ Tree shakeable

Tree Shaking: Simple Example

// SDK
import { 
  CanvasManager
} from './canvas-manager';

export function record(options) {
  if (options.recordCanvas) {
     new CanvasManager();
  }
} 

// Application
import { record } from 'sdk';

record({ recordCanvas: false });

❌ Not tree shakeable

Tree Shaking: Actual Example

// Application A
import { 
  record
} from 'sdk';

record({ getCanvasManager: undefined });

✅ Tree shakeable

Tree Shaking: Actual Example

// SDK
import { 
  CanvasManager
} from './canvas-manager';

export function getCanvasManager() {
  return new CanvasManager();
}

export function record(options) {
  if (options.getCanvasManager) {
    options.getCanvasManager();
  }
} 
// Application B
import { 
  record, 
  getCanvasManager
} from 'sdk';

record({ getCanvasManager });

How to Compress Data

Avoid unnecessary network traffic, where possible.

Compressing Data

  • Compress data in a web worker
  • Gracefully handle errors
  • Make sure to compare libraries (e.g. fflate)

Web Workers

Setting up the web worker

// worker.js
import { compressSync } from 'fflate';

function handleMessage(e) {
  const { input, id } = e.data;
  const compressed = compressSync(input);
  // Send compressed data back to main thread
  postMessage({ id, output: compressed });
}

// Receive uncompressed data from main thread
addEventListener('message', handleMessage);

Web Workers

Using the web worker from your application

// Application
const worker = new Worker('/worker.js');

function compressData(data) {
  const id = generateUuid();
  
  return new Promise(function (resolve) {
    function listener(response) {
      if (response.data.id === id) {
        worker.removeEventListener('message', listener);
        resolve(response.data.output);
      }
    }

    worker.addEventListener('message', listener);
    worker.postMessage({ id, input: data });
  });
}

The Sentry SDK is Open Source!

  • Everything we do is open source!
  • Look at the code, PRs, etc.
  • We love feedback!

Thank you!

www.fnovy.com

hello@fnovy.com

mydea

Francesco Novy

Session Replay Wey Wey Web 2023

By Francesco Novy

Session Replay Wey Wey Web 2023

  • 159