diff --git a/README.md b/README.md index effeb48f..180be1a3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**Note:** If you use the public Export Server at [https://export.highcharts.com](https://export.highcharts.com) you should read our [Terms of use and Fair Usage Policy](https://www.highcharts.com/docs/export-module/privacy-disclaimer-export). + # Highcharts Node.js Export Server Convert Highcharts.JS charts into static image files. diff --git a/bin/cli.js b/bin/cli.js index 916cfb0d..a240c77d 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -96,7 +96,8 @@ const start = async () => { } } else { throw new ExportError( - '[cli] No valid options provided. Please check your input and try again.' + '[cli] No valid options provided. Please check your input and try again.', + 400 ); } } catch (error) { diff --git a/lib/browser.js b/lib/browser.js index bd42990d..7aa7e16f 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -41,7 +41,7 @@ let browser; */ export function get() { if (!browser) { - throw new ExportError('[browser] No valid browser has been created.'); + throw new ExportError('[browser] No valid browser has been created.', 500); } return browser; } @@ -119,12 +119,13 @@ export async function create(puppeteerArgs) { } } catch (error) { throw new ExportError( - '[browser] Maximum retries to open a browser instance reached.' + '[browser] Maximum retries to open a browser instance reached.', + 500 ).setError(error); } if (!browser) { - throw new ExportError('[browser] Cannot find a browser to open.'); + throw new ExportError('[browser] Cannot find a browser to open.', 500); } } diff --git a/lib/cache.js b/lib/cache.js index cd712a15..f13c5db1 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -88,9 +88,10 @@ export const saveConfigToManifest = async (config, fetchedModules) => { 'utf8' ); } catch (error) { - throw new ExportError('[cache] Error writing the cache manifest.').setError( - error - ); + throw new ExportError( + '[cache] Error writing the cache manifest.', + 400 + ).setError(error); } }; diff --git a/lib/chart.js b/lib/chart.js index 47b28f8d..27cd5ee1 100644 --- a/lib/chart.js +++ b/lib/chart.js @@ -70,7 +70,7 @@ export const startExport = async (settings, endCallback) => { return result; } catch (error) { return endCallback( - new ExportError('[chart] Error loading SVG input.').setError(error) + new ExportError('[chart] Error loading SVG input.', 400).setError(error) ); } } @@ -84,7 +84,9 @@ export const startExport = async (settings, endCallback) => { return exportAsString(options.export.instr.trim(), options, endCallback); } catch (error) { return endCallback( - new ExportError('[chart] Error loading input file.').setError(error) + new ExportError('[chart] Error loading input file.', 400).setError( + error + ) ); } } @@ -120,7 +122,8 @@ export const startExport = async (settings, endCallback) => { // No input specified, pass an error message to the callback return endCallback( new ExportError( - `[chart] No valid input specified. Check if at least one of the following parameters is correctly set: 'infile', 'instr', 'options', or 'svg'.` + `[chart] No valid input specified. Check if at least one of the following parameters is correctly set: 'infile', 'instr', 'options', or 'svg'.`, + 400 ) ); }; @@ -344,7 +347,8 @@ const doExport = async (options, chartJson, endCallback, svg) => { // these settings. return endCallback( new ExportError( - `[chart] The 'callback', 'resources' and 'customCode' options have been disabled for this server.` + `[chart] The 'callback', 'resources' and 'customCode' options have been disabled for this server.`, + 400 ) ); } @@ -489,7 +493,8 @@ const doStraightInject = (options, endCallback) => { } catch (error) { return endCallback( new ExportError( - `[chart] Malformed input detected for ${options.export?.requestId || '?'}. Please make sure that your JSON/JavaScript options are sent using the "options" attribute, and that if you're using SVG, it is unescaped.` + `[chart] Malformed input detected for ${options.export?.requestId || '?'}. Please make sure that your JSON/JavaScript options are sent using the "options" attribute, and that if you're using SVG, it is unescaped.`, + 400 ).setError(error) ); } @@ -532,7 +537,8 @@ const exportAsString = (stringToExport, options, endCallback) => { // Do not allow straight injection without the allowCodeExecution flag return endCallback( new ExportError( - '[chart] Only JSON configurations and SVG are allowed for this server. If this is your server, JavaScript custom code can be enabled by starting the server with the --allowCodeExecution flag.' + '[chart] Only JSON configurations and SVG are allowed for this server. If this is your server, JavaScript custom code can be enabled by starting the server with the --allowCodeExecution flag.', + 400 ).setError(error) ); } diff --git a/lib/errors/ExportError.js b/lib/errors/ExportError.js index be659551..6e5b8160 100644 --- a/lib/errors/ExportError.js +++ b/lib/errors/ExportError.js @@ -1,22 +1,35 @@ class ExportError extends Error { - constructor(message) { + /** + * @param {string} message + * @param {number} [status] describes the status code (400, 500, etc.) + */ + constructor(message, status) { super(); + this.message = message; this.stackMessage = message; + + if (status) { + this.status = status; + } } setError(error) { this.error = error; + if (error.name) { this.name = error.name; } - if (error.statusCode) { - this.statusCode = error.statusCode; + + if (!this.status && error.statusCode) { + this.status = error.statusCode; } + if (error.stack) { this.stackMessage = error.message; this.stack = error.stack; } + return this; } } diff --git a/lib/export.js b/lib/export.js index 5ccd8629..a93b43c4 100644 --- a/lib/export.js +++ b/lib/export.js @@ -72,7 +72,7 @@ const createImage = (page, type, encoding, clip, rasterizationTimeout) => }), new Promise((_resolve, reject) => setTimeout( - () => reject(new ExportError('Rasterization timeout')), + () => reject(new ExportError('Rasterization timeout', 408)), rasterizationTimeout || 1500 ) ) @@ -106,7 +106,7 @@ const createPDF = async ( }), new Promise((_resolve, reject) => setTimeout( - () => reject(new ExportError('Rasterization timeout')), + () => reject(new ExportError('Rasterization timeout', 408)), rasterizationTimeout || 1500 ) ) @@ -294,7 +294,8 @@ export default async (page, chart, options) => { ); } else { throw new ExportError( - `[export] Unsupported output format ${exportOptions.type}.` + `[export] Unsupported output format ${exportOptions.type}.`, + 400 ); } diff --git a/lib/highcharts.js b/lib/highcharts.js index e82a2213..5e4b1df6 100644 --- a/lib/highcharts.js +++ b/lib/highcharts.js @@ -95,7 +95,7 @@ export async function triggerExport(chartOptions, options, displayErrors) { const userOptions = options.export.strInj ? new Function(`return ${options.export.strInj}`)() : chartOptions; - + // Trigger custom code if (options.customLogic.customCode) { new Function('options', options.customLogic.customCode)(userOptions); diff --git a/lib/pool.js b/lib/pool.js index 6a875a5e..eb133524 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -62,7 +62,7 @@ const factory = { page = await newPage(); if (!page || page.isClosed()) { - throw new ExportError('The page is invalid or closed.'); + throw new ExportError('The page is invalid or closed.', 500); } log( @@ -73,7 +73,8 @@ const factory = { ); } catch (error) { throw new ExportError( - 'Error encountered when creating a new page.' + 'Error encountered when creating a new page.', + 500 ).setError(error); } @@ -204,7 +205,8 @@ export const initPool = async (config) => { ); } catch (error) { throw new ExportError( - '[pool] Could not create the pool of workers.' + '[pool] Could not create the pool of workers.', + 500 ).setError(error); } }; @@ -262,7 +264,10 @@ export const postWork = async (chart, options) => { } if (!pool) { - throw new ExportError('Work received, but pool has not been started.'); + throw new ExportError( + 'Work received, but pool has not been started.', + 500 + ); } // Acquire the worker along with the id of resource and work count @@ -293,7 +298,8 @@ export const postWork = async (chart, options) => { if (!workerHandle.page) { throw new ExportError( - 'Resolved worker page is invalid: the pool setup is wonky.' + 'Resolved worker page is invalid: the pool setup is wonky.', + 500 ); } diff --git a/lib/server/server.js b/lib/server/server.js index 45c03b86..8523f469 100644 --- a/lib/server/server.js +++ b/lib/server/server.js @@ -185,7 +185,8 @@ export const startServer = async (serverConfig) => { errorHandler(app); } catch (error) { throw new ExportError( - '[server] Could not configure and start the server.' + '[server] Could not configure and start the server.', + 500 ).setError(error); } };