diff --git a/eslint.config.js b/eslint.config.js index e951d82..bdd7804 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -25,8 +25,12 @@ export default [ globals: { ...globals.node, // Add Node.js globals ...globals.browser, // Add browser globals - ...globals.es6, // Add ES6 globals (if not already included) - // ...js.configs.recommended.languageOptions?.globals, // Remove if you want to completely rely on globals package + ...globals.es6, // Add ES6 globals + ...globals.mocha, // Add Mocha globals (describe, it, before, after, etc.) + supertest: 'readonly', // Mark supertest as a global read-only variable + expect: 'readonly', // Mark expect as a global read-only variable if your assertion library isn't automatically detected + app: 'readonly', // Mark app as a global read-only variable + server: 'readonly', // Mark server as a global read-only variable }, }, }, diff --git a/src/serve_data.js b/src/serve_data.js index 5493ff2..16904ad 100644 --- a/src/serve_data.js +++ b/src/serve_data.js @@ -29,8 +29,8 @@ const packageJson = JSON.parse( ); const isLight = packageJson.name.slice(-6) === '-light'; -const serve_rendered = ( - await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`) +const serve_rendered = import( + `${!isLight ? `./serve_rendered.js` : `./serve_light.js`}` ).serve_rendered; export const serve_data = { diff --git a/src/serve_light.js b/src/serve_light.js index 48ed8b6..d1575ce 100644 --- a/src/serve_light.js +++ b/src/serve_light.js @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-unused-vars */ 'use strict'; diff --git a/src/serve_rendered.js b/src/serve_rendered.js index a7092e6..09a39df 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -61,7 +61,7 @@ const staticTypeRegex = new RegExp( ); const PATH_PATTERN = - /^((fill|stroke|width)\:[^\|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/; + /^((fill|stroke|width):[^|]+\|)*(enc:.+|-?\d+(\.\d*)?,-?\d+(\.\d*)?(\|-?\d+(\.\d*)?,-?\d+(\.\d*)?)+)/; const httpTester = /^https?:\/\//i; const mercator = new SphericalMercator(); @@ -273,7 +273,7 @@ function parseMarkerOptions(optionsList, marker) { break; // Icon offset as positive or negative pixel value in the following // format [offsetX],[offsetY] where [offsetY] is optional - case 'offset': + case 'offset': { const providedOffset = optionParts[1].split(','); // Set X-axis offset marker.offsetX = parseFloat(providedOffset[0]); @@ -282,6 +282,7 @@ function parseMarkerOptions(optionsList, marker) { marker.offsetY = parseFloat(providedOffset[1]); } break; + } } } } @@ -452,6 +453,7 @@ async function respondImage( } if (format === 'png' || format === 'webp') { + /* empty */ } else if (format === 'jpg' || format === 'jpeg') { format = 'jpeg'; } else { @@ -1486,68 +1488,78 @@ export const serve_rendered = { * @returns {object} */ getTerrainElevation: async function (data, param) { - return await new Promise(async (resolve, reject) => { - const image = new Image(); - image.onload = async () => { - const canvas = createCanvas(param['tile_size'], param['tile_size']); - const context = canvas.getContext('2d'); - context.drawImage(image, 0, 0); + return new Promise((resolve, reject) => { + const image = new Image(); // Create a new Image object + image.onload = () => { + try { + const canvas = createCanvas(param['tile_size'], param['tile_size']); + const context = canvas.getContext('2d'); + context.drawImage(image, 0, 0); - // calculate pixel coordinate of tile, - // see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates - let siny = Math.sin((param['lat'] * Math.PI) / 180); - // Truncating to 0.9999 effectively limits latitude to 89.189. This is - // about a third of a tile past the edge of the world tile. - siny = Math.min(Math.max(siny, -0.9999), 0.9999); - const xWorld = param['tile_size'] * (0.5 + param['long'] / 360); - const yWorld = - param['tile_size'] * - (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI)); + // calculate pixel coordinate of tile, + // see https://developers.google.com/maps/documentation/javascript/examples/map-coordinates + let siny = Math.sin((param['lat'] * Math.PI) / 180); + // Truncating to 0.9999 effectively limits latitude to 89.189. This is + // about a third of a tile past the edge of the world tile. + siny = Math.min(Math.max(siny, -0.9999), 0.9999); + const xWorld = param['tile_size'] * (0.5 + param['long'] / 360); + const yWorld = + param['tile_size'] * + (0.5 - Math.log((1 + siny) / (1 - siny)) / (4 * Math.PI)); - const scale = 1 << param['z']; + const scale = 1 << param['z']; - const xTile = Math.floor((xWorld * scale) / param['tile_size']); - const yTile = Math.floor((yWorld * scale) / param['tile_size']); + const xTile = Math.floor((xWorld * scale) / param['tile_size']); + const yTile = Math.floor((yWorld * scale) / param['tile_size']); - const xPixel = Math.floor(xWorld * scale) - xTile * param['tile_size']; - const yPixel = Math.floor(yWorld * scale) - yTile * param['tile_size']; - if ( - xPixel < 0 || - yPixel < 0 || - xPixel >= param['tile_size'] || - yPixel >= param['tile_size'] - ) { - return reject('Out of bounds Pixel'); + const xPixel = + Math.floor(xWorld * scale) - xTile * param['tile_size']; + const yPixel = + Math.floor(yWorld * scale) - yTile * param['tile_size']; + if ( + xPixel < 0 || + yPixel < 0 || + xPixel >= param['tile_size'] || + yPixel >= param['tile_size'] + ) { + return reject('Out of bounds Pixel'); + } + const imgdata = context.getImageData(xPixel, yPixel, 1, 1); + const red = imgdata.data[0]; + const green = imgdata.data[1]; + const blue = imgdata.data[2]; + let elevation; + if (param['encoding'] === 'mapbox') { + elevation = -10000 + (red * 256 * 256 + green * 256 + blue) * 0.1; + } else if (param['encoding'] === 'terrarium') { + elevation = red * 256 + green + blue / 256 - 32768; + } else { + elevation = 'invalid encoding'; + } + param['elevation'] = elevation; + param['red'] = red; + param['green'] = green; + param['blue'] = blue; + resolve(param); + } catch (error) { + reject(error); // Catch any errors during canvas operations } - const imgdata = context.getImageData(xPixel, yPixel, 1, 1); - const red = imgdata.data[0]; - const green = imgdata.data[1]; - const blue = imgdata.data[2]; - let elevation; - if (param['encoding'] === 'mapbox') { - elevation = -10000 + (red * 256 * 256 + green * 256 + blue) * 0.1; - } else if (param['encoding'] === 'terrarium') { - elevation = red * 256 + green + blue / 256 - 32768; - } else { - elevation = 'invalid encoding'; - } - param['elevation'] = elevation; - param['red'] = red; - param['green'] = green; - param['blue'] = blue; - resolve(param); }; image.onerror = (err) => reject(err); - if (param['format'] === 'webp') { + + // Load the image data - handle the sharp conversion outside the Promise + (async () => { try { - const img = await sharp(data).toFormat('png').toBuffer(); - image.src = img; + if (param['format'] === 'webp') { + const img = await sharp(data).toFormat('png').toBuffer(); + image.src = `data:image/png;base64,${img.toString('base64')}`; // Set data URL + } else { + image.src = data; + } } catch (err) { - reject(err); + reject(err); // Reject promise on sharp error } - } else { - image.src = data; - } + })(); }); }, }; diff --git a/src/server.js b/src/server.js index dd09db5..24d2197 100644 --- a/src/server.js +++ b/src/server.js @@ -30,8 +30,8 @@ const packageJson = JSON.parse( ); const isLight = packageJson.name.slice(-6) === '-light'; -const serve_rendered = ( - await import(`${!isLight ? `./serve_rendered.js` : `./serve_light.js`}`) +const serve_rendered = import( + `${!isLight ? `./serve_rendered.js` : `./serve_light.js`}` ).serve_rendered; /** @@ -148,13 +148,19 @@ async function start(opts) { }), ); + let dataDecoratorFunc = null; // Initialize to null if (options.dataDecorator) { try { - options.dataDecoratorFunc = require( - path.resolve(paths.root, options.dataDecorator), - ); - } catch (e) {} + const dataDecoratorPath = path.resolve(paths.root, options.dataDecorator); + const module = await import(dataDecoratorPath); + dataDecoratorFunc = module.default; + } catch (e) { + console.error(`Error loading data decorator: ${e}`); + // Handle the error (e.g., set a default decorator) + dataDecoratorFunc = null; // Or a default function + } } + options.dataDecoratorFunc = dataDecoratorFunc; const data = clone(config.data || {}); @@ -671,7 +677,7 @@ async function start(opts) { return null; } - if (wmts.hasOwnProperty('serve_rendered') && !wmts.serve_rendered) { + if (Object.hasOwn(wmts, 'serve_rendered') && !wmts.serve_rendered) { return null; } diff --git a/src/utils.js b/src/utils.js index 5dab80a..cf2a724 100644 --- a/src/utils.js +++ b/src/utils.js @@ -35,7 +35,6 @@ export function allowedScales(scale, maxScale = 9) { return 1; } - // eslint-disable-next-line security/detect-non-literal-regexp const regex = new RegExp(`^[2-${maxScale}]x$`); if (!regex.test(scale)) { return null; @@ -152,7 +151,7 @@ export function getTileUrls( const hostParts = urlObject.host.split('.'); const relativeSubdomainsUsable = hostParts.length > 1 && - !/^([0-9]{1,3}\.){3}[0-9]{1,3}(\:[0-9]+)?$/.test(urlObject.host); + !/^([0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]+)?$/.test(urlObject.host); const newDomains = []; for (const domain of domains) { if (domain.indexOf('*') !== -1) { @@ -238,7 +237,7 @@ export function fixTileJSONCenter(tileJSON) { export function readFile(filename) { return new Promise((resolve, reject) => { const sanitizedFilename = path.normalize(filename); // Normalize path, remove .. - // eslint-disable-next-line security/detect-non-literal-fs-filename + fs.readFile(String(sanitizedFilename), (err, data) => { if (err) { reject(err); @@ -260,7 +259,7 @@ export function readFile(filename) { */ async function getFontPbf(allowedFonts, fontPath, name, range, fallbacks) { if (!allowedFonts || (allowedFonts[name] && fallbacks)) { - const fontMatch = name?.match(/^[\p{L}\p{N} \-\.~!*'()@&=+,#$\[\]]+$/u); + const fontMatch = name?.match(/^[\p{L}\p{N} \-.~!*'()@&=+,#$[\]]+$/u); const sanitizedName = fontMatch?.[0] || 'invalid'; if (!name || typeof name !== 'string' || name.trim() === '' || !fontMatch) { console.error( @@ -394,7 +393,8 @@ export function isValidHttpUrl(string) { try { url = new URL(string); - } catch (_) { + } catch (e) { + void e; return false; } @@ -412,15 +412,19 @@ export function isValidHttpUrl(string) { */ export async function fetchTileData(source, sourceType, z, x, y) { if (sourceType === 'pmtiles') { - return await new Promise(async (resolve) => { + try { const tileinfo = await getPMtilesTile(source, z, x, y); - if (!tileinfo?.data) return resolve(null); - resolve({ data: tileinfo.data, headers: tileinfo.header }); - }); + if (!tileinfo?.data) return null; + return { data: tileinfo.data, headers: tileinfo.header }; + } catch (error) { + console.error('Error fetching PMTiles tile:', error); + return null; // Or throw the error if you want to propagate it + } } else if (sourceType === 'mbtiles') { - return await new Promise((resolve) => { + return new Promise((resolve) => { source.getTile(z, x, y, (err, tileData, tileHeader) => { if (err) { + console.error('Error fetching MBTiles tile:', err); return resolve(null); } resolve({ data: tileData, headers: tileHeader });