Skip to content

Form-Page 配置指南

概述

form-page 是 AgentOS 中用于嵌入外部页面或技能的核心输出 UI 规范,通过 iframe 方式加载外部内容,支持丰富的配置选项和权限控制。本文档详细介绍了 form-page 代码块中 JSON 配置的各项参数及其作用。

基本语法

form-page 使用 Markdown 代码块语法,以 form-page 作为语言标识符:

markdown
```form-page
{
    // JSON 配置内容
}
```

完整配置示例

markdown
```form-page
{
    "url": "http://10.1.5.65:5173/test-agentos",
    "name": "知识引擎接口文档",
    "pageType": "static",
    "data": {},
    "width": "100%",
    "height": "400px",
    "allow": "cross-origin-isolated; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; camera; microphone; geolocation; fullscreen",
    "sandbox": "allow-same-origin allow-scripts allow-downloads",
    "credentialless": false,
    "allowModelPreview": true,
    "config": {
        "skill_config": {},
        "info": {
            "task_id": "",
            "agent_id": "1955547108685766661",
            "message_id": "7f9b76e9-4253-4ccf-8830-15d0f6e55df2",
            "session_id": "4e0f95f1-5e19-43d0-b5b4-bb3b008bb044",
            "skill_id": "1760544913533448192",
            "skill_sort": 0,
            "route_id": "1955842087039696897"
        }
    }
}
```

配置参数详解

基础配置

url / code

参数类型必填默认值描述
urlstring是*-页面的 URL 地址,支持 占位符动态替换
codestring是*-Base64 编码的 HTML 代码,与 url 二选一

注意: urlcode 必须提供其中一个。url 用于加载外部页面,code 用于直接渲染 HTML 内容。

URL 占位符示例:

json
{
	"url": "http://example.com/user/{{user.id}}/profile",
	"data": {
		"user": {
			"id": "12345"
		}
	}
}

最终生成的 URL:http://example.com/user/12345/profile

name

参数类型必填默认值描述
namestring-名称,用于标识和显示

pageType

参数类型必填默认值描述
pageTypestring"static"页面类型,控制与 AgentOS 的交互方式

页面类型说明:

描述适用场景特性
"static"纯展示页面图片、PDF、文档预览等静态内容无需与 AgentOS 交互,轻量级
"tudouApp"交互式应用表单、数据处理、需要调用 AgentOS API 的技能可访问 AgentOS 功能和数据

data

参数类型必填默认值描述
dataobject{}传递给技能的初始数据

使用示例:

json
{
	"data": {
		"formDefaults": {
			"username": "admin",
			"email": "admin@example.com"
		},
		"apiConfig": {
			"baseUrl": "https://api.example.com"
		}
	}
}

尺寸配置

width

参数类型必填默认值描述
widthstring/number"100%"iframe 宽度

支持的值:

值类型示例描述
百分比"100%", "50%"相对于父容器的百分比
像素值"400px", 400固定像素宽度
预设值"mini", "default", "middle"预定义的尺寸规格

预设值说明:

  • "mini": 小尺寸,适合简单组件和工具
  • "default": 默认尺寸,适合通用场景
  • "middle": 中等尺寸,适合表单和复杂组件

height

参数类型必填默认值描述
heightstring/number"400px"iframe 高度

支持的值:

  • 像素值:"400px", 400, "600px"
  • 百分比:"100%", "80%"
  • 自适应:"auto" - 根据 iframe 内部内容自动调整高度

自适应高度说明:

当设置 height: "auto" 时,iframe 高度会根据内部页面的实际内容高度自动调整。这需要 iframe 内部页面配合实现高度通知机制。

外部监听高度变化:

javascript
// AgentOS 系统会自动监听 iframe 内部的高度变化通知
scp.get(target).$Bus.on("page-resize", params => {
	const hVal = `${params[0]}px`;
	const targetHeight = target.style.height;
	if (hVal !== targetHeight) {
		target.style.height = hVal;
	}
});

iframe 内部通知高度变化:

javascript
function handleResize() {
	// 获取顶层容器元素的实际高度
	const height = document.querySelector("#upload-form")?.scrollHeight;
	// 通知外部调整 iframe 高度(+2px 用于避免滚动条)
	scp.emit("page-resize", height + 2);
}

