@contrast/agent 5.0.8 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/check-flag-vs-node-version.mjs +10 -4
- package/lib/esm-hooks.mjs +200 -196
- package/lib/esm-loader.mjs +71 -69
- package/lib/initialize.mjs +2 -4
- package/package.json +8 -8
|
@@ -21,13 +21,19 @@ export default function checkImportVsLoaderVsNodeVersion() {
|
|
|
21
21
|
// allow testing to ignore these restrictions
|
|
22
22
|
const noValidate = process.env.CSI_EXPOSE_CORE === 'no-validate';
|
|
23
23
|
let flag;
|
|
24
|
+
const { execArgv, version, env: { NODE_OPTIONS } } = process;
|
|
24
25
|
|
|
25
|
-
const { execArgv, version } = process;
|
|
26
26
|
// eslint-disable-next-line newline-per-chained-call
|
|
27
27
|
const [major, minor] = version.slice(1).split('.').map(Number);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
|
|
29
|
+
const _execArgs = [
|
|
30
|
+
...(NODE_OPTIONS?.split?.(/\s+/).map((v) => v.trim()) || []),
|
|
31
|
+
...execArgv,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < _execArgs.length; i++) {
|
|
35
|
+
const loader = _execArgs[i];
|
|
36
|
+
const agent = _execArgs[i + 1];
|
|
31
37
|
if (['--import', '--loader', '--experimental-loader'].includes(loader) && agent?.startsWith('@contrast/agent')) {
|
|
32
38
|
flag = loader;
|
|
33
39
|
if (noValidate) {
|
package/lib/esm-hooks.mjs
CHANGED
|
@@ -28,229 +28,233 @@ if (process.env.CSI_HOOKS_LOG) {
|
|
|
28
28
|
const [major, minor] = process.versions.node.split('.').map(it => +it);
|
|
29
29
|
const isLT16_12 = major < 16 || (major === 16 && minor < 12);
|
|
30
30
|
|
|
31
|
+
|
|
32
|
+
// from esmock: git@github.com:iambumblehead/esmock.git
|
|
33
|
+
//
|
|
34
|
+
// new versions of node: when multiple loaders are used and context
|
|
35
|
+
// is passed to nextResolve, the process crashes in a recursive call
|
|
36
|
+
// see: /esmock/issues/#48
|
|
37
|
+
//
|
|
38
|
+
// old versions of node: if context.parentURL is defined, and context
|
|
39
|
+
// is not passed to nextResolve, the tests fail
|
|
40
|
+
//
|
|
41
|
+
// later versions of node v16 include 'node-addons'
|
|
42
|
+
async function protectedNextResolve(nextResolve, specifier, context) {
|
|
43
|
+
if (context.parentURL) {
|
|
44
|
+
if (context.conditions.at(-1) === 'node-addons' || context.importAssertions || isLT16_12) {
|
|
45
|
+
return nextResolve(specifier, context);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return nextResolve(specifier);
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
// import.meta.resolve('node-fetch') v20 became synchronous. not all that useful for now because
|
|
32
53
|
// of the significant break in behavior. but the function is great - it resolves a specifier to the
|
|
33
54
|
// file that would be loaded.
|
|
34
55
|
// file:///home/bruce/github/csi/rasp-v3/node_modules/node-fetch/src/index.js
|
|
35
56
|
|
|
36
57
|
const { default: core } = await import('./initialize.mjs');
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
let load, resolve, initialize;
|
|
59
|
+
|
|
60
|
+
if (core) {
|
|
61
|
+
const { esmHooks: { mappings, fixPath, getFileType } } = core;
|
|
62
|
+
initialize = Module.register && async function(data) {
|
|
63
|
+
if (data?.port) {
|
|
64
|
+
data.port.on('message', _msg => {
|
|
65
|
+
// we don't currently send messages to the loader thread but when we do,
|
|
66
|
+
// this is where they will show up.
|
|
67
|
+
//console.log('ESM HOOK -> INIT -> MESSAGE', _msg);
|
|
68
|
+
});
|
|
69
|
+
data.port.postMessage({ type: 'keep-alive', data: 'hello from esm-hooks' });
|
|
70
|
+
data.port.unref();
|
|
71
|
+
// this is running in the loader thread. save thread info because it can't
|
|
72
|
+
// be set from the main thread.
|
|
73
|
+
if (W.isMainThread) {
|
|
74
|
+
throw new Error('initialize() called from main thread.');
|
|
75
|
+
}
|
|
76
|
+
core.threadInfo.isMainThread = false;
|
|
77
|
+
core.threadInfo.threadId = W.threadId;
|
|
78
|
+
core.threadInfo.port = data.port;
|
|
79
|
+
// the loader thread's post() sends via the port.
|
|
80
|
+
core.threadInfo.post = (type, data) => {
|
|
81
|
+
core.threadInfo.port.postMessage({ type, data });
|
|
82
|
+
};
|
|
52
83
|
}
|
|
53
|
-
core.threadInfo.isMainThread = false;
|
|
54
|
-
core.threadInfo.threadId = W.threadId;
|
|
55
|
-
core.threadInfo.port = data.port;
|
|
56
|
-
// the loader thread's post() sends via the port.
|
|
57
|
-
core.threadInfo.post = (type, data) => {
|
|
58
|
-
core.threadInfo.port.postMessage({ type, data });
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
log && console.log('ESM HOOK -> INIT');
|
|
86
|
+
// it's not clear to me why Module is present when initialize() is being
|
|
87
|
+
// executed in the loader thread. but it is. (code here originally loaded
|
|
88
|
+
// Module via createRequire(), but that is not necessary.)
|
|
89
|
+
const originalRequire = Module.prototype.require;
|
|
90
|
+
const originalCompile = Module.prototype._compile;
|
|
91
|
+
const originalExtensions = Module._extensions['.js'];
|
|
92
|
+
const originalLoad = Module._load;
|
|
93
|
+
|
|
94
|
+
// Module needs to be patched in the loader thread too. initialize() runs in
|
|
95
|
+
// that context.
|
|
96
|
+
Module.prototype.require = function(moduleId) {
|
|
97
|
+
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> require(${moduleId})`);
|
|
98
|
+
return originalRequire.call(this, moduleId);
|
|
99
|
+
};
|
|
77
100
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
101
|
+
Module.prototype._compile = function(code, filename) {
|
|
102
|
+
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> _compile(${filename})`);
|
|
103
|
+
return originalCompile.call(this, code, filename);
|
|
104
|
+
};
|
|
82
105
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
106
|
+
Module._extensions['.js'] = function(module, filename) {
|
|
107
|
+
(log & logRequireAll) && console.log(`CJS(init ${W.threadId}) -> _extensions[".js"]: ${filename}`);
|
|
108
|
+
return originalExtensions.call(this, module, filename);
|
|
109
|
+
};
|
|
87
110
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
Module._load = function(request, parent, isMain) {
|
|
112
|
+
(log & logLoad) && console.log(`CJS(init ${W.threadId}) -> _load(${request})`);
|
|
113
|
+
return originalLoad.call(this, request, parent, isMain);
|
|
114
|
+
};
|
|
91
115
|
};
|
|
92
|
-
};
|
|
93
116
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
117
|
+
resolve = async function(specifier, context, nextResolve) {
|
|
118
|
+
(log & logResolve) && console.log(`ESM HOOK(${W.threadId}) -> RESOLVE -> ${specifier}`);
|
|
119
|
+
let isFlaggedToPatch = false;
|
|
120
|
+
if (context.parentURL) {
|
|
121
|
+
isFlaggedToPatch = context.parentURL.endsWith('csi-flag=', context.parentURL.length - 1);
|
|
122
|
+
}
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
// We could walk up the parent chain to see if we should interpret this specifier
|
|
122
|
-
// as a module or commonjs, but it seems more straightforward to just always
|
|
123
|
-
// redirect. walking up the parent chain would let us avoid redirecting commonjs
|
|
124
|
-
// files. we could also call nextResolve() and let node tell us type of the file
|
|
125
|
-
// is but that would potentially create problems when other hooks, e.g., datadog,
|
|
126
|
-
// are in place.
|
|
127
|
-
//
|
|
128
|
-
// also we could check to see if this module's already been patched and skip this.
|
|
129
|
-
// not sure of that though; it might get loaded as a module, not a commonjs file,
|
|
130
|
-
// and that would be a problem. so when we start patching esm-native modules, we'll
|
|
131
|
-
// need a second "already patched" weakmap.
|
|
132
|
-
if (!isFlaggedToPatch && specifier in mappings) {
|
|
133
|
-
// eslint-disable-next-line prefer-const
|
|
134
|
-
let { url, format, target } = mappings[specifier];
|
|
135
|
-
// set flag to module or commonjs. i'm not sure this is needed but am keeping it
|
|
136
|
-
// in place until we have to implement esm-native module rewrites/wrapping. this
|
|
137
|
-
// is the point at which resolve() communicates to load().
|
|
124
|
+
// this needs to be generalized so it uses the execArgv value. the idea is to
|
|
125
|
+
// capture when the application actually starts. is this needed? i don't think so
|
|
126
|
+
// because the loaders aren't instantiated until esm-loader.mjs returns/register()s
|
|
127
|
+
// the hooks. i am leaving the comment here as a breadcrumb in case we need to
|
|
128
|
+
// capture the application start time. but we could 1) look at process.argv, 2)
|
|
129
|
+
// find the first argument, and 3) notice when that's loaded and capture that
|
|
130
|
+
// "now we are in the user's application".
|
|
131
|
+
//
|
|
132
|
+
//if (false) debugger;
|
|
133
|
+
//if (log & logApplication) {
|
|
134
|
+
// const RE = /test-integration-servers\/express.m?js(\?.+)?$/;
|
|
135
|
+
// if (specifier.match(RE) || context.parentURL?.match(RE)) {
|
|
136
|
+
// console.log(`ESM HOOK(${W.threadId}) -> RESOLVE -> ${specifier} from ${context.parentURL}`);
|
|
137
|
+
// }
|
|
138
|
+
//}
|
|
139
|
+
|
|
140
|
+
// this works for most of what we need to patch because they are not esm-native
|
|
141
|
+
// modules. but for esm-native modules, e.g., node-fetch, this won't work. they
|
|
142
|
+
// will need to be patched in the loader thread.
|
|
138
143
|
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
144
|
+
// We could walk up the parent chain to see if we should interpret this specifier
|
|
145
|
+
// as a module or commonjs, but it seems more straightforward to just always
|
|
146
|
+
// redirect. walking up the parent chain would let us avoid redirecting commonjs
|
|
147
|
+
// files. we could also call nextResolve() and let node tell us type of the file
|
|
148
|
+
// is but that would potentially create problems when other hooks, e.g., datadog,
|
|
149
|
+
// are in place.
|
|
150
|
+
//
|
|
151
|
+
// also we could check to see if this module's already been patched and skip this.
|
|
152
|
+
// not sure of that though; it might get loaded as a module, not a commonjs file,
|
|
153
|
+
// and that would be a problem. so when we start patching esm-native modules, we'll
|
|
154
|
+
// need a second "already patched" weakmap.
|
|
155
|
+
if (!isFlaggedToPatch && specifier in mappings) {
|
|
156
|
+
// eslint-disable-next-line prefer-const
|
|
157
|
+
let { url, format, target } = mappings[specifier];
|
|
158
|
+
// set flag to module or commonjs. i'm not sure this is needed but am keeping it
|
|
159
|
+
// in place until we have to implement esm-native module rewrites/wrapping. this
|
|
160
|
+
// is the point at which resolve() communicates to load().
|
|
161
|
+
//
|
|
162
|
+
// builtin's are probably the most likely to be loaded, so they're first.
|
|
163
|
+
// some tweaks might be needed when we start to patch esm-native modules
|
|
164
|
+
// in esm-native-code:
|
|
165
|
+
// https://nodejs.org/docs/latest-v20.x/api/esm.html#builtin-modules
|
|
166
|
+
// https://nodejs.org/docs/latest-v20.x/api/module.html#modulesyncbuiltinesmexports
|
|
167
|
+
if (target === 'builtin') {
|
|
168
|
+
url = `${url.href}?csi-flag=m`;
|
|
169
|
+
format = 'module';
|
|
170
|
+
} else if (target === 'commonjs') {
|
|
171
|
+
url = `${url.href}?csi-flag=c`;
|
|
172
|
+
format = getFileType(url) || format || 'commonjs';
|
|
173
|
+
} else if (target === 'module') {
|
|
174
|
+
url = `${url.href}?csi-flag=m`;
|
|
175
|
+
format = getFileType(url) || format || 'module';
|
|
176
|
+
} else {
|
|
177
|
+
throw new Error(`unexpected target ${target} for ${specifier}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
url,
|
|
182
|
+
format,
|
|
183
|
+
shortCircuit: true,
|
|
184
|
+
};
|
|
155
185
|
}
|
|
156
186
|
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
format,
|
|
160
|
-
shortCircuit: true,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return protectedNextResolve(nextResolve, specifier, context);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// readFile is a live binding. we need to capture it so it won't be
|
|
168
|
-
// altered by any patching later.
|
|
169
|
-
const readFile = rf;
|
|
170
|
-
|
|
171
|
-
const load = async function(url, context, nextLoad) {
|
|
172
|
-
(log & logLoad) && console.log(`ESM HOOK(${W.threadId}) -> LOAD ${url}`);
|
|
187
|
+
return protectedNextResolve(nextResolve, specifier, context);
|
|
188
|
+
};
|
|
173
189
|
|
|
174
|
-
|
|
190
|
+
// readFile is a live binding. we need to capture it so it won't be
|
|
191
|
+
// altered by any patching later.
|
|
192
|
+
const readFile = rf;
|
|
175
193
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// processing of some sort for esm-native modules.
|
|
179
|
-
// eslint-disable-next-line no-unused-vars
|
|
180
|
-
const targetType = urlObject.searchParams.get('csi-flag');
|
|
181
|
-
const { pathname } = urlObject;
|
|
194
|
+
load = async function(url, context, nextLoad) {
|
|
195
|
+
(log & logLoad) && console.log(`ESM HOOK(${W.threadId}) -> LOAD ${url}`);
|
|
182
196
|
|
|
183
|
-
|
|
197
|
+
const urlObject = new URL(url);
|
|
184
198
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
199
|
+
if (urlObject.searchParams.has('csi-flag')) {
|
|
200
|
+
// target type will, i think, be used to determine if this will require extra
|
|
201
|
+
// processing of some sort for esm-native modules.
|
|
202
|
+
// eslint-disable-next-line no-unused-vars
|
|
203
|
+
const targetType = urlObject.searchParams.get('csi-flag');
|
|
204
|
+
const { pathname } = urlObject;
|
|
192
205
|
|
|
193
|
-
|
|
194
|
-
const metaUrl = JSON.stringify(url);
|
|
195
|
-
const addon = urlObject.pathname;
|
|
196
|
-
return {
|
|
197
|
-
source: `require("node:module").createRequire(${metaUrl})("${addon}")`,
|
|
198
|
-
format: 'commonjs',
|
|
199
|
-
shortCircuit: true,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
206
|
+
(log & logLoad) && console.log(`ESM HOOK(${W.threadId}) -> LOAD -> CSI FLAG ${pathname}`);
|
|
202
207
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return nextLoad(url, context);
|
|
208
|
+
const source = await readFile(fixPath(pathname), 'utf8');
|
|
209
|
+
return {
|
|
210
|
+
source,
|
|
211
|
+
format: 'module',
|
|
212
|
+
shortCircuit: true,
|
|
213
|
+
};
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
core.threadInfo.post('rewrite', rewriteOptions);
|
|
223
|
-
|
|
224
|
-
const result = core.rewriter.rewrite(source, rewriteOptions);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
source: result.code,
|
|
228
|
-
format: type || 'commonjs', // don't know what else to do here. log?
|
|
229
|
-
shortCircuit: true,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
// this never gets called, so hmmm.
|
|
233
|
-
return nextLoad(url, context);
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// from esmock: git@github.com:iambumblehead/esmock.git
|
|
237
|
-
//
|
|
238
|
-
// new versions of node: when multiple loaders are used and context
|
|
239
|
-
// is passed to nextResolve, the process crashes in a recursive call
|
|
240
|
-
// see: /esmock/issues/#48
|
|
241
|
-
//
|
|
242
|
-
// old versions of node: if context.parentURL is defined, and context
|
|
243
|
-
// is not passed to nextResolve, the tests fail
|
|
244
|
-
//
|
|
245
|
-
// later versions of node v16 include 'node-addons'
|
|
246
|
-
async function protectedNextResolve(nextResolve, specifier, context) {
|
|
247
|
-
if (context.parentURL) {
|
|
248
|
-
if (context.conditions.at(-1) === 'node-addons' || context.importAssertions || isLT16_12) {
|
|
249
|
-
return nextResolve(specifier, context);
|
|
216
|
+
if (urlObject.pathname.endsWith('.node')) {
|
|
217
|
+
const metaUrl = JSON.stringify(url);
|
|
218
|
+
const addon = urlObject.pathname;
|
|
219
|
+
return {
|
|
220
|
+
source: `require("node:module").createRequire(${metaUrl})("${addon}")`,
|
|
221
|
+
format: 'commonjs',
|
|
222
|
+
shortCircuit: true,
|
|
223
|
+
};
|
|
250
224
|
}
|
|
251
|
-
}
|
|
252
225
|
|
|
253
|
-
|
|
226
|
+
// if it's not a builtin, a .node addon, or a flagged file, it needs to be
|
|
227
|
+
// rewritten if it's a module.
|
|
228
|
+
if (urlObject.pathname.match(/(.js|.mjs|.cjs)$/)) {
|
|
229
|
+
// if it's not a module it will be rewritten by the require hooks.
|
|
230
|
+
const type = getFileType(urlObject);
|
|
231
|
+
if (type !== 'module') {
|
|
232
|
+
return nextLoad(url, context);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const filename = fixPath(urlObject.pathname);
|
|
236
|
+
const source = await readFile(filename, 'utf8');
|
|
237
|
+
|
|
238
|
+
const rewriteOptions = {
|
|
239
|
+
filename,
|
|
240
|
+
isModule: type === 'module',
|
|
241
|
+
inject: true,
|
|
242
|
+
wrap: type !== 'module', // cannot wrap modules
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
core.threadInfo.post('rewrite', rewriteOptions);
|
|
246
|
+
|
|
247
|
+
const result = core.rewriter.rewrite(source, rewriteOptions);
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
source: result.code,
|
|
251
|
+
format: type || 'commonjs', // don't know what else to do here. log?
|
|
252
|
+
shortCircuit: true,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
// this never gets called, so hmmm.
|
|
256
|
+
return nextLoad(url, context);
|
|
257
|
+
};
|
|
254
258
|
}
|
|
255
259
|
|
|
256
260
|
export {
|
package/lib/esm-loader.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import Module from 'node:module';
|
|
17
17
|
import W from 'node:worker_threads';
|
|
18
18
|
import EventEmitter from 'node:events';
|
|
19
|
-
|
|
19
|
+
import * as hooks from './esm-hooks.mjs';
|
|
20
20
|
import checkImportVsLoaderVsNodeVersion from './check-flag-vs-node-version.mjs';
|
|
21
21
|
|
|
22
22
|
// might need to get exclude function here as opposed to deep within initialize
|
|
@@ -37,6 +37,7 @@ if (process.env.CSI_HOOKS_LOG) {
|
|
|
37
37
|
|
|
38
38
|
// verify that we're running with the correct flag for the version of node.
|
|
39
39
|
const { flag, msg } = checkImportVsLoaderVsNodeVersion();
|
|
40
|
+
|
|
40
41
|
if (msg) {
|
|
41
42
|
console.error(msg);
|
|
42
43
|
throw new Error(msg);
|
|
@@ -50,11 +51,11 @@ if (msg) {
|
|
|
50
51
|
// Module. I don't think this will be called more than once, but the flag is
|
|
51
52
|
// in place just in case.
|
|
52
53
|
//
|
|
53
|
-
let core;
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const core = (await import('./initialize.mjs')).default;
|
|
56
|
+
let load, resolve;
|
|
57
57
|
|
|
58
|
+
if (core) {
|
|
58
59
|
// most of this can be removed; it's here for debugging. but considering the relatively
|
|
59
60
|
// low cost compared with loading a module, i'm leaving it in.
|
|
60
61
|
(log & logRequireAll) && console.log('ESM-LOADER executing CJS Module patching');
|
|
@@ -85,75 +86,76 @@ if (!core) {
|
|
|
85
86
|
(log & logLoad) && console.log(`CJS(${W.threadId}) -> _load() ${request}`);
|
|
86
87
|
return originalLoad.call(this, request, parent, isMain);
|
|
87
88
|
};
|
|
88
|
-
}
|
|
89
89
|
|
|
90
|
-
core.threadInfo.syncEmitter = new EventEmitter();
|
|
91
|
-
// abstract how notifications are posted so that the non-loader
|
|
92
|
-
// code is not coupled to the implementation. the loader-thread
|
|
93
|
-
// complement of this is in esm-hooks.
|
|
94
|
-
//
|
|
95
|
-
// specifically, this is the main thread and post() directly emits
|
|
96
|
-
// the event while in the loader thread post() uses post.postMessage()
|
|
97
|
-
// to communicate back to this (the main) thread.
|
|
98
|
-
core.threadInfo.post = (type, data) => {
|
|
99
|
-
|
|
100
|
-
};
|
|
90
|
+
core.threadInfo.syncEmitter = new EventEmitter();
|
|
91
|
+
// abstract how notifications are posted so that the non-loader
|
|
92
|
+
// code is not coupled to the implementation. the loader-thread
|
|
93
|
+
// complement of this is in esm-hooks.
|
|
94
|
+
//
|
|
95
|
+
// specifically, this is the main thread and post() directly emits
|
|
96
|
+
// the event while in the loader thread post() uses post.postMessage()
|
|
97
|
+
// to communicate back to this (the main) thread.
|
|
98
|
+
core.threadInfo.post = (type, data) => {
|
|
99
|
+
core.threadInfo.syncEmitter.emit(type, data);
|
|
100
|
+
};
|
|
101
101
|
|
|
102
|
-
//
|
|
103
|
-
// setup ESM hooks
|
|
104
|
-
//
|
|
105
|
-
// if register exists this is 20.6.0 or later. we do not support
|
|
106
|
-
// node 20 prior to 20.6.0 (or 20.9.0 when 20 became LTS).
|
|
107
|
-
// news flash: backported register to node 18.19.0, so checking register
|
|
108
|
-
// is no longer enough.
|
|
109
|
-
//
|
|
110
|
-
if (Module.register && flag === '--import') {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
const { MessageChannel } = await import('node:worker_threads');
|
|
119
|
-
const { port1, port2 } = new MessageChannel();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
core.threadInfo.port = port1;
|
|
123
|
-
// messages received on the port are re-emitted as standard node events. the
|
|
124
|
-
// body must contain the type; data can be undefined.
|
|
125
|
-
core.threadInfo.missingTypeCount = 0;
|
|
126
|
-
core.threadInfo.port.on('message', (body) => {
|
|
127
|
-
const { type, data } = body;
|
|
128
|
-
if (type) {
|
|
129
|
-
core.threadInfo.syncEmitter.emit(type, data);
|
|
130
|
-
} else {
|
|
131
|
-
core.threadInfo.missingTypeCount += 1;
|
|
102
|
+
//
|
|
103
|
+
// setup ESM hooks
|
|
104
|
+
//
|
|
105
|
+
// if register exists this is 20.6.0 or later. we do not support
|
|
106
|
+
// node 20 prior to 20.6.0 (or 20.9.0 when 20 became LTS).
|
|
107
|
+
// news flash: backported register to node 18.19.0, so checking register
|
|
108
|
+
// is no longer enough.
|
|
109
|
+
//
|
|
110
|
+
if (Module.register && flag === '--import') {
|
|
111
|
+
// this file should never be executed in the loader thread; this file creates
|
|
112
|
+
// the loader thread by calling register(). the loader thread must create its
|
|
113
|
+
// own copy of threadInfo and insert it into core.
|
|
114
|
+
if (!core.threadInfo.isMainThread) {
|
|
115
|
+
// only get an error in CI on node v18.
|
|
116
|
+
throw new Error('esm-loader.mjs should not be executed in the loader thread');
|
|
132
117
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
type: Event.SERVER_SETTINGS_UPDATE,
|
|
149
|
-
...msg,
|
|
118
|
+
const { MessageChannel } = await import('node:worker_threads');
|
|
119
|
+
const { port1, port2 } = new MessageChannel();
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
core.threadInfo.port = port1;
|
|
123
|
+
// messages received on the port are re-emitted as standard node events. the
|
|
124
|
+
// body must contain the type; data can be undefined.
|
|
125
|
+
core.threadInfo.missingTypeCount = 0;
|
|
126
|
+
core.threadInfo.port.on('message', (body) => {
|
|
127
|
+
const { type, data } = body;
|
|
128
|
+
if (type) {
|
|
129
|
+
core.threadInfo.syncEmitter.emit(type, data);
|
|
130
|
+
} else {
|
|
131
|
+
core.threadInfo.missingTypeCount += 1;
|
|
132
|
+
}
|
|
150
133
|
});
|
|
151
|
-
|
|
134
|
+
core.threadInfo.port.unref();
|
|
135
|
+
|
|
136
|
+
// record the URL of the entry point.
|
|
137
|
+
core.threadInfo.url = import.meta.url;
|
|
138
|
+
|
|
139
|
+
// get relative URL
|
|
140
|
+
const url = new URL('./esm-hooks.mjs', import.meta.url);
|
|
141
|
+
await Module.register(url.href, import.meta.url, { data: { port: port2 }, transferList: [port2] });
|
|
142
|
+
|
|
143
|
+
// we only need to do this if there is a background thread.
|
|
144
|
+
// The esmHooks component of the main agent will send TS settings update to the loader agent via the port.
|
|
145
|
+
// To get the loader agent components to update we just need to forward the settings using `.messages` emitter.
|
|
146
|
+
core.messages.on(Event.SERVER_SETTINGS_UPDATE, (msg) => {
|
|
147
|
+
core.threadInfo.port.postMessage({
|
|
148
|
+
type: Event.SERVER_SETTINGS_UPDATE,
|
|
149
|
+
...msg,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// it's not possible to conditionally export, but exporting undefined
|
|
154
|
+
// values is close.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
const finalHooks = (Module.register && flag === '--import') ? {} : hooks;
|
|
158
|
+
({ load, resolve } = finalHooks);
|
|
152
159
|
}
|
|
153
160
|
|
|
154
|
-
// it's not possible to conditionally export, but exporting undefined
|
|
155
|
-
// values is close.
|
|
156
|
-
import * as hooks from './esm-hooks.mjs';
|
|
157
|
-
const finalHooks = (Module.register && flag === '--import') ? {} : hooks;
|
|
158
|
-
const { load, resolve } = finalHooks;
|
|
159
161
|
export { load, resolve };
|
package/lib/initialize.mjs
CHANGED
|
@@ -74,11 +74,11 @@ if (!core) {
|
|
|
74
74
|
|
|
75
75
|
core = await loadModules({ core, options: {} });
|
|
76
76
|
|
|
77
|
-
core = await startAgent({ core, options: { executor, installOrder } });
|
|
77
|
+
if (core) core = await startAgent({ core, options: { executor, installOrder } });
|
|
78
78
|
|
|
79
79
|
// self identification
|
|
80
80
|
// for communications between the main and loader thread (if present)
|
|
81
|
-
core.threadInfo = {
|
|
81
|
+
if (core) core.threadInfo = {
|
|
82
82
|
isMainThread,
|
|
83
83
|
threadId,
|
|
84
84
|
port: undefined, // filled in if there's a loader thread
|
|
@@ -89,10 +89,8 @@ if (!core) {
|
|
|
89
89
|
const coreKey = Symbol.for('contrast:core');
|
|
90
90
|
global[coreKey] = core;
|
|
91
91
|
}
|
|
92
|
-
|
|
93
92
|
}
|
|
94
93
|
|
|
95
|
-
|
|
96
94
|
export default core;
|
|
97
95
|
|
|
98
96
|
async function executor(core) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/agent",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "Assess and Protect agents for Node.js",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"test": "../scripts/test.sh"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@contrast/agentify": "1.
|
|
25
|
-
"@contrast/architecture-components": "1.
|
|
26
|
-
"@contrast/assess": "1.
|
|
27
|
-
"@contrast/library-analysis": "1.
|
|
28
|
-
"@contrast/protect": "1.
|
|
29
|
-
"@contrast/route-coverage": "1.
|
|
30
|
-
"@contrast/telemetry": "1.
|
|
24
|
+
"@contrast/agentify": "1.20.0",
|
|
25
|
+
"@contrast/architecture-components": "1.16.0",
|
|
26
|
+
"@contrast/assess": "1.24.0",
|
|
27
|
+
"@contrast/library-analysis": "1.17.0",
|
|
28
|
+
"@contrast/protect": "1.32.0",
|
|
29
|
+
"@contrast/route-coverage": "1.16.0",
|
|
30
|
+
"@contrast/telemetry": "1.4.0",
|
|
31
31
|
"semver": "^7.3.7"
|
|
32
32
|
}
|
|
33
33
|
}
|