Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
apply text mask settings to inputs #1096
  • Loading branch information
mdellanoce authored and colingm committed May 1, 2024
commit b1a1922e59a123fdeb7bf381df0fd2cd6c3719dd
6 changes: 6 additions & 0 deletions .changeset/brave-spoons-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'rrweb-snapshot': patch
'rrweb': patch
---

text masking settings apply to inputs
20 changes: 18 additions & 2 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export function needMaskingText(
? (node as HTMLElement)
: node.parentElement;
if (el === null) return false;
if (maskTextSelector === '*') return true;
if (typeof maskTextClass === 'string') {
if (checkAncestors) {
if (el.closest(`.${maskTextClass}`)) return true;
Expand Down Expand Up @@ -503,11 +504,14 @@ function serializeNode(
keepIframeSrcFn,
newlyAddedElement,
rootId,
needsMask,
});
case n.TEXT_NODE:
return serializeTextNode(n as Text, {
needsMask,
maskTextFn,
maskInputOptions,
maskInputFn,
rootId,
});
case n.CDATA_SECTION_NODE:
Expand Down Expand Up @@ -538,16 +542,20 @@ function serializeTextNode(
options: {
needsMask: boolean | undefined;
maskTextFn: MaskTextFn | undefined;
maskInputOptions: MaskInputOptions;
maskInputFn: MaskInputFn | undefined;
rootId: number | undefined;
},
): serializedNode {
const { needsMask, maskTextFn, rootId } = options;
const { needsMask, maskTextFn, maskInputOptions, maskInputFn, rootId } =
options;
// The parent node may not be a html element which has a tagName attribute.
// So just let it be undefined which is ok in this use case.
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
let textContent = n.textContent;
const isStyle = parentTagName === 'STYLE' ? true : undefined;
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
const isTextarea = parentTagName === 'TEXTAREA' ? true : undefined;
if (isStyle && textContent) {
try {
// try to read style sheet
Expand Down Expand Up @@ -577,6 +585,11 @@ function serializeTextNode(
? maskTextFn(textContent, n.parentElement)
: textContent.replace(/[\S]/g, '*');
}
if (isTextarea && textContent && maskInputOptions.textarea) {
textContent = maskInputFn
? maskInputFn(textContent, n.parentNode as HTMLElement)
: textContent.replace(/[\S]/g, '*');
}

return {
type: NodeType.Text,
Expand Down Expand Up @@ -604,6 +617,7 @@ function serializeElementNode(
*/
newlyAddedElement?: boolean;
rootId: number | undefined;
needsMask?: boolean;
},
): serializedNode | false {
const {
Expand All @@ -619,6 +633,7 @@ function serializeElementNode(
keepIframeSrcFn,
newlyAddedElement = false,
rootId,
needsMask,
} = options;
const needBlock = _isBlockedElement(n, blockClass, blockSelector);
const tagName = getValidTagName(n);
Expand Down Expand Up @@ -682,6 +697,7 @@ function serializeElementNode(
value,
maskInputOptions,
maskInputFn,
needsMask,
});
} else if (checked) {
attributes.checked = checked;
Expand Down Expand Up @@ -1246,7 +1262,7 @@ function snapshot(
inlineStylesheet?: boolean;
maskAllInputs?: boolean | MaskInputOptions;
maskTextFn?: MaskTextFn;
maskInputFn?: MaskTextFn;
maskInputFn?: MaskInputFn;
slimDOM?: 'all' | boolean | SlimDOMOptions;
dataURLOptions?: DataURLOptions;
inlineImages?: boolean;
Expand Down
5 changes: 4 additions & 1 deletion packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,20 +219,23 @@ export function maskInputValue({
type,
value,
maskInputFn,
needsMask,
}: {
element: HTMLElement;
maskInputOptions: MaskInputOptions;
tagName: string;
type: string | null;
value: string | null;
maskInputFn?: MaskInputFn;
needsMask?: boolean;
}): string {
let text = value || '';
const actualType = type && toLowerCase(type);

if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
(actualType && maskInputOptions[actualType as keyof MaskInputOptions])
(actualType && maskInputOptions[actualType as keyof MaskInputOptions]) ||
needsMask
) {
if (maskInputFn) {
text = maskInputFn(text, element);
Expand Down
1 change: 1 addition & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ function record<T = eventWithTime>(
maskTextSelector,
inlineStylesheet,
maskAllInputs: maskInputOptions,
maskInputFn,
maskTextFn,
slimDOM: slimDOMOptions,
dataURLOptions,
Expand Down
8 changes: 8 additions & 0 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,13 +566,21 @@ export default class MutationBuffer {
if (attributeName === 'value') {
const type = getInputType(target);

const needsMask = needMaskingText(
m.target,
this.maskTextClass,
this.maskTextSelector,
true,
);

value = maskInputValue({
element: target,
maskInputOptions: this.maskInputOptions,
tagName: target.tagName,
type,
value,
maskInputFn: this.maskInputFn,
needsMask,
});
}
if (
Expand Down
14 changes: 13 additions & 1 deletion packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Mirror,
getInputType,
toLowerCase,
needMaskingText,
} from 'rrweb-snapshot';
import type { FontFaceSet } from 'css-font-loading-module';
import {
Expand Down Expand Up @@ -420,6 +421,8 @@ function initInputObserver({
maskInputFn,
sampling,
userTriggeredOnInput,
maskTextClass,
maskTextSelector,
}: observerParam): listenerHandler {
function eventHandler(event: Event) {
let target = getEventTarget(event) as HTMLElement | null;
Expand Down Expand Up @@ -452,11 +455,19 @@ function initInputObserver({
let isChecked = false;
const type: Lowercase<string> = getInputType(target) || '';

const needsMask = needMaskingText(
target as Node,
maskTextClass,
maskTextSelector,
true,
);

if (type === 'radio' || type === 'checkbox') {
isChecked = (target as HTMLInputElement).checked;
} else if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
maskInputOptions[type as keyof MaskInputOptions]
maskInputOptions[type as keyof MaskInputOptions] ||
needsMask
) {
text = maskInputValue({
element: target,
Expand All @@ -465,6 +476,7 @@ function initInputObserver({
type,
value: text,
maskInputFn,
needsMask,
});
}
cbWithDedup(
Expand Down
Loading