let observer;
onMounted(() => {
	// 初始化时计算一次高度
	handleResize();

	// 使用 MutationObserver 监听 DOM 变化,自动调整高度
	observer = new MutationObserver(() => {
		handleResize();
	});
	observer.observe(document.body, {
		childList: true,
		subtree: true,
		attributes: true
	});
});

onBeforeUnmount(() => {
	observer?.disconnect();
});

权限配置

allow - 浏览器功能权限

参数类型必填默认值描述
allowstring见下方默认值控制 iframe 可访问的浏览器功能和 API 权限

默认权限:

"cross-origin-isolated; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; camera; microphone; geolocation; fullscreen"

权限列表:

权限名称描述用途场景
cross-origin-isolated跨域隔离启用高精度计时器、SharedArrayBuffer 等高级功能
accelerometer加速度传感器游戏、运动应用、设备方向检测
autoplay自动播放视频、音频自动播放
clipboard-write剪贴板写入复制文本、图片到剪贴板
encrypted-media加密媒体播放受保护的媒体内容(DRM)
gyroscope陀螺仪获取设备旋转信息,VR/AR 应用
picture-in-picture画中画视频画中画模式
camera摄像头视频通话、拍照、扫码等功能
microphone麦克风语音输入、录音、语音通话
geolocation地理位置地图服务、位置相关功能
fullscreen全屏全屏显示内容
payment支付在线支付功能
usbUSB 设备访问 USB 设备
bluetooth蓝牙蓝牙设备连接
magnetometer磁力计指南针功能
ambient-light-sensor环境光传感器自动调节屏幕亮度
display-capture屏幕捕获屏幕录制、截图
midiMIDI 设备音乐制作、MIDI 设备控制
serial串口通信硬件设备通信
hidHID 设备人机接口设备(键盘、鼠标等)

配置格式:

json
// 单个权限
"allow": "camera"

// 多个权限(支持多种分隔符)
"allow": "camera; microphone; fullscreen"
"allow": "camera, microphone, fullscreen"
"allow": "camera microphone fullscreen"
"allow": "camera; microphone, fullscreen"

权限合并规则:

系统会自动将用户配置的权限与默认权限进行合并去重:

json
// 用户配置
"allow": "payment; usb; camera"

// 最终生成的权限(合并去重后)
"allow": "cross-origin-isolated; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; camera; microphone; geolocation; fullscreen; payment; usb"

sandbox - 安全沙箱权限

参数类型必填默认值描述
sandboxstring见下方默认值控制 iframe 的安全沙箱权限,限制可执行操作

默认权限:

"allow-same-origin allow-scripts allow-downloads"

权限列表:

权限名称描述用途场景
allow-same-origin同源访问访问同源资源和 API
allow-scripts脚本执行执行 JavaScript 代码
allow-downloads文件下载下载文件到本地
allow-forms表单提交提交表单数据
allow-popups弹出窗口打开新窗口或标签页
allow-modals模态框显示 alert、confirm 等弹框
allow-orientation-lock屏幕方向锁定锁定屏幕方向
allow-pointer-lock指针锁定游戏中的鼠标锁定
allow-popups-to-escape-sandbox弹窗逃脱沙箱弹出窗口不受沙箱限制
allow-presentation演示 API投屏、演示功能
allow-storage-access-by-user-activation用户激活的存储访问用户操作后访问存储
allow-top-navigation顶层导航导航到顶层窗口
allow-top-navigation-by-user-activation用户激活的顶层导航用户操作后的顶层导航
allow-top-navigation-to-custom-protocols自定义协议导航导航到自定义协议

配置格式:

json
// 单个权限
"sandbox": "allow-same-origin"

// 多个权限(空格分隔)
"sandbox": "allow-same-origin allow-scripts allow-forms"

权限合并规则:

与 allow 属性类似,sandbox 权限也会自动合并去重:

json
// 用户配置
"sandbox": "allow-forms allow-popups allow-same-origin"

// 最终生成的权限(合并去重后)
"sandbox": "allow-same-origin allow-scripts allow-downloads allow-forms allow-popups"

credentialless - 无凭据模式

参数类型必填默认值描述
credentiallessbooleanfalse控制 iframe 是否在无凭据模式下加载,用于跨域隔离兼容

说明:

  • 当设置为 true 时,iframe 将在无凭据模式下加载内容
  • 有助于解决 Cross-Origin Isolation 相关的兼容性问题
  • 只有明确设置为 true 时才会添加 credentialless 属性到 iframe

功能配置

allowModelPreview - 模型预览

