/** * 契约 #3 | WanxiangGameSDK 公共 API 类型 + SDK↔宿主 postMessage 协议 * owner:WS3 SDK 负责人 | 消费方:game-studio 宿主 / 游戏侧 * * 硬约束(来自 engineering-conventions §3): * - 零第三方依赖;Core(Lifecycle+EventBus+Telemetry+ErrorTrack) 压缩后 < 8KB;每 Plugin < 5KB * - 所有对外 API 不返回 Promise(fire-and-forget),不占游戏主线程,异常绝不向游戏抛 * - 新版本只增字段/方法、不删不改已有签名(semver) */ /** SDK 初始化参数(宿主注入) */ export interface WanxiangGameSDKInitOptions { gameId: string; versionId: string; /** 贯穿 生成→编译→加载→运行→上报 的 trace_id */ traceId: string; /** 是否开发模式(加载 Plugin.Debug,仅 dev) */ debug?: boolean; } /** 游戏生命周期事件名 */ export type LifecycleEvent = | 'sdk_ready' // SDK 初始化完成 | 'game_loaded' // 游戏资源加载完成 | 'game_start' // 开始游玩 | 'game_end' // 本局结束 | 'game_error'; // 运行错误 /** SDK 根对象(挂在游戏侧 window.WanxiangGameSDK) */ export interface WanxiangGameSDK { /** 初始化(宿主调用一次) */ init(options: WanxiangGameSDKInitOptions): void; /** 生命周期事件订阅(EventBus) */ on(event: LifecycleEvent, handler: (payload?: Record) => void): void; off(event: LifecycleEvent, handler: (payload?: Record) => void): void; /** 遥测上报(fire-and-forget,内部 10条/5s 批量 flush + sendBeacon 兜底) */ track(event: string, props?: Record): void; /** 错误上报(去重,不中断游戏) */ reportError(error: { message: string; stack?: string }): void; /** 插件(首次调用懒加载) */ ad: AdPlugin; pay: PayPlugin; /** 存储插件(首次调用懒加载;idle 离线存档;T1b-β 由前端 host/contract.ts 定稿形态上提至根契约) */ storage: StoragePlugin; } /** 广告插件(MVP 桩 → 真实穿山甲/优量汇切换;每调用 5s 超时降级) */ export interface AdPlugin { /** 激励视频;完成回调 rewarded=true */ showRewarded(slotId: string, cb: (result: { rewarded: boolean }) => void): void; /** 插屏广告 */ showInterstitial(slotId: string, cb?: (result: { shown: boolean }) => void): void; } /** 支付插件(MVP 桩 → 真实微信支付切换) */ export interface PayPlugin { /** 发起积分充值;宿主侧拉起支付 */ pay(order: { orderId: string; amount: number }, cb: (result: { paid: boolean }) => void): void; } /** 存储插件(idle 离线产出存档;HJ-MC-TPL-EXEC-002 §6.1.1 定稿对象形态,T1b-β 上提至根契约,semver 只增不改) */ export interface StoragePlugin { /** * 写入存档(fire-and-forget,不返回 Promise)。宿主侧白名单前缀校验 + 大小上限校验后落 localStorage。 * @param key 存档键(runtime 侧按 'idle:'+gameId+':'+versionId 约定)。 * @param value 存档值(JSON 字符串;宿主侧落库前校验大小)。 */ save(key: string, value: string): void; /** * 读取存档(宿主受信边界已解析为对象后回调;runtime 侧零 JSON.parse,守 runtime §5.5 零解析红线)。 * @param key 存档键。 * @param cb 回调;命中=已解析对象,未命中/校验失败/**非纯对象(含数组)**=null(见 StorageResultPayload.value 的 array 排除)。 */ load(key: string, cb: (value: Record | null) => void): void; } /* ============================================================================ * SDK ↔ 宿主 postMessage 协议 * 游戏在 iframe 沙箱内,通过 postMessage 与 game-studio 宿主通信。 * 宿主侧校验 origin 白名单 + 消息 schema(双校验,见 security-and-reliability §1.1)。 * ========================================================================== */ /** 消息方向 */ export type MessageDirection = 'game_to_host' | 'host_to_game'; /** 消息类型全集 */ export type PostMessageType = | 'init' // 宿主→游戏:初始化参数 | 'lifecycle' // 游戏→宿主:生命周期事件 | 'telemetry' // 游戏→宿主:遥测事件转发 | 'error' // 游戏→宿主:错误上报 | 'ad' // 双向:广告请求/结果(广告在 iframe 外宿主侧渲染) | 'pay' // 双向:支付请求/结果 | 'social' // 双向:分享等 | 'storage'; // 双向:键值存储 /** postMessage 信封 */ export interface PostMessageEnvelope { /** 固定标识,宿主据此过滤非本协议消息 */ channel: 'wanxiang-game-sdk'; type: PostMessageType; direction: MessageDirection; /** 请求/响应配对 ID(ad/pay 等需回调的消息) */ requestId?: string; traceId: string; payload: T; } /* ----- storage 消息 payload(idle 离线产出存档;HJ-MC-TPL-EXEC-002 §6.1.1,T1b-β 上提,semver 只增不改) ----- */ /** storage 写入请求 payload(游戏→宿主:保存键值) */ export interface StorageSetPayload { /** 存档键(runtime 侧按 'idle:'+gameId+':'+versionId 约定,宿主侧白名单前缀校验) */ key: string; /** 存档值(JSON 字符串,宿主侧落 localStorage 前校验大小上限) */ value: string; } /** storage 读取请求 payload(游戏→宿主:按 key 取值;带 requestId,宿主读分支按同 requestId 回包) */ export interface StorageGetPayload { key: string; } /** storage 读取结果 payload(宿主→游戏:回包 value,无则 null) */ export interface StorageResultPayload { key: string; /** * 命中的存档值 = 宿主受信边界已解析好的对象(非原始 JSON 字符串);未命中/校验失败为 null。 * 解析在宿主侧做:runtime 侧零 JSON.parse(守 runtime §5.5「runtime 内不引可抛解析路径」), * loadState resolve 出对象后 runtime 直接读字段。写入 payload(StorageSetPayload.value)仍是 JSON 字符串。 * **对象语义不含数组(T1b-β R4-P1)**:宿主回包端(game-studio inject.ts 已落 !Array.isArray 运行时排除) * 与本类型层须一致——value 虽 TS 标注 Record|null(array 在 TS 是 object 子型不被拒), * 但**契约语义明令排除数组**:数组/字符串/标量一律按未命中处理(resolve null)。 * storage 三套形状(本根契约 / game-studio host/contract.ts / inject.ts 应答端)收敛校验须覆盖此「array→null」运行时语义,不止类型签名对齐。 */ value: Record | null; }