mcpField
H
hz-store-pwa

hz-store-pwa

github.com/meta-quest开发241187 次安装5 周前更新
代码仓库

关于 hz-store-pwa

hz-store-pwa 是指导开发者将 Web 应用封装为 Meta VR 应用并提交至 Meta Horizon Store 的技能,涵盖 2D 面板和沉浸式 WebXR 模式。从部署和 PWA 配置到 APK 打包与上传均有说明。

主要功能

  1. 012D 面板与沉浸式 WebXR 模式指南
  2. 02Vercel 部署与 PWA 清单配置
  3. 03使用 @meta-quest/bubblewrap-cli 打包 APK
  4. 04签名、Digital Asset Links 与商店上传指南

适用场景

  1. 01准备提交至 Meta Horizon Store 的 Web 应用
  2. 02选择 2D 或沉浸式应用模式
  3. 03将已部署的 PWA 打包为签名 APK
  4. 04排查 TWA 启动和商店上传问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/meta-quest/agentic-tools --skill hz-store-pwa

安装后在 Claude Code 中输入/hz-store-pwa确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
app-modes.md
bubblewrap-packaging.md
manifest-and-icons.md
store-upload.md
troubleshooting.md
vercel-deploy.md

详细介绍

商店 PWA/TWA 技能

指导将 Web 应用封装为 Meta VR 应用并发布到 Meta Horizon 商店的端到端流程。此技能涵盖两种交付模式——2D 窗口化面板和沉浸式 WebXR/VR——并使用同一套流程:构建 Web 应用、部署到 Vercel、添加 PWA 清单和图标、使用 @meta-quest/bubblewrap-cli 打包为已签名的 Meta VR APK,并通过 ovr-platform-util 上传。

命令使用 <…> 标记(例如 <DOMAIN>、<HORIZON_APP_ID>、<team-slug>、<PW>)——运行前请替换为你自己的值。

何时使用此技能

在以下情况下使用此技能:

  • 将 Web 应用(2D 或 WebXR)作为 PWA/TWA 发布到 Meta Horizon 商店
  • 决定应用应作为 2D 面板运行,还是作为沉浸式 WebXR 会话运行
  • 使用 IWSDK 构建 WebXR 应用,并设置已安装 PWA 的自动进入会话功能
  • 将 PWA 部署到 Vercel,并生成有效且可安装的 Web 应用清单和图标
  • 使用 @meta-quest/bubblewrap-cli 将运行中的 PWA 打包为已签名的 Meta VR APK
  • 配置签名密钥库和 Digital Asset Links,以便 TWA 能够启动
  • 使用 ovr-platform-util 将构建版本上传到商店
  • 排查 2D 应用卡在加载界面、沉浸式应用显示 URL 栏、TWA 无法启动或上传受阻等问题

如需更深入的 IWSDK 应用构建指南,请参阅 hz-iwsdk-webxr 技能。如需了解更广泛的商店提交流程(VRC 合规、商店素材、审核跟踪),请参阅 hz-store-submit 技能。

流程概览

完整流程按以下顺序进行。两种模式各自不同的步骤已标出;其他步骤对 2D 和沉浸式模式均相同。

0. Pick app mode        → 2D panel vs immersive WebXR (sets steps 1 + 4)
1. Build the web app    → IWSDK WebXR app (immersive) OR any responsive PWA (2D)
2. Deploy to Vercel     → public HTTPS origin = <DOMAIN>
3. Manifest + icons     → installable web app manifest, PNG icons, live on <DOMAIN>
4. Package as APK       → bubblewrap: keystore, twa-manifest, build, asset links
5. Upload to the Store  → ovr-platform-util upload-quest-build

步骤之间的依赖关系很重要——请参阅末尾的操作顺序。

步骤 0:先选择应用模式

应用模式是最重要的单项决策,只需选择一次。它只会改变后续两项内容:

  1. Web 应用启动时是否自动进入 WebXR 会话(仅限沉浸式模式)。
  2. twa-manifest.json 中的 horizonOSAppMode 值("immersive" 或 "2D")。