参数类型必填默认值描述
allowModelPreviewboolean-是否显示全屏预览按钮

说明:

  • 当设置为 true 时,iframe 右下角会显示全屏预览按钮
  • 用户可以点击按钮将技能内容全屏显示

系统配置

config - 系统配置对象

参数类型必填默认值描述
configobject{}AgentOS 系统配置和业务流转数据

config 结构说明:

json
{
	"config": {
		"skill_config": {}, // 技能特定配置
		"info": {
			// 业务流转信息
			"task_id": "",
			"agent_id": "",
			"message_id": "",
			"session_id": "",
			"skill_id": "",
			"skill_sort": 0,
			"route_id": ""
		}
	}
}

info 字段说明:

字段名类型描述
task_idstring任务 ID
agent_idstring智能体 ID
message_idstring消息 ID
session_idstring会话 ID
skill_idstring技能 ID
skill_sortnumber技能排序
route_idstring路由 ID

配置场景示例

场景 1:简单静态页面

适用于展示文档、图片等静态内容:

markdown
```form-page
{
    "url": "https://example.com/docs/api.html",
    "name": "API 文档",
    "pageType": "static",
    "width": "100%",
    "height": "600px"
}
```

场景 2:交互式表单

适用于需要与用户交互的表单页面:

markdown
```form-page
{
    "url": "https://example.com/form/user-info",
    "name": "用户信息表单",
    "pageType": "tudouApp",
    "width": "middle",
    "height": "500px",
    "data": {
        "userId": "12345",
        "defaultValues": {
            "name": "张三",
            "email": "zhangsan@example.com"
        }
    },
    "allow": "camera; microphone; geolocation",
    "sandbox": "allow-same-origin allow-scripts allow-forms allow-popups"
}
```

场景 3:多媒体应用

适用于需要访问摄像头、麦克风等设备的应用:

markdown
```form-page
{
    "url": "https://example.com/video-call",
    "name": "视频通话",
    "pageType": "tudouApp",
    "width": "100%",
    "height": "400px",
    "allow": "camera; microphone; autoplay; fullscreen",
    "sandbox": "allow-same-origin allow-scripts allow-modals",
    "credentialless": true,
    "allowModelPreview": true
}
```

场景 4:跨域隔离应用

适用于需要高精度计时器或 SharedArrayBuffer 的应用:

markdown
```form-page
{
    "url": "https://example.com/high-performance-app",
    "name": "高性能计算应用",
    "pageType": "tudouApp",
    "width": "100%",
    "height": "600px",
    "allow": "cross-origin-isolated; clipboard-write",
    "credentialless": true
}
```

最佳实践

1. 权限最小化原则

只授予 Page 必需的权限,避免过度授权:

json
// ❌ 不推荐:授予所有权限
"allow": "camera; microphone; geolocation; usb; bluetooth; payment"

// ✅ 推荐:只授予必需权限
"allow": "camera; microphone"  // 仅视频通话需要

2. 合理设置页面类型

根据 Page 的实际需求选择合适的页面类型:

  • 纯展示内容使用 "static"
  • 需要与 AgentOS 交互使用 "tudouApp"

3. 响应式尺寸设计

优先使用百分比或预设值,确保在不同屏幕尺寸下的良好显示:

json
// ✅ 推荐:响应式设计
"width": "100%",
"height": "400px"

// ✅ 推荐:使用预设值
"width": "middle"

4. 数据传递规范

通过 data 字段传递结构化的初始数据:

json
{
	"data": {
		"config": {
			"theme": "dark",
			"language": "zh-CN"
		},
		"initialData": {
			"user": { "id": "123", "name": "用户名" }
		}
	}
}

5. 错误处理

确保 URL 有效性和数据格式正确:

json
// ✅ 确保 URL 可访问
"url": "https://valid-domain.com/path"

// ✅ 确保 JSON 格式正确
{
    "url": "...",
    "data": {}  // 注意:最后一项不要有逗号
}

常见问题

Q1: Page 页面无法加载?

可能原因:

  • URL 不可访问
  • 跨域策略限制
  • 权限配置不正确

解决方案:

  1. 检查 URL 是否可以在浏览器中正常访问
  2. 确认目标页面支持 iframe 嵌入
  3. 调整 allowsandbox 权限配置

Q2: 无法访问摄像头/麦克风?

解决方案:

json
{
	"allow": "camera; microphone",
	"sandbox": "allow-same-origin allow-scripts"
}

Q3: 跨域隔离功能不生效?

解决方案:

json
{
	"allow": "cross-origin-isolated",
	"credentialless": true
}

Q4: 表单无法提交?

解决方案:

json
{
	"sandbox": "allow-same-origin allow-scripts allow-forms"
}

Q5: 弹窗被阻止?

解决方案:

json
{
	"sandbox": "allow-same-origin allow-scripts allow-popups allow-modals"
}

Page 内部开发指南

数据获取

获取外部传入的数据

在 form-page 内部,可以通过 scp.chat.context.get() 方法获取外部传入的配置数据:

javascript
// 获取外部传入的完整配置数据
const scpData = await scp?.chat?.context.get();

// 访问具体的配置信息
const messageId = scpData?.config?.info?.message_id;
const skillData = scpData?.data; // 对应配置中的 data 字段
const skillConfig = scpData?.config?.skill_config;

获取用户 Token

Page 内部可以获取当前用户的认证 Token:

javascript
// 获取用户 Token
const token = await scp.env.token.get();
console.log("用户 Token:", token);

应用初始化

Vue3 工程化应用初始化

以下是一个完整的 Vue3 应用初始化示例,展示了如何在 iframe 环境中正确初始化应用:

typescript
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
import { getUserInfoByTokenApi, getUserInfo } from "./api/user";
import { useUserStore } from "./stores/user";
import storage from "./utils/storage";

const app = createApp(App);
const pinia = createPinia();
app.use(pinia);

// 应用初始化函数
async function initializeApp() {
	try {
		// 获取用户 Token
		const token = await scp.env.token.get();
		console.log("Page 获取到 Token:", token);

		if (token) {
			// 通过 Token 获取用户信息
			const { data: userInfoData } = await getUserInfoByTokenApi({ token });

			const userInfo = {
				tenantId: userInfoData.tenantId,
				userId: userInfoData.id,
				userName: userInfoData.username
			};

			// 存储 Token 和用户信息到本地存储
			const t0 = Date.now();
			storage.set({
				expires_time: Date.now() + 8888888888888888 * 1000 - (Date.now() - t0),
				token,
				access_token: token, // 兼容旧版 SAAS 应用
				userInfo: JSON.stringify(userInfo)
			});

			// 初始化并挂载 Vue 应用
			app.use(router).mount("#app");
			console.log("Vue 应用已成功初始化并挂载");
		} else {
			console.warn("未获取到 Token,使用默认配置初始化应用");
			app.use(router).mount("#app");
		}
	} catch (error) {
		console.error("初始化应用时发生错误:", error);
		// 即使出错也要挂载应用,避免白屏
		app.use(router).mount("#app");
	}
}

// 检测运行环境并初始化
if (window.self !== window.top) {
	// 当前页面被嵌入在 iframe 中( Page 环境)
	console.log("检测到 iframe 环境,等待 scp 加载完成");
	scp.onLoad(() => {
		initializeApp();
	});
} else {
	// 当前页面是顶层页面(独立运行)
	console.log("检测到独立运行环境");
	try {
		const { data } = await getUserInfo();
		const { setUserInfo } = useUserStore();
		setUserInfo(data);
		app.use(router).mount("#app");
	} catch (error) {
		console.error("独立环境初始化失败:", error);
		app.use(router).mount("#app");
	}
}

单页面应用初始化

对于简单的单页面应用,可以参考以下模式:

javascript
// 检测是否在 iframe 环境中
if (window.self !== window.top) {
	// iframe 环境
	scp.onLoad(async () => {
		try {
			// 获取外部传入的数据
			const scpData = await scp?.chat?.context.get();
			const token = await scp.env.token.get();

			// 初始化页面数据
			initializePage(scpData, token);
		} catch (error) {
			console.error("iframe 环境初始化失败:", error);
			// 使用默认配置初始化
			initializePage();
		}
	});
} else {
	// 独立环境
	initializePage();
}

function initializePage(scpData = null, token = null) {
	// 页面初始化逻辑
	console.log("页面初始化", { scpData, token });

	// 如果有外部数据,使用外部数据初始化
	if (scpData?.data) {
		// 使用 scpData.data 中的数据初始化表单或页面状态
		populateFormData(scpData.data);
	}

	// 如果有 token,设置 API 请求头
	if (token) {
		setApiToken(token);
	}
}

