# SDK API 参考

查阅公开入口、配置默认值、Hooks、检查结果与错误模型。

先完成[最小接入](/docs/integration)。本页用于查找参数，不要求一次配置所有选项。示例默认已有一个客户端实例；不要为错误上报或动态配置再创建第二个实例。

## 概览 {#overview}

Pakta SDK（npm 包 `rn-update`）的公开 API 全部从包入口导出。核心分五块：

- `Pakta` 客户端类与 `ClientOptions` 配置
- `UpdateProvider`（别名 `PaktaProvider`）React 接入层
- `useUpdate()` / `useUpdateProgress()` / `usePakta()` Hooks
- 更新元数据与崩溃报告关联
- `UpdateError` 错误类型与事件模型

## 客户端类 {#client}

```tsx
import { Pakta } from 'rn-update';

const client = new Pakta({
  appKey: '<你的 appKey>',
  updateStrategy: 'silentAndLater',
});

// 运行中动态更新配置（合并新配置，并通知 Provider）
client.setOptions({ checkStrategy: 'onAppResume' });

// 手动上报 JS 异常（默认自动监听未捕获的 JS 错误）
client.captureException(error, { context: 'checkout' });
```

## ClientOptions {#client-options}

| 字段 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| `appKey` | `string` | 必填 | 控制台分配的平台 appKey |
| `server` | `{ main: string[]; queryUrls?: string[] }` | 官方默认端点 | 自托管服务地址与端点发现清单 |
| `updateStrategy` | 见下表 | 生产为 `'alertUpdateAndIgnoreError'`，开发为 `'alwaysAlert'` | 更新提示与生效策略 |
| `checkStrategy` | `'onAppStart' \| 'onAppResume' \| 'both' \| null` | `'both'` | 检查时机；`null` 关闭 JS 自动检查 |
| `autoMarkSuccess` | `boolean` | `true` | 自动确认当前版本健康 |
| `autoMarkSuccessDelayMs` | `number` | `1000` | 自动确认延迟；关键模块加载晚时调大 |
| `healthCheck` | `() => boolean \| Promise<boolean>` | — | 确认前的健康检查；返回 `false` 跳过本次确认 |
| `maxRetries` | `number` | `3` | 下载失败重试次数 |
| `logger` | `({ type, data }) => void` | — | 更新事件记录器 |
| `locale` | `'zh' \| 'en'` | `'zh'` | 内置提示文案语言 |
| `debug` | `boolean` | `false` | 允许在开发构建检查和下载；实际生效仍用 Release |
| `throwError` | `boolean` | `false` | 把更新错误抛给 JS 层 |
| `testChannel` | `boolean` | `true` | 是否响应测试二维码/深链；生产建议 `false` |
| `beforeCheckUpdate` / `afterCheckUpdate` | 钩子 | — | 检查前后拦截与回调 |
| `beforeDownloadUpdate` / `afterDownloadUpdate` | 钩子 | — | 下载前后拦截与回调 |
| `beforeReload` | `(ctx: { type: 'switchVersion' \| 'restartApp' }) => …` | — | 重载前拦截 |
| `onPackageExpired` | 钩子 | — | 原生包过期时拦截默认行为 |
| `disableTelemetry` | `boolean` | `false` | 关闭客户端遥测及 JS 错误传输 |
| `disableErrorReporting` | `boolean` | `false` | 单独关闭 JS 错误上报 |
| `disableNativeCheck` | `boolean` | `false` | 关闭原生冷启动检查，见下方说明 |
| `dismissErrorAfter` | `number` | 不自动清除 | 自动清除 lastError 的延迟，毫秒 |
| `overridePackageVersion` | `string` | 原生实际版本 | 诊断时覆盖 JS 请求版本，不会修改安装包；正常发布不设置 |

`updateStrategy` 取值：`'silentAndNow'`、`'silentAndLater'`、`'alertUpdateAndIgnoreError'`、`'alwaysAlert'`、`null`。当前 Provider 在手动检查/下载后仍可能弹窗；完全自定义流程使用下方实例 API 示例。

## React 接入 {#react}

