# @evenrealities/even\_hub\_sdk 中文文档

规范地址：https://npmcn.com/package/%40evenrealities/even_hub_sdk/

版本：0\.0\.16
npm 元数据更新时间：2026-09-24T09:12:37.766Z
中文译文更新时间：2026-11-14T01:00:08.214Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40evenrealities/even_hub_sdk
原文对照：https://npmcn.com/package/%40evenrealities/even_hub_sdk/?lang=original

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：455 kB，8 个文件
- 模块格式：ESM + CJS
- TypeScript 类型：包内自带
- Node.js 要求：^20\.0\.0 \|\| \>=22\.0\.0
- 运行依赖：0 个
- 首次发布：2026-01-27T02:47:24.582Z
- 最新版本发布：2026-09-24T09:12:37.576Z
- 近 30 天下载量：437027（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：16（近 12 个月 16 个）

## 中文简介

EvenHub 开发者与 Even App 通信的 TypeScript SDK

## 安装

```sh
npm install @evenrealities/even_hub_sdk
```

也可以使用：`pnpm add @evenrealities/even_hub_sdk`、`yarn add @evenrealities/even_hub_sdk`、`bun add @evenrealities/even_hub_sdk`

## README

# @evenrealities/even\_hub\_sdk

Even App WebView 页面使用的 TypeScript SDK。

