M0: contracts 契约类型 SSOT(T-M0-04)

落地《契约规范》§8/§9/§12,全工程唯一来源:
- InboundHandlers 枚举:45 入站 handler 名,原样拼写(finsh/canclevibrator/opensaoma 等)
- OutboundHandlers 枚举:21 出站 handler 名,含命名陷阱(getphoneinfo≠getphoneInfo、
  getBattery≠getbattery、backgameData-末尾连字符)
- PayloadType:入站/出站 raw|json 标注表 + 入站同步返回类型 + 查询函数
- DTO interface ×17(§12 字段同名同义):Sharetype/Maplocation/PhoneInfo/savephoto/
  videoinfo/Othervideoinfo/GamePay/PayState/ShareLogin/ShareSuccess 等
- Result<T> + Errors(ErrorKind/LocationErrorCode/AppError)
- 经 entry 临时 smoke 全量引用,CompileArkTS 编译通过

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
lanterngamescn
2026-06-25 07:09:17 +08:00
co-authored by Claude Opus 4.8
parent 424c5f4d94
commit 26299c06ce
7 changed files with 567 additions and 3 deletions
+174
View File
@@ -0,0 +1,174 @@
/**
* 数据结构汇总(《契约规范》§12)。
*
* 🔴 字段名严格对应原 Android 代码,序列化时必须【同名同语义】,不可改名/改大小写。
* 多数字段为 String(H5 历史习惯),数值型按契约标注。
*/
/** 分享入参(friendsSharetypeUrlToptitleDescript 的 data),全 String。§12 sharetypeBean */
export interface SharetypeBean {
/** "1" 好友(弹面板) / "2" 朋友圈(直发微信) */
sharefriend: string;
/** "1" 网页/文本 / "2" Canvas截图 / "3" 图片链接 / "4" 视频(抖音) */
type: string;
/** 分享子类型 */
sharetype: string;
/** 分享链接 或 图片地址 */
webpageUrl: string;
title: string;
description: string;
}
/** 定位回传(getlocationinfo 的 data)。§12 MaplocationInfo */
export interface MaplocationInfo {
latitude: number; // double
longitude: number; // double
address: string;
country: string;
province: string;
city: string;
district: string;
street: string;
cityCode: string;
streetNum: string;
adCode: string;
aoiName: string;
accuracy: number; // float
locationType: number; // int
/** 0 成功 / -1 未就绪 / -2 权限拒绝 / 其他=底层错误码 */
errorCode: number;
errorMsg: string;
}
/** 手机信息回传(getphoneinfo 出站 data),全 String。§12 phoneInfoBean */
export interface PhoneInfoBean {
PhoneVersion: string;
PhoneAdresseMAC: string;
PhoneModel: string;
PhoneDeviceBrand: string;
PhoneProvidersName: string;
PhoneIMEI: string;
PhoneIMSI: string;
}
/** 图片下载数组元素(getphoto 回传数组)。§12 savephotoURLBean */
export interface SavephotoURLBean {
pid: string;
photourl: string;
}
/** 加入/创建房间入参(createRoom 的 data),全 String。§12 videoinfobean */
export interface VideoinfoBean {
playerid: string;
roomid: string;
agentid: string;
gameid: string;
left: string;
top: string;
pmw: string;
pmh: string;
width: string;
height: string;
}
/** 请求远端视频窗口入参(getVideoinfo 入站的 data)。§12 Othervideoinfo */
export interface OthervideoinfoBean {
playerid: string;
left: string;
top: string;
width: string;
height: string;
pmw: string;
pmh: string;
}
/** App 内微信支付入参(getGameplay 的 data)。§12 支付相关 */
export interface GamePayReq {
price: string;
body: string;
typeplay: number; // int
type: string;
user: number; // int
}
/** App 内微信支付结果回传(PayuserPaytypePaystate 出站 data)。§12 支付相关 */
export interface PayStateResp {
user: string;
state: number; // int1 成功 / 0 取消
typeplay: string;
type: string;
data: string;
}
/** 微信登录回传(sharelogin 出站 data),全 String。§12 sharelogin */
export interface ShareLoginResp {
openid: string;
headimgurl: string;
nickname: string;
sex: string;
city: string;
province: string;
unionid: string;
}
/** 分享结果回传(sharesuccess 出站 data)。§12 sharesuccess */
export interface ShareSuccessResp {
/** 2 成功 / 3 取消 */
success: number;
/** 1 好友 / 2 朋友圈 */
type: number;
}
/** 打开通用网页容器入参(OpenurlTitleData 的 data)。§8.8 / §11.2 */
export interface OpenUrlTitleDataReq {
url: string;
title: string;
data: string;
}
/** 按包名启动 App 入参(openApplyDownloadpath 的 data)。§8.8 */
export interface OpenApplyDownloadReq {
packagename: string;
downloadpath: string;
}
/** 容器内切换子游戏入参(SwitchOverGameData 的 data)。§11.1 */
export interface SwitchOverGameReq {
/** "2" 竖屏 / "3" 横屏 */
webtype: string;
/** 目标游戏目录名 */
Gamedirectory: string;
/** 游戏 id(用于判断/下载) */
gamedownloadurl: string;
/** 交换数据 */
data: string;
}
/** 播放语音消息入参(mediaTypeAudio 的 data)。§8.7 */
export interface MediaTypeAudioReq {
audiourl: string;
type: string;
user: string;
}
/** 播放游戏音效/背景乐入参(srcIsloop 的 data)。§8.7 / §10.6 */
export interface SrcIsLoopReq {
/** wav 文件名(非 URL),路径 <解压根>/<game>/assets/wav/<src> */
src: string;
/** 0 播一次 / >0 循环 / <0 停止 */
isloop: number;
}
/** 录音上传完成回传(getaudiourl 出站 data)。§9 / §10.6 */
export interface GetAudioUrlResp {
audiourl: string;
time: number; // float
/** 旧容器 webviewActivity 录音成功时额外携带,值同 audiourl */
filepath?: string;
}
/** WiFi 信号回传(getwifiLevel 出站 data)。§9 */
export interface WifiLevelResp {
ssidname: string;
signalLevel: number; // int 0~4
}
@@ -0,0 +1,122 @@
/**
* 入站 Handler 全集(H5 → 原生)。
*
* 来源:《TSGame_原生与H5接口契约总规范》§8。H5 通过
* `bridge.callHandler('<名称>', '<data>', responseCallback)` 调用,原生用
* `registerHandler('<名称>', ...)` 接收。
*
* 🔴 铁律:枚举【值】是 H5 实际发出的字符串,**必须与原 Android 代码逐字一致**,
* 包括拼写错误(`finsh`/`getcameraaAddress`/`canclevibrator` 等),不可"纠正"。
* 枚举【成员名】仅供 ArkTS 侧引用,可用规范驼峰。
*/
export enum InboundHandlers {
// —— §8.1 账号 / 社交 ——
/** 触发授权登录("1"=QQ 空实现 / 其他=微信)。异步回传 sharelogin */
AccreditLogin = 'accreditlogin',
/** 分享(微信/QQ/抖音),data=sharetypeBean JSON。异步回传 sharesuccess(仅微信) */
FriendsShare = 'friendsSharetypeUrlToptitleDescript',
/** 批量下载图片/头像到本地,data=图片下载 JSON 数组。异步回传 getphoto */
GetPhoto = 'getphoto',
// —— §8.2 支付 ——
/** 浏览器方式支付(当前生效),data=收银台 URL。无回传 */
PayBrowser = 'paybrowser',
/** App 内微信支付(仅旧版容器),data=GamePay JSON。异步回传 PayuserPaytypePaystate */
GetGameplay = 'getGameplay',
// —— §8.3 设备 / 系统信息 ——
/** 获取系统时间,同步返回毫秒时间戳裸串 */
GetTime = 'getTime',
/** 获取电话状态,同步返回 int 裸串(0 挂断/1 接起/2…) */
GetPhoneState = 'getphonestate',
/** 主动获取电量,异步回传 getBattery */
GetBattery = 'getbattery',
/** 主动获取 WiFi 信号,异步回传 getwifiLevel */
GetWifiLevel = 'getwifiLevel',
/** 主动获取网络状态,同步返回 int 裸串(1 无网/2 WiFi/3 移动) */
GetNetwork = 'getnetwork',
/** App 版本比较结果,同步返回 int 裸串(1=本地>网络,0=否) */
GetCompareCode = 'getcompareCode',
/** 获取手机配置信息(注意大写 I,区别出站 getphoneinfo),异步回传 getphoneinfo */
GetPhoneInfo = 'getphoneInfo',
/** 获取市场 ID,同步返回 market 值裸串 */
GetMarketName = 'getmarketname',
/** 获取任意本地配置值,data=配置名,同步返回该配置值裸串 */
GetOtherName = 'getothername',
/** 获取 other 配置值,同步返回裸串 */
GetOther = 'getOther',
// —— §8.4 交互 / 反馈 ——
/** 切换屏幕方向,data="1"横屏/其他竖屏 */
Orientation = 'orientation',
/** 震动,data=long 毫秒裸串 */
Vibrator = 'vibrator',
/** 重复震动,data=int-1 否/1 重复) */
RepeatVibrator = 'repeatvibrator',
/** 取消震动(拼写原样 cancle */
CancleVibrator = 'canclevibrator',
/** 写入剪贴板,data=文本 */
GameCopyText = 'gameCopytext',
/** 读取剪贴板,同步返回剪贴板内容裸串 */
GamePasteText = 'gamepastetext',
/** 发送通知(空实现,未落地),data=裸串 */
Notification = 'notification',
// —— §8.5 摇一摇 ——
/** 开始监听摇一摇,异步回传 shakeEnd */
StartShake = 'startshake',
/** 摇一摇声音开关,data="1"开/其他关 */
SwitchShake = 'SwitchShake',
/** 停止监听摇一摇 */
StopShake = 'stopshake',
// —— §8.6 定位 ——
/** 开启定位,data="1"连续/其他单次。异步回传 getlocationinfo */
StartLocation = 'startlocation',
/** 主动取最近一次定位,同步返回 MaplocationInfo JSON(未就绪返回错误码 JSON */
GetLocationInfo = 'getlocationinfo',
// —— §8.7 音频 ——
/** 准备/开始录音(长按)。异步回传 getaudiourl */
PrepareAudio = 'prepareaudio',
/** 播放语音消息,data=audiourl/type/user JSON。异步回传 gameui_play_voice/gameui_stop_voice */
MediaTypeAudio = 'mediaTypeAudio',
/** 播放游戏音效/背景乐,data=src/isloop JSON。无回传 */
SrcIsLoop = 'srcIsloop',
/** 语音播放总开关,data="1"开/其他静音 */
VoicePlaying = 'voicePlaying',
// —— §8.8 扫码 / 相机 / 浏览器 / 网页 ——
/** 打开扫一扫(拼写原样 saoma),异步回传 getsaomaData */
OpenSaoma = 'opensaoma',
/** 打开相机拍照,异步回传 getcameraaAddress */
OpenCamera = 'opencamera',
/** 系统/QQ 浏览器打开链接,data=URL */
Browser = 'browser',
/** 打开内置通用网页容器(子游戏跳转路径 B),data=url/title/data JSON。回传 getWebdata */
OpenUrlTitleData = 'OpenurlTitleData',
/** 按包名启动 App,失败则浏览器打开下载地址,data=packagename/downloadpath JSON */
OpenApplyDownloadPath = 'openApplyDownloadpath',
// —— §8.9 子游戏切换 / 房间(音视频) ——
/** 容器内切换子游戏(路径 A),data=webtype/Gamedirectory/gamedownloadurl/data JSON */
SwitchOverGameData = 'SwitchOverGameData',
/** 判断子游戏资源是否就绪,data=目录名,同步返回 "1"已装/"0"未装 */
GetGameInstall = 'getGameinstall',
/** 加入/创建音视频房间(声网),data=videoinfobean JSON。【本期桩】 */
CreateRoom = 'createRoom',
/** 退出房间,data=裸串。【本期桩】 */
ExitRoom = 'exitRoom',
/** 请求/绑定远端视频窗口,data=Othervideoinfo JSON。异步回传 getVideoinfo。【本期桩】 */
GetVideoInfo = 'getVideoinfo',
/** 视频悬浮框显隐,data="1"显示/其他隐藏。【本期桩】 */
DragViewVideoIsShow = 'DragViewvideoIsshow',
// —— §8.10 退出 / 返回 ——
/** 退出游戏(拼写原样 finsh,非 finish */
Finsh = 'finsh',
/** 子游戏带数据返回大厅(仅 NewwebviewActivity),data=裸串 */
BackGameData = 'backgameData',
/** 获取通讯录(未实现/已注释,保留名以兜底) */
GetAddressBook = 'getAddressBook',
}
@@ -0,0 +1,57 @@
/**
* 出站 Handler 全集(原生 → H5)。
*
* 来源:《TSGame_原生与H5接口契约总规范》§9。原生通过
* `callHandler('<名称>', '<data>', null)` 调用;H5 必须用
* `registerHandler('<名称>', ...)` 注册才能收到。
*
* 🔴 命名陷阱(与入站对照,逐字保留):
* - 出站 `getphoneinfo`(全小写) ≠ 入站 `getphoneInfo`(大写 I)。
* - 出站 `getBattery`(大写 B ≠ 入站 `getbattery`(小写 b)。
* - 出站 `backgameData-`**末尾连字符** ≠ 入站 `backgameData`(无连字符)。
* - getphoto/getwifiLevel/getnetwork/getlocationinfo/getVideoinfo 入站出站【同名】。
*/
export enum OutboundHandlers {
/** 前后台状态,data="1"前台/"2"后台。首次加载 100% 及前后台切换时下发 */
AppService = 'appservice',
/** 截图上传地址 http://<本机IP>:<端口>/testurl。页面加载完成时下发 */
SetPostUrl = 'setPostUrl',
/** 网页回传 text / Intent 透传 data。页面首次加载完成;或通用网页关闭回传(101) */
GetWebData = 'getWebdata',
/** 图片/头像下载完成,data=[{pid,photourl}] JSON 数组。与入站 getphoto 同名 */
GetPhoto = 'getphoto',
/** 微信授权登录成功,data=sharelogin JSON(用户资料) */
ShareLogin = 'sharelogin',
/** 分享结果,data={success,type} JSONsuccess 2 成功/3 取消)。仅微信完整 */
ShareSuccess = 'sharesuccess',
/** 摇一摇触发后约 1 秒,data="" 空串 */
ShakeEnd = 'shakeEnd',
/** 电量,data=float 裸串(0~1)。大写 B,区别入站 getbattery */
GetBattery = 'getBattery',
/** WiFi 信号,data={ssidname,signalLevel(0~4)} JSON。与入站同名 */
GetWifiLevel = 'getwifiLevel',
/** 网络状态变化,data=int 裸串(1 断网/2 WiFi/3 移动)。与入站同名 */
GetNetwork = 'getnetwork',
/** 录音上传完成/取消,data={audiourl,time(,filepath)} JSON */
GetAudioUrl = 'getaudiourl',
/** 语音开始播放,data=user 透传裸串 */
GameUiPlayVoice = 'gameui_play_voice',
/** 语音播放完成/被打断,data=user 透传裸串 */
GameUiStopVoice = 'gameui_stop_voice',
/** 手机配置信息,data=phoneInfoBean JSON。全小写,区别入站 getphoneInfo */
GetPhoneInfo = 'getphoneinfo',
/** 房间其他用户加入,data=int 裸串(远端 uid)。【本期桩不推】 */
GetVideoInfo = 'getVideoinfo',
/** App 内微信支付结果,data={user,state,typeplay,type,data} JSON。【本期桩不推】 */
PayUserPayTypePayState = 'PayuserPaytypePaystate',
/** 定位结果,data=MaplocationInfo JSON(或错误码 JSON)。与入站同名 */
GetLocationInfo = 'getlocationinfo',
/** 来电状态广播,data=int 裸串 */
PhoneState = 'phonestate',
/** 扫码返回,data=扫码结果裸串 */
GetSaomaData = 'getsaomaData',
/** 拍照返回(拼写原样双 a),data=照片路径裸串 */
GetCameraAddress = 'getcameraaAddress',
/** 返回键确认对话框流程,data="" 空串。⚠ 末尾连字符,区别入站 backgameData */
BackGameDataDash = 'backgameData-',
}
@@ -0,0 +1,112 @@
/**
* 每个 handler 的载荷类型标注(《契约规范》§8/§9,框架 §5.4 "裸串 vs JSON 陷阱")。
*
* `raw` —— data 是裸字符串,能力层直接使用,不做 JSON 解析/序列化。
* `json` —— data 是 JSON 字符串,能力层需 JSON.parse(入站)/ JSON.stringify(出站)。
*
* 用途:MessageCodec 不擅自 JSON 化 data;能力层据此逐 handler 决定如何处理载荷,
* 编译期可查、杜绝写错。查询用 `inboundPayloadType()` / `outboundPayloadType()`。
*/
import { InboundHandlers } from './InboundHandlers';
import { OutboundHandlers } from './OutboundHandlers';
export type PayloadType = 'raw' | 'json';
/** 入站 handler 的入参载荷类型(H5 → 原生)。 */
export const InboundPayload: Map<string, PayloadType> = new Map<string, PayloadType>([
[InboundHandlers.AccreditLogin, 'raw'],
[InboundHandlers.FriendsShare, 'json'],
[InboundHandlers.GetPhoto, 'json'],
[InboundHandlers.PayBrowser, 'raw'],
[InboundHandlers.GetGameplay, 'json'],
[InboundHandlers.GetTime, 'raw'],
[InboundHandlers.GetPhoneState, 'raw'],
[InboundHandlers.GetBattery, 'raw'],
[InboundHandlers.GetWifiLevel, 'raw'],
[InboundHandlers.GetNetwork, 'raw'],
[InboundHandlers.GetCompareCode, 'raw'],
[InboundHandlers.GetPhoneInfo, 'raw'],
[InboundHandlers.GetMarketName, 'raw'],
[InboundHandlers.GetOtherName, 'raw'],
[InboundHandlers.GetOther, 'raw'],
[InboundHandlers.Orientation, 'raw'],
[InboundHandlers.Vibrator, 'raw'],
[InboundHandlers.RepeatVibrator, 'raw'],
[InboundHandlers.CancleVibrator, 'raw'],
[InboundHandlers.GameCopyText, 'raw'],
[InboundHandlers.GamePasteText, 'raw'],
[InboundHandlers.Notification, 'raw'],
[InboundHandlers.StartShake, 'raw'],
[InboundHandlers.SwitchShake, 'raw'],
[InboundHandlers.StopShake, 'raw'],
[InboundHandlers.StartLocation, 'raw'],
[InboundHandlers.GetLocationInfo, 'raw'],
[InboundHandlers.PrepareAudio, 'raw'],
[InboundHandlers.MediaTypeAudio, 'json'],
[InboundHandlers.SrcIsLoop, 'json'],
[InboundHandlers.VoicePlaying, 'raw'],
[InboundHandlers.OpenSaoma, 'raw'],
[InboundHandlers.OpenCamera, 'raw'],
[InboundHandlers.Browser, 'raw'],
[InboundHandlers.OpenUrlTitleData, 'json'],
[InboundHandlers.OpenApplyDownloadPath, 'json'],
[InboundHandlers.SwitchOverGameData, 'json'],
[InboundHandlers.GetGameInstall, 'raw'],
[InboundHandlers.CreateRoom, 'json'],
[InboundHandlers.ExitRoom, 'raw'],
[InboundHandlers.GetVideoInfo, 'json'],
[InboundHandlers.DragViewVideoIsShow, 'raw'],
[InboundHandlers.Finsh, 'raw'],
[InboundHandlers.BackGameData, 'raw'],
[InboundHandlers.GetAddressBook, 'raw'],
]);
/**
* 入站 handler 的【同步返回】载荷类型(仅对有同步返回的 handler 标注)。
* 未列出者表示无同步返回(结果走出站异步推送或无返回)。
*/
export const InboundSyncReturn: Map<string, PayloadType> = new Map<string, PayloadType>([
[InboundHandlers.GetTime, 'raw'],
[InboundHandlers.GetPhoneState, 'raw'],
[InboundHandlers.GetNetwork, 'raw'],
[InboundHandlers.GetCompareCode, 'raw'],
[InboundHandlers.GetMarketName, 'raw'],
[InboundHandlers.GetOtherName, 'raw'],
[InboundHandlers.GetOther, 'raw'],
[InboundHandlers.GamePasteText, 'raw'],
[InboundHandlers.GetLocationInfo, 'json'],
[InboundHandlers.GetGameInstall, 'raw'],
]);
/** 出站 handler 的下发载荷类型(原生 → H5)。 */
export const OutboundPayload: Map<string, PayloadType> = new Map<string, PayloadType>([
[OutboundHandlers.AppService, 'raw'],
[OutboundHandlers.SetPostUrl, 'raw'],
[OutboundHandlers.GetWebData, 'raw'],
[OutboundHandlers.GetPhoto, 'json'],
[OutboundHandlers.ShareLogin, 'json'],
[OutboundHandlers.ShareSuccess, 'json'],
[OutboundHandlers.ShakeEnd, 'raw'],
[OutboundHandlers.GetBattery, 'raw'],
[OutboundHandlers.GetWifiLevel, 'json'],
[OutboundHandlers.GetNetwork, 'raw'],
[OutboundHandlers.GetAudioUrl, 'json'],
[OutboundHandlers.GameUiPlayVoice, 'raw'],
[OutboundHandlers.GameUiStopVoice, 'raw'],
[OutboundHandlers.GetPhoneInfo, 'json'],
[OutboundHandlers.GetVideoInfo, 'raw'],
[OutboundHandlers.PayUserPayTypePayState, 'json'],
[OutboundHandlers.GetLocationInfo, 'json'],
[OutboundHandlers.PhoneState, 'raw'],
[OutboundHandlers.GetSaomaData, 'raw'],
[OutboundHandlers.GetCameraAddress, 'raw'],
[OutboundHandlers.BackGameDataDash, 'raw'],
]);
export function inboundPayloadType(name: string): PayloadType {
return InboundPayload.get(name) ?? 'raw';
}
export function outboundPayloadType(name: string): PayloadType {
return OutboundPayload.get(name) ?? 'raw';
}
+47
View File
@@ -0,0 +1,47 @@
/**
* 错误码与错误类型定义。
*
* 定位错误码来自《契约规范》§10.8 / §12 MaplocationInfo.errorCode
* 其余为框架内部统一错误分类(框架 §10 ErrorCenter)。
*/
/** 定位错误码(MaplocationInfo.errorCode 取值约定)。 */
export enum LocationErrorCode {
Success = 0,
/** 高德/底层原始错误码为正数时按原值透传 */
NotReady = -1,
PermissionDenied = -2,
}
/** 框架内部错误类别(用于 ErrorCenter 收敛与处置策略)。 */
export enum ErrorKind {
/** 网络/请求失败 */
Network = 'network',
/** 配置解析/缺失 */
Config = 'config',
/** 资源下载/解压失败 */
Resource = 'resource',
/** 权限被拒 */
Permission = 'permission',
/** 桥消息编解码/分发异常 */
Bridge = 'bridge',
/** 能力 Provider 内部异常 */
Capability = 'capability',
/** 未分类 */
Unknown = 'unknown',
}
/** 统一错误对象。 */
export interface AppError {
kind: ErrorKind;
/** 机器可读错误码(可选,如定位错误码、HTTP 状态码) */
code?: number;
/** 人类可读信息 */
message: string;
/** 原始底层错误(可选) */
cause?: Object;
}
export function makeError(kind: ErrorKind, message: string, code?: number, cause?: Object): AppError {
return { kind, message, code, cause };
}
+35
View File
@@ -0,0 +1,35 @@
/**
* 统一返回类型 Result<T>(框架 §10 横切:错误中心)。
*
* 成功携带 data,失败携带 AppError。用工厂方法构造,避免散落的 try/catch
* 直接抛到上层;调用方用 `isOk()` 收窄后取 `data`/`error`。
*/
import { AppError, ErrorKind, makeError } from './Errors';
export class Result<T> {
readonly success: boolean;
readonly data?: T;
readonly error?: AppError;
private constructor(success: boolean, data?: T, error?: AppError) {
this.success = success;
this.data = data;
this.error = error;
}
static ok<T>(data: T): Result<T> {
return new Result<T>(true, data, undefined);
}
static fail<T>(error: AppError): Result<T> {
return new Result<T>(false, undefined, error);
}
static failWith<T>(kind: ErrorKind, message: string, code?: number): Result<T> {
return new Result<T>(false, undefined, makeError(kind, message, code));
}
isOk(): boolean {
return this.success;
}
}