Guest User

Untitled

a guest
Dec 28th, 2024
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import dotenv from 'dotenv';
  2. dotenv.config();
  3.  
  4. import helmet from 'helmet';
  5. import express from 'express';
  6. import puppeteer from 'puppeteer-core';
  7. import { logError, logInfo } from './logger.mjs';
  8. import 'express-async-errors';
  9. import rateLimit from 'express-rate-limit';
  10. import cors from 'cors';
  11.  
  12. const app = express();
  13.  
  14. // Middleware for API key validation
  15. const validateApiKey = (req, res, next) => {
  16.     const apiKey = req.header('x-api-key');
  17.    
  18.     if (!apiKey || apiKey !== process.env.API_KEY) {
  19.         return res.status(401).json({
  20.             success: false,
  21.             message: 'Invalid or missing API key'
  22.         });
  23.     }
  24.     next();
  25. };
  26.  
  27. app.use(helmet());
  28. app.use(cors());
  29. app.use(express.json({ limit: process.env.BODY_SIZE_LIMIT || '10mb' }));
  30. app.use(validateApiKey);
  31.  
  32. // Rate limit setup
  33. const limiter = rateLimit({
  34.     windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60 * 60 * 1000,
  35.     max: parseInt(process.env.RATE_LIMIT_MAX, 10) || 500,
  36.     message: {
  37.         success: false,
  38.         message: 'Too many requests from this IP, please try again after an hour.'
  39.     },
  40.     keyGenerator: (req) => req.socket.remoteAddress,
  41.     standardHeaders: true,
  42.     legacyHeaders: false,
  43. });
  44.  
  45. app.use(limiter);
  46.  
  47. // Handle large input errors
  48. app.use((err, req, res, next) => {
  49.     if (err.type === 'entity.too.large') {
  50.         return res.status(413).json({
  51.             success: false,
  52.             message: 'Payload too large. Please reduce the size of your input.',
  53.         });
  54.     }
  55.     next(err);
  56. });
  57.  
  58. // Usage endpoint
  59. app.get('/', (req, res) => {
  60.     res.status(200).json({
  61.         success: true,
  62.         message: 'Welcome to the Image Generation API!',
  63.         usage: {
  64.             endpoint: '/generate-image',
  65.             method: 'POST',
  66.             description: 'Generate an image from HTML template.',
  67.             parameters: {
  68.                 htmlTemplate: 'HTML template as a string (required)',
  69.                 data: 'An object containing dynamic data to replace in the template (optional)',
  70.             },
  71.             exampleRequest: {
  72.                 htmlTemplate: '<h1>{{title}}</h1>',
  73.                 data: { title: 'Hello World' },
  74.             },
  75.             exampleResponse: {
  76.                 success: true,
  77.                 message: 'Image generated successfully',
  78.                 image: '<base64-image>',
  79.             },
  80.         },
  81.     });
  82. });
  83.  
  84. // Generate image endpoint
  85. app.post('/generate-image', async (req, res) => {
  86.     const { htmlTemplate, data = {} } = req.body;
  87.  
  88.     if (!htmlTemplate) {
  89.         return res.status(400).json({
  90.             success: false,
  91.             message: 'htmlTemplate is required',
  92.         });
  93.     }
  94.  
  95.     try {
  96.         const base64Data = await generateImageFromHtml({ htmlTemplate, data });
  97.  
  98.         res.status(200).json({
  99.             success: true,
  100.             message: 'Image generated successfully',
  101.             image: base64Data,
  102.         });
  103.     } catch (error) {
  104.         logError('Error generating image:', error);
  105.         res.status(500).json({
  106.             success: false,
  107.             message: 'An error occurred while generating the image',
  108.             error: error.message,
  109.         });
  110.     }
  111. });
  112.  
  113. let browser;
  114.  
  115. async function initBrowser() {
  116.     try {
  117.         if (!browser || !(await browser.isConnected())) {
  118.             browser = await puppeteer.launch({
  119.                 args: [
  120.                     '--no-sandbox',
  121.                     '--disable-setuid-sandbox',
  122.                     '--disable-dev-shm-usage',
  123.                     '--disable-cache',
  124.                     '--disable-accelerated-2d-canvas',
  125.                     '--disable-gpu',
  126.                     '--disable-extensions',
  127.                 ],
  128.                 executablePath: process.env.PUPPETEER_EXECUTABLE_PATH,
  129.                 headless: process.env.PUPPETEER_HEADLESS === 'false' ? false : "new",
  130.                 ignoreDefaultArgs: ['--enable-automation'],
  131.             });
  132.             logInfo("Browser initialized successfully.");
  133.         }
  134.     } catch (error) {
  135.         logError("Error initializing the browser:", error);
  136.         throw new Error('Failed to initialize the browser: ' + error.message);
  137.     }
  138. }
  139.  
  140. async function closeBrowser() {
  141.     if (browser) {
  142.         try {
  143.             await browser.close();
  144.             logInfo("Browser closed successfully.");
  145.         } catch (error) {
  146.             logError("Error closing the browser:", error);
  147.         } finally {
  148.             browser = null;
  149.         }
  150.     }
  151. }
  152.  
  153. async function generateImageFromHtml({ htmlTemplate, data = {}, retryCount = 3 }) {
  154.     let page;
  155.     for (let attempt = 0; attempt < retryCount; attempt++) {
  156.         try {
  157.             await initBrowser();
  158.  
  159.             page = await browser.newPage();
  160.             await page.setCacheEnabled(false);
  161.             await page.setRequestInterception(true);
  162.  
  163.             page.on('request', (request) => {
  164.                 if (['redirect'].includes(request.redirectChain().length)) {
  165.                     request.abort();
  166.                 } else {
  167.                     request.continue();
  168.                 }
  169.             });
  170.  
  171.             const html = Object.keys(data).reduce(
  172.                 (acc, key) => acc.replace(new RegExp(`{{${key}}}`, 'g'), data[key]),
  173.                 htmlTemplate
  174.             );
  175.            
  176.             await page.setViewport({
  177.                 width: 1920,
  178.                 height: 1080,
  179.                 deviceScaleFactor: 2
  180.             });
  181.  
  182.             await page.setContent(html, { waitUntil: 'networkidle0' });
  183.             await new Promise(r => setTimeout(r, 2000));
  184.  
  185.             const screenshotOptions = {
  186.                 type: process.env.IMAGE_TYPE === "png" ? 'png' : "jpeg",
  187.                 encoding: 'base64',
  188.                 fullPage: true
  189.             };
  190.  
  191.             if (process.env.IMAGE_TYPE !== "png" && process.env.IMAGE_QUALITY) {
  192.                 screenshotOptions.quality = parseInt(process.env.IMAGE_QUALITY, 10);
  193.             }
  194.            
  195.             const base64Data = await page.screenshot(screenshotOptions);
  196.             return base64Data;
  197.  
  198.         } catch (error) {
  199.             logError(`Attempt ${attempt + 1} failed:`, error);
  200.             if (attempt === retryCount - 1) {
  201.                 throw new Error('Error generating image: ' + error.message);
  202.             }
  203.             await closeBrowser();
  204.             await new Promise(r => setTimeout(r, 2000));
  205.         } finally {
  206.             if (page) {
  207.                 await page.close();
  208.             }
  209.         }
  210.     }
  211. }
  212.  
  213. function errorHandler(err, req, res, next) {
  214.     logError('Error:', err);
  215.     res.status(err.status || 500).json({
  216.         success: false,
  217.         message: err.message || 'Internal Server Error',
  218.         stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
  219.     });
  220. }
  221.  
  222. app.use(errorHandler);
  223.  
  224. const PORT = process.env.PORT || 3000;
  225. app.listen(PORT, () => {
  226.     logInfo(`Server is running on port ${PORT}`);
  227. });
  228.  
  229. process.on('exit', closeBrowser);
  230. process.on('SIGINT', closeBrowser);
  231. process.on('SIGTERM', closeBrowser);
  232. process.on('uncaughtException', async (error) => {
  233.     console.error('Uncaught exception:', error);
  234.     await closeBrowser();
  235.     process.exit(1);
  236. });
Add Comment
Please, Sign In to add comment