Skip to content

Commit

Permalink
Extended masking function to allow passing custom fn
Browse files Browse the repository at this point in the history
  • Loading branch information
benjackwhite committed Sep 26, 2023
1 parent 58c9104 commit fbe73b2
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ function serializeTextNode(
needMaskingText(n, maskTextClass, maskTextSelector)
) {
textContent = maskTextFn
? maskTextFn(textContent)
? maskTextFn(textContent, n.parentElement)
: textContent.replace(/[\S]/g, '*');
}

Expand Down
2 changes: 1 addition & 1 deletion packages/rrweb-snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export type DataURLOptions = Partial<{
quality: number;
}>;

export type MaskTextFn = (text: string) => string;
export type MaskTextFn = (text: string, element: HTMLElement | null) => string;
export type MaskInputFn = (text: string, element: HTMLElement) => string;

export type KeepIframeSrcFn = (src: string) => boolean;
Expand Down
5 changes: 4 additions & 1 deletion packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
isSerializedStylesheet,
inDom,
getShadowHost,
getElementFromNode,
} from '../utils';

type DoubleLinkedListNode = {
Expand Down Expand Up @@ -508,6 +509,8 @@ export default class MutationBuffer {
switch (m.type) {
case 'characterData': {
const value = m.target.textContent;
const el = getElementFromNode(m.target)

if (
!isBlocked(m.target, this.blockClass, this.blockSelector, false) &&
value !== m.oldValue
Expand All @@ -520,7 +523,7 @@ export default class MutationBuffer {
this.maskTextSelector,
) && value
? this.maskTextFn
? this.maskTextFn(value)
? this.maskTextFn(value, el)
: value.replace(/[\S]/g, '*')
: value,
node: m.target,
Expand Down
27 changes: 22 additions & 5 deletions packages/rrweb/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,23 @@ export function getWindowWidth(): number {
);
}

/**
* Returns the given node as an HTMLElement if it is one, otherwise the parent node as an HTMLElement
* @param node - node to check
* @returns HTMLElement or null
*/

export function getElementFromNode(node: Node | null,): HTMLElement | null {
if (!node) {
return null;
}
const el: HTMLElement | null =
node.nodeType === node.ELEMENT_NODE
? (node as HTMLElement)
: node.parentElement;
return el
}

/**
* Checks if the given element set to be blocked by rrweb
* @param node - node to check
Expand All @@ -232,11 +249,11 @@ export function isBlocked(
if (!node) {
return false;
}
const el: HTMLElement | null =
node.nodeType === node.ELEMENT_NODE
? (node as HTMLElement)
: node.parentElement;
if (!el) return false;
const el = getElementFromNode(node)

if (!el) {
return false
}

try {
if (typeof blockClass === 'string') {
Expand Down
4 changes: 4 additions & 0 deletions packages/rrweb/test/html/mask-text.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@
<div>mask3</div>
</div>
</div>

<p data-unmask-example="true">
unmask1
</p>
</body>
</html>
25 changes: 25 additions & 0 deletions packages/rrweb/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,31 @@ describe('record integration tests', function (this: ISuite) {
assertSnapshot(snapshots);
});

it('should unmask texts using maskTextFn', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');
await page.setContent(
getHtml.call(this, 'mask-text.html', {
maskTextSelector: '*',
maskTextFn: (t: string, el: HTMLElement) => {
return el.matches('[data-unmask-example="true"]') ? t : t.replace(/[a-z]/g, '*');
},
}),
);

await page.type('#password', 'secr3t');

// Change type to text (simulate "show password")
await page.click('#show-password');
await page.type('#password', 'XY');
await page.click('#show-password');

const snapshots = (await page.evaluate(
'window.snapshots',
)) as eventWithTime[];
assertSnapshot(snapshots);
});

it('can mask character data mutations', async () => {
const page: puppeteer.Page = await browser.newPage();
await page.goto('about:blank');
Expand Down

0 comments on commit fbe73b2

Please sign in to comment.