@evenrealities/even_hub_sdk

中文文档
0.0.16发布于 2026/09/24MITTypeScriptESM + CJS零依赖近 30 天下载 43.7万

TypeScript SDK for Even Hub developers to communicate with Even App

原始使用文档
文档目录28 节
  1. ⚡️ 快速开始
  2. 📦 安装
  3. 🔧 常用能力
  4. 启动来源
  5. 设备状态
  6. App 定位
  7. 相册与相机
  8. MIC 来源
  9. IMU
  10. 🕶️ 眼镜 UI
  11. 📚 API 速查
  12. Bridge
  13. App
  14. 事件
  15. EvenHub
  16. 🧯 排障
  17. 📜 更新日志
  18. 0.0.16
  19. 0.0.15
  20. 0.0.14
  21. 0.0.13
  22. 0.0.12
  23. 0.0.11
  24. 0.0.10
  25. 0.0.9
  26. 0.0.8
  27. 0.0.1
  28. 📄 许可证

@evenrealities/even_hub_sdk

Even App WebView 页面使用的 TypeScript SDK。

npm version License: MIT Node.js Version

English | 中文

⚡️ 快速开始

npm install @evenrealities/even_hub_sdk
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、启动来源、设备状态。

📦 安装

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 完成后推送一次启动来源。

const bridge = await waitForEvenAppBridge();

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

// unsubscribe();

取值:

  • appMenu
  • glassesMenu

设备状态

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 定位

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);
}

连续定位:

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();

相册与相机

相册只支持单选。

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:

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

MIC 来源

可选择眼镜 MIC 或手机 MIC。

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

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 操作。

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 压缩,以减少传输体积,同时保持设备端快速解码。

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

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

重建页面:

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

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

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

处理上下文菜单事件:

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

// unsubscribeMenu();

处理长按和长按释放:

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(右镜腿、戒指或左镜腿),不会再丢失输入来源。

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

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();

关闭眼镜页面:

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

版本与发布记录

@evenrealities/even_hub_sdk 共发布 16 个版本,首次发布于 2026/01/27;过去 12 个月发布了 16 个版本。

版本发布日期距今
0.0.16latest2026/09/241 个月前
0.0.152026/09/072 个月前
0.0.142026/08/202 个月前
0.0.132026/07/313 个月前
0.0.122026/07/104 个月前
0.0.112026/06/224 个月前

关于 @evenrealities/even_hub_sdk 的常见问题

@evenrealities/even_hub_sdk 是什么?

TypeScript SDK for Even Hub developers to communicate with Even App

如何安装 @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 和提交记录判断。