2D PWA沉浸式 WebXR PWA
运行方式在 Horizon 上以窗口化 2D 面板运行进入完整的 VR/WebXR 会话
Web 应用任意响应式 PWA(IWSDK 可选)WebXR 应用(IWSDK 是简单的实现路径)
自动进入 requestSession否——不要添加(步骤 1)是——内置于应用中(步骤 1)
horizonOSAppMode"2D"(步骤 4)"immersive"(步骤 4)

horizonOSAppMode 值错误是最常见的故障模式:设为 immersive 的 2D 应用会卡在加载界面;设为 2D 的沉浸式应用会显示浏览器 URL 栏。

完整的决策指南请参阅 references/app-modes.md。

步骤 1:构建 Web 应用

沉浸式 WebXR 应用(IWSDK)

使用 @iwsdk/create 搭建脚手架(唯一受支持的脚手架工具):

npx @iwsdk/create@latest <app-name> --yes --mode vr --no-metaspatial \
  --no-physics --no-locomotion --grabbing

根据应用需求启用 --physics(Havok 重力/碰撞)、--locomotion(在大空间中漫游)和 --grabbing(用手或控制器拾取物体)。街机风格的应用建议使用确定性的手动运动,而非物理效果。

不要重新发明 IWSDK 应用代码。 模板附带的 CLAUDE.md、.claude/skills/iwsdk-* 技能以及 iwsdk-rag MCP 是导入、ECS、XR 输入、物理、UI 和调试相关信息的权威来源。请查询这些资源,而不要凭猜测行事。

从一开始就让沉浸式应用支持自动进入会话。 已安装的沉浸式 PWA 启动时不会显示 2D 页面,因此应用本身必须在加载时启动会话(点按应用图标即为用户激活)。通过 getDigitalGoodsService 设置条件,确保此功能仅在已安装的 PWA 中运行,而不会在浏览器标签页中运行:

const nav = navigator as Navigator & { xr?: { isSessionSupported?: (m:string)=>Promise<boolean> } };
if ("getDigitalGoodsService" in window && nav.xr?.isSessionSupported) {
  nav.xr.isSessionSupported("immersive-vr")
    .then(s => { if (s) world.launchXR(); })   // IWSDK launchXR == requestSession + setup
    .catch(() => {});
}

getDigitalGoodsService 仅适用于设备——请在头显上验证此路径。

2D 窗口应用

任何响应式 Web 应用/PWA 都可以,不需要 IWSDK。它会作为独立面板单实例运行,并在库中拥有自己的条目。确保它是有效且可安装的 PWA(步骤 3),并像任何静态/SPA 网站一样构建和部署(步骤 2)。不要添加上方的自动进入代码。

完整的脚手架标志、项目布局和自动进入的原因,见 references/app-modes.md。

步骤 2:部署到 Vercel

打包前,Web 应用必须已在公开 HTTPS 源站上线——bubblewrap 会从中获取清单和图标。在 Vite 配置中设置 base: "./",然后:

npx -y vercel@latest whoami
npx -y vercel@latest teams ls
npx -y vercel@latest deploy --prod --yes --scope <team-slug>

会得到两个 URL:

  • 规范别名 https://<project>.vercel.app → 公开(200)。后续所有步骤中都使用此 URL 作为 <DOMAIN>。
  • 每次部署生成的哈希 URL …-<team>.vercel.app → 在部署保护机制下返回 401。 不要分享,也不能用作 <DOMAIN>。

确认根路径和清单均返回 200,且清单以 application/manifest+json 类型提供:

curl -s -o /dev/null -w "%{http_code}\n" https://<DOMAIN>/manifest.webmanifest

详情及重新部署与重新构建规则见 references/vercel-deploy.md。

步骤 3:PWA 清单 + 图标

两种模式都需要有效且可安装的清单和 PNG 图标,并且必须在运行 bubblewrap update 前部署到 <DOMAIN>。放置 public/manifest.webmanifest:

