UNPKG

@jsreport/jsreport-core

Version:
245 lines (199 loc) 7.75 kB
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