import { callMonitored, monitor } from './monitor' import { noop } from './utils' export function instrumentMethod( object: OBJECT, method: METHOD, instrumentationFactory: ( original: OBJECT[METHOD] ) => (this: OBJECT, ...args: Parameters) => ReturnType ) { const original = object[method] let instrumentation = instrumentationFactory(original) const instrumentationWrapper = function (this: OBJECT): ReturnType | undefined { if (typeof instrumentation !== 'function') { return undefined } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return instrumentation.apply(this, arguments as unknown as Parameters) } object[method] = instrumentationWrapper as OBJECT[METHOD] return { stop: () => { if (object[method] === instrumentationWrapper) { object[method] = original } else { instrumentation = original } }, } } export function instrumentMethodAndCallOriginal( object: OBJECT, method: METHOD, { before, after, }: { before?: (this: OBJECT, ...args: Parameters) => void after?: (this: OBJECT, ...args: Parameters) => void } ) { return instrumentMethod( object, method, (original) => function () { const args = arguments as unknown as Parameters let result if (before) { callMonitored(before, this as ThisParameterType, args) } if (typeof original === 'function') { // eslint-disable-next-line @typescript-eslint/no-unsafe-call result = original.apply(this, args) } if (after) { callMonitored(after, this as ThisParameterType, args) } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return result } ) } export function instrumentSetter( object: OBJECT, property: PROPERTY, after: (thisObject: OBJECT, value: OBJECT[PROPERTY]) => void ) { const originalDescriptor = Object.getOwnPropertyDescriptor(object, property) if (!originalDescriptor || !originalDescriptor.set || !originalDescriptor.configurable) { return { stop: noop } } let instrumentation = (thisObject: OBJECT, value: OBJECT[PROPERTY]) => { // put hooked setter into event loop to avoid of set latency setTimeout( monitor(() => { after(thisObject, value) }), 0 ) } const instrumentationWrapper = function (this: OBJECT, value: OBJECT[PROPERTY]) { originalDescriptor.set!.call(this, value) instrumentation(this, value) } Object.defineProperty(object, property, { set: instrumentationWrapper, }) return { stop: () => { if (Object.getOwnPropertyDescriptor(object, property)?.set === instrumentationWrapper) { Object.defineProperty(object, property, originalDescriptor) } else { instrumentation = noop } }, } }