From e056f1ae8821d1a07dd95cf10a43cca177380c3b Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Wed, 1 Apr 2026 12:00:00 +0800 Subject: [PATCH] add the patch function to utils --- src/utils.ts | 40 ++++++++++++++++++++++++++++++++++++ typings/record/mutation.d.ts | 7 ++++--- typings/record/observer.d.ts | 1 - typings/replay/index.d.ts | 5 +++++ typings/types.d.ts | 21 ++++++++++++++----- typings/utils.d.ts | 37 ++++++++++++++++++++++++++++++++- 6 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 4c8c480f..f924fc0b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -116,6 +116,46 @@ export function hookSetter( return () => hookSetter(target, key, original || {}, true); } +// copy from https://github.com/getsentry/sentry-javascript/blob/b2109071975af8bf0316d3b5b38f519bdaf5dc15/packages/utils/src/object.ts +export function patch( + // tslint:disable-next-line:no-any + source: { [key: string]: any }, + name: string, + // tslint:disable-next-line:no-any + replacement: (...args: any[]) => any, +): () => void { + if (!(name in source)) { + return () => {}; + } + + const original = source[name] as () => unknown; + const wrapped = replacement(original); + + // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work + // otherwise it'll throw "TypeError: Object.defineProperties called on non-object" + // tslint:disable-next-line:strict-type-predicates + if (typeof wrapped === 'function') { + try { + wrapped.prototype = wrapped.prototype || {}; + Object.defineProperties(wrapped, { + __rrweb_original__: { + enumerable: false, + value: original, + }, + }); + } catch { + // This can throw if multiple fill happens on a global object like XMLHttpRequest + // Fixes https://github.com/getsentry/sentry-javascript/issues/2043 + } + } + + source[name] = wrapped; + + return () => { + source[name] = original; + }; +} + export function getWindowHeight(): number { return ( window.innerHeight || diff --git a/typings/record/mutation.d.ts b/typings/record/mutation.d.ts index 716aa9c3..8350ee19 100644 --- a/typings/record/mutation.d.ts +++ b/typings/record/mutation.d.ts @@ -1,3 +1,4 @@ +import { MaskInputOptions } from 'rrweb-snapshot'; import { mutationRecord, blockClass, mutationCallBack } from '../types'; export default class MutationBuffer { private texts; @@ -11,10 +12,10 @@ export default class MutationBuffer { private emissionCallback; private blockClass; private inlineStylesheet; - private maskAllInputs; - constructor(cb: mutationCallBack, blockClass: blockClass, inlineStylesheet: boolean, maskAllInputs: boolean); + private maskInputOptions; + constructor(cb: mutationCallBack, blockClass: blockClass, inlineStylesheet: boolean, maskInputOptions: MaskInputOptions); processMutations: (mutations: mutationRecord[]) => void; + emit: () => void; private processMutation; private genAdds; - emit: () => void; } diff --git a/typings/record/observer.d.ts b/typings/record/observer.d.ts index 703605fb..75cac487 100644 --- a/typings/record/observer.d.ts +++ b/typings/record/observer.d.ts @@ -1,4 +1,3 @@ import { observerParam, listenerHandler, hooksParam } from '../types'; export declare const INPUT_TAGS: string[]; -export declare const MASK_TYPES: string[]; export default function initObservers(o: observerParam, hooks?: hooksParam): listenerHandler; diff --git a/typings/replay/index.d.ts b/typings/replay/index.d.ts index 8db499c3..724060b4 100644 --- a/typings/replay/index.d.ts +++ b/typings/replay/index.d.ts @@ -12,6 +12,8 @@ export declare class Replayer { private noramlSpeed; private legacy_missingNodeRetryMap; private service; + private treeIndex; + private fragmentParentMap; constructor(events: Array, config?: Partial); on(event: string, handler: Handler): void; setConfig(config: Partial): void; @@ -31,6 +33,9 @@ export declare class Replayer { private rebuildFullSnapshot; private waitForStylesheetLoad; private applyIncremental; + private applyMutation; + private applyScroll; + private applyInput; private legacy_resolveMissingNode; private moveAndHover; private hoverElements; diff --git a/typings/types.d.ts b/typings/types.d.ts index 67f0d34e..05d7f29e 100644 --- a/typings/types.d.ts +++ b/typings/types.d.ts @@ -1,4 +1,4 @@ -import { serializedNodeWithId, idNodeMap, INode } from 'rrweb-snapshot'; +import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions } from 'rrweb-snapshot'; import { PackFn, UnpackFn } from './packer/base'; export declare enum EventType { DomContentLoaded = 0, @@ -90,6 +90,12 @@ export declare type eventWithTime = event & { delay?: number; }; export declare type blockClass = string | RegExp; +export declare type SamplingStrategy = Partial<{ + mousemove: boolean | number; + mouseInteraction: boolean | Record; + scroll: number; + input: 'all' | 'last'; +}>; export declare type recordOptions = { emit?: (e: T, isCheckout?: boolean) => void; checkoutEveryNth?: number; @@ -97,10 +103,12 @@ export declare type recordOptions = { blockClass?: blockClass; ignoreClass?: string; maskAllInputs?: boolean; + maskInputOptions?: MaskInputOptions; inlineStylesheet?: boolean; hooks?: hooksParam; - mousemoveWait?: number; packFn?: PackFn; + sampling?: SamplingStrategy; + mousemoveWait?: number; }; export declare type observerParam = { mutationCb: mutationCallBack; @@ -112,10 +120,10 @@ export declare type observerParam = { mediaInteractionCb: mediaInteractionCallback; blockClass: blockClass; ignoreClass: string; - maskAllInputs: boolean; + maskInputOptions: MaskInputOptions; inlineStylesheet: boolean; styleSheetRuleCb: styleSheetRuleCallback; - mousemoveWait: number; + sampling: SamplingStrategy; }; export declare type hooksParam = { mutation?: mutationCallBack; @@ -265,6 +273,8 @@ export declare type playerConfig = { unpackFn?: UnpackFn; }; export declare type playerMetaData = { + startTime: number; + endTime: number; totalTime: number; }; export declare type missingNode = { @@ -297,6 +307,7 @@ export declare enum ReplayerEvents { SkipEnd = "skip-end", MouseInteraction = "mouse-interaction", EventCast = "event-cast", - CustomEvent = "custom-event" + CustomEvent = "custom-event", + Flush = "flush" } export {}; diff --git a/typings/utils.d.ts b/typings/utils.d.ts index 32c62f84..7d6fc9f1 100644 --- a/typings/utils.d.ts +++ b/typings/utils.d.ts @@ -1,12 +1,47 @@ -import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass } from './types'; +import { Mirror, throttleOptions, listenerHandler, hookResetter, blockClass, eventWithTime, addedNodeMutation, removedNodeMutation, textMutation, attributeMutation, mutationData, scrollData, inputData } from './types'; import { INode } from 'rrweb-snapshot'; export declare function on(type: string, fn: EventListenerOrEventListenerObject, target?: Document | Window): listenerHandler; export declare const mirror: Mirror; export declare function throttle(func: (arg: T) => void, wait: number, options?: throttleOptions): (arg: T) => void; export declare function hookSetter(target: T, key: string | number | symbol, d: PropertyDescriptor, isRevoked?: boolean, win?: Window & typeof globalThis): hookResetter; +export declare function patch(source: { + [key: string]: any; +}, name: string, replacement: (...args: any[]) => any): () => void; export declare function getWindowHeight(): number; export declare function getWindowWidth(): number; export declare function isBlocked(node: Node | null, blockClass: blockClass): boolean; export declare function isAncestorRemoved(target: INode): boolean; export declare function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent; export declare function polyfill(): void; +export declare function needCastInSyncMode(event: eventWithTime): boolean; +export declare type TreeNode = { + id: number; + mutation: addedNodeMutation; + parent?: TreeNode; + children: Record; + texts: textMutation[]; + attributes: attributeMutation[]; +}; +export declare class TreeIndex { + tree: Record; + private removeNodeMutations; + private textMutations; + private attributeMutations; + private indexes; + private removeIdSet; + private scrollMap; + private inputMap; + constructor(); + add(mutation: addedNodeMutation): void; + remove(mutation: removedNodeMutation): void; + text(mutation: textMutation): void; + attribute(mutation: attributeMutation): void; + scroll(d: scrollData): void; + input(d: inputData): void; + flush(): { + mutationData: mutationData; + scrollMap: TreeIndex['scrollMap']; + inputMap: TreeIndex['inputMap']; + }; + private reset; +}