# 安装与恢复 .wuapp 文件

打包、验证、安装、挂载与更新带签名且由宿主授权的应用。

本页介绍 WU Framework `4.0.0`。升级现有项目之前，请阅读 [Framework 4.0 与 CLI 1.2 迁移指南](/zh-cn/docs/guides/releases/)。

`.wuapp` 文件包含一个已构建的应用、清单、文件及可选签名。它安装到 Cache Storage 的 `/@wuapp/<name>@<version>/` 路径。这是宿主同源的虚拟路径；签名确认可信密钥和完整性，不会提供跨源隔离。

<span id="preparar-el-paquete"></span>

<span class="section-anchor" id="section-1"></span>

## 准备安装包

```sh
wu build --native
wu pack --keygen keys/catalog.pem --json
wu pack catalog --version 1.2.0 --sign keys/catalog.pem --out catalog-1.2.0.wuapp
wu serve 4100
```

私钥应保存在公开内容之外。将输出的公钥 JWK 保存到宿主的 `catalog-public-key.json`。`catalog` 必须存在于 `dist/wu-manifest.json` 中。选项、格式与错误见 [wu pack](/zh-cn/docs/cli/pack/)。

<span id="instalar-y-montar"></span>

<span class="section-anchor" id="section-2"></span>

## 安装与挂载

```js
import { wu } from 'wu-framework/core';
import 'wu-framework/wuapp';

await wu.init({ apps: [] });
const publicKey = await fetch('/catalog-public-key.json').then(r => r.json());
const installed = await wu.wuapps.install('/catalog-1.2.0.wuapp', {
  trust: [publicKey],
  grants: ['events.emit:catalog:*'],
});
const worker = await wu.wuapps.check();
if (!worker.ok) throw new Error(worker.reason);
await wu.mount(installed.name, '#catalog-slot');
```

通过 HTTPS 或 localhost 提供安装包和公钥，并在宿主创建 `#catalog-slot`。`wu build --native` 生成的 Service Worker 必须控制页面，首次访问后可能需要刷新。`check().ok` 仅检查控制器是否存在，不证明任意 Service Worker 都实现了该路由。还应验证安装入口的 HTTP 响应。

`source` 接受 URL、`File`/`Blob`、`ArrayBuffer`、`Uint8Array` 或已解析对象。写入缓存前会验证结构、路径、ECDSA P-256 签名和各文件的 SHA-256。宿主决定 `trust` 与 `grants`；`trust: "any"` 接受任意签名密钥，`allowUnsigned: true` 允许无签名包。宿主在其他路径提供共享模块时，可用 `frameworkBase` 替换 `/@modules/`。

<span id="restaurar-actualizar-y-retirar"></span>

<span class="section-anchor" id="section-3"></span>

## 恢复、更新与移除

```js
await wu.wuapps.restore();
const inventory = await wu.wuapps.installed();

await wu.unmount('catalog', { force: true });
await wu.wuapps.install('/catalog-1.3.0.wuapp', {
  trust: [publicKey], grants: ['events.emit:catalog:*'],
});
await wu.mount('catalog', '#catalog-slot');

await wu.unmount('catalog', { force: true });
const removed = await wu.wuapps.uninstall('catalog');
```

`restore()` 注册持久化记录并返回恢复成功的项，但不会挂载。`installed()` 读取已保存的索引。安装其他版本会替换旧版本；更新或卸载已挂载或隐藏的应用会返回 `WU_WUAPP_MOUNTED`。请先完全卸载挂载实例。事件为 `wuapp:installed` 和 `wuapp:uninstalled`。

错误包括 `WU_WUAPP_INVALID`、`WU_WUAPP_UNSIGNED`、`WU_WUAPP_UNTRUSTED`、`WU_WUAPP_BAD_SIGNATURE`、`WU_WUAPP_INTEGRITY` 和 `WU_WUAPP_NO_CACHE`。保存成功不代表模块可挂载；宿主仍需提供共享模块、正确授权和由 worker 处理的路由。

<span id="integridad-y-reemplazo-en-40"></span>

<span class="section-anchor" id="section-4"></span>

## 4.0 中的完整性与替换

包中的 `type` 字段不决定 MIME：MIME 根据扩展名和 `wu pack` 映射表推导，并以 `nosniff` 提供。路径拒绝保留片段，包括 `__proto__`；签名 payload 使用无原型对象构建。

覆盖 host 已注册名称进行安装需要 `{ replace: true }`，否则会产生 `WU_WUAPP_NAME_TAKEN`。卸载后仍可更新已安装应用。`restore()` 会验证每个持久化条目，且绝不替换 host 注册。

`restore({ grants })` 接受 grants 数组或 host 定义的 `(entry) => grants` 函数。省略时使用安装时保存的 grants。若不希望从 localStorage 索引恢复权限，请设置该选项。