Files
photo-live/packages/embed-loader/src/params.js
T
浅念 ddb4c8175f feat(embed): versioned loader with postMessage whitelist
packages/embed-loader:手写零依赖加载器(esbuild bundle+minify→dist/v1/loader.js,
IIFE target es2018 4.7KB)。自发现 EMBED_ORIGIN(currentScript 兜底扫描)、
data-slot 必填缺失静默 no-op、__photoliveEmbedV1 标记去重+同槽去重、
窄屏 <480px 强制 sm+默认收起、postMessage 双向白名单(source+origin 严格
校验,resize 夹取 60-640)、sessionStorage 收起记忆、零全局零 console
全静默。sync 脚本携 sha256 校验拷贝至 viewer publicDir(两份产物入库);
src/params.js 为 loader/单测共用参数源(11 vitest 用例)。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-20 22:31:40 +08:00

80 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// packages/embed-loader/src/params.js
// 纯函数参数模块:loader(esbuild 打包内联)与 vitest 单测共用的唯一参数源。
// 取值域与缺省严格对齐契约 EmbedSlotSettings(packages/contracts/openapi.yaml),
// 非法/缺省一律静默回退——loader 对宿主零报错(A11「不破坏宿主」)。
export const POSITION_VALUES = ['left', 'right'];
export const SIZE_VALUES = ['sm', 'md'];
export const LANG_VALUES = ['auto', 'zh-CN', 'en'];
export const DEFAULTS = {
position: 'right',
size: 'md',
intervalMs: 5000,
lang: 'auto',
collapsedDefault: false,
};
/** size → 宿主侧 wrapper 宽度(px)。sm 为窄屏降级档。 */
export const SIZE_WIDTH = { sm: 280, md: 340 };
export const INTERVAL_MIN = 3000;
export const INTERVAL_MAX = 30000;
/** resize 消息高度夹取范围(px),越界/非数值返回 null 表示忽略。 */
export const HEIGHT_MIN = 60;
export const HEIGHT_MAX = 640;
function oneOf(allowed, value, fallback) {
return allowed.indexOf(value) >= 0 ? value : fallback;
}
/** 轮播间隔(ms):解析失败取缺省,超界夹取到 [3000, 30000]。 */
export function normalizeInterval(value) {
const n = parseInt(value, 10);
if (isNaN(n)) return DEFAULTS.intervalMs;
return Math.min(INTERVAL_MAX, Math.max(INTERVAL_MIN, n));
}
/**
* 解析宿主 script 标签 data-* 配置(dataset 形如 {slot, lang, position, interval})。
* opts.narrow=true(viewport <480px)时强制 sm 尺寸 + 默认收起(计划评审缺口:移动端可用)。
* data-slot 缺失/空白 → slot 为 null,调用方须静默 no-op。
*/
export function parseEmbedConfig(dataset, opts) {
const ds = dataset || {};
const slot = typeof ds.slot === 'string' ? ds.slot.replace(/^\s+|\s+$/g, '') : '';
const narrow = !!(opts && opts.narrow);
return {
slot: slot || null,
lang: oneOf(LANG_VALUES, ds.lang, DEFAULTS.lang),
position: oneOf(POSITION_VALUES, ds.position, DEFAULTS.position),
intervalMs: normalizeInterval(ds.interval),
size: narrow ? 'sm' : oneOf(SIZE_VALUES, ds.size, DEFAULTS.size),
collapsedDefault: narrow ? true : DEFAULTS.collapsedDefault,
};
}
/** iframe src:{origin}/embed/{slot}?lang=&pos=&interval=(槽 token 编码防注入)。 */
export function buildEmbedUrl(origin, slot, config) {
return (
origin +
'/embed/' +
encodeURIComponent(slot) +
'?lang=' +
encodeURIComponent(config.lang) +
'&pos=' +
encodeURIComponent(config.position) +
'&interval=' +
String(config.intervalMs)
);
}
/** resize 高度夹取:非有限数值/缺失 → null(忽略该消息)。 */
export function clampHeight(value) {
if (value === null || value === undefined) return null;
const n = Number(value);
if (isNaN(n) || !isFinite(n)) return null;
return Math.min(HEIGHT_MAX, Math.max(HEIGHT_MIN, Math.round(n)));
}