Files
photo-live/packages/embed-loader/README-embed.md
T
2026-09-21 18:55:15 +08:00

115 lines
6.6 KiB
Markdown
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.
# PhotoLive 悬浮组件接入说明(embed-loader v1)
宿主页只需引入一个版本化脚本文件即可嵌入照片悬浮轮播组件。加载器零依赖、零全局污染
(仅 `window.__photoliveEmbedV1` 版本标记)、出错静默不破坏宿主。
## 接入代码
```html
<script
src="https://{EMBED_ORIGIN}/embed/v1/loader.js"
data-slot="{展示位 token(后台展示位管理页复制)}"
defer
></script>
```
- `data-slot`(必填):展示位 publicToken;缺失时加载器静默不渲染任何内容。
- `data-lang`(可选):`auto`(缺省,按相册 defaultLocale)| `zh-CN` | `en`,非法值回退 `auto`。
- `data-position`(可选):`right`(缺省)| `left`,悬浮侧。
- `data-interval`(可选):轮播切换间隔毫秒,夹取到 3000–30000,缺省 5000。
仅影响轮播切换,不影响取数轮询(恒 30s 契约基准)。
- `data-image-fit`(可选,UI-01):`contain`(缺省,4:3 舞台完整显示)| `cover`
(填满裁切)。
- `data-offset-x`(可选,UI-01):距宿主边缘水平偏移 px,0–120,缺省 16。
- `data-offset-bottom`(可选,UI-01):距宿主底部垂直偏移 px,0–240,缺省 24。
- `data-z-index`(可选,UI-01):wrapper 层级,1–2147483000,缺省 1000。宿主页面
自身存在高层级元素(如全屏遮罩)时按需调高;把手与卡片共用该层级。
- `data-poll`(可选,仅测试):加速取数轮询毫秒数,经 iframe URL `&poll=` 透传,
**仅 dev 构建生效**(生产构建忽略)。e2e 用;生产接入不需要。
同一页面引入多个不同 `data-slot` 的标签即嵌入多个展示位;同一 `data-slot` 重复出现时
后者静默忽略。
### 配置优先级(UI-01 §5.4)
任一字段:**宿主 data-\* 显式提供 > 后台展示位设置 > 契约默认值**。未提供的
data-\* 不视为覆盖,后台设置变更经 iframe 轮询发现后以 `config` 消息动态应用
(loader 二次校验:仅白名单字段 + 版本单调递增,不信任任意 CSS/HTML)。
## 多槽与响应式(UI-01)
- **同侧多槽纵排**:同一侧(left/right)多个展示位按引入顺序自下而上堆叠,
间距 8px;视口高度不足时后引入的槽自动收起为把手(不写用户偏好——空间约束
非用户选择)。
- **窗口变化**:`resize` / `orientationchange` 重新评估全部几何;宽度取「配置宽
与视口剩余宽」的较小值(极窄视口下限 180px 防溢出)。
- **窄屏**(viewport <480px):强制小尺寸(280px 宽)且默认收起为把手。
- **收起把手**:图片图标 + 「照片 / Photos」胶囊按钮,高 44px、有效点击区
≥44×44、可键盘操作(Enter/Space)。收起状态会话记忆在 `sessionStorage`
(键 `pl-embed-collapsed:{slot}`),刷新后保持,不反复弹出。
## CSP 要求(宿主页)
| 指令 | 值 | 说明 |
| --- | --- | --- |
| `script-src` | `{EMBED_ORIGIN}` | 加载 loader.js |
| `frame-src` | `{EMBED_ORIGIN}` | 悬浮轮播 iframe(`{EMBED_ORIGIN}/embed/{slot}`) |
组件自身不内联脚本;iframe 内图片来自对象存储签名域名(`img-src` 无需为组件放开
项目域)。`frame-ancestors` 由**我们**的 iframe 页配置(限制可嵌入站点),不是宿主
的关注点。
## 生命周期消息(iframe → 宿主)
| type | 触发 | loader 行为 |
| --- | --- | --- |
| `resize` | iframe 内容高度变化 | wrapper 高度夹取 60–640px 应用 |
| `collapse` | 用户点击 iframe 内 × | 收起为把手 + 会话记忆 |
| `expand` | (保留) | — |
| `config` | 后台设置变更(configVersion 升值) | 校验后动态应用非 data-\* 显式字段 |
| `destroy` | 槽 404/410/停用/解绑(服务端终止展示) | **移除 wrapper + 把手 + 会话键**,不留无效按钮(与用户收起语义不同) |
宿主 → iframe 仅 `expand`(把手点击,目标源严格为 iframe 源)。
## 安全模型
- **iframe sandbox**:`allow-scripts allow-popups allow-popups-to-escape-sandbox
allow-same-origin`。必须含 `allow-same-origin`:否则 iframe 源为 opaque `null`,
下方 origin 白名单会拒掉自己 iframe 的合法消息,且 iframe 内调公开 API 需要全站
ACAO `*`。iframe 对宿主天然跨源(宿主域 ≠ 项目域),`allow-same-origin` 仅使
iframe 保有**项目域自己的**源,不会获得宿主页源。
- **postMessage 双向白名单**:loader 仅接受「`event.source` 等于已登记 iframe 的
`contentWindow` 且 `event.origin` 严格等于该 iframe src 源」的消息,且载荷须为
`{photoliveEmbed: true, type: resize|collapse|expand|config|destroy, ...}`;
resize 高度夹取 60–640px,config 经版本闸 + 字段白名单校验。向 iframe 发消息
(把手展开)同样以 iframe 源为目标,绝不使用 `*`。
- **零信任宿主数据**:slot token 经 encodeURIComponent 进 URL;
lang/position/interval/imageFit/offset/zIndex 白名单或夹取校验后才拼入查询串。
- **存储与 Cookie**:loader 不读写 Cookie、不写宿主 localStorage;仅上述
sessionStorage 收起标记(destroy 时一并清除)。无埋点、无对外数据上报。
- **DOM 足迹与样式免疫**:仅向 body 追加 wrapper 与收起把手两个 div(全部 inline
style,不注入样式表/类名,不影响宿主 CSS,也不受宿主全局样式影响——敌意 reset
下组件仍保持 fixed 定位与几何)。
## 产物与发布
- 发布物即文件本身(不走 npm):`packages/embed-loader/dist/v1/loader.js`
(esbuild bundle + minify,IIFE,target es2018)。
- **viewer 构建携带**:`pnpm --filter @photo/embed-loader run sync` 将产物拷贝至
`apps/viewer/public/embed/v1/loader.js`(sha256 校验)。viewer 的 vite publicDir
使 dev server 直接可访问、`vite build` 自动将其带入 dist/。两份产物均提交入库,
修改 loader 后需重新 build + sync。
- 路径 `/embed/v1/` 长期稳定;破坏性变更才会出现 `/embed/v2/`。
## 开发
```bash
pnpm --filter @photo/embed-loader run build # 打包 dist/v1/loader.js
pnpm --filter @photo/embed-loader run test # params 纯函数单测(vitest)
pnpm --filter @photo/embed-loader run sync # 拷贝至 viewer public/(含 hash 校验)
```
宿主页交互行为(iframe 出现/收起/记忆/窄屏/多槽堆叠/跨源宿主/postMessage 白名单/
后台设置动态生效)的端到端覆盖见 viewer Playwright `e2e/embed.spec.ts` 与
`e2e/embed-crossorigin.spec.ts`(宿主 fixture 页 / 3999 端口跨源静态页)。