/** * POST /api/offers/website-screenshot * Body: { url } — captures the first page of the given (public) website via the * free WordPress mShots service (no API key; it renders the page for us, so our * server never fetches the target URL itself), then crops the top into a wide * hero banner with sharp and returns it as a JPEG data URL. Used by the quote * modal to embed the customer's website into the Angebot PDF. * * mShots returns a placeholder GIF (307 → default.gif) while the screenshot is * still being generated — poll until a real JPEG/PNG arrives. */ import refreshTokenHelper from '../../utils/refreshTokenHelper' import errorHandlingHelper from '../../utils/errorHandlingHelper' import forceLogoutHelper from '../../utils/forceLogoutHelper' // Matches the PDF hero: full content width (515 pt) at ~2.4:1. const BANNER_WIDTH = 1200 const BANNER_HEIGHT = 500 const normalizeUrl = (raw: any): string | null => { let u = String(raw || '').trim() if (!u) return null if (!/^https?:\/\//i.test(u)) u = 'https://' + u try { const parsed = new URL(u) if (!/^https?:$/.test(parsed.protocol)) return null if (!parsed.hostname.includes('.')) return null return parsed.toString() } catch { return null } } const captureWebsite = async (url: string): Promise => { const endpoint = 'https://s0.wp.com/mshots/v1/' + encodeURIComponent(url) + '?w=1280&vpw=1280&vph=1024' let lastType = '' for (let attempt = 0; attempt < 8; attempt++) { if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, 1500)) const res: any = await $fetch.raw(endpoint, { responseType: 'arrayBuffer', timeout: 15000 }) lastType = String(res.headers.get('content-type') || '') if (/image\/(jpe?g|png)/i.test(lastType) && res._data) { return Buffer.from(res._data) } } throw new Error(`screenshot service returned no image (last content-type: ${lastType})`) } const handleFunc = async (event: any, authToken: string | null = null) => { // Auth gate only — the capture itself needs no iDempiere call. const token = authToken ?? await getTokenHelper(event) if (!token) return { status: 401, message: 'Not authenticated' } const body = await readBody(event) const url = normalizeUrl(body?.url) if (!url) return { status: 400, message: 'A valid website URL is required' } try { const raw = await captureWebsite(url) const sharp = (await import('sharp')).default const banner = await sharp(raw) .resize(BANNER_WIDTH, BANNER_HEIGHT, { fit: 'cover', position: 'top' }) .jpeg({ quality: 80 }) .toBuffer() return { status: 200, dataUrl: 'data:image/jpeg;base64,' + banner.toString('base64'), url } } catch (err: any) { console.error('Website screenshot error:', err?.message || err) return { status: 502, message: 'Screenshot could not be captured' } } } export default defineEventHandler(async (event) => { let data: any = {} try { data = await handleFunc(event) } catch (err: any) { try { let authToken: any = await refreshTokenHelper(event) data = await handleFunc(event, authToken) } catch (error: any) { data = errorHandlingHelper(err?.data ?? err, error?.data ?? error) forceLogoutHelper(event, data) } } return data })