Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/jsreport/jsreport
Browse files Browse the repository at this point in the history
  • Loading branch information
pofider committed Oct 31, 2023
2 parents 056a52c + d615661 commit 82832d1
Show file tree
Hide file tree
Showing 207 changed files with 5,255 additions and 4,372 deletions.
8 changes: 7 additions & 1 deletion packages/advanced-workers/lib/pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,24 @@ module.exports = ({
}
}

let timeoutId

return new Promise((resolve, reject) => {
const task = { resolve, reject }
this.tasksQueue.push(task)
if (opts && opts.timeout) {
setTimeout(() => {
timeoutId = setTimeout(() => {
const taskIndex = this.tasksQueue.indexOf(task)
if (taskIndex !== -1) {
this.tasksQueue.splice(taskIndex, 1)
task.reject(new Error('Timeout when waiting for worker'))
}
}, opts.timeout).unref()
}
}).finally(() => {
if (timeoutId != null) {
clearTimeout(timeoutId)
}
})
},

Expand Down
45 changes: 38 additions & 7 deletions packages/advanced-workers/lib/threadWorker.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const serializator = require('@jsreport/serializator')

function asyncAwaiter (id) {
const awaiter = {
isResolved: false
}
const awaiter = {}
awaiter.promise = new Promise((resolve, reject) => {
awaiter.resolve = (v) => {
awaiter.isSettled = true
Expand All @@ -27,16 +25,19 @@ module.exports = ({
let closingWhenWaitingForMainExecution

worker.on('message', (m) => {
currentAsyncAwaiter.resolve(m)
if (currentAsyncAwaiter) {
currentAsyncAwaiter.resolve(m)
}
})

function postAndWait (m, { executeMain, timeout }) {
let timeoutId
worker.ref()
// eslint-disable-next-line
return new Promise(async (resolve, reject) => {
let isDone = false
if (timeout) {
setTimeout(() => {
timeoutId = setTimeout(() => {
if (!isDone) {
isDone = true
const e = new Error(`Timeout occurred when waiting for the worker${m.systemAction != null ? ` action "${m.systemAction}"` : ''}`)
Expand Down Expand Up @@ -102,15 +103,34 @@ module.exports = ({
}
}
}
}).finally(() => {
if (timeoutId != null) {
clearTimeout(timeoutId)
}

// we only clear the awaiter here for the cases in which
// we are sure there is not work pending (timeouts, top level errors on worker),
// for the rest we let other logic to run and clear the awaiter
// at specific places
if (currentAsyncAwaiter && currentAsyncAwaiter.isSettled) {
// cleanup to avoid a hanging promise
currentAsyncAwaiter = null
}
})
}

let closingAwaiter
let closingTimeoutId
let exited = false

worker.on('exit', (exitCode) => {
exited = true
if (closingAwaiter && !closingAwaiter.isSettled) {
closingAwaiter.resolve()
// cleanup to avoid a hanging promise
closingAwaiter = null
clearTimeout(closingTimeoutId)
closingTimeoutId = null
} else {
if (currentAsyncAwaiter && !currentAsyncAwaiter.isSettled) {
const err = new Error('Worker unexpectedly exited')
Expand All @@ -119,6 +139,8 @@ module.exports = ({
workerCrashed: true,
err
})
// cleanup to avoid a hanging promise
currentAsyncAwaiter = null
}
}
worker.unref()
Expand All @@ -130,6 +152,8 @@ module.exports = ({
workerCrashed: true,
err
})
// cleanup to avoid a hanging promise
currentAsyncAwaiter = null
worker.unref()
})

Expand Down Expand Up @@ -159,24 +183,31 @@ module.exports = ({
workerCrashed: true,
err
})
// cleanup to avoid a hanging promise
currentAsyncAwaiter = null
worker.unref()
} else {
closingWhenWaitingForMainExecution = true
}

setTimeout(() => {
if (!exited && !closingAwaiter.isSettled) {
closingTimeoutId = setTimeout(() => {
if (!exited && closingAwaiter && !closingAwaiter.isSettled) {
worker.terminate()
closingAwaiter.resolve()
// cleanup to avoid a hanging promise
closingAwaiter = null
worker.unref()
}

closingTimeoutId = null
}, closeTimeout).unref()

closingAwaiter = asyncAwaiter()
worker.ref()
worker.postMessage({
systemAction: 'close'
})

return closingAwaiter.promise
}
}
Expand Down
10 changes: 8 additions & 2 deletions packages/advanced-workers/lib/workerHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async function processAndResponse (m, fn) {
try {
parentPort.postMessage(message)
} catch (e) {
// the errors are tricky and may contain unserializable props, we should probably omit these somehow but still keep ours
// the errors are tricky and may contain un-serializable props, we should probably omit these somehow but still keep ours
if (message.errorData) {
parentPort.postMessage({
type: 'response',
Expand Down Expand Up @@ -96,7 +96,9 @@ parentPort.on('message', (m) => {
return processAndResponse(m, init)
}
if (m.systemAction === 'close') {
workerModule.close()
const closePromise = typeof workerModule.close === 'function' ? workerModule.close() : Promise.resolve()

closePromise
.finally(() => {
// it seems it is important to exit on next tick
// otherwise the node.js crash with FATAL ERROR
Expand All @@ -112,11 +114,15 @@ parentPort.on('message', (m) => {
if (m.systemAction === 'callback-response') {
if (!m.errorData) {
currentCallbackResponseAwaiter.resolve(m.userData)
// cleanup to avoid a hanging promise
currentCallbackResponseAwaiter = null
} else {
const error = new Error(m.errorData.message)
error.stack = m.errorData.stack
Object.assign(error, m.errorData)
currentCallbackResponseAwaiter.reject(error)
// cleanup to avoid a hanging promise
currentCallbackResponseAwaiter = null
}
}
})
2 changes: 1 addition & 1 deletion packages/advanced-workers/test/workers/timeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ module.exports = (initData) => {
return
}
while (true) {
// endles loop
// endless loop
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/jsreport-assets/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ module.exports = function (reporter, definition) {
await response({
previewOptions: reporter.options.office != null && reporter.options.office.preview != null ? reporter.options.office.preview : {},
officeDocumentType: path.extname(asset.filename).slice(1),
buffer: asset.buffer
buffer: asset.buffer,
logger: reporter.logger
}, req, res)

res.setHeader('Content-Type', res.meta.contentType)
Expand Down
4 changes: 2 additions & 2 deletions packages/jsreport-core/lib/main/profiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module.exports = (reporter) => {
const profilerRequestMap = new Map()

function runInProfilerChain (fnOrOptions, req) {
if (req.context.profiling.mode === 'disabled') {
if (req.context.profiling == null || req.context.profiling.mode === 'disabled') {
return
}

Expand Down Expand Up @@ -352,7 +352,7 @@ module.exports = (reporter) => {
type: 'log',
level: info.level,
message: info.message,
previousOperationId: req.context.profiling.lastOperationId
previousOperationId: req.context.profiling?.lastOperationId
}, req)],
log: false
}, req)
Expand Down
2 changes: 1 addition & 1 deletion packages/jsreport-core/lib/main/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class MainReporter extends Reporter {

this._workersManager = this._workersManagerFactory
? this._workersManagerFactory(workersManagerOptions, workersManagerSystemOptions)
: WorkersManager(workersManagerOptions, workersManagerSystemOptions, this.logger)
: WorkersManager(workersManagerOptions, workersManagerSystemOptions)

const workersStart = new Date().getTime()

Expand Down
5 changes: 0 additions & 5 deletions packages/jsreport-core/lib/worker/render/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
*
* Orchestration of the rendering process
*/
const { Readable } = require('stream')
const extend = require('node.extend.without.arrays')
const ExecuteEngine = require('./executeEngine')
const Request = require('../../shared/request')
Expand Down Expand Up @@ -101,10 +100,6 @@ module.exports = (reporter) => {

async function afterRender (reporter, request, response) {
await reporter.afterRenderListeners.fire(request, response)

response.stream = Readable.from(response.content)
response.result = response.stream

return response
}

Expand Down
2 changes: 2 additions & 0 deletions packages/jsreport-docx/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ node_modules

# jsreport files
data
!test/data

logs
jsreport.config.json

Expand Down
5 changes: 5 additions & 0 deletions packages/jsreport-docx/lib/decodeXML.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const { decode } = require('html-entities')

module.exports = function decodeXML (str) {
return decode(str, { level: 'xml' })
}
64 changes: 39 additions & 25 deletions packages/jsreport-docx/lib/imageUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,55 @@ const { pxToEMU, cmToEMU, getDimension } = require('./utils')
module.exports.resolveImageSrc = async function resolveImageSrc (src) {
let imageBuffer
let imageExtension
let imageSource

if (src && src.startsWith('data:')) {
const imageSrc = src
try {
if (src && src.startsWith('data:')) {
const imageSrc = src

imageExtension = imageSrc.split(';')[0].split('/')[1]
imageSource = 'inline'

imageBuffer = Buffer.from(
imageSrc.split(';')[1].substring('base64,'.length),
'base64'
)
} else {
const response = await axios({
url: src,
responseType: 'arraybuffer',
method: 'GET'
})
imageExtension = imageSrc.split(';')[0].split('/')[1]
// we remove subtypes "+..." from the type, like in the case of "svg+xml"
imageExtension = imageExtension.split('+')[0]

const contentType = response.headers['content-type'] || response.headers['Content-Type']
imageBuffer = Buffer.from(
imageSrc.split(';')[1].substring('base64,'.length),
'base64'
)
} else {
imageSource = 'remote'

if (!contentType) {
throw new Error(`Empty content-type for remote image at "${src}"`)
}
const response = await axios({
url: src,
responseType: 'arraybuffer',
method: 'GET'
})

const extensionsParts = contentType.split(';')[0].split('/').filter((p) => p)
const contentType = response.headers['content-type'] || response.headers['Content-Type']

if (extensionsParts.length === 0 || extensionsParts.length > 2) {
throw new Error(`Invalid content-type "${contentType}" for remote image at "${src}"`)
}
if (!contentType) {
throw new Error(`Empty content-type for remote image at "${src}"`)
}

// some servers returns the image content type without the "image/" prefix
imageExtension = extensionsParts.length === 1 ? extensionsParts[0] : extensionsParts[1]
imageBuffer = Buffer.from(response.data)
const extensionsParts = contentType.split(';')[0].split('/').filter((p) => p)

if (extensionsParts.length === 0 || extensionsParts.length > 2) {
throw new Error(`Invalid content-type "${contentType}" for remote image at "${src}"`)
}

// some servers returns the image content type without the "image/" prefix
imageExtension = extensionsParts.length === 1 ? extensionsParts[0] : extensionsParts[1]
// we remove subtypes "+..." from the type, like in the case of "svg+xml"
imageExtension = imageExtension.split('+')[0]
imageBuffer = Buffer.from(response.data)
}
} catch (error) {
error.imageSource = imageSource
throw error
}

return { imageBuffer, imageExtension }
return { imageSource, imageBuffer, imageExtension }
}

module.exports.getImageSizeInEMU = function getImageSizeInEMU (imageBuffer, customSize = {}) {
Expand Down

0 comments on commit 82832d1

Please sign in to comment.