diff --git a/.env.sample b/.env.sample index a51572c1..bb328916 100644 --- a/.env.sample +++ b/.env.sample @@ -57,6 +57,7 @@ POOL_IDLE_TIMEOUT = 30000 POOL_CREATE_RETRY_INTERVAL = 200 POOL_REAPER_INTERVAL = 1000 POOL_BENCHMARKING = false +POOL_RESOURCES_INTERVAL = 30000 # LOGGING CONFIG LOGGING_LEVEL = 4 @@ -75,6 +76,7 @@ OTHER_LISTEN_TO_PROCESS_EXITS = true OTHER_NO_LOGO = false OTHER_HARD_RESET_PAGE = false OTHER_BROWSER_SHELL_MODE = true +OTHER_CONNECTION_OVER_PIPE = false # DEBUG CONFIG DEBUG_ENABLE = false diff --git a/CHANGELOG.md b/CHANGELOG.md index f257af67..5aad87f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ +# 4.0.3 + +_New Features:_ + +- Added support for absolute paths in the `HIGHCHARTS_CACHE_PATH` option [(#562)](https://github.com/highcharts/node-export-server/issues/562) + +_Fixes_: + +- Improved status codes (user errors instead of 500) [(#577)](https://github.com/highcharts/node-export-server/pull/577) + +- Improved memory management/usage [(#586)](https://github.com/highcharts/node-export-server/pull/586) + +_Other:_ + +- Add fair usage policy note on the page [(#583)](https://github.com/highcharts/node-export-server/pull/583) + # 4.0.2 +_Hotfix_: + +- Fixed missing 'msg' and 'public' bundle in 4.0.1 on NPM. + _Fixes:_ - Made chart userOptions available within `customCode` as variable `options` [(#551)](https://github.com/highcharts/node-export-server/issues/551). diff --git a/README.md b/README.md index c05848bd..86b89f5d 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). Note that a valid Highcharts License is required to do exports. + # Highcharts Node.js Export Server Convert Highcharts.JS charts into static image files. @@ -301,11 +303,11 @@ These variables are set in your environment and take precedence over options fro - `HIGHCHARTS_VERSION`: Highcharts version to use (defaults to `latest`). - `HIGHCHARTS_CDN_URL`: Highcharts CDN URL of scripts to be used (defaults to `https://code.highcharts.com/`). - `HIGHCHARTS_FORCE_FETCH`: The flag that determines whether to refetch all scripts after each server rerun (defaults to `false`). -- `HIGHCHARTS_CACHE_PATH`: In which directory should the fetched Highcharts scripts be placed (defaults to `.cache`). +- `HIGHCHARTS_CACHE_PATH`: In which directory should the fetched Highcharts scripts be placed (defaults to `.cache`). Since v4.0.3 can be either absolute or relative path. +- `HIGHCHARTS_ADMIN_TOKEN`: An authentication token that is required to switch the Highcharts version on the server at runtime (defaults to ``). - `HIGHCHARTS_CORE_SCRIPTS`: Highcharts core scripts to fetch (defaults to ``). - `HIGHCHARTS_MODULE_SCRIPTS`: Highcharts module scripts to fetch (defaults to ``). - `HIGHCHARTS_INDICATOR_SCRIPTS`: Highcharts indicator scripts to fetch (defaults to ``). -- `HIGHCHARTS_ADMIN_TOKEN`: An authentication token that is required to switch the Highcharts version on the server at runtime (defaults to ``). ### Export Config diff --git a/bin/cli.js b/bin/cli.js index 6fd2b6ca..a6a7dd40 100644 --- a/bin/cli.js +++ b/bin/cli.js @@ -110,7 +110,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/jest.config.js b/jest.config.js index f57f9616..4ee06fd5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,10 @@ -export default { +/** @type {import('jest').Config} */ +const config = { testEnvironment: 'jest-environment-node', testMatch: ['**/tests/unit/**/*.test.js'], transform: { // '^.+\\.test.js?$': 'babel-jest' } }; + +export default config; diff --git a/lib/browser.js b/lib/browser.js index 56f289b0..a326d9af 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -19,17 +19,74 @@ import puppeteer from 'puppeteer'; import { getCachePath } from './cache.js'; import { getOptions } from './config.js'; +import { envs } from './envs.js'; import { setupHighcharts } from './highcharts.js'; import { log, logWithStack } from './logger.js'; -import { __dirname } from './utils.js'; +import { __dirname, expBackoff } from './utils.js'; import ExportError from './errors/ExportError.js'; // Get the template for the page const template = readFileSync(__dirname + '/templates/template.html', 'utf8'); +// To save the browser let browser; +// To save the WebSocket endpoint in case of a sudden disconnect +let wsEndpoint; + +/** + * Reconnects to the browser instance when it is disconnected. If the current + * browser connection is lost, it attempts to reconnect using the previous + * WebSocket endpoint. If the reconnection fails, it will try to close the + * browser and relaunch a new instance. + */ +async function reconnect() { + try { + // Start the reconnecting + log(3, `[browser] Restarting the browser connection.`); + + // Try to reconnect the browser + if (browser && !browser.connected) { + browser = await puppeteer.connect({ + browserWSEndpoint: wsEndpoint + }); + } + + // Save a new WebSocket endpoint + wsEndpoint = browser.wsEndpoint(); + + // Add the reconnect event again + browser.on('disconnected', reconnect); + + // Log the success message + log(3, `[browser] Browser reconnected successfully.`); + } catch (error) { + logWithStack( + 1, + error, + '[browser] Could not restore the browser connection, attempting to relaunch.' + ); + + // Try to close the browser before relaunching + try { + await close(); + } catch (error) { + logWithStack( + 1, + error, + '[browser] Could not close the browser before relaunching (probably is already closed).' + ); + } + + // Try to relaunch the browser + await create(getOptions().puppeteer.args || []); + + // Log the success message + log(3, `[browser] Browser relaunched successfully.`); + } +} + /** * Retrieves the existing Puppeteer browser instance. * @@ -41,7 +98,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; } @@ -68,6 +125,8 @@ export async function create(puppeteerArgs) { headless: other.browserShellMode ? 'shell' : true, userDataDir: 'tmp', args: puppeteerArgs, + // Must be disabled for debugging to work + pipe: envs.OTHER_CONNECTION_OVER_PIPE, handleSIGINT: false, handleSIGTERM: false, handleSIGHUP: false, @@ -86,7 +145,26 @@ export async function create(puppeteerArgs) { 3, `[browser] Attempting to get a browser instance (try ${++tryCount}).` ); + + // Launch the browser browser = await puppeteer.launch(launchOptions); + + // Close the initial pages if any found + const pages = await browser.pages(); + if (pages) { + for (const page of pages) { + await page.close(); + } + } + + // Only for the WebSocket connection + if (!launchOptions.pipe) { + // Save WebSocket endpoint + wsEndpoint = browser.wsEndpoint(); + + // Attach the disconnected event + browser.on('disconnected', reconnect); + } } catch (error) { logWithStack( 1, @@ -119,12 +197,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); } } @@ -139,10 +218,11 @@ export async function create(puppeteerArgs) { * is closed. */ export async function close() { - // Close the browser when connnected - if (browser?.connected) { + // Close the browser when connected + if (browser && browser.connected) { await browser.close(); } + browser = null; log(4, '[browser] Closed the browser.'); } @@ -154,33 +234,51 @@ export async function close() { * The function creates a new page, disables caching, sets content using * setPageContent(), and returns the created Puppeteer Page. * + * @param {Object} poolResource - The pool resource that contians page and id. + * * @returns {(boolean|object)} Returns false if the browser instance is not * available, or a Puppeteer Page object representing the newly created page. */ -export async function newPage() { - if (!browser) { - return false; +export async function newPage(poolResource) { + const startDate = new Date().getTime(); + + // Throw an error in case of no connected browser + if (!browser || !browser.connected) { + throw new ExportError(`[browser] Browser is not yet connected.`, 500); } // Create a page - const page = await browser.newPage(); + poolResource.page = await browser.newPage(); // Disable cache - await page.setCacheEnabled(false); + await poolResource.page.setCacheEnabled(false); // Set the content - await setPageContent(page); + await setPageContent(poolResource.page); // Set page events - setPageEvents(page); + setPageEvents(poolResource); + + // Check if the page is correctly created + if (!poolResource.page || poolResource.page.isClosed()) { + throw new ExportError('The page is invalid or closed.', 500); + } - return page; + log( + 3, + `[pool] Pool resource [${poolResource.id}] - Successfully created a worker, took ${ + new Date().getTime() - startDate + }ms.` + ); + + // Return the resource with a ready to use page + return poolResource; } /** * Clears the content of a Puppeteer Page based on the specified mode. * - * @param {Object} page - The Puppeteer Page object to be cleared. + * @param {Object} poolResource - The pool resource that contians page and id. * @param {boolean} hardReset - A flag indicating the type of clearing * to be performed. If true, navigates to 'about:blank' and resets content * and scripts. If false, clears the body content by setting a predefined HTML @@ -188,18 +286,20 @@ export async function newPage() { * * @throws {Error} Logs thrown error if clearing the page content fails. */ -export async function clearPage(page, hardReset = false) { +export async function clearPage(poolResource, hardReset = false) { try { - if (!page.isClosed()) { + if (!poolResource.page.isClosed()) { if (hardReset) { // Navigate to about:blank - await page.goto('about:blank', { waitUntil: 'domcontentloaded' }); + await poolResource.page.goto('about:blank', { + waitUntil: 'domcontentloaded' + }); // Set the content and and scripts again - await setPageContent(page); + await setPageContent(poolResource.page); } else { // Clear body content - await page.evaluate(() => { + await poolResource.page.evaluate(() => { document.body.innerHTML = '
'; }); @@ -209,8 +309,10 @@ export async function clearPage(page, hardReset = false) { logWithStack( 2, error, - '[browser] Could not clear the content of the page.' + `[pool] Pool resource [${poolResource.id}] - Content of the page could not be cleared.` ); + // Set the `workLimit` to exceeded in order to recreate the resource + poolResource.workCount = getOptions().pool.workLimit + 1; } } @@ -328,45 +430,49 @@ export async function addPageResources(page, options) { * to be cleared. */ export async function clearPageResources(page, injectedResources) { - for (const resource of injectedResources) { - await resource.dispose(); - } + try { + for (const resource of injectedResources) { + await resource.dispose(); + } - // Destroy old charts after export is done and reset all CSS and script tags - await page.evaluate(() => { - // We are not guaranteed that Highcharts is loaded, e,g, when doing SVG - // exports - if (typeof Highcharts !== 'undefined') { - // eslint-disable-next-line no-undef - const oldCharts = Highcharts.charts; - - // Check in any already existing charts - if (Array.isArray(oldCharts) && oldCharts.length) { - // Destroy old charts - for (const oldChart of oldCharts) { - oldChart && oldChart.destroy(); - // eslint-disable-next-line no-undef - Highcharts.charts.shift(); + // Destroy old charts after export is done and reset all CSS and script tags + await page.evaluate(() => { + // We are not guaranteed that Highcharts is loaded, e,g, when doing SVG + // exports + if (typeof Highcharts !== 'undefined') { + // eslint-disable-next-line no-undef + const oldCharts = Highcharts.charts; + + // Check in any already existing charts + if (Array.isArray(oldCharts) && oldCharts.length) { + // Destroy old charts + for (const oldChart of oldCharts) { + oldChart && oldChart.destroy(); + // eslint-disable-next-line no-undef + Highcharts.charts.shift(); + } } } - } - // eslint-disable-next-line no-undef - const [...scriptsToRemove] = document.getElementsByTagName('script'); - // eslint-disable-next-line no-undef - const [, ...stylesToRemove] = document.getElementsByTagName('style'); - // eslint-disable-next-line no-undef - const [...linksToRemove] = document.getElementsByTagName('link'); - - // Remove tags - for (const element of [ - ...scriptsToRemove, - ...stylesToRemove, - ...linksToRemove - ]) { - element.remove(); - } - }); + // eslint-disable-next-line no-undef + const [...scriptsToRemove] = document.getElementsByTagName('script'); + // eslint-disable-next-line no-undef + const [, ...stylesToRemove] = document.getElementsByTagName('style'); + // eslint-disable-next-line no-undef + const [...linksToRemove] = document.getElementsByTagName('link'); + + // Remove tags + for (const element of [ + ...scriptsToRemove, + ...stylesToRemove, + ...linksToRemove + ]) { + element.remove(); + } + }); + } catch (error) { + logWithStack(1, error, `[browser] Could not clear page's resources.`); + } } /** @@ -390,24 +496,17 @@ async function setPageContent(page) { /** * Set events for a Puppeteer Page. * - * @param {Object} page - The Puppeteer Page object to set events to. + * @param {Object} poolResource - The pool resource that contians page and id. */ -function setPageEvents(page) { +function setPageEvents(poolResource) { // Get debug options - const { debug } = getOptions(); - - // Set the console listener, if needed - if (debug.enable && debug.listenToConsole) { - page.on('console', (message) => { - console.log(`[debug] ${message.text()}`); - }); - } + const { debug, pool } = getOptions(); // Set the pageerror listener - page.on('pageerror', async (error) => { + poolResource.page.on('pageerror', async (error) => { // TODO: Consider adding a switch here that turns on log(0) logging // on page errors. - await page.$eval( + await poolResource.page.$eval( '#container', (element, errorMessage) => { // eslint-disable-next-line no-undef @@ -418,6 +517,66 @@ function setPageEvents(page) { `