React 应用初始化

以下是在 form-page 中使用 React 开发应用的初始化示例:

ts
import { useEffect, useState } from "react"; // 新增useState
import { useLocation } from "react-router";

import { storage } from "@pt/utils/modules/storage";

import { getLoginUrl, ROUTER_WHITE_LIST } from "@/config";

import { getUserInfoByTokenApi } from '@/api/modules/login'

function loadScpSkillScript(): Promise<void> {
  return new Promise((resolve, reject) => {
    // @ts-ignore
    if (typeof scp !== 'undefined') {
      console.log('[AuthGuard] scp 对象已存在,跳过加载');
      resolve();
      return;
    }

    const script = document.createElement('script');
    script.src = '/scp.skill.js';
    script.async = false;
    script.onload = () => {
      console.log('[AuthGuard] scp.skill.js 加载成功');
      resolve();
    };
    script.onerror = () => {
      console.error('[AuthGuard] scp.skill.js 加载失败');
      reject(new Error('Failed to load scp.skill.js'));
    };
    document.head.appendChild(script);
  });
}

interface AuthGuardProps {
  children: React.ReactNode;
}

async function initConfig() {
  // 安全检查:确保 scp 对象存在
  // @ts-ignore
  if (typeof scp === 'undefined' || !scp.env || !scp.env.token) {
    console.warn('[AuthGuard] scp 对象未完全初始化,跳过 scp.env 配置');
    return;
  }

  try {
    // @ts-ignore
    await scp.env.token.set(storage.get("token"));
    // @ts-ignore
    const token = await scp.env.token.get()

    if (token) {
      const { data: userInfoData } = await getUserInfoByTokenApi({
        token,
      })
      let userInfo = {
        tenantId: userInfoData.tenantId,
        userId: userInfoData.id,
        userName: userInfoData.username,
      }

      // 存储token
      const t0 = Date.now()
      storage.set({
        expires_time: Date.now() + 8888888888888888 * 1000 - (Date.now() - t0),
        token,
        access_token: token, // 兼容 旧的 SAAS 应用,会使用 access_token
        userInfo: JSON.stringify(userInfo),
      })
    } else {
      // 如果没有 token,可以在这里做一些处理,比如跳转到登录页或者显示错误信息
      console.warn('未获取到 Token,Vue 应用未初始化。')
    }
  } catch (error) {
    console.error('[AuthGuard] scp.env 初始化失败:', error);
  }
}

/**
 * 路由守卫组件
 * 用于验证用户登录状态和权限
 */
export default function AuthGuard({ children }: AuthGuardProps) {
  const location = useLocation();
  // 新增加载状态:true=还在初始化/校验,false=已完成
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    let isCancelled = false;
    const checkAuth = async () => {
      try {
        const currentPath = location.pathname;
        const cookie_token = storage.get("token");
        const cookie_userInfoStr = storage.get("userInfo");

        console.log('[AuthGuard] 路由守卫检查:', {
          currentPath,
          hasToken: !!cookie_token,
          hasUserInfo: !!cookie_userInfoStr,
          token: cookie_token,
          userInfo: cookie_userInfoStr,
        });

        // 先加载 scp.skill.js
        try {
          await loadScpSkillScript();
        } catch (error) {
          console.warn('[AuthGuard] 加载 scp.skill.js 失败,但继续执行', error);
        }

        // 先执行初始化,确保scp.env配置完成
        if (window.self !== window.top) {
          await initConfig();
        }

        if (isCancelled) return;

        // 白名单路由直接放行
        if (ROUTER_WHITE_LIST.includes(currentPath)) {
          console.log('[AuthGuard] 白名单路由,直接放行');
          setIsLoading(false); // 加载完成,允许渲染
          return;
        }

        // 无认证信息则跳转登录
        if (!cookie_token || !cookie_userInfoStr) {
          console.error('[AuthGuard] 缺少认证信息,准备跳转登录页');
          storage.clear();
          window.location.href = getLoginUrl();
          return;
        }

        // 认证通过,加载完成
        console.log('[AuthGuard] 认证通过,允许访问');
        setIsLoading(false);
      } catch (error) {
        console.error('[AuthGuard] 认证检查/初始化失败:', error);
        if (!isCancelled) {
          storage.clear();
          window.location.href = getLoginUrl();
        }
      }
    };

    checkAuth();

    return () => {
      isCancelled = true;
      setIsLoading(true); // 卸载时重置加载状态
      console.log('[AuthGuard] 路由变化/组件卸载,终止后续操作');
    };
  }, [location.pathname]);

  // 关键:加载中不渲染子组件,避免提前读取未初始化的scp.env
  if (isLoading) {
    // 可替换为项目的加载组件,比如 <Spin />
    return <div>加载中...</div>;
  }

  return <>{children}</>;
}

