diff --git a/src/server.ts b/src/server.ts
index 554e962..a112ed7 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -214,11 +214,19 @@ function setupRoutes(): void {
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
+ const escapeHtml = (text: string): string => {
+ return text.replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ };
+
const tableRows = locations.map((location, index) => `
${index + 1} |
- ${location.address} |
- ${location.description || 'No additional details'} |
+ ${escapeHtml(location.address)} |
+ ${escapeHtml(location.description || 'No additional details')} |
${location.created_at ? formatDate(location.created_at) : 'Unknown'} |
${location.persistent ? 'Persistent' : (location.created_at ? formatTimeRemaining(location.created_at) : 'Unknown')} |
diff --git a/src/services/MapImageService.ts b/src/services/MapImageService.ts
index 1197c62..1bf0fea 100644
--- a/src/services/MapImageService.ts
+++ b/src/services/MapImageService.ts
@@ -19,9 +19,9 @@ export class MapImageService {
async generateMapImage(locations: Location[], options: Partial = {}): Promise {
const opts = { ...this.defaultOptions, ...options };
- console.log('Generating Mapbox static map focused on location data');
- console.log('Canvas size:', opts.width, 'x', opts.height);
- console.log('Number of locations:', locations.length);
+ console.info('Generating Mapbox static map focused on location data');
+ console.info('Canvas size:', opts.width, 'x', opts.height);
+ console.info('Number of locations:', locations.length);
const mapboxBuffer = await this.fetchMapboxStaticMapAutoFit(opts, locations);
@@ -47,7 +47,7 @@ export class MapImageService {
let overlays = '';
locations.forEach((location, index) => {
if (location.latitude && location.longitude) {
- console.log(`Location ${index + 1}: ${location.latitude}, ${location.longitude} (${location.address})`);
+ console.info(`Location ${index + 1}: ${location.latitude}, ${location.longitude} (${location.address})`);
// Correct format: pin-s-label+color(lng,lat)
const color = location.persistent ? 'ff9800' : 'ff0000'; // Orange for persistent, red for regular
const label = (index + 1).toString();
@@ -58,7 +58,7 @@ export class MapImageService {
// Remove trailing comma
overlays = overlays.replace(/,$/, '');
- console.log('Generated overlays string:', overlays);
+ console.info('Generated overlays string:', overlays);
// Build Mapbox Static Maps URL with auto-fit
let mapboxUrl;
@@ -72,8 +72,8 @@ export class MapImageService {
mapboxUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/${fallbackLng},${fallbackLat},10/${options.width}x${options.height}?access_token=${mapboxToken}`;
}
- console.log('Fetching Mapbox static map with auto-fit...');
- console.log('URL:', mapboxUrl.replace(mapboxToken, 'TOKEN_HIDDEN'));
+ console.info('Fetching Mapbox static map with auto-fit...');
+ console.info('URL:', mapboxUrl.replace(mapboxToken, 'TOKEN_HIDDEN'));
return new Promise((resolve) => {
const request = https.get(mapboxUrl, { timeout: 10000 }, (response) => {
@@ -81,7 +81,7 @@ export class MapImageService {
const chunks: Buffer[] = [];
response.on('data', (chunk) => chunks.push(chunk));
response.on('end', () => {
- console.log('Mapbox static map fetched successfully');
+ console.info('Mapbox static map fetched successfully');
resolve(Buffer.concat(chunks));
});
} else {
@@ -103,76 +103,27 @@ export class MapImageService {
});
}
- /**
- * Legacy method - keeping for potential fallback
- */
- private async fetchMapboxStaticMap(centerLat: number, centerLng: number, zoom: number, options: MapOptions, locations: Location[]): Promise {
- const mapboxToken = process.env.MAPBOX_ACCESS_TOKEN;
- if (!mapboxToken) {
- console.error('No Mapbox token available');
- return null;
- }
-
- // Build overlay string for location markers
- let overlays = '';
- locations.forEach((location, index) => {
- if (location.latitude && location.longitude) {
- console.log(`Location ${index + 1}: ${location.latitude}, ${location.longitude} (${location.address})`);
- const color = location.persistent ? 'orange' : 'red';
- const label = (index + 1).toString();
- overlays += `pin-s-${label}+${color}(${location.longitude},${location.latitude}),`;
- }
- });
-
- // Remove trailing comma
- overlays = overlays.replace(/,$/, '');
-
- // Build Mapbox Static Maps URL
- let mapboxUrl;
- if (overlays) {
- mapboxUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/${overlays}/${centerLng},${centerLat},${zoom}/${options.width}x${options.height}?access_token=${mapboxToken}`;
- } else {
- mapboxUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/${centerLng},${centerLat},${zoom}/${options.width}x${options.height}?access_token=${mapboxToken}`;
- }
-
- console.log('Fetching Mapbox static map...');
- console.log('URL:', mapboxUrl.replace(mapboxToken, 'TOKEN_HIDDEN'));
-
- return new Promise((resolve) => {
- const request = https.get(mapboxUrl, { timeout: 10000 }, (response) => {
- if (response.statusCode === 200) {
- const chunks: Buffer[] = [];
- response.on('data', (chunk) => chunks.push(chunk));
- response.on('end', () => {
- console.log('Mapbox static map fetched successfully');
- resolve(Buffer.concat(chunks));
- });
- } else {
- console.error('Mapbox API error:', response.statusCode);
- resolve(null);
- }
- });
-
- request.on('error', (err) => {
- console.error('Error fetching Mapbox map:', err.message);
- resolve(null);
- });
-
- request.on('timeout', () => {
- console.error('Mapbox request timeout');
- request.destroy();
- resolve(null);
- });
- });
- }
/**
* Generate a simple error image when Mapbox fails
*/
private generateErrorImage(options: MapOptions): Buffer {
- // Return a simple text-based error - in a real implementation you'd create a proper error image
- const errorText = `Map generation failed - Mapbox API error\nSize: ${options.width}x${options.height}`;
- return Buffer.from(errorText, 'utf8');
+ // Generate a simple 1x1 transparent PNG as fallback
+ // This is a valid PNG header + IHDR + IDAT + IEND for a 1x1 transparent pixel
+ const transparentPng = Buffer.from([
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, // PNG signature
+ 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, // IHDR chunk
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, // 1x1 dimensions
+ 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, // RGBA, no compression
+ 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, // IDAT chunk
+ 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, // Compressed data
+ 0x05, 0x00, 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, // (transparent pixel)
+ 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, // IEND chunk
+ 0x42, 0x60, 0x82
+ ]);
+
+ console.info('Generated transparent PNG fallback due to Mapbox failure');
+ return transparentPng;
}
}