{ "name":"…","short_name":"…","description":"…","start_url":"/","scope":"/",
  "display":"standalone","orientation":"landscape",
  "background_color":"#06010f","theme_color":"#0a0418",
  "icons":[
    {"src":"/icons/icon-192.png","type":"image/png","sizes":"192x192","purpose":"any"},
    {"src":"/icons/icon-512.png","type":"image/png","sizes":"512x512","purpose":"any"},
    {"src":"/icons/icon-512-maskable.png","type":"image/png","sizes":"512x512","purpose":"maskable"}]}

在 index.html 的 <head> 中链接清单(<link rel="manifest"> + <meta name="theme-color">

  • <link rel="icon">)。对于多源 2D 应用,请添加 additional_trusted_origins 并在每个源上托管资源链接。

这里没有 ImageMagick/PIL——使用 sharp(npm i -D sharp)从 SVG 生成图标。可遮罩图标必须铺满画布且不透明(不能有透明区域或圆角)。 Vite 会将 public/ 复制到 dist/。

图标脚本和完整清单参考见 references/manifest-and-icons.md。

步骤 4:打包为 Meta VR APK(bubblewrap)

bubblewrap 会将线上 PWA 封装为已签名的 Android APK(可信 Web 活动)。

npm i -g @meta-quest/bubblewrap-cli   # bin: bubblewrap

所需环境已预先配置在 ~/.bubblewrap 中(其中包含独立的 JDK 17 和 Android SDK)。动态查找工具:

KT=$(find ~/.bubblewrap/jdk -path '*/bin/keytool' | head -1)
BT=$(ls -d ~/.bubblewrap/android_sdk/build-tools/* | sort -V | tail -1)

bubblewrap init 使用需要真实 TTY 的交互式 inquirer 向导。非 TTY 调用方(由 Agent 驱动的 Bash)无法回答向导问题,而且没有可绕过交互的参数——请使用下方的脚本化流程。

  1. 签名密钥库——构建前暂停并询问开发者要使用哪个密钥。密钥是永久的:今后的每次更新都必须复用它。可以复用现有密钥库(询问其路径、别名和密码——更新已发布的应用时必需),也可以在可部署的 Web 目录之外生成新的密钥库。

  2. twa-manifest.json(脚本化流程)——根据权威的 TwaManifest 架构编写文件。关键字段:

    "horizonOSAppMode": "immersive"   // ← "immersive" for WebXR | "2D" for a 2D panel app
    

字段值错误是最常见的失败原因(步骤 0)。applicationId = 数字形式的 Horizon App ID("0" 可在不含 IAP 的情况下构建和侧载;进行商店相关工作前请设置真实 ID)。

  1. 构建——update 会重新生成 gradle 并递增版本;密码通过环境变量传递 (没有密码命令行参数):

    cd <twa-dir>
    export BUBBLEWRAP_KEYSTORE_PASSWORD=<PW> BUBBLEWRAP_KEY_PASSWORD=<PW>
    bubblewrap update && bubblewrap build
    # → app-release-signed.apk + app-release-bundle.aab
    "$BT/apksigner" verify --print-certs app-release-signed.apk | grep -i SHA-256  # must == keystore
    
  2. 数字资产链接——如果失败,TWA 将“无法启动”。在同一域名(以及每个受信任的源)上托管 public/.well-known/assetlinks.json,其中包含包名和冒号分隔的十六进制证书 SHA-256 值。重新部署后, 执行 curl https://<DOMAIN>/.well-known/assetlinks.json 进行确认。

**安全性:**密钥库和应用密钥绝不能上传到公开主机——使用 curl -o /dev/null -w "%{http_code}" https://<DOMAIN>/android.keystore 验证(预期返回 404)。 请备份密钥库。

完整的密钥库处理说明、twa-manifest.json 完整模板、构建验证和资产链接详情见 references/bubblewrap-packaging.md。

步骤 5:上传到 Meta Horizon Store

hzdb / metavr 仅适用于设备,无法上传。请使用 ovr-platform-util—— 同一条命令适用于 2D 和 WebXR 构建。上传前,请在真实头显上检查签名后的 APK(metavr app install,启动应用,快速查看 metavr log)—— Store 审核不是发现启动崩溃的好时机:

./ovr-platform-util upload-quest-build \
  --app-id <HORIZON_APP_ID> --app-secret <SECRET> \
  --apk app-release-signed.apk \
  --channel ALPHA --age-group MIXED_AGES \
  --notes "…" --disable-progress-bar

必需参数:--app-id、--apk、--channel、--age-group (TEENS_AND_ADULTS | MIXED_AGES | CHILDREN),以及 --app-secret 或 --token。 频道:测试使用 ALPHA/BETA/RC,生产使用 STORE。认证凭据为应用的 App Secret(Dashboard → 应用 → API 选项卡)或用户令牌——请向用户询问,切勿自行编造。

首次上传时可能遇到的阻碍:must first agree to our Developer Distribution Agreement——组织管理员必须先在 https://developers.meta.com/horizon/manage/organizations/<ORG_ID>/legal-documents/ 签署一次。暂停操作, 询问用户,然后重试相同的命令。

工具下载、认证和 DDA 阻碍详见 references/store-upload.md。

操作顺序

  • 尽早决定使用 2D 还是沉浸式模式(步骤 0)——这会决定自动进入步骤(仅限 WebXR 的步骤 1)和 horizonOSAppMode 的值(步骤 4)。
  • 在执行 bubblewrap update 前,清单和图标必须已上线——该命令会从 <DOMAIN> 获取它们。
  • TWA 启动前,资源链接必须已生效——对每个可信来源都如此。
  • 仅修改 Web 内容时,只需重新部署到 Vercel——已安装的 TWA 会在下次启动时获取更改。只有原生部分有改动时(ID、名称、图标、版本、应用模式、打包),才需要重新构建并重新上传 APK。

注意事项

  • horizonOSAppMode 不匹配是头号故障原因——将 "2D" 设为 immersive 会卡在加载画面;将 "immersive" 设为 2D 会显示 URL 栏。修正该值并重新构建。
  • 签名密钥是永久的——每次更新都必须重用相同的密钥库、别名和 packageId。密钥库丢失意味着必须新建应用条目。请备份,并将其保存在可部署 Web 目录之外。
  • bubblewrap init 需要 TTY——Agent 必须使用脚本化流程(手动编写 twa-manifest.json + update + build)。
  • 自动进入仅适用于沉浸式模式——切勿将 launchXR() 代码片段添加到 2D 应用中,并始终通过 getDigitalGoodsService 进行门控,避免它在浏览器标签页中触发。
  • getDigitalGoodsService 仅适用于设备——无法在桌面浏览器或模拟器中验证自动进入功能;请在头显上测试。
  • 使用 Vercel 的规范别名——哈希形式的单次部署 URL 在部署保护下会返回 401,不能用作 <DOMAIN>。
  • 可蒙版图标必须铺满画布且不透明——透明部分或圆角会在平台应用蒙版后产生明显瑕疵。
  • 资源链接决定应用能否启动——在 /.well-known/assetlinks.json 上线且包含匹配的包名和证书 SHA-256 之前,TWA“无法启动”。
  • DDA 会阻止首次上传——只有组织管理员可以签署;这是首次上传最常见的失败原因。

参考资料

  • 应用模式:2D 与沉浸式 — 决策指南、IWSDK 脚手架、项目布局,以及自动进入会话的设计依据。
  • Vercel 部署 — Vite 配置、部署命令、规范 URL 与哈希 URL 的区别,以及重新部署与重新构建的规则。
  • PWA 清单与图标 — 完整清单参考、多来源设置,以及 sharp 图标生成脚本。
  • Bubblewrap 打包 — 密钥库管理、twa-manifest.json 模板、构建验证、资源链接和安全性。
  • Store 上传 — ovr-platform-util 下载、认证、上传命令,以及 Developer Distribution Agreement 阻碍。
  • 故障排查 — 整个流程中的常见故障模式及修复方法。

评论

支持 Markdown