数据持久化

保存技能数据

技能可以将数据保存到 AgentOS 系统中,用于后续回填或恢复:

javascript
// 保存数据示例
async function saveSkillData(data) {
	try {
		const scpData = await scp?.chat?.context.get();
		const messageId = scpData?.config?.info?.message_id;

		if (messageId) {
			// 保存数据到指定的命名空间和键
			await saveDataByMessageId({
				namespace: "action_ui",
				key: messageId,
				value: data
			});
			console.log("技能数据保存成功");
		}
	} catch (error) {
		console.error("保存技能数据失败:", error);
	}
}

// 获取已保存的数据
async function loadSkillData() {
	try {
		const scpData = await scp?.chat?.context.get();
		const messageId = scpData?.config?.info?.message_id;

		if (messageId) {
			const { data } = await getSkillDataByMessageId({
				namespace: "action_ui",
				key: messageId
			});

			if (data?.value) {
				console.log("加载已保存的数据:", data.value);
				return data.value;
			}
		}
	} catch (error) {
		console.error("加载技能数据失败:", error);
	}
	return null;
}

最佳实践

1. 环境检测

始终检测运行环境,确保代码在不同环境下都能正常工作:

javascript
// 推荐的环境检测方式
const isIframeEnvironment = window.self !== window.top;
const isScpAvailable = typeof scp !== "undefined";

if (isIframeEnvironment && isScpAvailable) {
	// iframe + scp 环境
	scp.onLoad(() => {
		initializeApp();
	});
} else if (isIframeEnvironment) {
	// 仅 iframe 环境,无 scp
	console.warn("iframe 环境但 scp 不可用");
	initializeApp();
} else {
	// 独立环境
	initializeApp();
}

2. 错误处理

始终包含错误处理逻辑,确保应用的健壮性:

javascript
async function safeApiCall(apiFunction, fallbackValue = null) {
	try {
		return await apiFunction();
	} catch (error) {
		console.error("API 调用失败:", error);
		return fallbackValue;
	}
}

// 使用示例
const scpData = await safeApiCall(() => scp?.chat?.context.get(), {});
const token = await safeApiCall(() => scp.env.token.get(), null);

3. 数据验证

对外部传入的数据进行验证:

javascript
function validateScpData(scpData) {
	if (!scpData || typeof scpData !== "object") {
		return false;
	}

	// 验证必要字段
	const requiredFields = ["config"];
	return requiredFields.every(field => field in scpData);
}

// 使用示例
const scpData = await scp?.chat?.context.get();
if (validateScpData(scpData)) {
	// 使用验证过的数据
	initializeWithData(scpData);
} else {
	// 使用默认配置
	initializeWithDefaults();
}

Cross-Origin Isolation 配置指南

什么是 Cross-Origin Isolation

Cross-Origin Isolation(跨域隔离)是一种 Web 安全机制,它允许网页访问某些强大的 Web API,如高精度计时器(performance.now())和 SharedArrayBuffer。当页面处于跨域隔离状态时,crossOriginIsolated 属性会返回 true

启用 Cross-Origin Isolation 的方法

方法一:HTTP 响应头配置(推荐)

在服务器端设置以下 HTTP 响应头:

http
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin

Nginx 配置示例:

nginx
server {
    listen 80;
    server_name your-domain.com;

    location / {
        add_header Cross-Origin-Embedder-Policy "require-corp" always;
        add_header Cross-Origin-Opener-Policy "same-origin" always;
        # 其他配置...
    }
}

Apache 配置示例:

apache
<VirtualHost *:80>
    ServerName your-domain.com

    Header always set Cross-Origin-Embedder-Policy "require-corp"
    Header always set Cross-Origin-Opener-Policy "same-origin"
    # 其他配置...
</VirtualHost>

Node.js Express 配置示例:

javascript
app.use((req, res, next) => {
	res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
	res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
	next();
});

方法二:Meta 标签配置

在 HTML 页面的 <head> 部分添加:

html
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp" />
<meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin" />