Chart input data error:

${error.toString()}` ); }); + + // Set the console listener, if needed + if (debug.enable && debug.listenToConsole) { + poolResource.page.on('console', (message) => { + console.log(`[debug] ${message.text()}`); + }); + } + + // Add the framedetached event if the connection is over WebSocket + if (envs.OTHER_CONNECTION_OVER_PIPE === false) { + poolResource.page.on('framedetached', async (frame) => { + // Get the main frame + const mainFrame = poolResource.page.mainFrame(); + + // Check if a page's frame is detached and requires to be recreated + if ( + frame === mainFrame && + mainFrame.detached && + poolResource.workCount <= pool.workLimit + ) { + log( + 3, + `[browser] Pool resource [${poolResource.id}] - Page's frame detached.` + ); + try { + // Try to connect to a new page using exponential backoff strategy + expBackoff( + async (poolResourceId, poolResource) => { + try { + // Try to close the page with a detached frame + if (!poolResource.page.isClosed()) { + await poolResource.page.close(); + } + } catch (error) { + log( + 3, + `[browser] Pool resource [${poolResourceId}] - Could not close the page with a detached frame.` + ); + } + + // Trigger a page creation + await newPage(poolResource); + }, + 0, + poolResource.id, + poolResource + ); + } catch (error) { + logWithStack( + 3, + error, + `[browser] Pool resource [${poolResource.id}] - Could not create a new page.` + ); + + // Set the `workLimit` to exceeded in order to recreate the resource + poolResource.workCount = pool.workLimit + 1; + } + } + }); + } } export default { diff --git a/lib/cache.js b/lib/cache.js index cc9923c2..14e8e530 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -17,7 +17,7 @@ See LICENSE file in root for details. // before starting the service import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; -import { join } from 'path'; +import { isAbsolute, join } from 'path'; import { HttpsProxyAgent } from 'https-proxy-agent'; @@ -82,14 +82,15 @@ export const saveConfigToManifest = async (config, fetchedModules) => { log(3, '[cache] Writing a new manifest.'); try { writeFileSync( - join(__dirname, config.cachePath, 'manifest.json'), + join(getCachePath(), 'manifest.json'), JSON.stringify(newManifest), '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); } }; @@ -138,7 +139,8 @@ export const fetchAndProcessScript = async ( if (shouldThrowError) { throw new ExportError( - `Could not fetch the ${script}.js. The script might not exist in the requested version (status code: ${response.statusCode}).` + `Could not fetch the ${script}.js. The script might not exist in the requested version (status code: ${response.statusCode}).`, + 500 ).setError(response); } else { log( @@ -184,9 +186,10 @@ export const fetchScripts = async ( port: proxyPort }); } catch (error) { - throw new ExportError('[cache] Could not create a Proxy Agent.').setError( - error - ); + throw new ExportError( + '[cache] Could not create a Proxy Agent.', + 500 + ).setError(error); } } @@ -268,7 +271,8 @@ export const updateCache = async ( return fetchedModules; } catch (error) { throw new ExportError( - '[cache] Unable to update the local Highcharts cache.' + '[cache] Unable to update the local Highcharts cache.', + 500 ).setError(error); } }; @@ -306,15 +310,16 @@ export const updateVersion = async (newVersion) => { export const checkAndUpdateCache = async (options) => { const { highcharts, server } = options; - const cachePath = join(__dirname, highcharts.cachePath); + const cachePath = getCachePath(); let fetchedModules; + // Prepare paths to manifest and sources from the .cache folder const manifestPath = join(cachePath, 'manifest.json'); const sourcePath = join(cachePath, 'sources.js'); // Create the cache destination if it doesn't exist already - !existsSync(cachePath) && mkdirSync(cachePath); + !existsSync(cachePath) && mkdirSync(cachePath, { recursive: true }); // Fetch all the scripts either if manifest.json does not exist // or if the forceFetch option is enabled @@ -387,8 +392,18 @@ export const checkAndUpdateCache = async (options) => { await saveConfigToManifest(highcharts, fetchedModules); }; -export const getCachePath = () => - join(__dirname, getOptions().highcharts.cachePath); +/** + * Returns the path to the cache folder. + * @returns {string} The path to the cache folder. + */ +export const getCachePath = () => { + const cachePathOption = getOptions().highcharts.cachePath; + + // issue #562: support for absolute paths + return isAbsolute(cachePathOption) + ? cachePathOption + : join(__dirname, cachePathOption); +}; export const getCache = () => cache; diff --git a/lib/chart.js b/lib/chart.js index 5b1fa059..8c4d2cc7 100644 --- a/lib/chart.js +++ b/lib/chart.js @@ -69,7 +69,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) ); } } @@ -83,7 +83,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 + ) ); } } @@ -111,7 +113,7 @@ export const startExport = async (settings, endCallback) => { ); } catch (error) { return endCallback( - new ExportError('[chart] Error loading raw input.').setError(error) + new ExportError('[chart] Error loading raw input.', 400).setError(error) ); } } @@ -119,7 +121,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 ) ); }; @@ -408,7 +411,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 ) ); } @@ -553,7 +557,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) ); } @@ -586,6 +591,15 @@ const exportAsString = (stringToExport, options, endCallback) => { // Try to parse to JSON and call the doExport function const chartJSON = JSON.parse(stringToExport.replaceAll(/\t|\n|\r/g, ' ')); + if (!chartJSON || typeof chartJSON !== 'object') { + return endCallback( + new ExportError( + '[chart] Invalid configuration provided - the options must be an object, not a string', + 400 + ) + ); + } + // If a correct JSON, do the export return doExport(options, chartJSON, endCallback); } catch (error) { @@ -596,7 +610,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/envs.js b/lib/envs.js index 77bac3de..af94a3d3 100644 --- a/lib/envs.js +++ b/lib/envs.js @@ -176,6 +176,7 @@ export const Config = z.object({ POOL_CREATE_RETRY_INTERVAL: v.nonNegativeNum(), POOL_REAPER_INTERVAL: v.nonNegativeNum(), POOL_BENCHMARKING: v.boolean(), + POOL_RESOURCES_INTERVAL: v.nonNegativeNum(), // logger LOGGING_LEVEL: z @@ -207,6 +208,7 @@ export const Config = z.object({ OTHER_NO_LOGO: v.boolean(), OTHER_HARD_RESET_PAGE: v.boolean(), OTHER_BROWSER_SHELL_MODE: v.boolean(), + OTHER_CONNECTION_OVER_PIPE: v.boolean(), // debugger DEBUG_ENABLE: v.boolean(), 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 f7140fe7..3e1f6be5 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/pool.js b/lib/pool.js index 496853f3..5c88391d 100644 --- a/lib/pool.js +++ b/lib/pool.js @@ -21,7 +21,9 @@ import { newPage, clearPage } from './browser.js'; +import { envs } from './envs.js'; import puppeteerExport from './export.js'; +import { addInterval } from './intervals.js'; import { log, logWithStack } from './logger.js'; import { getNewDateTime, measureTime } from './utils.js'; @@ -53,74 +55,93 @@ const factory = { * page. */ create: async () => { - let page = false; - - const id = uuid(); - const startDate = getNewDateTime(); - try { - page = await newPage(); - - if (!page || page.isClosed()) { - throw new ExportError('The page is invalid or closed.'); - } - - log( - 3, - `[pool] Successfully created a worker ${id} - took ${ - getNewDateTime() - startDate - } ms.` - ); + const poolResource = { + id: uuid(), + // Try to distribute the initial work count + workCount: Math.round(Math.random() * (poolConfig.workLimit / 2)) + }; + return await newPage(poolResource); } catch (error) { throw new ExportError( - 'Error encountered when creating a new page.' + 'Error encountered when creating a new page.', + 500 ).setError(error); } - - return { - id, - page, - // Try to distribute the initial work count - workCount: Math.round(Math.random() * (poolConfig.workLimit / 2)) - }; }, /** * Validates a worker page in the export pool, checking if it has exceeded * the work limit. * - * @param {Object} workerHandle - The handle to the worker, containing the + * @param {Object} poolResource - The handle to the worker, containing the * worker's ID, a reference to the browser page, and work count. * * @returns {boolean} - Returns true if the worker is valid and within * the work limit; otherwise, returns false. */ - validate: async (workerHandle) => { + validate: async (poolResource) => { + let validated = true; + + // Check if the `workLimit` is exceeded if ( poolConfig.workLimit && - ++workerHandle.workCount > poolConfig.workLimit + ++poolResource.workCount > poolConfig.workLimit ) { log( 3, - `[pool] Worker failed validation: exceeded work limit (limit is ${poolConfig.workLimit}).` + `[pool] Pool resource [${poolResource.id}] - Validation failed (exceeded the ${poolConfig.workLimit} works limit).` ); - return false; + validated = false; } - return true; + + // Check if the `page` is not valid + if (!poolResource.page) { + // Check if the `page` is closed + if (poolResource.page.isClosed()) { + log( + 3, + `[pool] Pool resource [${poolResource.id}] - Validation failed (page is closed or invalid).` + ); + } + + // Check if the `mainFrame` is detached + if (poolResource.page.mainFrame().detached) { + log( + 3, + `[pool] Pool resource [${poolResource.id}] - Validation failed (page's frame is detached).` + ); + } + validated = false; + } + + return validated; }, /** * Destroys a worker entry in the export pool, closing its associated page. * - * @param {Object} workerHandle - The handle to the worker, containing + * @param {Object} poolResource - The handle to the worker, containing * the worker's ID and a reference to the browser page. */ - destroy: async (workerHandle) => { - log(3, `[pool] Destroying pool entry ${workerHandle.id}.`); + destroy: async (poolResource) => { + log(3, `[pool] Pool resource [${poolResource.id}] - Destroying a worker.`); + + if (poolResource.page) { + try { + // Remove all attached event listeners from the resource + poolResource.page.removeAllListeners('pageerror'); + poolResource.page.removeAllListeners('console'); + poolResource.page.removeAllListeners('framedetached'); - if (workerHandle.page) { - // We don't really need to wait around for this - await workerHandle.page.close(); + // We need to wait around for this + await poolResource.page.close(); + } catch (error) { + log( + 3, + `[pool] Pool resource [${poolResource.id}] - Page could not be closed upon destroying.` + ); + } } } }; @@ -173,13 +194,16 @@ export const initPool = async (config) => { // Set events pool.on('release', async (resource) => { - // Clear page - await clearPage(resource.page, false); - log(4, `[pool] Releasing a worker with ID ${resource.id}.`); + log(4, `[pool] Pool resource [${resource.id}] - Releasing a worker.`); + await clearPage(resource, false); }); - pool.on('destroySuccess', (eventId, resource) => { - log(4, `[pool] Destroyed a worker with ID ${resource.id}.`); + pool.on('destroySuccess', (_eventId, resource) => { + log( + 4, + `[pool] Pool resource [${resource.id}] - Destroyed a worker successfully.` + ); + resource.page = null; }); const initialResources = []; @@ -198,13 +222,20 @@ export const initPool = async (config) => { pool.release(resource); }); + // Init the interval for checking if the minimum number of resources exist + if (envs.POOL_RESOURCES_INTERVAL) { + // Register interval for the later clearing + addInterval(_checkingResourcesInterval(envs.POOL_RESOURCES_INTERVAL)); + } + log( 3, `[pool] The pool is ready${initialResources.length ? ` with ${initialResources.length} initial resources waiting.` : '.'}` ); } catch (error) { throw new ExportError( - '[pool] Could not create the pool of workers.' + '[pool] Could not create the pool of workers.', + 500 ).setError(error); } }; @@ -226,11 +257,17 @@ export async function killPool() { pool.release(worker.resource); } + // Remove all attached event listeners from the pool + pool.removeAllListeners('release'); + pool.removeAllListeners('destroySuccess'); + pool.removeAllListeners('destroyFail'); + // Destroy the pool if it is still available if (!pool.destroyed) { await pool.destroy(); log(4, '[browser] Destroyed the pool of resources.'); } + pool = null; } // Close the browser instance @@ -262,7 +299,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 @@ -292,15 +332,21 @@ export const postWork = async (chart, options) => { log(4, '[pool] Acquired a worker handle.'); if (!workerHandle.page) { + // Set the `workLimit` to exceeded in order to recreate the resource + workerHandle.workCount = poolConfig.workLimit + 1; throw new ExportError( - 'Resolved worker page is invalid: the pool setup is wonky.' + 'Resolved worker page is invalid: the pool setup is wonky.', + 500 ); } // Save the start time let workStart = getNewDateTime(); - log(4, `[pool] Starting work on pool entry with ID ${workerHandle.id}.`); + log( + 4, + `[pool] Pool resource [${workerHandle.id}] - Starting work on this pool entry.` + ); // Perform an export on a puppeteer level const exportCounter = measureTime(); @@ -308,10 +354,9 @@ export const postWork = async (chart, options) => { // Check if it's an error if (result instanceof Error) { - // TODO: If the export failed because puppeteer timed out, we need to force kill the worker so we get a new page. That needs to be handled better than this hack. if (result.message === 'Rasterization timeout') { - workerHandle.page.close(); - workerHandle.page = await newPage(); + // Set the `workLimit` to exceeded in order to recreate the resource + workerHandle.workCount = poolConfig.workLimit + 1; } throw new ExportError( @@ -379,10 +424,20 @@ export const getPool = () => pool; export const getPoolInfoJSON = () => ({ min: pool.min, max: pool.max, - all: pool.numFree() + pool.numUsed(), - available: pool.numFree(), used: pool.numUsed(), - pending: pool.numPendingAcquires() + available: pool.numFree(), + allCreated: pool.numUsed() + pool.numFree(), + pendingAcquires: pool.numPendingAcquires(), + pendingCreates: pool.numPendingCreates(), + pendingValidations: pool.numPendingValidations(), + pendingDestroys: pool.pendingDestroys.length, + absoluteAll: + pool.numUsed() + + pool.numFree() + + pool.numPendingAcquires() + + pool.numPendingCreates() + + pool.numPendingValidations() + + pool.pendingDestroys.length }); /** @@ -391,14 +446,83 @@ export const getPoolInfoJSON = () => ({ * requests. */ export function getPoolInfo() { - const { min, max, all, available, used, pending } = getPoolInfoJSON(); + const { + min, + max, + used, + available, + allCreated, + pendingAcquires, + pendingCreates, + pendingValidations, + pendingDestroys, + absoluteAll + } = getPoolInfoJSON(); log(5, `[pool] The minimum number of resources allowed by pool: ${min}.`); log(5, `[pool] The maximum number of resources allowed by pool: ${max}.`); - log(5, `[pool] The number of all created resources: ${all}.`); - log(5, `[pool] The number of available resources: ${available}.`); - log(5, `[pool] The number of acquired resources: ${used}.`); - log(5, `[pool] The number of resources waiting to be acquired: ${pending}.`); + log(5, `[pool] The number of used resources: ${used}.`); + log(5, `[pool] The number of free resources: ${available}.`); + log( + 5, + `[pool] The number of all created (used and free) resources: ${allCreated}.` + ); + log( + 5, + `[pool] The number of resources waiting to be acquired: ${pendingAcquires}.` + ); + log( + 5, + `[pool] The number of resources waiting to be created: ${pendingCreates}.` + ); + log( + 5, + `[pool] The number of resources waiting to be validated: ${pendingValidations}.` + ); + log( + 5, + `[pool] The number of resources waiting to be destroyed: ${pendingDestroys}.` + ); + log(5, `[pool] The number of all resources: ${absoluteAll}.`); +} + +/** + * Periodically checks and ensures the minimum number of resources in the pool. + * If the total number of used, free and about to be created resources falls + * below the minimum set with the `pool.min`, it creates additional resources to + * meet the minimum requirement. + * + * @param {number} resourceCheckInterval - The interval, in milliseconds, at + * which the pool resources are checked. + * + * @returns {NodeJS.Timeout} - Returns a timer ID that can be used to clear the + * interval later. + */ +function _checkingResourcesInterval(resourceCheckInterval) { + // Set the interval for checking the number of pool resources + return setInterval(async () => { + try { + // Get the current number of resources + let currentNumber = + pool.numUsed() + pool.numFree() + pool.numPendingCreates(); + + // Create missing resources + while (currentNumber++ < pool.min) { + try { + // Explicitly creating a resource + await pool._doCreate(); + } catch (error) { + logWithStack(2, error, '[pool] Could not create a missing resource.'); + } + } + } catch (error) { + logWithStack( + 1, + error, + `[pool] Something went wrong when trying to create missing resources.` + ); + } + }, resourceCheckInterval); } /** diff --git a/lib/resource_release.js b/lib/resource_release.js index a4a51a19..ebad2495 100644 --- a/lib/resource_release.js +++ b/lib/resource_release.js @@ -12,6 +12,7 @@ See LICENSE file in root for details. *******************************************************************************/ +import { get as getBrowser } from './browser.js'; import { clearAllTimers } from './timers.js'; import { killPool } from './pool.js'; import { closeServers } from './server/server.js'; @@ -23,6 +24,9 @@ import { terminateClients } from './server/web_socket.js'; * @param {number} exitCode - An exit code for the process.exit() function. */ export const shutdownCleanUp = async (exitCode) => { + // Remove all attached event listeners from the browser + getBrowser().removeAllListeners('disconnected'); + // Await freeing all resources await Promise.allSettled([ // Clear all ongoing timers diff --git a/lib/server/server.js b/lib/server/server.js index f19f37c6..c7265f58 100644 --- a/lib/server/server.js +++ b/lib/server/server.js @@ -44,7 +44,11 @@ const app = express(); app.disable('x-powered-by'); // Enable CORS support -app.use(cors()); +app.use( + cors({ + methods: ['POST', 'GET', 'OPTIONS'] + }) +); // Enable parsing of form data (files) with Multer package const storage = multer.memoryStorage(); @@ -196,7 +200,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); } }; diff --git a/lib/utils.js b/lib/utils.js index f2c8512a..5b570a55 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -90,6 +90,10 @@ export async function expBackoff(fn, attempt = 0, ...args) { // Wait given amount of time await new Promise((response) => setTimeout(response, delayInMs)); + log( + 3, + `[pool] Waited ${delayInMs}ms until next call for the resource of ID: ${args[0]}.` + ); // Try again return expBackoff(fn, attempt, ...args); diff --git a/package-lock.json b/package-lock.json index 3743866e..64e09fdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "highcharts-export-server", - "version": "4.0.1", + "version": "4.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "highcharts-export-server", - "version": "4.0.1", + "version": "4.0.3", + "hasInstallScript": true, "license": "MIT", "dependencies": { "colors": "1.4.0", @@ -20,7 +21,7 @@ "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "prompts": "^2.4.2", - "puppeteer": "^22.15.0", + "puppeteer": "^23.5.3", "tarn": "^3.0.2", "uuid": "^10.0.0", "ws": "^8.18.0", @@ -1237,12 +1238,11 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", - "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", - "license": "Apache-2.0", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.3.6", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", @@ -1262,7 +1262,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1294,224 +1293,208 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", - "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", + "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", - "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", + "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", - "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", + "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", - "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", + "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", - "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", + "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", - "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", + "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", "cpu": [ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", - "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", + "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", - "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", + "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", - "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", + "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", - "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", + "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", "cpu": [ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", - "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", + "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", "cpu": [ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", - "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", + "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", - "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", + "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", - "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", + "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", - "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", + "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", "cpu": [ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", - "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", + "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", "cpu": [ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" @@ -1547,8 +1530,7 @@ "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -1596,11 +1578,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true }, "node_modules/@types/graceful-fs": { "version": "4.1.9", @@ -1684,7 +1665,6 @@ "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -1971,7 +1951,6 @@ "version": "0.13.4", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", "dependencies": { "tslib": "^2.0.1" }, @@ -2002,10 +1981,9 @@ } }, "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "license": "Apache-2.0" + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" }, "node_modules/babel-jest": { "version": "29.7.0", @@ -2128,17 +2106,15 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", - "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", - "license": "Apache-2.0", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.0.tgz", + "integrity": "sha512-/E8dDe9dsbLyh2qrZ64PEPadOQ0F4gbl1sUJOrmph7xOiIxfY8vwab/4bFLh4Y88/Hk/ujKcrQKc+ps0mv873A==", "optional": true }, "node_modules/bare-fs": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", - "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", - "license": "Apache-2.0", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.5.tgz", + "integrity": "sha512-SlE9eTxifPDJrT6YgemQ1WGFleevzwY+XAP1Xqgl56HtcrisC2CHCZ2tq6dBpcH2TnNxwUEUGhweo+lrQtYuiw==", "optional": true, "dependencies": { "bare-events": "^2.0.0", @@ -2147,30 +2123,28 @@ } }, "node_modules/bare-os": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", - "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", - "license": "Apache-2.0", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.4.tgz", + "integrity": "sha512-z3UiI2yi1mK0sXeRdc4O1Kk8aOa/e+FNWZcTiPB/dfTWyLypuE99LibgRaQki914Jq//yAWylcAt+mknKdixRQ==", "optional": true }, "node_modules/bare-path": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", - "license": "Apache-2.0", "optional": true, "dependencies": { "bare-os": "^2.1.0" } }, "node_modules/bare-stream": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", - "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", - "license": "Apache-2.0", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.3.0.tgz", + "integrity": "sha512-pVRWciewGUeCyKEuRxwv06M079r+fRjAQjBEK2P6OYGrO43O+Z0LrPZZEjlc4mB6C2RpZ9AxJ1s7NLEtOHO6eA==", "optional": true, "dependencies": { - "streamx": "^2.18.0" + "b4a": "^1.6.6", + "streamx": "^2.20.0" } }, "node_modules/base64-js": { @@ -2190,14 +2164,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -2216,10 +2188,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "license": "MIT", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -2229,7 +2200,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -2243,7 +2214,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -2251,8 +2221,7 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -2339,7 +2308,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -2349,7 +2317,6 @@ "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", "engines": { "node": "*" } @@ -2381,7 +2348,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -2511,10 +2477,9 @@ } }, "node_modules/chromium-bidi": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", - "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", - "license": "Apache-2.0", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", + "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", "dependencies": { "mitt": "3.0.1", "urlpattern-polyfill": "10.0.0", @@ -2752,7 +2717,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2765,10 +2729,9 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "license": "MIT", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -2883,7 +2846,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", "engines": { "node": ">= 14" } @@ -2956,12 +2918,11 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", - "license": "MIT", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -3049,7 +3010,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", @@ -3072,7 +3032,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3081,7 +3040,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -3098,10 +3056,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1312386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", - "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", - "license": "BSD-3-Clause" + "version": "0.0.1342118", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1342118.tgz", + "integrity": "sha512-75fMas7PkYNDTmDyb6PRJCH7ILmHLp+BhrZGeMsa4bCh40DTxgCz2NRy5UDzII4C5KuD0oBMZ9vXKhEl6UD/3w==" }, "node_modules/diff-sequences": { "version": "29.6.3", @@ -3156,8 +3113,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { "version": "1.5.4", @@ -3187,10 +3143,9 @@ "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -3199,7 +3154,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -3397,8 +3351,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -3417,7 +3370,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -3748,7 +3700,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3811,37 +3762,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "license": "MIT", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -3886,7 +3836,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -3906,7 +3855,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -3934,8 +3882,7 @@ "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "license": "MIT" + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -3975,7 +3922,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -4007,13 +3953,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "license": "MIT", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -4028,7 +3973,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -4036,8 +3980,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-up": { "version": "5.0.0", @@ -4115,7 +4058,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4124,7 +4066,6 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -4290,7 +4231,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", - "license": "MIT", "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -4501,7 +4441,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -4569,7 +4508,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -4594,8 +4532,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "BSD-3-Clause" + ] }, "node_modules/ignore": { "version": "5.3.1", @@ -4697,7 +4634,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "license": "MIT", "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -5764,8 +5700,7 @@ "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT" + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" }, "node_modules/jsdom": { "version": "24.1.1", @@ -5864,7 +5799,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -6427,10 +6361,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -6449,11 +6385,10 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -6466,7 +6401,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", "bin": { "mime": "cli.js" }, @@ -6543,8 +6477,7 @@ "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, "node_modules/mkdirp": { "version": "0.5.6", @@ -6559,10 +6492,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/multer": { "version": "1.4.5-lts.1", @@ -6602,7 +6534,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -6821,7 +6752,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -6918,7 +6848,6 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", - "license": "MIT", "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.0.2", @@ -6937,7 +6866,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" @@ -6992,7 +6920,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -7035,16 +6962,14 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "license": "MIT" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, "node_modules/picocolors": { "version": "1.0.1", @@ -7244,7 +7169,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -7279,7 +7203,6 @@ "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", - "license": "MIT", "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", @@ -7298,7 +7221,6 @@ "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", "engines": { "node": ">=12" } @@ -7306,8 +7228,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/psl": { "version": "1.9.0", @@ -7323,10 +7244,9 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "license": "MIT", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7342,34 +7262,35 @@ } }, "node_modules/puppeteer": { - "version": "22.15.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz", - "integrity": "sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==", + "version": "23.5.3", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.5.3.tgz", + "integrity": "sha512-FghmfBsr/UUpe48OiCg1gV3W4vVfQJKjQehbF07SjnQvEpWcvPTah1nykfGWdOQQ1ydJPIXcajzWN7fliCU3zw==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.3.0", + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.8.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1312386", - "puppeteer-core": "22.15.0" + "devtools-protocol": "0.0.1342118", + "puppeteer-core": "23.5.3", + "typed-query-selector": "^2.12.0" }, "bin": { - "puppeteer": "lib/esm/puppeteer/node/cli.js" + "puppeteer": "lib/cjs/puppeteer/node/cli.js" }, "engines": { "node": ">=18" } }, "node_modules/puppeteer-core": { - "version": "22.15.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", - "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.3.0", - "chromium-bidi": "0.6.3", - "debug": "^4.3.6", - "devtools-protocol": "0.0.1312386", + "version": "23.5.3", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.5.3.tgz", + "integrity": "sha512-V58MZD/B3CwkYsqSEQlHKbavMJptF04fzhMdUpiCRCmUVhwZNwSGEPhaiZ1f8I3ABQUirg3VNhXVB6Z1ubHXtQ==", + "dependencies": { + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.8.0", + "debug": "^4.3.7", + "devtools-protocol": "0.0.1342118", + "typed-query-selector": "^2.12.0", "ws": "^8.18.0" }, "engines": { @@ -7394,12 +7315,11 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "license": "BSD-3-Clause", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -7438,8 +7358,7 @@ "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", - "license": "MIT" + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" }, "node_modules/randombytes": { "version": "2.1.0", @@ -7455,7 +7374,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7464,7 +7382,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -7692,13 +7609,12 @@ } }, "node_modules/rollup": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", - "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", + "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", "dev": true, - "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "@types/estree": "1.0.6" }, "bin": { "rollup": "dist/bin/rollup" @@ -7708,22 +7624,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.20.0", - "@rollup/rollup-android-arm64": "4.20.0", - "@rollup/rollup-darwin-arm64": "4.20.0", - "@rollup/rollup-darwin-x64": "4.20.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", - "@rollup/rollup-linux-arm-musleabihf": "4.20.0", - "@rollup/rollup-linux-arm64-gnu": "4.20.0", - "@rollup/rollup-linux-arm64-musl": "4.20.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", - "@rollup/rollup-linux-riscv64-gnu": "4.20.0", - "@rollup/rollup-linux-s390x-gnu": "4.20.0", - "@rollup/rollup-linux-x64-gnu": "4.20.0", - "@rollup/rollup-linux-x64-musl": "4.20.0", - "@rollup/rollup-win32-arm64-msvc": "4.20.0", - "@rollup/rollup-win32-ia32-msvc": "4.20.0", - "@rollup/rollup-win32-x64-msvc": "4.20.0", + "@rollup/rollup-android-arm-eabi": "4.24.0", + "@rollup/rollup-android-arm64": "4.24.0", + "@rollup/rollup-darwin-arm64": "4.24.0", + "@rollup/rollup-darwin-x64": "4.24.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", + "@rollup/rollup-linux-arm-musleabihf": "4.24.0", + "@rollup/rollup-linux-arm64-gnu": "4.24.0", + "@rollup/rollup-linux-arm64-musl": "4.24.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", + "@rollup/rollup-linux-riscv64-gnu": "4.24.0", + "@rollup/rollup-linux-s390x-gnu": "4.24.0", + "@rollup/rollup-linux-x64-gnu": "4.24.0", + "@rollup/rollup-linux-x64-musl": "4.24.0", + "@rollup/rollup-win32-arm64-msvc": "4.24.0", + "@rollup/rollup-win32-ia32-msvc": "4.24.0", + "@rollup/rollup-win32-x64-msvc": "4.24.0", "fsevents": "~2.3.2" } }, @@ -7850,10 +7766,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "license": "MIT", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -7877,7 +7792,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -7885,14 +7799,15 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -7905,15 +7820,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "license": "MIT", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -7955,8 +7869,7 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -8082,7 +7995,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -8099,7 +8011,6 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "license": "MIT", "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -8113,7 +8024,6 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "license": "MIT", "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", @@ -8147,8 +8057,7 @@ "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/stack-utils": { "version": "2.0.6", @@ -8177,7 +8086,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8191,10 +8099,9 @@ } }, "node_modules/streamx": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", - "license": "MIT", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", + "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", "dependencies": { "fast-fifo": "^1.3.2", "queue-tick": "^1.0.1", @@ -8440,7 +8347,6 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", - "license": "MIT", "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" @@ -8454,7 +8360,6 @@ "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "license": "MIT", "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", @@ -8523,10 +8428,9 @@ } }, "node_modules/text-decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", - "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", - "license": "Apache-2.0", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.0.tgz", + "integrity": "sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==", "dependencies": { "b4a": "^1.6.4" } @@ -8541,8 +8445,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "license": "MIT" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/tmpl": { "version": "1.0.5", @@ -8578,7 +8481,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", "engines": { "node": ">=0.6" } @@ -8797,6 +8699,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -8823,7 +8730,6 @@ "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", - "license": "MIT", "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -8847,7 +8753,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -8856,7 +8761,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -8915,8 +8819,7 @@ "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "license": "MIT" + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -9324,7 +9227,6 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/package.json b/package.json index 3b29deb9..e365243a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "author": "Highsoft AS (http://www.highcharts.com/about)", "license": "MIT", "type": "module", - "version": "4.0.1", + "version": "4.0.3", "main": "./dist/index.esm.js", "engines": { "node": ">=18.12.0" @@ -19,7 +19,9 @@ "bin", "templates", "install.js", - "lib" + "lib", + "msg", + "public" ], "repository": { "url": "https://github.com/highcharts/node-export-server", @@ -59,7 +61,7 @@ "jsonwebtoken": "^9.0.2", "multer": "^1.4.5-lts.1", "prompts": "^2.4.2", - "puppeteer": "^22.15.0", + "puppeteer": "^23.5.3", "tarn": "^3.0.2", "uuid": "^10.0.0", "ws": "^8.18.0", diff --git a/public/index.html b/public/index.html index bd3fc62d..da6ca9bf 100644 --- a/public/index.html +++ b/public/index.html @@ -1,52 +1,47 @@ - - Highcharts Export Server - - - - - - - - - - - - - -
-
-
-

Highcharts Export Server

+ + + Highcharts Export Server + + + + + + + + + + + + + + + + +
+
+
+

Highcharts Export Server

+ +

This page allows you to experiment with different options for the - export server. + export server. If you use the public Export Server at + https://export.highcharts.com + you should read our Terms of use and Fair Usage Policy. +

- -
Your Highcharts configuration object.
-
- - - - - -
- The exact pixel width of the exported image. Defaults to chart.width - or 600px. Maximum width is 2000px. -
- - - -
- A scaling factor for a higher image resolution. Maximum scaling is - set to 4x. Remember that the width parameter has a higher precedence - over scaling. -
- - - -
- Either a chart, stockChart, mapChart, or a ganttChart (depending on - what product you use). -
- + + + + +
+ The exact pixel width of the exported image. Defaults to chart.width + or 600px. Maximum width is 2000px.
-
+ + + +
+ A scaling factor for a higher image resolution. Maximum scaling is + set to 4x. Remember that the width parameter has a higher precedence + over scaling. +
+ -
-
-

Result Preview

-
-
Click the Preview button to see a preview.
-
+ +
+ Either a chart, stockChart, mapChart, or a ganttChart (depending on what product you use).
+
+
-
- - +
+
+

Result Preview

+
+
Click the Preview button to see a preview.
+
-
- +
+ + +
+
+ +
+ + + - - + \ No newline at end of file diff --git a/tests/unit/cache.test.js b/tests/unit/cache.test.js index 8086706c..31676e8a 100644 --- a/tests/unit/cache.test.js +++ b/tests/unit/cache.test.js @@ -1,4 +1,3 @@ -// cacheManager.test.js import { extractVersion, extractModuleName } from '../../lib/cache'; describe('extractVersion', () => {