@aglyn/shared-util-email 1.0.0-beta.246 → 1.0.0-beta.249
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/shared-util-email",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.249",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
28
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.249",
|
|
29
|
+
"@aglyn/shared-util-http": "1.0.0-beta.249",
|
|
30
30
|
"@msgpack/msgpack": "^3.1.3",
|
|
31
31
|
"@swc/helpers": "0.5.23"
|
|
32
32
|
},
|
package/src/lib/email-palette.js
CHANGED
|
@@ -636,7 +636,26 @@ const NUMBER = String.raw`[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[-+]?\d+)?(?:%|deg|gra
|
|
|
636
636
|
main: primaryColor
|
|
637
637
|
}
|
|
638
638
|
} : {};
|
|
639
|
-
const
|
|
639
|
+
const site = siteColorOverrides(input.colors);
|
|
640
|
+
const layered = _extends({}, base, site, brand);
|
|
641
|
+
// `mergeThemeOptions`: a tint the site leaves unset is washed from an
|
|
642
|
+
// accent it does set (`accentTint`), and the base's others stay.
|
|
643
|
+
const siteTint = isRecord(site['tint']) ? site['tint'] : {};
|
|
644
|
+
const tint = _extends({}, isRecord(base['tint']) ? base['tint'] : {}, siteTint);
|
|
645
|
+
for (const key of [
|
|
646
|
+
'primary',
|
|
647
|
+
'secondary',
|
|
648
|
+
'tertiary'
|
|
649
|
+
]){
|
|
650
|
+
const main = isRecord(site[key]) ? site[key]['main'] : undefined;
|
|
651
|
+
if (siteTint[key] || typeof main !== 'string') continue;
|
|
652
|
+
try {
|
|
653
|
+
tint[key] = lighten(main, 0.9);
|
|
654
|
+
} catch (unused) {
|
|
655
|
+
// Not a color the manipulators parse: keep the inherited tint.
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
layered['tint'] = tint;
|
|
640
659
|
try {
|
|
641
660
|
return derivePalette(layered);
|
|
642
661
|
} catch (unused) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/email-palette.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\n/**\n * The palette an email is painted with (AGL-3370).\n *\n * A color an author picks in the Besigner is stored as a palette TOKEN PATH\n * (`primary.main`, `grey.600`), which MUI resolves on a web page and a mail\n * client never will. So an email needs the same palette the site renders\n * with, as plain data, before it writes a single `style` attribute.\n *\n * That palette is built by `@aglyn/shared-ui-theme` — MUI's `createTheme`,\n * then `createResponsiveTheme`'s shade and accessibility passes — and this\n * lib may not import it: a `type:util` package cannot stand on a `type:ui`\n * one, and a server rendering mail has no business loading MUI to do it. What\n * follows is that derivation ported, light scheme only, with the INPUT colors\n * of the two bases copied and everything else computed the way MUI computes\n * it. `email-palette-parity.spec.ts` in `shared-ui-theme` builds both and\n * holds them equal, so the copy cannot drift without a red.\n */\n\n/**\n * A light-scheme MUI palette as plain data: `primary`, `secondary`,\n * `tertiary`, `surface`, `error`, `warning`, `info`, `success` (each\n * `{ main, light, dark, contrastText }`), `background`, `text`, `tint`,\n * `divider`, `inputOutline`, `grey` and `common`.\n */\nexport type EmailPalette = Readonly<Record<string, unknown>>\n\n/** One authored palette color: a `main`, and any shade the author also set. */\nexport interface EmailPaletteColorInput {\n main?: string\n light?: string\n dark?: string\n contrastText?: string\n}\n\n/**\n * The shape of `HostThemeSchemeColors` (`@aglyn/shared-data-types`), spelled\n * out so this lib needs no dependency on the types package for one shape. An\n * interface has no index signature, so without this a site's typed colors\n * would not be assignable to a plain record.\n */\nexport interface EmailPaletteSchemeColors {\n primary?: EmailPaletteColorInput\n secondary?: EmailPaletteColorInput\n tertiary?: EmailPaletteColorInput\n surface?: EmailPaletteColorInput\n error?: EmailPaletteColorInput\n warning?: EmailPaletteColorInput\n info?: EmailPaletteColorInput\n success?: EmailPaletteColorInput\n background?: { default?: string; paper?: string }\n text?: { primary?: string; secondary?: string; disabled?: string }\n tint?: { primary?: string; secondary?: string; tertiary?: string }\n divider?: string\n}\n\nexport interface EmailPaletteInput {\n /**\n * The base a site or sender layers onto: `'platform'` is the console brand\n * (`consoleOptions`), `'tenant'` the neutral default a customer site wears\n * (`tenantOptions`). {@link emailPaletteBaseForHost} picks it for a site.\n */\n base: 'platform' | 'tenant'\n /**\n * A site's authored light-scheme colors — `host.theme.colorSchemes.light`.\n * Stored data, so every leaf is checked as it is read; a plain record is\n * accepted as readily as the typed shape.\n */\n colors?: EmailPaletteSchemeColors | Readonly<Record<string, unknown>> | null\n /**\n * A white-label org's brand color. It becomes `primary.main` and the whole\n * `primary` record is re-derived from it, exactly as a theme given\n * `palette.primary = { main }` would. Ignored unless it parses as a color.\n */\n primaryColor?: string | null\n}\n\ntype ColorRecord = Record<string, unknown>\ntype PaletteDraft = Record<string, unknown>\n\n// ── The two bases' authored inputs ─────────────────────────────────────────\n//\n// Copied from `console.theme.ts` (`colorScheme.light`) and `tenant.theme.ts`\n// (`tenantColorScheme.light`), minus the `svg*` records no email reads. Only\n// what those files AUTHOR is here; every shade they leave to MUI is derived\n// below, so the copy stays as short as the thing it mirrors.\n\nconst BRAND_GREY = {\n 50: '#FAFAFA',\n 100: '#F5F5F5',\n 200: '#EEEEEE',\n 300: '#E0E0E0',\n 400: '#BDBDBD',\n 500: '#9E9E9E',\n 600: '#757575',\n 700: '#616161',\n 800: '#424242',\n 900: '#212121',\n A100: '#D5D5D5',\n A200: '#AAAAAA',\n A400: '#303030',\n A700: '#616161',\n}\n\nconst PLATFORM_LIGHT_INPUT: PaletteDraft = {\n mode: 'light',\n primary: { main: '#00b0ff', dark: '#0077ad', contrastText: '#FFFFFF' },\n secondary: { main: '#e040fb', dark: '#9d2db0', contrastText: '#FFFFFF' },\n tertiary: { main: '#404C5C', contrastText: '#FFFFFF' },\n surface: { main: '#F8F9FA', contrastText: '#000000' },\n tint: { primary: '#E6F5FF', secondary: '#FBE6FE', tertiary: '#EEF0F2' },\n inputOutline: 'rgba(0, 0, 0, 0.23)',\n background: { default: '#F5F5F5', paper: '#FFFFFF' },\n info: { main: '#1878cd', contrastText: '#FFFFFF' },\n error: { main: '#e32c27', contrastText: '#FFFFFF' },\n success: { main: '#4CAF50', contrastText: '#000000DE' },\n warning: { main: '#FFAB40', contrastText: '#000000DE' },\n grey: BRAND_GREY,\n}\n\nconst TENANT_LIGHT_INPUT: PaletteDraft = {\n mode: 'light',\n primary: { main: '#1976d2', dark: '#125393', contrastText: '#FFFFFF' },\n secondary: { main: '#9c27b0', dark: '#6d1b7b', contrastText: '#FFFFFF' },\n tertiary: { main: '#4a5568', dark: '#343b49', contrastText: '#FFFFFF' },\n surface: { main: '#f1f3f5', contrastText: '#000000DE' },\n tint: { primary: '#e8f1fb', secondary: '#f5e9f7', tertiary: '#eef0f3' },\n inputOutline: 'rgba(0, 0, 0, 0.23)',\n background: { default: '#fafafa', paper: '#ffffff' },\n info: { main: '#0288d1', dark: '#015f92', contrastText: '#000000DE' },\n error: { main: '#d32f2f', dark: '#942121', contrastText: '#FFFFFF' },\n success: { main: '#2e7d32', dark: '#205823', contrastText: '#FFFFFF' },\n warning: { main: '#ed6c02', dark: '#a64c01', contrastText: '#000000DE' },\n grey: BRAND_GREY,\n}\n\n// ── MUI's light-mode constants (`createPalette`, `colors/*`) ───────────────\n\nconst MUI_COMMON = { black: '#000', white: '#fff' }\n\nconst MUI_GREY = {\n 50: '#fafafa',\n 100: '#f5f5f5',\n 200: '#eeeeee',\n 300: '#e0e0e0',\n 400: '#bdbdbd',\n 500: '#9e9e9e',\n 600: '#757575',\n 700: '#616161',\n 800: '#424242',\n 900: '#212121',\n A100: '#f5f5f5',\n A200: '#eeeeee',\n A400: '#bdbdbd',\n A700: '#616161',\n}\n\n/** `getLight()` — the mode-hydrated slots, minus `action` (no email reads it). */\nfunction muiLightDefaults(): PaletteDraft {\n return {\n text: {\n primary: 'rgba(0, 0, 0, 0.87)',\n secondary: 'rgba(0, 0, 0, 0.6)',\n disabled: 'rgba(0, 0, 0, 0.38)',\n },\n divider: 'rgba(0, 0, 0, 0.12)',\n background: { paper: MUI_COMMON.white, default: MUI_COMMON.white },\n }\n}\n\n/** MUI's `light.text.primary`, the dark ink `getContrastText` falls back to. */\nconst MUI_DARK_INK = 'rgba(0, 0, 0, 0.87)'\nconst MUI_CONTRAST_THRESHOLD = 3\n/** MUI's default `tonalOffset` of 0.2, split the way `addLightOrDark` splits it. */\nconst TONAL_OFFSET_LIGHT = 0.2\nconst TONAL_OFFSET_DARK = 0.2 * 1.5\n\n/** The six colors `createPalette` augments. Both bases author every one. */\nconst MUI_AUGMENTED_KEYS = [\n 'primary',\n 'secondary',\n 'error',\n 'warning',\n 'info',\n 'success',\n] as const\n\n/** `createResponsiveTheme`'s FOREGROUND_COLOR_KEYS: `dark` is accent text. */\nconst FOREGROUND_COLOR_KEYS: ReadonlyArray<string> = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'error',\n 'warning',\n 'info',\n 'success',\n]\n/** …and `contrastText` is text wherever it lives, so `surface` joins. */\nconst CONTRAST_TEXT_COLOR_KEYS: ReadonlyArray<string> = [\n ...FOREGROUND_COLOR_KEYS,\n 'surface',\n]\n\n// ── MUI's colorManipulator, ported ─────────────────────────────────────────\n//\n// Byte-for-byte in behavior, including the parts that look odd: `recompose`\n// truncates rgb channels with `parseInt`, and `getLuminance` rounds to three\n// places. The palette's derived shades are strings, so a port that rounded\n// \"better\" would be a port that disagrees.\n\ninterface DecomposedColor {\n type: string\n values: Array<number>\n colorSpace?: string\n}\n\nfunction hexToRgb(color: string): string {\n const hex = color.slice(1)\n const pattern = new RegExp(`.{1,${hex.length >= 6 ? 2 : 1}}`, 'g')\n let parts: Array<string> | null = hex.match(pattern)\n if (parts && parts[0].length === 1) parts = parts.map((part) => part + part)\n return parts\n ? `rgb${parts.length === 4 ? 'a' : ''}(${parts\n .map((part, index) =>\n index < 3\n ? parseInt(part, 16)\n : Math.round((parseInt(part, 16) / 255) * 1000) / 1000,\n )\n .join(', ')})`\n : ''\n}\n\nfunction decomposeColor(color: string): DecomposedColor {\n if (color.charAt(0) === '#') return decomposeColor(hexToRgb(color))\n const marker = color.indexOf('(')\n const type = color.substring(0, marker)\n if (!['rgb', 'rgba', 'hsl', 'hsla', 'color'].includes(type)) {\n throw new Error(`email-palette: unsupported color \"${color}\"`)\n }\n const body = color.substring(marker + 1, color.length - 1)\n let raw: Array<string>\n let colorSpace: string | undefined\n if (type === 'color') {\n raw = body.split(' ')\n colorSpace = raw.shift()\n if (raw.length === 4 && raw[3].charAt(0) === '/') raw[3] = raw[3].slice(1)\n if (\n !['srgb', 'display-p3', 'a98-rgb', 'prophoto-rgb', 'rec-2020'].includes(\n colorSpace ?? '',\n )\n ) {\n throw new Error(`email-palette: unsupported color space \"${colorSpace}\"`)\n }\n } else {\n raw = body.split(',')\n }\n return { type, values: raw.map((value) => parseFloat(value)), colorSpace }\n}\n\nfunction recomposeColor({ type, colorSpace, values }: DecomposedColor): string {\n let parts: Array<string | number> = values\n if (type.includes('rgb')) {\n parts = values.map((value, index) =>\n index < 3 ? parseInt(String(value), 10) : value,\n )\n } else if (type.includes('hsl')) {\n parts = [...values]\n parts[1] = `${values[1]}%`\n parts[2] = `${values[2]}%`\n }\n const body = type.includes('color')\n ? `${colorSpace} ${parts.join(' ')}`\n : parts.join(', ')\n return `${type}(${body})`\n}\n\nfunction intToHex(value: number): string {\n const hex = value.toString(16)\n return hex.length === 1 ? `0${hex}` : hex\n}\n\nfunction rgbToHex(color: string): string {\n if (color.startsWith('#')) return color\n const { values } = decomposeColor(color)\n return `#${values\n .map((value, index) => intToHex(index === 3 ? Math.round(255 * value) : value))\n .join('')}`\n}\n\nfunction hslToRgb(color: string | DecomposedColor): string {\n const decomposed = typeof color === 'string' ? decomposeColor(color) : color\n const { values } = decomposed\n const hue = values[0]\n const saturation = values[1] / 100\n const lightness = values[2] / 100\n const chroma = saturation * Math.min(lightness, 1 - lightness)\n const channel = (n: number, k = (n + hue / 30) % 12) =>\n lightness - chroma * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n const rgb = [\n Math.round(channel(0) * 255),\n Math.round(channel(8) * 255),\n Math.round(channel(4) * 255),\n ]\n let type = 'rgb'\n if (decomposed.type === 'hsla') {\n type += 'a'\n rgb.push(values[3])\n }\n return recomposeColor({ type, values: rgb })\n}\n\nfunction clampUnit(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\nfunction lighten(color: string, coefficient: number): string {\n const decomposed = decomposeColor(color)\n const amount = clampUnit(coefficient)\n const { type, values } = decomposed\n if (type.includes('hsl')) {\n values[2] += (100 - values[2]) * amount\n } else if (type.includes('rgb')) {\n for (let i = 0; i < 3; i += 1) values[i] += (255 - values[i]) * amount\n } else if (type.includes('color')) {\n for (let i = 0; i < 3; i += 1) values[i] += (1 - values[i]) * amount\n }\n return recomposeColor(decomposed)\n}\n\nfunction darken(color: string, coefficient: number): string {\n const decomposed = decomposeColor(color)\n const amount = clampUnit(coefficient)\n const { type, values } = decomposed\n if (type.includes('hsl')) {\n values[2] *= 1 - amount\n } else if (type.includes('rgb') || type.includes('color')) {\n for (let i = 0; i < 3; i += 1) values[i] *= 1 - amount\n }\n return recomposeColor(decomposed)\n}\n\nfunction srgbChannelToLinear(value: number): number {\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\n/** MUI's `getLuminance`: WCAG luminance, rounded to three places. */\nfunction muiLuminance(color: string): number {\n const decomposed = decomposeColor(color)\n const rgb =\n decomposed.type === 'hsl' || decomposed.type === 'hsla'\n ? decomposeColor(hslToRgb(decomposed)).values\n : decomposed.values\n const linear = rgb.map((value) =>\n srgbChannelToLinear(decomposed.type !== 'color' ? value / 255 : value),\n )\n return Number(\n (0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]).toFixed(3),\n )\n}\n\n/** MUI's `getContrastRatio`, on the rounded luminance. */\nfunction muiContrastRatio(foreground: string, background: string): number {\n const a = muiLuminance(foreground)\n const b = muiLuminance(background)\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)\n}\n\n// ── `accessible-shade.ts`, ported ──────────────────────────────────────────\n\nconst AA_TEXT_CONTRAST = 4.5\n\n/** Full-precision WCAG luminance, as `accessible-shade.ts` measures it. */\nfunction relativeLuminance(color: string): number {\n const decomposed = decomposeColor(color)\n if (decomposed.type === 'color') {\n throw new Error(`email-palette: unsupported color format \"${color}\"`)\n }\n const values = decomposed.type.startsWith('hsl')\n ? decomposeColor(hslToRgb(color)).values\n : decomposed.values\n return (\n 0.2126 * srgbChannelToLinear(values[0] / 255) +\n 0.7152 * srgbChannelToLinear(values[1] / 255) +\n 0.0722 * srgbChannelToLinear(values[2] / 255)\n )\n}\n\nfunction contrastRatio(colorA: string, colorB: string): number {\n const a = relativeLuminance(colorA)\n const b = relativeLuminance(colorB)\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)\n}\n\nfunction meetsContrast(\n foreground: string,\n backgrounds: ReadonlyArray<string>,\n): boolean {\n return backgrounds.every(\n (background) => contrastRatio(foreground, background) >= AA_TEXT_CONTRAST,\n )\n}\n\nfunction toHslValues(color: string): [number, number, number] {\n const decomposed = decomposeColor(color)\n if (decomposed.type.startsWith('hsl')) {\n const [h, s, l] = decomposed.values\n return [h, s / 100, l / 100]\n }\n const r = decomposed.values[0] / 255\n const g = decomposed.values[1] / 255\n const b = decomposed.values[2] / 255\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const lightness = (max + min) / 2\n if (max === min) return [0, 0, lightness]\n const delta = max - min\n const saturation =\n lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n let hue: number\n if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0)\n else if (max === g) hue = (b - r) / delta + 2\n else hue = (r - g) / delta + 4\n return [hue * 60, saturation, lightness]\n}\n\nfunction hslToHex(hue: number, saturation: number, lightness: number): string {\n return rgbToHex(\n hslToRgb(`hsl(${hue}, ${saturation * 100}%, ${lightness * 100}%)`),\n )\n}\n\n/** The nearest shade of `color`, walking lightness one way, that clears AA. */\nfunction accessibleShade(\n color: string,\n backgrounds: ReadonlyArray<string>,\n direction: 'darken' | 'lighten',\n): string {\n if (!backgrounds.length || meetsContrast(color, backgrounds)) return color\n const [hue, saturation, initialLightness] = toHslValues(color)\n const delta = direction === 'lighten' ? 0.01 : -0.01\n let lightness = initialLightness\n let candidate = color\n for (let i = 0; i < 120; i += 1) {\n lightness = Math.min(1, Math.max(0, lightness + delta))\n candidate = hslToHex(hue, saturation, lightness)\n if (meetsContrast(candidate, backgrounds)) return candidate\n if (lightness === 0 || lightness === 1) return candidate\n }\n return candidate\n}\n\n// ── Palette assembly ───────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** MUI's `deepmerge` with `clone: true`, for plain data. */\nfunction deepMerge(target: PaletteDraft, source: PaletteDraft): PaletteDraft {\n const output: PaletteDraft = { ...target }\n for (const key of Object.keys(source)) {\n const next = source[key]\n const current = target[key]\n if (\n isRecord(next) &&\n Object.prototype.hasOwnProperty.call(target, key) &&\n isRecord(current)\n ) {\n output[key] = deepMerge(current, next)\n } else {\n output[key] = isRecord(next) ? deepMerge({}, next) : next\n }\n }\n return output\n}\n\n/**\n * `createPalette`'s `augmentColor` for an object with a `main`: fill `light`\n * and `dark` from the tonal offset, and `contrastText` by MUI's threshold.\n */\nfunction augmentColor(color: ColorRecord): ColorRecord {\n const augmented = { ...color }\n const main = augmented['main']\n if (typeof main !== 'string') {\n throw new Error('email-palette: a palette color needs a string `main`')\n }\n if (!augmented['light']) augmented['light'] = lighten(main, TONAL_OFFSET_LIGHT)\n if (!augmented['dark']) augmented['dark'] = darken(main, TONAL_OFFSET_DARK)\n if (!augmented['contrastText']) {\n augmented['contrastText'] =\n muiContrastRatio(main, MUI_COMMON.white) >= MUI_CONTRAST_THRESHOLD\n ? MUI_COMMON.white\n : MUI_DARK_INK\n }\n return augmented\n}\n\n/**\n * `createResponsiveTheme`'s `addShadeVariants`, for the two colors MUI does\n * not know about. Its contrast pick is handed the DARK tonal offset (0.3) as\n * the threshold, so it lands on white for any color at all; the port keeps\n * that, because the parity it owes is to what the site renders, and any\n * pairing below AA is repaired by the accessibility pass that follows.\n */\nfunction addShadeVariants(color: unknown): void {\n if (!isRecord(color) || !color['main']) return\n const main = color['main'] as string\n if (!color['dark']) color['dark'] = darken(main, TONAL_OFFSET_DARK)\n if (!color['light']) color['light'] = lighten(main, TONAL_OFFSET_LIGHT)\n if (!color['contrastText']) {\n color['contrastText'] =\n muiContrastRatio(main, '#FFFFFF') >= TONAL_OFFSET_DARK\n ? '#FFFFFF'\n : 'rgba(0, 0, 0, 0.87)'\n }\n}\n\n/**\n * `ensureAccessibleShades` for the light scheme: a DERIVED `dark` that misses\n * 4.5:1 on either background is walked darker, and a derived `contrastText`\n * that misses 4.5:1 on its `main` is walked toward the pole with more room.\n * Whatever the input authored passes through untouched.\n */\nfunction ensureAccessibleShades(\n palette: PaletteDraft,\n input: PaletteDraft,\n): void {\n const background = isRecord(palette['background']) ? palette['background'] : {}\n const backgrounds = [background['default'], background['paper']].filter(\n (value): value is string => typeof value === 'string',\n )\n if (!backgrounds.length) return\n\n for (const key of CONTRAST_TEXT_COLOR_KEYS) {\n const color = palette[key]\n if (!isRecord(color) || typeof color['main'] !== 'string') continue\n const provided = input[key]\n if (isRecord(provided) && !('main' in provided)) continue\n const explicit = isRecord(provided) ? provided : {}\n const main = color['main']\n\n try {\n if (\n FOREGROUND_COLOR_KEYS.includes(key) &&\n !explicit['dark'] &&\n typeof color['dark'] === 'string' &&\n !meetsContrast(color['dark'], backgrounds)\n ) {\n color['dark'] = accessibleShade(color['dark'], backgrounds, 'darken')\n }\n if (\n !explicit['contrastText'] &&\n typeof color['contrastText'] === 'string' &&\n !meetsContrast(color['contrastText'], [main])\n ) {\n const direction =\n contrastRatio('#fff', main) >= contrastRatio('#000', main)\n ? 'lighten'\n : 'darken'\n color['contrastText'] = accessibleShade(\n color['contrastText'],\n [main],\n direction,\n )\n }\n } catch {\n // An unparseable color is left as derived, as the theme leaves it.\n }\n }\n}\n\n/** `createTheme` + `createResponsiveTheme`'s passes, palette only. */\nfunction derivePalette(input: PaletteDraft): PaletteDraft {\n const rest: PaletteDraft = { ...input }\n delete rest['mode']\n delete rest['contrastThreshold']\n delete rest['tonalOffset']\n\n const augmented: PaletteDraft = {}\n for (const key of MUI_AUGMENTED_KEYS) {\n const color = input[key]\n if (!isRecord(color)) {\n throw new Error(`email-palette: the base palette is missing \\`${key}\\``)\n }\n augmented[key] = augmentColor(color)\n }\n\n const palette = deepMerge(\n {\n common: { ...MUI_COMMON },\n mode: 'light',\n ...augmented,\n grey: MUI_GREY,\n ...muiLightDefaults(),\n },\n rest,\n )\n addShadeVariants(palette['tertiary'])\n addShadeVariants(palette['surface'])\n ensureAccessibleShades(palette, input)\n return palette\n}\n\n// ── A site's colors, as `hostThemeToThemeOptions` reads them ───────────────\n\nconst SCHEME_COLOR_KEYS = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'surface',\n 'error',\n 'warning',\n 'info',\n 'success',\n] as const\n\n/** Truthy string, or nothing: the \"copy what was set\" test the theme uses. */\nfunction setString(value: unknown): string | undefined {\n return typeof value === 'string' && value ? value : undefined\n}\n\n/** `pickPaletteColor`: a color needs a `main`; its shades ride along if set. */\nfunction pickPaletteColor(color: unknown): ColorRecord | undefined {\n if (!isRecord(color)) return undefined\n const main = setString(color['main'])\n if (!main) return undefined\n const picked: ColorRecord = { main }\n for (const shade of ['light', 'dark', 'contrastText'] as const) {\n const value = setString(color[shade])\n if (value) picked[shade] = value\n }\n return picked\n}\n\n/** Picks the listed string leaves of a group that were set, or nothing. */\nfunction pickLeaves(\n group: unknown,\n keys: ReadonlyArray<string>,\n): Record<string, string> | undefined {\n if (!isRecord(group)) return undefined\n const picked: Record<string, string> = {}\n for (const key of keys) {\n const value = setString(group[key])\n if (value) picked[key] = value\n }\n return Object.keys(picked).length ? picked : undefined\n}\n\n/** `schemeColorsToPaletteOptions` for the light scheme. */\nfunction siteColorOverrides(\n input: EmailPaletteInput['colors'],\n): PaletteDraft {\n const overrides: PaletteDraft = { mode: 'light' }\n if (!isRecord(input)) return overrides\n const colors: Readonly<Record<string, unknown>> = input\n for (const key of SCHEME_COLOR_KEYS) {\n const color = pickPaletteColor(colors[key])\n if (color) overrides[key] = color\n }\n const background = pickLeaves(colors['background'], ['default', 'paper'])\n if (background) overrides['background'] = background\n const text = pickLeaves(colors['text'], ['primary', 'secondary', 'disabled'])\n if (text) overrides['text'] = text\n const tint = pickLeaves(colors['tint'], ['primary', 'secondary', 'tertiary'])\n if (tint) overrides['tint'] = tint\n const divider = setString(colors['divider'])\n if (divider) overrides['divider'] = divider\n return overrides\n}\n\n// ── Literal CSS colors ─────────────────────────────────────────────────────\n\nconst HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\nconst NUMBER = String.raw`[-+]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?(?:%|deg|grad|rad|turn)?`\n/**\n * `rgb()`, `rgba()`, `hsl()`, `hsla()` holding numbers and nothing else —\n * comma or space separated, with an optional `/ alpha`. Anything that could\n * close the declaration or open another (`;`, `\"`, `(`, `url`) cannot match.\n */\nconst FUNCTION_COLOR = new RegExp(\n String.raw`^(?:rgba?|hsla?)\\(\\s*${NUMBER}(?:\\s*,\\s*|\\s+)${NUMBER}(?:\\s*,\\s*|\\s+)${NUMBER}(?:(?:\\s*,\\s*|\\s*\\/\\s*)${NUMBER})?\\s*\\)$`,\n 'i',\n)\n/** A color MUI's own manipulators parse: hex, or a comma-separated function. */\nconst MUI_PARSEABLE_FUNCTION = new RegExp(\n String.raw`^(?:rgba?|hsla?)\\(\\s*${NUMBER}\\s*,\\s*${NUMBER}\\s*,\\s*${NUMBER}(?:\\s*,\\s*${NUMBER})?\\s*\\)$`,\n 'i',\n)\n\n/** CSS Color Module Level 4 named colors, plus `transparent`. */\nconst NAMED_COLORS: ReadonlySet<string> = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace ' +\n 'olive olivedrab orange orangered orchid palegoldenrod palegreen ' +\n 'paleturquoise palevioletred papayawhip peachpuff peru pink plum ' +\n 'powderblue purple rebeccapurple red rosybrown royalblue saddlebrown ' +\n 'salmon sandybrown seagreen seashell sienna silver skyblue slateblue ' +\n 'slategray slategrey snow springgreen steelblue tan teal thistle tomato ' +\n 'turquoise violet wheat white whitesmoke yellow yellowgreen transparent'\n ).split(' '),\n)\n\n/** True for a literal CSS color that is safe to write into a style attribute. */\nfunction isLiteralColor(value: string): boolean {\n return (\n HEX_COLOR.test(value) ||\n FUNCTION_COLOR.test(value) ||\n NAMED_COLORS.has(value.toLowerCase())\n )\n}\n\n/** True for a color MUI can derive shades from without throwing. */\nfunction isDerivableColor(value: string): boolean {\n return HEX_COLOR.test(value) || MUI_PARSEABLE_FUNCTION.test(value)\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * The fully derived light palette a site or sender renders with: the\n * chosen base, the site's authored colors layered one level deep (a color's\n * whole record replaced, so MUI re-derives its shades from the new `main`),\n * then a white-label `primaryColor` over that.\n *\n * Equal, for every token the Besigner's color picker offers, to the palette\n * of `createResponsiveTheme` over `mergeThemeOptions(siteBaseOptions(…))`\n * and the host theme's light `hostThemeToThemeOptions`.\n *\n * Where the site's colors cannot be derived at all — a `main` MUI cannot\n * parse, on which `createTheme` itself throws — the email falls back to the\n * base rather than failing the send: there is no rendered page to agree with.\n */\nexport function buildEmailPalette(input: EmailPaletteInput): EmailPalette {\n const base =\n input.base === 'platform' ? PLATFORM_LIGHT_INPUT : TENANT_LIGHT_INPUT\n const primaryColor =\n typeof input.primaryColor === 'string' &&\n isDerivableColor(input.primaryColor.trim())\n ? input.primaryColor.trim()\n : undefined\n const brand: PaletteDraft = primaryColor\n ? { primary: { main: primaryColor } }\n : {}\n\n const layered = { ...base, ...siteColorOverrides(input.colors), ...brand }\n try {\n return derivePalette(layered)\n } catch {\n return derivePalette({ ...base, ...brand })\n }\n}\n\n/** The platform's own palette — the console brand, light scheme. */\nexport const PLATFORM_EMAIL_PALETTE: EmailPalette = buildEmailPalette({\n base: 'platform',\n})\n\n/** Palette token paths: a key, optionally one dot and a leaf (`grey.600`). */\nconst TOKEN_PATH = /^[a-zA-Z][a-zA-Z0-9]*(?:\\.[a-zA-Z0-9]+)?$/\n\nfunction lookupToken(\n palette: EmailPalette,\n path: string,\n): string | undefined {\n let node: unknown = palette\n for (const key of path.split('.')) {\n if (!isRecord(node) || !Object.prototype.hasOwnProperty.call(node, key)) {\n return undefined\n }\n node = node[key]\n }\n return typeof node === 'string' ? node : undefined\n}\n\n/**\n * A palette value as an opaque `#rrggbb`. Translucent values (`text.primary`\n * is `rgba(0, 0, 0, 0.87)`, some `contrastText`s are `#000000DE`) are\n * composited over the palette's paper, the ground an email body sits on:\n * Outlook's Word engine drops `rgba()` and eight-digit hex outright, and the\n * flattened color is what a reader sees on that ground anyway.\n */\nfunction toOpaqueHex(value: string, palette: EmailPalette): string | undefined {\n const channels = rgbaChannels(value)\n if (!channels) return undefined\n const [r, g, b, alpha] = channels\n if (alpha >= 1) return channelsToHex(r, g, b)\n const background = palette['background']\n const paper = isRecord(background) ? background['paper'] : undefined\n const ground =\n (typeof paper === 'string' ? rgbaChannels(paper) : undefined) ?? [\n 255, 255, 255, 1,\n ]\n const over = (fore: number, back: number) =>\n fore * alpha + back * (1 - alpha)\n return channelsToHex(over(r, ground[0]), over(g, ground[1]), over(b, ground[2]))\n}\n\nfunction rgbaChannels(\n value: string,\n): [number, number, number, number] | undefined {\n if (!isDerivableColor(value)) return undefined\n try {\n const decomposed = decomposeColor(value)\n const rgb = decomposed.type.startsWith('hsl')\n ? decomposeColor(hslToRgb(decomposed)).values\n : decomposed.values\n const alpha = decomposed.values.length > 3 ? decomposed.values[3] : 1\n const channels: [number, number, number, number] = [\n rgb[0],\n rgb[1],\n rgb[2],\n alpha,\n ]\n return channels.every(Number.isFinite) ? channels : undefined\n } catch {\n return undefined\n }\n}\n\nfunction channelsToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b]\n .map((value) => intToHex(Math.round(Math.min(255, Math.max(0, value)))))\n .join('')}`\n}\n\n/**\n * Resolves a stored color prop for an email's markup.\n *\n * - A palette token path (`primary.main`, `grey.600`, `divider`) resolves\n * against `palette` to an opaque hex — or to nothing without a palette.\n * - A literal CSS color (hex, `rgb()`/`rgba()`/`hsl()`/`hsla()`,\n * `transparent`, a named color) passes through as written.\n * - Anything else — an unknown token, garbage, an empty value, and anything\n * that could step outside a `style` attribute — is `undefined`, so the\n * caller uses its own default.\n */\nexport function resolveEmailColor(\n value: unknown,\n palette: EmailPalette | undefined,\n): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed) return undefined\n\n if (palette && TOKEN_PATH.test(trimmed)) {\n const resolved = lookupToken(palette, trimmed)\n if (resolved !== undefined) {\n const hex = toOpaqueHex(resolved, palette)\n if (hex) return hex\n if (isLiteralColor(resolved)) return resolved\n return undefined\n }\n }\n return isLiteralColor(trimmed) ? trimmed : undefined\n}\n\n// ── Which base a site wears ────────────────────────────────────────────────\n\n/**\n * The prefix the tenant middleware puts on a custom domain before it becomes\n * the `[host]` route segment — mirrored from `tenant.theme.ts`.\n */\nconst CNAME_PREFIX = 'cname--'\n\n/**\n * The hosts whose brand is the operator's own, mirrored from\n * `PLATFORM_BRAND_HOSTS` in `tenant.theme.ts` and read from the same\n * variable. Dot notation for the same reason it has there: Next substitutes\n * `process.env.NAME` textually and never the bracket form.\n */\nconst PLATFORM_BRAND_HOSTS: ReadonlySet<string> = new Set(\n (process.env.NEXT_PUBLIC_PLATFORM_BRAND_HOSTS ?? 'aglyn.com,aglyn.io')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n)\n\n/**\n * Which base a site's mail layers onto: `'platform'` for the operator's own\n * hosts, `'tenant'` for every customer site — the answer\n * `wearsPlatformBrand(hostBrandKey(host))` gives the page itself, so a site's\n * email and its published page start from the same palette.\n */\nexport function emailPaletteBaseForHost(\n host: { cname?: string | null; subdomain?: string | null } | null | undefined,\n): 'platform' | 'tenant' {\n const key = host?.cname || host?.subdomain || undefined\n if (!key) return 'tenant'\n const normalized = key.trim().toLowerCase()\n const bare = normalized.startsWith(CNAME_PREFIX)\n ? normalized.slice(CNAME_PREFIX.length)\n : normalized\n return PLATFORM_BRAND_HOSTS.has(bare) ? 'platform' : 'tenant'\n}\n"],"names":["process","BRAND_GREY","A100","A200","A400","A700","PLATFORM_LIGHT_INPUT","mode","primary","main","dark","contrastText","secondary","tertiary","surface","tint","inputOutline","background","default","paper","info","error","success","warning","grey","TENANT_LIGHT_INPUT","MUI_COMMON","black","white","MUI_GREY","muiLightDefaults","text","disabled","divider","MUI_DARK_INK","MUI_CONTRAST_THRESHOLD","TONAL_OFFSET_LIGHT","TONAL_OFFSET_DARK","MUI_AUGMENTED_KEYS","FOREGROUND_COLOR_KEYS","CONTRAST_TEXT_COLOR_KEYS","hexToRgb","color","hex","slice","pattern","RegExp","length","parts","match","map","part","index","parseInt","Math","round","join","decomposeColor","charAt","marker","indexOf","type","substring","includes","Error","body","raw","colorSpace","split","shift","values","value","parseFloat","recomposeColor","String","intToHex","toString","rgbToHex","startsWith","hslToRgb","decomposed","hue","saturation","lightness","chroma","min","channel","n","k","max","rgb","push","clampUnit","lighten","coefficient","amount","i","darken","srgbChannelToLinear","muiLuminance","linear","Number","toFixed","muiContrastRatio","foreground","a","b","AA_TEXT_CONTRAST","relativeLuminance","contrastRatio","colorA","colorB","meetsContrast","backgrounds","every","toHslValues","h","s","l","r","g","delta","hslToHex","accessibleShade","direction","initialLightness","candidate","isRecord","Array","isArray","deepMerge","target","source","output","key","Object","keys","next","current","prototype","hasOwnProperty","call","augmentColor","augmented","addShadeVariants","ensureAccessibleShades","palette","input","filter","provided","explicit","derivePalette","rest","common","SCHEME_COLOR_KEYS","setString","undefined","pickPaletteColor","picked","shade","pickLeaves","group","siteColorOverrides","overrides","colors","HEX_COLOR","NUMBER","FUNCTION_COLOR","MUI_PARSEABLE_FUNCTION","NAMED_COLORS","Set","isLiteralColor","test","has","toLowerCase","isDerivableColor","buildEmailPalette","base","primaryColor","trim","brand","layered","PLATFORM_EMAIL_PALETTE","TOKEN_PATH","lookupToken","path","node","toOpaqueHex","channels","rgbaChannels","alpha","channelsToHex","ground","over","fore","back","isFinite","resolveEmailColor","trimmed","resolved","CNAME_PREFIX","PLATFORM_BRAND_HOSTS","env","NEXT_PUBLIC_PLATFORM_BRAND_HOSTS","host","Boolean","emailPaletteBaseForHost","cname","subdomain","normalized","bare"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/email-palette.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\n/**\n * The palette an email is painted with (AGL-3370).\n *\n * A color an author picks in the Besigner is stored as a palette TOKEN PATH\n * (`primary.main`, `grey.600`), which MUI resolves on a web page and a mail\n * client never will. So an email needs the same palette the site renders\n * with, as plain data, before it writes a single `style` attribute.\n *\n * That palette is built by `@aglyn/shared-ui-theme` — MUI's `createTheme`,\n * then `createResponsiveTheme`'s shade and accessibility passes — and this\n * lib may not import it: a `type:util` package cannot stand on a `type:ui`\n * one, and a server rendering mail has no business loading MUI to do it. What\n * follows is that derivation ported, light scheme only, with the INPUT colors\n * of the two bases copied and everything else computed the way MUI computes\n * it. `email-palette-parity.spec.ts` in `shared-ui-theme` builds both and\n * holds them equal, so the copy cannot drift without a red.\n */\n\n/**\n * A light-scheme MUI palette as plain data: `primary`, `secondary`,\n * `tertiary`, `surface`, `error`, `warning`, `info`, `success` (each\n * `{ main, light, dark, contrastText }`), `background`, `text`, `tint`,\n * `divider`, `inputOutline`, `grey` and `common`.\n */\nexport type EmailPalette = Readonly<Record<string, unknown>>\n\n/** One authored palette color: a `main`, and any shade the author also set. */\nexport interface EmailPaletteColorInput {\n main?: string\n light?: string\n dark?: string\n contrastText?: string\n}\n\n/**\n * The shape of `HostThemeSchemeColors` (`@aglyn/shared-data-types`), spelled\n * out so this lib needs no dependency on the types package for one shape. An\n * interface has no index signature, so without this a site's typed colors\n * would not be assignable to a plain record.\n */\nexport interface EmailPaletteSchemeColors {\n primary?: EmailPaletteColorInput\n secondary?: EmailPaletteColorInput\n tertiary?: EmailPaletteColorInput\n surface?: EmailPaletteColorInput\n error?: EmailPaletteColorInput\n warning?: EmailPaletteColorInput\n info?: EmailPaletteColorInput\n success?: EmailPaletteColorInput\n background?: { default?: string; paper?: string }\n text?: { primary?: string; secondary?: string; disabled?: string }\n tint?: { primary?: string; secondary?: string; tertiary?: string }\n divider?: string\n}\n\nexport interface EmailPaletteInput {\n /**\n * The base a site or sender layers onto: `'platform'` is the console brand\n * (`consoleOptions`), `'tenant'` the neutral default a customer site wears\n * (`tenantOptions`). {@link emailPaletteBaseForHost} picks it for a site.\n */\n base: 'platform' | 'tenant'\n /**\n * A site's authored light-scheme colors — `host.theme.colorSchemes.light`.\n * Stored data, so every leaf is checked as it is read; a plain record is\n * accepted as readily as the typed shape.\n */\n colors?: EmailPaletteSchemeColors | Readonly<Record<string, unknown>> | null\n /**\n * A white-label org's brand color. It becomes `primary.main` and the whole\n * `primary` record is re-derived from it, exactly as a theme given\n * `palette.primary = { main }` would. Ignored unless it parses as a color.\n */\n primaryColor?: string | null\n}\n\ntype ColorRecord = Record<string, unknown>\ntype PaletteDraft = Record<string, unknown>\n\n// ── The two bases' authored inputs ─────────────────────────────────────────\n//\n// Copied from `console.theme.ts` (`colorScheme.light`) and `tenant.theme.ts`\n// (`tenantColorScheme.light`), minus the `svg*` records no email reads. Only\n// what those files AUTHOR is here; every shade they leave to MUI is derived\n// below, so the copy stays as short as the thing it mirrors.\n\nconst BRAND_GREY = {\n 50: '#FAFAFA',\n 100: '#F5F5F5',\n 200: '#EEEEEE',\n 300: '#E0E0E0',\n 400: '#BDBDBD',\n 500: '#9E9E9E',\n 600: '#757575',\n 700: '#616161',\n 800: '#424242',\n 900: '#212121',\n A100: '#D5D5D5',\n A200: '#AAAAAA',\n A400: '#303030',\n A700: '#616161',\n}\n\nconst PLATFORM_LIGHT_INPUT: PaletteDraft = {\n mode: 'light',\n primary: { main: '#00b0ff', dark: '#0077ad', contrastText: '#FFFFFF' },\n secondary: { main: '#e040fb', dark: '#9d2db0', contrastText: '#FFFFFF' },\n tertiary: { main: '#404C5C', contrastText: '#FFFFFF' },\n surface: { main: '#F8F9FA', contrastText: '#000000' },\n tint: { primary: '#E6F5FF', secondary: '#FBE6FE', tertiary: '#EEF0F2' },\n inputOutline: 'rgba(0, 0, 0, 0.23)',\n background: { default: '#F5F5F5', paper: '#FFFFFF' },\n info: { main: '#1878cd', contrastText: '#FFFFFF' },\n error: { main: '#e32c27', contrastText: '#FFFFFF' },\n success: { main: '#4CAF50', contrastText: '#000000DE' },\n warning: { main: '#FFAB40', contrastText: '#000000DE' },\n grey: BRAND_GREY,\n}\n\nconst TENANT_LIGHT_INPUT: PaletteDraft = {\n mode: 'light',\n primary: { main: '#1976d2', dark: '#125393', contrastText: '#FFFFFF' },\n secondary: { main: '#9c27b0', dark: '#6d1b7b', contrastText: '#FFFFFF' },\n tertiary: { main: '#4a5568', dark: '#343b49', contrastText: '#FFFFFF' },\n surface: { main: '#f1f3f5', contrastText: '#000000DE' },\n tint: { primary: '#e8f1fb', secondary: '#f5e9f7', tertiary: '#eef0f3' },\n inputOutline: 'rgba(0, 0, 0, 0.23)',\n background: { default: '#fafafa', paper: '#ffffff' },\n info: { main: '#0288d1', dark: '#015f92', contrastText: '#000000DE' },\n error: { main: '#d32f2f', dark: '#942121', contrastText: '#FFFFFF' },\n success: { main: '#2e7d32', dark: '#205823', contrastText: '#FFFFFF' },\n warning: { main: '#ed6c02', dark: '#a64c01', contrastText: '#000000DE' },\n grey: BRAND_GREY,\n}\n\n// ── MUI's light-mode constants (`createPalette`, `colors/*`) ───────────────\n\nconst MUI_COMMON = { black: '#000', white: '#fff' }\n\nconst MUI_GREY = {\n 50: '#fafafa',\n 100: '#f5f5f5',\n 200: '#eeeeee',\n 300: '#e0e0e0',\n 400: '#bdbdbd',\n 500: '#9e9e9e',\n 600: '#757575',\n 700: '#616161',\n 800: '#424242',\n 900: '#212121',\n A100: '#f5f5f5',\n A200: '#eeeeee',\n A400: '#bdbdbd',\n A700: '#616161',\n}\n\n/** `getLight()` — the mode-hydrated slots, minus `action` (no email reads it). */\nfunction muiLightDefaults(): PaletteDraft {\n return {\n text: {\n primary: 'rgba(0, 0, 0, 0.87)',\n secondary: 'rgba(0, 0, 0, 0.6)',\n disabled: 'rgba(0, 0, 0, 0.38)',\n },\n divider: 'rgba(0, 0, 0, 0.12)',\n background: { paper: MUI_COMMON.white, default: MUI_COMMON.white },\n }\n}\n\n/** MUI's `light.text.primary`, the dark ink `getContrastText` falls back to. */\nconst MUI_DARK_INK = 'rgba(0, 0, 0, 0.87)'\nconst MUI_CONTRAST_THRESHOLD = 3\n/** MUI's default `tonalOffset` of 0.2, split the way `addLightOrDark` splits it. */\nconst TONAL_OFFSET_LIGHT = 0.2\nconst TONAL_OFFSET_DARK = 0.2 * 1.5\n\n/** The six colors `createPalette` augments. Both bases author every one. */\nconst MUI_AUGMENTED_KEYS = [\n 'primary',\n 'secondary',\n 'error',\n 'warning',\n 'info',\n 'success',\n] as const\n\n/** `createResponsiveTheme`'s FOREGROUND_COLOR_KEYS: `dark` is accent text. */\nconst FOREGROUND_COLOR_KEYS: ReadonlyArray<string> = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'error',\n 'warning',\n 'info',\n 'success',\n]\n/** …and `contrastText` is text wherever it lives, so `surface` joins. */\nconst CONTRAST_TEXT_COLOR_KEYS: ReadonlyArray<string> = [\n ...FOREGROUND_COLOR_KEYS,\n 'surface',\n]\n\n// ── MUI's colorManipulator, ported ─────────────────────────────────────────\n//\n// Byte-for-byte in behavior, including the parts that look odd: `recompose`\n// truncates rgb channels with `parseInt`, and `getLuminance` rounds to three\n// places. The palette's derived shades are strings, so a port that rounded\n// \"better\" would be a port that disagrees.\n\ninterface DecomposedColor {\n type: string\n values: Array<number>\n colorSpace?: string\n}\n\nfunction hexToRgb(color: string): string {\n const hex = color.slice(1)\n const pattern = new RegExp(`.{1,${hex.length >= 6 ? 2 : 1}}`, 'g')\n let parts: Array<string> | null = hex.match(pattern)\n if (parts && parts[0].length === 1) parts = parts.map((part) => part + part)\n return parts\n ? `rgb${parts.length === 4 ? 'a' : ''}(${parts\n .map((part, index) =>\n index < 3\n ? parseInt(part, 16)\n : Math.round((parseInt(part, 16) / 255) * 1000) / 1000,\n )\n .join(', ')})`\n : ''\n}\n\nfunction decomposeColor(color: string): DecomposedColor {\n if (color.charAt(0) === '#') return decomposeColor(hexToRgb(color))\n const marker = color.indexOf('(')\n const type = color.substring(0, marker)\n if (!['rgb', 'rgba', 'hsl', 'hsla', 'color'].includes(type)) {\n throw new Error(`email-palette: unsupported color \"${color}\"`)\n }\n const body = color.substring(marker + 1, color.length - 1)\n let raw: Array<string>\n let colorSpace: string | undefined\n if (type === 'color') {\n raw = body.split(' ')\n colorSpace = raw.shift()\n if (raw.length === 4 && raw[3].charAt(0) === '/') raw[3] = raw[3].slice(1)\n if (\n !['srgb', 'display-p3', 'a98-rgb', 'prophoto-rgb', 'rec-2020'].includes(\n colorSpace ?? '',\n )\n ) {\n throw new Error(`email-palette: unsupported color space \"${colorSpace}\"`)\n }\n } else {\n raw = body.split(',')\n }\n return { type, values: raw.map((value) => parseFloat(value)), colorSpace }\n}\n\nfunction recomposeColor({ type, colorSpace, values }: DecomposedColor): string {\n let parts: Array<string | number> = values\n if (type.includes('rgb')) {\n parts = values.map((value, index) =>\n index < 3 ? parseInt(String(value), 10) : value,\n )\n } else if (type.includes('hsl')) {\n parts = [...values]\n parts[1] = `${values[1]}%`\n parts[2] = `${values[2]}%`\n }\n const body = type.includes('color')\n ? `${colorSpace} ${parts.join(' ')}`\n : parts.join(', ')\n return `${type}(${body})`\n}\n\nfunction intToHex(value: number): string {\n const hex = value.toString(16)\n return hex.length === 1 ? `0${hex}` : hex\n}\n\nfunction rgbToHex(color: string): string {\n if (color.startsWith('#')) return color\n const { values } = decomposeColor(color)\n return `#${values\n .map((value, index) => intToHex(index === 3 ? Math.round(255 * value) : value))\n .join('')}`\n}\n\nfunction hslToRgb(color: string | DecomposedColor): string {\n const decomposed = typeof color === 'string' ? decomposeColor(color) : color\n const { values } = decomposed\n const hue = values[0]\n const saturation = values[1] / 100\n const lightness = values[2] / 100\n const chroma = saturation * Math.min(lightness, 1 - lightness)\n const channel = (n: number, k = (n + hue / 30) % 12) =>\n lightness - chroma * Math.max(Math.min(k - 3, 9 - k, 1), -1)\n const rgb = [\n Math.round(channel(0) * 255),\n Math.round(channel(8) * 255),\n Math.round(channel(4) * 255),\n ]\n let type = 'rgb'\n if (decomposed.type === 'hsla') {\n type += 'a'\n rgb.push(values[3])\n }\n return recomposeColor({ type, values: rgb })\n}\n\nfunction clampUnit(value: number): number {\n return Math.min(1, Math.max(0, value))\n}\n\nfunction lighten(color: string, coefficient: number): string {\n const decomposed = decomposeColor(color)\n const amount = clampUnit(coefficient)\n const { type, values } = decomposed\n if (type.includes('hsl')) {\n values[2] += (100 - values[2]) * amount\n } else if (type.includes('rgb')) {\n for (let i = 0; i < 3; i += 1) values[i] += (255 - values[i]) * amount\n } else if (type.includes('color')) {\n for (let i = 0; i < 3; i += 1) values[i] += (1 - values[i]) * amount\n }\n return recomposeColor(decomposed)\n}\n\nfunction darken(color: string, coefficient: number): string {\n const decomposed = decomposeColor(color)\n const amount = clampUnit(coefficient)\n const { type, values } = decomposed\n if (type.includes('hsl')) {\n values[2] *= 1 - amount\n } else if (type.includes('rgb') || type.includes('color')) {\n for (let i = 0; i < 3; i += 1) values[i] *= 1 - amount\n }\n return recomposeColor(decomposed)\n}\n\nfunction srgbChannelToLinear(value: number): number {\n return value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4\n}\n\n/** MUI's `getLuminance`: WCAG luminance, rounded to three places. */\nfunction muiLuminance(color: string): number {\n const decomposed = decomposeColor(color)\n const rgb =\n decomposed.type === 'hsl' || decomposed.type === 'hsla'\n ? decomposeColor(hslToRgb(decomposed)).values\n : decomposed.values\n const linear = rgb.map((value) =>\n srgbChannelToLinear(decomposed.type !== 'color' ? value / 255 : value),\n )\n return Number(\n (0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]).toFixed(3),\n )\n}\n\n/** MUI's `getContrastRatio`, on the rounded luminance. */\nfunction muiContrastRatio(foreground: string, background: string): number {\n const a = muiLuminance(foreground)\n const b = muiLuminance(background)\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)\n}\n\n// ── `accessible-shade.ts`, ported ──────────────────────────────────────────\n\nconst AA_TEXT_CONTRAST = 4.5\n\n/** Full-precision WCAG luminance, as `accessible-shade.ts` measures it. */\nfunction relativeLuminance(color: string): number {\n const decomposed = decomposeColor(color)\n if (decomposed.type === 'color') {\n throw new Error(`email-palette: unsupported color format \"${color}\"`)\n }\n const values = decomposed.type.startsWith('hsl')\n ? decomposeColor(hslToRgb(color)).values\n : decomposed.values\n return (\n 0.2126 * srgbChannelToLinear(values[0] / 255) +\n 0.7152 * srgbChannelToLinear(values[1] / 255) +\n 0.0722 * srgbChannelToLinear(values[2] / 255)\n )\n}\n\nfunction contrastRatio(colorA: string, colorB: string): number {\n const a = relativeLuminance(colorA)\n const b = relativeLuminance(colorB)\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)\n}\n\nfunction meetsContrast(\n foreground: string,\n backgrounds: ReadonlyArray<string>,\n): boolean {\n return backgrounds.every(\n (background) => contrastRatio(foreground, background) >= AA_TEXT_CONTRAST,\n )\n}\n\nfunction toHslValues(color: string): [number, number, number] {\n const decomposed = decomposeColor(color)\n if (decomposed.type.startsWith('hsl')) {\n const [h, s, l] = decomposed.values\n return [h, s / 100, l / 100]\n }\n const r = decomposed.values[0] / 255\n const g = decomposed.values[1] / 255\n const b = decomposed.values[2] / 255\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const lightness = (max + min) / 2\n if (max === min) return [0, 0, lightness]\n const delta = max - min\n const saturation =\n lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min)\n let hue: number\n if (max === r) hue = (g - b) / delta + (g < b ? 6 : 0)\n else if (max === g) hue = (b - r) / delta + 2\n else hue = (r - g) / delta + 4\n return [hue * 60, saturation, lightness]\n}\n\nfunction hslToHex(hue: number, saturation: number, lightness: number): string {\n return rgbToHex(\n hslToRgb(`hsl(${hue}, ${saturation * 100}%, ${lightness * 100}%)`),\n )\n}\n\n/** The nearest shade of `color`, walking lightness one way, that clears AA. */\nfunction accessibleShade(\n color: string,\n backgrounds: ReadonlyArray<string>,\n direction: 'darken' | 'lighten',\n): string {\n if (!backgrounds.length || meetsContrast(color, backgrounds)) return color\n const [hue, saturation, initialLightness] = toHslValues(color)\n const delta = direction === 'lighten' ? 0.01 : -0.01\n let lightness = initialLightness\n let candidate = color\n for (let i = 0; i < 120; i += 1) {\n lightness = Math.min(1, Math.max(0, lightness + delta))\n candidate = hslToHex(hue, saturation, lightness)\n if (meetsContrast(candidate, backgrounds)) return candidate\n if (lightness === 0 || lightness === 1) return candidate\n }\n return candidate\n}\n\n// ── Palette assembly ───────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** MUI's `deepmerge` with `clone: true`, for plain data. */\nfunction deepMerge(target: PaletteDraft, source: PaletteDraft): PaletteDraft {\n const output: PaletteDraft = { ...target }\n for (const key of Object.keys(source)) {\n const next = source[key]\n const current = target[key]\n if (\n isRecord(next) &&\n Object.prototype.hasOwnProperty.call(target, key) &&\n isRecord(current)\n ) {\n output[key] = deepMerge(current, next)\n } else {\n output[key] = isRecord(next) ? deepMerge({}, next) : next\n }\n }\n return output\n}\n\n/**\n * `createPalette`'s `augmentColor` for an object with a `main`: fill `light`\n * and `dark` from the tonal offset, and `contrastText` by MUI's threshold.\n */\nfunction augmentColor(color: ColorRecord): ColorRecord {\n const augmented = { ...color }\n const main = augmented['main']\n if (typeof main !== 'string') {\n throw new Error('email-palette: a palette color needs a string `main`')\n }\n if (!augmented['light']) augmented['light'] = lighten(main, TONAL_OFFSET_LIGHT)\n if (!augmented['dark']) augmented['dark'] = darken(main, TONAL_OFFSET_DARK)\n if (!augmented['contrastText']) {\n augmented['contrastText'] =\n muiContrastRatio(main, MUI_COMMON.white) >= MUI_CONTRAST_THRESHOLD\n ? MUI_COMMON.white\n : MUI_DARK_INK\n }\n return augmented\n}\n\n/**\n * `createResponsiveTheme`'s `addShadeVariants`, for the two colors MUI does\n * not know about. Its contrast pick is handed the DARK tonal offset (0.3) as\n * the threshold, so it lands on white for any color at all; the port keeps\n * that, because the parity it owes is to what the site renders, and any\n * pairing below AA is repaired by the accessibility pass that follows.\n */\nfunction addShadeVariants(color: unknown): void {\n if (!isRecord(color) || !color['main']) return\n const main = color['main'] as string\n if (!color['dark']) color['dark'] = darken(main, TONAL_OFFSET_DARK)\n if (!color['light']) color['light'] = lighten(main, TONAL_OFFSET_LIGHT)\n if (!color['contrastText']) {\n color['contrastText'] =\n muiContrastRatio(main, '#FFFFFF') >= TONAL_OFFSET_DARK\n ? '#FFFFFF'\n : 'rgba(0, 0, 0, 0.87)'\n }\n}\n\n/**\n * `ensureAccessibleShades` for the light scheme: a DERIVED `dark` that misses\n * 4.5:1 on either background is walked darker, and a derived `contrastText`\n * that misses 4.5:1 on its `main` is walked toward the pole with more room.\n * Whatever the input authored passes through untouched.\n */\nfunction ensureAccessibleShades(\n palette: PaletteDraft,\n input: PaletteDraft,\n): void {\n const background = isRecord(palette['background']) ? palette['background'] : {}\n const backgrounds = [background['default'], background['paper']].filter(\n (value): value is string => typeof value === 'string',\n )\n if (!backgrounds.length) return\n\n for (const key of CONTRAST_TEXT_COLOR_KEYS) {\n const color = palette[key]\n if (!isRecord(color) || typeof color['main'] !== 'string') continue\n const provided = input[key]\n if (isRecord(provided) && !('main' in provided)) continue\n const explicit = isRecord(provided) ? provided : {}\n const main = color['main']\n\n try {\n if (\n FOREGROUND_COLOR_KEYS.includes(key) &&\n !explicit['dark'] &&\n typeof color['dark'] === 'string' &&\n !meetsContrast(color['dark'], backgrounds)\n ) {\n color['dark'] = accessibleShade(color['dark'], backgrounds, 'darken')\n }\n if (\n !explicit['contrastText'] &&\n typeof color['contrastText'] === 'string' &&\n !meetsContrast(color['contrastText'], [main])\n ) {\n const direction =\n contrastRatio('#fff', main) >= contrastRatio('#000', main)\n ? 'lighten'\n : 'darken'\n color['contrastText'] = accessibleShade(\n color['contrastText'],\n [main],\n direction,\n )\n }\n } catch {\n // An unparseable color is left as derived, as the theme leaves it.\n }\n }\n}\n\n/** `createTheme` + `createResponsiveTheme`'s passes, palette only. */\nfunction derivePalette(input: PaletteDraft): PaletteDraft {\n const rest: PaletteDraft = { ...input }\n delete rest['mode']\n delete rest['contrastThreshold']\n delete rest['tonalOffset']\n\n const augmented: PaletteDraft = {}\n for (const key of MUI_AUGMENTED_KEYS) {\n const color = input[key]\n if (!isRecord(color)) {\n throw new Error(`email-palette: the base palette is missing \\`${key}\\``)\n }\n augmented[key] = augmentColor(color)\n }\n\n const palette = deepMerge(\n {\n common: { ...MUI_COMMON },\n mode: 'light',\n ...augmented,\n grey: MUI_GREY,\n ...muiLightDefaults(),\n },\n rest,\n )\n addShadeVariants(palette['tertiary'])\n addShadeVariants(palette['surface'])\n ensureAccessibleShades(palette, input)\n return palette\n}\n\n// ── A site's colors, as `hostThemeToThemeOptions` reads them ───────────────\n\nconst SCHEME_COLOR_KEYS = [\n 'primary',\n 'secondary',\n 'tertiary',\n 'surface',\n 'error',\n 'warning',\n 'info',\n 'success',\n] as const\n\n/** Truthy string, or nothing: the \"copy what was set\" test the theme uses. */\nfunction setString(value: unknown): string | undefined {\n return typeof value === 'string' && value ? value : undefined\n}\n\n/** `pickPaletteColor`: a color needs a `main`; its shades ride along if set. */\nfunction pickPaletteColor(color: unknown): ColorRecord | undefined {\n if (!isRecord(color)) return undefined\n const main = setString(color['main'])\n if (!main) return undefined\n const picked: ColorRecord = { main }\n for (const shade of ['light', 'dark', 'contrastText'] as const) {\n const value = setString(color[shade])\n if (value) picked[shade] = value\n }\n return picked\n}\n\n/** Picks the listed string leaves of a group that were set, or nothing. */\nfunction pickLeaves(\n group: unknown,\n keys: ReadonlyArray<string>,\n): Record<string, string> | undefined {\n if (!isRecord(group)) return undefined\n const picked: Record<string, string> = {}\n for (const key of keys) {\n const value = setString(group[key])\n if (value) picked[key] = value\n }\n return Object.keys(picked).length ? picked : undefined\n}\n\n/** `schemeColorsToPaletteOptions` for the light scheme. */\nfunction siteColorOverrides(\n input: EmailPaletteInput['colors'],\n): PaletteDraft {\n const overrides: PaletteDraft = { mode: 'light' }\n if (!isRecord(input)) return overrides\n const colors: Readonly<Record<string, unknown>> = input\n for (const key of SCHEME_COLOR_KEYS) {\n const color = pickPaletteColor(colors[key])\n if (color) overrides[key] = color\n }\n const background = pickLeaves(colors['background'], ['default', 'paper'])\n if (background) overrides['background'] = background\n const text = pickLeaves(colors['text'], ['primary', 'secondary', 'disabled'])\n if (text) overrides['text'] = text\n const tint = pickLeaves(colors['tint'], ['primary', 'secondary', 'tertiary'])\n if (tint) overrides['tint'] = tint\n const divider = setString(colors['divider'])\n if (divider) overrides['divider'] = divider\n return overrides\n}\n\n// ── Literal CSS colors ─────────────────────────────────────────────────────\n\nconst HEX_COLOR = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\nconst NUMBER = String.raw`[-+]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[-+]?\\d+)?(?:%|deg|grad|rad|turn)?`\n/**\n * `rgb()`, `rgba()`, `hsl()`, `hsla()` holding numbers and nothing else —\n * comma or space separated, with an optional `/ alpha`. Anything that could\n * close the declaration or open another (`;`, `\"`, `(`, `url`) cannot match.\n */\nconst FUNCTION_COLOR = new RegExp(\n String.raw`^(?:rgba?|hsla?)\\(\\s*${NUMBER}(?:\\s*,\\s*|\\s+)${NUMBER}(?:\\s*,\\s*|\\s+)${NUMBER}(?:(?:\\s*,\\s*|\\s*\\/\\s*)${NUMBER})?\\s*\\)$`,\n 'i',\n)\n/** A color MUI's own manipulators parse: hex, or a comma-separated function. */\nconst MUI_PARSEABLE_FUNCTION = new RegExp(\n String.raw`^(?:rgba?|hsla?)\\(\\s*${NUMBER}\\s*,\\s*${NUMBER}\\s*,\\s*${NUMBER}(?:\\s*,\\s*${NUMBER})?\\s*\\)$`,\n 'i',\n)\n\n/** CSS Color Module Level 4 named colors, plus `transparent`. */\nconst NAMED_COLORS: ReadonlySet<string> = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace ' +\n 'olive olivedrab orange orangered orchid palegoldenrod palegreen ' +\n 'paleturquoise palevioletred papayawhip peachpuff peru pink plum ' +\n 'powderblue purple rebeccapurple red rosybrown royalblue saddlebrown ' +\n 'salmon sandybrown seagreen seashell sienna silver skyblue slateblue ' +\n 'slategray slategrey snow springgreen steelblue tan teal thistle tomato ' +\n 'turquoise violet wheat white whitesmoke yellow yellowgreen transparent'\n ).split(' '),\n)\n\n/** True for a literal CSS color that is safe to write into a style attribute. */\nfunction isLiteralColor(value: string): boolean {\n return (\n HEX_COLOR.test(value) ||\n FUNCTION_COLOR.test(value) ||\n NAMED_COLORS.has(value.toLowerCase())\n )\n}\n\n/** True for a color MUI can derive shades from without throwing. */\nfunction isDerivableColor(value: string): boolean {\n return HEX_COLOR.test(value) || MUI_PARSEABLE_FUNCTION.test(value)\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────\n\n/**\n * The fully derived light palette a site or sender renders with: the\n * chosen base, the site's authored colors layered one level deep (a color's\n * whole record replaced, so MUI re-derives its shades from the new `main`),\n * then a white-label `primaryColor` over that.\n *\n * Equal, for every token the Besigner's color picker offers, to the palette\n * of `createResponsiveTheme` over `mergeThemeOptions(siteBaseOptions(…))`\n * and the host theme's light `hostThemeToThemeOptions`.\n *\n * Where the site's colors cannot be derived at all — a `main` MUI cannot\n * parse, on which `createTheme` itself throws — the email falls back to the\n * base rather than failing the send: there is no rendered page to agree with.\n */\nexport function buildEmailPalette(input: EmailPaletteInput): EmailPalette {\n const base =\n input.base === 'platform' ? PLATFORM_LIGHT_INPUT : TENANT_LIGHT_INPUT\n const primaryColor =\n typeof input.primaryColor === 'string' &&\n isDerivableColor(input.primaryColor.trim())\n ? input.primaryColor.trim()\n : undefined\n const brand: PaletteDraft = primaryColor\n ? { primary: { main: primaryColor } }\n : {}\n\n const site = siteColorOverrides(input.colors)\n const layered: PaletteDraft = { ...base, ...site, ...brand }\n // `mergeThemeOptions`: a tint the site leaves unset is washed from an\n // accent it does set (`accentTint`), and the base's others stay.\n const siteTint = isRecord(site['tint']) ? site['tint'] : {}\n const tint: Record<string, unknown> = { ...(isRecord(base['tint']) ? base['tint'] : {}), ...siteTint }\n for (const key of ['primary', 'secondary', 'tertiary'] as const) {\n const main = isRecord(site[key]) ? site[key]['main'] : undefined\n if (siteTint[key] || typeof main !== 'string') continue\n try {\n tint[key] = lighten(main, 0.9)\n } catch {\n // Not a color the manipulators parse: keep the inherited tint.\n }\n }\n layered['tint'] = tint\n try {\n return derivePalette(layered)\n } catch {\n return derivePalette({ ...base, ...brand })\n }\n}\n\n/** The platform's own palette — the console brand, light scheme. */\nexport const PLATFORM_EMAIL_PALETTE: EmailPalette = buildEmailPalette({\n base: 'platform',\n})\n\n/** Palette token paths: a key, optionally one dot and a leaf (`grey.600`). */\nconst TOKEN_PATH = /^[a-zA-Z][a-zA-Z0-9]*(?:\\.[a-zA-Z0-9]+)?$/\n\nfunction lookupToken(\n palette: EmailPalette,\n path: string,\n): string | undefined {\n let node: unknown = palette\n for (const key of path.split('.')) {\n if (!isRecord(node) || !Object.prototype.hasOwnProperty.call(node, key)) {\n return undefined\n }\n node = node[key]\n }\n return typeof node === 'string' ? node : undefined\n}\n\n/**\n * A palette value as an opaque `#rrggbb`. Translucent values (`text.primary`\n * is `rgba(0, 0, 0, 0.87)`, some `contrastText`s are `#000000DE`) are\n * composited over the palette's paper, the ground an email body sits on:\n * Outlook's Word engine drops `rgba()` and eight-digit hex outright, and the\n * flattened color is what a reader sees on that ground anyway.\n */\nfunction toOpaqueHex(value: string, palette: EmailPalette): string | undefined {\n const channels = rgbaChannels(value)\n if (!channels) return undefined\n const [r, g, b, alpha] = channels\n if (alpha >= 1) return channelsToHex(r, g, b)\n const background = palette['background']\n const paper = isRecord(background) ? background['paper'] : undefined\n const ground =\n (typeof paper === 'string' ? rgbaChannels(paper) : undefined) ?? [\n 255, 255, 255, 1,\n ]\n const over = (fore: number, back: number) =>\n fore * alpha + back * (1 - alpha)\n return channelsToHex(over(r, ground[0]), over(g, ground[1]), over(b, ground[2]))\n}\n\nfunction rgbaChannels(\n value: string,\n): [number, number, number, number] | undefined {\n if (!isDerivableColor(value)) return undefined\n try {\n const decomposed = decomposeColor(value)\n const rgb = decomposed.type.startsWith('hsl')\n ? decomposeColor(hslToRgb(decomposed)).values\n : decomposed.values\n const alpha = decomposed.values.length > 3 ? decomposed.values[3] : 1\n const channels: [number, number, number, number] = [\n rgb[0],\n rgb[1],\n rgb[2],\n alpha,\n ]\n return channels.every(Number.isFinite) ? channels : undefined\n } catch {\n return undefined\n }\n}\n\nfunction channelsToHex(r: number, g: number, b: number): string {\n return `#${[r, g, b]\n .map((value) => intToHex(Math.round(Math.min(255, Math.max(0, value)))))\n .join('')}`\n}\n\n/**\n * Resolves a stored color prop for an email's markup.\n *\n * - A palette token path (`primary.main`, `grey.600`, `divider`) resolves\n * against `palette` to an opaque hex — or to nothing without a palette.\n * - A literal CSS color (hex, `rgb()`/`rgba()`/`hsl()`/`hsla()`,\n * `transparent`, a named color) passes through as written.\n * - Anything else — an unknown token, garbage, an empty value, and anything\n * that could step outside a `style` attribute — is `undefined`, so the\n * caller uses its own default.\n */\nexport function resolveEmailColor(\n value: unknown,\n palette: EmailPalette | undefined,\n): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed) return undefined\n\n if (palette && TOKEN_PATH.test(trimmed)) {\n const resolved = lookupToken(palette, trimmed)\n if (resolved !== undefined) {\n const hex = toOpaqueHex(resolved, palette)\n if (hex) return hex\n if (isLiteralColor(resolved)) return resolved\n return undefined\n }\n }\n return isLiteralColor(trimmed) ? trimmed : undefined\n}\n\n// ── Which base a site wears ────────────────────────────────────────────────\n\n/**\n * The prefix the tenant middleware puts on a custom domain before it becomes\n * the `[host]` route segment — mirrored from `tenant.theme.ts`.\n */\nconst CNAME_PREFIX = 'cname--'\n\n/**\n * The hosts whose brand is the operator's own, mirrored from\n * `PLATFORM_BRAND_HOSTS` in `tenant.theme.ts` and read from the same\n * variable. Dot notation for the same reason it has there: Next substitutes\n * `process.env.NAME` textually and never the bracket form.\n */\nconst PLATFORM_BRAND_HOSTS: ReadonlySet<string> = new Set(\n (process.env.NEXT_PUBLIC_PLATFORM_BRAND_HOSTS ?? 'aglyn.com,aglyn.io')\n .split(',')\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n)\n\n/**\n * Which base a site's mail layers onto: `'platform'` for the operator's own\n * hosts, `'tenant'` for every customer site — the answer\n * `wearsPlatformBrand(hostBrandKey(host))` gives the page itself, so a site's\n * email and its published page start from the same palette.\n */\nexport function emailPaletteBaseForHost(\n host: { cname?: string | null; subdomain?: string | null } | null | undefined,\n): 'platform' | 'tenant' {\n const key = host?.cname || host?.subdomain || undefined\n if (!key) return 'tenant'\n const normalized = key.trim().toLowerCase()\n const bare = normalized.startsWith(CNAME_PREFIX)\n ? normalized.slice(CNAME_PREFIX.length)\n : normalized\n return PLATFORM_BRAND_HOSTS.has(bare) ? 'platform' : 'tenant'\n}\n"],"names":["process","BRAND_GREY","A100","A200","A400","A700","PLATFORM_LIGHT_INPUT","mode","primary","main","dark","contrastText","secondary","tertiary","surface","tint","inputOutline","background","default","paper","info","error","success","warning","grey","TENANT_LIGHT_INPUT","MUI_COMMON","black","white","MUI_GREY","muiLightDefaults","text","disabled","divider","MUI_DARK_INK","MUI_CONTRAST_THRESHOLD","TONAL_OFFSET_LIGHT","TONAL_OFFSET_DARK","MUI_AUGMENTED_KEYS","FOREGROUND_COLOR_KEYS","CONTRAST_TEXT_COLOR_KEYS","hexToRgb","color","hex","slice","pattern","RegExp","length","parts","match","map","part","index","parseInt","Math","round","join","decomposeColor","charAt","marker","indexOf","type","substring","includes","Error","body","raw","colorSpace","split","shift","values","value","parseFloat","recomposeColor","String","intToHex","toString","rgbToHex","startsWith","hslToRgb","decomposed","hue","saturation","lightness","chroma","min","channel","n","k","max","rgb","push","clampUnit","lighten","coefficient","amount","i","darken","srgbChannelToLinear","muiLuminance","linear","Number","toFixed","muiContrastRatio","foreground","a","b","AA_TEXT_CONTRAST","relativeLuminance","contrastRatio","colorA","colorB","meetsContrast","backgrounds","every","toHslValues","h","s","l","r","g","delta","hslToHex","accessibleShade","direction","initialLightness","candidate","isRecord","Array","isArray","deepMerge","target","source","output","key","Object","keys","next","current","prototype","hasOwnProperty","call","augmentColor","augmented","addShadeVariants","ensureAccessibleShades","palette","input","filter","provided","explicit","derivePalette","rest","common","SCHEME_COLOR_KEYS","setString","undefined","pickPaletteColor","picked","shade","pickLeaves","group","siteColorOverrides","overrides","colors","HEX_COLOR","NUMBER","FUNCTION_COLOR","MUI_PARSEABLE_FUNCTION","NAMED_COLORS","Set","isLiteralColor","test","has","toLowerCase","isDerivableColor","buildEmailPalette","base","primaryColor","trim","brand","site","layered","siteTint","PLATFORM_EMAIL_PALETTE","TOKEN_PATH","lookupToken","path","node","toOpaqueHex","channels","rgbaChannels","alpha","channelsToHex","ground","over","fore","back","isFinite","resolveEmailColor","trimmed","resolved","CNAME_PREFIX","PLATFORM_BRAND_HOSTS","env","NEXT_PUBLIC_PLATFORM_BRAND_HOSTS","host","Boolean","emailPaletteBaseForHost","cname","subdomain","normalized","bare"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
1
2
|
/**
|
|
2
3
|
* @license
|
|
3
4
|
* Copyright 2026 Aglyn LLC
|
|
@@ -72,6 +73,22 @@
|
|
|
72
73
|
*=========================================*/ import { ACCOUNT_LURE_PATTERNS, DOCUMENT_SHARE_LURE_PATTERNS, isWorkspaceOwnBrand, linkHostsIn, linkUrlsIn, isBrandsOwnDomain, lookalikeBrandForHost, PHISHING_LURE_PATTERNS, PHISHING_SCREEN_BRANDS, registrableDomain, isAnyOfficialBrandDomain, isCommonLinkDomain, squashScreenText, visibleTextOf } from "./outbound-phishing-screen.js";
|
|
73
74
|
/** The most a page's text is read to, so a huge page costs a bounded scan. */ const MAX_PAGE_TEXT = 2000000;
|
|
74
75
|
/** The most nodes the walk visits. */ const MAX_NODES = 20000;
|
|
76
|
+
/**
|
|
77
|
+
* MUI's separate form elements that take an answer (`libs/plugins/mui`
|
|
78
|
+
* form-controls.tsx). Each is a field the author defined, and none carries
|
|
79
|
+
* its own words: those sit BESIDE it, in a Form Label inside the same Form
|
|
80
|
+
* Control or in the Choice Label holding a Checkbox or Radio.
|
|
81
|
+
*/ const FIELD_ELEMENT_COMPONENTS = new Set([
|
|
82
|
+
'muiInput',
|
|
83
|
+
'muiSelect',
|
|
84
|
+
'muiCheckbox',
|
|
85
|
+
'muiRadioGroup',
|
|
86
|
+
'muiRadio',
|
|
87
|
+
'muiRating'
|
|
88
|
+
]);
|
|
89
|
+
const FORM_CONTROL_COMPONENT = 'muiFormControl';
|
|
90
|
+
const FORM_LABEL_COMPONENT = 'muiFormLabel';
|
|
91
|
+
const CHOICE_LABEL_COMPONENT = 'muiFormControlLabel';
|
|
75
92
|
/** Props that describe a field the author defined, rather than content. */ const FIELD_PROP_KEYS = [
|
|
76
93
|
'fieldType',
|
|
77
94
|
'fieldName',
|
|
@@ -157,23 +174,38 @@
|
|
|
157
174
|
/** Every node in the tree, breadth-first in document order, bounded. */ function walkNodes(root) {
|
|
158
175
|
const found = [];
|
|
159
176
|
const seen = new Set();
|
|
177
|
+
// Each queued value carries the walk index of the node it is nested in, so
|
|
178
|
+
// a nested tree knows its parents without stored ids.
|
|
160
179
|
const stack = [
|
|
161
|
-
|
|
180
|
+
{
|
|
181
|
+
value: root
|
|
182
|
+
}
|
|
162
183
|
];
|
|
163
184
|
for(let head = 0; head < stack.length && found.length < MAX_NODES; head += 1){
|
|
164
|
-
const value = stack[head];
|
|
185
|
+
const { value, parentIndex } = stack[head];
|
|
165
186
|
if (!value || typeof value !== 'object' || seen.has(value)) continue;
|
|
166
187
|
seen.add(value);
|
|
167
188
|
const record = value;
|
|
189
|
+
let enclosing = parentIndex;
|
|
168
190
|
if (typeof record['componentId'] === 'string') {
|
|
169
191
|
const props = record['props'];
|
|
170
|
-
found.
|
|
192
|
+
enclosing = found.length;
|
|
193
|
+
found.push(_extends({
|
|
171
194
|
componentId: record['componentId'],
|
|
172
195
|
props: props && typeof props === 'object' ? props : {}
|
|
173
|
-
}
|
|
196
|
+
}, typeof record['$id'] === 'string' ? {
|
|
197
|
+
id: record['$id']
|
|
198
|
+
} : {}, typeof record['parentId'] === 'string' ? {
|
|
199
|
+
parentId: record['parentId']
|
|
200
|
+
} : {}, parentIndex !== undefined ? {
|
|
201
|
+
parentIndex
|
|
202
|
+
} : {}));
|
|
174
203
|
}
|
|
175
204
|
for (const child of Array.isArray(value) ? value : Object.values(record)){
|
|
176
|
-
if (child && typeof child === 'object') stack.push(
|
|
205
|
+
if (child && typeof child === 'object') stack.push({
|
|
206
|
+
value: child,
|
|
207
|
+
parentIndex: enclosing
|
|
208
|
+
});
|
|
177
209
|
}
|
|
178
210
|
}
|
|
179
211
|
return found;
|
|
@@ -195,17 +227,53 @@
|
|
|
195
227
|
return out;
|
|
196
228
|
}
|
|
197
229
|
/** Does this node describe a field its author defined? */ function isAuthoredField(node) {
|
|
230
|
+
if (FIELD_ELEMENT_COMPONENTS.has(node.componentId)) return true;
|
|
198
231
|
if (/field|input/i.test(node.componentId)) return true;
|
|
199
232
|
return FIELD_PROP_KEYS.some((key)=>node.props[key] !== undefined);
|
|
200
233
|
}
|
|
201
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* The words BESIDE one of MUI's separate form elements, which carries none of
|
|
236
|
+
* its own: the Choice Label holding a Checkbox or Radio, and the Form Label in
|
|
237
|
+
* the Form Control around it. Read through stored `parentId`s (a flat node
|
|
238
|
+
* map) or through nesting (a tree), whichever the page has.
|
|
239
|
+
*/ function siblingLabelOf(nodes, index) {
|
|
240
|
+
const node = nodes[index];
|
|
241
|
+
if (!node || !FIELD_ELEMENT_COMPONENTS.has(node.componentId)) return '';
|
|
242
|
+
const byId = new Map();
|
|
243
|
+
nodes.forEach((one, at)=>{
|
|
244
|
+
if (one.id && !byId.has(one.id)) byId.set(one.id, at);
|
|
245
|
+
});
|
|
246
|
+
const parentOf = (at)=>{
|
|
247
|
+
const one = nodes[at];
|
|
248
|
+
const stored = (one == null ? void 0 : one.parentId) !== undefined ? byId.get(one.parentId) : undefined;
|
|
249
|
+
return stored !== undefined && stored !== at ? stored : one == null ? void 0 : one.parentIndex;
|
|
250
|
+
};
|
|
251
|
+
const textOf = (one, key)=>typeof (one == null ? void 0 : one.props[key]) === 'string' ? one.props[key].trim() : '';
|
|
252
|
+
const words = [];
|
|
253
|
+
let at = parentOf(index);
|
|
254
|
+
for(let depth = 0; at !== undefined && depth < 8; depth += 1){
|
|
255
|
+
const ancestor = nodes[at];
|
|
256
|
+
if ((ancestor == null ? void 0 : ancestor.componentId) === CHOICE_LABEL_COMPONENT && textOf(ancestor, 'label')) {
|
|
257
|
+
words.push(textOf(ancestor, 'label'));
|
|
258
|
+
}
|
|
259
|
+
if ((ancestor == null ? void 0 : ancestor.componentId) === FORM_CONTROL_COMPONENT) {
|
|
260
|
+
const control = at;
|
|
261
|
+
const formLabel = nodes.find((one, oneAt)=>one.componentId === FORM_LABEL_COMPONENT && parentOf(oneAt) === control);
|
|
262
|
+
if (textOf(formLabel, 'children')) words.unshift(textOf(formLabel, 'children'));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
at = parentOf(at);
|
|
266
|
+
}
|
|
267
|
+
return words.join(' ');
|
|
268
|
+
}
|
|
269
|
+
/** The credential a field asks for, or null. */ function credentialOf(node, besideLabel = '') {
|
|
202
270
|
const text = (key)=>typeof node.props[key] === 'string' ? node.props[key] : '';
|
|
203
271
|
const type = [
|
|
204
272
|
text('fieldType'),
|
|
205
273
|
text('type'),
|
|
206
274
|
text('inputType')
|
|
207
275
|
].join(' ').toLowerCase();
|
|
208
|
-
const label = text('label') || text('placeholder') || text('fieldName') || text('name');
|
|
276
|
+
const label = text('label') || besideLabel || text('ariaLabel') || text('placeholder') || text('fieldName') || text('name');
|
|
209
277
|
if (/\bpassword\b/.test(type)) return {
|
|
210
278
|
field: 'password',
|
|
211
279
|
label: label || 'password'
|
|
@@ -218,6 +286,8 @@
|
|
|
218
286
|
};
|
|
219
287
|
const wording = [
|
|
220
288
|
text('label'),
|
|
289
|
+
besideLabel,
|
|
290
|
+
text('ariaLabel'),
|
|
221
291
|
text('placeholder'),
|
|
222
292
|
text('fieldName'),
|
|
223
293
|
text('name'),
|
|
@@ -311,7 +381,7 @@
|
|
|
311
381
|
};
|
|
312
382
|
nodes.forEach((node, index)=>{
|
|
313
383
|
if (isAuthoredField(node)) {
|
|
314
|
-
const credential = credentialOf(node);
|
|
384
|
+
const credential = credentialOf(node, siblingLabelOf(nodes, index));
|
|
315
385
|
if (credential) noteCredential(credential.field, credential.label);
|
|
316
386
|
}
|
|
317
387
|
for (const html of nodeStrings[index]){
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/hosted-page-screen.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\n/*==========================================\n * THE HOSTED PAGE SCREEN (AGL-3362).\n *\n * The outbound phishing screen, read over a PUBLISHED PAGE instead of an\n * email: the same brand list, the same lookalike logic, the same tiers\n * (`signalsThatHold`). A fraud actor who could not mail a lure could still\n * host one, and the incident's sibling site published a page at the\n * document-share path `/reviewfile`.\n *\n * Five shapes:\n *\n * 1. **A lookalike link or embed** (`lookalike-link`, STRONG) — any `href`,\n * `src`, form action or URL in the page's authored content whose host\n * wears a brand without being the brand's domain.\n * 2. **A credential field** (`credential-field`, STRONG) — a field the\n * merchant AUTHORED that asks for a password, a card number or a\n * one-time code: its type, its autocomplete token, or its label,\n * placeholder or name. The platform's own sign-in, account and checkout\n * elements render their inputs in code, not in stored props, so they are\n * never read as a field here — which is exactly the sanctioned/unsanctioned\n * line: what the platform collects through its own components is\n * Aglyn's to protect, and what a page's author collects in a form of\n * their own is not something a site may ask a stranger for.\n * 3. **A brand's call to action** (`brand-action-page`, SOFT) — one element\n * whose text names a brand that is not the workspace's AND asks the reader\n * to sign in, verify, confirm or open a document, on a page that links\n * somewhere that is neither the workspace's, nor a brand's own, nor a\n * common social or maps link.\n * 4. **A brand's lure, page-wide** (`brand-lure-page`, SOFT, AGL-3447) — the\n * same brand and link anywhere on the page, beside an account or\n * document-share LURE anywhere on it (\"Secure Document Access Portal\"), or\n * a call to action that leaves the site worded as an action (\"Continue to\n * Document\"). The 2026-10-01 phishing page put Proofpoint's name in its\n * body, the lure in its heading and the action on its button: three\n * elements, none of which shape 3 reads on its own. What it does not count page-wide is\n * a bare \"sign in\" that stays on the site — a hotel that says \"find us on\n * Booking.com\" in one place and has a member sign-in in another is two\n * ordinary sentences.\n * 5. **A call to action that leaves, beside a lure** (`offsite-action-page`,\n * SOFT, AGL-3447) — the page's only call to action, or one worded as an\n * account or document action, sends visitors to a host that is not the\n * site's own, a listed brand's or a common link, on a page that carries an\n * account or document-share lure. No brand needed: a \"Secure Document\n * Access Portal\" whose one button leaves for the kit is the shape whatever\n * name it wears.\n *\n * Shapes 3-5 are soft, so they hold only for a workspace in its first\n * fortnight (`signalsThatHold`); an established workspace's page is held by\n * shapes 1 and 2 alone.\n *\n * The same reading applies to a site REDIRECT ({@link screenSiteRedirect}):\n * a rule that sends a path off the site is a page whose one call to action\n * fires on arrival, and its path is the only text it has.\n *\n * Pure: the caller hands over the composed node tree and the workspace's own\n * names and hosts, and decides what a hold does.\n *=========================================*/\n\nimport {\n ACCOUNT_LURE_PATTERNS,\n DOCUMENT_SHARE_LURE_PATTERNS,\n isWorkspaceOwnBrand,\n linkHostsIn,\n linkUrlsIn,\n isBrandsOwnDomain,\n lookalikeBrandForHost,\n PHISHING_LURE_PATTERNS,\n PHISHING_SCREEN_BRANDS,\n type PhishingScreenBrand,\n type PhishingScreenSignal,\n registrableDomain,\n isAnyOfficialBrandDomain,\n isCommonLinkDomain,\n squashScreenText,\n visibleTextOf,\n} from './outbound-phishing-screen'\n\nexport interface HostedPageScreenInput {\n /**\n * The page as it will be served — the composed tree, or any object that\n * holds nodes (`{ componentId, props }`) at any depth. Walked, never\n * trusted to have one shape: a screen, a layout's chrome, a grafted\n * component and a form all arrive together.\n */\n nodes: unknown\n /** The workspace's own names, as `screenOutboundEmail` reads them. */\n ownNames?: readonly (string | null | undefined)[]\n /** The workspace's own hosts — a link to one is never \"elsewhere\". */\n ownDomains?: readonly (string | null | undefined)[]\n}\n\nexport interface HostedPageScreenVerdict {\n signals: PhishingScreenSignal[]\n /**\n * Every host the page (or the redirect) links, embeds or posts to, lowercased — what the\n * link reputation lookup reads (AGL-3451), so the page is walked once.\n */\n linkHosts: string[]\n /**\n * The same links whole, as written (`linkUrlsIn`), for the lookup's\n * `'url'` mode (AGL-3459). Query strings are still on them:\n * `foreignLinksForReputation` drops them before anything is looked up or\n * stored.\n */\n linkUrls: string[]\n}\n\n/** A node as the walk finds it. */\ninterface WalkedNode {\n componentId: string\n props: Record<string, unknown>\n}\n\n/** The most a page's text is read to, so a huge page costs a bounded scan. */\nconst MAX_PAGE_TEXT = 2_000_000\n/** The most nodes the walk visits. */\nconst MAX_NODES = 20_000\n\n/** Props that describe a field the author defined, rather than content. */\nconst FIELD_PROP_KEYS = ['fieldType', 'fieldName', 'inputType', 'autoComplete', 'autocomplete']\n\n/** Autocomplete tokens (WHATWG) that name a credential. */\nconst CREDENTIAL_AUTOCOMPLETE: Record<string, 'password' | 'card' | 'otp'> = {\n 'current-password': 'password',\n 'new-password': 'password',\n 'cc-number': 'card',\n 'cc-csc': 'card',\n 'cc-exp': 'card',\n 'one-time-code': 'otp',\n}\n\n/** Field wording that asks for a credential, by kind. */\nconst CREDENTIAL_LABELS: ReadonlyArray<{ field: 'password' | 'card' | 'otp'; pattern: RegExp }> = [\n { field: 'password', pattern: /\\bpass\\s?(?:word|code|phrase)s?\\b|\\bpasswd\\b|\\bpin\\s*(?:code|number)?\\s*$/i },\n {\n field: 'card',\n pattern:\n /\\b(?:credit|debit)\\s*card\\b|\\bcard\\s*(?:number|no\\.?|#)|\\bcardnumber\\b|\\bcvv2?\\b|\\bcvc\\b|\\bcard\\s*security\\s*code\\b|\\bcard\\s*expir/i,\n },\n {\n field: 'otp',\n pattern:\n /\\bone[-\\s]?time\\s*(?:pass\\s?code|password|code|pin)\\b|\\botp\\b|\\bverification\\s*code\\b|\\b2fa\\b|\\btwo[-\\s]?factor\\b|\\bauthenticat(?:ion|or)\\s*code\\b|\\bsms\\s*code\\b/i,\n },\n]\n\n/** Raw HTML an author placed that holds a credential input. */\nconst HTML_CREDENTIAL_INPUTS: ReadonlyArray<{ field: 'password' | 'card' | 'otp'; pattern: RegExp }> = [\n { field: 'password', pattern: /<input\\b[^>]*\\btype\\s*=\\s*[\"']?password/i },\n { field: 'password', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:pass(?:word|wd)?|current-password|new-password)\\b/i },\n { field: 'card', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:card_?number|cc-number|cc-csc|cvv|cvc)\\b/i },\n { field: 'otp', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:otp|one-time-code|verification_?code)\\b/i },\n]\n\n/**\n * What an element asks its reader to DO with an account or a document —\n * counted beside a brand in the SAME element, or on a call to action that\n * leaves the site. The email lure phrasing counts too.\n */\nconst PAGE_ACTION_PATTERNS: readonly RegExp[] = [\n /\\b(?:sign|log)[\\s-]?in\\b/i,\n /\\bverify\\b/i,\n /\\bconfirm\\s+(?:your|the)\\b/i,\n /\\bunlock\\b/i,\n /\\benter\\s+your\\b/i,\n /\\b(?:view|review|open|download|access)\\s+(?:the\\s+|your\\s+)?(?:shared\\s+|secure\\s+)?(?:file|document|invoice|statement)s?\\b/i,\n /\\b(?:continue|proceed)\\s+to\\s+(?:the\\s+|your\\s+)?(?:document|file|account|sign[\\s-]?in|log[\\s-]?in)s?\\b/i,\n]\n\n/**\n * The lures a page is read for as a WHOLE (AGL-3447): account and\n * document-share wording. Not the marketplace, booking and parcel lures,\n * which a hotel's \"guest reviews\" and a shop's delivery page carry every day\n * and which still count in one element beside a brand.\n */\nconst PAGE_LURE_PATTERNS: readonly RegExp[] = [...ACCOUNT_LURE_PATTERNS, ...DOCUMENT_SHARE_LURE_PATTERNS]\n\n/**\n * A component that renders a call to action — a button or a link, however a\n * plugin names it (`muiButton`, `muiScreenLink`, `muiLinkBox`). It is one\n * when it carries a target: an `href`, or a `screenId` on the site itself.\n */\nconst CALL_TO_ACTION_COMPONENT = /button|fab|cta|link/i\n\n/** The props a call to action's visible words live in; the first found wins. */\nconst CALL_TO_ACTION_LABEL_KEYS = ['children', 'label', 'text', 'title', 'html']\n\n/** URLs in page text, so prose is read for a brand or a lure without them. */\nconst PROSE_URL_PATTERN = /(?:https?:)?\\/\\/[^\\s\"'<>]+|\\bwww\\.[^\\s\"'<>]+/gi\n\n/** One call to action: what it says, and the host it leaves for (null: it stays). */\ninterface CallToAction {\n label: string\n host: string | null\n}\n\n/** Every node in the tree, breadth-first in document order, bounded. */\nfunction walkNodes(root: unknown): WalkedNode[] {\n const found: WalkedNode[] = []\n const seen = new Set<unknown>()\n const stack: unknown[] = [root]\n for (let head = 0; head < stack.length && found.length < MAX_NODES; head += 1) {\n const value = stack[head]\n if (!value || typeof value !== 'object' || seen.has(value)) continue\n seen.add(value)\n const record = value as Record<string, unknown>\n if (typeof record['componentId'] === 'string') {\n const props = record['props']\n found.push({\n componentId: record['componentId'] as string,\n props: props && typeof props === 'object' ? (props as Record<string, unknown>) : {},\n })\n }\n for (const child of Array.isArray(value) ? value : Object.values(record)) {\n if (child && typeof child === 'object') stack.push(child)\n }\n }\n return found\n}\n\n/** Every string a node's props hold, one level of arrays and objects deep. */\nfunction propStrings(props: Record<string, unknown>, skip: ReadonlySet<string> = new Set()): string[] {\n const out: string[] = []\n const visit = (value: unknown, depth: number) => {\n if (typeof value === 'string') out.push(value)\n else if (depth < 3 && value && typeof value === 'object') {\n for (const entry of Array.isArray(value) ? value : Object.values(value as Record<string, unknown>)) {\n visit(entry, depth + 1)\n }\n }\n }\n for (const [key, value] of Object.entries(props)) {\n if (key === 'sx' || skip.has(key)) continue\n visit(value, 0)\n }\n return out\n}\n\n/** Does this node describe a field its author defined? */\nfunction isAuthoredField(node: WalkedNode): boolean {\n if (/field|input/i.test(node.componentId)) return true\n return FIELD_PROP_KEYS.some((key) => node.props[key] !== undefined)\n}\n\n/** The credential a field asks for, or null. */\nfunction credentialOf(node: WalkedNode): { field: 'password' | 'card' | 'otp'; label: string } | null {\n const text = (key: string) => (typeof node.props[key] === 'string' ? (node.props[key] as string) : '')\n const type = [text('fieldType'), text('type'), text('inputType')].join(' ').toLowerCase()\n const label = text('label') || text('placeholder') || text('fieldName') || text('name')\n if (/\\bpassword\\b/.test(type)) return { field: 'password', label: label || 'password' }\n const autocomplete = (text('autoComplete') || text('autocomplete')).toLowerCase().trim()\n const byToken = CREDENTIAL_AUTOCOMPLETE[autocomplete]\n if (byToken) return { field: byToken, label: label || autocomplete }\n const wording = [text('label'), text('placeholder'), text('fieldName'), text('name'), text('helperText')]\n .filter(Boolean)\n .join(' ')\n for (const { field, pattern } of CREDENTIAL_LABELS) {\n if (pattern.test(wording)) return { field, label: (label || wording).slice(0, 80) }\n }\n return null\n}\n\n/**\n * The credential an input asks for, read from the two attributes that decide\n * it in the browser — its `type` and its `autocomplete` token — or null.\n * For code that builds inputs rather than storing them as page props: the\n * marketplace verifier reads a plugin bundle's `{ type: 'password' }` and\n * `setAttribute('autocomplete', 'cc-number')` with this (AGL-3362).\n */\nexport function credentialForInputAttribute(\n attribute: string,\n value: string,\n): 'password' | 'card' | 'otp' | null {\n const name = String(attribute ?? '').toLowerCase()\n const token = String(value ?? '').trim().toLowerCase()\n if ((name === 'type' || name === 'inputtype') && token === 'password') return 'password'\n if (name === 'autocomplete') return CREDENTIAL_AUTOCOMPLETE[token] ?? null\n return null\n}\n\n/** The credential a piece of raw HTML asks for in an input of its own, or null. */\nexport function credentialInHtml(html: string): 'password' | 'card' | 'otp' | null {\n const text = String(html ?? '')\n if (!text.includes('<')) return null\n return HTML_CREDENTIAL_INPUTS.find(({ pattern }) => pattern.test(text))?.field ?? null\n}\n\n/**\n * Does a submitted field's NAME ask for a credential — a password, a card\n * number or security code, a one-time code? The same wording the page\n * screen holds a field for, read over a name as a form posts it\n * (`card_number`, `cardNumber`, `one-time-code`).\n *\n * For the one path that does not pass a publish: a public form endpoint\n * takes whatever field names a request carries, so a page the screen never\n * saw (third-party code, a hand-built request) could still post a password\n * to it. The endpoint drops such a field rather than store it.\n */\nexport function isCredentialFieldName(name: string): boolean {\n const words = String(name ?? '')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_\\-.[\\]]+/g, ' ')\n .trim()\n if (!words) return false\n if (CREDENTIAL_AUTOCOMPLETE[words.toLowerCase().replace(/\\s+/g, '-')]) return true\n if (/^(?:pass|pwd|ccnum|cc\\s?num|cc\\s?number|cvv2?|cvc|csc)$/i.test(words)) return true\n return CREDENTIAL_LABELS.some(({ pattern }) => pattern.test(words))\n}\n\n/** URLs a page carries in a form Links-by-shape would miss: `//host/…`. */\nfunction protocolRelativeUrls(strings: readonly string[]): string {\n return strings\n .filter((value) => /^\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)+/i.test(value.trim()))\n .map((value) => `https:${value.trim()}`)\n .join('\\n')\n}\n\n/**\n * Screen one page as it will be served. The caller applies the tiers with\n * `signalsThatHold` and decides what a hold does.\n */\nexport function screenHostedPage(input: HostedPageScreenInput): HostedPageScreenVerdict {\n const signals: PhishingScreenSignal[] = []\n const nodes = walkNodes(input.nodes)\n const ownNames = squashScreenText((input.ownNames ?? []).filter(Boolean).join(' '))\n const ownHosts = (input.ownDomains ?? [])\n .map((domain) => String(domain ?? '').trim().toLowerCase().replace(/\\.+$/, ''))\n .filter((domain) => domain.includes('.'))\n const ownRegistrables = new Set(ownHosts.map(registrableDomain))\n const isOwnHost = (host: string) => ownHosts.some((own) => host === own || host.endsWith(`.${own}`))\n\n const nodeStrings = nodes.map((node) => propStrings(node.props))\n let pageText = nodeStrings.map((strings) => strings.join('\\n')).join('\\n')\n pageText = `${pageText}\\n${protocolRelativeUrls(nodeStrings.flat())}`.slice(0, MAX_PAGE_TEXT)\n\n // 1. A lookalike host anywhere the page links, embeds or posts to.\n const linkHosts = linkHostsIn(pageText)\n for (const host of linkHosts) {\n const brand = lookalikeBrandForHost(host)\n if (brand && !isBrandsOwnDomain(brand, ownRegistrables)) {\n signals.push({ code: 'lookalike-link', brand: brand.id, host })\n }\n }\n\n // 2. A credential field the page's author defined.\n const credentialSeen = new Set<string>()\n const noteCredential = (field: 'password' | 'card' | 'otp', label: string) => {\n if (credentialSeen.has(field)) return\n credentialSeen.add(field)\n signals.push({ code: 'credential-field', field, label: label.slice(0, 80) })\n }\n nodes.forEach((node, index) => {\n if (isAuthoredField(node)) {\n const credential = credentialOf(node)\n if (credential) noteCredential(credential.field, credential.label)\n }\n for (const html of nodeStrings[index]) {\n if (!html.includes('<')) continue\n for (const { field, pattern } of HTML_CREDENTIAL_INPUTS) {\n if (pattern.test(html)) noteCredential(field, 'an input in the page’s own HTML')\n }\n }\n })\n\n const isElsewhere = (host: string) => {\n if (isOwnHost(host)) return false\n const registrable = registrableDomain(host)\n return !isAnyOfficialBrandDomain(registrable) && !isCommonLinkDomain(registrable)\n }\n const foreign = linkHosts.find(isElsewhere)\n const actions = [...PAGE_ACTION_PATTERNS, ...PHISHING_LURE_PATTERNS]\n const named = new Set<string>()\n const isOthersBrand = (brand: PhishingScreenBrand) =>\n !named.has(brand.id) && !isWorkspaceOwnBrand(brand, ownNames, ownRegistrables)\n\n // 3. A brand's call to action, in one element, on a page that links away.\n if (foreign) {\n for (const strings of nodeStrings) {\n const text = proseOf(strings.join(' '))\n if (!text) continue\n const action = firstMatch(actions, text)\n if (!action) continue\n for (const brand of PHISHING_SCREEN_BRANDS) {\n if (isOthersBrand(brand) && brand.mention.test(text)) {\n named.add(brand.id)\n signals.push({ code: 'brand-action-page', brand: brand.id, action: action.slice(0, 80) })\n }\n }\n }\n }\n\n const prose = proseOf(pageText)\n const pageLure = firstMatch(PAGE_LURE_PATTERNS, prose)\n const ctas = callsToAction(nodes)\n const leaving = ctas.filter((cta): cta is CallToAction & { host: string } =>\n Boolean(cta.host && isElsewhere(cta.host)),\n )\n const wordedLeaving = leaving.find((cta) => firstMatch(actions, cta.label))\n\n // 4. A brand's lure, read over the page as a whole, on a page that links away.\n const brandLure = pageLure ?? (wordedLeaving ? firstMatch(actions, wordedLeaving.label) : undefined)\n if (foreign && brandLure) {\n const host = (wordedLeaving ?? leaving[0])?.host ?? foreign\n for (const brand of PHISHING_SCREEN_BRANDS) {\n if (isOthersBrand(brand) && brand.mention.test(prose)) {\n named.add(brand.id)\n signals.push({ code: 'brand-lure-page', brand: brand.id, lure: brandLure.slice(0, 120), host })\n }\n }\n }\n\n // 5. The page's call to action leaves the site, beside a lure. Its only\n // one, or the one worded as an account or document action.\n const primary = wordedLeaving ?? (ctas.length === 1 ? leaving[0] : undefined)\n if (primary && pageLure) {\n signals.push({\n code: 'offsite-action-page',\n action: (primary.label || primary.host).slice(0, 80),\n lure: pageLure.slice(0, 120),\n host: primary.host,\n })\n }\n\n return { signals, linkHosts, linkUrls: linkUrlsIn(pageText) }\n}\n\n/**\n * What a visitor reads in page text: its markup taken out the way the email\n * screen takes it out ({@link visibleTextOf}), so a custom HTML block or a\n * rich-text prop cannot split a brand or a lure letter by letter with tags\n * (AGL-3453), and its bare URLs taken out too. Links are still read from\n * the raw text, where an href lives.\n */\nfunction proseOf(text: string): string {\n return visibleTextOf(text).replace(PROSE_URL_PATTERN, ' ').replace(/\\s+/g, ' ').trim()\n}\n\n/** The first pattern's match in the text, or undefined. */\nfunction firstMatch(patterns: readonly RegExp[], text: string): string | undefined {\n if (!text) return undefined\n for (const pattern of patterns) {\n const found = text.match(pattern)?.[0]\n if (found) return found\n }\n return undefined\n}\n\n/** The words a call to action shows, from its label prop, markup taken out. */\nfunction callToActionLabel(props: Record<string, unknown>): string {\n for (const key of CALL_TO_ACTION_LABEL_KEYS) {\n const value = props[key]\n if (typeof value === 'string' && value.trim()) {\n return visibleTextOf(value).split('\\n')[0]\n }\n }\n return ''\n}\n\n/**\n * Every call to action on the page: each button or link that carries a\n * target. A `screenId` is a page of the site and takes precedence over an\n * `href`, as the button renders it; a relative, `mailto:` or `tel:` href has\n * no web host and stays too.\n */\nfunction callsToAction(nodes: readonly WalkedNode[]): CallToAction[] {\n const found: CallToAction[] = []\n for (const node of nodes) {\n if (!CALL_TO_ACTION_COMPONENT.test(node.componentId)) continue\n const screenId = typeof node.props['screenId'] === 'string' ? node.props['screenId'].trim() : ''\n const href = typeof node.props['href'] === 'string' ? node.props['href'].trim() : ''\n if (!screenId && !href) continue\n const absolute = href.startsWith('//') ? `https:${href}` : href\n found.push({\n label: callToActionLabel(node.props),\n host: screenId ? null : (linkHostsIn(absolute)[0] ?? null),\n })\n }\n return found\n}\n\nexport interface SiteRedirectScreenInput {\n /** The path the rule answers — `/secure-document` — or its pattern. */\n source: string\n /** Where it sends the visitor, after any `$n` substitution. */\n destination: string\n /** The workspace's own names, as `screenHostedPage` reads them. */\n ownNames?: readonly (string | null | undefined)[]\n /** The workspace's own hosts — a redirect to one never leaves. */\n ownDomains?: readonly (string | null | undefined)[]\n}\n\n/**\n * Screen one site redirect (AGL-3447): a rule that sends a path off the site\n * is a call to action that fires on arrival, so it is read with the page's\n * rules over the one text it has, its path.\n *\n * - `lookalike-link` (STRONG) — the destination wears a brand it is not.\n * - `offsite-redirect` (SOFT) — the destination is not the site's own, a\n * listed brand's or a common link, and the path reads as an account or\n * document-share lure (`/secure-document-access`).\n * - `brand-lure-link` (SOFT) — the same, and the path names a brand that is\n * not the workspace's (`/paypal-verify-your-account`), the email screen's\n * three-part lure.\n *\n * The caller applies the tiers with `signalsThatHold`, with the workspace's\n * age, and decides what a hold does.\n */\nexport function screenSiteRedirect(input: SiteRedirectScreenInput): HostedPageScreenVerdict {\n const signals: PhishingScreenSignal[] = []\n const destination = String(input.destination ?? '').trim()\n const absolute = destination.startsWith('//') ? `https:${destination}` : destination\n const hosts = linkHostsIn(absolute)\n const linkUrls = linkUrlsIn(absolute)\n if (!hosts.length) return { signals, linkHosts: hosts, linkUrls }\n const ownNames = squashScreenText((input.ownNames ?? []).filter(Boolean).join(' '))\n const ownHosts = (input.ownDomains ?? [])\n .map((domain) => String(domain ?? '').trim().toLowerCase().replace(/\\.+$/, ''))\n .filter((domain) => domain.includes('.'))\n const ownRegistrables = new Set(ownHosts.map(registrableDomain))\n\n for (const host of hosts) {\n const brand = lookalikeBrandForHost(host)\n if (brand && !isBrandsOwnDomain(brand, ownRegistrables)) {\n signals.push({ code: 'lookalike-link', brand: brand.id, host })\n }\n }\n\n // The first host is where the browser goes; the rest is a userinfo disguise.\n const host = hosts[0]\n const registrable = registrableDomain(host)\n const leaves =\n !ownHosts.some((own) => host === own || host.endsWith(`.${own}`)) &&\n !isAnyOfficialBrandDomain(registrable) &&\n !isCommonLinkDomain(registrable)\n if (!leaves) return { signals, linkHosts: hosts, linkUrls }\n\n const source = String(input.source ?? '').trim()\n const words = pathWords(source)\n const lure = firstMatch(PAGE_LURE_PATTERNS, words)\n if (lure) {\n signals.push({ code: 'offsite-redirect', source: source.slice(0, 120), lure: lure.slice(0, 120), host })\n }\n const anyLure = lure ?? firstMatch(PHISHING_LURE_PATTERNS, words)\n if (anyLure) {\n const brand = PHISHING_SCREEN_BRANDS.find(\n (candidate) =>\n candidate.mention.test(words) && !isWorkspaceOwnBrand(candidate, ownNames, ownRegistrables),\n )\n if (brand) {\n signals.push({ code: 'brand-lure-link', brand: brand.id, lure: anyLure.slice(0, 120), host })\n }\n }\n return { signals, linkHosts: hosts, linkUrls }\n}\n\n/** A path read as words: `/secure-document_access` → `secure document access`. */\nfunction pathWords(path: string): string {\n let decoded = path\n try {\n decoded = decodeURIComponent(path)\n } catch {\n // A malformed escape is read as typed.\n }\n return decoded.replace(/[^a-z0-9]+/gi, ' ').trim()\n}\n"],"names":["ACCOUNT_LURE_PATTERNS","DOCUMENT_SHARE_LURE_PATTERNS","isWorkspaceOwnBrand","linkHostsIn","linkUrlsIn","isBrandsOwnDomain","lookalikeBrandForHost","PHISHING_LURE_PATTERNS","PHISHING_SCREEN_BRANDS","registrableDomain","isAnyOfficialBrandDomain","isCommonLinkDomain","squashScreenText","visibleTextOf","MAX_PAGE_TEXT","MAX_NODES","FIELD_PROP_KEYS","CREDENTIAL_AUTOCOMPLETE","CREDENTIAL_LABELS","field","pattern","HTML_CREDENTIAL_INPUTS","PAGE_ACTION_PATTERNS","PAGE_LURE_PATTERNS","CALL_TO_ACTION_COMPONENT","CALL_TO_ACTION_LABEL_KEYS","PROSE_URL_PATTERN","walkNodes","root","found","seen","Set","stack","head","length","value","has","add","record","props","push","componentId","child","Array","isArray","Object","values","propStrings","skip","out","visit","depth","entry","key","entries","isAuthoredField","node","test","some","undefined","credentialOf","text","type","join","toLowerCase","label","autocomplete","trim","byToken","wording","filter","Boolean","slice","credentialForInputAttribute","attribute","name","String","token","credentialInHtml","html","includes","find","isCredentialFieldName","words","replace","protocolRelativeUrls","strings","map","screenHostedPage","input","signals","nodes","ownNames","ownHosts","ownDomains","domain","ownRegistrables","isOwnHost","host","own","endsWith","nodeStrings","pageText","flat","linkHosts","brand","code","id","credentialSeen","noteCredential","forEach","index","credential","isElsewhere","registrable","foreign","actions","named","isOthersBrand","proseOf","action","firstMatch","mention","prose","pageLure","ctas","callsToAction","leaving","cta","wordedLeaving","brandLure","lure","primary","linkUrls","patterns","match","callToActionLabel","split","screenId","href","absolute","startsWith","screenSiteRedirect","destination","hosts","leaves","source","pathWords","anyLure","candidate","path","decoded","decodeURIComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/shared/util/email/src/lib/hosted-page-screen.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\n/*==========================================\n * THE HOSTED PAGE SCREEN (AGL-3362).\n *\n * The outbound phishing screen, read over a PUBLISHED PAGE instead of an\n * email: the same brand list, the same lookalike logic, the same tiers\n * (`signalsThatHold`). A fraud actor who could not mail a lure could still\n * host one, and the incident's sibling site published a page at the\n * document-share path `/reviewfile`.\n *\n * Five shapes:\n *\n * 1. **A lookalike link or embed** (`lookalike-link`, STRONG) — any `href`,\n * `src`, form action or URL in the page's authored content whose host\n * wears a brand without being the brand's domain.\n * 2. **A credential field** (`credential-field`, STRONG) — a field the\n * merchant AUTHORED that asks for a password, a card number or a\n * one-time code: its type, its autocomplete token, or its label,\n * placeholder or name. The platform's own sign-in, account and checkout\n * elements render their inputs in code, not in stored props, so they are\n * never read as a field here — which is exactly the sanctioned/unsanctioned\n * line: what the platform collects through its own components is\n * Aglyn's to protect, and what a page's author collects in a form of\n * their own is not something a site may ask a stranger for.\n * 3. **A brand's call to action** (`brand-action-page`, SOFT) — one element\n * whose text names a brand that is not the workspace's AND asks the reader\n * to sign in, verify, confirm or open a document, on a page that links\n * somewhere that is neither the workspace's, nor a brand's own, nor a\n * common social or maps link.\n * 4. **A brand's lure, page-wide** (`brand-lure-page`, SOFT, AGL-3447) — the\n * same brand and link anywhere on the page, beside an account or\n * document-share LURE anywhere on it (\"Secure Document Access Portal\"), or\n * a call to action that leaves the site worded as an action (\"Continue to\n * Document\"). The 2026-10-01 phishing page put Proofpoint's name in its\n * body, the lure in its heading and the action on its button: three\n * elements, none of which shape 3 reads on its own. What it does not count page-wide is\n * a bare \"sign in\" that stays on the site — a hotel that says \"find us on\n * Booking.com\" in one place and has a member sign-in in another is two\n * ordinary sentences.\n * 5. **A call to action that leaves, beside a lure** (`offsite-action-page`,\n * SOFT, AGL-3447) — the page's only call to action, or one worded as an\n * account or document action, sends visitors to a host that is not the\n * site's own, a listed brand's or a common link, on a page that carries an\n * account or document-share lure. No brand needed: a \"Secure Document\n * Access Portal\" whose one button leaves for the kit is the shape whatever\n * name it wears.\n *\n * Shapes 3-5 are soft, so they hold only for a workspace in its first\n * fortnight (`signalsThatHold`); an established workspace's page is held by\n * shapes 1 and 2 alone.\n *\n * The same reading applies to a site REDIRECT ({@link screenSiteRedirect}):\n * a rule that sends a path off the site is a page whose one call to action\n * fires on arrival, and its path is the only text it has.\n *\n * Pure: the caller hands over the composed node tree and the workspace's own\n * names and hosts, and decides what a hold does.\n *=========================================*/\n\nimport {\n ACCOUNT_LURE_PATTERNS,\n DOCUMENT_SHARE_LURE_PATTERNS,\n isWorkspaceOwnBrand,\n linkHostsIn,\n linkUrlsIn,\n isBrandsOwnDomain,\n lookalikeBrandForHost,\n PHISHING_LURE_PATTERNS,\n PHISHING_SCREEN_BRANDS,\n type PhishingScreenBrand,\n type PhishingScreenSignal,\n registrableDomain,\n isAnyOfficialBrandDomain,\n isCommonLinkDomain,\n squashScreenText,\n visibleTextOf,\n} from './outbound-phishing-screen'\n\nexport interface HostedPageScreenInput {\n /**\n * The page as it will be served — the composed tree, or any object that\n * holds nodes (`{ componentId, props }`) at any depth. Walked, never\n * trusted to have one shape: a screen, a layout's chrome, a grafted\n * component and a form all arrive together.\n */\n nodes: unknown\n /** The workspace's own names, as `screenOutboundEmail` reads them. */\n ownNames?: readonly (string | null | undefined)[]\n /** The workspace's own hosts — a link to one is never \"elsewhere\". */\n ownDomains?: readonly (string | null | undefined)[]\n}\n\nexport interface HostedPageScreenVerdict {\n signals: PhishingScreenSignal[]\n /**\n * Every host the page (or the redirect) links, embeds or posts to, lowercased — what the\n * link reputation lookup reads (AGL-3451), so the page is walked once.\n */\n linkHosts: string[]\n /**\n * The same links whole, as written (`linkUrlsIn`), for the lookup's\n * `'url'` mode (AGL-3459). Query strings are still on them:\n * `foreignLinksForReputation` drops them before anything is looked up or\n * stored.\n */\n linkUrls: string[]\n}\n\n/** A node as the walk finds it. */\ninterface WalkedNode {\n componentId: string\n props: Record<string, unknown>\n /** Its stored `$id`, when it has one. */\n id?: string\n /** Its stored `parentId`, when it has one (a flat node map). */\n parentId?: string\n /** The nearest node it is NESTED in, by walk index (a nested tree). */\n parentIndex?: number\n}\n\n/** The most a page's text is read to, so a huge page costs a bounded scan. */\nconst MAX_PAGE_TEXT = 2_000_000\n/** The most nodes the walk visits. */\nconst MAX_NODES = 20_000\n\n/**\n * MUI's separate form elements that take an answer (`libs/plugins/mui`\n * form-controls.tsx). Each is a field the author defined, and none carries\n * its own words: those sit BESIDE it, in a Form Label inside the same Form\n * Control or in the Choice Label holding a Checkbox or Radio.\n */\nconst FIELD_ELEMENT_COMPONENTS = new Set([\n 'muiInput',\n 'muiSelect',\n 'muiCheckbox',\n 'muiRadioGroup',\n 'muiRadio',\n 'muiRating',\n])\nconst FORM_CONTROL_COMPONENT = 'muiFormControl'\nconst FORM_LABEL_COMPONENT = 'muiFormLabel'\nconst CHOICE_LABEL_COMPONENT = 'muiFormControlLabel'\n\n/** Props that describe a field the author defined, rather than content. */\nconst FIELD_PROP_KEYS = ['fieldType', 'fieldName', 'inputType', 'autoComplete', 'autocomplete']\n\n/** Autocomplete tokens (WHATWG) that name a credential. */\nconst CREDENTIAL_AUTOCOMPLETE: Record<string, 'password' | 'card' | 'otp'> = {\n 'current-password': 'password',\n 'new-password': 'password',\n 'cc-number': 'card',\n 'cc-csc': 'card',\n 'cc-exp': 'card',\n 'one-time-code': 'otp',\n}\n\n/** Field wording that asks for a credential, by kind. */\nconst CREDENTIAL_LABELS: ReadonlyArray<{ field: 'password' | 'card' | 'otp'; pattern: RegExp }> = [\n { field: 'password', pattern: /\\bpass\\s?(?:word|code|phrase)s?\\b|\\bpasswd\\b|\\bpin\\s*(?:code|number)?\\s*$/i },\n {\n field: 'card',\n pattern:\n /\\b(?:credit|debit)\\s*card\\b|\\bcard\\s*(?:number|no\\.?|#)|\\bcardnumber\\b|\\bcvv2?\\b|\\bcvc\\b|\\bcard\\s*security\\s*code\\b|\\bcard\\s*expir/i,\n },\n {\n field: 'otp',\n pattern:\n /\\bone[-\\s]?time\\s*(?:pass\\s?code|password|code|pin)\\b|\\botp\\b|\\bverification\\s*code\\b|\\b2fa\\b|\\btwo[-\\s]?factor\\b|\\bauthenticat(?:ion|or)\\s*code\\b|\\bsms\\s*code\\b/i,\n },\n]\n\n/** Raw HTML an author placed that holds a credential input. */\nconst HTML_CREDENTIAL_INPUTS: ReadonlyArray<{ field: 'password' | 'card' | 'otp'; pattern: RegExp }> = [\n { field: 'password', pattern: /<input\\b[^>]*\\btype\\s*=\\s*[\"']?password/i },\n { field: 'password', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:pass(?:word|wd)?|current-password|new-password)\\b/i },\n { field: 'card', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:card_?number|cc-number|cc-csc|cvv|cvc)\\b/i },\n { field: 'otp', pattern: /<input\\b[^>]*\\b(?:name|autocomplete)\\s*=\\s*[\"']?(?:otp|one-time-code|verification_?code)\\b/i },\n]\n\n/**\n * What an element asks its reader to DO with an account or a document —\n * counted beside a brand in the SAME element, or on a call to action that\n * leaves the site. The email lure phrasing counts too.\n */\nconst PAGE_ACTION_PATTERNS: readonly RegExp[] = [\n /\\b(?:sign|log)[\\s-]?in\\b/i,\n /\\bverify\\b/i,\n /\\bconfirm\\s+(?:your|the)\\b/i,\n /\\bunlock\\b/i,\n /\\benter\\s+your\\b/i,\n /\\b(?:view|review|open|download|access)\\s+(?:the\\s+|your\\s+)?(?:shared\\s+|secure\\s+)?(?:file|document|invoice|statement)s?\\b/i,\n /\\b(?:continue|proceed)\\s+to\\s+(?:the\\s+|your\\s+)?(?:document|file|account|sign[\\s-]?in|log[\\s-]?in)s?\\b/i,\n]\n\n/**\n * The lures a page is read for as a WHOLE (AGL-3447): account and\n * document-share wording. Not the marketplace, booking and parcel lures,\n * which a hotel's \"guest reviews\" and a shop's delivery page carry every day\n * and which still count in one element beside a brand.\n */\nconst PAGE_LURE_PATTERNS: readonly RegExp[] = [...ACCOUNT_LURE_PATTERNS, ...DOCUMENT_SHARE_LURE_PATTERNS]\n\n/**\n * A component that renders a call to action — a button or a link, however a\n * plugin names it (`muiButton`, `muiScreenLink`, `muiLinkBox`). It is one\n * when it carries a target: an `href`, or a `screenId` on the site itself.\n */\nconst CALL_TO_ACTION_COMPONENT = /button|fab|cta|link/i\n\n/** The props a call to action's visible words live in; the first found wins. */\nconst CALL_TO_ACTION_LABEL_KEYS = ['children', 'label', 'text', 'title', 'html']\n\n/** URLs in page text, so prose is read for a brand or a lure without them. */\nconst PROSE_URL_PATTERN = /(?:https?:)?\\/\\/[^\\s\"'<>]+|\\bwww\\.[^\\s\"'<>]+/gi\n\n/** One call to action: what it says, and the host it leaves for (null: it stays). */\ninterface CallToAction {\n label: string\n host: string | null\n}\n\n/** Every node in the tree, breadth-first in document order, bounded. */\nfunction walkNodes(root: unknown): WalkedNode[] {\n const found: WalkedNode[] = []\n const seen = new Set<unknown>()\n // Each queued value carries the walk index of the node it is nested in, so\n // a nested tree knows its parents without stored ids.\n const stack: Array<{ value: unknown; parentIndex?: number }> = [{ value: root }]\n for (let head = 0; head < stack.length && found.length < MAX_NODES; head += 1) {\n const { value, parentIndex } = stack[head] as { value: unknown; parentIndex?: number }\n if (!value || typeof value !== 'object' || seen.has(value)) continue\n seen.add(value)\n const record = value as Record<string, unknown>\n let enclosing = parentIndex\n if (typeof record['componentId'] === 'string') {\n const props = record['props']\n enclosing = found.length\n found.push({\n componentId: record['componentId'] as string,\n props: props && typeof props === 'object' ? (props as Record<string, unknown>) : {},\n ...(typeof record['$id'] === 'string' ? { id: record['$id'] } : {}),\n ...(typeof record['parentId'] === 'string' ? { parentId: record['parentId'] } : {}),\n ...(parentIndex !== undefined ? { parentIndex } : {}),\n })\n }\n for (const child of Array.isArray(value) ? value : Object.values(record)) {\n if (child && typeof child === 'object') stack.push({ value: child, parentIndex: enclosing })\n }\n }\n return found\n}\n\n/** Every string a node's props hold, one level of arrays and objects deep. */\nfunction propStrings(props: Record<string, unknown>, skip: ReadonlySet<string> = new Set()): string[] {\n const out: string[] = []\n const visit = (value: unknown, depth: number) => {\n if (typeof value === 'string') out.push(value)\n else if (depth < 3 && value && typeof value === 'object') {\n for (const entry of Array.isArray(value) ? value : Object.values(value as Record<string, unknown>)) {\n visit(entry, depth + 1)\n }\n }\n }\n for (const [key, value] of Object.entries(props)) {\n if (key === 'sx' || skip.has(key)) continue\n visit(value, 0)\n }\n return out\n}\n\n/** Does this node describe a field its author defined? */\nfunction isAuthoredField(node: WalkedNode): boolean {\n if (FIELD_ELEMENT_COMPONENTS.has(node.componentId)) return true\n if (/field|input/i.test(node.componentId)) return true\n return FIELD_PROP_KEYS.some((key) => node.props[key] !== undefined)\n}\n\n/**\n * The words BESIDE one of MUI's separate form elements, which carries none of\n * its own: the Choice Label holding a Checkbox or Radio, and the Form Label in\n * the Form Control around it. Read through stored `parentId`s (a flat node\n * map) or through nesting (a tree), whichever the page has.\n */\nfunction siblingLabelOf(nodes: readonly WalkedNode[], index: number): string {\n const node = nodes[index]\n if (!node || !FIELD_ELEMENT_COMPONENTS.has(node.componentId)) return ''\n const byId = new Map<string, number>()\n nodes.forEach((one, at) => {\n if (one.id && !byId.has(one.id)) byId.set(one.id, at)\n })\n const parentOf = (at: number): number | undefined => {\n const one = nodes[at]\n const stored = one?.parentId !== undefined ? byId.get(one.parentId) : undefined\n return stored !== undefined && stored !== at ? stored : one?.parentIndex\n }\n const textOf = (one: WalkedNode | undefined, key: string) =>\n typeof one?.props[key] === 'string' ? (one.props[key] as string).trim() : ''\n const words: string[] = []\n let at = parentOf(index)\n for (let depth = 0; at !== undefined && depth < 8; depth += 1) {\n const ancestor = nodes[at]\n if (ancestor?.componentId === CHOICE_LABEL_COMPONENT && textOf(ancestor, 'label')) {\n words.push(textOf(ancestor, 'label'))\n }\n if (ancestor?.componentId === FORM_CONTROL_COMPONENT) {\n const control = at\n const formLabel = nodes.find(\n (one, oneAt) => one.componentId === FORM_LABEL_COMPONENT && parentOf(oneAt) === control,\n )\n if (textOf(formLabel, 'children')) words.unshift(textOf(formLabel, 'children'))\n break\n }\n at = parentOf(at)\n }\n return words.join(' ')\n}\n\n/** The credential a field asks for, or null. */\nfunction credentialOf(\n node: WalkedNode,\n besideLabel = '',\n): { field: 'password' | 'card' | 'otp'; label: string } | null {\n const text = (key: string) => (typeof node.props[key] === 'string' ? (node.props[key] as string) : '')\n const type = [text('fieldType'), text('type'), text('inputType')].join(' ').toLowerCase()\n const label =\n text('label') || besideLabel || text('ariaLabel') || text('placeholder') || text('fieldName') || text('name')\n if (/\\bpassword\\b/.test(type)) return { field: 'password', label: label || 'password' }\n const autocomplete = (text('autoComplete') || text('autocomplete')).toLowerCase().trim()\n const byToken = CREDENTIAL_AUTOCOMPLETE[autocomplete]\n if (byToken) return { field: byToken, label: label || autocomplete }\n const wording = [\n text('label'),\n besideLabel,\n text('ariaLabel'),\n text('placeholder'),\n text('fieldName'),\n text('name'),\n text('helperText'),\n ]\n .filter(Boolean)\n .join(' ')\n for (const { field, pattern } of CREDENTIAL_LABELS) {\n if (pattern.test(wording)) return { field, label: (label || wording).slice(0, 80) }\n }\n return null\n}\n\n/**\n * The credential an input asks for, read from the two attributes that decide\n * it in the browser — its `type` and its `autocomplete` token — or null.\n * For code that builds inputs rather than storing them as page props: the\n * marketplace verifier reads a plugin bundle's `{ type: 'password' }` and\n * `setAttribute('autocomplete', 'cc-number')` with this (AGL-3362).\n */\nexport function credentialForInputAttribute(\n attribute: string,\n value: string,\n): 'password' | 'card' | 'otp' | null {\n const name = String(attribute ?? '').toLowerCase()\n const token = String(value ?? '').trim().toLowerCase()\n if ((name === 'type' || name === 'inputtype') && token === 'password') return 'password'\n if (name === 'autocomplete') return CREDENTIAL_AUTOCOMPLETE[token] ?? null\n return null\n}\n\n/** The credential a piece of raw HTML asks for in an input of its own, or null. */\nexport function credentialInHtml(html: string): 'password' | 'card' | 'otp' | null {\n const text = String(html ?? '')\n if (!text.includes('<')) return null\n return HTML_CREDENTIAL_INPUTS.find(({ pattern }) => pattern.test(text))?.field ?? null\n}\n\n/**\n * Does a submitted field's NAME ask for a credential — a password, a card\n * number or security code, a one-time code? The same wording the page\n * screen holds a field for, read over a name as a form posts it\n * (`card_number`, `cardNumber`, `one-time-code`).\n *\n * For the one path that does not pass a publish: a public form endpoint\n * takes whatever field names a request carries, so a page the screen never\n * saw (third-party code, a hand-built request) could still post a password\n * to it. The endpoint drops such a field rather than store it.\n */\nexport function isCredentialFieldName(name: string): boolean {\n const words = String(name ?? '')\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_\\-.[\\]]+/g, ' ')\n .trim()\n if (!words) return false\n if (CREDENTIAL_AUTOCOMPLETE[words.toLowerCase().replace(/\\s+/g, '-')]) return true\n if (/^(?:pass|pwd|ccnum|cc\\s?num|cc\\s?number|cvv2?|cvc|csc)$/i.test(words)) return true\n return CREDENTIAL_LABELS.some(({ pattern }) => pattern.test(words))\n}\n\n/** URLs a page carries in a form Links-by-shape would miss: `//host/…`. */\nfunction protocolRelativeUrls(strings: readonly string[]): string {\n return strings\n .filter((value) => /^\\/\\/[a-z0-9-]+(?:\\.[a-z0-9-]+)+/i.test(value.trim()))\n .map((value) => `https:${value.trim()}`)\n .join('\\n')\n}\n\n/**\n * Screen one page as it will be served. The caller applies the tiers with\n * `signalsThatHold` and decides what a hold does.\n */\nexport function screenHostedPage(input: HostedPageScreenInput): HostedPageScreenVerdict {\n const signals: PhishingScreenSignal[] = []\n const nodes = walkNodes(input.nodes)\n const ownNames = squashScreenText((input.ownNames ?? []).filter(Boolean).join(' '))\n const ownHosts = (input.ownDomains ?? [])\n .map((domain) => String(domain ?? '').trim().toLowerCase().replace(/\\.+$/, ''))\n .filter((domain) => domain.includes('.'))\n const ownRegistrables = new Set(ownHosts.map(registrableDomain))\n const isOwnHost = (host: string) => ownHosts.some((own) => host === own || host.endsWith(`.${own}`))\n\n const nodeStrings = nodes.map((node) => propStrings(node.props))\n let pageText = nodeStrings.map((strings) => strings.join('\\n')).join('\\n')\n pageText = `${pageText}\\n${protocolRelativeUrls(nodeStrings.flat())}`.slice(0, MAX_PAGE_TEXT)\n\n // 1. A lookalike host anywhere the page links, embeds or posts to.\n const linkHosts = linkHostsIn(pageText)\n for (const host of linkHosts) {\n const brand = lookalikeBrandForHost(host)\n if (brand && !isBrandsOwnDomain(brand, ownRegistrables)) {\n signals.push({ code: 'lookalike-link', brand: brand.id, host })\n }\n }\n\n // 2. A credential field the page's author defined.\n const credentialSeen = new Set<string>()\n const noteCredential = (field: 'password' | 'card' | 'otp', label: string) => {\n if (credentialSeen.has(field)) return\n credentialSeen.add(field)\n signals.push({ code: 'credential-field', field, label: label.slice(0, 80) })\n }\n nodes.forEach((node, index) => {\n if (isAuthoredField(node)) {\n const credential = credentialOf(node, siblingLabelOf(nodes, index))\n if (credential) noteCredential(credential.field, credential.label)\n }\n for (const html of nodeStrings[index]) {\n if (!html.includes('<')) continue\n for (const { field, pattern } of HTML_CREDENTIAL_INPUTS) {\n if (pattern.test(html)) noteCredential(field, 'an input in the page’s own HTML')\n }\n }\n })\n\n const isElsewhere = (host: string) => {\n if (isOwnHost(host)) return false\n const registrable = registrableDomain(host)\n return !isAnyOfficialBrandDomain(registrable) && !isCommonLinkDomain(registrable)\n }\n const foreign = linkHosts.find(isElsewhere)\n const actions = [...PAGE_ACTION_PATTERNS, ...PHISHING_LURE_PATTERNS]\n const named = new Set<string>()\n const isOthersBrand = (brand: PhishingScreenBrand) =>\n !named.has(brand.id) && !isWorkspaceOwnBrand(brand, ownNames, ownRegistrables)\n\n // 3. A brand's call to action, in one element, on a page that links away.\n if (foreign) {\n for (const strings of nodeStrings) {\n const text = proseOf(strings.join(' '))\n if (!text) continue\n const action = firstMatch(actions, text)\n if (!action) continue\n for (const brand of PHISHING_SCREEN_BRANDS) {\n if (isOthersBrand(brand) && brand.mention.test(text)) {\n named.add(brand.id)\n signals.push({ code: 'brand-action-page', brand: brand.id, action: action.slice(0, 80) })\n }\n }\n }\n }\n\n const prose = proseOf(pageText)\n const pageLure = firstMatch(PAGE_LURE_PATTERNS, prose)\n const ctas = callsToAction(nodes)\n const leaving = ctas.filter((cta): cta is CallToAction & { host: string } =>\n Boolean(cta.host && isElsewhere(cta.host)),\n )\n const wordedLeaving = leaving.find((cta) => firstMatch(actions, cta.label))\n\n // 4. A brand's lure, read over the page as a whole, on a page that links away.\n const brandLure = pageLure ?? (wordedLeaving ? firstMatch(actions, wordedLeaving.label) : undefined)\n if (foreign && brandLure) {\n const host = (wordedLeaving ?? leaving[0])?.host ?? foreign\n for (const brand of PHISHING_SCREEN_BRANDS) {\n if (isOthersBrand(brand) && brand.mention.test(prose)) {\n named.add(brand.id)\n signals.push({ code: 'brand-lure-page', brand: brand.id, lure: brandLure.slice(0, 120), host })\n }\n }\n }\n\n // 5. The page's call to action leaves the site, beside a lure. Its only\n // one, or the one worded as an account or document action.\n const primary = wordedLeaving ?? (ctas.length === 1 ? leaving[0] : undefined)\n if (primary && pageLure) {\n signals.push({\n code: 'offsite-action-page',\n action: (primary.label || primary.host).slice(0, 80),\n lure: pageLure.slice(0, 120),\n host: primary.host,\n })\n }\n\n return { signals, linkHosts, linkUrls: linkUrlsIn(pageText) }\n}\n\n/**\n * What a visitor reads in page text: its markup taken out the way the email\n * screen takes it out ({@link visibleTextOf}), so a custom HTML block or a\n * rich-text prop cannot split a brand or a lure letter by letter with tags\n * (AGL-3453), and its bare URLs taken out too. Links are still read from\n * the raw text, where an href lives.\n */\nfunction proseOf(text: string): string {\n return visibleTextOf(text).replace(PROSE_URL_PATTERN, ' ').replace(/\\s+/g, ' ').trim()\n}\n\n/** The first pattern's match in the text, or undefined. */\nfunction firstMatch(patterns: readonly RegExp[], text: string): string | undefined {\n if (!text) return undefined\n for (const pattern of patterns) {\n const found = text.match(pattern)?.[0]\n if (found) return found\n }\n return undefined\n}\n\n/** The words a call to action shows, from its label prop, markup taken out. */\nfunction callToActionLabel(props: Record<string, unknown>): string {\n for (const key of CALL_TO_ACTION_LABEL_KEYS) {\n const value = props[key]\n if (typeof value === 'string' && value.trim()) {\n return visibleTextOf(value).split('\\n')[0]\n }\n }\n return ''\n}\n\n/**\n * Every call to action on the page: each button or link that carries a\n * target. A `screenId` is a page of the site and takes precedence over an\n * `href`, as the button renders it; a relative, `mailto:` or `tel:` href has\n * no web host and stays too.\n */\nfunction callsToAction(nodes: readonly WalkedNode[]): CallToAction[] {\n const found: CallToAction[] = []\n for (const node of nodes) {\n if (!CALL_TO_ACTION_COMPONENT.test(node.componentId)) continue\n const screenId = typeof node.props['screenId'] === 'string' ? node.props['screenId'].trim() : ''\n const href = typeof node.props['href'] === 'string' ? node.props['href'].trim() : ''\n if (!screenId && !href) continue\n const absolute = href.startsWith('//') ? `https:${href}` : href\n found.push({\n label: callToActionLabel(node.props),\n host: screenId ? null : (linkHostsIn(absolute)[0] ?? null),\n })\n }\n return found\n}\n\nexport interface SiteRedirectScreenInput {\n /** The path the rule answers — `/secure-document` — or its pattern. */\n source: string\n /** Where it sends the visitor, after any `$n` substitution. */\n destination: string\n /** The workspace's own names, as `screenHostedPage` reads them. */\n ownNames?: readonly (string | null | undefined)[]\n /** The workspace's own hosts — a redirect to one never leaves. */\n ownDomains?: readonly (string | null | undefined)[]\n}\n\n/**\n * Screen one site redirect (AGL-3447): a rule that sends a path off the site\n * is a call to action that fires on arrival, so it is read with the page's\n * rules over the one text it has, its path.\n *\n * - `lookalike-link` (STRONG) — the destination wears a brand it is not.\n * - `offsite-redirect` (SOFT) — the destination is not the site's own, a\n * listed brand's or a common link, and the path reads as an account or\n * document-share lure (`/secure-document-access`).\n * - `brand-lure-link` (SOFT) — the same, and the path names a brand that is\n * not the workspace's (`/paypal-verify-your-account`), the email screen's\n * three-part lure.\n *\n * The caller applies the tiers with `signalsThatHold`, with the workspace's\n * age, and decides what a hold does.\n */\nexport function screenSiteRedirect(input: SiteRedirectScreenInput): HostedPageScreenVerdict {\n const signals: PhishingScreenSignal[] = []\n const destination = String(input.destination ?? '').trim()\n const absolute = destination.startsWith('//') ? `https:${destination}` : destination\n const hosts = linkHostsIn(absolute)\n const linkUrls = linkUrlsIn(absolute)\n if (!hosts.length) return { signals, linkHosts: hosts, linkUrls }\n const ownNames = squashScreenText((input.ownNames ?? []).filter(Boolean).join(' '))\n const ownHosts = (input.ownDomains ?? [])\n .map((domain) => String(domain ?? '').trim().toLowerCase().replace(/\\.+$/, ''))\n .filter((domain) => domain.includes('.'))\n const ownRegistrables = new Set(ownHosts.map(registrableDomain))\n\n for (const host of hosts) {\n const brand = lookalikeBrandForHost(host)\n if (brand && !isBrandsOwnDomain(brand, ownRegistrables)) {\n signals.push({ code: 'lookalike-link', brand: brand.id, host })\n }\n }\n\n // The first host is where the browser goes; the rest is a userinfo disguise.\n const host = hosts[0]\n const registrable = registrableDomain(host)\n const leaves =\n !ownHosts.some((own) => host === own || host.endsWith(`.${own}`)) &&\n !isAnyOfficialBrandDomain(registrable) &&\n !isCommonLinkDomain(registrable)\n if (!leaves) return { signals, linkHosts: hosts, linkUrls }\n\n const source = String(input.source ?? '').trim()\n const words = pathWords(source)\n const lure = firstMatch(PAGE_LURE_PATTERNS, words)\n if (lure) {\n signals.push({ code: 'offsite-redirect', source: source.slice(0, 120), lure: lure.slice(0, 120), host })\n }\n const anyLure = lure ?? firstMatch(PHISHING_LURE_PATTERNS, words)\n if (anyLure) {\n const brand = PHISHING_SCREEN_BRANDS.find(\n (candidate) =>\n candidate.mention.test(words) && !isWorkspaceOwnBrand(candidate, ownNames, ownRegistrables),\n )\n if (brand) {\n signals.push({ code: 'brand-lure-link', brand: brand.id, lure: anyLure.slice(0, 120), host })\n }\n }\n return { signals, linkHosts: hosts, linkUrls }\n}\n\n/** A path read as words: `/secure-document_access` → `secure document access`. */\nfunction pathWords(path: string): string {\n let decoded = path\n try {\n decoded = decodeURIComponent(path)\n } catch {\n // A malformed escape is read as typed.\n }\n return decoded.replace(/[^a-z0-9]+/gi, ' ').trim()\n}\n"],"names":["ACCOUNT_LURE_PATTERNS","DOCUMENT_SHARE_LURE_PATTERNS","isWorkspaceOwnBrand","linkHostsIn","linkUrlsIn","isBrandsOwnDomain","lookalikeBrandForHost","PHISHING_LURE_PATTERNS","PHISHING_SCREEN_BRANDS","registrableDomain","isAnyOfficialBrandDomain","isCommonLinkDomain","squashScreenText","visibleTextOf","MAX_PAGE_TEXT","MAX_NODES","FIELD_ELEMENT_COMPONENTS","Set","FORM_CONTROL_COMPONENT","FORM_LABEL_COMPONENT","CHOICE_LABEL_COMPONENT","FIELD_PROP_KEYS","CREDENTIAL_AUTOCOMPLETE","CREDENTIAL_LABELS","field","pattern","HTML_CREDENTIAL_INPUTS","PAGE_ACTION_PATTERNS","PAGE_LURE_PATTERNS","CALL_TO_ACTION_COMPONENT","CALL_TO_ACTION_LABEL_KEYS","PROSE_URL_PATTERN","walkNodes","root","found","seen","stack","value","head","length","parentIndex","has","add","record","enclosing","props","push","componentId","id","parentId","undefined","child","Array","isArray","Object","values","propStrings","skip","out","visit","depth","entry","key","entries","isAuthoredField","node","test","some","siblingLabelOf","nodes","index","byId","Map","forEach","one","at","set","parentOf","stored","get","textOf","trim","words","ancestor","control","formLabel","find","oneAt","unshift","join","credentialOf","besideLabel","text","type","toLowerCase","label","autocomplete","byToken","wording","filter","Boolean","slice","credentialForInputAttribute","attribute","name","String","token","credentialInHtml","html","includes","isCredentialFieldName","replace","protocolRelativeUrls","strings","map","screenHostedPage","input","signals","ownNames","ownHosts","ownDomains","domain","ownRegistrables","isOwnHost","host","own","endsWith","nodeStrings","pageText","flat","linkHosts","brand","code","credentialSeen","noteCredential","credential","isElsewhere","registrable","foreign","actions","named","isOthersBrand","proseOf","action","firstMatch","mention","prose","pageLure","ctas","callsToAction","leaving","cta","wordedLeaving","brandLure","lure","primary","linkUrls","patterns","match","callToActionLabel","split","screenId","href","absolute","startsWith","screenSiteRedirect","destination","hosts","leaves","source","pathWords","anyLure","candidate","path","decoded","decodeURIComponent"],"mappings":"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"}
|