Skip to content

Commit

Permalink
feat(instrumentation): implement require-in-the-middle singleton
Browse files Browse the repository at this point in the history
  • Loading branch information
mhassan1 committed Aug 12, 2022
1 parent 038df3f commit 76fcc07
Showing 1 changed file with 63 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export abstract class InstrumentationBase<T = any>
extends InstrumentationAbstract
implements types.Instrumentation {
private _modules: InstrumentationModuleDefinition<T>[];
private _hooks: RequireInTheMiddle.Hooked[] = [];
private _hooked = false;
private _enabled = false;

constructor(
Expand Down Expand Up @@ -142,7 +142,7 @@ export abstract class InstrumentationBase<T = any>
this._enabled = true;

// already hooked, just call patch again
if (this._hooks.length > 0) {
if (this._hooked) {
for (const module of this._modules) {
if (typeof module.patch === 'function' && module.moduleExports) {
module.patch(module.moduleExports, module.moduleVersion);
Expand All @@ -158,22 +158,20 @@ export abstract class InstrumentationBase<T = any>

this._warnOnPreloadedModules();
for (const module of this._modules) {
this._hooks.push(
RequireInTheMiddle(
[module.name],
{ internals: true },
(exports, name, baseDir) => {
return this._onRequire<typeof exports>(
(module as unknown) as InstrumentationModuleDefinition<
typeof exports
requireInTheMiddleSingleton.register(
module.name,
(exports, name, baseDir) => {
return this._onRequire<typeof exports>(
(module as unknown) as InstrumentationModuleDefinition<
typeof exports
>,
exports,
name,
baseDir
);
}
)
exports,
name,
baseDir
);
}
);
this._hooked = true;
}
}

Expand Down Expand Up @@ -210,3 +208,52 @@ function isSupported(supportedVersions: string[], version?: string, includePrere
return satisfies(version, supportedVersion, { includePrerelease });
});
}

/**
* Singleton class for `require-in-the-middle`
* Allows instrumentation plugins to patch modules with only a single `require` patch
* TODO: Move this to a higher level to ensure it is a singleton across the Node.js process
*/
class RequireInTheMiddleSingleton {
private _modulesToHook: Array<{ moduleName: string, onRequire: RequireInTheMiddle.OnRequireFn }> = [];

constructor() {
this._initialize();
}

private _initialize() {
RequireInTheMiddle(
// Intercept all `require` calls; we will filter the matching ones below
null,
{ internals: true },
(exports, name, baseDir) => {
const matches = this._modulesToHook.filter(({ moduleName: hookedModuleName }) => {
return RequireInTheMiddleSingleton._shouldHook(hookedModuleName, name, baseDir);
});

for (const { onRequire } of matches) {
exports = onRequire(exports, name, baseDir);
}

return exports;
}
);
}

private static _shouldHook(hookedModuleName: string, requiredModuleName: string, requiredModuleBaseDir: string | undefined) {
if (path.isAbsolute(hookedModuleName)) {
if (requiredModuleBaseDir === undefined) {
return false;
}
requiredModuleName = path.join(requiredModuleBaseDir, requiredModuleName);
}

return requiredModuleName === hookedModuleName || requiredModuleName.startsWith(hookedModuleName + path.sep);
}

register(moduleName: string, onRequire: RequireInTheMiddle.OnRequireFn) {
this._modulesToHook.push({ moduleName, onRequire });
}
}

const requireInTheMiddleSingleton = new RequireInTheMiddleSingleton();

0 comments on commit 76fcc07

Please sign in to comment.