UNPKG

@nanggo/social-preview

Version:

Generate beautiful social media preview images from any URL

184 lines (183 loc) 8.27 kB
"use strict"; /** * Social Preview Generator * Generate beautiful social media preview images from any URL */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generatePreview = generatePreview; exports.generateImageWithTemplate = generateImageWithTemplate; exports.generatePreviewWithDetails = generatePreviewWithDetails; const types_1 = require("./types"); const metadata_extractor_1 = require("./core/metadata-extractor"); const image_generator_1 = require("./core/image-generator"); const registry_1 = require("./templates/registry"); const validators_1 = require("./utils/validators"); const image_security_1 = require("./utils/image-security"); const overlay_generator_1 = require("./core/overlay-generator"); const template_image_processing_1 = require("./core/template-image-processing"); const preview_cache_1 = require("./utils/preview-cache"); const sharp_cache_1 = require("./utils/sharp-cache"); // Initialize Sharp security settings (0, image_security_1.initializeSharpSecurity)(); __exportStar(require("./exports"), exports); // Note: Sharp caching utilities (createCachedSVG, createCachedCanvas) are used internally // Direct Sharp instance creation is now recommended over pooling for better reliability const FALLBACK_PREVIEW_CACHE_TTL_MS = 30_000; /** * Generate a social preview image from a URL * @param url - The URL to generate preview for * @param options - Configuration options * @returns Buffer containing the generated image */ async function generatePreview(url, options = {}) { const result = await generatePreviewWithDetails(url, options); return result.buffer; } /** * Generate image with specific template */ async function generateImageWithTemplate(metadata, template, options) { // Use centralized validation gateway - returns sanitized options const sanitizedOptions = (0, validators_1.sanitizeOptions)(options); const width = sanitizedOptions.width || image_generator_1.DEFAULT_DIMENSIONS.width; const height = sanitizedOptions.height || image_generator_1.DEFAULT_DIMENSIONS.height; const quality = sanitizedOptions.quality || 90; try { // Validate dimensions once at the start (0, validators_1.validateDimensions)(width, height); // Create base image with template-specific processing const baseImage = await (0, template_image_processing_1.processImageForTemplate)(metadata, template, width, height, sanitizedOptions); // Generate overlay using template's overlay generator let overlayBuffer; if (template.overlayGenerator) { const overlaySvg = template.overlayGenerator(metadata, width, height, options, template); overlayBuffer = sharp_cache_1.svgCache.getCachedSVG(overlaySvg) ?? sharp_cache_1.svgCache.cacheSVG(overlaySvg); } else { // Fallback to default overlay generation for custom templates overlayBuffer = await (0, overlay_generator_1.generateDefaultOverlay)(metadata, template, width, height, options); } // Composite overlay on base image const finalImage = await baseImage .composite([ { input: overlayBuffer, top: 0, left: 0, }, ]) .jpeg({ quality, progressive: true, mozjpeg: true }) .toBuffer(); return finalImage; } catch (error) { throw new types_1.PreviewGeneratorError(types_1.ErrorType.IMAGE_ERROR, `Failed to generate image with template: ${error instanceof Error ? error.message : String(error)}`, error); } } /** * Generate preview with full result details */ async function generatePreviewWithDetails(url, options = {}) { try { // Validate options first (0, validators_1.validateOptions)(options); // Set default options const finalOptions = { template: 'modern', width: image_generator_1.DEFAULT_DIMENSIONS.width, height: image_generator_1.DEFAULT_DIMENSIONS.height, quality: 90, cache: false, ...options, }; const shouldCache = finalOptions.cache === true; if (shouldCache) { const cached = (0, preview_cache_1.getCachedPreview)(url, finalOptions); if (cached) { return { ...cached, cached: true }; } } // Extract metadata from URL once let metadata; try { metadata = await (0, metadata_extractor_1.extractMetadata)(url, finalOptions.security); // Validate metadata if (!(0, metadata_extractor_1.validateMetadata)(metadata)) { // Apply fallbacks if metadata is incomplete metadata = (0, metadata_extractor_1.applyFallbacks)(metadata, url); } } catch (error) { // If metadata extraction fails completely, use fallback if (finalOptions.fallback?.strategy === 'generate' || finalOptions.fallback?.strategy === 'auto') { const buffer = await (0, image_generator_1.createFallbackImage)(url, finalOptions); // Create minimal metadata for fallback const fallbackMetadata = (0, metadata_extractor_1.applyFallbacks)({}, url); const fallbackResult = { buffer, format: 'jpeg', dimensions: { width: finalOptions.width || image_generator_1.DEFAULT_DIMENSIONS.width, height: finalOptions.height || image_generator_1.DEFAULT_DIMENSIONS.height, }, metadata: fallbackMetadata, template: finalOptions.template || 'modern', cached: false, }; if (shouldCache) { (0, preview_cache_1.setCachedPreview)(url, finalOptions, fallbackResult, FALLBACK_PREVIEW_CACHE_TTL_MS); } return fallbackResult; } throw error; } // Get template configuration const templateName = finalOptions.template || 'modern'; const template = registry_1.templates[templateName]; if (!template) { throw new types_1.PreviewGeneratorError(types_1.ErrorType.TEMPLATE_ERROR, `Template "${templateName}" not found`); } // Generate image based on template - reuse metadata instead of re-extracting const buffer = await generateImageWithTemplate(metadata, template || registry_1.templates.modern, finalOptions); const result = { buffer, format: 'jpeg', dimensions: { width: finalOptions.width || image_generator_1.DEFAULT_DIMENSIONS.width, height: finalOptions.height || image_generator_1.DEFAULT_DIMENSIONS.height, }, metadata, template: finalOptions.template || 'modern', cached: false, }; if (shouldCache) { (0, preview_cache_1.setCachedPreview)(url, finalOptions, result); } return result; } catch (error) { if (error instanceof types_1.PreviewGeneratorError) { throw error; } throw new types_1.PreviewGeneratorError(types_1.ErrorType.IMAGE_ERROR, `Failed to generate preview with details for ${url}: ${error instanceof Error ? error.message : String(error)}`, error); } }