Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

john/hig-231-block-recording-on-all-text-nodes #30

Merged
merged 7 commits into from
Mar 25, 2021
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@highlight-run/rrweb",
"version": "0.9.29",
"version": "0.10.0",
"description": "record and replay the web",
"scripts": {
"test": "npm run bundle:browser && cross-env TS_NODE_CACHE=false TS_NODE_FILES=true mocha -r ts-node/register test/**/*.test.ts",
Expand Down
7 changes: 5 additions & 2 deletions src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ function record<T = eventWithTime>(
emit,
checkoutEveryNms,
checkoutEveryNth,
blockClass = 'rr-block',
blockClass = 'highlight-block',
blockSelector = null,
ignoreClass = 'rr-ignore',
ignoreClass = 'highlight-ignore',
inlineStylesheet = true,
maskAllInputs,
maskInputOptions: _maskInputOptions,
Expand All @@ -49,6 +49,7 @@ function record<T = eventWithTime>(
collectFonts = false,
recordLog = false,
debug,
enableStrictPrivacy = false,
} = options;
// runtime checks for user options
if (!emit) {
Expand Down Expand Up @@ -191,6 +192,7 @@ function record<T = eventWithTime>(
maskAllInputs: maskInputOptions,
slimDOM: slimDOMOptions,
recordCanvas,
enableStrictPrivacy,
});

if (!node) {
Expand Down Expand Up @@ -366,6 +368,7 @@ function record<T = eventWithTime>(
collectFonts,
slimDOMOptions,
logOptions,
enableStrictPrivacy,
},
hooks,
),
Expand Down
4 changes: 4 additions & 0 deletions src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export default class MutationBuffer {
private inlineStylesheet: boolean;
private maskInputOptions: MaskInputOptions;
private recordCanvas: boolean;
private enableStrictPrivacy: boolean;
private slimDOMOptions: SlimDOMOptions;

public init(
Expand All @@ -157,13 +158,15 @@ export default class MutationBuffer {
maskInputOptions: MaskInputOptions,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
enableStrictPrivacy: boolean,
) {
this.blockClass = blockClass;
this.blockSelector = blockSelector;
this.inlineStylesheet = inlineStylesheet;
this.maskInputOptions = maskInputOptions;
this.recordCanvas = recordCanvas;
this.slimDOMOptions = slimDOMOptions;
this.enableStrictPrivacy = enableStrictPrivacy;
this.emissionCallback = cb;
}

Expand Down Expand Up @@ -228,6 +231,7 @@ export default class MutationBuffer {
maskInputOptions: this.maskInputOptions,
slimDOMOptions: this.slimDOMOptions,
recordCanvas: this.recordCanvas,
enableStrictPrivacy: this.enableStrictPrivacy,
});
if (sn) {
adds.push({
Expand Down
3 changes: 3 additions & 0 deletions src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ function initMutationObserver(
maskInputOptions: MaskInputOptions,
recordCanvas: boolean,
slimDOMOptions: SlimDOMOptions,
enableStrictPrivacy: boolean,
): MutationObserver {
// see mutation.ts for details
mutationBuffer.init(
Expand All @@ -70,6 +71,7 @@ function initMutationObserver(
maskInputOptions,
recordCanvas,
slimDOMOptions,
enableStrictPrivacy,
);
let mutationBufferCtor = window.MutationObserver;
const angularZoneSymbol = (window as WindowWithAngularZone)?.Zone?.__symbol__?.(
Expand Down Expand Up @@ -720,6 +722,7 @@ export function initObservers(
o.maskInputOptions,
o.recordCanvas,
o.slimDOMOptions,
o.enableStrictPrivacy,
);
const mousemoveHandler = initMoveObserver(o.mousemoveCb, o.sampling);
const mouseInteractionHandler = initMouseInteractionObserver(
Expand Down
3 changes: 2 additions & 1 deletion src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class Replayer {
skipInactive: false,
showWarning: true,
showDebug: false,
blockClass: 'rr-block',
blockClass: 'highlight-block',
liveMode: false,
insertStyleRules: [],
triggerFocus: true,
Expand Down Expand Up @@ -641,6 +641,7 @@ export class Replayer {
const styleEl = document.createElement('style');
const { documentElement, head } = this.iframe.contentDocument;
documentElement!.insertBefore(styleEl, head);
console.log(this.config.blockClass);
const injectStylesRules = getInjectStyleRules(
this.config.blockClass,
).concat(this.config.insertStyleRules);
Expand Down
2 changes: 2 additions & 0 deletions src/replay/styles/inject-style.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const rules: (blockClass: string) => string[] = (blockClass: string) => [
`iframe, .${blockClass} { background: #ccc }`,
'noscript { display: none !important; }',
`.${blockClass} { background: black; border-radius: 5px; }`,
`.${blockClass}:hover::after {content: 'Redacted'; color: white; text-align: center; width: 100%; display: block;}`,
];

export default rules;
54 changes: 51 additions & 3 deletions src/snapshot/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ function serializeNode(
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
recordCanvas: boolean;
enableStrictPrivacy: boolean;
},
): serializedNode | false {
const {
Expand All @@ -214,7 +215,9 @@ function serializeNode(
inlineStylesheet,
maskInputOptions = {},
recordCanvas,
enableStrictPrivacy,
} = options;

switch (n.nodeType) {
case n.DOCUMENT_NODE:
return {
Expand All @@ -229,7 +232,7 @@ function serializeNode(
systemId: (n as DocumentType).systemId,
};
case n.ELEMENT_NODE:
const needBlock = _isBlockedElement(
let needBlock = _isBlockedElement(
n as HTMLElement,
blockClass,
blockSelector,
Expand Down Expand Up @@ -318,13 +321,14 @@ function serializeNode(
if ((n as HTMLElement).scrollTop) {
attributes.rr_scrollTop = (n as HTMLElement).scrollTop;
}
if (needBlock) {
if (needBlock || (tagName === 'img' && enableStrictPrivacy)) {
const { width, height } = (n as HTMLElement).getBoundingClientRect();
attributes = {
class: attributes.class,
rr_width: `${width}px`,
rr_height: `${height}px`,
};
needBlock = true;
}
return {
type: NodeType.Element,
Expand All @@ -341,11 +345,38 @@ function serializeNode(
n.parentNode && (n.parentNode as HTMLElement).tagName;
let textContent = (n as Text).textContent;
const isStyle = parentTagName === 'STYLE' ? true : undefined;
/** Determines if this node has been handled already. */
let textContentHandled = false;
if (isStyle && textContent) {
textContent = absoluteToStylesheet(textContent, getHref());
textContentHandled = true;
}
if (parentTagName === 'SCRIPT') {
textContent = 'SCRIPT_PLACEHOLDER';
textContentHandled = true;
} else if (parentTagName === 'NOSCRIPT') {
textContent = '';
textContentHandled = true;
}

// Randomizes the text content to a string of the same length.
if (enableStrictPrivacy && !textContentHandled && parentTagName) {
const IGNORE_TAG_NAMES = new Set([
'HEAD',
'TITLE',
'STYLE',
'SCRIPT',
'HTML',
'BODY',
'NOSCRIPT',
]);
if (!IGNORE_TAG_NAMES.has(parentTagName)) {
textContent =
textContent
?.split(' ')
.map((word) => Math.random().toString(20).substr(2, word.length))
.join(' ') || '';
}
}
return {
type: NodeType.Text,
Expand Down Expand Up @@ -472,6 +503,7 @@ export function serializeNodeWithId(
slimDOMOptions: SlimDOMOptions;
recordCanvas?: boolean;
preserveWhiteSpace?: boolean;
enableStrictPrivacy: boolean;
},
): serializedNodeWithId | null {
const {
Expand All @@ -484,6 +516,7 @@ export function serializeNodeWithId(
maskInputOptions = {},
slimDOMOptions,
recordCanvas = false,
enableStrictPrivacy,
} = options;
let { preserveWhiteSpace = true } = options;
const _serializedNode = serializeNode(n, {
Expand All @@ -493,6 +526,7 @@ export function serializeNodeWithId(
inlineStylesheet,
maskInputOptions,
recordCanvas,
enableStrictPrivacy,
});
if (!_serializedNode) {
// TODO: dev only
Expand Down Expand Up @@ -524,6 +558,16 @@ export function serializeNodeWithId(
let recordChild = !skipChild;
if (serializedNode.type === NodeType.Element) {
recordChild = recordChild && !serializedNode.needBlock;

/** Highlight Code Begin */
// Remove the image's src if enableStrictPrivacy.
if (serializedNode.needBlock && serializedNode.tagName === 'img') {
const clone = n.cloneNode();
((clone as unknown) as HTMLImageElement).src = '';
map[id] = clone as INode;
}
/** Highlight Code End */

// this property was not needed in replay side
delete serializedNode.needBlock;
}
Expand Down Expand Up @@ -552,6 +596,7 @@ export function serializeNodeWithId(
slimDOMOptions,
recordCanvas,
preserveWhiteSpace,
enableStrictPrivacy,
});
if (serializedChildNode) {
serializedNode.childNodes.push(serializedChildNode);
Expand All @@ -570,15 +615,17 @@ function snapshot(
slimDOM?: boolean | SlimDOMOptions;
recordCanvas?: boolean;
blockSelector?: string | null;
enableStrictPrivacy: boolean;
},
): [serializedNodeWithId | null, idNodeMap] {
const {
blockClass = 'rr-block',
blockClass = 'highlight-block',
inlineStylesheet = true,
recordCanvas = false,
blockSelector = null,
maskAllInputs = false,
slimDOM = false,
enableStrictPrivacy = false,
} = options || {};
const idNodeMap: idNodeMap = {};
const maskInputOptions: MaskInputOptions =
Expand Down Expand Up @@ -632,6 +679,7 @@ function snapshot(
maskInputOptions,
slimDOMOptions,
recordCanvas,
enableStrictPrivacy,
}),
idNodeMap,
];
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,11 @@ export type recordOptions<T> = {
mousemoveWait?: number;
recordLog?: boolean | LogRecordOptions;
debug?: boolean;
/**
* Enabling this will disable recording of text data on the page. This is useful if you do not want to record personally identifiable information.
* Text will be randomized. Instead of seeing "Hello World" in a recording, you will see "1fds1 j59a0".
*/
enableStrictPrivacy?: boolean;
};

export type observerParam = {
Expand All @@ -232,6 +237,7 @@ export type observerParam = {
recordCanvas: boolean;
collectFonts: boolean;
slimDOMOptions: SlimDOMOptions;
enableStrictPrivacy: boolean;
};

export type hooksParam = {
Expand Down
2 changes: 1 addition & 1 deletion test/html/block.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<title>Block record</title>
</head>
<body>
<div class="rr-block" style="width: 50px; height: 50px;">
<div class="highlight-block" style="width: 50px; height: 50px">
<input type="text" /> <span id="text"></span>
</div>
</body>
Expand Down
3 changes: 2 additions & 1 deletion typings/record/mutation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export default class MutationBuffer {
private inlineStylesheet;
private maskInputOptions;
private recordCanvas;
private enableStrictPrivacy;
private slimDOMOptions;
init(cb: mutationCallBack, blockClass: blockClass, blockSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions): void;
init(cb: mutationCallBack, blockClass: blockClass, blockSelector: string | null, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions, recordCanvas: boolean, slimDOMOptions: SlimDOMOptions, enableStrictPrivacy: boolean): void;
freeze(): void;
unfreeze(): void;
isFrozen(): boolean;
Expand Down
2 changes: 1 addition & 1 deletion typings/replay/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ export declare class Replayer {
private mouseTail;
private tailPositions;
private emitter;
private activityIntervals;
private inactiveEndTimestamp;
private legacy_missingNodeRetryMap;
private treeIndex;
private fragmentParentMap;
private elementStateMap;
private imageMap;
private activityIntervals;
constructor(events: Array<eventWithTime | string>, config?: Partial<playerConfig>);
on(event: string, handler: Handler): this;
setConfig(config: Partial<playerConfig>): void;
Expand Down
2 changes: 0 additions & 2 deletions typings/snapshot/rebuild.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ export declare function buildNodeWithSN(n: serializedNodeWithId, options: {
map: idNodeMap;
skipChild?: boolean;
hackCss: boolean;
afterAppend?: (n: INode) => unknown;
}): INode | null;
declare function rebuild(n: serializedNodeWithId, options: {
doc: Document;
onVisit?: (node: INode) => unknown;
hackCss?: boolean;
afterAppend?: (n: INode) => unknown;
}): [Node | null, idNodeMap];
export default rebuild;
11 changes: 2 additions & 9 deletions typings/snapshot/snapshot.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ export declare function serializeNodeWithId(n: Node | INode, options: {
slimDOMOptions: SlimDOMOptions;
recordCanvas?: boolean;
preserveWhiteSpace?: boolean;
onSerialize?: (n: INode) => unknown;
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
iframeLoadTimeout?: number;
debug?: boolean;
enableStrictPrivacy: boolean;
}): serializedNodeWithId | null;
declare function snapshot(n: Document, options?: {
blockClass?: string | RegExp;
Expand All @@ -27,11 +24,7 @@ declare function snapshot(n: Document, options?: {
slimDOM?: boolean | SlimDOMOptions;
recordCanvas?: boolean;
blockSelector?: string | null;
preserveWhiteSpace?: boolean;
onSerialize?: (n: INode) => unknown;
onIframeLoad?: (iframeINode: INode, node: serializedNodeWithId) => unknown;
iframeLoadTimeout?: number;
debug?: boolean;
enableStrictPrivacy: boolean;
}): [serializedNodeWithId | null, idNodeMap];
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
export declare function cleanupSnapshot(): void;
Expand Down
Loading