best-email-builder 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +74 -8
- package/dist/ai-server.js +116 -1
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.js +639 -176
- package/dist/ai.js.map +1 -1
- package/dist/builder.js +4 -4
- package/dist/{chunk-2Y7CDXZM.js → chunk-3RPWH5PC.js} +3 -3
- package/dist/{chunk-H4W65GBX.js → chunk-C6WHIBGH.js} +2 -2
- package/dist/{chunk-H4W65GBX.js.map → chunk-C6WHIBGH.js.map} +1 -1
- package/dist/{chunk-BAA5XMWY.js → chunk-CTZ56YJG.js} +252 -28
- package/dist/chunk-CTZ56YJG.js.map +1 -0
- package/dist/{chunk-7QQH6MTV.js → chunk-QH5OI43L.js} +4 -4
- package/dist/{chunk-N7DEVAIP.js → chunk-QRUMXCGW.js} +2 -2
- package/dist/{chunk-2RKVJQSD.js → chunk-UMVBT7NW.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/library.js +4 -4
- package/dist/server.js +4 -4
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/ai/ConnectAi.d.ts +5 -0
- package/dist/types/ai/ModelPicker.d.ts +34 -0
- package/dist/types/ai/agent.d.ts +31 -0
- package/dist/types/ai/agentEndpoint.d.ts +69 -0
- package/dist/types/ai/claude.d.ts +31 -5
- package/dist/types/ai/createAi.d.ts +5 -3
- package/dist/types/ai/draft.d.ts +10 -4
- package/dist/types/ai/generator.d.ts +10 -4
- package/dist/types/ai/index.d.ts +3 -2
- package/dist/types/ai/keys.d.ts +28 -26
- package/dist/types/ai/providers.d.ts +58 -1
- package/dist/types/ai/rewrite.d.ts +2 -1
- package/dist/types/ai/server.d.ts +3 -1
- package/dist/types/ai/subjects.d.ts +2 -1
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/engine/NativeAdapter.d.ts +1 -1
- package/dist/types/server/handler.d.ts +3 -0
- package/package.json +1 -1
- package/dist/chunk-BAA5XMWY.js.map +0 -1
- /package/dist/{chunk-2Y7CDXZM.js.map → chunk-3RPWH5PC.js.map} +0 -0
- /package/dist/{chunk-7QQH6MTV.js.map → chunk-QH5OI43L.js.map} +0 -0
- /package/dist/{chunk-N7DEVAIP.js.map → chunk-QRUMXCGW.js.map} +0 -0
- /package/dist/{chunk-2RKVJQSD.js.map → chunk-UMVBT7NW.js.map} +0 -0
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/assets.js","../src/server/errors.js","../src/validation/quality.js","../src/server/compileMjml.js","../src/server/render.js","../src/server/sendTest.js","../src/validation/projectValidator.js","../src/server/templates.js","../src/server/variables.js","../src/server/brand.js","../src/server/brandImport.js","../src/server/handler.js","../src/server/node.js"],"sourcesContent":["// @ts-check\n/**\n * UPLOADED IMAGES.\n *\n * The type is read from the BYTES (PNG, JPEG, GIF only), never trusted from the\n * file name or the declared type: an SVG renamed `.png` carries scripts and is\n * refused. The public key is derived from the content hash (32 hex characters\n * of SHA-256 + extension): the same file uploaded twice is stored once, and a\n * key reveals nothing and cannot be guessed.\n *\n * Storage is a REPOSITORY supplied by the application (database, object\n * storage…); `createMemoryAssetRepository` is the reference implementation.\n */\n\nimport { createHash } from 'node:crypto';\nimport { identity } from '../i18n/index.js';\nimport { ASSET_KEY_PATTERN, MAX_IMAGE_BYTES, detectImage } from '../validation/images.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {{\n * key: string,\n * sha256: string,\n * contentType: string,\n * size: number,\n * name: string|null,\n * data: Uint8Array,\n * createdBy: string|null,\n * }} AssetRecord\n *\n * @typedef {{\n * findBySha256: (sha256: string) => Promise<{key: string}|null>,\n * insert: (record: AssetRecord) => Promise<void>,\n * get: (key: string) => Promise<{contentType: string, data: Uint8Array, sha256?: string}|null>,\n * }} AssetRepository\n */\n\n/**\n * @param {Uint8Array} bytes\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function inspectAsset(bytes, { t = identity } = {}) {\n if (!bytes || bytes.length === 0) throw new HttpError(400, t('Empty file'), 'EMPTY_FILE');\n if (bytes.length > MAX_IMAGE_BYTES) {\n throw new HttpError(413, t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) }), 'TOO_LARGE');\n }\n const image = detectImage(bytes);\n if (!image) throw new HttpError(415, t('Format rejected: PNG, JPEG or GIF expected'), 'UNSUPPORTED_IMAGE');\n const sha256 = createHash('sha256').update(bytes).digest('hex');\n return { type: image.type, key: `${sha256.slice(0, 32)}.${image.ext}`, sha256 };\n}\n\nfunction cleanName(name) {\n // eslint-disable-next-line no-control-regex\n return String(name || '').replace(/[\\u0000-\\u001f\\u007f<>\"]/g, '').trim().slice(0, 255) || null;\n}\n\n/**\n * @param {{repository: AssetRepository, publicPath: string, t?: import('../i18n/index.js').Translate}} options\n */\nexport function createAssetService({ repository, publicPath, t = identity }) {\n const prefix = publicPath.endsWith('/') ? publicPath : `${publicPath}/`;\n return {\n /**\n * @param {{bytes: Uint8Array, name?: string|null, user?: {id?: string|number, name?: string}|null}} input\n */\n async upload({ bytes, name = null, user = null }) {\n const { type, key, sha256 } = inspectAsset(bytes, { t });\n const result = { key, path: `${prefix}${key}`, contentType: type, size: bytes.length };\n const existing = await repository.findBySha256(sha256);\n if (existing) return { ...result, key: existing.key, path: `${prefix}${existing.key}`, created: false };\n try {\n await repository.insert({\n key,\n sha256,\n contentType: type,\n size: bytes.length,\n name: cleanName(name),\n data: bytes,\n createdBy: user ? String(user.name ?? user.id ?? '') || null : null,\n });\n } catch (error) {\n // Two simultaneous uploads of the same file: the other one won, same image.\n if (await repository.findBySha256(sha256)) return { ...result, created: false };\n throw error;\n }\n return { ...result, created: true };\n },\n\n /** @param {string} key */\n async get(key) {\n if (!ASSET_KEY_PATTERN.test(String(key || ''))) return null;\n return repository.get(key);\n },\n };\n}\n\n/** @returns {AssetRepository & {size: () => number}} */\nexport function createMemoryAssetRepository() {\n /** @type {Map<string, AssetRecord>} */\n const byKey = new Map();\n return {\n async findBySha256(sha256) {\n for (const record of byKey.values()) if (record.sha256 === sha256) return { key: record.key };\n return null;\n },\n async insert(record) {\n if (byKey.has(record.key)) throw new Error('duplicate asset');\n byKey.set(record.key, { ...record, data: new Uint8Array(record.data) });\n },\n async get(key) {\n const record = byKey.get(key);\n return record ? { contentType: record.contentType, data: record.data, sha256: record.sha256 } : null;\n },\n size: () => byKey.size,\n };\n}\n","// @ts-check\n/**\n * Errors with an HTTP status: services throw them, the handler turns them into\n * JSON responses (`{ error, code }`). Any other error is a 500 whose message is\n * not exposed.\n */\n\nexport class HttpError extends Error {\n /**\n * @param {number} status\n * @param {string} message\n * @param {string} [code]\n */\n constructor(status, message, code) {\n super(message);\n this.name = 'HttpError';\n this.status = status;\n this.code = code;\n }\n}\n\n/** Validators and the document reader throw errors carrying a `status`. */\nexport function statusOf(error) {\n const status = Number(/** @type {any} */ (error)?.status);\n return Number.isInteger(status) && status >= 400 && status < 600 ? status : 500;\n}\n","// @ts-check\n/**\n * QUALITY CHECKS for a template — issues that do not block saving, but that\n * should be known before sending.\n *\n * missing unsubscribe a legal requirement for bulk sending;\n * image without file an empty frame for the recipient;\n * image without alt text Outlook blocks images by default: the alternative\n * text is then all that can be read;\n * button without link a button that leads nowhere;\n * http link flagged as \"not secure\" by some email clients;\n * empty subject the message goes out as \"(no subject)\";\n * size > 102 KB Gmail clips the message and hides the end —\n * including the unsubscribe link.\n *\n * Dependency-free module: used by the preview (server) and tested under Node.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Gmail threshold beyond which the message is clipped (\"[Message clipped]\"). */\nexport const GMAIL_CLIP_BYTES = 102 * 1024;\n\n/**\n * @typedef {{code: string, level: 'warning'|'info', message: string, count?: number}} QualityIssue\n */\n\n/** @param {string} source @param {RegExp} pattern */\nfunction count(source, pattern) {\n return (source.match(pattern) || []).length;\n}\n\n/**\n * @param {{mjml: string, html?: string, subject?: string|null}} input\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n * @returns {QualityIssue[]}\n */\nexport function qualityReport({ mjml, html = '', subject = null }, { t = identity } = {}) {\n const source = String(mjml || '');\n /** @type {QualityIssue[]} */\n const issues = [];\n\n if (!/\\{\\{\\s*unsubscribe\\s*\\}\\}|\\[UNSUBSCRIBE\\]/i.test(source)) {\n issues.push({ code: 'NO_UNSUBSCRIBE', level: 'warning', message: t('Missing unsubscribe link') });\n }\n\n const images = source.match(/<mj-image\\b[^>]*>/gi) || [];\n const noSrc = images.filter((tag) => !/\\ssrc=\"[^\"]+\"/i.test(tag)).length;\n if (noSrc) {\n issues.push({\n code: 'IMAGE_NO_SRC',\n level: 'warning',\n count: noSrc,\n message: t('Image without file ({count})', { count: noSrc }),\n });\n }\n const noAlt = images.filter((tag) => !/\\salt=\"[^\"]+\"/i.test(tag)).length;\n if (noAlt) {\n issues.push({\n code: 'IMAGE_NO_ALT',\n level: 'warning',\n count: noAlt,\n message: t('Image without alt text ({count})', { count: noAlt }),\n });\n }\n\n const noHref = count(source, /<mj-button\\b(?![^>]*\\shref=\"[^\"]+\")[^>]*>/gi);\n if (noHref) {\n issues.push({\n code: 'BUTTON_NO_LINK',\n level: 'warning',\n count: noHref,\n message: t('Button without link ({count})', { count: noHref }),\n });\n }\n\n const http = count(source, /\\shref=\"http:\\/\\//gi);\n if (http) {\n issues.push({\n code: 'HTTP_LINK',\n level: 'info',\n count: http,\n message: t('Insecure http link ({count})', { count: http }),\n });\n }\n\n if (subject !== null && !String(subject).trim()) {\n issues.push({ code: 'NO_SUBJECT', level: 'warning', message: t('Empty subject') });\n }\n\n const bytes = new TextEncoder().encode(String(html || '')).length;\n if (bytes > GMAIL_CLIP_BYTES) {\n issues.push({\n code: 'GMAIL_CLIP',\n level: 'warning',\n message: t('Message is {size} KB: Gmail clips messages over {limit} KB', {\n size: Math.round(bytes / 1024),\n limit: GMAIL_CLIP_BYTES / 1024,\n }),\n });\n }\n\n return issues;\n}\n","// @ts-check\n/**\n * MJML → HTML compilation, ON THE SERVER — the only one that counts.\n *\n * The canvas compiles too (mjml-browser) to display the email while editing;\n * but the saved HTML, a test's and a campaign's all come from here. The\n * browser never sends HTML to the server.\n *\n * ignoreIncludes `mj-include` would read a server file (the validator\n * already rejects it; belt and braces);\n * fonts: {} no Google font added by default: one more external link in\n * the message, for fonts the blocks do not use;\n * validationLevel soft: MJML deviations come back as warnings. Blocking them\n * would lose work for a mistyped attribute value; safety is\n * enforced upstream by `validation/`.\n *\n * Two lines are added to MJML's <head>, as email best practice asks:\n * x-apple-disable-message-reformatting Apple Mail (macOS, iOS) does not\n * rescale the email it judges too wide;\n * format-detection iOS does not turn dates, addresses and phone numbers\n * into blue links over the design (a real link is written\n * as one: `tel:`, `mailto:`).\n */\n\nconst CLIENT_METAS =\n '<meta name=\"x-apple-disable-message-reformatting\">' +\n '<meta name=\"format-detection\" content=\"telephone=no, date=no, address=no, email=no, url=no\">';\n\n/** @param {string} html */\nexport function withClientMetas(html) {\n if (!html || html.includes('x-apple-disable-message-reformatting')) return html;\n const viewport = /<meta name=\"viewport\"[^>]*>/i.exec(html);\n if (viewport) return html.slice(0, viewport.index + viewport[0].length) + CLIENT_METAS + html.slice(viewport.index + viewport[0].length);\n return html.replace(/<head>/i, (head) => head + CLIENT_METAS);\n}\n\nimport mjml2html from 'mjml';\n\n/** @typedef {{line?: number, message: string, tagName?: string}} CompileWarning */\n\n/**\n * @param {string} mjml\n * @returns {Promise<{html: string, warnings: CompileWarning[]}>}\n */\nexport async function compileMjml(mjml) {\n const compile = /** @type {any} */ (mjml2html);\n // MJML 5 compiles asynchronously; `await` also accepts a synchronous result.\n const result = await compile(String(mjml || ''), {\n validationLevel: 'soft',\n keepComments: false,\n beautify: false,\n minify: false,\n ignoreIncludes: true,\n fonts: {},\n });\n return {\n html: withClientMetas(String(result?.html || '')),\n warnings: (result?.errors || []).map((/** @type {any} */ e) => ({\n line: e.line,\n message: String(e.formattedMessage || e.message || e),\n tagName: e.tagName,\n })),\n };\n}\n","// @ts-check\n/**\n * Rendering of a template outside of a campaign — preview and test send.\n *\n * MJML ─► validation ─► compilation (mjml, server) ─► variables ─► links ─► HTML\n *\n * Same validation and compilation steps as saving (`templates.js`): what the\n * preview shows is what will be stored. Variables get SAMPLE values (the real\n * address for a test); the unsubscribe link is inert.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { assertValidMjml } from '../validation/mjmlValidator.js';\nimport { qualityReport } from '../validation/quality.js';\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { applyVariables, unknownVariables } from '../features/variables/service.js';\nimport { prepareMjml } from '../renderer/prepareMjml.js';\nimport { absoluteAssetUrls, applyServiceLinks } from '../renderer/links.js';\nimport { compileMjml } from './compileMjml.js';\n\n/**\n * @typedef {import('../i18n/index.js').Translate} Translate\n * @typedef {import('../features/variables/catalog.js').EmailVariable} EmailVariable\n */\n\n/**\n * Validation + compilation, once per template. The quality report also lists\n * tags that are not in the catalog (typos), which would render empty.\n *\n * @param {{mjml: string, subject?: string|null, preheader?: string|null}} input\n * @param {{t?: Translate, lang?: string, variables?: EmailVariable[], preview?: boolean}} [options]\n * `preview`: for a person, not an inbox — an image with no picture yet is\n * kept (the page draws its frame) instead of left out.\n */\nexport async function compileTemplate(\n { mjml, subject = null, preheader = null },\n { t = identity, lang = 'en', variables = DEFAULT_VARIABLES, preview = false } = {}\n) {\n assertValidMjml(mjml, { t });\n const prepared = prepareMjml(mjml, { title: subject, preheader, lang, emptyImages: preview ? 'keep' : 'drop' });\n const { html, warnings } = await compileMjml(prepared);\n const quality = qualityReport({ mjml, html, subject }, { t });\n const unknown = unknownVariables([mjml, subject || '', preheader || ''].join('\\n'), variables);\n if (unknown.length) {\n quality.push({\n code: 'UNKNOWN_VARIABLE',\n level: 'warning',\n count: unknown.length,\n message: t('Unknown variables (rendered empty): {tags}', { tags: unknown.map((k) => `{{${k}}}`).join(', ') }),\n });\n }\n return { compiled: html, warnings, quality };\n}\n\n/**\n * Personalization of compiled HTML, once per recipient. `baseUrl` makes\n * uploaded images absolute — omit it for a preview inside the application,\n * which reads them from its own origin.\n *\n * @param {string} compiled\n * @param {{\n * values: Record<string, string|null|undefined>,\n * unsubscribeUrl: string,\n * baseUrl?: string|null,\n * variables?: EmailVariable[],\n * }} options\n */\nexport function personalize(compiled, { values, unsubscribeUrl, baseUrl = null, variables = DEFAULT_VARIABLES }) {\n const html = applyServiceLinks(applyVariables(compiled, values, variables), { unsubscribeUrl });\n return absoluteAssetUrls(html, baseUrl);\n}\n\n/**\n * Compiles and personalizes in one call — for a single email.\n *\n * @param {{\n * mjml: string,\n * subject?: string|null,\n * preheader?: string|null,\n * values?: Record<string, string|null|undefined>,\n * unsubscribeUrl?: string,\n * baseUrl?: string|null,\n * variables?: EmailVariable[],\n * t?: Translate,\n * lang?: string,\n * }} input\n */\nexport async function renderEmail({\n mjml,\n subject = null,\n preheader = null,\n values = {},\n unsubscribeUrl = '#',\n baseUrl = null,\n variables = DEFAULT_VARIABLES,\n t = identity,\n lang = 'en',\n}) {\n const { compiled, warnings, quality } = await compileTemplate({ mjml, subject, preheader }, { t, lang, variables });\n return { html: personalize(compiled, { values, unsubscribeUrl, baseUrl, variables }), warnings, quality };\n}\n\n/**\n * Plain-text alternative (clients without HTML, spam filters).\n * @param {string} html\n */\nexport function htmlToText(html) {\n return String(html || '')\n .replace(/<(style|head)[\\s\\S]*?<\\/\\1>/gi, '')\n .replace(/<(br|\\/p|\\/div|\\/tr|\\/h\\d)[^>]*>/gi, '\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/ /g, ' ')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, '&')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n","// @ts-check\n/**\n * TEST SEND of a template — to proofreading addresses only.\n *\n * A browser preview proves nothing about what Outlook (Word's engine) will do:\n * a test in a real mailbox is the only real check.\n *\n * - same rendering as the preview, one address at a time (`to` is never a\n * list: reviewers do not see each other);\n * - subject prefixed with \"[TEST]\"; the email variable gets the address being\n * served, the others sample values; inert unsubscribe link;\n * - a bounded number of addresses — beyond that it is no longer a test;\n * - one failure does not stop the others: each address gets its result.\n *\n * The transport is INJECTED, which makes this testable without sending.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { applyVariablesToText, sampleValues } from '../features/variables/service.js';\nimport { HttpError } from './errors.js';\nimport { compileTemplate, htmlToText, personalize } from './render.js';\n\nexport const MAX_TEST_RECIPIENTS = 10;\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/;\n\n/**\n * @typedef {{to: string, subject: string, html: string, text: string}} OutgoingEmail\n * @typedef {{send: (email: OutgoingEmail) => Promise<unknown>}} EmailTransport\n */\n\n/**\n * @param {{\n * mjml: string,\n * subject?: string|null,\n * preheader?: string|null,\n * emails: string[]|string,\n * transport: EmailTransport,\n * baseUrl?: string|null,\n * unsubscribeUrl?: string,\n * variables?: import('../features/variables/catalog.js').EmailVariable[],\n * maxRecipients?: number,\n * t?: import('../i18n/index.js').Translate,\n * lang?: string,\n * }} input\n */\nexport async function sendTestEmail({\n mjml,\n subject = null,\n preheader = null,\n emails,\n transport,\n baseUrl = null,\n unsubscribeUrl = '#',\n variables = DEFAULT_VARIABLES,\n maxRecipients = MAX_TEST_RECIPIENTS,\n t = identity,\n lang = 'en',\n}) {\n const list = (Array.isArray(emails) ? emails : String(emails || '').split(/[,;\\s]+/))\n .map((e) => String(e || '').trim().toLowerCase())\n .filter(Boolean);\n const valid = [...new Set(list.filter((e) => EMAIL_SHAPE.test(e)))];\n const invalid = list.filter((e) => !EMAIL_SHAPE.test(e));\n if (!valid.length) throw new HttpError(400, t('No usable test address'), 'NO_RECIPIENT');\n if (valid.length > maxRecipients) {\n throw new HttpError(400, t('{max} test addresses at most', { max: maxRecipients }), 'TOO_MANY_RECIPIENTS');\n }\n\n // Compiled once (validation included), then personalized per address.\n const { compiled, warnings, quality } = await compileTemplate({ mjml, subject, preheader }, { t, lang, variables });\n\n const results = [];\n for (const email of valid) {\n try {\n const values = sampleValues(variables, { 'contact.email': email });\n const html = personalize(compiled, { values, unsubscribeUrl, baseUrl, variables });\n const personal = applyVariablesToText(String(subject || '').trim(), values, variables) || t('No subject');\n await transport.send({ to: email, subject: `[TEST] ${personal}`.slice(0, 200), html, text: htmlToText(html) });\n results.push({ email, ok: true, error: null });\n } catch (error) {\n // Short message: a gateway error can carry the whole HTML.\n results.push({ email, ok: false, error: String(/** @type {Error} */ (error)?.message || 'failed').slice(0, 300) });\n }\n }\n\n return {\n sent: results.filter((r) => r.ok).length,\n failed: results.filter((r) => !r.ok).length,\n invalid,\n results,\n warnings,\n quality,\n };\n}\n","// @ts-check\n/**\n * What a PROJECT (the editor's project data) may contain.\n *\n * The project is reloaded as-is into the editor canvas — an iframe of the host\n * page. It does NOT go through the editor engine's HTML parser (which drops\n * scripts and `on*` attributes): it is already structured data. The check must\n * therefore happen here, on save; otherwise a tampered project would run code\n * in the browser of the next user who opens it.\n *\n * The walk is generic (any object, any array) rather than modelled on the\n * engine's format: with another engine or another version, the rule still\n * holds.\n */\n\nimport { readDocument } from '../core/document.js';\nimport { identity } from '../i18n/index.js';\nimport { IMAGE_ATTRIBUTES, LINK_ATTRIBUTES, isSafeUrl } from './urls.js';\n\n/** Maximum size of a project's JSON. */\nexport const PROJECT_MAX_LENGTH = 2 * 1024 * 1024;\n\nconst MAX_DEPTH = 200;\n/** Forbidden HTML/MJML tags (a component's `tagName`). */\nconst DANGEROUS_TAGS = /^(script|iframe|frame|object|embed|form|base|link|meta|svg|math|mj-include|mj-raw)$/i;\n/**\n * Forbidden component types (`type`). Shorter list than the tags: for the\n * engine, `link` is the type of an `<a>` link — unrelated to the `<link>` tag\n * (external style sheet), which is still rejected through its `tagName`.\n */\nconst DANGEROUS_TYPES = /^(script|iframe|frame|object|embed|form|svg|mj-include|mj-raw)$/i;\n\n/**\n * @typedef {import('./mjmlValidator.js').ValidationIssue} ValidationIssue\n */\n\n/**\n * @param {unknown} raw envelope (object or JSON string)\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n * @returns {{ok: boolean, issues: ValidationIssue[], document: import('../core/document.js').EmailDocument|null, json: string}}\n */\nexport function validateProject(raw, { t = identity } = {}) {\n /** @type {ValidationIssue[]} */\n const issues = [];\n let document;\n try {\n document = readDocument(raw);\n } catch (error) {\n return {\n ok: false,\n issues: [{ code: 'ENVELOPE', message: /** @type {Error} */ (error).message }],\n document: null,\n json: '',\n };\n }\n\n const json = JSON.stringify(document);\n if (json.length > PROJECT_MAX_LENGTH) {\n issues.push({\n code: 'TOO_LARGE',\n message: t('project too large ({size} KB, maximum {max} KB)', {\n size: Math.round(json.length / 1024),\n max: PROJECT_MAX_LENGTH / 1024,\n }),\n });\n }\n\n /** @param {any} node @param {number} depth */\n function walk(node, depth) {\n if (depth > MAX_DEPTH) {\n issues.push({ code: 'TOO_DEEP', message: t('project too deep') });\n return;\n }\n if (Array.isArray(node)) {\n for (const item of node) walk(item, depth + 1);\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n // `tag`: the builder's own project; `type` / `tagName`: projects of other engines.\n for (const [key, pattern] of /** @type {[string, RegExp][]} */ ([['type', DANGEROUS_TYPES], ['tagName', DANGEROUS_TAGS], ['tag', DANGEROUS_TAGS]])) {\n const value = node[key];\n if (typeof value === 'string' && pattern.test(value)) {\n issues.push({ code: 'FORBIDDEN_TAG', message: t('forbidden element in project: {tag}', { tag: value }), tag: value });\n }\n }\n for (const key of ['script', 'script-export', 'script-props']) {\n const value = node[key];\n if (value && (typeof value !== 'object' || Object.keys(value).length > 0)) {\n issues.push({ code: 'SCRIPT', message: t('component script not allowed') });\n }\n }\n for (const key of ['content', 'components']) {\n const value = node[key];\n if (typeof value === 'string' && /<\\s*(script|iframe|object|embed)\\b|javascript:/i.test(value)) {\n issues.push({ code: 'UNSAFE_CONTENT', message: t('executable content not allowed') });\n }\n }\n const attributes = node.attributes;\n if (attributes && typeof attributes === 'object' && !Array.isArray(attributes)) {\n for (const [name, value] of Object.entries(attributes)) {\n const lower = name.toLowerCase();\n if (lower.startsWith('on')) {\n issues.push({ code: 'EVENT_ATTRIBUTE', message: t('attribute {name} not allowed', { name }), attribute: name });\n } else if (LINK_ATTRIBUTES.has(lower) && !isSafeUrl(String(value ?? ''), 'link')) {\n issues.push({ code: 'UNSAFE_URL', message: t('URL rejected ({name})', { name }), attribute: name });\n } else if (IMAGE_ATTRIBUTES.has(lower) && !isSafeUrl(String(value ?? ''), 'image')) {\n issues.push({ code: 'UNSAFE_IMAGE', message: t('image rejected ({name})', { name }), attribute: name });\n }\n }\n }\n for (const [key, value] of Object.entries(node)) {\n if (key !== 'attributes' && value && typeof value === 'object') walk(value, depth + 1);\n }\n }\n\n walk(document.project, 0);\n return { ok: issues.length === 0, issues, document, json };\n}\n\n/**\n * Validates and throws a readable 422 error listing the first rejections.\n * @param {unknown} raw\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function assertValidProject(raw, { t = identity } = {}) {\n const result = validateProject(raw, { t });\n if (result.ok && result.document) return { document: result.document, json: result.json };\n const shown = result.issues.slice(0, 3).map((i) => i.message).join('; ');\n const error = /** @type {Error & {status?: number, issues?: ValidationIssue[]}} */ (\n new Error(t('Project rejected: {issues}', { issues: shown }))\n );\n error.status = 422;\n error.issues = result.issues;\n throw error;\n}\n","// @ts-check\n/**\n * TEMPLATES — the server-side business rules, independent of the database.\n *\n * ── THE PROJECT IS THE SOURCE, HTML A RESULT ────────────────────────────────\n * Every write receives the PROJECT (versioned envelope) and its MJML export.\n * The server validates both (`validation/`), compiles the MJML itself and only\n * stores ITS HTML. No route accepts HTML coming from the browser.\n *\n * ── CONCURRENT WRITES ───────────────────────────────────────────────────────\n * Two people can open the same template. Each write carries the `revision`\n * read at load time; the repository refuses the write (409) when someone else\n * saved in the meantime — instead of silently overwriting their work.\n *\n * ── VERSIONS ───────────────────────────────────────────────────────────────\n * One version per EXPLICIT save (`createVersion`), never per autosave. Only the\n * latest `maxVersions` stay listed.\n *\n * Storage is a REPOSITORY supplied by the application (see the typedef below\n * and `createMemoryTemplateRepository`, the reference implementation).\n */\n\nimport { identity } from '../i18n/index.js';\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from '../core/constants.js';\nimport { assertValidProject } from '../validation/projectValidator.js';\nimport { assertValidMjml } from '../validation/mjmlValidator.js';\nimport { prepareMjml } from '../renderer/prepareMjml.js';\nimport { compileMjml } from './compileMjml.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {{\n * name: string,\n * description: string|null,\n * subject: string|null,\n * preheader: string|null,\n * projectJson: string,\n * mjml: string,\n * html: string,\n * builderVersion: string,\n * schemaVersion: number,\n * }} TemplateFields\n *\n * @typedef {TemplateFields & {\n * id: string|number,\n * revision: string,\n * createdAt: string|Date|null,\n * updatedAt: string|Date|null,\n * createdBy: string|null,\n * updatedBy: string|null,\n * }} TemplateRecord\n *\n * @typedef {{\n * id: string|number,\n * versionNumber: number,\n * name: string,\n * subject: string|null,\n * preheader: string|null,\n * projectJson: string,\n * mjml: string,\n * createdBy: string|null,\n * createdAt: string|Date|null,\n * }} VersionRecord\n *\n * Every method may be async. `update` MUST compare `expectedRevision` and write\n * atomically (e.g. `UPDATE … WHERE id = ? AND revision = ?`), returning\n * `{ conflict: true, current }` on mismatch and `null` when the id is unknown.\n * `addVersion` numbers versions (max + 1) and keeps the latest `keep` listed.\n *\n * @typedef {{\n * list: () => Promise<TemplateRecord[]>,\n * find: (id: string) => Promise<TemplateRecord|null>,\n * insert: (fields: TemplateFields & {createdBy: string|null}) => Promise<TemplateRecord>,\n * update: (id: string, fields: Partial<TemplateFields> & {updatedBy: string|null},\n * options: {expectedRevision: string|null}) =>\n * Promise<TemplateRecord | {conflict: true, current: TemplateRecord} | null>,\n * remove: (id: string) => Promise<boolean>,\n * addVersion: (templateId: string, version: Omit<VersionRecord, 'id'|'versionNumber'|'createdAt'>,\n * options: {keep: number}) => Promise<{id: string|number, versionNumber: number}>,\n * listVersions: (templateId: string) => Promise<VersionRecord[]>,\n * findVersion: (templateId: string, versionId: string) => Promise<VersionRecord|null>,\n * transaction?: <T>(work: () => Promise<T>) => Promise<T>,\n * }} TemplateRepository\n *\n * @typedef {{id?: string|number, name?: string}|null} Actor\n */\n\n/** Column sizes. */\nexport const LIMITS = { name: 200, description: 1000, subject: 250, preheader: 250 };\n\nexport const MAX_ACTIVE_VERSIONS = 50;\n\nfunction text(value, max) {\n const out = String(value ?? '').trim();\n return out ? out.slice(0, max) : null;\n}\n\nconst iso = (value) => (value ? new Date(value).toISOString() : null);\nconst actorName = (/** @type {Actor} */ user) => (user ? String(user.name ?? user.id ?? '') || null : null);\n\nfunction parseProject(json) {\n try {\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\n/** Record → API object. */\nfunction toTemplate(/** @type {TemplateRecord} */ row, { withProject = true } = {}) {\n return {\n id: row.id,\n name: row.name,\n description: row.description,\n subject: row.subject,\n preheader: row.preheader,\n revision: row.revision,\n builderVersion: row.builderVersion,\n schemaVersion: row.schemaVersion,\n createdBy: row.createdBy,\n updatedBy: row.updatedBy,\n createdAt: iso(row.createdAt),\n updatedAt: iso(row.updatedAt),\n ...(withProject ? { document: parseProject(row.projectJson), mjml: row.mjml, html: row.html } : {}),\n };\n}\n\n/** @param {VersionRecord} row */\nfunction toVersion(row, { withProject = false } = {}) {\n return {\n id: row.id,\n versionNumber: row.versionNumber,\n name: row.name,\n subject: row.subject,\n createdBy: row.createdBy,\n createdAt: iso(row.createdAt),\n ...(withProject ? { preheader: row.preheader, document: parseProject(row.projectJson), mjml: row.mjml } : {}),\n };\n}\n\n/**\n * @param {{\n * repository: TemplateRepository,\n * t?: import('../i18n/index.js').Translate,\n * lang?: string,\n * maxVersions?: number,\n * }} options\n */\nexport function createTemplateService({ repository, t = identity, lang = 'en', maxVersions = MAX_ACTIVE_VERSIONS }) {\n const inTransaction = (work) => (repository.transaction ? repository.transaction(work) : work());\n\n /**\n * Validates the project and the MJML, compiles the final HTML.\n * @param {{document: any, mjml: any, subject?: string|null, preheader?: string|null}} input\n */\n async function compile({ document, mjml, subject = null, preheader = null }) {\n const { json } = assertValidProject(document, { t });\n assertValidMjml(mjml, { t });\n const { html, warnings } = await compileMjml(prepareMjml(mjml, { title: subject, preheader, lang }));\n if (!html) throw new HttpError(422, t('Empty MJML compilation'), 'EMPTY_COMPILATION');\n return { projectJson: json, mjml: String(mjml), html, warnings };\n }\n\n async function findOrThrow(id) {\n const row = await repository.find(String(id));\n if (!row) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n return row;\n }\n\n const service = {\n compile,\n\n async list() {\n const rows = await repository.list();\n return rows.map((row) => toTemplate(row, { withProject: false }));\n },\n\n /** @param {string|number} id */\n async get(id) {\n return toTemplate(await findOrThrow(id));\n },\n\n /**\n * Creates a template and its version 1.\n * @param {any} body\n * @param {Actor} [user]\n */\n async create(body, user = null) {\n const name = text(body?.name, LIMITS.name);\n if (!name) throw new HttpError(400, t('Name required'), 'NAME_REQUIRED');\n const subject = text(body?.subject, LIMITS.subject);\n const preheader = text(body?.preheader, LIMITS.preheader);\n const { projectJson, mjml, html, warnings } = await compile({ document: body?.document, mjml: body?.mjml, subject, preheader });\n const by = actorName(user);\n\n const row = await inTransaction(async () => {\n const created = await repository.insert({\n name,\n description: text(body?.description, LIMITS.description),\n subject,\n preheader,\n projectJson,\n mjml,\n html,\n builderVersion: EMAIL_BUILDER_VERSION,\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n createdBy: by,\n });\n await repository.addVersion(\n String(created.id),\n { name, subject, preheader, projectJson, mjml, createdBy: by },\n { keep: maxVersions }\n );\n return created;\n });\n return { template: toTemplate(row), warnings };\n },\n\n /**\n * Saves a template. Fields left undefined keep their stored value.\n * @param {string|number} id\n * @param {any} body\n * @param {Actor} [user]\n */\n async save(id, body, user = null) {\n const current = await findOrThrow(id);\n const name = body?.name !== undefined ? text(body.name, LIMITS.name) : current.name;\n if (!name) throw new HttpError(400, t('Name required'), 'NAME_REQUIRED');\n const subject = body?.subject !== undefined ? text(body.subject, LIMITS.subject) : current.subject;\n const preheader = body?.preheader !== undefined ? text(body.preheader, LIMITS.preheader) : current.preheader;\n const description =\n body?.description !== undefined ? text(body.description, LIMITS.description) : current.description;\n const { projectJson, mjml, html, warnings } = await compile({ document: body?.document, mjml: body?.mjml, subject, preheader });\n const by = actorName(user);\n\n const result = await inTransaction(async () => {\n const updated = await repository.update(\n String(id),\n {\n name,\n description,\n subject,\n preheader,\n projectJson,\n mjml,\n html,\n builderVersion: EMAIL_BUILDER_VERSION,\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n updatedBy: by,\n },\n { expectedRevision: body?.revision ? String(body.revision) : null }\n );\n if (!updated) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n if ('conflict' in updated) {\n const who = updated.current.updatedBy;\n throw new HttpError(\n 409,\n who\n ? t('Template changed in the meantime by {user} — reload it before saving', { user: who })\n : t('Template changed in the meantime — reload it before saving'),\n 'CONFLICT'\n );\n }\n const version = body?.createVersion\n ? await repository.addVersion(\n String(id),\n { name, subject, preheader, projectJson, mjml, createdBy: by },\n { keep: maxVersions }\n )\n : null;\n return { row: updated, version };\n });\n\n // The editor keeps its own document: only what changed server side is returned.\n const { document: _omit, ...template } = toTemplate(result.row);\n return { template, warnings, version: result.version };\n },\n\n /** @param {string|number} id @param {Actor} [user] */\n async duplicate(id, user = null) {\n const source = await findOrThrow(id);\n return service.create(\n {\n name: t('{name} (copy)', { name: source.name }).slice(0, LIMITS.name),\n description: source.description,\n subject: source.subject,\n preheader: source.preheader,\n document: parseProject(source.projectJson),\n mjml: source.mjml,\n },\n user\n );\n },\n\n /** @param {string|number} id */\n async remove(id) {\n if (!(await repository.remove(String(id)))) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n return { id };\n },\n\n /** @param {string|number} id */\n async listVersions(id) {\n await findOrThrow(id);\n const rows = await repository.listVersions(String(id));\n return rows.map((row) => toVersion(row));\n },\n\n /** @param {string|number} id @param {string|number} versionId */\n async getVersion(id, versionId) {\n const row = await repository.findVersion(String(id), String(versionId));\n if (!row) throw new HttpError(404, t('Version not found'), 'NOT_FOUND');\n return toVersion(row, { withProject: true });\n },\n };\n return service;\n}\n\n/**\n * In-memory repository — reference implementation of the contract, used by the\n * tests and the example application. Data is lost on restart.\n *\n * @returns {TemplateRepository}\n */\nexport function createMemoryTemplateRepository() {\n /** @type {Map<string, TemplateRecord>} */\n const rows = new Map();\n /** @type {Map<string, VersionRecord[]>} */\n const versions = new Map();\n let nextId = 1;\n let nextVersionId = 1;\n let clock = 0;\n // Strictly increasing, even within the same millisecond.\n const stamp = () => {\n clock = Math.max(clock + 1, Date.now());\n return new Date(clock);\n };\n const copy = (row) => (row ? { ...row } : null);\n\n return {\n async list() {\n return [...rows.values()]\n .map(copy)\n .sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime());\n },\n async find(id) {\n return copy(rows.get(id));\n },\n async insert(fields) {\n const at = stamp();\n const row = { ...fields, id: nextId++, revision: at.toISOString(), createdAt: at, updatedAt: null, updatedBy: null };\n rows.set(String(row.id), row);\n return copy(row);\n },\n async update(id, fields, { expectedRevision }) {\n const row = rows.get(id);\n if (!row) return null;\n if (expectedRevision && expectedRevision !== row.revision) return { conflict: true, current: copy(row) };\n const at = stamp();\n Object.assign(row, fields, { updatedAt: at, revision: at.toISOString() });\n return copy(row);\n },\n async remove(id) {\n versions.delete(id);\n return rows.delete(id);\n },\n async addVersion(templateId, version, { keep }) {\n const list = versions.get(templateId) || [];\n const versionNumber = list.reduce((max, v) => Math.max(max, v.versionNumber), 0) + 1;\n const entry = { ...version, id: nextVersionId++, versionNumber, createdAt: stamp() };\n versions.set(templateId, [...list, entry].slice(-keep));\n return { id: entry.id, versionNumber };\n },\n async listVersions(templateId) {\n return [...(versions.get(templateId) || [])].reverse().map(copy);\n },\n async findVersion(templateId, versionId) {\n return copy((versions.get(templateId) || []).find((v) => String(v.id) === versionId));\n },\n };\n}\n","// @ts-check\n/**\n * CUSTOM VARIABLES — created by users in the builder, shared by every template.\n *\n * System variables (the `variables` option of the handler) are read-only; a\n * custom variable can never take one of their keys. A key never changes after\n * creation: templates contain it.\n *\n * Storage is a REPOSITORY supplied by the application;\n * `createMemoryVariableRepository` is the reference implementation.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES, mergeVariables, validateCustomVariable } from '../features/variables/catalog.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {import('../features/variables/catalog.js').EmailVariable} EmailVariable\n *\n * @typedef {{\n * list: () => Promise<EmailVariable[]>,\n * insert: (variable: EmailVariable & {createdBy: string|null}) => Promise<void>,\n * update: (key: string, variable: EmailVariable & {updatedBy: string|null}) => Promise<boolean>,\n * remove: (key: string) => Promise<boolean>,\n * }} VariableRepository\n */\n\nconst actorName = (user) => (user ? String(user.name ?? user.id ?? '') || null : null);\n\n/** Public shape (no audit fields). @param {any} row @returns {EmailVariable} */\nconst toVariable = (row) => ({\n key: row.key,\n label: row.label,\n group: row.group,\n sample: row.sample || '',\n fallback: row.fallback || '',\n description: row.description || '',\n custom: true,\n});\n\n/**\n * @param {{\n * repository?: VariableRepository|null,\n * system?: EmailVariable[],\n * t?: import('../i18n/index.js').Translate,\n * }} options\n */\nexport function createVariableService({ repository = null, system = DEFAULT_VARIABLES, t = identity }) {\n const requireRepository = () => {\n if (!repository) throw new HttpError(501, t('Custom variables are not configured'), 'NO_VARIABLE_STORE');\n return repository;\n };\n\n const service = {\n /** Custom variables only. */\n async listCustom() {\n return repository ? (await repository.list()).map(toVariable) : [];\n },\n\n /** System + custom: the catalog used for rendering. */\n async catalog() {\n return mergeVariables(system, await service.listCustom());\n },\n\n /** @param {any} input @param {any} [user] */\n async create(input, user = null) {\n const store = requireRepository();\n const existing = [...system, ...(await service.listCustom())];\n const { variable, errors } = validateCustomVariable(input, { existing, t });\n if (errors) throw Object.assign(new HttpError(400, Object.values(errors)[0], 'INVALID_VARIABLE'), { issues: errors });\n await store.insert({ ...variable, createdBy: actorName(user) });\n return variable;\n },\n\n /** @param {string} key @param {any} input @param {any} [user] */\n async update(key, input, user = null) {\n const store = requireRepository();\n const current = (await service.listCustom()).find((v) => v.key === key);\n if (!current) throw new HttpError(404, t('Variable not found'), 'NOT_FOUND');\n const { variable, errors } = validateCustomVariable({ ...current, ...input, key }, { editing: key, t });\n if (errors) throw Object.assign(new HttpError(400, Object.values(errors)[0], 'INVALID_VARIABLE'), { issues: errors });\n await store.update(key, { ...variable, updatedBy: actorName(user) });\n return variable;\n },\n\n /** @param {string} key */\n async remove(key) {\n if (!(await requireRepository().remove(key))) throw new HttpError(404, t('Variable not found'), 'NOT_FOUND');\n return { key };\n },\n };\n return service;\n}\n\n/** @returns {VariableRepository} */\nexport function createMemoryVariableRepository() {\n /** @type {Map<string, any>} */\n const rows = new Map();\n return {\n async list() {\n return [...rows.values()].map((row) => ({ ...row })).sort((a, b) => a.label.localeCompare(b.label));\n },\n async insert(variable) {\n if (rows.has(variable.key)) throw new Error('duplicate variable');\n rows.set(variable.key, { ...variable, createdAt: new Date() });\n },\n async update(key, variable) {\n if (!rows.has(key)) return false;\n rows.set(key, { ...rows.get(key), ...variable, key, updatedAt: new Date() });\n return true;\n },\n async remove(key) {\n return rows.delete(key);\n },\n };\n}\n","// @ts-check\n/**\n * BRAND — the look every email of the workspace starts from (name, logo,\n * main color, headings color, font), shared by all users like the custom\n * variables. What it does to an email: features/brand/brand.js.\n *\n * Storage is a REPOSITORY supplied by the application (one row is enough);\n * `createMemoryBrandRepository` is the reference implementation.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { cleanBrand } from '../features/brand/brand.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {import('../features/brand/brand.js').Brand} Brand\n *\n * @typedef {{\n * get: () => Promise<Brand|null>,\n * save: (brand: Brand & {updatedBy: string|null}) => Promise<void>,\n * }} BrandRepository\n */\n\n/**\n * @param {{repository?: BrandRepository|null, t?: import('../i18n/index.js').Translate}} options\n */\nexport function createBrandService({ repository = null, t = identity }) {\n return {\n /** The brand, `{}` when none was set (or no repository). */\n async get() {\n return repository ? cleanBrand(await repository.get()) : {};\n },\n\n /** @param {unknown} input @param {{id?: string|number, name?: string}|null} [user] */\n async save(input, user = null) {\n if (!repository) throw new HttpError(501, t('The brand is not configured'), 'NO_BRAND_STORE');\n if (!input || typeof input !== 'object') throw new HttpError(400, t('Invalid JSON'), 'INVALID_JSON');\n const brand = cleanBrand(input);\n await repository.save({ ...brand, updatedBy: user ? String(user.name ?? user.id ?? '') || null : null });\n return brand;\n },\n };\n}\n\n/** @returns {BrandRepository} */\nexport function createMemoryBrandRepository() {\n /** @type {any} */\n let row = null;\n return {\n async get() {\n return row ? { ...row } : null;\n },\n async save(brand) {\n row = { ...brand, updatedAt: new Date() };\n },\n };\n}\n","// @ts-check\n/**\n * BRAND IMPORT — reads a company's website on the server: its name, logo,\n * main color, headings color and font (what it reads: features/brand/extract.js).\n *\n * The server downloads a page someone typed the address of: that is a door\n * into the server's own network (SSRF) unless every request is held to\n * these rules —\n *\n * - http and https only, ports 80 and 443, no user:password in the address;\n * - every address the name resolves to must be PUBLIC: loopback, private\n * networks, link-local (cloud metadata, 169.254.169.254), carrier NAT,\n * multicast, documentation and reserved ranges are refused, IPv4 hidden\n * in IPv6 (::ffff:10.0.0.1, 64:ff9b::/96, 2002::/16) included;\n * - the check happens in the socket's own DNS lookup, so the address that\n * was checked is the one connected to (no DNS rebinding between the two);\n * - redirects are followed by hand, at most 4, each one checked again;\n * - 8 seconds in all; sizes capped AFTER decompression (a small gzip can\n * unpack to gigabytes): page 1.5 MB, each stylesheet 400 KB, logo 2 MB;\n * - nothing downloaded is executed or served as is: the page and the CSS\n * are only read as text, the logo is checked to be a PNG, JPEG or GIF\n * and stored like any upload.\n *\n * Node only (node:dns, node:http, node:https, node:net, node:zlib).\n */\n\nimport dns from 'node:dns';\nimport http from 'node:http';\nimport https from 'node:https';\nimport net from 'node:net';\nimport zlib from 'node:zlib';\nimport { cleanBrand } from '../features/brand/brand.js';\nimport { brandFromCss, imageSize, logoWidthFor, readPage } from '../features/brand/extract.js';\nimport { MAX_IMAGE_BYTES, detectImage } from '../validation/images.js';\n\n/**\n * @typedef {'BAD_URL'|'PRIVATE_ADDRESS'|'UNREACHABLE'|'TIMEOUT'|'HTTP_ERROR'|'NOT_HTML'|'TOO_MANY_REDIRECTS'|'TOO_LARGE'} BrandImportCode\n * @typedef {'LOGO_NOT_FOUND'|'LOGO_UNSUPPORTED'|'LOGO_ICON'|'COLORS_NOT_FOUND'} BrandNote\n * `LOGO_UNSUPPORTED`: the site's logo is an SVG or a WebP, which most email\n * apps do not show; `LOGO_ICON`: the site's icon was used instead.\n *\n * @typedef {{\n * brand: import('../features/brand/brand.js').Brand,\n * logo: {bytes: Uint8Array, width: number, height: number, type: string, ext: string, shownWidth: number}|null,\n * notes: BrandNote[],\n * }} BrandImport\n */\n\nexport const BRAND_IMPORT_LIMITS = {\n pageBytes: 1.5 * 1024 * 1024,\n cssBytes: 400 * 1024,\n logoBytes: MAX_IMAGE_BYTES,\n stylesheets: 4,\n logos: 5,\n redirects: 4,\n timeoutMs: 8000,\n};\n\nconst USER_AGENT = 'BestEmailBuilder-BrandReader/1.0 (+https://github.com/SergeMiro/best-email-builder)';\n\n/** Why an import failed; the handler turns `code` into a message. */\nexport class BrandImportError extends Error {\n /** @param {BrandImportCode} code @param {string} [detail] */\n constructor(code, detail = '') {\n super(detail ? `${code}: ${detail}` : code);\n this.name = 'BrandImportError';\n this.code = code;\n }\n}\n\n/* ── Which addresses may be reached ─────────────────────────────────────── */\n\nconst blocked = new net.BlockList();\nfor (const [network, prefix] of /** @type {[string, number][]} */ ([\n ['0.0.0.0', 8], // \"this network\"\n ['10.0.0.0', 8], // private\n ['100.64.0.0', 10], // carrier-grade NAT\n ['127.0.0.0', 8], // loopback\n ['169.254.0.0', 16], // link-local, cloud metadata\n ['172.16.0.0', 12], // private\n ['192.0.0.0', 24], // IETF protocol assignments\n ['192.0.2.0', 24], // documentation\n ['192.88.99.0', 24], // 6to4 relay\n ['192.168.0.0', 16], // private\n ['198.18.0.0', 15], // benchmarking\n ['198.51.100.0', 24], // documentation\n ['203.0.113.0', 24], // documentation\n ['224.0.0.0', 4], // multicast\n ['240.0.0.0', 4], // reserved, broadcast\n])) {\n blocked.addSubnet(network, prefix, 'ipv4');\n}\nfor (const [network, prefix] of /** @type {[string, number][]} */ ([\n ['::', 128], // unspecified\n ['::1', 128], // loopback\n ['::', 96], // IPv4-compatible (deprecated)\n ['100::', 64], // discard\n ['2001::', 32], // Teredo\n ['2001:db8::', 32], // documentation\n ['fc00::', 7], // unique local\n ['fe80::', 10], // link-local\n ['fec0::', 10], // site-local (deprecated)\n ['ff00::', 8], // multicast\n])) {\n blocked.addSubnet(network, prefix, 'ipv6');\n}\n\n/**\n * The IPv4 address an IPv6 one carries (mapped, NAT64, 6to4), or ''.\n * @param {string} address\n */\nfunction embeddedIpv4(address) {\n const lower = address.toLowerCase();\n const dotted = lower.match(/^(?:::ffff:(?:0:)?|64:ff9b::)(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (dotted) return dotted[1];\n const groups = expandIpv6(lower);\n if (!groups) return '';\n const toV4 = (/** @type {number} */ hi, /** @type {number} */ lo) => `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;\n // ::ffff:a.b.c.d written in hex\n if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 0xffff) return toV4(groups[6], groups[7]);\n // NAT64 64:ff9b::/96\n if (groups[0] === 0x64 && groups[1] === 0xff9b && groups.slice(2, 6).every((g) => g === 0)) return toV4(groups[6], groups[7]);\n // 6to4 2002:AABB:CCDD::/48\n if (groups[0] === 0x2002) return toV4(groups[1], groups[2]);\n return '';\n}\n\n/** The eight 16-bit groups of an IPv6 address, or null. @param {string} address */\nfunction expandIpv6(address) {\n if (!net.isIPv6(address)) return null;\n let text = address.replace(/%.*$/, '');\n const v4 = text.match(/(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (v4) {\n const [a, b, c, d] = v4[1].split('.').map(Number);\n text = text.replace(v4[1], `${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`);\n }\n const [left, right = ''] = text.split('::');\n const head = left ? left.split(':') : [];\n const tail = text.includes('::') && right ? right.split(':') : [];\n const fill = text.includes('::') ? Array(8 - head.length - tail.length).fill('0') : [];\n const groups = [...head, ...fill, ...tail].map((g) => parseInt(g || '0', 16));\n return groups.length === 8 ? groups : null;\n}\n\n/**\n * May the server connect to this IP address? Public unicast only.\n * @param {string} address\n */\nexport function isPublicAddress(address) {\n const ip = String(address ?? '').replace(/^\\[|\\]$/g, '');\n const family = net.isIP(ip);\n if (family === 4) return !blocked.check(ip, 'ipv4');\n if (family === 6) {\n const v4 = embeddedIpv4(ip);\n if (v4) return !blocked.check(v4, 'ipv4');\n return !blocked.check(ip.replace(/%.*$/, ''), 'ipv6');\n }\n return false;\n}\n\n/**\n * A DNS lookup for the socket that refuses names leading anywhere but the\n * public internet: if ANY of the addresses is refused, none is used.\n * @param {(address: string) => boolean} allow\n */\nfunction guardedLookup(allow) {\n /** @type {any} */\n const lookup = (/** @type {string} */ hostname, /** @type {any} */ options, /** @type {any} */ callback) => {\n if (typeof options === 'function') {\n callback = options;\n options = {};\n }\n dns.lookup(hostname, { ...options, all: true }, (error, addresses) => {\n if (error) return callback(error);\n const list = /** @type {dns.LookupAddress[]} */ (/** @type {unknown} */ (addresses));\n if (!list.length || list.some((entry) => !allow(entry.address))) {\n return callback(new BrandImportError('PRIVATE_ADDRESS', hostname));\n }\n if (options.all) return callback(null, list);\n return callback(null, list[0].address, list[0].family);\n });\n };\n return lookup;\n}\n\n/* ── Downloading ────────────────────────────────────────────────────────── */\n\n/** The web's own ports. @param {number} port */\nconst isWebPort = (port) => port === 80 || port === 443;\n\n/**\n * An address the importer may request, or a BrandImportError.\n * @param {string|URL} input\n * @param {(address: string) => boolean} allow\n * @param {(port: number) => boolean} [allowPort]\n */\nfunction checkUrl(input, allow, allowPort = isWebPort) {\n let url;\n try {\n url = new URL(String(input));\n } catch {\n throw new BrandImportError('BAD_URL');\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new BrandImportError('BAD_URL', url.protocol);\n if (url.username || url.password) throw new BrandImportError('BAD_URL', 'credentials');\n if (url.port && !allowPort(Number(url.port))) throw new BrandImportError('BAD_URL', `port ${url.port}`);\n const host = url.hostname.replace(/^\\[|\\]$/g, '');\n if (!host) throw new BrandImportError('BAD_URL');\n // An IP written in the address skips the DNS lookup: checked here.\n if (net.isIP(host) && !allow(host)) throw new BrandImportError('PRIVATE_ADDRESS', host);\n return url;\n}\n\n/**\n * What the user typed, as the address of a home page: \"acme.com\" →\n * \"https://acme.com/\".\n * @param {unknown} input\n */\nexport function siteUrl(input) {\n const text = String(input ?? '').trim();\n if (!text || text.length > 500 || /\\s/.test(text)) throw new BrandImportError('BAD_URL');\n const withScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(text) ? text : `https://${text}`;\n let url;\n try {\n url = new URL(withScheme);\n } catch {\n throw new BrandImportError('BAD_URL');\n }\n if (!/^https?:$/.test(url.protocol) || !url.hostname.includes('.') && !net.isIP(url.hostname.replace(/^\\[|\\]$/g, ''))) {\n throw new BrandImportError('BAD_URL');\n }\n return url.href;\n}\n\n/**\n * GET one address and its redirects, under the rules above.\n * @param {string} address\n * @param {{\n * allow: (address: string) => boolean,\n * allowPort?: (port: number) => boolean,\n * signal: AbortSignal,\n * maxBytes: number,\n * truncate?: boolean,\n * accept?: string,\n * redirects?: number,\n * }} options\n * `truncate`: past `maxBytes`, keep what came (a page's head, the start of\n * a stylesheet) instead of failing.\n * @returns {Promise<{bytes: Buffer, url: string, contentType: string}>}\n */\nexport async function safeGet(\n address,\n { allow, allowPort = isWebPort, signal, maxBytes, truncate = false, accept = '*/*', redirects = BRAND_IMPORT_LIMITS.redirects }\n) {\n let url = checkUrl(address, allow, allowPort);\n for (let hop = 0; ; hop += 1) {\n const response = await requestOnce(url, { allow, signal, maxBytes, truncate, accept });\n if (response.location === null) return { bytes: response.bytes, url: url.href, contentType: response.contentType };\n if (hop >= redirects) throw new BrandImportError('TOO_MANY_REDIRECTS');\n url = checkUrl(new URL(response.location, url), allow, allowPort);\n }\n}\n\n/**\n * @param {URL} url\n * @param {{allow: (address: string) => boolean, signal: AbortSignal, maxBytes: number, truncate: boolean, accept: string}} options\n * @returns {Promise<{location: string|null, bytes: Buffer, contentType: string}>}\n */\nfunction requestOnce(url, { allow, signal, maxBytes, truncate, accept }) {\n return new Promise((resolve, reject) => {\n if (signal.aborted) return reject(abortReason(signal));\n const client = url.protocol === 'https:' ? https : http;\n const request = client.request(url, {\n method: 'GET',\n agent: false,\n lookup: guardedLookup(allow),\n signal,\n headers: {\n 'User-Agent': USER_AGENT,\n Accept: accept,\n 'Accept-Encoding': 'gzip, deflate, br',\n 'Accept-Language': 'en;q=0.9, *;q=0.5',\n },\n });\n request.on('error', (error) => reject(toImportError(error, signal)));\n request.on('response', (response) => {\n const status = response.statusCode || 0;\n if (status >= 300 && status < 400 && response.headers.location) {\n response.resume();\n request.destroy();\n return resolve({ location: response.headers.location, bytes: Buffer.alloc(0), contentType: '' });\n }\n if (status < 200 || status >= 300) {\n response.resume();\n request.destroy();\n return reject(new BrandImportError('HTTP_ERROR', String(status)));\n }\n const encoding = String(response.headers['content-encoding'] || '').toLowerCase().trim();\n /** @type {NodeJS.ReadableStream} */\n let body = response;\n if (encoding === 'gzip' || encoding === 'x-gzip') body = response.pipe(zlib.createGunzip());\n else if (encoding === 'deflate') body = response.pipe(zlib.createInflate());\n else if (encoding === 'br') body = response.pipe(zlib.createBrotliDecompress());\n /** @type {Buffer[]} */\n const chunks = [];\n let size = 0;\n let done = false;\n const finish = (/** @type {Error|null} */ error) => {\n if (done) return;\n done = true;\n request.destroy();\n if (error) reject(error);\n else resolve({ location: null, bytes: Buffer.concat(chunks), contentType: String(response.headers['content-type'] || '') });\n };\n body.on('data', (/** @type {Buffer} */ chunk) => {\n if (done) return;\n if (size + chunk.length > maxBytes) {\n if (!truncate) return finish(new BrandImportError('TOO_LARGE'));\n chunks.push(chunk.subarray(0, maxBytes - size));\n size = maxBytes;\n return finish(null);\n }\n chunks.push(chunk);\n size += chunk.length;\n });\n body.on('end', () => finish(null));\n body.on('error', (error) => finish(toImportError(error, signal)));\n response.on('error', (error) => finish(toImportError(error, signal)));\n response.on('close', () => {\n if (!response.complete) finish(toImportError(new Error('connection closed'), signal));\n });\n });\n request.end();\n });\n}\n\n/** @param {AbortSignal} signal */\nconst abortReason = (signal) =>\n signal.reason?.name === 'TimeoutError' ? new BrandImportError('TIMEOUT') : Object.assign(new Error('Aborted'), { name: 'AbortError' });\n\n/** @param {any} error @param {AbortSignal} signal */\nfunction toImportError(error, signal) {\n if (error instanceof BrandImportError) return error;\n if (error?.cause instanceof BrandImportError) return error.cause;\n if (signal.aborted) return abortReason(signal);\n return new BrandImportError('UNREACHABLE', error?.code || error?.message || '');\n}\n\n/** The text of a page or stylesheet, in its declared charset. @param {Buffer} bytes @param {string} contentType */\nfunction textOf(bytes, contentType) {\n const sniffed = bytes.subarray(0, 2048).toString('latin1').match(/<meta[^>]+charset\\s*=\\s*[\"']?([\\w-]+)/i)?.[1];\n const charset = contentType.match(/charset\\s*=\\s*[\"']?([\\w-]+)/i)?.[1] || sniffed || 'utf-8';\n try {\n return new TextDecoder(charset).decode(bytes);\n } catch {\n return new TextDecoder('utf-8').decode(bytes);\n }\n}\n\n/* ── The importer ───────────────────────────────────────────────────────── */\n\n/**\n * @param {{\n * timeoutMs?: number,\n * allowAddress?: (address: string) => boolean,\n * allowPort?: (port: number) => boolean,\n * }} [options]\n * `allowAddress`, `allowPort`: which IP addresses and ports may be reached\n * (default: public addresses, ports 80 and 443). Tests reading a local\n * server loosen them; nothing else should.\n */\nexport function createBrandImporter({\n timeoutMs = BRAND_IMPORT_LIMITS.timeoutMs,\n allowAddress = isPublicAddress,\n allowPort = isWebPort,\n} = {}) {\n /**\n * @param {unknown} input the site's address, as typed (\"acme.com\" is fine)\n * @param {{signal?: AbortSignal}} [options]\n * @returns {Promise<BrandImport>}\n */\n async function importBrand(input, { signal } = {}) {\n const deadline = AbortSignal.timeout(timeoutMs);\n const all = signal ? AbortSignal.any([signal, deadline]) : deadline;\n const get = (/** @type {string} */ address, /** @type {{maxBytes: number, truncate?: boolean, accept?: string}} */ options) =>\n safeGet(address, { allow: allowAddress, allowPort, signal: all, ...options });\n\n const page = await get(siteUrl(input), {\n maxBytes: BRAND_IMPORT_LIMITS.pageBytes,\n truncate: true,\n accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.5',\n });\n if (page.contentType && !/html|xml/i.test(page.contentType)) throw new BrandImportError('NOT_HTML', page.contentType);\n const reading = readPage(textOf(page.bytes, page.contentType), page.url);\n\n /** @type {BrandNote[]} */\n const notes = [];\n const [sheets, logo] = await Promise.all([\n Promise.all(\n reading.stylesheets.slice(0, BRAND_IMPORT_LIMITS.stylesheets).map((href) =>\n get(href, { maxBytes: BRAND_IMPORT_LIMITS.cssBytes, truncate: true, accept: 'text/css,*/*;q=0.1' })\n .then((sheet) => textOf(sheet.bytes, sheet.contentType))\n .catch((error) => {\n if (error?.name === 'AbortError') throw error;\n return '';\n })\n )\n ),\n (async () => {\n let unsupported = false;\n for (const candidate of reading.logos.slice(0, BRAND_IMPORT_LIMITS.logos)) {\n if (/\\.(svg|webp|ico)(\\?|#|$)/i.test(new URL(candidate.url).pathname)) {\n unsupported ||= candidate.from !== 'icon';\n continue;\n }\n let file;\n try {\n file = await get(candidate.url, { maxBytes: BRAND_IMPORT_LIMITS.logoBytes, accept: 'image/png,image/jpeg,image/gif;q=0.9,*/*;q=0.1' });\n } catch (error) {\n if (/** @type {any} */ (error)?.name === 'AbortError') throw error;\n continue;\n }\n const bytes = new Uint8Array(file.bytes);\n const type = detectImage(bytes);\n const size = type && imageSize(bytes);\n if (!type || !size) {\n unsupported ||= candidate.from !== 'icon' && /svg|webp/i.test(file.contentType);\n continue;\n }\n if (size.width < 32 && size.height < 32) continue;\n if (candidate.from === 'icon') notes.push('LOGO_ICON');\n return { bytes, ...size, ...type };\n }\n notes.push(unsupported ? 'LOGO_UNSUPPORTED' : 'LOGO_NOT_FOUND');\n return null;\n })(),\n ]);\n\n const style = brandFromCss({ css: [reading.css, ...sheets].join('\\n'), themeColor: reading.themeColor });\n if (!style.primary) notes.push('COLORS_NOT_FOUND');\n const brand = cleanBrand({ name: reading.name, website: page.url, ...style });\n return { brand, logo: logo && { ...logo, shownWidth: logoWidthFor(logo) }, notes };\n }\n\n return { import: importBrand };\n}\n","// @ts-check\n/**\n * HTTP HANDLER — every route the builder needs, as one function\n * `(request: Request) => Promise<Response>` (Fetch API).\n *\n * Works as is in Next.js route handlers, Hono, Bun, Deno, Cloudflare Workers;\n * with Express / Node's `http` through `toNodeListener` (see `node.js`).\n *\n * GET {base}/templates read list\n * POST {base}/templates write create, or { duplicateOf }\n * GET {base}/templates/:id read template\n * PUT {base}/templates/:id write save\n * DELETE {base}/templates/:id write delete\n * GET {base}/templates/:id/versions read versions\n * GET {base}/templates/:id/versions/:vid read version with its project\n * POST {base}/render read preview (server rendering)\n * POST {base}/test write test send (needs `transport`)\n * POST {base}/assets write image upload (multipart, field `file`)\n * POST {base}/generate write write an email from a purpose (needs `generator`,\n * the AI module: best-email-builder/ai/server)\n * POST {base}/rewrite write rewrite one text of an email (needs `generator`)\n * POST {base}/subjects write three other subject lines (needs `generator`)\n * GET {base}/variables read custom variables\n * POST {base}/variables write create a custom variable\n * PUT {base}/variables/:key write edit (label, group, sample, fallback)\n * DELETE {base}/variables/:key write delete\n * GET {base}/brand read the workspace's brand\n * PUT {base}/brand write save it (needs `brandStore`)\n * POST {base}/brand/import write read a brand from a website (needs `brandImporter`)\n * GET {assetsPath}:key PUBLIC uploaded image\n *\n * Images are PUBLIC on purpose: mail clients download them without any\n * session. Their keys are content hashes (unguessable), and they are served\n * with headers that forbid any interpretation other than an image.\n *\n * `authorize(request, { action })` is REQUIRED: it returns the user (any\n * object with `id`/`name`, recorded as author) or null to refuse (401).\n * Throw an `HttpError(403, …)` to refuse with another status.\n */\n\nimport { messagesOf, translatorFor } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES, defineVariables } from '../features/variables/catalog.js';\nimport { applyVariablesToText, sampleValues } from '../features/variables/service.js';\nimport { MAX_IMAGE_BYTES } from '../validation/images.js';\nimport { createAssetService } from './assets.js';\nimport { HttpError, statusOf } from './errors.js';\nimport { compileTemplate, personalize } from './render.js';\nimport { MAX_TEST_RECIPIENTS, sendTestEmail } from './sendTest.js';\nimport { createTemplateService } from './templates.js';\nimport { createVariableService } from './variables.js';\nimport { createBrandService } from './brand.js';\nimport { BrandImportError } from './brandImport.js';\nimport { BRAND_IMPORT_ERRORS } from '../features/brand/messages.js';\n\n/**\n * @typedef {'read'|'write'} Action\n * @typedef {{id?: string|number, name?: string}} User\n *\n * @typedef {{\n * templates: import('./templates.js').TemplateRepository,\n * assets: import('./assets.js').AssetRepository,\n * authorize: (request: Request, context: {action: Action}) => Promise<User|null|false> | User | null | false,\n * basePath?: string,\n * assetsPath?: string,\n * publicUrl?: string|null,\n * transport?: import('./sendTest.js').EmailTransport|null,\n * generator?: {\n * generate?: (input: import('../ai/generator.js').GenerateInput, options?: {signal?: AbortSignal}) => Promise<import('../ai/generator.js').GeneratedEmail>,\n * rewrite?: (input: import('../ai/rewrite.js').RewriteInput, options?: {signal?: AbortSignal}) => Promise<{text: string}>,\n * suggestSubjects?: (input: import('../ai/subjects.js').SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[]}>,\n * messages?: Record<string, Record<string, string>>,\n * }|null,\n * variables?: import('../features/variables/catalog.js').EmailVariable[],\n * variableStore?: import('./variables.js').VariableRepository|null,\n * brandStore?: import('./brand.js').BrandRepository|null,\n * brandImporter?: {import: (url: string, options?: {signal?: AbortSignal}) => Promise<import('./brandImport.js').BrandImport>}|null,\n * locale?: string,\n * messages?: Record<string, string>,\n * lang?: string,\n * previewUnsubscribeUrl?: string,\n * maxVersions?: number,\n * maxTestRecipients?: number,\n * rateLimits?: {testPerHour?: number, uploadsPerHour?: number, generatePerHour?: number, rewritePerHour?: number, subjectsPerHour?: number, brandImportsPerHour?: number},\n * onError?: (error: unknown, request: Request) => void,\n * }} HandlerOptions\n */\n\n/** Largest JSON body accepted (project 2 MB + MJML 512 KB + metadata). */\nconst MAX_JSON_BYTES = 3 * 1024 * 1024;\n\nconst HOUR = 60 * 60 * 1000;\n\n/**\n * An error of the AI module, recognised without importing it (a server\n * without AI does not load it).\n * @param {any} error\n * @returns {error is {name: 'EmailGenerationError', status: number, message: string, code: string}}\n */\nconst isGenerationError = (error) =>\n error?.name === 'EmailGenerationError' && Number.isInteger(error.status) && typeof error.code === 'string';\n\nconst json = (data, status = 200) =>\n new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' },\n });\n\n/** Sliding one-hour window per key, in memory (one process). */\nfunction createLimiter(perHour) {\n /** @type {Map<string, number[]>} */\n const hits = new Map();\n return (key) => {\n if (!perHour) return true;\n const now = Date.now();\n const recent = (hits.get(key) || []).filter((at) => now - at < HOUR);\n if (recent.length >= perHour) {\n hits.set(key, recent);\n return false;\n }\n recent.push(now);\n hits.set(key, recent);\n return true;\n };\n}\n\n/**\n * The returned function also exposes `handler.catalog()`: system + custom\n * variables, for the application's own sending pipeline (`personalize`).\n *\n * @param {HandlerOptions} options\n * @returns {((request: Request) => Promise<Response>) & {catalog: () => Promise<import('../features/variables/catalog.js').EmailVariable[]>}}\n */\nexport function createEmailBuilderHandler(options) {\n const {\n templates: templateRepository,\n assets: assetRepository,\n authorize,\n basePath = '/api/email-builder',\n publicUrl = null,\n transport = null,\n generator = null,\n brandImporter = null,\n locale = 'en',\n messages,\n lang = locale.slice(0, 2),\n previewUnsubscribeUrl = '#',\n maxVersions,\n maxTestRecipients = MAX_TEST_RECIPIENTS,\n rateLimits = {},\n onError = (error) => console.error('[best-email-builder]', error),\n } = options || /** @type {any} */ ({});\n if (typeof authorize !== 'function') throw new Error('createEmailBuilderHandler: `authorize` is required');\n if (!templateRepository || !assetRepository) {\n throw new Error('createEmailBuilderHandler: `templates` and `assets` repositories are required');\n }\n\n const base = basePath.replace(/\\/+$/, '');\n const assetsPath = (options.assetsPath || `${base}/assets/`).replace(/\\/*$/, '/');\n const variables = defineVariables(options.variables || DEFAULT_VARIABLES);\n // The AI module's texts (its errors) under the host's own.\n const t = translatorFor(locale, { ...messagesOf(generator?.messages, locale), ...messages });\n const templates = createTemplateService({ repository: templateRepository, t, lang, maxVersions });\n const assets = createAssetService({ repository: assetRepository, publicPath: assetsPath, t });\n const customVariables = createVariableService({ repository: options.variableStore || null, system: variables, t });\n const brand = createBrandService({ repository: options.brandStore || null, t });\n const testLimiter = createLimiter(rateLimits.testPerHour ?? 20);\n const uploadLimiter = createLimiter(rateLimits.uploadsPerHour ?? 100);\n const generateLimiter = createLimiter(rateLimits.generatePerHour ?? 30);\n const rewriteLimiter = createLimiter(rateLimits.rewritePerHour ?? 200);\n const subjectsLimiter = createLimiter(rateLimits.subjectsPerHour ?? 100);\n const brandImportLimiter = createLimiter(rateLimits.brandImportsPerHour ?? 20);\n\n /** @param {Request} request @param {Action} action */\n async function userFor(request, action) {\n const user = await authorize(request, { action });\n if (!user) throw new HttpError(401, t('Authentication required'), 'UNAUTHORIZED');\n return /** @type {User} */ (user);\n }\n\n const userKey = (/** @type {User} */ user) => String(user.id ?? user.name ?? 'anonymous');\n\n /** @param {Request} request */\n /** The browser's language when it is one (`fr`, `pt-BR`), else the server's. @param {unknown} value */\n const languageOr = (value) => (typeof value === 'string' && /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i.test(value) ? value : lang);\n\n async function readJson(request) {\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_JSON_BYTES) throw new HttpError(413, t('Request too large'), 'TOO_LARGE');\n const body = await request.text();\n if (body.length > MAX_JSON_BYTES) throw new HttpError(413, t('Request too large'), 'TOO_LARGE');\n try {\n return body ? JSON.parse(body) : {};\n } catch {\n throw new HttpError(400, t('Invalid JSON'), 'INVALID_JSON');\n }\n }\n\n /**\n * A model call answered as JSON: its errors as the interface shows them,\n * a cancelled request (the user closed the dialog) as 499.\n * @param {Request} request @param {() => Promise<unknown>} call\n */\n async function askModel(request, call) {\n try {\n return json({ data: await call() });\n } catch (error) {\n if (request.signal?.aborted) return json({ error: t('Cancelled'), code: 'CANCELLED' }, 499);\n // The generator's own errors (EmailGenerationError, best-email-builder/ai/server) say what to show.\n if (isGenerationError(error)) throw new HttpError(error.status, t(error.message), error.code);\n onError(error, request);\n throw new HttpError(502, t('The AI service did not answer — try again in a moment.'), 'MODEL_ERROR');\n }\n }\n\n /** @param {string} key */\n async function serveAsset(key) {\n const asset = await assets.get(key);\n if (!asset) return new Response('Not found', { status: 404, headers: { 'Content-Type': 'text/plain' } });\n return new Response(/** @type {any} */ (asset.data), {\n status: 200,\n headers: {\n 'Content-Type': asset.contentType,\n 'Content-Length': String(asset.data.length),\n 'Cache-Control': 'public, max-age=31536000, immutable',\n ETag: `\"${asset.sha256 || key}\"`,\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n },\n });\n }\n\n /** @param {Request} request */\n async function route(request) {\n const url = new URL(request.url);\n const path = url.pathname;\n const method = request.method.toUpperCase();\n\n if (path.startsWith(assetsPath) && (method === 'GET' || method === 'HEAD')) {\n return serveAsset(decodeURIComponent(path.slice(assetsPath.length)));\n }\n if (path !== base && !path.startsWith(`${base}/`)) return null;\n const parts = path.slice(base.length).split('/').filter(Boolean).map(decodeURIComponent);\n const [section, id, sub, subId] = parts;\n\n if (section === 'templates') {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await templates.list() });\n }\n if (!id && method === 'POST') {\n const user = await userFor(request, 'write');\n const body = await readJson(request);\n const result =\n body?.duplicateOf !== undefined && body?.duplicateOf !== null\n ? await templates.duplicate(body.duplicateOf, user)\n : await templates.create(body, user);\n return json({ data: result }, 201);\n }\n if (id && !sub && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await templates.get(id) });\n }\n if (id && !sub && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await templates.save(id, await readJson(request), user) });\n }\n if (id && !sub && method === 'DELETE') {\n await userFor(request, 'write');\n return json({ data: await templates.remove(id) });\n }\n if (id && sub === 'versions' && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: subId ? await templates.getVersion(id, subId) : await templates.listVersions(id) });\n }\n }\n\n if (section === 'render' && !id && method === 'POST') {\n await userFor(request, 'read');\n const body = await readJson(request);\n const catalog = await customVariables.catalog();\n const { compiled, warnings, quality } = await compileTemplate(\n { mjml: body?.mjml, subject: body?.subject, preheader: body?.preheader },\n { t, lang, variables: catalog, preview: true }\n );\n // Same origin as the application: uploaded images stay relative.\n const values = sampleValues(catalog);\n const html = personalize(compiled, { values, unsubscribeUrl: previewUnsubscribeUrl, variables: catalog });\n return json({\n data: {\n html,\n subject: applyVariablesToText(body?.subject || '', values, catalog),\n preheader: applyVariablesToText(body?.preheader || '', values, catalog),\n warnings,\n quality,\n },\n });\n }\n\n if (section === 'test' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!transport) throw new HttpError(501, t('Test sending is not configured'), 'NO_TRANSPORT');\n if (!testLimiter(userKey(user))) throw new HttpError(429, t('Too many test sends — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n const result = await sendTestEmail({\n mjml: body?.mjml,\n subject: body?.subject,\n preheader: body?.preheader,\n emails: body?.emails,\n transport,\n baseUrl: publicUrl,\n unsubscribeUrl: previewUnsubscribeUrl,\n variables: await customVariables.catalog(),\n maxRecipients: maxTestRecipients,\n t,\n lang,\n });\n return json({ data: result });\n }\n\n if (section === 'generate' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const generate = generator?.generate;\n if (typeof generate !== 'function') throw new HttpError(501, t('Email generation is not configured'), 'NO_GENERATOR');\n if (!generateLimiter(userKey(user))) {\n throw new HttpError(429, t('Too many generated emails — try again later'), 'RATE_LIMITED');\n }\n const body = await readJson(request);\n // The variables are the server's catalog, whatever the browser sent.\n const catalog = await customVariables.catalog();\n return askModel(request, () =>\n generate.call(generator, { ...body, language: languageOr(body?.language), variables: catalog }, { signal: request.signal })\n );\n }\n\n if (section === 'rewrite' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const rewrite = generator?.rewrite;\n if (typeof rewrite !== 'function') throw new HttpError(501, t('Text rewriting is not configured'), 'NO_GENERATOR');\n if (!rewriteLimiter(userKey(user))) throw new HttpError(429, t('Too many rewrites — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n return askModel(request, () => rewrite(body, { signal: request.signal }));\n }\n\n if (section === 'subjects' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const suggestSubjects = generator?.suggestSubjects;\n if (typeof suggestSubjects !== 'function') throw new HttpError(501, t('Subject ideas are not configured'), 'NO_GENERATOR');\n if (!subjectsLimiter(userKey(user))) throw new HttpError(429, t('Too many subject ideas — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n // The merge tags are the server's catalog, whatever the browser sent.\n const catalog = await customVariables.catalog();\n return askModel(request, () =>\n suggestSubjects({ ...body, language: languageOr(body?.language), variables: catalog }, { signal: request.signal })\n );\n }\n\n if (section === 'assets' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!uploadLimiter(userKey(user))) throw new HttpError(429, t('Too many uploads — try again later'), 'RATE_LIMITED');\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_IMAGE_BYTES + 64 * 1024) {\n throw new HttpError(413, t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) }), 'TOO_LARGE');\n }\n let form;\n try {\n form = await request.formData();\n } catch {\n throw new HttpError(400, t('Multipart form expected'), 'INVALID_FORM');\n }\n const file = form.get('file');\n if (!file || typeof file === 'string') throw new HttpError(400, t('File missing'), 'FILE_MISSING');\n const bytes = new Uint8Array(await file.arrayBuffer());\n const result = await assets.upload({ bytes, name: file.name, user });\n return json({ data: result }, result.created ? 201 : 200);\n }\n\n if (section === 'variables') {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await customVariables.listCustom() });\n }\n if (!id && method === 'POST') {\n const user = await userFor(request, 'write');\n return json({ data: await customVariables.create(await readJson(request), user) }, 201);\n }\n if (id && !sub && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await customVariables.update(id, await readJson(request), user) });\n }\n if (id && !sub && method === 'DELETE') {\n await userFor(request, 'write');\n return json({ data: await customVariables.remove(id) });\n }\n }\n\n if (section === 'brand' && !sub) {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await brand.get() });\n }\n if (!id && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await brand.save(await readJson(request), user) });\n }\n if (id === 'import' && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!brandImporter) throw new HttpError(501, t('Reading a brand from a website is not configured'), 'NO_BRAND_IMPORTER');\n if (!brandImportLimiter(userKey(user))) {\n throw new HttpError(429, t('Too many websites read — try again later'), 'RATE_LIMITED');\n }\n const body = await readJson(request);\n return json({ data: await importBrand(request, body?.url, user) });\n }\n }\n\n throw new HttpError(404, t('Route not found'), 'ROUTE_NOT_FOUND');\n }\n\n /**\n * A website's brand, its logo stored like an upload. Not saved: the user\n * looks at it first.\n * @param {Request} request @param {unknown} address @param {User} user\n */\n async function importBrand(request, address, user) {\n let result;\n try {\n result = await /** @type {NonNullable<typeof brandImporter>} */ (brandImporter).import(String(address ?? ''), { signal: request.signal });\n } catch (error) {\n if (request.signal?.aborted) throw new HttpError(499, t('Cancelled'), 'CANCELLED');\n if (!(error instanceof BrandImportError)) throw error;\n const [status, message] = BRAND_IMPORT_ERRORS[error.code] || BRAND_IMPORT_ERRORS.UNREACHABLE;\n throw new HttpError(status, t(message), `BRAND_${error.code}`);\n }\n const found = { ...result.brand };\n if (result.logo) {\n const { path } = await assets.upload({ bytes: result.logo.bytes, name: `logo.${result.logo.ext}`, user });\n found.logoUrl = path;\n found.logoWidth = result.logo.shownWidth;\n }\n return { brand: found, notes: result.notes };\n }\n\n async function handle(request) {\n try {\n const response = await route(request);\n return response || json({ error: t('Route not found'), code: 'ROUTE_NOT_FOUND' }, 404);\n } catch (error) {\n const status = statusOf(error);\n // A 5xx we raised ourselves (not configured, AI service down) says why.\n if (status >= 500 && !(error instanceof HttpError)) {\n onError(error, request);\n return json({ error: t('Internal error'), code: 'INTERNAL' }, 500);\n }\n const err = /** @type {any} */ (error);\n return json({ error: err.message, code: err.code || undefined, issues: err.issues || undefined }, status);\n }\n }\n\n handle.catalog = () => customVariables.catalog();\n return handle;\n}\n","// @ts-check\n/**\n * Bridge between a Fetch-API handler and Node's `http` / Express / Connect.\n *\n * app.use(toNodeListener(handler)); // Express\n * http.createServer(toNodeListener(handler)) // plain Node\n *\n * Requests the handler does not own (404 with code ROUTE_NOT_FOUND, outside its\n * routes) are passed to `next()` when there is one.\n */\n\nimport { Readable } from 'node:stream';\n\n/**\n * @param {(request: Request) => Promise<Response>} handler\n * @param {{origin?: string}} [options]\n */\nexport function toNodeListener(handler, { origin } = {}) {\n /**\n * @param {import('node:http').IncomingMessage} req\n * @param {import('node:http').ServerResponse} res\n * @param {(error?: unknown) => void} [next]\n */\n return async function listener(req, res, next) {\n try {\n const host = origin || `http://${req.headers.host || 'localhost'}`;\n const url = new URL(req.url || '/', host);\n const method = (req.method || 'GET').toUpperCase();\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) value.forEach((v) => headers.append(key, v));\n else if (value !== undefined) headers.set(key, value);\n }\n const hasBody = method !== 'GET' && method !== 'HEAD';\n const request = new Request(url, {\n method,\n headers,\n body: hasBody ? /** @type {any} */ (Readable.toWeb(req)) : undefined,\n // @ts-ignore — required by Node for streamed bodies\n duplex: hasBody ? 'half' : undefined,\n });\n const response = await handler(request);\n if (response.status === 404 && next) {\n const body = await response.clone().json().catch(() => null);\n if (body?.code === 'ROUTE_NOT_FOUND') return next();\n }\n res.statusCode = response.status;\n response.headers.forEach((value, key) => res.setHeader(key, value));\n if (!response.body || method === 'HEAD') {\n res.end();\n return;\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } catch (error) {\n if (next) next(error);\n else {\n res.statusCode = 500;\n res.end();\n }\n }\n };\n}\n"],"mappings":"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","names":["http","json","json","actorName","text"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/assets.js","../src/server/errors.js","../src/validation/quality.js","../src/server/compileMjml.js","../src/server/render.js","../src/server/sendTest.js","../src/validation/projectValidator.js","../src/server/templates.js","../src/server/variables.js","../src/server/brand.js","../src/server/brandImport.js","../src/server/handler.js","../src/server/node.js"],"sourcesContent":["// @ts-check\n/**\n * UPLOADED IMAGES.\n *\n * The type is read from the BYTES (PNG, JPEG, GIF only), never trusted from the\n * file name or the declared type: an SVG renamed `.png` carries scripts and is\n * refused. The public key is derived from the content hash (32 hex characters\n * of SHA-256 + extension): the same file uploaded twice is stored once, and a\n * key reveals nothing and cannot be guessed.\n *\n * Storage is a REPOSITORY supplied by the application (database, object\n * storage…); `createMemoryAssetRepository` is the reference implementation.\n */\n\nimport { createHash } from 'node:crypto';\nimport { identity } from '../i18n/index.js';\nimport { ASSET_KEY_PATTERN, MAX_IMAGE_BYTES, detectImage } from '../validation/images.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {{\n * key: string,\n * sha256: string,\n * contentType: string,\n * size: number,\n * name: string|null,\n * data: Uint8Array,\n * createdBy: string|null,\n * }} AssetRecord\n *\n * @typedef {{\n * findBySha256: (sha256: string) => Promise<{key: string}|null>,\n * insert: (record: AssetRecord) => Promise<void>,\n * get: (key: string) => Promise<{contentType: string, data: Uint8Array, sha256?: string}|null>,\n * }} AssetRepository\n */\n\n/**\n * @param {Uint8Array} bytes\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function inspectAsset(bytes, { t = identity } = {}) {\n if (!bytes || bytes.length === 0) throw new HttpError(400, t('Empty file'), 'EMPTY_FILE');\n if (bytes.length > MAX_IMAGE_BYTES) {\n throw new HttpError(413, t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) }), 'TOO_LARGE');\n }\n const image = detectImage(bytes);\n if (!image) throw new HttpError(415, t('Format rejected: PNG, JPEG or GIF expected'), 'UNSUPPORTED_IMAGE');\n const sha256 = createHash('sha256').update(bytes).digest('hex');\n return { type: image.type, key: `${sha256.slice(0, 32)}.${image.ext}`, sha256 };\n}\n\nfunction cleanName(name) {\n // eslint-disable-next-line no-control-regex\n return String(name || '').replace(/[\\u0000-\\u001f\\u007f<>\"]/g, '').trim().slice(0, 255) || null;\n}\n\n/**\n * @param {{repository: AssetRepository, publicPath: string, t?: import('../i18n/index.js').Translate}} options\n */\nexport function createAssetService({ repository, publicPath, t = identity }) {\n const prefix = publicPath.endsWith('/') ? publicPath : `${publicPath}/`;\n return {\n /**\n * @param {{bytes: Uint8Array, name?: string|null, user?: {id?: string|number, name?: string}|null}} input\n */\n async upload({ bytes, name = null, user = null }) {\n const { type, key, sha256 } = inspectAsset(bytes, { t });\n const result = { key, path: `${prefix}${key}`, contentType: type, size: bytes.length };\n const existing = await repository.findBySha256(sha256);\n if (existing) return { ...result, key: existing.key, path: `${prefix}${existing.key}`, created: false };\n try {\n await repository.insert({\n key,\n sha256,\n contentType: type,\n size: bytes.length,\n name: cleanName(name),\n data: bytes,\n createdBy: user ? String(user.name ?? user.id ?? '') || null : null,\n });\n } catch (error) {\n // Two simultaneous uploads of the same file: the other one won, same image.\n if (await repository.findBySha256(sha256)) return { ...result, created: false };\n throw error;\n }\n return { ...result, created: true };\n },\n\n /** @param {string} key */\n async get(key) {\n if (!ASSET_KEY_PATTERN.test(String(key || ''))) return null;\n return repository.get(key);\n },\n };\n}\n\n/** @returns {AssetRepository & {size: () => number}} */\nexport function createMemoryAssetRepository() {\n /** @type {Map<string, AssetRecord>} */\n const byKey = new Map();\n return {\n async findBySha256(sha256) {\n for (const record of byKey.values()) if (record.sha256 === sha256) return { key: record.key };\n return null;\n },\n async insert(record) {\n if (byKey.has(record.key)) throw new Error('duplicate asset');\n byKey.set(record.key, { ...record, data: new Uint8Array(record.data) });\n },\n async get(key) {\n const record = byKey.get(key);\n return record ? { contentType: record.contentType, data: record.data, sha256: record.sha256 } : null;\n },\n size: () => byKey.size,\n };\n}\n","// @ts-check\n/**\n * Errors with an HTTP status: services throw them, the handler turns them into\n * JSON responses (`{ error, code }`). Any other error is a 500 whose message is\n * not exposed.\n */\n\nexport class HttpError extends Error {\n /**\n * @param {number} status\n * @param {string} message\n * @param {string} [code]\n */\n constructor(status, message, code) {\n super(message);\n this.name = 'HttpError';\n this.status = status;\n this.code = code;\n }\n}\n\n/** Validators and the document reader throw errors carrying a `status`. */\nexport function statusOf(error) {\n const status = Number(/** @type {any} */ (error)?.status);\n return Number.isInteger(status) && status >= 400 && status < 600 ? status : 500;\n}\n","// @ts-check\n/**\n * QUALITY CHECKS for a template — issues that do not block saving, but that\n * should be known before sending.\n *\n * missing unsubscribe a legal requirement for bulk sending;\n * image without file an empty frame for the recipient;\n * image without alt text Outlook blocks images by default: the alternative\n * text is then all that can be read;\n * button without link a button that leads nowhere;\n * http link flagged as \"not secure\" by some email clients;\n * empty subject the message goes out as \"(no subject)\";\n * size > 102 KB Gmail clips the message and hides the end —\n * including the unsubscribe link.\n *\n * Dependency-free module: used by the preview (server) and tested under Node.\n */\n\nimport { identity } from '../i18n/index.js';\n\n/** Gmail threshold beyond which the message is clipped (\"[Message clipped]\"). */\nexport const GMAIL_CLIP_BYTES = 102 * 1024;\n\n/**\n * @typedef {{code: string, level: 'warning'|'info', message: string, count?: number}} QualityIssue\n */\n\n/** @param {string} source @param {RegExp} pattern */\nfunction count(source, pattern) {\n return (source.match(pattern) || []).length;\n}\n\n/**\n * @param {{mjml: string, html?: string, subject?: string|null}} input\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n * @returns {QualityIssue[]}\n */\nexport function qualityReport({ mjml, html = '', subject = null }, { t = identity } = {}) {\n const source = String(mjml || '');\n /** @type {QualityIssue[]} */\n const issues = [];\n\n if (!/\\{\\{\\s*unsubscribe\\s*\\}\\}|\\[UNSUBSCRIBE\\]/i.test(source)) {\n issues.push({ code: 'NO_UNSUBSCRIBE', level: 'warning', message: t('Missing unsubscribe link') });\n }\n\n const images = source.match(/<mj-image\\b[^>]*>/gi) || [];\n const noSrc = images.filter((tag) => !/\\ssrc=\"[^\"]+\"/i.test(tag)).length;\n if (noSrc) {\n issues.push({\n code: 'IMAGE_NO_SRC',\n level: 'warning',\n count: noSrc,\n message: t('Image without file ({count})', { count: noSrc }),\n });\n }\n const noAlt = images.filter((tag) => !/\\salt=\"[^\"]+\"/i.test(tag)).length;\n if (noAlt) {\n issues.push({\n code: 'IMAGE_NO_ALT',\n level: 'warning',\n count: noAlt,\n message: t('Image without alt text ({count})', { count: noAlt }),\n });\n }\n\n const noHref = count(source, /<mj-button\\b(?![^>]*\\shref=\"[^\"]+\")[^>]*>/gi);\n if (noHref) {\n issues.push({\n code: 'BUTTON_NO_LINK',\n level: 'warning',\n count: noHref,\n message: t('Button without link ({count})', { count: noHref }),\n });\n }\n\n const http = count(source, /\\shref=\"http:\\/\\//gi);\n if (http) {\n issues.push({\n code: 'HTTP_LINK',\n level: 'info',\n count: http,\n message: t('Insecure http link ({count})', { count: http }),\n });\n }\n\n if (subject !== null && !String(subject).trim()) {\n issues.push({ code: 'NO_SUBJECT', level: 'warning', message: t('Empty subject') });\n }\n\n const bytes = new TextEncoder().encode(String(html || '')).length;\n if (bytes > GMAIL_CLIP_BYTES) {\n issues.push({\n code: 'GMAIL_CLIP',\n level: 'warning',\n message: t('Message is {size} KB: Gmail clips messages over {limit} KB', {\n size: Math.round(bytes / 1024),\n limit: GMAIL_CLIP_BYTES / 1024,\n }),\n });\n }\n\n return issues;\n}\n","// @ts-check\n/**\n * MJML → HTML compilation, ON THE SERVER — the only one that counts.\n *\n * The canvas compiles too (mjml-browser) to display the email while editing;\n * but the saved HTML, a test's and a campaign's all come from here. The\n * browser never sends HTML to the server.\n *\n * ignoreIncludes `mj-include` would read a server file (the validator\n * already rejects it; belt and braces);\n * fonts: {} no Google font added by default: one more external link in\n * the message, for fonts the blocks do not use;\n * validationLevel soft: MJML deviations come back as warnings. Blocking them\n * would lose work for a mistyped attribute value; safety is\n * enforced upstream by `validation/`.\n *\n * Two lines are added to MJML's <head>, as email best practice asks:\n * x-apple-disable-message-reformatting Apple Mail (macOS, iOS) does not\n * rescale the email it judges too wide;\n * format-detection iOS does not turn dates, addresses and phone numbers\n * into blue links over the design (a real link is written\n * as one: `tel:`, `mailto:`).\n */\n\nconst CLIENT_METAS =\n '<meta name=\"x-apple-disable-message-reformatting\">' +\n '<meta name=\"format-detection\" content=\"telephone=no, date=no, address=no, email=no, url=no\">';\n\n/** @param {string} html */\nexport function withClientMetas(html) {\n if (!html || html.includes('x-apple-disable-message-reformatting')) return html;\n const viewport = /<meta name=\"viewport\"[^>]*>/i.exec(html);\n if (viewport) return html.slice(0, viewport.index + viewport[0].length) + CLIENT_METAS + html.slice(viewport.index + viewport[0].length);\n return html.replace(/<head>/i, (head) => head + CLIENT_METAS);\n}\n\nimport mjml2html from 'mjml';\n\n/** @typedef {{line?: number, message: string, tagName?: string}} CompileWarning */\n\n/**\n * @param {string} mjml\n * @returns {Promise<{html: string, warnings: CompileWarning[]}>}\n */\nexport async function compileMjml(mjml) {\n const compile = /** @type {any} */ (mjml2html);\n // MJML 5 compiles asynchronously; `await` also accepts a synchronous result.\n const result = await compile(String(mjml || ''), {\n validationLevel: 'soft',\n keepComments: false,\n beautify: false,\n minify: false,\n ignoreIncludes: true,\n fonts: {},\n });\n return {\n html: withClientMetas(String(result?.html || '')),\n warnings: (result?.errors || []).map((/** @type {any} */ e) => ({\n line: e.line,\n message: String(e.formattedMessage || e.message || e),\n tagName: e.tagName,\n })),\n };\n}\n","// @ts-check\n/**\n * Rendering of a template outside of a campaign — preview and test send.\n *\n * MJML ─► validation ─► compilation (mjml, server) ─► variables ─► links ─► HTML\n *\n * Same validation and compilation steps as saving (`templates.js`): what the\n * preview shows is what will be stored. Variables get SAMPLE values (the real\n * address for a test); the unsubscribe link is inert.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { assertValidMjml } from '../validation/mjmlValidator.js';\nimport { qualityReport } from '../validation/quality.js';\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { applyVariables, unknownVariables } from '../features/variables/service.js';\nimport { prepareMjml } from '../renderer/prepareMjml.js';\nimport { absoluteAssetUrls, applyServiceLinks } from '../renderer/links.js';\nimport { compileMjml } from './compileMjml.js';\n\n/**\n * @typedef {import('../i18n/index.js').Translate} Translate\n * @typedef {import('../features/variables/catalog.js').EmailVariable} EmailVariable\n */\n\n/**\n * Validation + compilation, once per template. The quality report also lists\n * tags that are not in the catalog (typos), which would render empty.\n *\n * @param {{mjml: string, subject?: string|null, preheader?: string|null}} input\n * @param {{t?: Translate, lang?: string, variables?: EmailVariable[], preview?: boolean}} [options]\n * `preview`: for a person, not an inbox — an image with no picture yet is\n * kept (the page draws its frame) instead of left out.\n */\nexport async function compileTemplate(\n { mjml, subject = null, preheader = null },\n { t = identity, lang = 'en', variables = DEFAULT_VARIABLES, preview = false } = {}\n) {\n assertValidMjml(mjml, { t });\n const prepared = prepareMjml(mjml, { title: subject, preheader, lang, emptyImages: preview ? 'keep' : 'drop' });\n const { html, warnings } = await compileMjml(prepared);\n const quality = qualityReport({ mjml, html, subject }, { t });\n const unknown = unknownVariables([mjml, subject || '', preheader || ''].join('\\n'), variables);\n if (unknown.length) {\n quality.push({\n code: 'UNKNOWN_VARIABLE',\n level: 'warning',\n count: unknown.length,\n message: t('Unknown variables (rendered empty): {tags}', { tags: unknown.map((k) => `{{${k}}}`).join(', ') }),\n });\n }\n return { compiled: html, warnings, quality };\n}\n\n/**\n * Personalization of compiled HTML, once per recipient. `baseUrl` makes\n * uploaded images absolute — omit it for a preview inside the application,\n * which reads them from its own origin.\n *\n * @param {string} compiled\n * @param {{\n * values: Record<string, string|null|undefined>,\n * unsubscribeUrl: string,\n * baseUrl?: string|null,\n * variables?: EmailVariable[],\n * }} options\n */\nexport function personalize(compiled, { values, unsubscribeUrl, baseUrl = null, variables = DEFAULT_VARIABLES }) {\n const html = applyServiceLinks(applyVariables(compiled, values, variables), { unsubscribeUrl });\n return absoluteAssetUrls(html, baseUrl);\n}\n\n/**\n * Compiles and personalizes in one call — for a single email.\n *\n * @param {{\n * mjml: string,\n * subject?: string|null,\n * preheader?: string|null,\n * values?: Record<string, string|null|undefined>,\n * unsubscribeUrl?: string,\n * baseUrl?: string|null,\n * variables?: EmailVariable[],\n * t?: Translate,\n * lang?: string,\n * }} input\n */\nexport async function renderEmail({\n mjml,\n subject = null,\n preheader = null,\n values = {},\n unsubscribeUrl = '#',\n baseUrl = null,\n variables = DEFAULT_VARIABLES,\n t = identity,\n lang = 'en',\n}) {\n const { compiled, warnings, quality } = await compileTemplate({ mjml, subject, preheader }, { t, lang, variables });\n return { html: personalize(compiled, { values, unsubscribeUrl, baseUrl, variables }), warnings, quality };\n}\n\n/**\n * Plain-text alternative (clients without HTML, spam filters).\n * @param {string} html\n */\nexport function htmlToText(html) {\n return String(html || '')\n .replace(/<(style|head)[\\s\\S]*?<\\/\\1>/gi, '')\n .replace(/<(br|\\/p|\\/div|\\/tr|\\/h\\d)[^>]*>/gi, '\\n')\n .replace(/<[^>]+>/g, '')\n .replace(/ /g, ' ')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, '&')\n .replace(/[ \\t]+\\n/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n","// @ts-check\n/**\n * TEST SEND of a template — to proofreading addresses only.\n *\n * A browser preview proves nothing about what Outlook (Word's engine) will do:\n * a test in a real mailbox is the only real check.\n *\n * - same rendering as the preview, one address at a time (`to` is never a\n * list: reviewers do not see each other);\n * - subject prefixed with \"[TEST]\"; the email variable gets the address being\n * served, the others sample values; inert unsubscribe link;\n * - a bounded number of addresses — beyond that it is no longer a test;\n * - one failure does not stop the others: each address gets its result.\n *\n * The transport is INJECTED, which makes this testable without sending.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES } from '../features/variables/catalog.js';\nimport { applyVariablesToText, sampleValues } from '../features/variables/service.js';\nimport { HttpError } from './errors.js';\nimport { compileTemplate, htmlToText, personalize } from './render.js';\n\nexport const MAX_TEST_RECIPIENTS = 10;\nconst EMAIL_SHAPE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]{2,}$/;\n\n/**\n * @typedef {{to: string, subject: string, html: string, text: string}} OutgoingEmail\n * @typedef {{send: (email: OutgoingEmail) => Promise<unknown>}} EmailTransport\n */\n\n/**\n * @param {{\n * mjml: string,\n * subject?: string|null,\n * preheader?: string|null,\n * emails: string[]|string,\n * transport: EmailTransport,\n * baseUrl?: string|null,\n * unsubscribeUrl?: string,\n * variables?: import('../features/variables/catalog.js').EmailVariable[],\n * maxRecipients?: number,\n * t?: import('../i18n/index.js').Translate,\n * lang?: string,\n * }} input\n */\nexport async function sendTestEmail({\n mjml,\n subject = null,\n preheader = null,\n emails,\n transport,\n baseUrl = null,\n unsubscribeUrl = '#',\n variables = DEFAULT_VARIABLES,\n maxRecipients = MAX_TEST_RECIPIENTS,\n t = identity,\n lang = 'en',\n}) {\n const list = (Array.isArray(emails) ? emails : String(emails || '').split(/[,;\\s]+/))\n .map((e) => String(e || '').trim().toLowerCase())\n .filter(Boolean);\n const valid = [...new Set(list.filter((e) => EMAIL_SHAPE.test(e)))];\n const invalid = list.filter((e) => !EMAIL_SHAPE.test(e));\n if (!valid.length) throw new HttpError(400, t('No usable test address'), 'NO_RECIPIENT');\n if (valid.length > maxRecipients) {\n throw new HttpError(400, t('{max} test addresses at most', { max: maxRecipients }), 'TOO_MANY_RECIPIENTS');\n }\n\n // Compiled once (validation included), then personalized per address.\n const { compiled, warnings, quality } = await compileTemplate({ mjml, subject, preheader }, { t, lang, variables });\n\n const results = [];\n for (const email of valid) {\n try {\n const values = sampleValues(variables, { 'contact.email': email });\n const html = personalize(compiled, { values, unsubscribeUrl, baseUrl, variables });\n const personal = applyVariablesToText(String(subject || '').trim(), values, variables) || t('No subject');\n await transport.send({ to: email, subject: `[TEST] ${personal}`.slice(0, 200), html, text: htmlToText(html) });\n results.push({ email, ok: true, error: null });\n } catch (error) {\n // Short message: a gateway error can carry the whole HTML.\n results.push({ email, ok: false, error: String(/** @type {Error} */ (error)?.message || 'failed').slice(0, 300) });\n }\n }\n\n return {\n sent: results.filter((r) => r.ok).length,\n failed: results.filter((r) => !r.ok).length,\n invalid,\n results,\n warnings,\n quality,\n };\n}\n","// @ts-check\n/**\n * What a PROJECT (the editor's project data) may contain.\n *\n * The project is reloaded as-is into the editor canvas — an iframe of the host\n * page. It does NOT go through the editor engine's HTML parser (which drops\n * scripts and `on*` attributes): it is already structured data. The check must\n * therefore happen here, on save; otherwise a tampered project would run code\n * in the browser of the next user who opens it.\n *\n * The walk is generic (any object, any array) rather than modelled on the\n * engine's format: with another engine or another version, the rule still\n * holds.\n */\n\nimport { readDocument } from '../core/document.js';\nimport { identity } from '../i18n/index.js';\nimport { IMAGE_ATTRIBUTES, LINK_ATTRIBUTES, isSafeUrl } from './urls.js';\n\n/** Maximum size of a project's JSON. */\nexport const PROJECT_MAX_LENGTH = 2 * 1024 * 1024;\n\nconst MAX_DEPTH = 200;\n/** Forbidden HTML/MJML tags (a component's `tagName`). */\nconst DANGEROUS_TAGS = /^(script|iframe|frame|object|embed|form|base|link|meta|svg|math|mj-include|mj-raw)$/i;\n/**\n * Forbidden component types (`type`). Shorter list than the tags: for the\n * engine, `link` is the type of an `<a>` link — unrelated to the `<link>` tag\n * (external style sheet), which is still rejected through its `tagName`.\n */\nconst DANGEROUS_TYPES = /^(script|iframe|frame|object|embed|form|svg|mj-include|mj-raw)$/i;\n\n/**\n * @typedef {import('./mjmlValidator.js').ValidationIssue} ValidationIssue\n */\n\n/**\n * @param {unknown} raw envelope (object or JSON string)\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n * @returns {{ok: boolean, issues: ValidationIssue[], document: import('../core/document.js').EmailDocument|null, json: string}}\n */\nexport function validateProject(raw, { t = identity } = {}) {\n /** @type {ValidationIssue[]} */\n const issues = [];\n let document;\n try {\n document = readDocument(raw);\n } catch (error) {\n return {\n ok: false,\n issues: [{ code: 'ENVELOPE', message: /** @type {Error} */ (error).message }],\n document: null,\n json: '',\n };\n }\n\n const json = JSON.stringify(document);\n if (json.length > PROJECT_MAX_LENGTH) {\n issues.push({\n code: 'TOO_LARGE',\n message: t('project too large ({size} KB, maximum {max} KB)', {\n size: Math.round(json.length / 1024),\n max: PROJECT_MAX_LENGTH / 1024,\n }),\n });\n }\n\n /** @param {any} node @param {number} depth */\n function walk(node, depth) {\n if (depth > MAX_DEPTH) {\n issues.push({ code: 'TOO_DEEP', message: t('project too deep') });\n return;\n }\n if (Array.isArray(node)) {\n for (const item of node) walk(item, depth + 1);\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n // `tag`: the builder's own project; `type` / `tagName`: projects of other engines.\n for (const [key, pattern] of /** @type {[string, RegExp][]} */ ([['type', DANGEROUS_TYPES], ['tagName', DANGEROUS_TAGS], ['tag', DANGEROUS_TAGS]])) {\n const value = node[key];\n if (typeof value === 'string' && pattern.test(value)) {\n issues.push({ code: 'FORBIDDEN_TAG', message: t('forbidden element in project: {tag}', { tag: value }), tag: value });\n }\n }\n for (const key of ['script', 'script-export', 'script-props']) {\n const value = node[key];\n if (value && (typeof value !== 'object' || Object.keys(value).length > 0)) {\n issues.push({ code: 'SCRIPT', message: t('component script not allowed') });\n }\n }\n for (const key of ['content', 'components']) {\n const value = node[key];\n if (typeof value === 'string' && /<\\s*(script|iframe|object|embed)\\b|javascript:/i.test(value)) {\n issues.push({ code: 'UNSAFE_CONTENT', message: t('executable content not allowed') });\n }\n }\n const attributes = node.attributes;\n if (attributes && typeof attributes === 'object' && !Array.isArray(attributes)) {\n for (const [name, value] of Object.entries(attributes)) {\n const lower = name.toLowerCase();\n if (lower.startsWith('on')) {\n issues.push({ code: 'EVENT_ATTRIBUTE', message: t('attribute {name} not allowed', { name }), attribute: name });\n } else if (LINK_ATTRIBUTES.has(lower) && !isSafeUrl(String(value ?? ''), 'link')) {\n issues.push({ code: 'UNSAFE_URL', message: t('URL rejected ({name})', { name }), attribute: name });\n } else if (IMAGE_ATTRIBUTES.has(lower) && !isSafeUrl(String(value ?? ''), 'image')) {\n issues.push({ code: 'UNSAFE_IMAGE', message: t('image rejected ({name})', { name }), attribute: name });\n }\n }\n }\n for (const [key, value] of Object.entries(node)) {\n if (key !== 'attributes' && value && typeof value === 'object') walk(value, depth + 1);\n }\n }\n\n walk(document.project, 0);\n return { ok: issues.length === 0, issues, document, json };\n}\n\n/**\n * Validates and throws a readable 422 error listing the first rejections.\n * @param {unknown} raw\n * @param {{t?: import('../i18n/index.js').Translate}} [options]\n */\nexport function assertValidProject(raw, { t = identity } = {}) {\n const result = validateProject(raw, { t });\n if (result.ok && result.document) return { document: result.document, json: result.json };\n const shown = result.issues.slice(0, 3).map((i) => i.message).join('; ');\n const error = /** @type {Error & {status?: number, issues?: ValidationIssue[]}} */ (\n new Error(t('Project rejected: {issues}', { issues: shown }))\n );\n error.status = 422;\n error.issues = result.issues;\n throw error;\n}\n","// @ts-check\n/**\n * TEMPLATES — the server-side business rules, independent of the database.\n *\n * ── THE PROJECT IS THE SOURCE, HTML A RESULT ────────────────────────────────\n * Every write receives the PROJECT (versioned envelope) and its MJML export.\n * The server validates both (`validation/`), compiles the MJML itself and only\n * stores ITS HTML. No route accepts HTML coming from the browser.\n *\n * ── CONCURRENT WRITES ───────────────────────────────────────────────────────\n * Two people can open the same template. Each write carries the `revision`\n * read at load time; the repository refuses the write (409) when someone else\n * saved in the meantime — instead of silently overwriting their work.\n *\n * ── VERSIONS ───────────────────────────────────────────────────────────────\n * One version per EXPLICIT save (`createVersion`), never per autosave. Only the\n * latest `maxVersions` stay listed.\n *\n * Storage is a REPOSITORY supplied by the application (see the typedef below\n * and `createMemoryTemplateRepository`, the reference implementation).\n */\n\nimport { identity } from '../i18n/index.js';\nimport { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from '../core/constants.js';\nimport { assertValidProject } from '../validation/projectValidator.js';\nimport { assertValidMjml } from '../validation/mjmlValidator.js';\nimport { prepareMjml } from '../renderer/prepareMjml.js';\nimport { compileMjml } from './compileMjml.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {{\n * name: string,\n * description: string|null,\n * subject: string|null,\n * preheader: string|null,\n * projectJson: string,\n * mjml: string,\n * html: string,\n * builderVersion: string,\n * schemaVersion: number,\n * }} TemplateFields\n *\n * @typedef {TemplateFields & {\n * id: string|number,\n * revision: string,\n * createdAt: string|Date|null,\n * updatedAt: string|Date|null,\n * createdBy: string|null,\n * updatedBy: string|null,\n * }} TemplateRecord\n *\n * @typedef {{\n * id: string|number,\n * versionNumber: number,\n * name: string,\n * subject: string|null,\n * preheader: string|null,\n * projectJson: string,\n * mjml: string,\n * createdBy: string|null,\n * createdAt: string|Date|null,\n * }} VersionRecord\n *\n * Every method may be async. `update` MUST compare `expectedRevision` and write\n * atomically (e.g. `UPDATE … WHERE id = ? AND revision = ?`), returning\n * `{ conflict: true, current }` on mismatch and `null` when the id is unknown.\n * `addVersion` numbers versions (max + 1) and keeps the latest `keep` listed.\n *\n * @typedef {{\n * list: () => Promise<TemplateRecord[]>,\n * find: (id: string) => Promise<TemplateRecord|null>,\n * insert: (fields: TemplateFields & {createdBy: string|null}) => Promise<TemplateRecord>,\n * update: (id: string, fields: Partial<TemplateFields> & {updatedBy: string|null},\n * options: {expectedRevision: string|null}) =>\n * Promise<TemplateRecord | {conflict: true, current: TemplateRecord} | null>,\n * remove: (id: string) => Promise<boolean>,\n * addVersion: (templateId: string, version: Omit<VersionRecord, 'id'|'versionNumber'|'createdAt'>,\n * options: {keep: number}) => Promise<{id: string|number, versionNumber: number}>,\n * listVersions: (templateId: string) => Promise<VersionRecord[]>,\n * findVersion: (templateId: string, versionId: string) => Promise<VersionRecord|null>,\n * transaction?: <T>(work: () => Promise<T>) => Promise<T>,\n * }} TemplateRepository\n *\n * @typedef {{id?: string|number, name?: string}|null} Actor\n */\n\n/** Column sizes. */\nexport const LIMITS = { name: 200, description: 1000, subject: 250, preheader: 250 };\n\nexport const MAX_ACTIVE_VERSIONS = 50;\n\nfunction text(value, max) {\n const out = String(value ?? '').trim();\n return out ? out.slice(0, max) : null;\n}\n\nconst iso = (value) => (value ? new Date(value).toISOString() : null);\nconst actorName = (/** @type {Actor} */ user) => (user ? String(user.name ?? user.id ?? '') || null : null);\n\nfunction parseProject(json) {\n try {\n return JSON.parse(json);\n } catch {\n return null;\n }\n}\n\n/** Record → API object. */\nfunction toTemplate(/** @type {TemplateRecord} */ row, { withProject = true } = {}) {\n return {\n id: row.id,\n name: row.name,\n description: row.description,\n subject: row.subject,\n preheader: row.preheader,\n revision: row.revision,\n builderVersion: row.builderVersion,\n schemaVersion: row.schemaVersion,\n createdBy: row.createdBy,\n updatedBy: row.updatedBy,\n createdAt: iso(row.createdAt),\n updatedAt: iso(row.updatedAt),\n ...(withProject ? { document: parseProject(row.projectJson), mjml: row.mjml, html: row.html } : {}),\n };\n}\n\n/** @param {VersionRecord} row */\nfunction toVersion(row, { withProject = false } = {}) {\n return {\n id: row.id,\n versionNumber: row.versionNumber,\n name: row.name,\n subject: row.subject,\n createdBy: row.createdBy,\n createdAt: iso(row.createdAt),\n ...(withProject ? { preheader: row.preheader, document: parseProject(row.projectJson), mjml: row.mjml } : {}),\n };\n}\n\n/**\n * @param {{\n * repository: TemplateRepository,\n * t?: import('../i18n/index.js').Translate,\n * lang?: string,\n * maxVersions?: number,\n * }} options\n */\nexport function createTemplateService({ repository, t = identity, lang = 'en', maxVersions = MAX_ACTIVE_VERSIONS }) {\n const inTransaction = (work) => (repository.transaction ? repository.transaction(work) : work());\n\n /**\n * Validates the project and the MJML, compiles the final HTML.\n * @param {{document: any, mjml: any, subject?: string|null, preheader?: string|null}} input\n */\n async function compile({ document, mjml, subject = null, preheader = null }) {\n const { json } = assertValidProject(document, { t });\n assertValidMjml(mjml, { t });\n const { html, warnings } = await compileMjml(prepareMjml(mjml, { title: subject, preheader, lang }));\n if (!html) throw new HttpError(422, t('Empty MJML compilation'), 'EMPTY_COMPILATION');\n return { projectJson: json, mjml: String(mjml), html, warnings };\n }\n\n async function findOrThrow(id) {\n const row = await repository.find(String(id));\n if (!row) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n return row;\n }\n\n const service = {\n compile,\n\n async list() {\n const rows = await repository.list();\n return rows.map((row) => toTemplate(row, { withProject: false }));\n },\n\n /** @param {string|number} id */\n async get(id) {\n return toTemplate(await findOrThrow(id));\n },\n\n /**\n * Creates a template and its version 1.\n * @param {any} body\n * @param {Actor} [user]\n */\n async create(body, user = null) {\n const name = text(body?.name, LIMITS.name);\n if (!name) throw new HttpError(400, t('Name required'), 'NAME_REQUIRED');\n const subject = text(body?.subject, LIMITS.subject);\n const preheader = text(body?.preheader, LIMITS.preheader);\n const { projectJson, mjml, html, warnings } = await compile({ document: body?.document, mjml: body?.mjml, subject, preheader });\n const by = actorName(user);\n\n const row = await inTransaction(async () => {\n const created = await repository.insert({\n name,\n description: text(body?.description, LIMITS.description),\n subject,\n preheader,\n projectJson,\n mjml,\n html,\n builderVersion: EMAIL_BUILDER_VERSION,\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n createdBy: by,\n });\n await repository.addVersion(\n String(created.id),\n { name, subject, preheader, projectJson, mjml, createdBy: by },\n { keep: maxVersions }\n );\n return created;\n });\n return { template: toTemplate(row), warnings };\n },\n\n /**\n * Saves a template. Fields left undefined keep their stored value.\n * @param {string|number} id\n * @param {any} body\n * @param {Actor} [user]\n */\n async save(id, body, user = null) {\n const current = await findOrThrow(id);\n const name = body?.name !== undefined ? text(body.name, LIMITS.name) : current.name;\n if (!name) throw new HttpError(400, t('Name required'), 'NAME_REQUIRED');\n const subject = body?.subject !== undefined ? text(body.subject, LIMITS.subject) : current.subject;\n const preheader = body?.preheader !== undefined ? text(body.preheader, LIMITS.preheader) : current.preheader;\n const description =\n body?.description !== undefined ? text(body.description, LIMITS.description) : current.description;\n const { projectJson, mjml, html, warnings } = await compile({ document: body?.document, mjml: body?.mjml, subject, preheader });\n const by = actorName(user);\n\n const result = await inTransaction(async () => {\n const updated = await repository.update(\n String(id),\n {\n name,\n description,\n subject,\n preheader,\n projectJson,\n mjml,\n html,\n builderVersion: EMAIL_BUILDER_VERSION,\n schemaVersion: EMAIL_DOCUMENT_SCHEMA_VERSION,\n updatedBy: by,\n },\n { expectedRevision: body?.revision ? String(body.revision) : null }\n );\n if (!updated) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n if ('conflict' in updated) {\n const who = updated.current.updatedBy;\n throw new HttpError(\n 409,\n who\n ? t('Template changed in the meantime by {user} — reload it before saving', { user: who })\n : t('Template changed in the meantime — reload it before saving'),\n 'CONFLICT'\n );\n }\n const version = body?.createVersion\n ? await repository.addVersion(\n String(id),\n { name, subject, preheader, projectJson, mjml, createdBy: by },\n { keep: maxVersions }\n )\n : null;\n return { row: updated, version };\n });\n\n // The editor keeps its own document: only what changed server side is returned.\n const { document: _omit, ...template } = toTemplate(result.row);\n return { template, warnings, version: result.version };\n },\n\n /** @param {string|number} id @param {Actor} [user] */\n async duplicate(id, user = null) {\n const source = await findOrThrow(id);\n return service.create(\n {\n name: t('{name} (copy)', { name: source.name }).slice(0, LIMITS.name),\n description: source.description,\n subject: source.subject,\n preheader: source.preheader,\n document: parseProject(source.projectJson),\n mjml: source.mjml,\n },\n user\n );\n },\n\n /** @param {string|number} id */\n async remove(id) {\n if (!(await repository.remove(String(id)))) throw new HttpError(404, t('Template not found'), 'NOT_FOUND');\n return { id };\n },\n\n /** @param {string|number} id */\n async listVersions(id) {\n await findOrThrow(id);\n const rows = await repository.listVersions(String(id));\n return rows.map((row) => toVersion(row));\n },\n\n /** @param {string|number} id @param {string|number} versionId */\n async getVersion(id, versionId) {\n const row = await repository.findVersion(String(id), String(versionId));\n if (!row) throw new HttpError(404, t('Version not found'), 'NOT_FOUND');\n return toVersion(row, { withProject: true });\n },\n };\n return service;\n}\n\n/**\n * In-memory repository — reference implementation of the contract, used by the\n * tests and the example application. Data is lost on restart.\n *\n * @returns {TemplateRepository}\n */\nexport function createMemoryTemplateRepository() {\n /** @type {Map<string, TemplateRecord>} */\n const rows = new Map();\n /** @type {Map<string, VersionRecord[]>} */\n const versions = new Map();\n let nextId = 1;\n let nextVersionId = 1;\n let clock = 0;\n // Strictly increasing, even within the same millisecond.\n const stamp = () => {\n clock = Math.max(clock + 1, Date.now());\n return new Date(clock);\n };\n const copy = (row) => (row ? { ...row } : null);\n\n return {\n async list() {\n return [...rows.values()]\n .map(copy)\n .sort((a, b) => new Date(b.updatedAt || b.createdAt).getTime() - new Date(a.updatedAt || a.createdAt).getTime());\n },\n async find(id) {\n return copy(rows.get(id));\n },\n async insert(fields) {\n const at = stamp();\n const row = { ...fields, id: nextId++, revision: at.toISOString(), createdAt: at, updatedAt: null, updatedBy: null };\n rows.set(String(row.id), row);\n return copy(row);\n },\n async update(id, fields, { expectedRevision }) {\n const row = rows.get(id);\n if (!row) return null;\n if (expectedRevision && expectedRevision !== row.revision) return { conflict: true, current: copy(row) };\n const at = stamp();\n Object.assign(row, fields, { updatedAt: at, revision: at.toISOString() });\n return copy(row);\n },\n async remove(id) {\n versions.delete(id);\n return rows.delete(id);\n },\n async addVersion(templateId, version, { keep }) {\n const list = versions.get(templateId) || [];\n const versionNumber = list.reduce((max, v) => Math.max(max, v.versionNumber), 0) + 1;\n const entry = { ...version, id: nextVersionId++, versionNumber, createdAt: stamp() };\n versions.set(templateId, [...list, entry].slice(-keep));\n return { id: entry.id, versionNumber };\n },\n async listVersions(templateId) {\n return [...(versions.get(templateId) || [])].reverse().map(copy);\n },\n async findVersion(templateId, versionId) {\n return copy((versions.get(templateId) || []).find((v) => String(v.id) === versionId));\n },\n };\n}\n","// @ts-check\n/**\n * CUSTOM VARIABLES — created by users in the builder, shared by every template.\n *\n * System variables (the `variables` option of the handler) are read-only; a\n * custom variable can never take one of their keys. A key never changes after\n * creation: templates contain it.\n *\n * Storage is a REPOSITORY supplied by the application;\n * `createMemoryVariableRepository` is the reference implementation.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES, mergeVariables, validateCustomVariable } from '../features/variables/catalog.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {import('../features/variables/catalog.js').EmailVariable} EmailVariable\n *\n * @typedef {{\n * list: () => Promise<EmailVariable[]>,\n * insert: (variable: EmailVariable & {createdBy: string|null}) => Promise<void>,\n * update: (key: string, variable: EmailVariable & {updatedBy: string|null}) => Promise<boolean>,\n * remove: (key: string) => Promise<boolean>,\n * }} VariableRepository\n */\n\nconst actorName = (user) => (user ? String(user.name ?? user.id ?? '') || null : null);\n\n/** Public shape (no audit fields). @param {any} row @returns {EmailVariable} */\nconst toVariable = (row) => ({\n key: row.key,\n label: row.label,\n group: row.group,\n sample: row.sample || '',\n fallback: row.fallback || '',\n description: row.description || '',\n custom: true,\n});\n\n/**\n * @param {{\n * repository?: VariableRepository|null,\n * system?: EmailVariable[],\n * t?: import('../i18n/index.js').Translate,\n * }} options\n */\nexport function createVariableService({ repository = null, system = DEFAULT_VARIABLES, t = identity }) {\n const requireRepository = () => {\n if (!repository) throw new HttpError(501, t('Custom variables are not configured'), 'NO_VARIABLE_STORE');\n return repository;\n };\n\n const service = {\n /** Custom variables only. */\n async listCustom() {\n return repository ? (await repository.list()).map(toVariable) : [];\n },\n\n /** System + custom: the catalog used for rendering. */\n async catalog() {\n return mergeVariables(system, await service.listCustom());\n },\n\n /** @param {any} input @param {any} [user] */\n async create(input, user = null) {\n const store = requireRepository();\n const existing = [...system, ...(await service.listCustom())];\n const { variable, errors } = validateCustomVariable(input, { existing, t });\n if (errors) throw Object.assign(new HttpError(400, Object.values(errors)[0], 'INVALID_VARIABLE'), { issues: errors });\n await store.insert({ ...variable, createdBy: actorName(user) });\n return variable;\n },\n\n /** @param {string} key @param {any} input @param {any} [user] */\n async update(key, input, user = null) {\n const store = requireRepository();\n const current = (await service.listCustom()).find((v) => v.key === key);\n if (!current) throw new HttpError(404, t('Variable not found'), 'NOT_FOUND');\n const { variable, errors } = validateCustomVariable({ ...current, ...input, key }, { editing: key, t });\n if (errors) throw Object.assign(new HttpError(400, Object.values(errors)[0], 'INVALID_VARIABLE'), { issues: errors });\n await store.update(key, { ...variable, updatedBy: actorName(user) });\n return variable;\n },\n\n /** @param {string} key */\n async remove(key) {\n if (!(await requireRepository().remove(key))) throw new HttpError(404, t('Variable not found'), 'NOT_FOUND');\n return { key };\n },\n };\n return service;\n}\n\n/** @returns {VariableRepository} */\nexport function createMemoryVariableRepository() {\n /** @type {Map<string, any>} */\n const rows = new Map();\n return {\n async list() {\n return [...rows.values()].map((row) => ({ ...row })).sort((a, b) => a.label.localeCompare(b.label));\n },\n async insert(variable) {\n if (rows.has(variable.key)) throw new Error('duplicate variable');\n rows.set(variable.key, { ...variable, createdAt: new Date() });\n },\n async update(key, variable) {\n if (!rows.has(key)) return false;\n rows.set(key, { ...rows.get(key), ...variable, key, updatedAt: new Date() });\n return true;\n },\n async remove(key) {\n return rows.delete(key);\n },\n };\n}\n","// @ts-check\n/**\n * BRAND — the look every email of the workspace starts from (name, logo,\n * main color, headings color, font), shared by all users like the custom\n * variables. What it does to an email: features/brand/brand.js.\n *\n * Storage is a REPOSITORY supplied by the application (one row is enough);\n * `createMemoryBrandRepository` is the reference implementation.\n */\n\nimport { identity } from '../i18n/index.js';\nimport { cleanBrand } from '../features/brand/brand.js';\nimport { HttpError } from './errors.js';\n\n/**\n * @typedef {import('../features/brand/brand.js').Brand} Brand\n *\n * @typedef {{\n * get: () => Promise<Brand|null>,\n * save: (brand: Brand & {updatedBy: string|null}) => Promise<void>,\n * }} BrandRepository\n */\n\n/**\n * @param {{repository?: BrandRepository|null, t?: import('../i18n/index.js').Translate}} options\n */\nexport function createBrandService({ repository = null, t = identity }) {\n return {\n /** The brand, `{}` when none was set (or no repository). */\n async get() {\n return repository ? cleanBrand(await repository.get()) : {};\n },\n\n /** @param {unknown} input @param {{id?: string|number, name?: string}|null} [user] */\n async save(input, user = null) {\n if (!repository) throw new HttpError(501, t('The brand is not configured'), 'NO_BRAND_STORE');\n if (!input || typeof input !== 'object') throw new HttpError(400, t('Invalid JSON'), 'INVALID_JSON');\n const brand = cleanBrand(input);\n await repository.save({ ...brand, updatedBy: user ? String(user.name ?? user.id ?? '') || null : null });\n return brand;\n },\n };\n}\n\n/** @returns {BrandRepository} */\nexport function createMemoryBrandRepository() {\n /** @type {any} */\n let row = null;\n return {\n async get() {\n return row ? { ...row } : null;\n },\n async save(brand) {\n row = { ...brand, updatedAt: new Date() };\n },\n };\n}\n","// @ts-check\n/**\n * BRAND IMPORT — reads a company's website on the server: its name, logo,\n * main color, headings color and font (what it reads: features/brand/extract.js).\n *\n * The server downloads a page someone typed the address of: that is a door\n * into the server's own network (SSRF) unless every request is held to\n * these rules —\n *\n * - http and https only, ports 80 and 443, no user:password in the address;\n * - every address the name resolves to must be PUBLIC: loopback, private\n * networks, link-local (cloud metadata, 169.254.169.254), carrier NAT,\n * multicast, documentation and reserved ranges are refused, IPv4 hidden\n * in IPv6 (::ffff:10.0.0.1, 64:ff9b::/96, 2002::/16) included;\n * - the check happens in the socket's own DNS lookup, so the address that\n * was checked is the one connected to (no DNS rebinding between the two);\n * - redirects are followed by hand, at most 4, each one checked again;\n * - 8 seconds in all; sizes capped AFTER decompression (a small gzip can\n * unpack to gigabytes): page 1.5 MB, each stylesheet 400 KB, logo 2 MB;\n * - nothing downloaded is executed or served as is: the page and the CSS\n * are only read as text, the logo is checked to be a PNG, JPEG or GIF\n * and stored like any upload.\n *\n * Node only (node:dns, node:http, node:https, node:net, node:zlib).\n */\n\nimport dns from 'node:dns';\nimport http from 'node:http';\nimport https from 'node:https';\nimport net from 'node:net';\nimport zlib from 'node:zlib';\nimport { cleanBrand } from '../features/brand/brand.js';\nimport { brandFromCss, imageSize, logoWidthFor, readPage } from '../features/brand/extract.js';\nimport { MAX_IMAGE_BYTES, detectImage } from '../validation/images.js';\n\n/**\n * @typedef {'BAD_URL'|'PRIVATE_ADDRESS'|'UNREACHABLE'|'TIMEOUT'|'HTTP_ERROR'|'NOT_HTML'|'TOO_MANY_REDIRECTS'|'TOO_LARGE'} BrandImportCode\n * @typedef {'LOGO_NOT_FOUND'|'LOGO_UNSUPPORTED'|'LOGO_ICON'|'COLORS_NOT_FOUND'} BrandNote\n * `LOGO_UNSUPPORTED`: the site's logo is an SVG or a WebP, which most email\n * apps do not show; `LOGO_ICON`: the site's icon was used instead.\n *\n * @typedef {{\n * brand: import('../features/brand/brand.js').Brand,\n * logo: {bytes: Uint8Array, width: number, height: number, type: string, ext: string, shownWidth: number}|null,\n * notes: BrandNote[],\n * }} BrandImport\n */\n\nexport const BRAND_IMPORT_LIMITS = {\n pageBytes: 1.5 * 1024 * 1024,\n cssBytes: 400 * 1024,\n logoBytes: MAX_IMAGE_BYTES,\n stylesheets: 4,\n logos: 5,\n redirects: 4,\n timeoutMs: 8000,\n};\n\nconst USER_AGENT = 'BestEmailBuilder-BrandReader/1.0 (+https://github.com/SergeMiro/best-email-builder)';\n\n/** Why an import failed; the handler turns `code` into a message. */\nexport class BrandImportError extends Error {\n /** @param {BrandImportCode} code @param {string} [detail] */\n constructor(code, detail = '') {\n super(detail ? `${code}: ${detail}` : code);\n this.name = 'BrandImportError';\n this.code = code;\n }\n}\n\n/* ── Which addresses may be reached ─────────────────────────────────────── */\n\nconst blocked = new net.BlockList();\nfor (const [network, prefix] of /** @type {[string, number][]} */ ([\n ['0.0.0.0', 8], // \"this network\"\n ['10.0.0.0', 8], // private\n ['100.64.0.0', 10], // carrier-grade NAT\n ['127.0.0.0', 8], // loopback\n ['169.254.0.0', 16], // link-local, cloud metadata\n ['172.16.0.0', 12], // private\n ['192.0.0.0', 24], // IETF protocol assignments\n ['192.0.2.0', 24], // documentation\n ['192.88.99.0', 24], // 6to4 relay\n ['192.168.0.0', 16], // private\n ['198.18.0.0', 15], // benchmarking\n ['198.51.100.0', 24], // documentation\n ['203.0.113.0', 24], // documentation\n ['224.0.0.0', 4], // multicast\n ['240.0.0.0', 4], // reserved, broadcast\n])) {\n blocked.addSubnet(network, prefix, 'ipv4');\n}\nfor (const [network, prefix] of /** @type {[string, number][]} */ ([\n ['::', 128], // unspecified\n ['::1', 128], // loopback\n ['::', 96], // IPv4-compatible (deprecated)\n ['100::', 64], // discard\n ['2001::', 32], // Teredo\n ['2001:db8::', 32], // documentation\n ['fc00::', 7], // unique local\n ['fe80::', 10], // link-local\n ['fec0::', 10], // site-local (deprecated)\n ['ff00::', 8], // multicast\n])) {\n blocked.addSubnet(network, prefix, 'ipv6');\n}\n\n/**\n * The IPv4 address an IPv6 one carries (mapped, NAT64, 6to4), or ''.\n * @param {string} address\n */\nfunction embeddedIpv4(address) {\n const lower = address.toLowerCase();\n const dotted = lower.match(/^(?:::ffff:(?:0:)?|64:ff9b::)(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (dotted) return dotted[1];\n const groups = expandIpv6(lower);\n if (!groups) return '';\n const toV4 = (/** @type {number} */ hi, /** @type {number} */ lo) => `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;\n // ::ffff:a.b.c.d written in hex\n if (groups.slice(0, 5).every((g) => g === 0) && groups[5] === 0xffff) return toV4(groups[6], groups[7]);\n // NAT64 64:ff9b::/96\n if (groups[0] === 0x64 && groups[1] === 0xff9b && groups.slice(2, 6).every((g) => g === 0)) return toV4(groups[6], groups[7]);\n // 6to4 2002:AABB:CCDD::/48\n if (groups[0] === 0x2002) return toV4(groups[1], groups[2]);\n return '';\n}\n\n/** The eight 16-bit groups of an IPv6 address, or null. @param {string} address */\nfunction expandIpv6(address) {\n if (!net.isIPv6(address)) return null;\n let text = address.replace(/%.*$/, '');\n const v4 = text.match(/(\\d+\\.\\d+\\.\\d+\\.\\d+)$/);\n if (v4) {\n const [a, b, c, d] = v4[1].split('.').map(Number);\n text = text.replace(v4[1], `${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`);\n }\n const [left, right = ''] = text.split('::');\n const head = left ? left.split(':') : [];\n const tail = text.includes('::') && right ? right.split(':') : [];\n const fill = text.includes('::') ? Array(8 - head.length - tail.length).fill('0') : [];\n const groups = [...head, ...fill, ...tail].map((g) => parseInt(g || '0', 16));\n return groups.length === 8 ? groups : null;\n}\n\n/**\n * May the server connect to this IP address? Public unicast only.\n * @param {string} address\n */\nexport function isPublicAddress(address) {\n const ip = String(address ?? '').replace(/^\\[|\\]$/g, '');\n const family = net.isIP(ip);\n if (family === 4) return !blocked.check(ip, 'ipv4');\n if (family === 6) {\n const v4 = embeddedIpv4(ip);\n if (v4) return !blocked.check(v4, 'ipv4');\n return !blocked.check(ip.replace(/%.*$/, ''), 'ipv6');\n }\n return false;\n}\n\n/**\n * A DNS lookup for the socket that refuses names leading anywhere but the\n * public internet: if ANY of the addresses is refused, none is used.\n * @param {(address: string) => boolean} allow\n */\nfunction guardedLookup(allow) {\n /** @type {any} */\n const lookup = (/** @type {string} */ hostname, /** @type {any} */ options, /** @type {any} */ callback) => {\n if (typeof options === 'function') {\n callback = options;\n options = {};\n }\n dns.lookup(hostname, { ...options, all: true }, (error, addresses) => {\n if (error) return callback(error);\n const list = /** @type {dns.LookupAddress[]} */ (/** @type {unknown} */ (addresses));\n if (!list.length || list.some((entry) => !allow(entry.address))) {\n return callback(new BrandImportError('PRIVATE_ADDRESS', hostname));\n }\n if (options.all) return callback(null, list);\n return callback(null, list[0].address, list[0].family);\n });\n };\n return lookup;\n}\n\n/* ── Downloading ────────────────────────────────────────────────────────── */\n\n/** The web's own ports. @param {number} port */\nconst isWebPort = (port) => port === 80 || port === 443;\n\n/**\n * An address the importer may request, or a BrandImportError.\n * @param {string|URL} input\n * @param {(address: string) => boolean} allow\n * @param {(port: number) => boolean} [allowPort]\n */\nfunction checkUrl(input, allow, allowPort = isWebPort) {\n let url;\n try {\n url = new URL(String(input));\n } catch {\n throw new BrandImportError('BAD_URL');\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new BrandImportError('BAD_URL', url.protocol);\n if (url.username || url.password) throw new BrandImportError('BAD_URL', 'credentials');\n if (url.port && !allowPort(Number(url.port))) throw new BrandImportError('BAD_URL', `port ${url.port}`);\n const host = url.hostname.replace(/^\\[|\\]$/g, '');\n if (!host) throw new BrandImportError('BAD_URL');\n // An IP written in the address skips the DNS lookup: checked here.\n if (net.isIP(host) && !allow(host)) throw new BrandImportError('PRIVATE_ADDRESS', host);\n return url;\n}\n\n/**\n * What the user typed, as the address of a home page: \"acme.com\" →\n * \"https://acme.com/\".\n * @param {unknown} input\n */\nexport function siteUrl(input) {\n const text = String(input ?? '').trim();\n if (!text || text.length > 500 || /\\s/.test(text)) throw new BrandImportError('BAD_URL');\n const withScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(text) ? text : `https://${text}`;\n let url;\n try {\n url = new URL(withScheme);\n } catch {\n throw new BrandImportError('BAD_URL');\n }\n if (!/^https?:$/.test(url.protocol) || !url.hostname.includes('.') && !net.isIP(url.hostname.replace(/^\\[|\\]$/g, ''))) {\n throw new BrandImportError('BAD_URL');\n }\n return url.href;\n}\n\n/**\n * GET one address and its redirects, under the rules above.\n * @param {string} address\n * @param {{\n * allow: (address: string) => boolean,\n * allowPort?: (port: number) => boolean,\n * signal: AbortSignal,\n * maxBytes: number,\n * truncate?: boolean,\n * accept?: string,\n * redirects?: number,\n * }} options\n * `truncate`: past `maxBytes`, keep what came (a page's head, the start of\n * a stylesheet) instead of failing.\n * @returns {Promise<{bytes: Buffer, url: string, contentType: string}>}\n */\nexport async function safeGet(\n address,\n { allow, allowPort = isWebPort, signal, maxBytes, truncate = false, accept = '*/*', redirects = BRAND_IMPORT_LIMITS.redirects }\n) {\n let url = checkUrl(address, allow, allowPort);\n for (let hop = 0; ; hop += 1) {\n const response = await requestOnce(url, { allow, signal, maxBytes, truncate, accept });\n if (response.location === null) return { bytes: response.bytes, url: url.href, contentType: response.contentType };\n if (hop >= redirects) throw new BrandImportError('TOO_MANY_REDIRECTS');\n url = checkUrl(new URL(response.location, url), allow, allowPort);\n }\n}\n\n/**\n * @param {URL} url\n * @param {{allow: (address: string) => boolean, signal: AbortSignal, maxBytes: number, truncate: boolean, accept: string}} options\n * @returns {Promise<{location: string|null, bytes: Buffer, contentType: string}>}\n */\nfunction requestOnce(url, { allow, signal, maxBytes, truncate, accept }) {\n return new Promise((resolve, reject) => {\n if (signal.aborted) return reject(abortReason(signal));\n const client = url.protocol === 'https:' ? https : http;\n const request = client.request(url, {\n method: 'GET',\n agent: false,\n lookup: guardedLookup(allow),\n signal,\n headers: {\n 'User-Agent': USER_AGENT,\n Accept: accept,\n 'Accept-Encoding': 'gzip, deflate, br',\n 'Accept-Language': 'en;q=0.9, *;q=0.5',\n },\n });\n request.on('error', (error) => reject(toImportError(error, signal)));\n request.on('response', (response) => {\n const status = response.statusCode || 0;\n if (status >= 300 && status < 400 && response.headers.location) {\n response.resume();\n request.destroy();\n return resolve({ location: response.headers.location, bytes: Buffer.alloc(0), contentType: '' });\n }\n if (status < 200 || status >= 300) {\n response.resume();\n request.destroy();\n return reject(new BrandImportError('HTTP_ERROR', String(status)));\n }\n const encoding = String(response.headers['content-encoding'] || '').toLowerCase().trim();\n /** @type {NodeJS.ReadableStream} */\n let body = response;\n if (encoding === 'gzip' || encoding === 'x-gzip') body = response.pipe(zlib.createGunzip());\n else if (encoding === 'deflate') body = response.pipe(zlib.createInflate());\n else if (encoding === 'br') body = response.pipe(zlib.createBrotliDecompress());\n /** @type {Buffer[]} */\n const chunks = [];\n let size = 0;\n let done = false;\n const finish = (/** @type {Error|null} */ error) => {\n if (done) return;\n done = true;\n request.destroy();\n if (error) reject(error);\n else resolve({ location: null, bytes: Buffer.concat(chunks), contentType: String(response.headers['content-type'] || '') });\n };\n body.on('data', (/** @type {Buffer} */ chunk) => {\n if (done) return;\n if (size + chunk.length > maxBytes) {\n if (!truncate) return finish(new BrandImportError('TOO_LARGE'));\n chunks.push(chunk.subarray(0, maxBytes - size));\n size = maxBytes;\n return finish(null);\n }\n chunks.push(chunk);\n size += chunk.length;\n });\n body.on('end', () => finish(null));\n body.on('error', (error) => finish(toImportError(error, signal)));\n response.on('error', (error) => finish(toImportError(error, signal)));\n response.on('close', () => {\n if (!response.complete) finish(toImportError(new Error('connection closed'), signal));\n });\n });\n request.end();\n });\n}\n\n/** @param {AbortSignal} signal */\nconst abortReason = (signal) =>\n signal.reason?.name === 'TimeoutError' ? new BrandImportError('TIMEOUT') : Object.assign(new Error('Aborted'), { name: 'AbortError' });\n\n/** @param {any} error @param {AbortSignal} signal */\nfunction toImportError(error, signal) {\n if (error instanceof BrandImportError) return error;\n if (error?.cause instanceof BrandImportError) return error.cause;\n if (signal.aborted) return abortReason(signal);\n return new BrandImportError('UNREACHABLE', error?.code || error?.message || '');\n}\n\n/** The text of a page or stylesheet, in its declared charset. @param {Buffer} bytes @param {string} contentType */\nfunction textOf(bytes, contentType) {\n const sniffed = bytes.subarray(0, 2048).toString('latin1').match(/<meta[^>]+charset\\s*=\\s*[\"']?([\\w-]+)/i)?.[1];\n const charset = contentType.match(/charset\\s*=\\s*[\"']?([\\w-]+)/i)?.[1] || sniffed || 'utf-8';\n try {\n return new TextDecoder(charset).decode(bytes);\n } catch {\n return new TextDecoder('utf-8').decode(bytes);\n }\n}\n\n/* ── The importer ───────────────────────────────────────────────────────── */\n\n/**\n * @param {{\n * timeoutMs?: number,\n * allowAddress?: (address: string) => boolean,\n * allowPort?: (port: number) => boolean,\n * }} [options]\n * `allowAddress`, `allowPort`: which IP addresses and ports may be reached\n * (default: public addresses, ports 80 and 443). Tests reading a local\n * server loosen them; nothing else should.\n */\nexport function createBrandImporter({\n timeoutMs = BRAND_IMPORT_LIMITS.timeoutMs,\n allowAddress = isPublicAddress,\n allowPort = isWebPort,\n} = {}) {\n /**\n * @param {unknown} input the site's address, as typed (\"acme.com\" is fine)\n * @param {{signal?: AbortSignal}} [options]\n * @returns {Promise<BrandImport>}\n */\n async function importBrand(input, { signal } = {}) {\n const deadline = AbortSignal.timeout(timeoutMs);\n const all = signal ? AbortSignal.any([signal, deadline]) : deadline;\n const get = (/** @type {string} */ address, /** @type {{maxBytes: number, truncate?: boolean, accept?: string}} */ options) =>\n safeGet(address, { allow: allowAddress, allowPort, signal: all, ...options });\n\n const page = await get(siteUrl(input), {\n maxBytes: BRAND_IMPORT_LIMITS.pageBytes,\n truncate: true,\n accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.5',\n });\n if (page.contentType && !/html|xml/i.test(page.contentType)) throw new BrandImportError('NOT_HTML', page.contentType);\n const reading = readPage(textOf(page.bytes, page.contentType), page.url);\n\n /** @type {BrandNote[]} */\n const notes = [];\n const [sheets, logo] = await Promise.all([\n Promise.all(\n reading.stylesheets.slice(0, BRAND_IMPORT_LIMITS.stylesheets).map((href) =>\n get(href, { maxBytes: BRAND_IMPORT_LIMITS.cssBytes, truncate: true, accept: 'text/css,*/*;q=0.1' })\n .then((sheet) => textOf(sheet.bytes, sheet.contentType))\n .catch((error) => {\n if (error?.name === 'AbortError') throw error;\n return '';\n })\n )\n ),\n (async () => {\n let unsupported = false;\n for (const candidate of reading.logos.slice(0, BRAND_IMPORT_LIMITS.logos)) {\n if (/\\.(svg|webp|ico)(\\?|#|$)/i.test(new URL(candidate.url).pathname)) {\n unsupported ||= candidate.from !== 'icon';\n continue;\n }\n let file;\n try {\n file = await get(candidate.url, { maxBytes: BRAND_IMPORT_LIMITS.logoBytes, accept: 'image/png,image/jpeg,image/gif;q=0.9,*/*;q=0.1' });\n } catch (error) {\n if (/** @type {any} */ (error)?.name === 'AbortError') throw error;\n continue;\n }\n const bytes = new Uint8Array(file.bytes);\n const type = detectImage(bytes);\n const size = type && imageSize(bytes);\n if (!type || !size) {\n unsupported ||= candidate.from !== 'icon' && /svg|webp/i.test(file.contentType);\n continue;\n }\n if (size.width < 32 && size.height < 32) continue;\n if (candidate.from === 'icon') notes.push('LOGO_ICON');\n return { bytes, ...size, ...type };\n }\n notes.push(unsupported ? 'LOGO_UNSUPPORTED' : 'LOGO_NOT_FOUND');\n return null;\n })(),\n ]);\n\n const style = brandFromCss({ css: [reading.css, ...sheets].join('\\n'), themeColor: reading.themeColor });\n if (!style.primary) notes.push('COLORS_NOT_FOUND');\n const brand = cleanBrand({ name: reading.name, website: page.url, ...style });\n return { brand, logo: logo && { ...logo, shownWidth: logoWidthFor(logo) }, notes };\n }\n\n return { import: importBrand };\n}\n","// @ts-check\n/**\n * HTTP HANDLER — every route the builder needs, as one function\n * `(request: Request) => Promise<Response>` (Fetch API).\n *\n * Works as is in Next.js route handlers, Hono, Bun, Deno, Cloudflare Workers;\n * with Express / Node's `http` through `toNodeListener` (see `node.js`).\n *\n * GET {base}/templates read list\n * POST {base}/templates write create, or { duplicateOf }\n * GET {base}/templates/:id read template\n * PUT {base}/templates/:id write save\n * DELETE {base}/templates/:id write delete\n * GET {base}/templates/:id/versions read versions\n * GET {base}/templates/:id/versions/:vid read version with its project\n * POST {base}/render read preview (server rendering)\n * POST {base}/test write test send (needs `transport`)\n * POST {base}/assets write image upload (multipart, field `file`)\n * POST {base}/generate write write an email from a purpose (needs `generator`,\n * the AI module: best-email-builder/ai/server)\n * POST {base}/rewrite write rewrite one text of an email (needs `generator`)\n * POST {base}/subjects write three other subject lines (needs `generator`)\n * GET {base}/variables read custom variables\n * POST {base}/variables write create a custom variable\n * PUT {base}/variables/:key write edit (label, group, sample, fallback)\n * DELETE {base}/variables/:key write delete\n * GET {base}/brand read the workspace's brand\n * PUT {base}/brand write save it (needs `brandStore`)\n * POST {base}/brand/import write read a brand from a website (needs `brandImporter`)\n * GET {assetsPath}:key PUBLIC uploaded image\n *\n * Images are PUBLIC on purpose: mail clients download them without any\n * session. Their keys are content hashes (unguessable), and they are served\n * with headers that forbid any interpretation other than an image.\n *\n * `authorize(request, { action })` is REQUIRED: it returns the user (any\n * object with `id`/`name`, recorded as author) or null to refuse (401).\n * Throw an `HttpError(403, …)` to refuse with another status. The AI routes\n * hand that user to the generator, down to its model: an agent of your own\n * (agentEmailModel) forwards who asks.\n */\n\nimport { messagesOf, translatorFor } from '../i18n/index.js';\nimport { DEFAULT_VARIABLES, defineVariables } from '../features/variables/catalog.js';\nimport { applyVariablesToText, sampleValues } from '../features/variables/service.js';\nimport { MAX_IMAGE_BYTES } from '../validation/images.js';\nimport { createAssetService } from './assets.js';\nimport { HttpError, statusOf } from './errors.js';\nimport { compileTemplate, personalize } from './render.js';\nimport { MAX_TEST_RECIPIENTS, sendTestEmail } from './sendTest.js';\nimport { createTemplateService } from './templates.js';\nimport { createVariableService } from './variables.js';\nimport { createBrandService } from './brand.js';\nimport { BrandImportError } from './brandImport.js';\nimport { BRAND_IMPORT_ERRORS } from '../features/brand/messages.js';\n\n/**\n * @typedef {'read'|'write'} Action\n * @typedef {{id?: string|number, name?: string}} User\n *\n * @typedef {{\n * templates: import('./templates.js').TemplateRepository,\n * assets: import('./assets.js').AssetRepository,\n * authorize: (request: Request, context: {action: Action}) => Promise<User|null|false> | User | null | false,\n * basePath?: string,\n * assetsPath?: string,\n * publicUrl?: string|null,\n * transport?: import('./sendTest.js').EmailTransport|null,\n * generator?: {\n * generate?: (input: import('../ai/generator.js').GenerateInput, options?: {signal?: AbortSignal, user?: User}) => Promise<import('../ai/generator.js').GeneratedEmail>,\n * rewrite?: (input: import('../ai/rewrite.js').RewriteInput, options?: {signal?: AbortSignal, user?: User}) => Promise<{text: string}>,\n * suggestSubjects?: (input: import('../ai/subjects.js').SubjectsInput, options?: {signal?: AbortSignal, user?: User}) => Promise<{subjects: string[]}>,\n * messages?: Record<string, Record<string, string>>,\n * }|null,\n * variables?: import('../features/variables/catalog.js').EmailVariable[],\n * variableStore?: import('./variables.js').VariableRepository|null,\n * brandStore?: import('./brand.js').BrandRepository|null,\n * brandImporter?: {import: (url: string, options?: {signal?: AbortSignal}) => Promise<import('./brandImport.js').BrandImport>}|null,\n * locale?: string,\n * messages?: Record<string, string>,\n * lang?: string,\n * previewUnsubscribeUrl?: string,\n * maxVersions?: number,\n * maxTestRecipients?: number,\n * rateLimits?: {testPerHour?: number, uploadsPerHour?: number, generatePerHour?: number, rewritePerHour?: number, subjectsPerHour?: number, brandImportsPerHour?: number},\n * onError?: (error: unknown, request: Request) => void,\n * }} HandlerOptions\n */\n\n/** Largest JSON body accepted (project 2 MB + MJML 512 KB + metadata). */\nconst MAX_JSON_BYTES = 3 * 1024 * 1024;\n\nconst HOUR = 60 * 60 * 1000;\n\n/**\n * An error of the AI module, recognised without importing it (a server\n * without AI does not load it).\n * @param {any} error\n * @returns {error is {name: 'EmailGenerationError', status: number, message: string, code: string}}\n */\nconst isGenerationError = (error) =>\n error?.name === 'EmailGenerationError' && Number.isInteger(error.status) && typeof error.code === 'string';\n\nconst json = (data, status = 200) =>\n new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' },\n });\n\n/** Sliding one-hour window per key, in memory (one process). */\nfunction createLimiter(perHour) {\n /** @type {Map<string, number[]>} */\n const hits = new Map();\n return (key) => {\n if (!perHour) return true;\n const now = Date.now();\n const recent = (hits.get(key) || []).filter((at) => now - at < HOUR);\n if (recent.length >= perHour) {\n hits.set(key, recent);\n return false;\n }\n recent.push(now);\n hits.set(key, recent);\n return true;\n };\n}\n\n/**\n * The returned function also exposes `handler.catalog()`: system + custom\n * variables, for the application's own sending pipeline (`personalize`).\n *\n * @param {HandlerOptions} options\n * @returns {((request: Request) => Promise<Response>) & {catalog: () => Promise<import('../features/variables/catalog.js').EmailVariable[]>}}\n */\nexport function createEmailBuilderHandler(options) {\n const {\n templates: templateRepository,\n assets: assetRepository,\n authorize,\n basePath = '/api/email-builder',\n publicUrl = null,\n transport = null,\n generator = null,\n brandImporter = null,\n locale = 'en',\n messages,\n lang = locale.slice(0, 2),\n previewUnsubscribeUrl = '#',\n maxVersions,\n maxTestRecipients = MAX_TEST_RECIPIENTS,\n rateLimits = {},\n onError = (error) => console.error('[best-email-builder]', error),\n } = options || /** @type {any} */ ({});\n if (typeof authorize !== 'function') throw new Error('createEmailBuilderHandler: `authorize` is required');\n if (!templateRepository || !assetRepository) {\n throw new Error('createEmailBuilderHandler: `templates` and `assets` repositories are required');\n }\n\n const base = basePath.replace(/\\/+$/, '');\n const assetsPath = (options.assetsPath || `${base}/assets/`).replace(/\\/*$/, '/');\n const variables = defineVariables(options.variables || DEFAULT_VARIABLES);\n // The AI module's texts (its errors) under the host's own.\n const t = translatorFor(locale, { ...messagesOf(generator?.messages, locale), ...messages });\n const templates = createTemplateService({ repository: templateRepository, t, lang, maxVersions });\n const assets = createAssetService({ repository: assetRepository, publicPath: assetsPath, t });\n const customVariables = createVariableService({ repository: options.variableStore || null, system: variables, t });\n const brand = createBrandService({ repository: options.brandStore || null, t });\n const testLimiter = createLimiter(rateLimits.testPerHour ?? 20);\n const uploadLimiter = createLimiter(rateLimits.uploadsPerHour ?? 100);\n const generateLimiter = createLimiter(rateLimits.generatePerHour ?? 30);\n const rewriteLimiter = createLimiter(rateLimits.rewritePerHour ?? 200);\n const subjectsLimiter = createLimiter(rateLimits.subjectsPerHour ?? 100);\n const brandImportLimiter = createLimiter(rateLimits.brandImportsPerHour ?? 20);\n\n /** @param {Request} request @param {Action} action */\n async function userFor(request, action) {\n const user = await authorize(request, { action });\n if (!user) throw new HttpError(401, t('Authentication required'), 'UNAUTHORIZED');\n return /** @type {User} */ (user);\n }\n\n const userKey = (/** @type {User} */ user) => String(user.id ?? user.name ?? 'anonymous');\n\n /** @param {Request} request */\n /** The browser's language when it is one (`fr`, `pt-BR`), else the server's. @param {unknown} value */\n const languageOr = (value) => (typeof value === 'string' && /^[a-z]{2,3}(-[a-z0-9]{2,8})*$/i.test(value) ? value : lang);\n\n async function readJson(request) {\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_JSON_BYTES) throw new HttpError(413, t('Request too large'), 'TOO_LARGE');\n const body = await request.text();\n if (body.length > MAX_JSON_BYTES) throw new HttpError(413, t('Request too large'), 'TOO_LARGE');\n try {\n return body ? JSON.parse(body) : {};\n } catch {\n throw new HttpError(400, t('Invalid JSON'), 'INVALID_JSON');\n }\n }\n\n /**\n * A model call answered as JSON: its errors as the interface shows them,\n * a cancelled request (the user closed the dialog) as 499.\n * @param {Request} request @param {() => Promise<unknown>} call\n */\n async function askModel(request, call) {\n try {\n return json({ data: await call() });\n } catch (error) {\n if (request.signal?.aborted) return json({ error: t('Cancelled'), code: 'CANCELLED' }, 499);\n // The generator's own errors (EmailGenerationError, best-email-builder/ai/server) say what to show.\n if (isGenerationError(error)) throw new HttpError(error.status, t(error.message), error.code);\n onError(error, request);\n throw new HttpError(502, t('The AI service did not answer — try again in a moment.'), 'MODEL_ERROR');\n }\n }\n\n /** @param {string} key */\n async function serveAsset(key) {\n const asset = await assets.get(key);\n if (!asset) return new Response('Not found', { status: 404, headers: { 'Content-Type': 'text/plain' } });\n return new Response(/** @type {any} */ (asset.data), {\n status: 200,\n headers: {\n 'Content-Type': asset.contentType,\n 'Content-Length': String(asset.data.length),\n 'Cache-Control': 'public, max-age=31536000, immutable',\n ETag: `\"${asset.sha256 || key}\"`,\n 'X-Content-Type-Options': 'nosniff',\n 'Content-Security-Policy': \"default-src 'none'; sandbox\",\n },\n });\n }\n\n /** @param {Request} request */\n async function route(request) {\n const url = new URL(request.url);\n const path = url.pathname;\n const method = request.method.toUpperCase();\n\n if (path.startsWith(assetsPath) && (method === 'GET' || method === 'HEAD')) {\n return serveAsset(decodeURIComponent(path.slice(assetsPath.length)));\n }\n if (path !== base && !path.startsWith(`${base}/`)) return null;\n const parts = path.slice(base.length).split('/').filter(Boolean).map(decodeURIComponent);\n const [section, id, sub, subId] = parts;\n\n if (section === 'templates') {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await templates.list() });\n }\n if (!id && method === 'POST') {\n const user = await userFor(request, 'write');\n const body = await readJson(request);\n const result =\n body?.duplicateOf !== undefined && body?.duplicateOf !== null\n ? await templates.duplicate(body.duplicateOf, user)\n : await templates.create(body, user);\n return json({ data: result }, 201);\n }\n if (id && !sub && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await templates.get(id) });\n }\n if (id && !sub && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await templates.save(id, await readJson(request), user) });\n }\n if (id && !sub && method === 'DELETE') {\n await userFor(request, 'write');\n return json({ data: await templates.remove(id) });\n }\n if (id && sub === 'versions' && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: subId ? await templates.getVersion(id, subId) : await templates.listVersions(id) });\n }\n }\n\n if (section === 'render' && !id && method === 'POST') {\n await userFor(request, 'read');\n const body = await readJson(request);\n const catalog = await customVariables.catalog();\n const { compiled, warnings, quality } = await compileTemplate(\n { mjml: body?.mjml, subject: body?.subject, preheader: body?.preheader },\n { t, lang, variables: catalog, preview: true }\n );\n // Same origin as the application: uploaded images stay relative.\n const values = sampleValues(catalog);\n const html = personalize(compiled, { values, unsubscribeUrl: previewUnsubscribeUrl, variables: catalog });\n return json({\n data: {\n html,\n subject: applyVariablesToText(body?.subject || '', values, catalog),\n preheader: applyVariablesToText(body?.preheader || '', values, catalog),\n warnings,\n quality,\n },\n });\n }\n\n if (section === 'test' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!transport) throw new HttpError(501, t('Test sending is not configured'), 'NO_TRANSPORT');\n if (!testLimiter(userKey(user))) throw new HttpError(429, t('Too many test sends — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n const result = await sendTestEmail({\n mjml: body?.mjml,\n subject: body?.subject,\n preheader: body?.preheader,\n emails: body?.emails,\n transport,\n baseUrl: publicUrl,\n unsubscribeUrl: previewUnsubscribeUrl,\n variables: await customVariables.catalog(),\n maxRecipients: maxTestRecipients,\n t,\n lang,\n });\n return json({ data: result });\n }\n\n if (section === 'generate' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const generate = generator?.generate;\n if (typeof generate !== 'function') throw new HttpError(501, t('Email generation is not configured'), 'NO_GENERATOR');\n if (!generateLimiter(userKey(user))) {\n throw new HttpError(429, t('Too many generated emails — try again later'), 'RATE_LIMITED');\n }\n const body = await readJson(request);\n // The variables are the server's catalog, whatever the browser sent.\n const catalog = await customVariables.catalog();\n return askModel(request, () =>\n generate.call(generator, { ...body, language: languageOr(body?.language), variables: catalog }, { signal: request.signal, user })\n );\n }\n\n if (section === 'rewrite' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const rewrite = generator?.rewrite;\n if (typeof rewrite !== 'function') throw new HttpError(501, t('Text rewriting is not configured'), 'NO_GENERATOR');\n if (!rewriteLimiter(userKey(user))) throw new HttpError(429, t('Too many rewrites — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n return askModel(request, () => rewrite(body, { signal: request.signal, user }));\n }\n\n if (section === 'subjects' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n const suggestSubjects = generator?.suggestSubjects;\n if (typeof suggestSubjects !== 'function') throw new HttpError(501, t('Subject ideas are not configured'), 'NO_GENERATOR');\n if (!subjectsLimiter(userKey(user))) throw new HttpError(429, t('Too many subject ideas — try again later'), 'RATE_LIMITED');\n const body = await readJson(request);\n // The merge tags are the server's catalog, whatever the browser sent.\n const catalog = await customVariables.catalog();\n return askModel(request, () =>\n suggestSubjects({ ...body, language: languageOr(body?.language), variables: catalog }, { signal: request.signal, user })\n );\n }\n\n if (section === 'assets' && !id && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!uploadLimiter(userKey(user))) throw new HttpError(429, t('Too many uploads — try again later'), 'RATE_LIMITED');\n const length = Number(request.headers.get('content-length') || 0);\n if (length > MAX_IMAGE_BYTES + 64 * 1024) {\n throw new HttpError(413, t('Image too large: {max} MB maximum', { max: MAX_IMAGE_BYTES / (1024 * 1024) }), 'TOO_LARGE');\n }\n let form;\n try {\n form = await request.formData();\n } catch {\n throw new HttpError(400, t('Multipart form expected'), 'INVALID_FORM');\n }\n const file = form.get('file');\n if (!file || typeof file === 'string') throw new HttpError(400, t('File missing'), 'FILE_MISSING');\n const bytes = new Uint8Array(await file.arrayBuffer());\n const result = await assets.upload({ bytes, name: file.name, user });\n return json({ data: result }, result.created ? 201 : 200);\n }\n\n if (section === 'variables') {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await customVariables.listCustom() });\n }\n if (!id && method === 'POST') {\n const user = await userFor(request, 'write');\n return json({ data: await customVariables.create(await readJson(request), user) }, 201);\n }\n if (id && !sub && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await customVariables.update(id, await readJson(request), user) });\n }\n if (id && !sub && method === 'DELETE') {\n await userFor(request, 'write');\n return json({ data: await customVariables.remove(id) });\n }\n }\n\n if (section === 'brand' && !sub) {\n if (!id && method === 'GET') {\n await userFor(request, 'read');\n return json({ data: await brand.get() });\n }\n if (!id && method === 'PUT') {\n const user = await userFor(request, 'write');\n return json({ data: await brand.save(await readJson(request), user) });\n }\n if (id === 'import' && method === 'POST') {\n const user = await userFor(request, 'write');\n if (!brandImporter) throw new HttpError(501, t('Reading a brand from a website is not configured'), 'NO_BRAND_IMPORTER');\n if (!brandImportLimiter(userKey(user))) {\n throw new HttpError(429, t('Too many websites read — try again later'), 'RATE_LIMITED');\n }\n const body = await readJson(request);\n return json({ data: await importBrand(request, body?.url, user) });\n }\n }\n\n throw new HttpError(404, t('Route not found'), 'ROUTE_NOT_FOUND');\n }\n\n /**\n * A website's brand, its logo stored like an upload. Not saved: the user\n * looks at it first.\n * @param {Request} request @param {unknown} address @param {User} user\n */\n async function importBrand(request, address, user) {\n let result;\n try {\n result = await /** @type {NonNullable<typeof brandImporter>} */ (brandImporter).import(String(address ?? ''), { signal: request.signal });\n } catch (error) {\n if (request.signal?.aborted) throw new HttpError(499, t('Cancelled'), 'CANCELLED');\n if (!(error instanceof BrandImportError)) throw error;\n const [status, message] = BRAND_IMPORT_ERRORS[error.code] || BRAND_IMPORT_ERRORS.UNREACHABLE;\n throw new HttpError(status, t(message), `BRAND_${error.code}`);\n }\n const found = { ...result.brand };\n if (result.logo) {\n const { path } = await assets.upload({ bytes: result.logo.bytes, name: `logo.${result.logo.ext}`, user });\n found.logoUrl = path;\n found.logoWidth = result.logo.shownWidth;\n }\n return { brand: found, notes: result.notes };\n }\n\n async function handle(request) {\n try {\n const response = await route(request);\n return response || json({ error: t('Route not found'), code: 'ROUTE_NOT_FOUND' }, 404);\n } catch (error) {\n const status = statusOf(error);\n // A 5xx we raised ourselves (not configured, AI service down) says why.\n if (status >= 500 && !(error instanceof HttpError)) {\n onError(error, request);\n return json({ error: t('Internal error'), code: 'INTERNAL' }, 500);\n }\n const err = /** @type {any} */ (error);\n return json({ error: err.message, code: err.code || undefined, issues: err.issues || undefined }, status);\n }\n }\n\n handle.catalog = () => customVariables.catalog();\n return handle;\n}\n","// @ts-check\n/**\n * Bridge between a Fetch-API handler and Node's `http` / Express / Connect.\n *\n * app.use(toNodeListener(handler)); // Express\n * http.createServer(toNodeListener(handler)) // plain Node\n *\n * Requests the handler does not own (404 with code ROUTE_NOT_FOUND, outside its\n * routes) are passed to `next()` when there is one.\n */\n\nimport { Readable } from 'node:stream';\n\n/**\n * @param {(request: Request) => Promise<Response>} handler\n * @param {{origin?: string}} [options]\n */\nexport function toNodeListener(handler, { origin } = {}) {\n /**\n * @param {import('node:http').IncomingMessage} req\n * @param {import('node:http').ServerResponse} res\n * @param {(error?: unknown) => void} [next]\n */\n return async function listener(req, res, next) {\n try {\n const host = origin || `http://${req.headers.host || 'localhost'}`;\n const url = new URL(req.url || '/', host);\n const method = (req.method || 'GET').toUpperCase();\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) value.forEach((v) => headers.append(key, v));\n else if (value !== undefined) headers.set(key, value);\n }\n const hasBody = method !== 'GET' && method !== 'HEAD';\n const request = new Request(url, {\n method,\n headers,\n body: hasBody ? /** @type {any} */ (Readable.toWeb(req)) : undefined,\n // @ts-ignore — required by Node for streamed bodies\n duplex: hasBody ? 'half' : undefined,\n });\n const response = await handler(request);\n if (response.status === 404 && next) {\n const body = await response.clone().json().catch(() => null);\n if (body?.code === 'ROUTE_NOT_FOUND') return next();\n }\n res.statusCode = response.status;\n response.headers.forEach((value, key) => res.setHeader(key, value));\n if (!response.body || method === 'HEAD') {\n res.end();\n return;\n }\n res.end(Buffer.from(await response.arrayBuffer()));\n } catch (error) {\n if (next) next(error);\n else {\n res.statusCode = 500;\n res.end();\n }\n }\n };\n}\n"],"mappings":"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","names":["http","json","json","actorName","text"]}
|