```tsx
import { Pakta, UpdateProvider, PaktaProvider, useUpdate, usePakta, useUpdateProgress } from 'rn-update';
```

- `UpdateProvider` / `PaktaProvider`（别名）——接收 `client` prop；同一进程重复挂载第二个 Provider 会直接抛错。
- `useUpdate()` —— 读取更新状态与动作（见下）。
- `useUpdateProgress()` —— 单独订阅下载进度 `{ hash, received, total, progress? }`。
- `usePakta()` —— `useUpdate()` 的别名，返回相同状态与动作；实例通过 `useUpdate().client` 取得。

`useUpdate()` 返回的 `UpdateContextValue`：

| 成员 | 说明 |
| --- | --- |
| `updateInfo` | 最近一次 `CheckResult` |
| `lastError` | 最近一次错误 |
| `currentHash` / `packageVersion` | 当前热更新 hash 与原生包版本 |
| `currentVersionInfo` | `{ name?, description?, metaInfo? }` |
| `checkUpdate(params?)` | 手动检查 |
| `downloadUpdate(info?)` | 下载 |
| `switchVersion(info?)` | 立即切换已下载版本（重载 JS） |
| `switchVersionLater(info?)` | 下次启动生效 |
| `markSuccess()` | 确认当前版本健康 |
| `restartApp()` | 重启应用 |
| `resetToPackagedBundle(options?)` | 回到内置 bundle |
| `downloadAndInstallApk(url)` | 下载并安装原生升级包（原生包过期场景） |
| `parseTestQrCode(code)` | 解析测试二维码/深链 |
| `dismissError()` | 清除 `lastError` |

## CheckResult 与版本信息 {#check-result}

```ts
interface CheckResult {
  upToDate?: boolean;      // 已是最新
  update?: boolean;        // 有可用更新
  expired?: boolean;       // 原生包过期，downloadUrl 指向新安装包
  paused?: 'app' | 'package'; // 应用或原生包暂停
  downloadUrl?: string;    // 过期安装包地址
  bundleStatus?: 'matched' | 'rebuiltSameJs' | 'unknownBundle';
  name?: string;           // 版本名
  hash?: string;           // 版本 hash
  description?: string;    // 版本描述
  metaInfo?: string;       // 自定义元信息（JSON 字符串）
  config?: { rollout?: Record<string, number>; forceBoot?: boolean };
  pdiff?: string;          // 精确差分包地址
  diff?: string;           // 普通差分包地址
  full?: string;           // 全量包地址
}
```

`bundleStatus` 说明原生包登记状态：`matched` 完全匹配；`rebuiltSameJs` 同 JS 指纹但构建时间不同（只允许全量）；`unknownBundle` 指纹未登记（严格渠道下保持当前版本）。

## 元数据与崩溃报告关联 {#metadata}

```tsx
import * as Sentry from '@sentry/react-native';
import { attachToSentry, getUpdateMetadata } from 'rn-update';

// 在已有 Sentry 初始化之后调用。
attachToSentry(Sentry);
const metadata = getUpdateMetadata();
```

`getUpdateMetadata()` 返回当前更新版本信息（含 `currentVersion`），`updateMetadataTags()` 生成可直接挂到 Sentry tags 的键值对。

## 错误与事件 {#errors}

- `UpdateError` 携带稳定的机器可读 `code`（`UpdateErrorCode`）。
- `EventData`（`logger` 回调）包含 `type`（如 `checking`、`downloading`、`downloadSuccess`、`rollback`、`markSuccess`、`errorUpdate`…）与错误详情。
- JS 错误上报与堆栈还原见 [JS 报错监控](/docs/errors)。

Crashlytics 需向 `attachToCrashlytics(reporter)` 传入已初始化且提供 setAttribute/setAttributes 的实例。默认标签前缀为 `pakta.`，当前更新标签为 `pakta.currentVersion`。


## checkUpdate(params?) {#async-function-checkupdate}

在 `UpdateProvider` 的子组件中调用。检查后会更新 `updateInfo`，并执行所选更新策略，可能继续下载或弹窗。

