@aglyn/plugins-email 1.0.0-beta.200 → 1.0.0-beta.201
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-email",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.201",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.201",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.201",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.201",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.201",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.201",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.201",
|
|
34
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.201",
|
|
35
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.201",
|
|
36
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.201",
|
|
37
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.201",
|
|
38
|
+
"@aglyn/shared-util-email": "1.0.0-beta.201",
|
|
39
|
+
"@aglyn/shared-util-http": "1.0.0-beta.201",
|
|
40
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.201",
|
|
41
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.201",
|
|
42
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.201",
|
|
43
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.201",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -16,7 +16,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/ import { createResourceUid } from "@aglyn/aglyn/app-utils/create-resource-uid";
|
|
18
18
|
import { uniqueDuplicateName } from "@aglyn/aglyn/app-utils/duplicate-resource";
|
|
19
|
-
import {
|
|
19
|
+
import { artifactCreateListKeys } from "@aglyn/aglyn/app-utils/artifact-list-keys";
|
|
20
20
|
import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
|
|
21
21
|
import { NON_PAGE_SCREEN_MAX_PER_HOST, nonPageScreenIds, SCREEN_KIND_EMAIL } from "@aglyn/aglyn/app-utils/screen-route";
|
|
22
22
|
import { encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
@@ -363,8 +363,11 @@ export function createEmailDesignDraftWriter(deps = {}) {
|
|
|
363
363
|
createdBy: request.uid
|
|
364
364
|
};
|
|
365
365
|
const copy = copyFields(read.value);
|
|
366
|
-
// The keys the email templates list finds it by
|
|
367
|
-
|
|
366
|
+
// The keys the email templates list finds it by, and the stored
|
|
367
|
+
// `deletedAt: null` a campaign's screens list asks for (AGL-3321).
|
|
368
|
+
tx.create(screenRef, _extends({}, screen, artifactCreateListKeys('screens', {
|
|
369
|
+
displayName: name
|
|
370
|
+
}), copy, stamps));
|
|
368
371
|
tx.create(screenRef.collection('versions').doc(versionId), _extends({}, version, {
|
|
369
372
|
nodes: Buffer.from(packed),
|
|
370
373
|
hostId: request.hostId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/email/src/lib/server-email-drafts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { displayNameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport {\n NON_PAGE_SCREEN_MAX_PER_HOST,\n nonPageScreenIds,\n SCREEN_KIND_EMAIL,\n type BillableScreenSource,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n registerPluginResourceDraftWriter,\n type PluginDraftCheck,\n type PluginDraftCheckContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n composeReusableComponentNodes,\n REUSABLE_INSTANCE_COMPONENT_ID,\n} from '@aglyn/aglyn/app-utils/compose-reusable-components'\nimport { hasSafeLinkScheme } from '@aglyn/shared-util-http/safe-url-scheme'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n emailPlainTextState,\n renderRecipientEmail,\n} from '@aglyn/aglyn/app-utils/recipient-email-render'\nimport { emailDesignDocuments } from './model/email-design-document'\n\n/**\n * AN EMAIL DESIGN ANOTHER PLUGIN ASKS FOR (AGL-2912).\n *\n * The draft writer this plugin registers for the `emailDesign` resource on the\n * core's resource-drafts seam. A plugin that produces an email — a generator\n * working from a brief, an importer bringing templates over — asks for the\n * writer by name and gets this plugin's rules, never a copy of them:\n *\n * - THE DOCUMENTS are the pair `createEmailScreen` makes, built by the same\n * function (`emailDesignDocuments`): a `kind: 'email'` screen and its first\n * version. So the design lists on the Templates tab, opens in the besigner\n * and is offered by the campaign composer like one a person made. The\n * screen carries the subject and preheader a campaign built from it opens\n * on, and the other lines worth trying beside them.\n * - THE ROOM is the flat platform ceiling on non-page screens, met inside the\n * transaction with the arithmetic `/api/hosts/resources` and the duplicate\n * module use, and refused in their words.\n * - THE ROLE is the resources route's: a member who may write the site's\n * content.\n * - THE CHECK renders the design through `renderRecipientEmail`, the function\n * the send path and the composer's preview both call, so content that\n * passes is content that would render in both.\n *\n * Nothing is sent, scheduled or routed. A design is inert until a campaign\n * names it, and the writer touches the two new documents and nothing else.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource name this writer is registered under. */\nexport const EMAIL_DESIGN_DRAFT_RESOURCE = 'emailDesign'\n\n/** The id `plugins.config.json` registers this plugin under. */\nconst EMAIL_PLUGIN_ID = 'email'\n\n/** A design's name when the caller asks for none, as the composer's create names one. */\nexport const EMAIL_DESIGN_DEFAULT_NAME = 'Untitled email'\n\n/** The longest subject or preheader stored, the send route's header cap. */\nexport const EMAIL_DESIGN_HEADER_MAX_CHARS = 200\n\n/** The most alternative subject lines or preheaders a design keeps. */\nexport const EMAIL_DESIGN_MAX_VARIANTS = 10\n\n/** The resources route's refusal at the non-page ceiling. */\nexport const EMAIL_DESIGN_LIMIT_REFUSAL =\n 'This site is at its limit of ' +\n `${NON_PAGE_SCREEN_MAX_PER_HOST} email and template screens — ` +\n 'delete some to make room'\n\n/** The resources route's refusal for a member who may not write the site. */\nexport const EMAIL_DESIGN_ROLE_REFUSAL = 'Editing requires the editor role'\n\n/*\n * What the check renders against. No mail is sent and nothing is fetched: the\n * origin makes the renderer keep images and links it would drop without one,\n * and the recipient fills the merge tokens a real send would fill.\n */\nconst CHECK_SITE_BASE = 'https://example.com'\nconst CHECK_RECIPIENT = { email: 'reader@example.com', name: 'Sample Reader' }\n\n/** One node of a design, as far as the writer reads it. */\nexport interface EmailDesignNode {\n componentId?: unknown\n props?: Record<string, unknown>\n nodes?: unknown\n}\n\n/**\n * What a caller sends as `content`: the node map rooted at the canvas root,\n * the subject and preheader, and the alternatives to them.\n */\nexport interface EmailDesignDraftContent {\n nodes: Record<string, EmailDesignNode>\n subject: string\n preheader: string\n subjectVariants: string[]\n preheaderVariants: string[]\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A header value on one line: controls and runs of space folded to one space. */\nfunction headerLine(value: unknown): string {\n return String(value ?? '').replace(/[\\p{Cc}\\s]+/gu, ' ').trim()\n}\n\n/** Alternatives as the design keeps them: on one line, unique, non-empty, bounded. */\nfunction variantsOf(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n const lines: string[] = []\n for (const raw of value) {\n const line = headerLine(raw)\n const key = line.toLowerCase()\n if (!line || seen.has(key)) continue\n seen.add(key)\n lines.push(line)\n }\n return lines.slice(0, EMAIL_DESIGN_MAX_VARIANTS)\n}\n\n/** The ids reachable from the canvas root, or the structural problem that stops the walk. */\nfunction reachableIds(nodes: Record<string, EmailDesignNode>): { ids: Set<string>; problems: string[] } {\n const ids = new Set<string>()\n const problems = new Set<string>()\n const stack = [CANVAS_ROOT_ELEMENT_ID]\n while (stack.length) {\n const id = stack.pop() as string\n if (ids.has(id)) {\n problems.add('A block is placed in the design more than once')\n continue\n }\n const node = nodes[id]\n if (!isRecord(node)) {\n problems.add('The design lists a block it does not hold')\n continue\n }\n ids.add(id)\n for (const child of Array.isArray(node.nodes) ? node.nodes : []) {\n if (typeof child === 'string') stack.push(child)\n }\n }\n return { ids, problems: [...problems] }\n}\n\nexport type EmailDesignContentRead =\n | { ok: true; value: EmailDesignDraftContent; reachable: Set<string> }\n | { ok: false; problems: string[] }\n\n/** The content as the writer stores it, or every problem that stops it. */\nexport function readEmailDesignContent(content: Readonly<Record<string, unknown>>): EmailDesignContentRead {\n const nodes = content['nodes']\n if (!isRecord(nodes) || !Object.keys(nodes).length) {\n return { ok: false, problems: ['The design has no blocks'] }\n }\n const map = nodes as Record<string, EmailDesignNode>\n if (!isRecord(map[CANVAS_ROOT_ELEMENT_ID])) {\n return { ok: false, problems: ['The design has no root to render from'] }\n }\n const { ids, problems } = reachableIds(map)\n const stranded = Object.keys(map).filter((id) => !ids.has(id)).length\n if (stranded) {\n problems.push(\n `${stranded} ${stranded === 1 ? 'block is' : 'blocks are'} not placed in the design, so ${\n stranded === 1 ? 'it' : 'they'\n } would not be sent`,\n )\n }\n const value: EmailDesignDraftContent = {\n nodes: map,\n subject: headerLine(content['subject']),\n preheader: headerLine(content['preheader']),\n subjectVariants: variantsOf(content['subjectVariants']),\n preheaderVariants: variantsOf(content['preheaderVariants']),\n }\n const long = (lines: string[]) => lines.some((line) => line.length > EMAIL_DESIGN_HEADER_MAX_CHARS)\n if (long([value.subject, ...value.subjectVariants])) {\n problems.push(`A subject line is longer than ${EMAIL_DESIGN_HEADER_MAX_CHARS} characters`)\n }\n if (long([value.preheader, ...value.preheaderVariants])) {\n problems.push(`A preheader is longer than ${EMAIL_DESIGN_HEADER_MAX_CHARS} characters`)\n }\n return problems.length ? { ok: false, problems } : { ok: true, value, reachable: ids }\n}\n\n/** Why a link on a block would not open from an inbox, or `null`. */\nfunction linkProblem(label: string, raw: unknown, required: boolean): string | null {\n const written = String(raw ?? '').trim()\n if (/\\{\\{\\s*contact\\./.test(written)) {\n return `${label} puts the reader's own details in its link, which is never filled`\n }\n const href = written\n .replace(/\\{\\{\\s*site\\.url\\s*\\}\\}/g, CHECK_SITE_BASE)\n .replace(/\\{\\{\\s*unsubscribeUrl\\s*\\}\\}/g, `${CHECK_SITE_BASE}/preferences`)\n if (!href || href === '#') return required ? `${label} links nowhere` : null\n if (href.startsWith('/') && !href.startsWith('//')) {\n return `${label} links to a path with no site in front of it, which an inbox cannot open`\n }\n return hasSafeLinkScheme(href) ? null : `${label} links to something that is not a web address`\n}\n\n/** A placement still standing once the site's components are grafted. */\nexport const EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM =\n 'A reusable block names a component this site has not published, so it would send empty'\n\n/**\n * Whether content renders as an email: well-formed, every link one an inbox\n * can open, and a message once rendered through the send path's own renderer.\n *\n * Judged COMPOSED (AGL-3287). The reusable blocks the design places — a shared\n * header, a footer — are grafted from `context.components` first, as every send\n * grafts them, so a link inside a footer is checked like any other and a\n * header's text counts toward the message. A placement still standing after\n * that names a component the caller could not supply, and would go out as\n * nothing; it is refused rather than passed as a design that renders.\n */\nexport function checkEmailDesignContent(\n content: Readonly<Record<string, unknown>>,\n context: PluginDraftCheckContext,\n): PluginDraftCheck {\n const read = readEmailDesignContent(content)\n if (read.ok === false) return read\n const { value } = read\n // Property values and attribute overrides apply in the graft; style\n // overrides land in `sx`, which the mail renderer never reads.\n const nodes = composeReusableComponentNodes(\n value.nodes as never,\n context.components as never,\n ) as unknown as Record<string, EmailDesignNode>\n const reachable =\n nodes === value.nodes ? read.reachable : reachableIds(nodes).ids\n const problems: string[] = []\n for (const id of reachable) {\n const node = nodes[id]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID) {\n problems.push(EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM)\n continue\n }\n if (node.componentId === 'emailButton') {\n const problem = linkProblem('A button', node.props?.['href'], true)\n if (problem) problems.push(problem)\n }\n if (node.componentId === 'emailImage') {\n const problem = linkProblem('An image', node.props?.['href'], false)\n if (problem) problems.push(problem)\n }\n }\n const rendered = renderRecipientEmail({\n subject: value.subject,\n preheader: value.preheader,\n content: {\n mode: 'design',\n template: { nodes, subject: value.subject, preheader: value.preheader },\n },\n recipient: CHECK_RECIPIENT,\n siteBase: CHECK_SITE_BASE,\n hostId: context.hostId,\n })\n if (!rendered.messageText.trim()) {\n problems.push('The design renders no message: it needs at least one block of text')\n }\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n return {\n ok: true,\n facts: {\n htmlBytes: new TextEncoder().encode(rendered.html).length,\n messageText: rendered.messageText,\n // The blocks the design itself holds, as stored — a placed component\n // counts once, however many blocks it expands into.\n blocks: read.reachable.size,\n // No part is written by hand, so the text a campaign sends is the design's own.\n plainText: emailPlainTextState({}),\n },\n }\n}\n\ninterface ScreenRow extends BillableScreenSource {\n name: string\n}\n\nasync function readScreenRows(\n hostRef: FirebaseFirestore.DocumentReference,\n read: (query: FirebaseFirestore.Query) => Promise<FirebaseFirestore.QuerySnapshot>,\n): Promise<ScreenRow[]> {\n const snapshot = await read(hostRef.collection('screens').select('displayName', 'kind', 'deletedAt'))\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n kind: doc.get('kind'),\n deletedAt: doc.get('deletedAt'),\n name: String(doc.get('displayName') ?? ''),\n }))\n}\n\nfunction roleRefusal(host: FirebaseFirestore.DocumentSnapshot, uid: string): PluginDraftRefusal | null {\n const role = (host.get('memberRoles') ?? {})[uid]\n return hostRoleCanWrite(role) ? null : { status: 403, error: EMAIL_DESIGN_ROLE_REFUSAL }\n}\n\nfunction roomRefusal(rows: ScreenRow[], host: FirebaseFirestore.DocumentSnapshot): PluginDraftRefusal | null {\n const used = nonPageScreenIds(rows, host.get('screens') as never).size\n return used >= NON_PAGE_SCREEN_MAX_PER_HOST ? { status: 403, error: EMAIL_DESIGN_LIMIT_REFUSAL } : null\n}\n\nfunction strings(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n}\n\nfunction recordOf(screen: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const versionId = screen.get('versionId')\n return {\n id: screen.id,\n name: String(screen.get('displayName') ?? ''),\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n facts: {\n subject: String(screen.get('emailSubject') ?? ''),\n preheader: String(screen.get('emailPreheader') ?? ''),\n subjectVariants: strings(screen.get('emailSubjectVariants')),\n preheaderVariants: strings(screen.get('emailPreheaderVariants')),\n },\n }\n}\n\n/** The subject, preheader and alternatives as the screen document stores them. */\nfunction copyFields(value: EmailDesignDraftContent): Record<string, unknown> {\n return {\n ...(value.subject ? { emailSubject: value.subject } : {}),\n ...(value.preheader ? { emailPreheader: value.preheader } : {}),\n ...(value.subjectVariants.length ? { emailSubjectVariants: value.subjectVariants } : {}),\n ...(value.preheaderVariants.length ? { emailPreheaderVariants: value.preheaderVariants } : {}),\n }\n}\n\nexport interface EmailDesignDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** Mints the first version's id. */\n mintId?: () => string\n}\n\nexport function createEmailDesignDraftWriter(\n deps: EmailDesignDraftWriterDeps = {},\n): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const mintId = deps.mintId ?? createResourceUid\n return {\n refusal: async (context) => {\n const hostRef = firestore().collection('hosts').doc(context.hostId)\n const host = await hostRef.get()\n if (!host.exists) return { status: 404, error: 'Unknown site' }\n return (\n roleRefusal(host, context.uid) ??\n roomRefusal(await readScreenRows(hostRef, (query) => query.get()), host)\n )\n },\n\n check: checkEmailDesignContent,\n\n read: async ({ hostId, id }) => {\n const screen = await firestore().collection('hosts').doc(hostId).collection('screens').doc(id).get()\n return screen.exists && screen.get('kind') === SCREEN_KIND_EMAIL ? recordOf(screen) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n const read = readEmailDesignContent(request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const packed = encodeStoredNodes(read.value.nodes)\n if (!packed) return { ok: false, status: 400, error: 'The design has no blocks' }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(request.hostId)\n const screenRef = hostRef.collection('screens').doc(request.id)\n const versionId = mintId()\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, rows] = await Promise.all([\n tx.get(hostRef),\n tx.get(screenRef),\n readScreenRows(hostRef, (query) => tx.get(query)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return existing.get('kind') === SCREEN_KIND_EMAIL\n ? { ok: true, replayed: true, ...recordOf(existing) }\n : { ok: false, status: 409, error: 'That id already names a screen that is not an email design' }\n }\n const refusal = roleRefusal(host, request.uid) ?? roomRefusal(rows, host)\n if (refusal) return { ok: false, ...refusal }\n const name = uniqueDuplicateName(\n headerLine(request.name) || EMAIL_DESIGN_DEFAULT_NAME,\n rows.filter((row) => row.deletedAt == null).map((row) => row.name),\n )\n const { screen, version } = emailDesignDocuments({\n screenId: request.id,\n versionId,\n displayName: name,\n nodes: read.value.nodes,\n })\n const stamps = { createdAt: request.now, updatedAt: request.now, createdBy: request.uid }\n const copy = copyFields(read.value)\n // The keys the email templates list finds it by (AGL-3321).\n tx.create(screenRef, { ...screen, ...displayNameSearchFields(name), ...copy, ...stamps })\n tx.create(screenRef.collection('versions').doc(versionId), {\n ...version,\n nodes: Buffer.from(packed),\n hostId: request.hostId,\n ...stamps,\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name,\n versionId,\n facts: {\n subject: read.value.subject,\n preheader: read.value.preheader,\n subjectVariants: read.value.subjectVariants,\n preheaderVariants: read.value.preheaderVariants,\n },\n }\n })\n },\n }\n}\n\nexport const emailDesignDraftWriter = createEmailDesignDraftWriter()\n\n/**\n * Registers the writer; the console surface calls it, since only the console\n * runs AI jobs (AGL-3026). Idempotent: a second call replaces the first.\n */\nexport function registerEmailDesignDraftWriter(): void {\n registerPluginResourceDraftWriter(EMAIL_DESIGN_DRAFT_RESOURCE, emailDesignDraftWriter, {\n pluginId: EMAIL_PLUGIN_ID,\n })\n}\n"],"names":["createResourceUid","uniqueDuplicateName","displayNameSearchFields","hostRoleCanWrite","NON_PAGE_SCREEN_MAX_PER_HOST","nonPageScreenIds","SCREEN_KIND_EMAIL","encodeStoredNodes","CANVAS_ROOT_ELEMENT_ID","registerPluginResourceDraftWriter","composeReusableComponentNodes","REUSABLE_INSTANCE_COMPONENT_ID","hasSafeLinkScheme","firebaseAdmin","emailPlainTextState","renderRecipientEmail","emailDesignDocuments","EMAIL_DESIGN_DRAFT_RESOURCE","EMAIL_PLUGIN_ID","EMAIL_DESIGN_DEFAULT_NAME","EMAIL_DESIGN_HEADER_MAX_CHARS","EMAIL_DESIGN_MAX_VARIANTS","EMAIL_DESIGN_LIMIT_REFUSAL","EMAIL_DESIGN_ROLE_REFUSAL","CHECK_SITE_BASE","CHECK_RECIPIENT","email","name","isRecord","value","Array","isArray","headerLine","String","replace","trim","variantsOf","seen","Set","lines","raw","line","key","toLowerCase","has","add","push","slice","reachableIds","nodes","ids","problems","stack","length","id","pop","node","child","readEmailDesignContent","content","Object","keys","ok","map","stranded","filter","subject","preheader","subjectVariants","preheaderVariants","long","some","reachable","linkProblem","label","required","written","test","href","startsWith","EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM","checkEmailDesignContent","context","read","components","componentId","problem","props","rendered","mode","template","recipient","siteBase","hostId","messageText","facts","htmlBytes","TextEncoder","encode","html","blocks","size","plainText","readScreenRows","hostRef","snapshot","collection","select","docs","doc","kind","get","deletedAt","roleRefusal","host","uid","role","status","error","roomRefusal","rows","used","strings","entry","recordOf","screen","versionId","copyFields","emailSubject","emailPreheader","emailSubjectVariants","emailPreheaderVariants","createEmailDesignDraftWriter","deps","firestore","app","mintId","refusal","exists","query","check","write","request","packed","db","screenRef","runTransaction","tx","existing","Promise","all","replayed","row","version","screenId","displayName","stamps","createdAt","now","updatedAt","createdBy","copy","create","Buffer","from","emailDesignDraftWriter","registerEmailDesignDraftWriter","pluginId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,QAAQ,6CAA4C;AAC9E,SAASC,mBAAmB,QAAQ,4CAA2C;AAC/E,SAASC,uBAAuB,QAAQ,qCAAoC;AAC5E,SAASC,gBAAgB,QAAQ,uCAAsC;AACvE,SACEC,4BAA4B,EAC5BC,gBAAgB,EAChBC,iBAAiB,QAEZ,sCAAqC;AAC5C,SAASC,iBAAiB,QAAQ,sCAAqC;AACvE,SAASC,sBAAsB,QAAQ,2CAA0C;AACjF,SACEC,iCAAiC,QAO5B,qDAAoD;AAC3D,SACEC,6BAA6B,EAC7BC,8BAA8B,QACzB,qDAAoD;AAC3D,SAASC,iBAAiB,QAAQ,0CAAyC;AAC3E,SAASC,aAAa,QAAQ,2BAA0B;AACxD,SACEC,mBAAmB,EACnBC,oBAAoB,QACf,gDAA+C;AACtD,SAASC,oBAAoB,QAAQ,mCAA+B;AA+BpE,uDAAuD,GACvD,OAAO,MAAMC,8BAA8B,cAAa;AAExD,8DAA8D,GAC9D,MAAMC,kBAAkB;AAExB,uFAAuF,GACvF,OAAO,MAAMC,4BAA4B,iBAAgB;AAEzD,0EAA0E,GAC1E,OAAO,MAAMC,gCAAgC,IAAG;AAEhD,qEAAqE,GACrE,OAAO,MAAMC,4BAA4B,GAAE;AAE3C,2DAA2D,GAC3D,OAAO,MAAMC,6BACX,kCACA,GAAGlB,6BAA6B,8BAA8B,CAAC,GAC/D,2BAA0B;AAE5B,2EAA2E,GAC3E,OAAO,MAAMmB,4BAA4B,mCAAkC;AAE3E;;;;CAIC,GACD,MAAMC,kBAAkB;AACxB,MAAMC,kBAAkB;IAAEC,OAAO;IAAsBC,MAAM;AAAgB;AAqB7E,SAASC,SAASC,KAAc;IAC9B,OAAO,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACC,MAAMC,OAAO,CAACF;AACvE;AAEA,gFAAgF,GAChF,SAASG,WAAWH,KAAc;IAChC,OAAOI,OAAOJ,gBAAAA,QAAS,IAAIK,OAAO,CAAC,sDAAiB,KAAKC,IAAI;AAC/D;AAEA,oFAAoF,GACpF,SAASC,WAAWP,KAAc;IAChC,IAAI,CAACC,MAAMC,OAAO,CAACF,QAAQ,OAAO,EAAE;IACpC,MAAMQ,OAAO,IAAIC;IACjB,MAAMC,QAAkB,EAAE;IAC1B,KAAK,MAAMC,OAAOX,MAAO;QACvB,MAAMY,OAAOT,WAAWQ;QACxB,MAAME,MAAMD,KAAKE,WAAW;QAC5B,IAAI,CAACF,QAAQJ,KAAKO,GAAG,CAACF,MAAM;QAC5BL,KAAKQ,GAAG,CAACH;QACTH,MAAMO,IAAI,CAACL;IACb;IACA,OAAOF,MAAMQ,KAAK,CAAC,GAAG1B;AACxB;AAEA,2FAA2F,GAC3F,SAAS2B,aAAaC,KAAsC;IAC1D,MAAMC,MAAM,IAAIZ;IAChB,MAAMa,WAAW,IAAIb;IACrB,MAAMc,QAAQ;QAAC5C;KAAuB;IACtC,MAAO4C,MAAMC,MAAM,CAAE;QACnB,MAAMC,KAAKF,MAAMG,GAAG;QACpB,IAAIL,IAAIN,GAAG,CAACU,KAAK;YACfH,SAASN,GAAG,CAAC;YACb;QACF;QACA,MAAMW,OAAOP,KAAK,CAACK,GAAG;QACtB,IAAI,CAAC1B,SAAS4B,OAAO;YACnBL,SAASN,GAAG,CAAC;YACb;QACF;QACAK,IAAIL,GAAG,CAACS;QACR,KAAK,MAAMG,SAAS3B,MAAMC,OAAO,CAACyB,KAAKP,KAAK,IAAIO,KAAKP,KAAK,GAAG,EAAE,CAAE;YAC/D,IAAI,OAAOQ,UAAU,UAAUL,MAAMN,IAAI,CAACW;QAC5C;IACF;IACA,OAAO;QAAEP;QAAKC,UAAU;eAAIA;SAAS;IAAC;AACxC;AAMA,yEAAyE,GACzE,OAAO,SAASO,uBAAuBC,OAA0C;IAC/E,MAAMV,QAAQU,OAAO,CAAC,QAAQ;IAC9B,IAAI,CAAC/B,SAASqB,UAAU,CAACW,OAAOC,IAAI,CAACZ,OAAOI,MAAM,EAAE;QAClD,OAAO;YAAES,IAAI;YAAOX,UAAU;gBAAC;aAA2B;QAAC;IAC7D;IACA,MAAMY,MAAMd;IACZ,IAAI,CAACrB,SAASmC,GAAG,CAACvD,uBAAuB,GAAG;QAC1C,OAAO;YAAEsD,IAAI;YAAOX,UAAU;gBAAC;aAAwC;QAAC;IAC1E;IACA,MAAM,EAAED,GAAG,EAAEC,QAAQ,EAAE,GAAGH,aAAae;IACvC,MAAMC,WAAWJ,OAAOC,IAAI,CAACE,KAAKE,MAAM,CAAC,CAACX,KAAO,CAACJ,IAAIN,GAAG,CAACU,KAAKD,MAAM;IACrE,IAAIW,UAAU;QACZb,SAASL,IAAI,CACX,GAAGkB,SAAS,CAAC,EAAEA,aAAa,IAAI,aAAa,aAAa,8BAA8B,EACtFA,aAAa,IAAI,OAAO,OACzB,kBAAkB,CAAC;IAExB;IACA,MAAMnC,QAAiC;QACrCoB,OAAOc;QACPG,SAASlC,WAAW2B,OAAO,CAAC,UAAU;QACtCQ,WAAWnC,WAAW2B,OAAO,CAAC,YAAY;QAC1CS,iBAAiBhC,WAAWuB,OAAO,CAAC,kBAAkB;QACtDU,mBAAmBjC,WAAWuB,OAAO,CAAC,oBAAoB;IAC5D;IACA,MAAMW,OAAO,CAAC/B,QAAoBA,MAAMgC,IAAI,CAAC,CAAC9B,OAASA,KAAKY,MAAM,GAAGjC;IACrE,IAAIkD,KAAK;QAACzC,MAAMqC,OAAO;WAAKrC,MAAMuC,eAAe;KAAC,GAAG;QACnDjB,SAASL,IAAI,CAAC,CAAC,8BAA8B,EAAE1B,8BAA8B,WAAW,CAAC;IAC3F;IACA,IAAIkD,KAAK;QAACzC,MAAMsC,SAAS;WAAKtC,MAAMwC,iBAAiB;KAAC,GAAG;QACvDlB,SAASL,IAAI,CAAC,CAAC,2BAA2B,EAAE1B,8BAA8B,WAAW,CAAC;IACxF;IACA,OAAO+B,SAASE,MAAM,GAAG;QAAES,IAAI;QAAOX;IAAS,IAAI;QAAEW,IAAI;QAAMjC;QAAO2C,WAAWtB;IAAI;AACvF;AAEA,mEAAmE,GACnE,SAASuB,YAAYC,KAAa,EAAElC,GAAY,EAAEmC,QAAiB;IACjE,MAAMC,UAAU3C,OAAOO,cAAAA,MAAO,IAAIL,IAAI;IACtC,IAAI,mBAAmB0C,IAAI,CAACD,UAAU;QACpC,OAAO,GAAGF,MAAM,iEAAiE,CAAC;IACpF;IACA,MAAMI,OAAOF,QACV1C,OAAO,CAAC,4BAA4BV,iBACpCU,OAAO,CAAC,iCAAiC,GAAGV,gBAAgB,YAAY,CAAC;IAC5E,IAAI,CAACsD,QAAQA,SAAS,KAAK,OAAOH,WAAW,GAAGD,MAAM,cAAc,CAAC,GAAG;IACxE,IAAII,KAAKC,UAAU,CAAC,QAAQ,CAACD,KAAKC,UAAU,CAAC,OAAO;QAClD,OAAO,GAAGL,MAAM,wEAAwE,CAAC;IAC3F;IACA,OAAO9D,kBAAkBkE,QAAQ,OAAO,GAAGJ,MAAM,6CAA6C,CAAC;AACjG;AAEA,uEAAuE,GACvE,OAAO,MAAMM,wCACX,yFAAwF;AAE1F;;;;;;;;;;CAUC,GACD,OAAO,SAASC,wBACdtB,OAA0C,EAC1CuB,OAAgC;IAEhC,MAAMC,OAAOzB,uBAAuBC;IACpC,IAAIwB,KAAKrB,EAAE,KAAK,OAAO,OAAOqB;IAC9B,MAAM,EAAEtD,KAAK,EAAE,GAAGsD;IAClB,oEAAoE;IACpE,+DAA+D;IAC/D,MAAMlC,QAAQvC,8BACZmB,MAAMoB,KAAK,EACXiC,QAAQE,UAAU;IAEpB,MAAMZ,YACJvB,UAAUpB,MAAMoB,KAAK,GAAGkC,KAAKX,SAAS,GAAGxB,aAAaC,OAAOC,GAAG;IAClE,MAAMC,WAAqB,EAAE;IAC7B,KAAK,MAAMG,MAAMkB,UAAW;QAC1B,MAAMhB,OAAOP,KAAK,CAACK,GAAG;QACtB,IAAIE,KAAK6B,WAAW,KAAK1E,gCAAgC;YACvDwC,SAASL,IAAI,CAACkC;YACd;QACF;QACA,IAAIxB,KAAK6B,WAAW,KAAK,eAAe;gBACE7B;YAAxC,MAAM8B,UAAUb,YAAY,aAAYjB,cAAAA,KAAK+B,KAAK,qBAAV/B,WAAY,CAAC,OAAO,EAAE;YAC9D,IAAI8B,SAASnC,SAASL,IAAI,CAACwC;QAC7B;QACA,IAAI9B,KAAK6B,WAAW,KAAK,cAAc;gBACG7B;YAAxC,MAAM8B,UAAUb,YAAY,aAAYjB,eAAAA,KAAK+B,KAAK,qBAAV/B,YAAY,CAAC,OAAO,EAAE;YAC9D,IAAI8B,SAASnC,SAASL,IAAI,CAACwC;QAC7B;IACF;IACA,MAAME,WAAWzE,qBAAqB;QACpCmD,SAASrC,MAAMqC,OAAO;QACtBC,WAAWtC,MAAMsC,SAAS;QAC1BR,SAAS;YACP8B,MAAM;YACNC,UAAU;gBAAEzC;gBAAOiB,SAASrC,MAAMqC,OAAO;gBAAEC,WAAWtC,MAAMsC,SAAS;YAAC;QACxE;QACAwB,WAAWlE;QACXmE,UAAUpE;QACVqE,QAAQX,QAAQW,MAAM;IACxB;IACA,IAAI,CAACL,SAASM,WAAW,CAAC3D,IAAI,IAAI;QAChCgB,SAASL,IAAI,CAAC;IAChB;IACA,IAAIK,SAASE,MAAM,EAAE,OAAO;QAAES,IAAI;QAAOX,UAAU;eAAI,IAAIb,IAAIa;SAAU;IAAC;IAC1E,OAAO;QACLW,IAAI;QACJiC,OAAO;YACLC,WAAW,IAAIC,cAAcC,MAAM,CAACV,SAASW,IAAI,EAAE9C,MAAM;YACzDyC,aAAaN,SAASM,WAAW;YACjC,qEAAqE;YACrE,oDAAoD;YACpDM,QAAQjB,KAAKX,SAAS,CAAC6B,IAAI;YAC3B,gFAAgF;YAChFC,WAAWxF,oBAAoB,CAAC;QAClC;IACF;AACF;AAMA,eAAeyF,eACbC,OAA4C,EAC5CrB,IAAkF;IAElF,MAAMsB,WAAW,MAAMtB,KAAKqB,QAAQE,UAAU,CAAC,WAAWC,MAAM,CAAC,eAAe,QAAQ;IACxF,OAAOF,SAASG,IAAI,CAAC7C,GAAG,CAAC,CAAC8C;YAIXA;eAJoB;YACjCvD,IAAIuD,IAAIvD,EAAE;YACVwD,MAAMD,IAAIE,GAAG,CAAC;YACdC,WAAWH,IAAIE,GAAG,CAAC;YACnBpF,MAAMM,QAAO4E,WAAAA,IAAIE,GAAG,CAAC,0BAARF,WAA0B;QACzC;;AACF;AAEA,SAASI,YAAYC,IAAwC,EAAEC,GAAW;QAC1DD;IAAd,MAAME,OAAO,EAACF,YAAAA,KAAKH,GAAG,CAAC,0BAATG,YAA2B,CAAC,EAAE,CAACC,IAAI;IACjD,OAAOhH,iBAAiBiH,QAAQ,OAAO;QAAEC,QAAQ;QAAKC,OAAO/F;IAA0B;AACzF;AAEA,SAASgG,YAAYC,IAAiB,EAAEN,IAAwC;IAC9E,MAAMO,OAAOpH,iBAAiBmH,MAAMN,KAAKH,GAAG,CAAC,YAAqBV,IAAI;IACtE,OAAOoB,QAAQrH,+BAA+B;QAAEiH,QAAQ;QAAKC,OAAOhG;IAA2B,IAAI;AACrG;AAEA,SAASoG,QAAQ7F,KAAc;IAC7B,OAAOC,MAAMC,OAAO,CAACF,SAASA,MAAMoC,MAAM,CAAC,CAAC0D,QAA2B,OAAOA,UAAU,YAAY,EAAE;AACxG;AAEA,SAASC,SAASC,MAA0C;QAI3CA,aAGKA,cACEA;IAPtB,MAAMC,YAAYD,OAAOd,GAAG,CAAC;IAC7B,OAAO;QACLzD,IAAIuE,OAAOvE,EAAE;QACb3B,MAAMM,QAAO4F,cAAAA,OAAOd,GAAG,CAAC,0BAAXc,cAA6B;QAC1CC,WAAW,OAAOA,cAAc,YAAYA,YAAYA,YAAY;QACpE/B,OAAO;YACL7B,SAASjC,QAAO4F,eAAAA,OAAOd,GAAG,CAAC,2BAAXc,eAA8B;YAC9C1D,WAAWlC,QAAO4F,eAAAA,OAAOd,GAAG,CAAC,6BAAXc,eAAgC;YAClDzD,iBAAiBsD,QAAQG,OAAOd,GAAG,CAAC;YACpC1C,mBAAmBqD,QAAQG,OAAOd,GAAG,CAAC;QACxC;IACF;AACF;AAEA,gFAAgF,GAChF,SAASgB,WAAWlG,KAA8B;IAChD,OAAO,aACDA,MAAMqC,OAAO,GAAG;QAAE8D,cAAcnG,MAAMqC,OAAO;IAAC,IAAI,CAAC,GACnDrC,MAAMsC,SAAS,GAAG;QAAE8D,gBAAgBpG,MAAMsC,SAAS;IAAC,IAAI,CAAC,GACzDtC,MAAMuC,eAAe,CAACf,MAAM,GAAG;QAAE6E,sBAAsBrG,MAAMuC,eAAe;IAAC,IAAI,CAAC,GAClFvC,MAAMwC,iBAAiB,CAAChB,MAAM,GAAG;QAAE8E,wBAAwBtG,MAAMwC,iBAAiB;IAAC,IAAI,CAAC;AAEhG;AASA,OAAO,SAAS+D,6BACdC,OAAmC,CAAC,CAAC;QAEnBA,iBACHA;IADf,MAAMC,aAAYD,kBAAAA,KAAKC,SAAS,YAAdD,kBAAmB,IAAMxH,cAAc0H,GAAG,GAAGD,SAAS;IACxE,MAAME,UAASH,eAAAA,KAAKG,MAAM,YAAXH,eAAerI;IAC9B,OAAO;QACLyI,SAAS,OAAOvD;gBAKZ+B;YAJF,MAAMT,UAAU8B,YAAY5B,UAAU,CAAC,SAASG,GAAG,CAAC3B,QAAQW,MAAM;YAClE,MAAMqB,OAAO,MAAMV,QAAQO,GAAG;YAC9B,IAAI,CAACG,KAAKwB,MAAM,EAAE,OAAO;gBAAErB,QAAQ;gBAAKC,OAAO;YAAe;YAC9D,QACEL,eAAAA,YAAYC,MAAMhC,QAAQiC,GAAG,aAA7BF,eACAM,YAAY,MAAMhB,eAAeC,SAAS,CAACmC,QAAUA,MAAM5B,GAAG,KAAKG;QAEvE;QAEA0B,OAAO3D;QAEPE,MAAM,OAAO,EAAEU,MAAM,EAAEvC,EAAE,EAAE;YACzB,MAAMuE,SAAS,MAAMS,YAAY5B,UAAU,CAAC,SAASG,GAAG,CAAChB,QAAQa,UAAU,CAAC,WAAWG,GAAG,CAACvD,IAAIyD,GAAG;YAClG,OAAOc,OAAOa,MAAM,IAAIb,OAAOd,GAAG,CAAC,YAAYzG,oBAAoBsH,SAASC,UAAU;QACxF;QAEAgB,OAAO,OAAOC;YACZ,MAAM3D,OAAOzB,uBAAuBoF,QAAQnF,OAAO;YACnD,IAAIwB,KAAKrB,EAAE,KAAK,OAAO,OAAO;gBAAEA,IAAI;gBAAOuD,QAAQ;gBAAKC,OAAOnC,KAAKhC,QAAQ,CAAC,EAAE;YAAC;YAChF,MAAM4F,SAASxI,kBAAkB4E,KAAKtD,KAAK,CAACoB,KAAK;YACjD,IAAI,CAAC8F,QAAQ,OAAO;gBAAEjF,IAAI;gBAAOuD,QAAQ;gBAAKC,OAAO;YAA2B;YAChF,MAAM0B,KAAKV;YACX,MAAM9B,UAAUwC,GAAGtC,UAAU,CAAC,SAASG,GAAG,CAACiC,QAAQjD,MAAM;YACzD,MAAMoD,YAAYzC,QAAQE,UAAU,CAAC,WAAWG,GAAG,CAACiC,QAAQxF,EAAE;YAC9D,MAAMwE,YAAYU;YAClB,OAAOQ,GAAGE,cAAc,CAAC,OAAOC;oBAadlC;gBAZhB,yDAAyD;gBACzD,MAAM,CAACC,MAAMkC,UAAU5B,KAAK,GAAG,MAAM6B,QAAQC,GAAG,CAAC;oBAC/CH,GAAGpC,GAAG,CAACP;oBACP2C,GAAGpC,GAAG,CAACkC;oBACP1C,eAAeC,SAAS,CAACmC,QAAUQ,GAAGpC,GAAG,CAAC4B;iBAC3C;gBACD,IAAI,CAACzB,KAAKwB,MAAM,EAAE,OAAO;oBAAE5E,IAAI;oBAAOuD,QAAQ;oBAAKC,OAAO;gBAAe;gBACzE,IAAI8B,SAASV,MAAM,EAAE;oBACnB,OAAOU,SAASrC,GAAG,CAAC,YAAYzG,oBAC5B;wBAAEwD,IAAI;wBAAMyF,UAAU;uBAAS3B,SAASwB,aACxC;wBAAEtF,IAAI;wBAAOuD,QAAQ;wBAAKC,OAAO;oBAA6D;gBACpG;gBACA,MAAMmB,WAAUxB,eAAAA,YAAYC,MAAM4B,QAAQ3B,GAAG,aAA7BF,eAAkCM,YAAYC,MAAMN;gBACpE,IAAIuB,SAAS,OAAO;oBAAE3E,IAAI;mBAAU2E;gBACpC,MAAM9G,OAAO1B,oBACX+B,WAAW8G,QAAQnH,IAAI,KAAKR,2BAC5BqG,KAAKvD,MAAM,CAAC,CAACuF,MAAQA,IAAIxC,SAAS,IAAI,MAAMjD,GAAG,CAAC,CAACyF,MAAQA,IAAI7H,IAAI;gBAEnE,MAAM,EAAEkG,MAAM,EAAE4B,OAAO,EAAE,GAAGzI,qBAAqB;oBAC/C0I,UAAUZ,QAAQxF,EAAE;oBACpBwE;oBACA6B,aAAahI;oBACbsB,OAAOkC,KAAKtD,KAAK,CAACoB,KAAK;gBACzB;gBACA,MAAM2G,SAAS;oBAAEC,WAAWf,QAAQgB,GAAG;oBAAEC,WAAWjB,QAAQgB,GAAG;oBAAEE,WAAWlB,QAAQ3B,GAAG;gBAAC;gBACxF,MAAM8C,OAAOlC,WAAW5C,KAAKtD,KAAK;gBAClC,4DAA4D;gBAC5DsH,GAAGe,MAAM,CAACjB,WAAW,aAAKpB,QAAW3H,wBAAwByB,OAAUsI,MAASL;gBAChFT,GAAGe,MAAM,CAACjB,UAAUvC,UAAU,CAAC,YAAYG,GAAG,CAACiB,YAAY,aACtD2B;oBACHxG,OAAOkH,OAAOC,IAAI,CAACrB;oBACnBlD,QAAQiD,QAAQjD,MAAM;mBACnB+D;gBAEL,OAAO;oBACL9F,IAAI;oBACJyF,UAAU;oBACVjG,IAAIwF,QAAQxF,EAAE;oBACd3B;oBACAmG;oBACA/B,OAAO;wBACL7B,SAASiB,KAAKtD,KAAK,CAACqC,OAAO;wBAC3BC,WAAWgB,KAAKtD,KAAK,CAACsC,SAAS;wBAC/BC,iBAAiBe,KAAKtD,KAAK,CAACuC,eAAe;wBAC3CC,mBAAmBc,KAAKtD,KAAK,CAACwC,iBAAiB;oBACjD;gBACF;YACF;QACF;IACF;AACF;AAEA,OAAO,MAAMgG,yBAAyBjC,+BAA8B;AAEpE;;;CAGC,GACD,OAAO,SAASkC;IACd7J,kCAAkCQ,6BAA6BoJ,wBAAwB;QACrFE,UAAUrJ;IACZ;AACF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/email/src/lib/server-email-drafts.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { uniqueDuplicateName } from '@aglyn/aglyn/app-utils/duplicate-resource'\nimport { artifactCreateListKeys } from '@aglyn/aglyn/app-utils/artifact-list-keys'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport {\n NON_PAGE_SCREEN_MAX_PER_HOST,\n nonPageScreenIds,\n SCREEN_KIND_EMAIL,\n type BillableScreenSource,\n} from '@aglyn/aglyn/app-utils/screen-route'\nimport { encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport {\n registerPluginResourceDraftWriter,\n type PluginDraftCheck,\n type PluginDraftCheckContext,\n type PluginDraftRecord,\n type PluginDraftRefusal,\n type PluginDraftWrite,\n type PluginResourceDraftWriter,\n} from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n composeReusableComponentNodes,\n REUSABLE_INSTANCE_COMPONENT_ID,\n} from '@aglyn/aglyn/app-utils/compose-reusable-components'\nimport { hasSafeLinkScheme } from '@aglyn/shared-util-http/safe-url-scheme'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n emailPlainTextState,\n renderRecipientEmail,\n} from '@aglyn/aglyn/app-utils/recipient-email-render'\nimport { emailDesignDocuments } from './model/email-design-document'\n\n/**\n * AN EMAIL DESIGN ANOTHER PLUGIN ASKS FOR (AGL-2912).\n *\n * The draft writer this plugin registers for the `emailDesign` resource on the\n * core's resource-drafts seam. A plugin that produces an email — a generator\n * working from a brief, an importer bringing templates over — asks for the\n * writer by name and gets this plugin's rules, never a copy of them:\n *\n * - THE DOCUMENTS are the pair `createEmailScreen` makes, built by the same\n * function (`emailDesignDocuments`): a `kind: 'email'` screen and its first\n * version. So the design lists on the Templates tab, opens in the besigner\n * and is offered by the campaign composer like one a person made. The\n * screen carries the subject and preheader a campaign built from it opens\n * on, and the other lines worth trying beside them.\n * - THE ROOM is the flat platform ceiling on non-page screens, met inside the\n * transaction with the arithmetic `/api/hosts/resources` and the duplicate\n * module use, and refused in their words.\n * - THE ROLE is the resources route's: a member who may write the site's\n * content.\n * - THE CHECK renders the design through `renderRecipientEmail`, the function\n * the send path and the composer's preview both call, so content that\n * passes is content that would render in both.\n *\n * Nothing is sent, scheduled or routed. A design is inert until a campaign\n * names it, and the writer touches the two new documents and nothing else.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** The resource name this writer is registered under. */\nexport const EMAIL_DESIGN_DRAFT_RESOURCE = 'emailDesign'\n\n/** The id `plugins.config.json` registers this plugin under. */\nconst EMAIL_PLUGIN_ID = 'email'\n\n/** A design's name when the caller asks for none, as the composer's create names one. */\nexport const EMAIL_DESIGN_DEFAULT_NAME = 'Untitled email'\n\n/** The longest subject or preheader stored, the send route's header cap. */\nexport const EMAIL_DESIGN_HEADER_MAX_CHARS = 200\n\n/** The most alternative subject lines or preheaders a design keeps. */\nexport const EMAIL_DESIGN_MAX_VARIANTS = 10\n\n/** The resources route's refusal at the non-page ceiling. */\nexport const EMAIL_DESIGN_LIMIT_REFUSAL =\n 'This site is at its limit of ' +\n `${NON_PAGE_SCREEN_MAX_PER_HOST} email and template screens — ` +\n 'delete some to make room'\n\n/** The resources route's refusal for a member who may not write the site. */\nexport const EMAIL_DESIGN_ROLE_REFUSAL = 'Editing requires the editor role'\n\n/*\n * What the check renders against. No mail is sent and nothing is fetched: the\n * origin makes the renderer keep images and links it would drop without one,\n * and the recipient fills the merge tokens a real send would fill.\n */\nconst CHECK_SITE_BASE = 'https://example.com'\nconst CHECK_RECIPIENT = { email: 'reader@example.com', name: 'Sample Reader' }\n\n/** One node of a design, as far as the writer reads it. */\nexport interface EmailDesignNode {\n componentId?: unknown\n props?: Record<string, unknown>\n nodes?: unknown\n}\n\n/**\n * What a caller sends as `content`: the node map rooted at the canvas root,\n * the subject and preheader, and the alternatives to them.\n */\nexport interface EmailDesignDraftContent {\n nodes: Record<string, EmailDesignNode>\n subject: string\n preheader: string\n subjectVariants: string[]\n preheaderVariants: string[]\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** A header value on one line: controls and runs of space folded to one space. */\nfunction headerLine(value: unknown): string {\n return String(value ?? '').replace(/[\\p{Cc}\\s]+/gu, ' ').trim()\n}\n\n/** Alternatives as the design keeps them: on one line, unique, non-empty, bounded. */\nfunction variantsOf(value: unknown): string[] {\n if (!Array.isArray(value)) return []\n const seen = new Set<string>()\n const lines: string[] = []\n for (const raw of value) {\n const line = headerLine(raw)\n const key = line.toLowerCase()\n if (!line || seen.has(key)) continue\n seen.add(key)\n lines.push(line)\n }\n return lines.slice(0, EMAIL_DESIGN_MAX_VARIANTS)\n}\n\n/** The ids reachable from the canvas root, or the structural problem that stops the walk. */\nfunction reachableIds(nodes: Record<string, EmailDesignNode>): { ids: Set<string>; problems: string[] } {\n const ids = new Set<string>()\n const problems = new Set<string>()\n const stack = [CANVAS_ROOT_ELEMENT_ID]\n while (stack.length) {\n const id = stack.pop() as string\n if (ids.has(id)) {\n problems.add('A block is placed in the design more than once')\n continue\n }\n const node = nodes[id]\n if (!isRecord(node)) {\n problems.add('The design lists a block it does not hold')\n continue\n }\n ids.add(id)\n for (const child of Array.isArray(node.nodes) ? node.nodes : []) {\n if (typeof child === 'string') stack.push(child)\n }\n }\n return { ids, problems: [...problems] }\n}\n\nexport type EmailDesignContentRead =\n | { ok: true; value: EmailDesignDraftContent; reachable: Set<string> }\n | { ok: false; problems: string[] }\n\n/** The content as the writer stores it, or every problem that stops it. */\nexport function readEmailDesignContent(content: Readonly<Record<string, unknown>>): EmailDesignContentRead {\n const nodes = content['nodes']\n if (!isRecord(nodes) || !Object.keys(nodes).length) {\n return { ok: false, problems: ['The design has no blocks'] }\n }\n const map = nodes as Record<string, EmailDesignNode>\n if (!isRecord(map[CANVAS_ROOT_ELEMENT_ID])) {\n return { ok: false, problems: ['The design has no root to render from'] }\n }\n const { ids, problems } = reachableIds(map)\n const stranded = Object.keys(map).filter((id) => !ids.has(id)).length\n if (stranded) {\n problems.push(\n `${stranded} ${stranded === 1 ? 'block is' : 'blocks are'} not placed in the design, so ${\n stranded === 1 ? 'it' : 'they'\n } would not be sent`,\n )\n }\n const value: EmailDesignDraftContent = {\n nodes: map,\n subject: headerLine(content['subject']),\n preheader: headerLine(content['preheader']),\n subjectVariants: variantsOf(content['subjectVariants']),\n preheaderVariants: variantsOf(content['preheaderVariants']),\n }\n const long = (lines: string[]) => lines.some((line) => line.length > EMAIL_DESIGN_HEADER_MAX_CHARS)\n if (long([value.subject, ...value.subjectVariants])) {\n problems.push(`A subject line is longer than ${EMAIL_DESIGN_HEADER_MAX_CHARS} characters`)\n }\n if (long([value.preheader, ...value.preheaderVariants])) {\n problems.push(`A preheader is longer than ${EMAIL_DESIGN_HEADER_MAX_CHARS} characters`)\n }\n return problems.length ? { ok: false, problems } : { ok: true, value, reachable: ids }\n}\n\n/** Why a link on a block would not open from an inbox, or `null`. */\nfunction linkProblem(label: string, raw: unknown, required: boolean): string | null {\n const written = String(raw ?? '').trim()\n if (/\\{\\{\\s*contact\\./.test(written)) {\n return `${label} puts the reader's own details in its link, which is never filled`\n }\n const href = written\n .replace(/\\{\\{\\s*site\\.url\\s*\\}\\}/g, CHECK_SITE_BASE)\n .replace(/\\{\\{\\s*unsubscribeUrl\\s*\\}\\}/g, `${CHECK_SITE_BASE}/preferences`)\n if (!href || href === '#') return required ? `${label} links nowhere` : null\n if (href.startsWith('/') && !href.startsWith('//')) {\n return `${label} links to a path with no site in front of it, which an inbox cannot open`\n }\n return hasSafeLinkScheme(href) ? null : `${label} links to something that is not a web address`\n}\n\n/** A placement still standing once the site's components are grafted. */\nexport const EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM =\n 'A reusable block names a component this site has not published, so it would send empty'\n\n/**\n * Whether content renders as an email: well-formed, every link one an inbox\n * can open, and a message once rendered through the send path's own renderer.\n *\n * Judged COMPOSED (AGL-3287). The reusable blocks the design places — a shared\n * header, a footer — are grafted from `context.components` first, as every send\n * grafts them, so a link inside a footer is checked like any other and a\n * header's text counts toward the message. A placement still standing after\n * that names a component the caller could not supply, and would go out as\n * nothing; it is refused rather than passed as a design that renders.\n */\nexport function checkEmailDesignContent(\n content: Readonly<Record<string, unknown>>,\n context: PluginDraftCheckContext,\n): PluginDraftCheck {\n const read = readEmailDesignContent(content)\n if (read.ok === false) return read\n const { value } = read\n // Property values and attribute overrides apply in the graft; style\n // overrides land in `sx`, which the mail renderer never reads.\n const nodes = composeReusableComponentNodes(\n value.nodes as never,\n context.components as never,\n ) as unknown as Record<string, EmailDesignNode>\n const reachable =\n nodes === value.nodes ? read.reachable : reachableIds(nodes).ids\n const problems: string[] = []\n for (const id of reachable) {\n const node = nodes[id]\n if (node.componentId === REUSABLE_INSTANCE_COMPONENT_ID) {\n problems.push(EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM)\n continue\n }\n if (node.componentId === 'emailButton') {\n const problem = linkProblem('A button', node.props?.['href'], true)\n if (problem) problems.push(problem)\n }\n if (node.componentId === 'emailImage') {\n const problem = linkProblem('An image', node.props?.['href'], false)\n if (problem) problems.push(problem)\n }\n }\n const rendered = renderRecipientEmail({\n subject: value.subject,\n preheader: value.preheader,\n content: {\n mode: 'design',\n template: { nodes, subject: value.subject, preheader: value.preheader },\n },\n recipient: CHECK_RECIPIENT,\n siteBase: CHECK_SITE_BASE,\n hostId: context.hostId,\n })\n if (!rendered.messageText.trim()) {\n problems.push('The design renders no message: it needs at least one block of text')\n }\n if (problems.length) return { ok: false, problems: [...new Set(problems)] }\n return {\n ok: true,\n facts: {\n htmlBytes: new TextEncoder().encode(rendered.html).length,\n messageText: rendered.messageText,\n // The blocks the design itself holds, as stored — a placed component\n // counts once, however many blocks it expands into.\n blocks: read.reachable.size,\n // No part is written by hand, so the text a campaign sends is the design's own.\n plainText: emailPlainTextState({}),\n },\n }\n}\n\ninterface ScreenRow extends BillableScreenSource {\n name: string\n}\n\nasync function readScreenRows(\n hostRef: FirebaseFirestore.DocumentReference,\n read: (query: FirebaseFirestore.Query) => Promise<FirebaseFirestore.QuerySnapshot>,\n): Promise<ScreenRow[]> {\n const snapshot = await read(hostRef.collection('screens').select('displayName', 'kind', 'deletedAt'))\n return snapshot.docs.map((doc) => ({\n id: doc.id,\n kind: doc.get('kind'),\n deletedAt: doc.get('deletedAt'),\n name: String(doc.get('displayName') ?? ''),\n }))\n}\n\nfunction roleRefusal(host: FirebaseFirestore.DocumentSnapshot, uid: string): PluginDraftRefusal | null {\n const role = (host.get('memberRoles') ?? {})[uid]\n return hostRoleCanWrite(role) ? null : { status: 403, error: EMAIL_DESIGN_ROLE_REFUSAL }\n}\n\nfunction roomRefusal(rows: ScreenRow[], host: FirebaseFirestore.DocumentSnapshot): PluginDraftRefusal | null {\n const used = nonPageScreenIds(rows, host.get('screens') as never).size\n return used >= NON_PAGE_SCREEN_MAX_PER_HOST ? { status: 403, error: EMAIL_DESIGN_LIMIT_REFUSAL } : null\n}\n\nfunction strings(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n}\n\nfunction recordOf(screen: FirebaseFirestore.DocumentSnapshot): PluginDraftRecord {\n const versionId = screen.get('versionId')\n return {\n id: screen.id,\n name: String(screen.get('displayName') ?? ''),\n versionId: typeof versionId === 'string' && versionId ? versionId : null,\n facts: {\n subject: String(screen.get('emailSubject') ?? ''),\n preheader: String(screen.get('emailPreheader') ?? ''),\n subjectVariants: strings(screen.get('emailSubjectVariants')),\n preheaderVariants: strings(screen.get('emailPreheaderVariants')),\n },\n }\n}\n\n/** The subject, preheader and alternatives as the screen document stores them. */\nfunction copyFields(value: EmailDesignDraftContent): Record<string, unknown> {\n return {\n ...(value.subject ? { emailSubject: value.subject } : {}),\n ...(value.preheader ? { emailPreheader: value.preheader } : {}),\n ...(value.subjectVariants.length ? { emailSubjectVariants: value.subjectVariants } : {}),\n ...(value.preheaderVariants.length ? { emailPreheaderVariants: value.preheaderVariants } : {}),\n }\n}\n\nexport interface EmailDesignDraftWriterDeps {\n /** The Admin SDK handle; specs hand in a double. */\n firestore?: () => Firestore\n /** Mints the first version's id. */\n mintId?: () => string\n}\n\nexport function createEmailDesignDraftWriter(\n deps: EmailDesignDraftWriterDeps = {},\n): PluginResourceDraftWriter {\n const firestore = deps.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const mintId = deps.mintId ?? createResourceUid\n return {\n refusal: async (context) => {\n const hostRef = firestore().collection('hosts').doc(context.hostId)\n const host = await hostRef.get()\n if (!host.exists) return { status: 404, error: 'Unknown site' }\n return (\n roleRefusal(host, context.uid) ??\n roomRefusal(await readScreenRows(hostRef, (query) => query.get()), host)\n )\n },\n\n check: checkEmailDesignContent,\n\n read: async ({ hostId, id }) => {\n const screen = await firestore().collection('hosts').doc(hostId).collection('screens').doc(id).get()\n return screen.exists && screen.get('kind') === SCREEN_KIND_EMAIL ? recordOf(screen) : null\n },\n\n write: async (request): Promise<PluginDraftWrite> => {\n const read = readEmailDesignContent(request.content)\n if (read.ok === false) return { ok: false, status: 400, error: read.problems[0] }\n const packed = encodeStoredNodes(read.value.nodes)\n if (!packed) return { ok: false, status: 400, error: 'The design has no blocks' }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(request.hostId)\n const screenRef = hostRef.collection('screens').doc(request.id)\n const versionId = mintId()\n return db.runTransaction(async (tx): Promise<PluginDraftWrite> => {\n // Every read before any write, which Firestore requires.\n const [host, existing, rows] = await Promise.all([\n tx.get(hostRef),\n tx.get(screenRef),\n readScreenRows(hostRef, (query) => tx.get(query)),\n ])\n if (!host.exists) return { ok: false, status: 404, error: 'Unknown site' }\n if (existing.exists) {\n return existing.get('kind') === SCREEN_KIND_EMAIL\n ? { ok: true, replayed: true, ...recordOf(existing) }\n : { ok: false, status: 409, error: 'That id already names a screen that is not an email design' }\n }\n const refusal = roleRefusal(host, request.uid) ?? roomRefusal(rows, host)\n if (refusal) return { ok: false, ...refusal }\n const name = uniqueDuplicateName(\n headerLine(request.name) || EMAIL_DESIGN_DEFAULT_NAME,\n rows.filter((row) => row.deletedAt == null).map((row) => row.name),\n )\n const { screen, version } = emailDesignDocuments({\n screenId: request.id,\n versionId,\n displayName: name,\n nodes: read.value.nodes,\n })\n const stamps = { createdAt: request.now, updatedAt: request.now, createdBy: request.uid }\n const copy = copyFields(read.value)\n // The keys the email templates list finds it by, and the stored\n // `deletedAt: null` a campaign's screens list asks for (AGL-3321).\n tx.create(screenRef, {\n ...screen,\n ...artifactCreateListKeys('screens', { displayName: name }),\n ...copy,\n ...stamps,\n })\n tx.create(screenRef.collection('versions').doc(versionId), {\n ...version,\n nodes: Buffer.from(packed),\n hostId: request.hostId,\n ...stamps,\n })\n return {\n ok: true,\n replayed: false,\n id: request.id,\n name,\n versionId,\n facts: {\n subject: read.value.subject,\n preheader: read.value.preheader,\n subjectVariants: read.value.subjectVariants,\n preheaderVariants: read.value.preheaderVariants,\n },\n }\n })\n },\n }\n}\n\nexport const emailDesignDraftWriter = createEmailDesignDraftWriter()\n\n/**\n * Registers the writer; the console surface calls it, since only the console\n * runs AI jobs (AGL-3026). Idempotent: a second call replaces the first.\n */\nexport function registerEmailDesignDraftWriter(): void {\n registerPluginResourceDraftWriter(EMAIL_DESIGN_DRAFT_RESOURCE, emailDesignDraftWriter, {\n pluginId: EMAIL_PLUGIN_ID,\n })\n}\n"],"names":["createResourceUid","uniqueDuplicateName","artifactCreateListKeys","hostRoleCanWrite","NON_PAGE_SCREEN_MAX_PER_HOST","nonPageScreenIds","SCREEN_KIND_EMAIL","encodeStoredNodes","CANVAS_ROOT_ELEMENT_ID","registerPluginResourceDraftWriter","composeReusableComponentNodes","REUSABLE_INSTANCE_COMPONENT_ID","hasSafeLinkScheme","firebaseAdmin","emailPlainTextState","renderRecipientEmail","emailDesignDocuments","EMAIL_DESIGN_DRAFT_RESOURCE","EMAIL_PLUGIN_ID","EMAIL_DESIGN_DEFAULT_NAME","EMAIL_DESIGN_HEADER_MAX_CHARS","EMAIL_DESIGN_MAX_VARIANTS","EMAIL_DESIGN_LIMIT_REFUSAL","EMAIL_DESIGN_ROLE_REFUSAL","CHECK_SITE_BASE","CHECK_RECIPIENT","email","name","isRecord","value","Array","isArray","headerLine","String","replace","trim","variantsOf","seen","Set","lines","raw","line","key","toLowerCase","has","add","push","slice","reachableIds","nodes","ids","problems","stack","length","id","pop","node","child","readEmailDesignContent","content","Object","keys","ok","map","stranded","filter","subject","preheader","subjectVariants","preheaderVariants","long","some","reachable","linkProblem","label","required","written","test","href","startsWith","EMAIL_DESIGN_UNRESOLVED_BLOCK_PROBLEM","checkEmailDesignContent","context","read","components","componentId","problem","props","rendered","mode","template","recipient","siteBase","hostId","messageText","facts","htmlBytes","TextEncoder","encode","html","blocks","size","plainText","readScreenRows","hostRef","snapshot","collection","select","docs","doc","kind","get","deletedAt","roleRefusal","host","uid","role","status","error","roomRefusal","rows","used","strings","entry","recordOf","screen","versionId","copyFields","emailSubject","emailPreheader","emailSubjectVariants","emailPreheaderVariants","createEmailDesignDraftWriter","deps","firestore","app","mintId","refusal","exists","query","check","write","request","packed","db","screenRef","runTransaction","tx","existing","Promise","all","replayed","row","version","screenId","displayName","stamps","createdAt","now","updatedAt","createdBy","copy","create","Buffer","from","emailDesignDraftWriter","registerEmailDesignDraftWriter","pluginId"],"mappings":"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"}
|