@jsreport/jsreport-core
Version:
javascript based business reporting
245 lines (199 loc) • 7.75 kB
JavaScript
const ExtensionsManager = require('./extensionsManager')
const DocumentStore = require('./documentStore')
const Templates = require('./templates')
const createLogger = require('./logger')
const runInSandbox = require('./sandbox/runInSandbox')
const createNoneEngine = require('./render/noneEngine')
const htmlRecipe = require('./render/htmlRecipe')
const defaultProxyExtend = require('./defaultProxyExtend')
const Reporter = require('../shared/reporter')
const BlobStorage = require('./blobStorage.js')
const Render = require('./render/render')
const Profiler = require('./render/profiler.js')
const engineStream = require('./render/engineStream.js')
class WorkerReporter extends Reporter {
constructor (workerData, executeMain) {
const { options, documentStore, extensionsDefs, workerId } = workerData
super(options)
this.workerId = workerId
this._executeMain = executeMain
this._initialized = false
this._lockedDown = false
this._documentStoreData = documentStore
this._requestContextMetaConfigCollection = new Map()
this._proxyRegistrationFns = []
this.requestModulesCache = new Map()
this._workerActions = new Map()
this._registerRenderAction()
this.registerHelpersListeners = this.createListenerCollection('registerHelpers')
this.afterTemplatingEnginesExecutedListeners = this.createListenerCollection('afterTemplatingEnginesExecuted')
this.validateRenderListeners = this.createListenerCollection('validateRender')
this.extensionsManager = ExtensionsManager(this, extensionsDefs)
this.extendProxy((proxy, req) => defaultProxyExtend(this)(proxy, req))
this.beforeMainActionListeners = this.createListenerCollection('beforeMainAction')
}
async init () {
if (this._initialized === true) {
throw new Error('jsreport already initialized. Make sure init is called only once')
}
super.init()
Templates(this)
this.profiler = Profiler(this)
this.logger = createLogger(this.profiler)
this._render = Render(this)
await this.extensionsManager.init()
this.documentStore = DocumentStore(this._documentStoreData, this.executeMainAction.bind(this))
this.blobStorage = BlobStorage(this.executeMainAction.bind(this), { writeTempFile: this.writeTempFile.bind(this), readTempFile: this.readTempFile.bind(this) })
this.addRequestContextMetaConfig('rootId', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('id', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('reportCounter', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('startTimestamp', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('logs', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('isChildRequest', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('originalInputDataIsEmpty', { sandboxReadOnly: true })
this.addRequestContextMetaConfig('skipModificationDateUpdate', { sandboxHidden: true })
this._runInSandbox = runInSandbox(this)
const { compile: compileNone, execute: executeNone } = createNoneEngine()
this.extensionsManager.engines.push({
name: 'none',
compile: compileNone,
execute: executeNone
})
this.extensionsManager.recipes.push({
name: 'html',
execute: htmlRecipe
})
engineStream(this)
await this.initializeListeners.fire()
if (!this._lockedDown && this.options.trustUserCode === false) {
require('@jsreport/ses')
// eslint-disable-next-line
repairIntrinsics({
// don't change locale based methods which users may be using in their templates
localeTaming: 'unsafe',
errorTaming: 'unsafe',
stackFiltering: 'verbose',
/*
FROM SES DOCS
The 'severe' setting enables all the properties on at least Object.prototype, which is sometimes needed for compatibility with code generated by rollup or webpack.
However, this extra compatibility comes at the price of a miserable debugging experience.
We need this to make jsrender working, which overrides constructor.
In case we need to put back default, we will need to fork jsrender and change the following line
(Tag.prototype = compiledDef).constructor = compiledDef._ctr = Tag;
x
Tag.prototype = compiledDef
compiledDef._ctr = Tag
*/
overrideTaming: 'severe'
})
// NOTE: we need to add these overrides between repairIntrinsics and hardenIntrinsics
// for them to be valid.
// in this mode we alias the unsafe methods to safe ones
Buffer.allocUnsafe = function allocUnsafe (size) {
return Buffer.alloc(size)
}
Buffer.allocUnsafeSlow = function allocUnsafeSlow (size) {
return Buffer.alloc(size)
}
// eslint-disable-next-line
hardenIntrinsics()
// we also harden Buffer because we expose it to sandbox
// eslint-disable-next-line
harden(Buffer)
// we need to expose Intl to sandbox
// eslint-disable-next-line
harden(Intl)
this._lockedDown = true
}
this._initialized = true
}
/**
* @public
*/
addRequestContextMetaConfig (property, options) {
this._requestContextMetaConfigCollection.set(property, options)
}
/**
* @public
*/
getRequestContextMetaConfig (property) {
if (property === undefined) {
const all = {}
for (const [key, value] of this._requestContextMetaConfigCollection.entries()) {
all[key] = value
}
return all
}
return this._requestContextMetaConfigCollection.get(property)
}
extendProxy (registrationFn) {
this._proxyRegistrationFns.push(registrationFn)
}
createProxy ({ req, runInSandbox, context, getTopLevelFunctions, sandboxRequire }) {
const proxyInstance = {}
for (const fn of this._proxyRegistrationFns) {
fn(proxyInstance, req, {
runInSandbox,
context,
getTopLevelFunctions,
sandboxRequire
})
}
return proxyInstance
}
async render (req, parentReq) {
return await this._render(req, parentReq)
}
async executeMainAction (actionName, data, req) {
await this.beforeMainActionListeners.fire(actionName, data, req)
return this._executeMain(actionName, data, req)
}
async runInSandbox ({
manager,
context,
userCode,
initFn,
executionFn,
currentPath,
onRequire,
propertiesConfig,
errorLineNumberOffset
}, req) {
// we flush before running code in sandbox because it can potentially
// include code that blocks the whole process (like `while (true) {}`) and we
// want to ensure that the batched messages are flushed before trying to execute the code
await this.profiler.flush(req.context.rootId)
return this._runInSandbox({
manager,
context,
userCode,
initFn,
executionFn,
currentPath,
onRequire,
propertiesConfig,
errorLineNumberOffset
}, req)
}
registerWorkerAction (actionName, fn) {
this._workerActions.set(actionName, fn)
}
async executeWorkerAction (actionName, data, req) {
const action = this._workerActions.get(actionName)
if (!action) {
throw new Error(`Worker action ${actionName} not registered`)
}
return action(data, req)
}
_registerRenderAction () {
this.registerWorkerAction('render', async (data, req) => {
const response = await this._render(req)
return response.serialize()
})
}
async close () {
this.logger.debug('Closing jsreport worker')
return this.closeListeners.fire()
}
}
module.exports = WorkerReporter