```typescript
checkUpdate(params?: { extra?: { toHash?: string } }): Promise<CheckResult | undefined>
```

不传参数检查当前设备可用的更新。`extra.toHash` 用于测试指定更新，不用于设置渠道。跳过检查时可能返回 `undefined`；失败默认记录到 `lastError`，配置 `throwError: true` 后可以捕获异常。

```tsx
const { checkUpdate, lastError } = useUpdate();

async function onCheck() {
  const info = await checkUpdate();
  if (!info) return;
  if (info.expired) {
    // 当前安装包需要升级，下载地址在 info.downloadUrl。
    return;
  }
  if (info.paused) return;
  if (info.upToDate) return;
  if (info.update) {
    // 后续下载和生效行为取决于 updateStrategy。
  }
}
```

只想取得原始检查结果、自行画界面时使用 `client.checkUpdate()`，不要同时调用 Provider 和 client 两套检查流程。完整手动示例见[自定义更新界面](#custom-update)。

## downloadUpdate(info?) {#async-function-downloadupdate}

```typescript
downloadUpdate(info?: CheckResult): Promise<boolean | undefined>
```

`info` 默认是最近一次检查结果。没有可下载更新时返回 `false`；成功完成 Provider 下载流程时返回 `true`。`afterDownloadUpdate` 返回 `false` 时也返回 `false`，即使文件已经下载。

```tsx
const { updateInfo, downloadUpdate } = useUpdate();

async function onDownload() {
  if (!updateInfo?.update) return;
  const completed = await downloadUpdate(updateInfo);
  if (!completed) return;
}
```

Provider 的这个方法会继续执行下载后的策略：`silentAndNow` 请求立即重载，`silentAndLater` 安排下次启动，其余情况可能显示生效提示。不要把它当成“只下载文件”的接口。

只下载时调用 `client.downloadUpdate(info, onProgress?)`：返回 `Promise<string | undefined>`，字符串是下载完成的更新 hash；然后自己调用 `client.switchVersion(hash)` 或 `client.switchVersionLater(hash)`。

## downloadAndInstallApk(url) {#async-function-downloadandinstallapkurl}

下载完整 Android 安装包并打开系统安装界面，用于需要升级原生版本的情况。它不安装 `.ppk`，也不负责 iOS 或 HarmonyOS 安装。

```typescript
downloadAndInstallApk(url: string): Promise<void>
```

| 项目 | 要求 |
| --- | --- |
| `url` | 可直接下载的 HTTPS APK 地址，不是商店网页或 AAB |
| 平台 | Android；其他平台跳过 |
| 安装包 | 与当前 App 包名和签名匹配，版本符合系统升级要求 |
| 返回值 | 无；Promise 完成不代表用户已确认安装 |
| 错误 | 默认在 `lastError` 中；`throwError: true` 时可用 try/catch |

### 1. 添加安装权限

在 App 的 `android/app/src/main/AndroidManifest.xml` 中，放到 `<manifest>` 内、`<application>` 外：

```xml title="android/app/src/main/AndroidManifest.xml"
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
```

重新构建并分发安装包，权限不会通过 JS 更新加入。SDK 使用 Android 系统安装会话，不需要额外添加 FileProvider。HTTP 地址会被拒绝。

### 2. 添加升级按钮

下面组件放在现有 `UpdateProvider` 下。`apkUrl` 由业务传入实际 APK 下载地址，也可以来自检查结果的 `downloadUrl`。

```tsx title="NativeUpgradeButton.tsx"
import { useState } from 'react';
import { Button, Platform, Text, View } from 'react-native';
import { useUpdate, useUpdateProgress } from 'rn-update';

export function NativeUpgradeButton({ apkUrl }: { apkUrl: string }) {
  const { downloadAndInstallApk, lastError } = useUpdate();
  const progress = useUpdateProgress();
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState('');

  async function install() {
    if (busy || !apkUrl) return;
    setBusy(true);
    setError('');
    try {
      await downloadAndInstallApk(apkUrl);
    } catch (cause) {
      setError(cause instanceof Error ? cause.message : String(cause));
    } finally {
      setBusy(false);
    }
  }

  if (Platform.OS !== 'android') return null;
  const percent = progress?.hash === 'downloadingApk' && progress.total > 0
    ? Math.round(progress.received / progress.total * 100)
    : undefined;
  return (
    <View>
      <Button title={busy ? '正在下载' : '升级安装包'}
        disabled={busy || !apkUrl} onPress={() => { void install(); }} />
      {percent !== undefined ? <Text>{percent}%</Text> : null}
      {error || lastError ? <Text>{error || lastError?.message}</Text> : null}
    </View>
  );
}
```

### 3. 处理系统授权和安装结果

Android 8 及以上需要允许当前 App 安装未知来源应用。权限未开启时，SDK 尝试打开系统设置，并报告 `APK_INSTALL_PERMISSION_REQUIRED`。用户允许后回到 App，再点升级按钮。

下载过程中重复调用会跳过。已经下载并等待安装时，再调用可能得到 `APK_INSTALL_PENDING`；让用户完成系统安装，不要循环下载。权限已允许但安装失败时，检查 APK 是否完整、包名、签名和版本号。

如果发布渠道要求通过商店更新，使用业务自己的商店跳转，不调用 APK 安装方法。

## markSuccess() {#function-marksuccess}

确认当前更新已经能正常使用，结束此次新版本启动的回滚保护。

```typescript
markSuccess(): Promise<boolean | undefined>
```

返回 `true` 表示原生层接受确认；`false` 表示拒绝；已确认、开发环境或不在新版本首次启动时可能返回 `undefined`。原生调用失败会抛错。

默认 Provider 在挂载后约 1000 ms 自动确认。只有需要等关键页面或初始化完成时才关闭自动确认：

```tsx title="加入现有 client 配置"
autoMarkSuccess: false,
```

```tsx title="关键初始化成功后"
const { markSuccess } = useUpdate();

async function confirmReady() {
  try {
    const accepted = await markSuccess();
    if (accepted === false) {
      // 未接受确认，保留诊断信息。
    }
  } catch (error) {
    // 展示或记录确认失败，不要当成已成功。
  }
}
```

不要在刚下载完时确认：要确认的是新版本启动成功。已确认后的任意业务异常不一定触发自动回滚。

## currentVersionInfo {#currentversioninfo}

```typescript
currentVersionInfo: {
  name?: string;
  description?: string;
  metaInfo?: string;
} | null
```

表示**当前运行**更新的名称、说明和自定义数据。`updateInfo` 则是最近检查到的候选更新，两者不要混用。

```tsx
const { currentVersionInfo, currentHash, packageVersion } = useUpdate();
const label = currentVersionInfo?.name || '安装包自带版本';
```

`currentHash` 为空表示正在使用安装包自带的 JS。`packageVersion` 是原生安装包版本，不会因为热更新名称变化而改变。

## getCurrentVersionInfo() {#async-function-getcurrentversioninfo}

```typescript
getCurrentVersionInfo(): Promise<{
  name?: string;
  description?: string;
  metaInfo?: string;
}>
```

兼容旧代码的异步方法，新代码直接读取 `currentVersionInfo`。

```tsx
const { getCurrentVersionInfo } = useUpdate();
const version = await getCurrentVersionInfo();
```

## restartApp() {#function-restartapp}

```typescript
restartApp(): Promise<void>
```

请求原生层重启 React Native 应用环境。它不会检查或下载更新，也不保证操作系统杀掉整个 App 进程。调用前保存草稿、支付状态等必要数据。

```tsx
const { restartApp } = useUpdate();
await restartApp();
```

调用前执行 `beforeReload({ type: 'restartApp' })`，返回 `false` 取消重启。钩子或原生重启失败会抛错，应在按钮回调中捕获。

## resetToPackagedBundle(options?) {#async-function-resettopackagedbundleoptions}

清除已下载更新和本地更新状态，恢复安装包自带的 JS，保留设备标识。它不是选择某个历史热更新。

```typescript
resetToPackagedBundle(options?: { restart?: boolean }): Promise<boolean | undefined>
```

```tsx
const { resetToPackagedBundle } = useUpdate();
const reset = await resetToPackagedBundle({ restart: true });
if (!reset) {
  // 重置没有成功，不要向用户显示“已恢复”。
}
```

默认不立即重启，下次启动加载内置包；`restart: true` 会在重置后请求重启。返回 `true` 只表示重置成功，重启可能被钩子取消或失败。Web 返回 `false`；不支持此原生方法的旧安装包会报告 `RESET_FAILED`。服务端仍发布相同更新时，后续检查可能再次下载，运营应先[停止问题版本](/docs/console#stop)。

## switchVersion(info?) {#function-switchversion}

```typescript
switchVersion(info?: CheckResult): Promise<boolean | undefined>
```

立即切换到**已经下载**的更新，参数默认最近检查结果。无 hash 时跳过；返回 `true` 表示已请求原生重载，不能当成新版本已正常启动。

```tsx
const { switchVersion, updateInfo } = useUpdate();
if (updateInfo?.hash) await switchVersion(updateInfo);
```

必须先下载。该方法执行 `beforeReload`，返回 `false` 可取消；当前页面会在重载时中断。调用客户端实例对应的方法时传 hash 字符串：`client.switchVersion(hash)`。

## switchVersionLater(info?) {#function-switchversionlater}

```typescript
switchVersionLater(info?: CheckResult): Promise<void>
```

把已下载更新设为下次启动使用的版本，保持当前页面。无 hash 时跳过，不触发 `beforeReload`。

```tsx
const { switchVersionLater, updateInfo } = useUpdate();
if (updateInfo?.hash) await switchVersionLater(updateInfo);
```

测试时完整关闭并重新启动 App，仅切换页面不算重启。客户端实例用法为 `client.switchVersionLater(hash)`。

## parseTestQrCode(code) {#function-parsetestqrcodeqrcode-string}

```typescript
parseTestQrCode(code: string | UpdateTestPayload): boolean
```

接受扫码得到的 JSON 字符串或对象。返回 `true` 只表示识别了测试载荷，不代表下载或生效成功。

```tsx
const { parseTestQrCode } = useUpdate();
const accepted = parseTestQrCode({
  type: '__rnPaktaVersionHash',
  data: '替换成控制台里目标更新的完整 hash',
});
```

识别后会检查指定更新；仍需观察后续检查结果。`testChannel: false` 拒绝测试载荷。扫码组件由业务提供，SDK 不会替你打开摄像头。

使用已有 App 深链时，可传 `?type=__rnPaktaVersionHash&data=HASH`，Provider 会读取查询参数。原生 scheme 仍需在工程中配置并重新构建。扫码测试与安装包里的固定渠道是两个设置。

## useUpdateProgress() {#use-update-progress}

```tsx
import { Text } from 'react-native';
import { useUpdateProgress } from 'rn-update';

export function DownloadProgress() {
  const data = useUpdateProgress();
  if (!data) return null;
  const percent = data.progress ?? (data.total > 0
    ? Math.round(data.received / data.total * 100) : undefined);
  return <Text>{percent === undefined ? '正在下载' : `${percent}%`}</Text>;
}
```

返回 `ProgressData | undefined`：`received` / `total` 为字节数，`progress` 是可选的 0–100 百分比。APK 进度可能只有字节数；未知总量时显示下载中。仅需进度的组件用此 Hook，可减少其他更新状态变化带来的渲染。

## dismissError() {#dismiss-error}

清空 Provider 的 `lastError`，不会重试或改变已下载的内容。

```tsx
const { lastError, dismissError } = useUpdate();
// 在用户关闭错误提示时调用 dismissError()。
```

需要自动清除时，把 `dismissErrorAfter: 5000` 加入现有客户端配置，单位毫秒。

## 自定义更新界面 {#custom-update}

实例的 `checkUpdate` / `downloadUpdate` 用于自己控制流程。它们和 Hook 方法的参数、返回值不同：实例下载返回 hash，实例切换接受 hash。

```tsx title="ManualUpdateButton.tsx"
import { useState } from 'react';
import { Button, Text, View } from 'react-native';
import { useUpdate } from 'rn-update';

export function ManualUpdateButton() {
  const { client } = useUpdate();
  const [busy, setBusy] = useState(false);
  const [message, setMessage] = useState('');
  async function update() {
    if (!client || busy) return;
    setBusy(true);
    try {
      const info = await client.checkUpdate();
      if (!info?.update) {
        setMessage(info?.expired ? '请升级安装包' : '暂无可下载更新');
        return;
      }
      const hash = await client.downloadUpdate(info, (data) => {
        setMessage(data.progress === undefined ? '正在下载' : `${data.progress}%`);
      });
      if (!hash) return;
      await client.switchVersionLater(hash);
      setMessage('下载完成，下次启动生效');
    } catch (error) {
      setMessage(error instanceof Error ? error.message : String(error));
    } finally {
      setBusy(false);
    }
  }
  return <View>
    <Button title="检查并下载" disabled={busy} onPress={() => { void update(); }} />
    <Text>{message}</Text>
  </View>;
}
```

配套把 `checkStrategy: null`、`throwError: true` 加入现有客户端。`checkStrategy` 只关闭 JS 自动检查；需要同时关闭原生后台检查时另设 `disableNativeCheck: true`，并接受失去原生救援能力的影响。

## 检查、下载和重载钩子 {#lifecycle-hooks}

| 配置 | 收到什么 | 返回值如何影响流程 |
| --- | --- | --- |
| `beforeCheckUpdate` | 无参数 | `false` 跳过本次检查 |
| `afterCheckUpdate` | `{ status, result?, error? }` | 记录检查完成、跳过或失败，不替换结果 |
| `beforeDownloadUpdate` | `CheckResult` | `false` 跳过下载 |
| `afterDownloadUpdate` | `CheckResult` | `false` 停止 Provider 下载后的提示或切换 |
| `onPackageExpired` | `CheckResult` | `false` 阻止内置原生升级处理 |
| `beforeReload` | `{ type, hash? }` | `false` 取消立即重载；抛错也不继续 |

这些配置都加入根组件外的已有客户端。比如在立即重载前等待业务保存：

```tsx
beforeReload: async ({ type }) => {
  if (type === 'switchVersion') {
    const saved = await savePendingDrafts();
    return saved;
  }
  return true;
},
```

`savePendingDrafts` 是业务自己的保存函数，应返回是否可以继续。等待操作设置合理超时，避免更新按钮永久等待。

## 原生启动检查 {#native-check}

`disableNativeCheck` 默认关闭，即原生检查启用。它在 App 冷启动后独立检查更新，JS 不能运行时也能下载修复。

`checkStrategy: null` 不会关闭这次网络请求；服务端 `forceBoot` 可以安排修复在后续启动生效。本地已回滚版本仍受保护。操作入口见[强制救砖](/docs/console#rescue)。

## captureException(error, context?) {#function-captureexceptionerror-context}

使用已有客户端记录业务捕获的 JS 异常，无需再创建一个实例：

```tsx
const { client } = useUpdate();
try {
  await submitOrder();
} catch (error) {
  client?.captureException(error, { context: 'checkout' });
}
```

未捕获 JS 错误默认自动上报；`disableErrorReporting` 单独关闭错误上报，`disableTelemetry` 也会阻止相关传输。上下文只放诊断所需信息。对应 sourcemap 的归档和还原见[报错监控](/docs/errors)。

## Android 混编：setCustomInstanceManager {#updatecontextsetcustominstancemanagerreactinstancemanager-instancemanager}

已有原生 Android 工程自己维护 `ReactInstanceManager`、没有标准 `ReactApplication` 时，把**同一个正在使用的实例**交给 SDK，供重载使用：

```java
import cn.reactnative.modules.update.UpdateContext;

UpdateContext.setCustomInstanceManager(reactInstanceManager);
```

在创建该实例后调用。它不代替 bundle 路径配置，构建实例时仍需使用 `UpdateContext.getBundleUrl(context)`；标准 React Native 工程按[原生接入](/docs/getting-started#android)即可，无需额外创建实例。
