Skip to content

Commit

Permalink
!!! ref(sentry): custom masking of input logic
Browse files Browse the repository at this point in the history
This is so input masking (maskAllInputs) is independent of `maskAllText`
  • Loading branch information
billyvg committed Jul 29, 2023
1 parent 8767b24 commit e274f88
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 75 deletions.
58 changes: 32 additions & 26 deletions packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
toLowerCase,
toUpperCase,
validateStringifiedCssRule,
shouldMaskInput,
} from './utils';

let _id = 1;
Expand Down Expand Up @@ -701,19 +702,16 @@ function serializeTextNode(
if (isScript) {
textContent = 'SCRIPT_PLACEHOLDER';
}
if (
!isStyle &&
!isScript &&
textContent &&
needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
)
) {
const forceMask = needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
);

if (!isStyle && !isScript && textContent && forceMask) {
textContent = maskTextFn
? maskTextFn(textContent)
: textContent.replace(/[\S]/g, '*');
Expand All @@ -726,12 +724,23 @@ function serializeTextNode(

// Handle <option> text like an input value
if (parentTagName === 'OPTION' && textContent) {
textContent = maskInputValue({
element: n as unknown as HTMLElement,
const isInputMasked = shouldMaskInput({
type: null,
tagName: parentTagName,
value: textContent,
maskInputOptions,
});

textContent = maskInputValue({
isMasked: needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
isInputMasked,
),
element: n as unknown as HTMLElement,
value: textContent,
maskInputFn,
});
}
Expand Down Expand Up @@ -852,29 +861,26 @@ function serializeElementNode(
const type = getInputType(el);
const value = getInputValue(el, toUpperCase(tagName), type);
const checked = (n as HTMLInputElement).checked;
if (
attributes.type !== 'submit' &&
attributes.type !== 'button' &&
value
) {
if (attributes.type !== 'submit' && attributes.type !== 'button' && value) {
const type = getInputType(n);
const forceMask = needMaskingText(
n,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
shouldMaskInput({
type,
tagName: toUpperCase(tagName),
maskInputOptions,
})
);

attributes.value = maskInputValue({
isMasked: forceMask,
element: el,
type,
tagName: toUpperCase(tagName),
value,
maskInputOptions,
maskInputFn,
forceMask,
});
}
if (checked) {
Expand Down
49 changes: 27 additions & 22 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,43 +164,48 @@ export function createMirror(): Mirror {
return new Mirror();
}

export function maskInputValue({
element,
export function shouldMaskInput({
maskInputOptions,
tagName,
type,
value,
maskInputFn,
forceMask,
}: {
element: HTMLElement;
maskInputOptions: MaskInputOptions;
tagName: Uppercase<string>;
type: Lowercase<string> | null;
}): boolean {
// Handle `option` like `select
if (tagName === 'OPTION') {
tagName = 'SELECT';
}
const actualType = type && toLowerCase(type);
return Boolean(
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
(actualType && maskInputOptions[actualType as keyof MaskInputOptions])
);
}

export function maskInputValue({
isMasked,
element,
value,
maskInputFn,
}: {
isMasked: boolean;
element: HTMLElement;
value: string | null;
maskInputFn?: MaskInputFn;
forceMask?: boolean;
}): string {
let text = value || '';
const actualType = type && toLowerCase(type);

// Handle `option` like `select
if (tagName === 'OPTION') {
tagName = 'SELECT';
if (!isMasked) {
return text;
}

if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
(actualType && maskInputOptions[actualType as keyof MaskInputOptions]) ||
forceMask
) {
if (maskInputFn) {
text = maskInputFn(text, element);
} else {
text = '*'.repeat(text.length);
}
if (maskInputFn) {
text = maskInputFn(text, element);
}
return text;

return '*'.repeat(text.length);
}

export function toLowerCase<T extends string>(str: T): Lowercase<T> {
Expand Down
14 changes: 9 additions & 5 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getInputType,
toLowerCase,
getInputValue,
shouldMaskInput,
} from '@sentry-internal/rrweb-snapshot';
import type { observerParam, MutationBufferParam } from '../types';
import type {
Expand Down Expand Up @@ -531,23 +532,26 @@ export default class MutationBuffer {
const tagName = target.tagName as unknown as Uppercase<string>;
value = getInputValue(target as HTMLInputElement, tagName, type);

const isInputMasked = shouldMaskInput({
maskInputOptions: this.maskInputOptions,
tagName,
type,
})

const forceMask = needMaskingText(
m.target,
this.maskTextClass,
this.maskTextSelector,
this.unmaskTextClass,
this.unmaskTextSelector,
this.maskAllText,
isInputMasked,
);

value = maskInputValue({
isMasked: forceMask,
element: target,
maskInputOptions: this.maskInputOptions,
tagName,
type,
value,
maskInputFn: this.maskInputFn,
forceMask,
});
}
if (
Expand Down
41 changes: 19 additions & 22 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
MaskInputOptions,
maskInputValue,
Mirror,
getInputType,
getInputValue,
shouldMaskInput,
toLowerCase,
needMaskingText,
toUpperCase,
Expand Down Expand Up @@ -126,7 +126,7 @@ export function initMutationObserver(
// If this callback returns `false`, we do not want to process the mutations
// This can be used to e.g. do a manual full snapshot when mutations become too large, or similar.
if (options.onMutation && options.onMutation(mutations) === false) {
return;
return;
}
mutationBuffer.processMutations.bind(mutationBuffer)(mutations);
}),
Expand Down Expand Up @@ -438,7 +438,6 @@ function initInputObserver({
maskInputFn,
sampling,
userTriggeredOnInput,
maskAllText,
maskTextClass,
unmaskTextClass,
maskTextSelector,
Expand Down Expand Up @@ -475,33 +474,32 @@ function initInputObserver({
let text = getInputValue(el, tagName, type);
let isChecked = false;

const isInputMasked = shouldMaskInput({
maskInputOptions,
tagName,
type,
});

const forceMask = needMaskingText(
target as Node,
maskTextClass,
maskTextSelector,
unmaskTextClass,
unmaskTextSelector,
maskAllText,
isInputMasked,
);

if (type === 'radio' || type === 'checkbox') {
isChecked = (target as HTMLInputElement).checked;
}
if (
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
(type && maskInputOptions[type as keyof MaskInputOptions]) ||
forceMask
) {
text = maskInputValue({
element: target,
maskInputOptions,
tagName,
type,
value: text,
maskInputFn,
forceMask,
});
}

text = maskInputValue({
isMasked: forceMask,
element: target,
value: text,
maskInputFn,
});

cbWithDedup(
target,
callbackWrapper(wrapEventWithUserTriggeredFlag)(
Expand All @@ -518,10 +516,9 @@ function initInputObserver({
.forEach((el) => {
if (el !== target) {
const text = maskInputValue({
// share mask behavior of `target`
isMasked: forceMask,
element: el as HTMLInputElement,
maskInputOptions,
tagName,
type,
value: getInputValue(el as HTMLInputElement, tagName, type),
maskInputFn,
});
Expand Down

0 comments on commit e274f88

Please sign in to comment.