Skip to content

Commit 8bce148

Browse files
authored
rrweb changes based on ue-sdk (#8)
* restored files * Apply formatting changes --------- Co-authored-by: kanishq1 <[email protected]>
1 parent db725cf commit 8bce148

File tree

7 files changed

+135
-3
lines changed

7 files changed

+135
-3
lines changed

CHANGELOG_UE.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Changelog
2+
3+
This objective of this document is to track the changes being made in the rrweb package files
4+
5+
## v0.3.0
6+
7+
### Features
8+
9+
- Added image masking capability on img tags using class selector 'ue-mask'
10+
- Added masking capability on input tags using class selector 'ue-input-mask'
11+
12+
### Changes
13+
14+
/packages/rrweb-snapshot/src/snapshot.ts
15+
16+
- added img masking logic in function serializeElementNode(n, options) on line 793
17+
18+
/packages/rrweb/src/record/mutation.ts
19+
20+
- added mutation logic for masking img in function processMutation on line 670
21+
22+
/packages/rrweb/src/record/observer.ts
23+
24+
- added masking logic for input tags in function eventHandler on line 470
25+
26+
/packages/rrweb/src/record/index.ts
27+
28+
- added property 'maskInputClass' on line number 76 and 530 inside record and observe functions.
29+
30+
/packages/rrweb/src/types.ts
31+
32+
- updated types recordOptions and observeParam, added maskTextClass property in both.
33+
34+
## v0.3.4
35+
36+
### Fix
37+
38+
- Memory leak issues on IframeManager and observers
39+
40+
### Changes
41+
42+
/packages/rrweb/src/record/observer.ts
43+
44+
- flushMutationBuffers() added to clear mutationBuffers
45+
46+
/packages/rrweb/src/record/iframe-manager.ts
47+
48+
- function to cleanup handleMessage() in eventListener
49+
50+
/packages/rrweb/src/record/index.ts
51+
52+
- calling flushMutationBuffers & IframeManger cleanup() in record()

packages/rrweb-snapshot/src/snapshot.ts

+38
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const tagNameRegex = new RegExp('[^a-z0-9-_:]');
3232

3333
export const IGNORED_NODE = -2;
3434

35+
const BLANK_IMAGE_SRC =
36+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAABNJREFUCB1jZGBg+A/EDEwgAgQADigBA//q6GsAAAAASUVORK5CYII%3D';
37+
3538
export function genId(): number {
3639
return _id++;
3740
}
@@ -440,6 +443,7 @@ function serializeNode(
440443
mirror: Mirror;
441444
blockClass: string | RegExp;
442445
blockSelector: string | null;
446+
maskTextClass: string | RegExp;
443447
needsMask: boolean | undefined;
444448
inlineStylesheet: boolean;
445449
maskInputOptions: MaskInputOptions;
@@ -460,6 +464,7 @@ function serializeNode(
460464
mirror,
461465
blockClass,
462466
blockSelector,
467+
maskTextClass,
463468
needsMask,
464469
inlineStylesheet,
465470
maskInputOptions = {},
@@ -500,6 +505,7 @@ function serializeNode(
500505
doc,
501506
blockClass,
502507
blockSelector,
508+
maskTextClass,
503509
inlineStylesheet,
504510
maskInputOptions,
505511
maskInputFn,
@@ -600,6 +606,7 @@ function serializeElementNode(
600606
doc: Document;
601607
blockClass: string | RegExp;
602608
blockSelector: string | null;
609+
maskTextClass: string | RegExp;
603610
inlineStylesheet: boolean;
604611
maskInputOptions: MaskInputOptions;
605612
maskInputFn: MaskInputFn | undefined;
@@ -779,6 +786,36 @@ function serializeElementNode(
779786
if (image.complete && image.naturalWidth !== 0) recordInlineImage();
780787
else image.addEventListener('load', recordInlineImage);
781788
}
789+
// dont store src of img with maskTextClass class
790+
// replace src with blank png
791+
let maskTextClass = options.maskTextClass;
792+
if (
793+
tagName === 'img' &&
794+
attributes.class &&
795+
typeof attributes.class === 'string' &&
796+
maskTextClass &&
797+
typeof maskTextClass === 'string' &&
798+
attributes.class.includes(maskTextClass)
799+
) {
800+
const image = n as HTMLImageElement;
801+
const maskImg = () => {
802+
attributes.src = BLANK_IMAGE_SRC;
803+
// set width & height in style attribute of img
804+
if (attributes.style && typeof attributes.style === 'string') {
805+
if (!attributes.style.includes('width')) {
806+
attributes.style = `${attributes.style}; width: ${image.width}px;`;
807+
}
808+
if (!attributes.style.includes('height')) {
809+
attributes.style = `${attributes.style}; height: ${image.height}px`;
810+
}
811+
} else {
812+
attributes.style = `width: ${image.width}px; height: ${image.height}px`;
813+
}
814+
};
815+
// The image content may not have finished loading yet.
816+
if (image.complete) maskImg();
817+
else image.onload = maskImg;
818+
}
782819
// media elements
783820
if (tagName === 'audio' || tagName === 'video') {
784821
const mediaAttributes = attributes as mediaAttributes;
@@ -1026,6 +1063,7 @@ export function serializeNodeWithId(
10261063
blockClass,
10271064
blockSelector,
10281065
needsMask,
1066+
maskTextClass,
10291067
inlineStylesheet,
10301068
maskInputOptions,
10311069
maskTextFn,

packages/rrweb/src/record/iframe-manager.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export class IframeManager {
2424
private loadListener?: (iframeEl: HTMLIFrameElement) => unknown;
2525
private stylesheetManager: StylesheetManager;
2626
private recordCrossOriginIframes: boolean;
27+
private boundHandleMessage;
2728

2829
constructor(options: {
2930
mirror: Mirror;
@@ -42,8 +43,9 @@ export class IframeManager {
4243
),
4344
);
4445
this.mirror = options.mirror;
46+
this.boundHandleMessage = this.handleMessage.bind(this);
4547
if (this.recordCrossOriginIframes) {
46-
window.addEventListener('message', this.handleMessage.bind(this));
48+
window.addEventListener('message', this.boundHandleMessage);
4749
}
4850
}
4951

@@ -299,4 +301,8 @@ export class IframeManager {
299301
});
300302
}
301303
}
304+
305+
public cleanup() {
306+
window.removeEventListener('message', this.boundHandleMessage);
307+
}
302308
}

packages/rrweb/src/record/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import {
44
SlimDOMOptions,
55
createMirror,
66
} from 'rrweb-snapshot';
7-
import { initObservers, mutationBuffers } from './observer';
7+
import {
8+
flushMutationBuffers,
9+
initObservers,
10+
mutationBuffers,
11+
} from './observer';
812
import {
913
on,
1014
getWindowWidth,
@@ -73,6 +77,7 @@ function record<T = eventWithTime>(
7377
ignoreClass = 'rr-ignore',
7478
ignoreSelector = null,
7579
maskTextClass = 'rr-mask',
80+
maskInputClass = 'rr-input-mask',
7681
maskTextSelector = null,
7782
inlineStylesheet = true,
7883
maskAllInputs,
@@ -526,6 +531,7 @@ function record<T = eventWithTime>(
526531
ignoreClass,
527532
ignoreSelector,
528533
maskTextClass,
534+
maskInputClass,
529535
maskTextSelector,
530536
maskInputOptions,
531537
inlineStylesheet,
@@ -617,6 +623,8 @@ function record<T = eventWithTime>(
617623
processedNodeManager.destroy();
618624
recording = false;
619625
unregisterErrorHandler();
626+
flushMutationBuffers();
627+
iframeManager.cleanup();
620628
};
621629
} catch (error) {
622630
// TODO: handle internal error

packages/rrweb/src/record/mutation.ts

+18
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ class DoubleLinkedList {
133133

134134
const moveKey = (id: number, parentId: number) => `${id}@${parentId}`;
135135

136+
const BLANK_IMAGE_SRC =
137+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAABNJREFUCB1jZGBg+A/EDEwgAgQADigBA//q6GsAAAAASUVORK5CYII%3D';
138+
136139
/**
137140
* controls behaviour of a MutationObserver
138141
*/
@@ -665,6 +668,21 @@ export default class MutationBuffer {
665668
}
666669
}
667670
}
671+
// dont track img src changes if it has maskTextClass class
672+
if (
673+
m.attributeName === 'src' &&
674+
(m.target as HTMLElement).nodeName === 'IMG' &&
675+
this.maskTextClass &&
676+
typeof this.maskTextClass === 'string' &&
677+
target.classList.toString().includes(this.maskTextClass)
678+
) {
679+
item.attributes[m.attributeName!] = transformAttribute(
680+
this.doc,
681+
toLowerCase((m.target as HTMLElement).tagName),
682+
m.attributeName!,
683+
BLANK_IMAGE_SRC,
684+
);
685+
}
668686
break;
669687
}
670688
case 'childList': {

packages/rrweb/src/record/observer.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ type WindowWithAngularZone = IWindow & {
6060
};
6161
};
6262

63-
export const mutationBuffers: MutationBuffer[] = [];
63+
export let mutationBuffers: MutationBuffer[] = [];
64+
65+
export const flushMutationBuffers = () => {
66+
mutationBuffers = [];
67+
};
6468

6569
// Event.path is non-standard and used in some older browsers
6670
type NonStandardEvent = Omit<Event, 'composedPath'> & {
@@ -418,6 +422,7 @@ function initInputObserver({
418422
ignoreSelector,
419423
maskInputOptions,
420424
maskInputFn,
425+
maskInputClass,
421426
sampling,
422427
userTriggeredOnInput,
423428
}: observerParam): listenerHandler {
@@ -466,7 +471,10 @@ function initInputObserver({
466471
value: text,
467472
maskInputFn,
468473
});
474+
} else if ((target as HTMLElement).classList.contains(maskInputClass)) {
475+
text = '*'.repeat(text.length);
469476
}
477+
470478
cbWithDedup(
471479
target,
472480
userTriggeredOnInput

packages/rrweb/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type recordOptions<T> = {
5050
ignoreClass?: string;
5151
ignoreSelector?: string;
5252
maskTextClass?: maskTextClass;
53+
maskInputClass?: string;
5354
maskTextSelector?: string;
5455
maskAllInputs?: boolean;
5556
maskInputOptions?: MaskInputOptions;
@@ -90,6 +91,7 @@ export type observerParam = {
9091
ignoreClass: string;
9192
ignoreSelector: string | null;
9293
maskTextClass: maskTextClass;
94+
maskInputClass: string;
9395
maskTextSelector: string | null;
9496
maskInputOptions: MaskInputOptions;
9597
maskInputFn?: MaskInputFn;

0 commit comments

Comments
 (0)