Skip to content

Commit

Permalink
add checkout config to recorder (#36)
Browse files Browse the repository at this point in the history
* add checkout config to recorder

* add test cases for checkout feature and extract assertSnapshot method
  • Loading branch information
Yuyz0112 authored Jan 11, 2019
1 parent b45655e commit cedc87c
Show file tree
Hide file tree
Showing 9 changed files with 738 additions and 314 deletions.
74 changes: 74 additions & 0 deletions guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,80 @@ You may find some contents on the webpage which are not willing to be recorded,
- An element with the class name `.rr-ignore` will not record its input events.
- `input[type="password"]` will be ignored as default.

#### Checkout

By default, all the emitted events are required to replay a session and if you do not want to store all the events, you can use the checkout config.

**Most of the time you do not need to configure this**. But if you want to do something like capturing just the last N events from when an error has occurred, here is an example:

```js
// We use a two-dimensional array to store multiple events array
const eventsMatrix = [[]];

rrweb.record({
emit(event, isCheckout) {
// isCheckout is a flag to tell you the events has been checkout
if (isCheckout) {
eventsMatrix.push([]);
}
const lastEvents = eventsMatrix[eventsMatrix.length - 1];
lastEvents.push(event);
},
checkoutEveryNth: 200, // checkout every 200 events
});

// send last two events array to the backend
window.onerror = function() {
const len = eventsMatrix.length;
const events = eventsMatrix[len - 2].concat(eventsMatrix[len - 1]);
const body = JSON.stringify({ events });
fetch('http://YOUR_BACKEND_API', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body,
});
};
```

Due to the incremental-snapshot-chain mechanism rrweb used, we can not capture the last N events accurately. With the sample code above, you will finally get the last 200 to 400 events been sent to your backend.

Similarly, you can also configure `checkoutEveryNms` to capture the last N minutes events:

```js
// We use a two-dimensional array to store multiple events array
const eventsMatrix = [[]];

rrweb.record({
emit(event, isCheckout) {
// isCheckout is a flag to tell you the events has been checkout
if (isCheckout) {
eventsMatrix.push([]);
}
const lastEvents = eventsMatrix[eventsMatrix.length - 1];
lastEvents.push(event);
},
checkoutEveryNms: 5 * 60 * 1000, // checkout every 5 minutes
});

// send last two events array to the backend
window.onerror = function() {
const len = eventsMatrix.length;
const events = eventsMatrix[len - 2].concat(eventsMatrix[len - 1]);
const body = JSON.stringify({ events });
fetch('http://YOUR_BACKEND_API', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body,
});
};
```

With the sample code above, you will finally get the last 5 to 10 minutes of events been sent to your backend.

### Replay

You need to include the style sheet before replay:
Expand Down
99 changes: 63 additions & 36 deletions src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,68 @@ function wrapEvent(e: event): eventWithTime {
}

function record(options: recordOptions = {}): listenerHandler | undefined {
const { emit } = options;
const { emit, checkoutEveryNms, checkoutEveryNth } = options;
// runtime checks for user options
if (!emit) {
throw new Error('emit function is required');
}

let lastFullSnapshotEvent: eventWithTime;
let incrementalSnapshotCount = 0;
const wrappedEmit = (e: eventWithTime, isCheckout?: boolean) => {
emit(e, isCheckout);
if (e.type === EventType.FullSnapshot) {
lastFullSnapshotEvent = e;
incrementalSnapshotCount = 0;
} else if (e.type === EventType.IncrementalSnapshot) {
incrementalSnapshotCount++;
const exceedCount =
checkoutEveryNth && incrementalSnapshotCount >= checkoutEveryNth;
const exceedTime =
checkoutEveryNms &&
e.timestamp - lastFullSnapshotEvent.timestamp > checkoutEveryNms;
if (exceedCount || exceedTime) {
takeFullSnapshot(true);
}
}
};

function takeFullSnapshot(isCheckout = false) {
wrappedEmit(
wrapEvent({
type: EventType.Meta,
data: {
href: window.location.href,
width: getWindowWidth(),
height: getWindowHeight(),
},
}),
isCheckout,
);
const [node, idNodeMap] = snapshot(document);
if (!node) {
return console.warn('Failed to snapshot the document');
}
mirror.map = idNodeMap;
wrappedEmit(
wrapEvent({
type: EventType.FullSnapshot,
data: {
node,
initialOffset: {
left: document.documentElement!.scrollLeft,
top: document.documentElement!.scrollTop,
},
},
}),
);
}

try {
const handlers: listenerHandler[] = [];
handlers.push(
on('DOMContentLoaded', () => {
emit(
wrappedEmit(
wrapEvent({
type: EventType.DomContentLoaded,
data: {},
Expand All @@ -36,37 +88,12 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
);
const init = () => {
emit(
wrapEvent({
type: EventType.Meta,
data: {
href: window.location.href,
width: getWindowWidth(),
height: getWindowHeight(),
},
}),
);
const [node, idNodeMap] = snapshot(document);
if (!node) {
return console.warn('Failed to snapshot the document');
}
mirror.map = idNodeMap;
emit(
wrapEvent({
type: EventType.FullSnapshot,
data: {
node,
initialOffset: {
left: document.documentElement!.scrollLeft,
top: document.documentElement!.scrollTop,
},
},
}),
);
takeFullSnapshot();

handlers.push(
initObservers({
mutationCb: m =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -76,7 +103,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
),
mousemoveCb: positions =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -86,7 +113,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
),
mouseInteractionCb: d =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -96,7 +123,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
),
scrollCb: p =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -106,7 +133,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
),
viewportResizeCb: d =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -116,7 +143,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
}),
),
inputCb: v =>
emit(
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
Expand All @@ -138,7 +165,7 @@ function record(options: recordOptions = {}): listenerHandler | undefined {
on(
'load',
() => {
emit(
wrappedEmit(
wrapEvent({
type: EventType.Load,
data: {},
Expand Down
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ export type eventWithTime = event & {
};

export type recordOptions = {
emit?: (e: eventWithTime) => void;
emit?: (e: eventWithTime, isCheckout?: boolean) => void;
checkoutEveryNth?: number;
checkoutEveryNms?: number;
};

export type observerParam = {
Expand Down
Loading

0 comments on commit cedc87c

Please sign in to comment.