Form-Page 中的 Cross-Origin Isolation

配置示例

json
{
	"url": "https://your-domain.com/high-performance-app",
	"name": "高性能计算应用",
	"pageType": "tudouApp",
	"allow": "cross-origin-isolated; clipboard-write",
	"credentialless": true,
	"height": "600px"
}

关键配置项

  1. allow 权限:必须包含 cross-origin-isolated
  2. credentialless 属性:设置为 true 有助于跨域隔离兼容性
  3. 目标页面配置:iframe 内的页面必须正确设置 COEP 和 COOP 头

验证 Cross-Origin Isolation 状态

在浏览器控制台检查

javascript
// 检查当前页面是否处于跨域隔离状态
console.log("Cross-Origin Isolated:", crossOriginIsolated);

// 检查可用的高精度 API
if (crossOriginIsolated) {
	console.log("高精度计时器可用:", performance.now());
	console.log("SharedArrayBuffer 可用:", typeof SharedArrayBuffer !== "undefined");
} else {
	console.log("跨域隔离未启用,某些 API 可能不可用");
}

在技能内部检查

javascript
// 在 form-page 内部检查
scp.onLoad(async () => {
	const isIsolated = self.crossOriginIsolated;
	console.log("技能页面跨域隔离状态:", isIsolated);

	if (isIsolated) {
		// 可以安全使用高精度 API
		const highResTime = performance.now();
		console.log("高精度时间戳:", highResTime);
	}
});

常见问题和解决方案

Q1: crossOriginIsolated 返回 false

可能原因:

  • 服务器未设置正确的 COEP 和 COOP 响应头
  • 页面中包含不兼容的跨域资源
  • iframe 权限配置不正确

解决方案:

  1. 确保服务器设置了正确的响应头
  2. 检查所有跨域资源是否设置了 Cross-Origin-Resource-Policy
  3. 在 form-page 配置中添加 cross-origin-isolated 权限

参考文档:Cross-Origin Isolation 配置指南

Q2: SharedArrayBuffer 不可用

解决方案:

json
{
	"allow": "cross-origin-isolated",
	"credentialless": true,
	"sandbox": "allow-same-origin allow-scripts"
}

Q3: 第三方资源加载失败

当启用 Cross-Origin Isolation 后,所有跨域资源都需要明确的 CORP 头。

解决方案:

http
# 对于图片、样式等资源
Cross-Origin-Resource-Policy: cross-origin

# 对于同源资源
Cross-Origin-Resource-Policy: same-origin

# 对于特定源的资源
Cross-Origin-Resource-Policy: same-site

最佳实践

1. 渐进式启用

javascript
// 检测支持情况后再启用高级功能
if (crossOriginIsolated && typeof SharedArrayBuffer !== "undefined") {
	// 使用高性能 API
	initHighPerformanceFeatures();
} else {
	// 降级到兼容模式
	initCompatibilityMode();
}

2. 资源预检

javascript
// 预检关键资源的可用性
async function checkResourceAvailability() {
	const checks = {
		sharedArrayBuffer: typeof SharedArrayBuffer !== "undefined",
		highResTimer: performance.now() !== performance.now(),
		crossOriginIsolated: self.crossOriginIsolated
	};

	console.log("功能可用性检查:", checks);
	return checks;
}

3. 错误处理

javascript
// 优雅处理 Cross-Origin Isolation 相关错误
try {
	const buffer = new SharedArrayBuffer(1024);
	// 使用 SharedArrayBuffer
} catch (error) {
	console.warn("SharedArrayBuffer 不可用,使用替代方案:", error);
	// 使用 ArrayBuffer 替代
	const buffer = new ArrayBuffer(1024);
}

性能优化建议

1. 预加载关键资源

html
<!-- 预加载并设置正确的 CORP -->
<link rel="preload" href="/critical-resource.js" as="script" crossorigin="anonymous" />

2. 资源缓存策略

javascript
// 利用 Cross-Origin Isolation 的缓存优势
if (crossOriginIsolated) {
	// 可以使用更激进的缓存策略
	cacheStrategy = "aggressive";
} else {
	cacheStrategy = "conservative";
}

兼容性说明

官方文档

权限策略


版本信息: v1.0 更新时间: 2025-04-12 适用版本: AgentOS v2.0+

本文档将随着 AgentOS 系统的更新而持续维护和完善。如有问题或建议,请联系开发团队。

Released under the MIT License.