@jsreport/jsreport-core 4.3.0 → 4.4.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/README.md +10 -0
- package/lib/main/profiler.js +4 -0
- package/lib/main/reporter.js +6 -1
- package/lib/worker/render/executeEngine.js +136 -125
- package/lib/worker/reporter.js +6 -1
- package/lib/worker/sandbox/isolatedRequire.js +25 -17
- package/lib/worker/sandbox/requireSandbox.js +16 -3
- package/lib/worker/sandbox/runInSandbox.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -282,6 +282,16 @@ jsreport.documentStore.collection('templates')
|
|
|
282
282
|
|
|
283
283
|
## Changelog
|
|
284
284
|
|
|
285
|
+
### 4.4.0
|
|
286
|
+
|
|
287
|
+
- trigger async report using header to avoid allocating worker
|
|
288
|
+
- fix require('..') and require('.') in sandbox
|
|
289
|
+
- fix component rendering in loop with async work on helpers
|
|
290
|
+
|
|
291
|
+
### 4.3.1
|
|
292
|
+
|
|
293
|
+
- fix `waitForAsyncHelper`, `waitForAsyncHelpers` not working with trustUserCode: true
|
|
294
|
+
|
|
285
295
|
### 4.3.0
|
|
286
296
|
|
|
287
297
|
- expose safe properties of `req.context.user` in sandbox
|
package/lib/main/profiler.js
CHANGED
|
@@ -159,6 +159,10 @@ module.exports = (reporter) => {
|
|
|
159
159
|
reporter.beforeRenderWorkerAllocatedListeners.add('profiler', async (req) => {
|
|
160
160
|
req.context.profiling = req.context.profiling || {}
|
|
161
161
|
|
|
162
|
+
if (req.context.profiling.enabled === false) {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
162
166
|
if (req.context.profiling.mode == null) {
|
|
163
167
|
const profilerSettings = await reporter.settings.findValue('profiler', req)
|
|
164
168
|
const defaultMode = reporter.options.profiler.defaultMode || 'standard'
|
package/lib/main/reporter.js
CHANGED
|
@@ -382,7 +382,12 @@ class MainReporter extends Reporter {
|
|
|
382
382
|
const res = Response(this, req.context.id)
|
|
383
383
|
|
|
384
384
|
try {
|
|
385
|
-
await this.beforeRenderWorkerAllocatedListeners.fire(req)
|
|
385
|
+
await this.beforeRenderWorkerAllocatedListeners.fire(req, res)
|
|
386
|
+
if (req.context.clientNotification) {
|
|
387
|
+
const r = req.context.clientNotification
|
|
388
|
+
delete req.context.clientNotification
|
|
389
|
+
return r
|
|
390
|
+
}
|
|
386
391
|
|
|
387
392
|
worker = await this._workersManager.allocate(req, {
|
|
388
393
|
timeout: this.getReportTimeout(req)
|
|
@@ -14,7 +14,7 @@ module.exports = (reporter) => {
|
|
|
14
14
|
|
|
15
15
|
reporter.templatingEngines = { cache: templatesCache }
|
|
16
16
|
|
|
17
|
-
const contextExecutionChainMap = new
|
|
17
|
+
const contextExecutionChainMap = new Map()
|
|
18
18
|
const executionFnParsedParamsMap = new Map()
|
|
19
19
|
const executionAsyncResultsMap = new Map()
|
|
20
20
|
const executionAsyncCallChainMap = new Map()
|
|
@@ -69,7 +69,7 @@ module.exports = (reporter) => {
|
|
|
69
69
|
return templatingEnginesEvaluate(false, executionInfo, entityInfo, req)
|
|
70
70
|
},
|
|
71
71
|
waitForAsyncHelper: async (maybeAsyncContent) => {
|
|
72
|
-
const executionChain = contextExecutionChainMap.get(context) || []
|
|
72
|
+
const executionChain = contextExecutionChainMap.get(context.__sandboxId) || []
|
|
73
73
|
const executionId = executionChain[executionChain.length - 1]
|
|
74
74
|
|
|
75
75
|
if (
|
|
@@ -99,7 +99,7 @@ module.exports = (reporter) => {
|
|
|
99
99
|
return content
|
|
100
100
|
},
|
|
101
101
|
waitForAsyncHelpers: async () => {
|
|
102
|
-
const executionChain = contextExecutionChainMap.get(context) || []
|
|
102
|
+
const executionChain = contextExecutionChainMap.get(context.__sandboxId) || []
|
|
103
103
|
const executionId = executionChain[executionChain.length - 1]
|
|
104
104
|
|
|
105
105
|
if (executionId != null && executionAsyncResultsMap.has(executionId)) {
|
|
@@ -115,7 +115,7 @@ module.exports = (reporter) => {
|
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
addFinishListener: (fn) => {
|
|
118
|
-
const executionChain = contextExecutionChainMap.get(context) || []
|
|
118
|
+
const executionChain = contextExecutionChainMap.get(context.__sandboxId) || []
|
|
119
119
|
const executionId = executionChain[executionChain.length - 1]
|
|
120
120
|
|
|
121
121
|
if (executionId && executionFinishListenersMap.has(executionId)) {
|
|
@@ -123,7 +123,7 @@ module.exports = (reporter) => {
|
|
|
123
123
|
}
|
|
124
124
|
},
|
|
125
125
|
createAsyncHelperResult: (v) => {
|
|
126
|
-
const executionChain = contextExecutionChainMap.get(context) || []
|
|
126
|
+
const executionChain = contextExecutionChainMap.get(context.__sandboxId) || []
|
|
127
127
|
const executionId = executionChain[executionChain.length - 1]
|
|
128
128
|
|
|
129
129
|
const asyncResultMap = executionAsyncResultsMap.get(executionId)
|
|
@@ -170,6 +170,7 @@ module.exports = (reporter) => {
|
|
|
170
170
|
|
|
171
171
|
const normalizedHelpers = `${helpers || ''}`
|
|
172
172
|
const executionFnParsedParamsKey = `entity:${entity.shortid || 'anonymous'}:helpers:${normalizedHelpers}`
|
|
173
|
+
let sandboxId
|
|
173
174
|
|
|
174
175
|
const initFn = async (getTopLevelFunctions, compileScript) => {
|
|
175
176
|
if (systemHelpersCache != null) {
|
|
@@ -213,112 +214,118 @@ module.exports = (reporter) => {
|
|
|
213
214
|
}
|
|
214
215
|
|
|
215
216
|
const executionFn = async ({ require, console, topLevelFunctions, context }) => {
|
|
217
|
+
sandboxId = context.__sandboxId
|
|
216
218
|
const asyncResultMap = new Map()
|
|
217
219
|
const asyncCallChainSet = new Set()
|
|
218
220
|
|
|
219
|
-
if (!contextExecutionChainMap.has(
|
|
220
|
-
contextExecutionChainMap.set(
|
|
221
|
+
if (!contextExecutionChainMap.has(sandboxId)) {
|
|
222
|
+
contextExecutionChainMap.set(sandboxId, [])
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
contextExecutionChainMap.get(
|
|
225
|
+
contextExecutionChainMap.get(sandboxId).push(executionId)
|
|
224
226
|
|
|
225
227
|
executionAsyncResultsMap.set(executionId, asyncResultMap)
|
|
226
228
|
executionAsyncCallChainMap.set(executionId, asyncCallChainSet)
|
|
227
229
|
executionFinishListenersMap.set(executionId, reporter.createListenerCollection())
|
|
228
230
|
executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
|
|
229
231
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
e
|
|
239
|
-
|
|
232
|
+
try {
|
|
233
|
+
const key = engine.buildTemplateCacheKey
|
|
234
|
+
? engine.buildTemplateCacheKey({ content }, req)
|
|
235
|
+
: `template:${content}:${engine.name}`
|
|
236
|
+
|
|
237
|
+
if (!templatesCache.has(key)) {
|
|
238
|
+
try {
|
|
239
|
+
templatesCache.set(key, engine.compile(content, { require }))
|
|
240
|
+
} catch (e) {
|
|
241
|
+
e.property = 'content'
|
|
242
|
+
throw e
|
|
243
|
+
}
|
|
240
244
|
}
|
|
241
|
-
}
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
const compiledTemplate = templatesCache.get(key)
|
|
247
|
+
const wrappedTopLevelFunctions = {}
|
|
245
248
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
+
for (const h of Object.keys(topLevelFunctions)) {
|
|
250
|
+
// extra wrapping for enhance the error with the helper name
|
|
251
|
+
wrappedTopLevelFunctions[h] = wrapHelperForHelperNameWhenError(topLevelFunctions[h], h, () => executionFnParsedParamsMap.has(req.context.id))
|
|
249
252
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
|
|
254
|
+
wrappedTopLevelFunctions[h] = engine.wrapHelper(wrappedTopLevelFunctions[h], { context })
|
|
255
|
+
} else {
|
|
256
|
+
wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], asyncResultMap, asyncCallChainSet)
|
|
257
|
+
}
|
|
254
258
|
}
|
|
255
|
-
}
|
|
256
259
|
|
|
257
|
-
|
|
260
|
+
let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
|
|
258
261
|
|
|
259
|
-
|
|
262
|
+
const resolvedResultsMap = new Map()
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
// we need to use the cloned map, because there can be a waitForAsyncHelper pending that needs the asyncResultMap values
|
|
265
|
+
let clonedMap = new Map(asyncResultMap)
|
|
263
266
|
|
|
264
|
-
|
|
265
|
-
|
|
267
|
+
while (clonedMap.size > 0) {
|
|
268
|
+
const keysEvaluated = [...clonedMap.keys()]
|
|
266
269
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
270
|
+
await Promise.all(keysEvaluated.map(async (k) => {
|
|
271
|
+
const result = await clonedMap.get(k)
|
|
272
|
+
asyncCallChainSet.delete(k)
|
|
273
|
+
resolvedResultsMap.set(k, `${result}`)
|
|
274
|
+
clonedMap.delete(k)
|
|
275
|
+
}))
|
|
273
276
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// we want to process the new generated pending async results
|
|
281
|
-
if (asyncResultMap.size > 0) {
|
|
282
|
-
clonedMap = new Map(asyncResultMap)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
277
|
+
// we need to remove the keys processed from the original map at this point
|
|
278
|
+
// (after the await) because during the async work the asyncResultMap will be read
|
|
279
|
+
for (const k of keysEvaluated) {
|
|
280
|
+
asyncResultMap.delete(k)
|
|
281
|
+
}
|
|
285
282
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// this can happen if a child jsreport.templatingEngines.evaluate receives an async value from outer scope
|
|
290
|
-
// because every evaluate uses a unique map of async results
|
|
291
|
-
// example is the case when component receives as a value async thing
|
|
292
|
-
// instead of returning "undefined" we let the outer eval to do the replace
|
|
293
|
-
if (!resolvedResultsMap.has(asyncResultId)) {
|
|
294
|
-
// returning asyncUnresolvedHelperResult just to avoid endless loop, after replace we put it back to asyncHelperResult
|
|
295
|
-
return `{#asyncUnresolvedHelperResult ${asyncResultId}}`
|
|
283
|
+
// we want to process the new generated pending async results
|
|
284
|
+
if (asyncResultMap.size > 0) {
|
|
285
|
+
clonedMap = new Map(asyncResultMap)
|
|
296
286
|
}
|
|
297
|
-
|
|
298
|
-
})
|
|
299
|
-
}
|
|
287
|
+
}
|
|
300
288
|
|
|
301
|
-
|
|
289
|
+
while (contentResult.includes('{#asyncHelperResult')) {
|
|
290
|
+
contentResult = contentResult.replace(/{#asyncHelperResult ([^{}]+)}/g, (str, p1) => {
|
|
291
|
+
const asyncResultId = p1
|
|
292
|
+
// this can happen if a child jsreport.templatingEngines.evaluate receives an async value from outer scope
|
|
293
|
+
// because every evaluate uses a unique map of async results
|
|
294
|
+
// example is the case when component receives as a value async thing
|
|
295
|
+
// instead of returning "undefined" we let the outer eval to do the replace
|
|
296
|
+
if (!resolvedResultsMap.has(asyncResultId)) {
|
|
297
|
+
// returning asyncUnresolvedHelperResult just to avoid endless loop, after replace we put it back to asyncHelperResult
|
|
298
|
+
return `{#asyncUnresolvedHelperResult ${asyncResultId}}`
|
|
299
|
+
}
|
|
300
|
+
return `${resolvedResultsMap.get(asyncResultId)}`
|
|
301
|
+
})
|
|
302
|
+
}
|
|
302
303
|
|
|
303
|
-
|
|
304
|
+
contentResult = contentResult.replace(/asyncUnresolvedHelperResult/g, 'asyncHelperResult')
|
|
304
305
|
|
|
305
|
-
|
|
306
|
+
await executionFinishListenersMap.get(executionId).fire()
|
|
306
307
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
308
|
+
return {
|
|
309
|
+
// handlebars escapes single brackets before execution to prevent errors on {#asset}
|
|
310
|
+
// we need to unescape them later here, because at the moment the engine.execute finishes
|
|
311
|
+
// the async helpers aren't executed yet
|
|
312
|
+
content: engine.unescape ? engine.unescape(contentResult) : contentResult
|
|
313
|
+
}
|
|
314
|
+
} finally {
|
|
315
|
+
// ensure we clean the execution from the chain always, even on errors
|
|
316
|
+
contextExecutionChainMap.set(sandboxId, contextExecutionChainMap.get(sandboxId).filter((id) => id !== executionId))
|
|
312
317
|
}
|
|
313
318
|
}
|
|
314
319
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
320
|
+
try {
|
|
321
|
+
// executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
|
|
322
|
+
// we store there for the particular request and component a promise and only the first component gets compiled
|
|
323
|
+
if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
|
|
324
|
+
const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
|
|
325
|
+
|
|
326
|
+
return await executionFn({ require, console, topLevelFunctions, context })
|
|
327
|
+
}
|
|
319
328
|
|
|
320
|
-
return executionFn({ require, console, topLevelFunctions, context })
|
|
321
|
-
} else {
|
|
322
329
|
const awaiter = {}
|
|
323
330
|
|
|
324
331
|
awaiter.promise = new Promise((resolve) => {
|
|
@@ -326,67 +333,71 @@ module.exports = (reporter) => {
|
|
|
326
333
|
})
|
|
327
334
|
|
|
328
335
|
executionFnParsedParamsMap.get(req.context.id).set(executionFnParsedParamsKey, awaiter)
|
|
329
|
-
}
|
|
330
336
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
337
|
+
if (reporter.options.sandbox.cache && reporter.options.sandbox.cache.enabled === false) {
|
|
338
|
+
templatesCache.reset()
|
|
339
|
+
}
|
|
334
340
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
341
|
+
try {
|
|
342
|
+
return await reporter.runInSandbox({
|
|
343
|
+
context: {
|
|
344
|
+
...(engine.createContext ? engine.createContext(req) : {})
|
|
345
|
+
},
|
|
346
|
+
userCode: normalizedHelpers,
|
|
347
|
+
initFn,
|
|
348
|
+
executionFn,
|
|
349
|
+
currentPath: entityPath,
|
|
350
|
+
onRequire: (moduleName, { context }) => {
|
|
351
|
+
if (engine.onRequire) {
|
|
352
|
+
return engine.onRequire(moduleName, { context })
|
|
353
|
+
}
|
|
347
354
|
}
|
|
355
|
+
}, req)
|
|
356
|
+
} catch (e) {
|
|
357
|
+
if (!handleErrors) {
|
|
358
|
+
throw e
|
|
348
359
|
}
|
|
349
|
-
}, req)
|
|
350
|
-
} catch (e) {
|
|
351
|
-
if (!handleErrors) {
|
|
352
|
-
throw e
|
|
353
|
-
}
|
|
354
360
|
|
|
355
|
-
|
|
361
|
+
const nestedErrorWithEntity = e.entity != null
|
|
356
362
|
|
|
357
|
-
|
|
363
|
+
const templatePath = req.template._id ? await reporter.folders.resolveEntityPath(req.template, 'templates', req) : 'anonymous'
|
|
358
364
|
|
|
359
|
-
|
|
365
|
+
const newError = reporter.createError(`Error when evaluating engine ${engine.name} for template ${templatePath}`, { original: e })
|
|
360
366
|
|
|
361
|
-
|
|
362
|
-
|
|
367
|
+
if (templatePath !== 'anonymous' && !nestedErrorWithEntity) {
|
|
368
|
+
const templateFound = await reporter.folders.resolveEntityFromPath(templatePath, 'templates', req)
|
|
363
369
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
370
|
+
if (templateFound != null) {
|
|
371
|
+
newError.entity = {
|
|
372
|
+
shortid: templateFound.entity.shortid,
|
|
373
|
+
name: templateFound.entity.name,
|
|
374
|
+
content
|
|
375
|
+
}
|
|
369
376
|
}
|
|
370
377
|
}
|
|
371
|
-
}
|
|
372
378
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
if (!nestedErrorWithEntity && e.property !== 'content') {
|
|
380
|
+
newError.property = 'helpers'
|
|
381
|
+
}
|
|
376
382
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
383
|
+
if (nestedErrorWithEntity) {
|
|
384
|
+
// errors from nested assets evals needs an unwrap for some reason
|
|
385
|
+
newError.entity = { ...e.entity }
|
|
386
|
+
}
|
|
381
387
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
+
// we remove the decoratedSuffix (created from sandbox) from the stack trace (if it is there) because it
|
|
389
|
+
// just creates noise and duplication when printing the error,
|
|
390
|
+
// we just want the decoration on the message not in the stack trace
|
|
391
|
+
if (e.decoratedSuffix != null && newError.stack.includes(e.decoratedSuffix)) {
|
|
392
|
+
newError.stack = newError.stack.replace(e.decoratedSuffix, '')
|
|
393
|
+
}
|
|
388
394
|
|
|
389
|
-
|
|
395
|
+
throw newError
|
|
396
|
+
}
|
|
397
|
+
} finally {
|
|
398
|
+
if (sandboxId != null && contextExecutionChainMap.get(sandboxId)?.length === 0) {
|
|
399
|
+
contextExecutionChainMap.delete(sandboxId)
|
|
400
|
+
}
|
|
390
401
|
}
|
|
391
402
|
}
|
|
392
403
|
|
package/lib/worker/reporter.js
CHANGED
|
@@ -88,7 +88,7 @@ class WorkerReporter extends Reporter {
|
|
|
88
88
|
require('@jsreport/ses')
|
|
89
89
|
|
|
90
90
|
// eslint-disable-next-line
|
|
91
|
-
|
|
91
|
+
repairIntrinsics({
|
|
92
92
|
// don't change locale based methods which users may be using in their templates
|
|
93
93
|
localeTaming: 'unsafe',
|
|
94
94
|
errorTaming: 'unsafe',
|
|
@@ -108,6 +108,8 @@ class WorkerReporter extends Reporter {
|
|
|
108
108
|
overrideTaming: 'severe'
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
+
// NOTE: we need to add these overrides between repairIntrinsics and hardenIntrinsics
|
|
112
|
+
// for them to be valid.
|
|
111
113
|
// in this mode we alias the unsafe methods to safe ones
|
|
112
114
|
Buffer.allocUnsafe = function allocUnsafe (size) {
|
|
113
115
|
return Buffer.alloc(size)
|
|
@@ -117,6 +119,9 @@ class WorkerReporter extends Reporter {
|
|
|
117
119
|
return Buffer.alloc(size)
|
|
118
120
|
}
|
|
119
121
|
|
|
122
|
+
// eslint-disable-next-line
|
|
123
|
+
hardenIntrinsics()
|
|
124
|
+
|
|
120
125
|
// we also harden Buffer because we expose it to sandbox
|
|
121
126
|
// eslint-disable-next-line
|
|
122
127
|
harden(Buffer)
|
|
@@ -12,7 +12,7 @@ const ISOLATED_PACKAGE_JSON_CACHE = new Map()
|
|
|
12
12
|
// to bring isolated modules across renders and without memory leaks.
|
|
13
13
|
// most of the code is copied from node.js source code and adapted a bit
|
|
14
14
|
// (you will see in some parts specific links to node.js source code counterpart for reference)
|
|
15
|
-
function isolatedRequire (_moduleId, modulesMeta,
|
|
15
|
+
function isolatedRequire (_moduleId, modulesMeta, resolveModule) {
|
|
16
16
|
const parentModule = typeof _moduleId !== 'string' ? _moduleId.parent : null
|
|
17
17
|
const moduleId = parentModule ? _moduleId.moduleId : _moduleId
|
|
18
18
|
|
|
@@ -32,10 +32,14 @@ function isolatedRequire (_moduleId, modulesMeta, requireFromRootDirectory) {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const { rootModule, modulesCache, requireExtensions } = modulesMeta
|
|
35
|
-
|
|
36
|
-
const fullModulePath = resolveFilename(ISOLATED_REQUIRE_RESOLVE_CACHE, requireFromRootDirectory.resolve, moduleId, { parentModulePath: parentModule?.path })
|
|
35
|
+
const fullModulePath = resolveFilename(ISOLATED_REQUIRE_RESOLVE_CACHE, resolveModule, moduleId, { parentModulePath: parentModule?.path })
|
|
37
36
|
|
|
38
37
|
if (modulesCache[fullModulePath]) {
|
|
38
|
+
// if module was already tried to be loaded and ended with error we rethrow the error
|
|
39
|
+
if (modulesCache[fullModulePath].loadingError != null) {
|
|
40
|
+
throw modulesCache[fullModulePath].loadingError
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
return modulesCache[fullModulePath].exports
|
|
40
44
|
}
|
|
41
45
|
|
|
@@ -50,29 +54,33 @@ function isolatedRequire (_moduleId, modulesMeta, requireFromRootDirectory) {
|
|
|
50
54
|
// https://github.com/nodejs/node/blob/v18.14.2/lib/internal/modules/cjs/loader.js#L1133
|
|
51
55
|
// we can not add this to the IsolatedModule.prototype because we need access to other variables
|
|
52
56
|
mod.require = function (id) {
|
|
53
|
-
return isolatedRequire({ parent: this, moduleId: id }, modulesMeta,
|
|
57
|
+
return isolatedRequire({ parent: this, moduleId: id }, modulesMeta, resolveModule)
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
modulesCache[fullModulePath] = mod
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
try {
|
|
63
|
+
mod.filename = fullModulePath
|
|
64
|
+
mod.paths = Module._nodeModulePaths(path.dirname(fullModulePath))
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
const extension = findLongestRegisteredExtension(fullModulePath, requireExtensions)
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const moduleResolver = requireExtensions[extension]
|
|
68
|
+
// https://github.com/nodejs/node/blob/v18.14.2/lib/internal/modules/cjs/loader.js#L1113
|
|
69
|
+
// allow .mjs to be overridden
|
|
70
|
+
if (fullModulePath.endsWith('.mjs') && !requireExtensions['.mjs']) {
|
|
71
|
+
throw createRequireESMError(fullModulePath)
|
|
72
|
+
}
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
const moduleResolver = requireExtensions[extension]
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
moduleResolver(mod, fullModulePath)
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
mod.loaded = true
|
|
79
|
+
return mod.exports
|
|
80
|
+
} catch (error) {
|
|
81
|
+
mod.loadingError = error
|
|
82
|
+
throw error
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
function setDefaultRequireExtensions (currentExtensions, requireFromRootDirectory, compileScript) {
|
|
@@ -80,11 +80,24 @@ function doRequire (moduleId, requireFromRootDirectory, _requirePaths, modulesMe
|
|
|
80
80
|
const _require = isolateModules ? isolatedRequire : requireFromRootDirectory
|
|
81
81
|
const extraRequireParams = []
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const resolveModule = (mId, ...args) => {
|
|
84
|
+
let normalizedModuleId = mId
|
|
85
|
+
|
|
86
|
+
if (normalizedModuleId === '..' || normalizedModuleId === '.') {
|
|
87
|
+
// NOTE: we need to manually normalize because node has a bug
|
|
88
|
+
// https://github.com/nodejs/node/issues/47000
|
|
89
|
+
// when using require.resolve and using options.paths, it does not recognize for "..", "."
|
|
90
|
+
// to be relative, just other cases work like "../", "..\\",
|
|
91
|
+
// so when we detect this case we normalize it in order for node to resolve correctly
|
|
92
|
+
normalizedModuleId = normalizedModuleId + path.sep
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return requireFromRootDirectory.resolve(normalizedModuleId, ...args)
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
if (isolateModules) {
|
|
99
|
+
extraRequireParams.push(modulesMeta, resolveModule)
|
|
100
|
+
}
|
|
88
101
|
|
|
89
102
|
let result = executeRequire(_require, resolveModule, moduleId, searchedPaths, ...extraRequireParams)
|
|
90
103
|
|
|
@@ -25,6 +25,9 @@ module.exports = function createRunInSandbox (reporter) {
|
|
|
25
25
|
// it may turn out it is a bad approach in assets so we gonna delete it here
|
|
26
26
|
const executionFnName = `${nanoid()}_executionFn`
|
|
27
27
|
|
|
28
|
+
// creating new id different than execution to ensure user code can not get access to
|
|
29
|
+
// internal functions by using the __sandboxId
|
|
30
|
+
context.__sandboxId = nanoid()
|
|
28
31
|
context[executionFnName] = executionFn
|
|
29
32
|
context.__appDirectory = reporter.options.appDirectory
|
|
30
33
|
context.__rootDirectory = reporter.options.rootDirectory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsreport/jsreport-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "javascript based business reporting",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"report",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@jsreport/mingo": "2.4.1",
|
|
45
45
|
"@jsreport/reap": "0.1.0",
|
|
46
46
|
"@jsreport/serializator": "1.0.0",
|
|
47
|
-
"@jsreport/ses": "1.0
|
|
47
|
+
"@jsreport/ses": "1.1.0",
|
|
48
48
|
"ajv": "6.12.6",
|
|
49
49
|
"app-root-path": "3.0.0",
|
|
50
50
|
"bytes": "3.1.2",
|