@mtdt.temp/browser-core
Version:
Datadog browser core utilities.
118 lines • 5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.instrumentMethod = instrumentMethod;
exports.instrumentSetter = instrumentSetter;
const timer_1 = require("./timer");
const monitor_1 = require("./monitor");
const functionUtils_1 = require("./utils/functionUtils");
const handlingStack_1 = require("./stackTrace/handlingStack");
/**
* Instruments a method on a object, calling the given callback before the original method is
* invoked. The callback receives an object with information about the method call.
*
* This function makes sure that we are "good citizens" regarding third party instrumentations: when
* removing the instrumentation, the original method is usually restored, but if a third party
* instrumentation was set after ours, we keep it in place and just replace our instrumentation with
* a noop.
*
* Note: it is generally better to instrument methods that are "owned" by the object instead of ones
* that are inherited from the prototype chain. Example:
* * do: `instrumentMethod(Array.prototype, 'push', ...)`
* * don't: `instrumentMethod([], 'push', ...)`
*
* This method is also used to set event handler properties (ex: window.onerror = ...), as it has
* the same requirements as instrumenting a method:
* * if the event handler is already set by a third party, we need to call it and not just blindly
* override it.
* * if the event handler is set by a third party after us, we need to keep it in place when
* removing ours.
*
* @example
*
* instrumentMethod(window, 'fetch', ({ target, parameters, onPostCall }) => {
* console.log('Before calling fetch on', target, 'with parameters', parameters)
*
* onPostCall((result) => {
* console.log('After fetch calling on', target, 'with parameters', parameters, 'and result', result)
* })
* })
*/
function instrumentMethod(targetPrototype, method, onPreCall, { computeHandlingStack } = {}) {
let original = targetPrototype[method];
if (typeof original !== 'function') {
if (method in targetPrototype && method.startsWith('on')) {
original = functionUtils_1.noop;
}
else {
return { stop: functionUtils_1.noop };
}
}
let stopped = false;
const instrumentation = function () {
if (stopped) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
return original.apply(this, arguments);
}
const parameters = Array.from(arguments);
let postCallCallback;
(0, monitor_1.callMonitored)(onPreCall, null, [
{
target: this,
parameters,
onPostCall: (callback) => {
postCallCallback = callback;
},
handlingStack: computeHandlingStack ? (0, handlingStack_1.createHandlingStack)('instrumented method') : undefined,
},
]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
const result = original.apply(this, parameters);
if (postCallCallback) {
(0, monitor_1.callMonitored)(postCallCallback, null, [result]);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return result;
};
targetPrototype[method] = instrumentation;
return {
stop: () => {
stopped = true;
// If the instrumentation has been removed by a third party, keep the last one
if (targetPrototype[method] === instrumentation) {
targetPrototype[method] = original;
}
},
};
}
function instrumentSetter(targetPrototype, property, after) {
const originalDescriptor = Object.getOwnPropertyDescriptor(targetPrototype, property);
if (!originalDescriptor || !originalDescriptor.set || !originalDescriptor.configurable) {
return { stop: functionUtils_1.noop };
}
const stoppedInstrumentation = functionUtils_1.noop;
let instrumentation = (target, value) => {
// put hooked setter into event loop to avoid of set latency
(0, timer_1.setTimeout)(() => {
if (instrumentation !== stoppedInstrumentation) {
after(target, value);
}
}, 0);
};
const instrumentationWrapper = function (value) {
originalDescriptor.set.call(this, value);
instrumentation(this, value);
};
Object.defineProperty(targetPrototype, property, {
set: instrumentationWrapper,
});
return {
stop: () => {
var _a;
if (((_a = Object.getOwnPropertyDescriptor(targetPrototype, property)) === null || _a === void 0 ? void 0 : _a.set) === instrumentationWrapper) {
Object.defineProperty(targetPrototype, property, originalDescriptor);
}
instrumentation = stoppedInstrumentation;
},
};
}
//# sourceMappingURL=instrumentMethod.js.map