npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@evenrealities/even_hub_sdk

v0.0.12

Published

TypeScript SDK for Even Hub developers to communicate with Even App

Readme

@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.12
  • 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,
  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
});

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

说明:

  • 默认来源:AudioInputSource.Glasses
  • 音频数据通过 onEvenHubEvent 下发。
  • 使用眼镜 MIC 前,先创建启动页。

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 是协议档位:P100P1000

🕶️ 眼镜 UI

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

import {
  ImageContainerProperty,
  ImageRawDataUpdateResult,
  ListContainerProperty,
  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',
  isEventCapture: 0,
}];

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

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

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

更新图片内容:

图片原始数据由 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,
  }],
});

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

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); // 交给前台交互层决定是否退出

规则:

  • 坐标原点:左上角。
  • containerTotalNum112
  • textObject:最多 8 个。
  • 只能有一个容器使用 isEventCapture: 1
  • 图片容器创建后,需要再调用 updateImageRawData
  • zOrderIndex 只有在同一个画面的所有容器都不填写时才可以省略,用于兼容旧版本 SDK 页面。
  • 同一个画面里,只要任意容器填写了 zOrderIndex,所有 list/text/image 容器都必须填写。
  • 同一个画面内 zOrderIndex 数值必须唯一;数值越大,画面层级越靠前。
  • 违反上述 zOrderIndex 规则时,SDK 会在调用原生前输出 EvenHubPageContainerValidationErrorCode 错误日志。createStartUpPageContainer 返回 StartUpPageCreateResult.invalidrebuildPageContainer 返回 false

📚 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 |

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.12

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

0.0.11

  • 新增 App 定位 API:单次定位和连续定位。
  • 新增 App 相册图片选择,只支持单选。
  • 新增 App 相机拍摄 API。
  • 新增 MIC 来源选择:glassesphone

0.0.10

  • 增强 WebView 后台保活能力。

0.0.9

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

0.0.8

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

0.0.1

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

📄 许可证

MIT