# PhotoLive 悬浮组件接入说明(embed-loader v1) 宿主页只需引入一个版本化脚本文件即可嵌入照片悬浮轮播组件。加载器零依赖、零全局污染 (仅 `window.__photoliveEmbedV1` 版本标记)、出错静默不破坏宿主。 ## 接入代码 ```html ``` - `data-slot`(必填):展示位 publicToken;缺失时加载器静默不渲染任何内容。 - `data-lang`(可选):`auto`(缺省,按相册 defaultLocale)| `zh-CN` | `en`,非法值回退 `auto`。 - `data-position`(可选):`right`(缺省)| `left`,悬浮侧。 - `data-interval`(可选):轮播间隔毫秒,夹取到 3000–30000,缺省 5000。 同一页面引入多个不同 `data-slot` 的标签即嵌入多个展示位;同一 `data-slot` 重复出现时 后者静默忽略。位置/尺寸/语言等展示位级设置以后台配置为准,data-* 为宿主侧覆盖。 ## CSP 要求(宿主页) | 指令 | 值 | 说明 | | --- | --- | --- | | `script-src` | `{EMBED_ORIGIN}` | 加载 loader.js | | `frame-src` | `{EMBED_ORIGIN}` | 悬浮轮播 iframe(`{EMBED_ORIGIN}/embed/{slot}`) | 组件自身不内联脚本、不加载其他域资源。`frame-ancestors` 由**我们**的 iframe 页在 P5 配置(限制可嵌入站点),不是宿主的关注点。 ## 窄屏行为 viewport 宽度 <480px 时:组件强制降为小尺寸(280px 宽)并默认收起为侧边圆形把手, 点击把手展开。会话内收起状态记忆在 `sessionStorage`(键 `pl-embed-collapsed:{slot}`), 刷新后保持收起,不反复弹出。 ## 安全模型 - **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, ...}`;resize 高度夹取 60–640px。向 iframe 发消息(把手展开)同样以 iframe 源为目标,绝不使用 `*`。 - **零信任宿主数据**:slot token 经 encodeURIComponent 进 URL;lang/position/interval 白名单校验后拼入查询串。 - **存储与 Cookie**:loader 不读写 Cookie、不写宿主 localStorage;仅上述 sessionStorage 收起标记。无埋点、无对外数据上报。 - **DOM 足迹**:仅向 body 追加 wrapper 与收起把手两个 div(全部 inline style, 不注入样式表/类名,不影响宿主 CSS)。 ## 产物与发布 - 发布物即文件本身(不走 npm):`packages/embed-loader/dist/v1/loader.js` (esbuild bundle + minify,IIFE,target es2018,≤5KB 目标)。 - **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 出现/收起/记忆/窄屏)的端到端覆盖见 viewer Playwright `e2e/embed.spec.ts`(P4b Task 4,宿主 fixture 页)。