[![npm version](https://img.shields.io/npm/v/@evenrealities/even_hub_sdk.svg)](https://www.npmjs.com/package/@evenrealities/even_hub_sdk) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org/)

[English](https://npmcn.com/package/%40evenrealities/even_hub_sdk/README.md) | **中文**

## ⚡️ 快速开始

```bash
npm install @evenrealities/even_hub_sdk
```

```ts
import { waitForEvenAppBridge } from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

const user = await bridge.getUserInfo();
const device = await bridge.getDeviceInfo();

await bridge.setLocalStorage('theme', 'dark');
const theme = await bridge.getLocalStorage('theme');

console.log(user.name, device?.model, theme);
```

当 Web 页面运行在 Even App WebView 内，并需要这些能力时使用：

-   App 桥接：用户、设备、本地存储。
-   App 能力：定位、相册、相机。
-   眼镜 UI：启动页容器、重建、上下文菜单、文本/图片更新。
-   传感器与事件：MIC、IMU、启动来源、设备状态。

## 📦 安装

```bash
npm install @evenrealities/even_hub_sdk
# 或
pnpm add @evenrealities/even_hub_sdk
# 或
yarn add @evenrealities/even_hub_sdk
```

要求：

-   SDK 版本：`0.0.16`
-   Even App：`2.2.10` 或更高版本
-   Node.js：`^20.0.0 || >=22.0.0`
-   运行环境：提供 `window.flutter_inappwebview.callHandler` 的 Even App WebView

## 🔧 常用能力

### 启动来源

宿主在 WebView loading 完成后推送一次启动来源。

```ts
const bridge = await waitForEvenAppBridge();

const unsubscribe = bridge.onLaunchSource((source) => {
  if (source === 'glassesMenu') {
    console.log('从眼镜菜单打开');
  }
});

// unsubscribe();
```

取值：

-   `appMenu`
-   `glassesMenu`

### 设备状态

```ts
import {
  DeviceConnectType,
  waitForEvenAppBridge,
} from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

const unsubscribe = bridge.onDeviceStatusChanged((status) => {
  if (status.connectType === DeviceConnectType.Connected) {
    console.log('电量:', status.batteryLevel);
  }
});

// unsubscribe();
```

### App 定位

```ts
import {
  AppLocationAccuracy,
  waitForEvenAppBridge,
} from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

const location = await bridge.getAppLocation({
  accuracy: AppLocationAccuracy.High,
  timeoutMs: 5000,
});

if (location) {
  console.log(location.latitude, location.longitude);
}
```

连续定位：

```ts
await bridge.startAppLocationUpdates({
  accuracy: AppLocationAccuracy.Medium,
  intervalMs: 1000,
  distanceFilter: 5,
});

const unsubscribeLocation = bridge.onAppLocationChanged((location) => {
  console.log('位置:', location.latitude, location.longitude);
});

// await bridge.stopAppLocationUpdates();
// unsubscribeLocation();
```

### 相册与相机

相册只支持单选。

```ts
const albumImage = await bridge.pickImageFromAlbum();
if (albumImage) {
  console.log(albumImage.name, albumImage.mimeType, albumImage.size);
  // Web 侧使用 albumImage.base64
}

const cameraImage = await bridge.captureImageFromCamera();
if (cameraImage) {
  console.log('拍摄成功:', cameraImage.name);
}
```

`AppImageAsset`：

```ts
type AppImageAsset = {
  path: string;
  name: string;
  mimeType: string;
  size: number;
  base64: string;
};
```

### MIC 来源

可选择眼镜 MIC 或手机 MIC。

```ts
import {
  AudioInputSource,
  AudioSpeakerRole,
  waitForEvenAppBridge,
} from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

await bridge.audioControl(true, AudioInputSource.Glasses);
// await bridge.audioControl(true, AudioInputSource.Phone);

const unsubscribeAudio = bridge.onEvenHubEvent((event) => {
  const audio = event.audioEvent;
  if (!audio) return;

  console.log(audio.source); // AudioInputSource.Glasses | AudioInputSource.Phone
  console.log(audio.audioPcm.length); // Uint8Array
  console.log(audio.direction); // int16 有符号整数或 null
  console.log(audio.speakerRole); // AudioSpeakerRole.Self | Other | Unknown

  if (audio.speakerRole === AudioSpeakerRole.Self) {
    console.log('App 算法将这一帧判定为本人说话');
  }
});

// await bridge.audioControl(false);
// unsubscribeAudio();
```

说明：

-   默认来源：`AudioInputSource.Glasses`。
-   音频数据通过 `onEvenHubEvent` 下发。
-   使用眼镜 MIC 前，先创建启动页。
-   眼镜 `direction` 是与处理后 PCM 同帧的原始 int16 有符号方向标签；SDK 不转换单位或区间。
-   `speakerRole` 是 App 音频算法结果，不是固件直接给出的身份结论。
-   手机 MIC 和旧 Host 数据使用 `direction: null` 与 `AudioSpeakerRole.Unknown`。

### IMU

```ts
import {
  ImuReportPace,
  OsEventTypeList,
  waitForEvenAppBridge,
} from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

await bridge.imuControl(true, ImuReportPace.P500);

const unsubscribeImu = bridge.onEvenHubEvent((event) => {
  const sys = event.sysEvent;
  if (sys?.eventType !== OsEventTypeList.IMU_DATA_REPORT) return;
  if (!sys.imuData) return;

  console.log(sys.imuData.x, sys.imuData.y, sys.imuData.z);
});

// await bridge.imuControl(false);
// unsubscribeImu();
```

`ImuReportPace` 是协议档位：`P100` 到 `P1000`。

## 🕶️ 眼镜 UI

先调用 `createStartUpPageContainer`，再做其他眼镜 UI 操作。

```ts
import {
  ImageContainerProperty,
  ImageRawDataUpdateResult,
  ListContainerProperty,
  MenuContainerProperty,
  StartUpPageCreateResult,
  TextContainerProperty,
  waitForEvenAppBridge,
} from '@evenrealities/even_hub_sdk';

const bridge = await waitForEvenAppBridge();

const listObject: ListContainerProperty[] = [{
  xPosition: 100,
  yPosition: 50,
  width: 200,
  height: 150,
  containerID: 1,
  containerName: 'list-1',
  zOrderIndex: 1,
  itemContainer: {
    itemCount: 3,
    itemName: ['Item 1', 'Item 2', 'Item 3'],
  },
  isEventCapture: 1,
}];

const textObject: TextContainerProperty[] = [{
  xPosition: 100,
  yPosition: 220,
  width: 200,
  height: 50,
  containerID: 2,
  containerName: 'text-1',
  zOrderIndex: 2,
  content: 'Hello',
  textColor: 4,
  isEventCapture: 0,
}];

const imageObject: ImageContainerProperty[] = [{
  xPosition: 320,
  yPosition: 50,
  width: 100,
  height: 80,
  containerID: 3,
  containerName: 'image-1',
  zOrderIndex: 3,
}];

const menuObject: MenuContainerProperty = {
  menuItems: [{
    itemName: 'Pause',
    itemID: 1,
  }, {
    itemName: 'Resume',
    itemID: 2,
  }],
};

const result = await bridge.createStartUpPageContainer({
  containerTotalNum: 3,
  listObject,
  textObject,
  imageObject,
  menuObject,
});

if (result === StartUpPageCreateResult.success) {
  await bridge.textContainerUpgrade({
    containerID: 2,
    containerName: 'text-1',
    content: 'Updated',
    textColor: 2,
  });
}
```

`textColor` 可选，接受 `0` 到 `4` 的整数亮度值。创建/重建时省略则使用设备默认亮度 `4`；在 `textContainerUpgrade` 中省略则保持当前亮度。SDK 会在调用宿主前拒绝超出 `0..4` 的值。

更新图片内容：

图片原始数据由 SDK 内部使用 LZ4 压缩，以减少传输体积，同时保持设备端快速解码。

```ts
const imageResult = await bridge.updateImageRawData({
  containerID: 3,
  containerName: 'image-1',
  imageData: [/* 灰度图片字节 */],
});

if (imageResult !== ImageRawDataUpdateResult.success) {
  console.warn('图片更新失败:', imageResult);
}
```

重建页面：

```ts
await bridge.rebuildPageContainer({
  containerTotalNum: 1,
  textObject: [{
    xPosition: 100,
    yPosition: 80,
    width: 240,
    height: 60,
    containerID: 4,
    containerName: 'status-text',
    content: 'Ready',
    isEventCapture: 1,
  }],
});
```

重建时不传 `menuObject` 表示清除自定义菜单：

```ts
await bridge.rebuildPageContainer({
  containerTotalNum: 1,
  textObject: [/* ... */],
});
```

处理上下文菜单事件：

```ts
const unsubscribeMenu = bridge.onEvenHubEvent((event) => {
  if (event.menuItemClickEvent) {
    console.log('动作项:', event.menuItemClickEvent.itemID);
  }
});

// unsubscribeMenu();
```

处理长按和长按释放：

```ts
const unsubscribePress = bridge.onEvenHubEvent((event) => {
  const pressEvent = event.sysEvent;
  if (!pressEvent) return;

  if (pressEvent.eventType === OsEventTypeList.LONG_PRESS_EVENT) {
    console.log('开始长按，来源:', pressEvent.eventSource);
  } else if (pressEvent.eventType === OsEventTypeList.LONG_PRESS_RELEASE_EVENT) {
    console.log('长按已释放，来源:', pressEvent.eventSource);
  }
});

// unsubscribePress();
```

`LONG_PRESS_EVENT` 的 PB 值为 `9`，`LONG_PRESS_RELEASE_EVENT` 的 PB 值为 `10`。当前带来源的推送通过 `sysEvent` 上报，其中 `eventSource` 保留 PB `EventSourceType`（右镜腿、戒指或左镜腿），不会再丢失输入来源。

监听列表、文本和系统事件：

```ts
const unsubscribeHub = bridge.onEvenHubEvent((event) => {
  if (event.listEvent) {
    console.log('选中:', event.listEvent.currentSelectItemName);
  }
  if (event.textEvent) {
    console.log('文本事件:', event.textEvent.containerName);
  }
  if (event.sysEvent) {
    console.log('系统事件:', event.sysEvent.eventType);
  }
});

// unsubscribeHub();
```

关闭眼镜页面：

```ts
await bridge.shutDownPageContainer(0);
// await bridge.shutDownPageContainer(1); // 交给前台交互层决定是否退出
```

规则：

-   坐标原点：左上角。
-   `containerTotalNum`：`1` 到 `12`。
-   `textObject`：最多 `8` 个。
-   只能有一个容器使用 `isEventCapture: 1`。
-   图片容器创建后，需要再调用 `updateImageRawData`。
-   `zOrderIndex` 只有在同一个画面的所有容器都不填写时才可以省略，用于兼容旧版本 SDK 页面。
-   同一个画面里，只要任意容器填写了 `zOrderIndex`，所有 list/text/image 容器都必须填写。
-   同一个画面内 `zOrderIndex` 数值必须唯一；数值越大，画面层级越靠前。
-   违反上述 `zOrderIndex` 规则时，SDK 会在调用原生前输出 `EvenHubPageContainerValidationErrorCode` 错误日志。`createStartUpPageContainer` 返回 `StartUpPageCreateResult.invalid`，`rebuildPageContainer` 返回 `false`。
-   `menuObject.menuItems`：最多 `10` 个；每个顶层 `itemID` 必须非 0 且唯一。
-   菜单 `itemName`：最多 `32` 个 UTF-8 字节。
-   菜单数量、ID 或名称非法时，SDK 会在调用原生前拒绝。rebuild 不传 `menuObject` 会清除自定义菜单并恢复默认注册。
-   长按和长按释放仍通过普通 list/text/sys 事件上报，分别对应 `OsEventTypeList.LONG_PRESS_EVENT` 与 `OsEventTypeList.LONG_PRESS_RELEASE_EVENT`。

## 📚 API 速查

### Bridge

| API | 返回 |
| --- | --- |
| `waitForEvenAppBridge()` | `Promise<EvenAppBridge>` |
| `EvenAppBridge.getInstance()` | `EvenAppBridge` |
| `callEvenApp(method, params?)` | `Promise<any>` |

### App

| API | 返回 |
| --- | --- |
| `getUserInfo()` | `Promise<UserInfo>` |
| `getDeviceInfo()` | `Promise<DeviceInfo | null>` |
| `setLocalStorage(key, value)` | `Promise<boolean>` |
| `getLocalStorage(key)` | `Promise<string>` |
| `getAppLocation(options?)` | `Promise<AppLocation | null>` |
| `startAppLocationUpdates(options?)` | `Promise<boolean>` |
| `stopAppLocationUpdates()` | `Promise<boolean>` |
| `pickImageFromAlbum()` | `Promise<AppImageAsset | null>` |
| `captureImageFromCamera()` | `Promise<AppImageAsset | null>` |

### 事件

| API | 事件 |
| --- | --- |
| `onLaunchSource(callback)` | `appMenu` / `glassesMenu` |
| `onDeviceStatusChanged(callback)` | 设备状态 |
| `onAppLocationChanged(callback)` | App 位置 |
| `onEvenHubEvent(callback)` | list / text / sys / audio / 上下文菜单 |

`AudioEvent` 提供 `audioPcm`、`source`、`direction` 和 `speakerRole`。

### EvenHub

| API | 返回 |
| --- | --- |
| `createStartUpPageContainer(container)` | `Promise<StartUpPageCreateResult>` |
| `rebuildPageContainer(container)` | `Promise<boolean>` |
| `updateImageRawData(data)` | `Promise<ImageRawDataUpdateResult>` |
| `textContainerUpgrade(container)` | `Promise<boolean>` |
| `audioControl(isOpen, source?: AudioInputSource)` | `Promise<boolean>` |
| `imuControl(isOpen, reportFrq?)` | `Promise<boolean>` |
| `shutDownPageContainer(exitMode?)` | `Promise<boolean>` |

## 🧯 排障

| 现象 | 处理 |
| --- | --- |
| `Flutter handler not available` | 需要运行在 Even App WebView 内，普通浏览器不能调用原生能力。 |
| 收不到启动来源 | 尽早注册 `onLaunchSource`，宿主只在加载完成后推送一次。 |
| 眼镜 MIC 返回 `false` | 先创建启动页，再调用 `audioControl(true, AudioInputSource.Glasses)`。 |
| 连续定位没有回调 | 先调用 `startAppLocationUpdates`，并保持 `onAppLocationChanged` 订阅。 |
| 眼镜图片不显示 | 先创建/重建图片容器，再调用 `updateImageRawData`。 |

## 📜 更新日志

### 0.0.16

-   修复 `setTimeout` / `setInterval` 回调重复执行的问题。

### 0.0.15

-   最低支持的 Even App 版本提升至 `2.2.10`。
-   长按及释放的 `sysEvent` 保留 `eventSource`，调用方可区分右镜腿、戒指和左镜腿来源。

### 0.0.14

-   最低支持的 Even App 版本提升至 `2.2.9`。
-   新增启动页/重建页容器上的一级上下文菜单模型。
-   新增 `onEvenHubEvent` 中的一级菜单 `menuItemClickEvent` 解析。
-   新增 SDK 侧一级菜单校验：数量、ID 唯一性和 UTF-8 名称长度。
-   新增列表、文本和系统事件中的 `LONG_PRESS_EVENT(9)` 与 `LONG_PRESS_RELEASE_EVENT(10)` 解析。
-   新增文本容器及增量文本更新的可选 `textColor` 字段，支持 `0` 到 `4` 档亮度。
-   新增处理后音频的 `direction` 与 `AudioSpeakerRole`；手机 MIC 和旧 Host 数据安全降级为 `null` 与 `Unknown`。

### 0.0.13

-   补充最低支持的 Even App 版本元数据，最低支持版本为 `2.2.6`。

### 0.0.12

-   新增列表、文本、图片容器的 `zOrderIndex` 支持，用于控制页面中多个容器的前后叠放顺序。
-   图片原始数据更新由 SDK 内部使用 LZ4 压缩，减少传输体积，同时保持快速编解码，降低图片更新延迟。

### 0.0.11

-   新增 App 定位 API：单次定位和连续定位。
-   新增 App 相册图片选择，只支持单选。
-   新增 App 相机拍摄 API。
-   新增 MIC 来源选择：`glasses` 或 `phone`。

### 0.0.10

-   增强 WebView 后台保活能力。

### 0.0.9

-   优化 `EventSourceType` 兼容性。
-   增加默认来源枚举兜底。
-   提升事件来源解析一致性。

### 0.0.8

-   新增启动来源事件：`appMenu` / `glassesMenu`。
-   启动页容器数量从 `4` 扩展到 `12`。
-   新增 IMU 控制和 IMU 数据事件。

### 0.0.1

-   初始桥接、本地存储、设备信息、EvenHub 协议和事件 API。

## 📄 许可证

[MIT](https://npmcn.com/package/%40evenrealities/even_hub_sdk/LICENSE)

## 常见问题

### @evenrealities/even\_hub\_sdk 是什么？

EvenHub 开发者与 Even App 通信的 TypeScript SDK

### 如何安装 @evenrealities/even\_hub\_sdk？

在项目目录运行 npm install @evenrealities/even\_hub\_sdk，或使用 pnpm add / yarn add / bun add。

### @evenrealities/even\_hub\_sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @evenrealities/even\_hub\_sdk 支持 ESM 还是 CommonJS？

同时提供 ES Module 与 CommonJS 入口，import 和 require\(\) 都可以使用，打包工具会自动选择合适的版本。

### @evenrealities/even\_hub\_sdk 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js ^20\.0\.0 \|\| \>=22\.0\.0。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @evenrealities/even\_hub\_sdk 可以商用吗？

@evenrealities/even\_hub\_sdk 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @evenrealities/even\_hub\_sdk 的下载量有多少？

根据 npm 官方下载统计，@evenrealities/even\_hub\_sdk 在 2026/10/14 – 2026/11/12 这 30 天内被下载 437,027 次，日均约 14,568 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @evenrealities/even\_hub\_sdk 还在维护吗？

最新版本 0\.0\.16 发布于 2026/09/24（1 个月前）；至今共发布 16 个版本，其中过去 12 个月发布了 16 个；首次发布于 2026/01/27。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

