Note
要求 Toooony Head 版本 >= 1.8.0。
Toooony 小程序运行时 API 的 TypeScript 类型定义,为页面提供全局 t4ony 对象及相关类型。
使用 pnpm:
pnpm add -D @ziztechnology/miniprogram-api-typings也可以使用 npm:
npm install --save-dev @ziztechnology/miniprogram-api-typings安装后,需要在项目实际使用的 tsconfig.json 中,通过
compilerOptions.types 引入本包:
{
"compilerOptions": {
"types": ["@ziztechnology/miniprogram-api-typings"]
}
}如果项目已经配置了 types,请保留原有内容并追加本包,不要直接覆盖。例如:
{
"compilerOptions": {
"types": ["node", "vite/client", "@ziztechnology/miniprogram-api-typings"]
}
}
types是一个显式的类型包白名单。配置该字段后,只有列表中的类型包会被加入 全局作用域。在 monorepo 或存在多个tsconfig.json的项目中,请确认修改的是当前 应用实际加载的配置文件。
配置完成后,不需要手动 import,即可直接使用全局 t4ony:
const runtimeInfo = await t4ony.getRuntimeInfo();
const windowInfo = await t4ony.getWindowInfo();
console.log(runtimeInfo.apiVersion);
console.log(windowInfo.windowWidth, windowInfo.windowHeight);如果编辑器没有立即识别到类型,请重启 TypeScript 服务或开发服务器。
除全局对象外,也可以按需导入公开类型:
import type {
RuntimeInfo,
T4ony,
WindowInfo,
} from '@ziztechnology/miniprogram-api-typings';以下示例假定已完成上述安装与 TypeScript 配置。公开 API 仅注入到受支持的
PACKAGED_H5 表盘页面中;普通浏览器、Node.js、服务端渲染环境和
EXTERNAL_URL_H5 页面不保证存在该对象。可选功能应先判断宿主环境:
const hasT4ony =
typeof window !== 'undefined' && typeof window.t4ony === 'object';
if (hasT4ony) {
renderWatchFace();
} else {
renderRuntimeUnavailable();
}应用只能直接调用 T4ony 接口中声明的方法。驾驶状态、驾驶表情、贴纸播放、
车载多媒体和兼容层能力应通过 @ziztechnology/dial-library 使用,不要直接访问
window.unifiedSensorInfo、window.AndroidCarBridge、window.__carBridge 等运行时
内部对象。
getRuntimeInfo() 会返回 API 版本、运行时版本、平台、配置档案,以及每个公开
API 的当前能力状态:
const { capabilities } = await t4ony.getRuntimeInfo();
const sensorCapability = capabilities.getSensorSnapshot;
switch (sensorCapability.state) {
case 'available':
renderSensorUI();
break;
case 'inactive':
case 'denied':
case 'unavailable':
renderSensorUnavailable(sensorCapability.reason);
break;
}能力状态只是调用前的即时快照,实际调用仍可能失败,因此还需要捕获异常。公开
API 没有 canIUse() 方法。
公开方法失败时会拒绝 Promise,并返回可能带有 api、code 和 details 字段的
T4onyError。业务分支应依据稳定的 code,不要依赖错误文案:
import type { T4onyError } from '@ziztechnology/miniprogram-api-typings';
const isT4onyError = (error: unknown): error is T4onyError =>
error instanceof Error &&
typeof (error as Partial<T4onyError>).api === 'string' &&
typeof (error as Partial<T4onyError>).code === 'string';
try {
await t4ony.accessFile({ directory: 'data', path: 'profile.json' });
} catch (error) {
if (isT4onyError(error) && error.code === 'NOT_FOUND') {
renderFirstRunState();
} else {
throw error;
}
}权限、能力不可用、页面失效、参数错误、配额、I/O、忙碌、中止、超时、不支持及
内部错误,均应按照声明中的 T4onyErrorCode 处理。
窗口、运行时、设备和电池信息可以独立读取,也可以并行读取:
const [windowState, runtimeState, deviceState, batteryState] =
await Promise.all([
t4ony.getWindowInfo(),
t4ony.getRuntimeInfo(),
t4ony.getDeviceInfo(),
t4ony.getBatteryInfo(),
]);
console.log(windowState.windowWidth, windowState.windowHeight);
console.log(runtimeState.apiVersion, runtimeState.runtimeVersion);
console.log(deviceState.brand, deviceState.model, deviceState.androidApiLevel);
console.log(batteryState.level, batteryState.isCharging);getWindowInfo() 的尺寸单位为 CSS 像素。设备和电池结果中的可选字段可能不存在,
getDeviceInfo() 也不会提供序列号或厂商私有的系统版本。
屏幕常亮和亮度设置只对当前页面会话生效:
await t4ony.setKeepScreenOn({ keepScreenOn: true });
await t4ony.setScreenBrightness({ mode: 'manual', value: 0.65 });
const brightness = await t4ony.getScreenBrightness();
console.log(brightness.mode, brightness.value);
await t4ony.setScreenBrightness({ mode: 'system' });
await t4ony.setKeepScreenOn({ keepScreenOn: false });手动亮度 value 必须是 0 到 1 之间的有限数值;使用系统亮度时不要传
value。新页面会话开始后,需要重新设置期望值。
文件 API 提供三个虚拟目录:
data:持久化应用文件,可读写。cache:保存可重新生成的临时文件,可读写。package:读取表盘安装包中的文件,只读。
一般条目必须使用相对路径,不能包含反斜杠、NUL、空路径段、.、.. 或末尾
斜杠。只有 accessFile()、statFile() 和 readDirectory() 可用空字符串路径
表示虚拟目录根节点。
在确定分块大小或执行配额敏感的写入前,先读取文件系统信息:
const fileSystemInfo = await t4ony.getFileSystemInfo();
const dataVolume = fileSystemInfo.volumes.find(
(volume) => volume.directory === 'data',
);
if (!dataVolume?.writable) {
throw new Error('数据目录不可用');
}
console.log(dataVolume.remainingBytes);
console.log(fileSystemInfo.limits.maxWriteChunkBytes);文本读写都应显式指定 encoding: "utf8":
await t4ony.createDirectory({
directory: 'data',
path: 'settings',
recursive: true,
});
const writeResult = await t4ony.writeFile({
directory: 'data',
path: 'settings/events.log',
data: '表盘已启动\n',
encoding: 'utf8',
overwrite: true,
createParents: true,
});
await t4ony.appendFile({
directory: 'data',
path: 'settings/events.log',
data: '表盘已恢复\n',
encoding: 'utf8',
});
const textFile = await t4ony.readFile({
directory: 'data',
path: 'settings/events.log',
encoding: 'utf8',
});
console.log(writeResult.bytesWritten, textFile.data, textFile.sizeBytes);二进制写入必须传入真正的 ArrayBuffer。读取大文件时,应按运行时给出的上限
分块,并根据 bytesRead 推进偏移量,直到 endOfFile 为 true:
const { limits } = await t4ony.getFileSystemInfo();
const chunks: Uint8Array[] = [];
let offset = 0;
let endOfFile = false;
while (!endOfFile) {
const result = await t4ony.readFile({
directory: 'package',
path: 'assets/model.bin',
encoding: 'binary',
offset,
length: limits.maxReadChunkBytes,
});
chunks.push(new Uint8Array(result.data));
offset += result.bytesRead;
endOfFile = result.endOfFile;
if (!endOfFile && result.bytesRead === 0) {
throw new Error('二进制读取没有取得进展');
}
}每次二进制写入或追加都不能超过 maxWriteChunkBytes,生成的文件不能超过
maxFileBytes。
其余文件方法可用于检查文件、读取元数据、分页列目录、复制、移动和删除:
await t4ony.accessFile({ directory: 'data', path: 'settings/events.log' });
const stat = await t4ony.statFile({
directory: 'data',
path: 'settings/events.log',
});
const firstPage = await t4ony.readDirectory({
directory: 'data',
path: 'settings',
limit: 50,
});
const secondPage = firstPage.nextCursor
? await t4ony.readDirectory({
directory: 'data',
path: 'settings',
limit: 50,
cursor: firstPage.nextCursor,
})
: null;
await t4ony.copyFile({
source: { directory: 'data', path: 'settings/events.log' },
destination: { directory: 'cache', path: 'events-copy.log' },
overwrite: true,
});
await t4ony.moveFile({
source: { directory: 'cache', path: 'events-copy.log' },
destination: { directory: 'cache', path: 'events-archive.log' },
overwrite: true,
});
await t4ony.removeFile({
directory: 'cache',
path: 'events-archive.log',
ignoreIfNotExists: true,
});
await t4ony.removeDirectory({
directory: 'cache',
path: 'temporary',
recursive: true,
ignoreIfNotExists: true,
});
console.log(stat.type, firstPage.entries, secondPage?.entries);readDirectory() 只返回直接子项。需要完整列表时,应持续使用 nextCursor
翻页,直到结果不再返回游标。
需要跨表盘更新或资源缓存清理保留的数据,应写入 t4ony Storage。它以
miniProgramId(打包表盘对应 faceId)为作用域,与资源版本无关。只允许写入
JSON 值,不能包含函数、undefined、symbol、BigInt、类实例或循环引用。
await t4ony.setStorage({
key: 'preferences',
data: {
theme: 'dark',
complications: ['battery', 'weather'],
},
});
const preferences = await t4ony.getStorage({ key: 'preferences' });
const storageInfo = await t4ony.getStorageInfo();
console.log(
preferences.data,
storageInfo.usedBytes,
storageInfo.remainingBytes,
);
await t4ony.removeStorage({ key: 'preferences' });当前运行时用 code: "INVALID_ARGUMENT" 与
details.reason: "KEY_NOT_FOUND" 的组合表示键不存在,用同一错误码与
details.reason: "QUOTA_EXCEEDED" 表示配额不足。配额敏感的写入前可先调用
getStorageInfo(),同时仍需处理写入期间空间变化导致的异常。
clearStorage() 会删除当前应用命名空间内的全部键,仅应在产品行为明确要求清空
所有数据时调用:
await t4ony.clearStorage();表盘实例被删除、全部实例解除绑定或应用主动清空时,该存储命名空间会被移除。
如果数据只需保留在当前资源版本中,并允许在更新后丢弃,可使用 localStorage。
注册和移除监听器时必须传入同一个函数。监听器注册后不会补发已经发生的
initial 事件,因此当前 UI 状态需要单独初始化:
import type {
T4onyHeadHideListener,
T4onyHeadShowListener,
} from '@ziztechnology/miniprogram-api-typings';
const handleHeadShow: T4onyHeadShowListener = (event) => {
resumeAnimations(event.reason);
};
const handleHeadHide: T4onyHeadHideListener = (event) => {
pauseAnimations(event.reason);
};
await t4ony.onT4onyHeadShow(handleHeadShow);
await t4ony.onT4onyHeadHide(handleHeadHide);
const disposeHeadLifecycle = async (): Promise<void> => {
await Promise.all([
t4ony.offT4onyHeadShow(handleHeadShow),
t4ony.offT4onyHeadHide(handleHeadHide),
]);
};occurredAtElapsedRealtimeMs 适合计算经过时间,不能与 Unix 时间戳比较。
先用 getNetworkType() 取得初始状态,需要实时更新时再注册一个稳定的监听函数:
import type { NetworkStatusListener } from '@ziztechnology/miniprogram-api-typings';
const initialNetworkStatus = await t4ony.getNetworkType();
renderNetworkStatus(initialNetworkStatus);
const handleNetworkStatusChange: NetworkStatusListener = (status) => {
renderNetworkStatus(status);
};
await t4ony.onNetworkStatusChange(handleNetworkStatusChange);
const disposeNetworkStatus = async (): Promise<void> => {
await t4ony.offNetworkStatusChange(handleNetworkStatusChange);
};可直接读取 isConnected、networkType、hasSystemProxy 和 weakNet;
signalStrengthDbm 是可选字段。
getSensorSnapshot() 返回一次性的基础传感器和设备指标。每个指标都要先检查
available 判别字段,再读取 value:
const snapshot = await t4ony.getSensorSnapshot();
if (snapshot.accelerometer.available) {
const { x, y, z } = snapshot.accelerometer.value;
renderAcceleration({ x, y, z });
} else {
renderAccelerationUnavailable(snapshot.accelerometer.unavailableReason);
}
if (snapshot.wifiSsid.available) {
renderWifiName(snapshot.wifiSsid.value);
}连续采样使用 startAccelerometer() 和 startGyroscope()。页面暂时隐藏时应保留
返回的订阅;运行时会在文档冻结时释放底层传感器,并在恢复后自动还原逻辑订阅。
只有组件卸载或永久销毁等不再需要采样的场景才停止订阅:
const accelerometerSubscription = await t4ony.startAccelerometer({
rate: 'ui',
listener(sample) {
renderAcceleration(sample);
},
});
const gyroscopeSubscription = await t4ony.startGyroscope({
rate: 'normal',
listener(sample) {
renderAngularVelocity(sample);
},
});
const stopSensors = async (): Promise<void> => {
await Promise.all([
accelerometerSubscription.stop(),
gyroscopeSubscription.stop(),
]);
};getSensorSnapshot() 不提供驾驶模型使用的原子 drivingMotionFrame 或
drivingSensorContext。驾驶状态功能应使用 @ziztechnology/dial-library 的
DrivingStatusController、calibrateDrivingSensors() 等公开导出。
getBluetoothNowPlaying() 返回一份原始播放快照:
const nowPlaying = await t4ony.getBluetoothNowPlaying();
if (!nowPlaying.available) {
renderBluetoothUnavailable(nowPlaying.status, nowPlaying.message);
} else if (nowPlaying.status !== 'OK' && nowPlaying.status !== 'IDLE') {
renderBluetoothReadFailure(nowPlaying.status, nowPlaying.message);
} else if (!nowPlaying.active) {
renderNoActiveBluetoothSession();
} else {
renderTrack({
title: nowPlaying.title,
artist: nowPlaying.artist,
positionMs: nowPlaying.positionMs,
durationMs: nowPlaying.durationMs,
canPause: nowPlaying.canPause,
});
}直接调用返回 available、status 和 active,不包含 SDK 归一化结果中的
success、displayTitle、displaySubtitle 或 displayDescription。需要归一化的
BluetoothNowPlayingSnapshot 时,请使用 @ziztechnology/dial-library。
此 API 只能读取元数据和播放能力,不能控制播放、订阅变化、获取封面或估算进度。 如果应用自行轮询,页面隐藏时应停止轮询,并避免请求重叠。
已有部署绑定时可无参数调用,也可传入公开字段 clientId 和 scope:
const tokenResult = await t4ony.requestSSOToken({
clientId: 'finance-watch-face',
scope: 'finance.read',
});
const response = await fetch(
new URL('/api/v1/finance/a-stock-app', tokenResult.issuer),
{
headers: {
'X-Toooony-SSO-Token': tokenResult.ssoToken,
},
},
);直接结果字段为 ssoToken、expiresInSeconds、expiresAtEpochSeconds、
deviceId、userId、issuer 和 authorizeUrl。它们不同于 SDK 归一化结果中的
token、expiresAtMs、deviceID、userID 和 authorizeURL。
每次访问受保护接口前都应获取新的单次令牌,并且只通过指定的请求头发送。令牌不应
进入 URL、日志、分析事件、持久化缓存、浏览器存储或组件状态;请求重试时也不能复用
原令牌。公开调用不依赖旧版 SDK 兼容方案中的 requestSSOToken customFields
Handler。
本包只提供类型声明,不提供浏览器模拟器;SDK 的测试入口也不会在真实 window 上
安装完整的公开对象。需要单元测试的直接调用,应封装到应用自有适配器中,再注入
实现相同接口的 mock:
type BatteryAPI = Pick<typeof t4ony, 'getBatteryInfo'>;
export const runtimeBatteryAPI: BatteryAPI = {
getBatteryInfo: () => t4ony.getBatteryInfo(),
};测试中使用实现 BatteryAPI 的普通对象,不要替换生产环境的全局对象。最终验证应在
受支持的 Toooony Runtime 中,以打包表盘方式完成。
pnpm check