[English](./README.md) | 简体中文

# @ray-js/mini-game-sdk

[![npm version](https://img.shields.io/npm/v/@ray-js/mini-game-sdk/latest.svg)](https://www.npmjs.com/package/@ray-js/mini-game-sdk) [![downloads](https://img.shields.io/npm/dt/@ray-js/mini-game-sdk.svg)](https://www.npmjs.com/package/@ray-js/mini-game-sdk)

> 在智能小程序 WebView 内开发小游戏。SDK 通过 `postMessage` 连接 WebView（游戏）与小程序页，游戏内可通过 `ty` 调用小程序 API，并通过 `tyReady` 接收同步数据与多语言配置。

## 特性

- **WebView 侧**：通过 `ty.*` 调用小程序 API、使用 `I18n`、通过 `onMiniEnvReady` 等待环境就绪后再启动游戏。
- **小程序侧**：注入 WebView 上下文、处理 WebView 消息、向游戏下发 `tyReady`（syncData + i18n）。
- **共用**：消息类型常量（`MESSAGE_TYPE_TY_READY` 等），便于两端类型安全地收发消息。

## 通信原理

SDK 通过 `<web-view>` 的消息通道连接**小程序页**与 **WebView（游戏）**：

1. **页面加载** → 小程序侧先调用同步 API（如 `getSystemInfoSync`）和异步 API（如 `getLangContent`），再 `setData({ src, syncData, i18n })`，使 WebView 的地址和下发数据就绪。
2. **WebView 加载** → 小程序收到 `bind:load`（`onWebViewLoad`）后，通过 `postMessageToWebView({ type: 'tyReady', data: { syncData, language, locales } })` 下发 **tyReady**。WebView 侧 SDK 收到后写入内部 `syncData` / `I18n`，并执行所有 `onMiniEnvReady` 回调，游戏即可启动。
3. **游戏调用小程序 API** → WebView 内使用 `ty.xxx(options)`。SDK 会向小程序发送 **tyCall**，小程序侧执行 `ty[xxx](options)` 并回传 **tyResponse**，WebView 侧 SDK 再调用传入的 `success` / `fail` 回调。

因此：**tyReady** 是小程序向 WebView 的一次性「环境就绪」下发（同步数据 + 多语言）；**tyCall / tyResponse** 是每次 `ty.*` 调用的请求/回包。调试时注意：确保 WebView 加载后再发 tyReady，且 `<web-view>` 的 `bind:message` 已绑定 `onMessageFromWebView`。

## 示例与模板

[小游戏组件（涂鸦开发者）](https://developer.tuya.com/material/library_hKiOVClc/component?code=MiniGame)

## 安装

```bash
npm install @ray-js/mini-game-sdk
```

## 使用

### 1. WebView（游戏）入口

在游戏入口（如 `webview/main.js`）中：

```js
import { ty, onMiniEnvReady, I18n } from '@ray-js/mini-game-sdk';

// 等待小程序环境就绪后再启动游戏
onMiniEnvReady(() => {
  // game.start();
});

// 在 WebView 内直接调用小程序 API
ty.getUserInfo({ success: console.log });
ty.getSystemInfo({ success: console.log });

// 使用多语言（语言与文案由 tyReady 下发）
I18n.t('start_btn');
```

### 2. 小程序页面

小程序页需要：（1）在渲染 WebView 前准备好 **syncData** 与 **i18n**；（2）在 `onReady` 中注入 WebView 上下文；（3）在 WebView 的 `load` 回调里发送 **tyReady**，让游戏拿到环境与多语言。

**为何需要 `handleSync`？** WebView 运行在独立 JS 上下文中，无法直接调用小程序的同步 API（如 `getSystemInfoSync`）。因此由页面在 `onLoad` 里统一调用这些同步接口一次，将结果放入 `syncData`，再通过 tyReady 传给 WebView。WebView 侧 SDK 会把这些结果缓存起来，游戏里调用 `ty.getSystemInfoSync()` 等时会直接返回缓存值，无需再发消息。可按游戏需要增删 `handleSync` 中的同步 API。

**页面逻辑**（`miniapp/app.js`）：

```js
import {
  setWebViewContext,
  onMessageFromWebView,
  postMessageToWebView,
  MESSAGE_TYPE_TY_READY,
} from '@ray-js/mini-game-sdk';

Page({
  data: {
    src: '',
    syncData: {},
    i18n: { language: '', locales: {} },
  },

  async onLoad() {
    const syncData = this.handleSync();
    const language = syncData.getSystemInfoSync.language;
    const locales = await new Promise((resolve, reject) => {
      ty.getLangContent({
        success: (res) => resolve(res.langContent),
        fail: reject,
      });
    });

    this.setData({
      src: 'webview://web-mobile/index.html',
      syncData,
      i18n: { language, locales },
    });
  },

  onReady() {
    this.webviewContext = ty.createWebviewContext('webviewContainer');
    setWebViewContext(this.webviewContext);
  },

  onMessageFromWebView,
  onWebViewLoad() {
    postMessageToWebView({
      type: MESSAGE_TYPE_TY_READY,
      data: {
        syncData: this.data.syncData,
        language: this.data.i18n.language,
        locales: this.data.i18n.locales,
      },
    });
  },

  // 在页面侧集中调用同步 API 一次，结果通过 tyReady 传给 WebView，供 ty.getXxxSync() 直接读缓存。
  handleSync() {
    return {
      getSystemInfoSync: ty.getSystemInfoSync(),
      getAccountInfoSync: ty.getAccountInfoSync(),
      getEnterOptionsSync: ty.getEnterOptionsSync(),
      getLaunchOptionsSync: ty.getLaunchOptionsSync(),
    };
  },
});
```

**页面模板**（`miniapp/app.tyml`）：

```xml
<view class="container">
  <web-view
    id="webviewContainer"
    ty:if="{{src}}"
    src="{{src}}"
    bind:message="onMessageFromWebView"
    bind:load="onWebViewLoad"
    progressBar="false"
  />
</view>
```

### 3. 消息类型（可选）

当两端需要共用同一类型字符串（如自定义消息处理）时，可使用导出的常量：

```js
import { MESSAGE_TYPE_TY_READY, MESSAGE_TYPE_TY_RESPONSE, MESSAGE_TYPE_TY_LOG, MESSAGE_TYPE_TY_CALL } from '@ray-js/mini-game-sdk';
```

## 主要 API 用法

### WebView 侧（游戏内）

| API | 用法 |
|-----|------|
| **`ty`** | 调用小程序 API 的代理。仅在 `onMiniEnvReady` 内或之后使用，入参与小程序 API 一致（如 `success`、`fail`、`complete`）。 |
| **`onMiniEnvReady(cb)`** | 注册「小程序环境就绪」回调，在回调内执行 `game.start()` 等。非小程序环境会立即执行。 |
| **`I18n`** | `I18n.t(key, { defaultValue?: string })` 返回当前语言文案；`I18n.language`、`I18n.locales` 由 `tyReady` 下发。 |
| **`miniProgram.postMessage(data)`** | 向小程序发送消息；`data` 须为包含 `type` 的纯对象。内部用于 `ty.*` 与日志。 |
| **`onMiniMessage(cb)`** | 注册非 `tyReady`/`tyResponse` 消息的回调（如自定义业务消息）。 |
| **`setAtopMockData(data)`** | 非小程序环境下，为 `ty.apiRequestByAtop` 设置按 API 名索引的 mock 数据。 |
| **`setLocales(locales)`** | 非小程序环境下，手动设置 `I18n.language` 与 `I18n.locales`。 |
| **`inTyMiniApp`** / **`inTyDevTool`** / **`inTyMiniWebview`** | 布尔值：是否运行在小程序环境、开发者工具、小程序 WebView 内。 |

**示例（WebView）：**

```js
import { ty, onMiniEnvReady, I18n } from '@ray-js/mini-game-sdk';

onMiniEnvReady(() => {
  // 此处可安全使用 ty
  ty.getSystemInfo({ success: (res) => console.log(res) });
});
I18n.t('start_btn'); // 文案 key 来自 tyReady 下发的 locales
```

### 小程序侧（页面内）

| API | 用法 |
|-----|------|
| **`setWebViewContext(context)`** | 在 `onReady` 中调用一次，传入 `ty.createWebviewContext('webviewContainer')`，供 SDK 向 WebView 发消息。 |
| **`onMessageFromWebView(event)`** | 绑定到 `<web-view>` 的 `message` 事件。SDK 会处理 `tyLog`（打印）和 `tyCall`（调用小程序 API 并回传 `tyResponse`）。 |
| **`postMessageToWebView(data)`** | 向 WebView 发消息。WebView 加载完成后发送 `type: MESSAGE_TYPE_TY_READY` 及 `data: { syncData, language, locales }`，游戏即可获得环境与多语言。 |

**示例（小程序）：**

```js
import { setWebViewContext, onMessageFromWebView, postMessageToWebView, MESSAGE_TYPE_TY_READY } from '@ray-js/mini-game-sdk';

Page({
  onReady() {
    setWebViewContext(ty.createWebviewContext('webviewContainer'));
  },
  onMessageFromWebView, // 绑定到 web-view 的 bind:message
  onWebViewLoad() {
    postMessageToWebView({
      type: MESSAGE_TYPE_TY_READY,
      data: { syncData: this.data.syncData, language: this.data.i18n.language, locales: this.data.i18n.locales },
    });
  },
});
```

### 共用（常量）

| 常量 | 值 | 使用场景 |
|------|-----|----------|
| `MESSAGE_TYPE_TY_READY` | `'tyReady'` | 小程序向 WebView 下发环境与 i18n。 |
| `MESSAGE_TYPE_TY_RESPONSE` | `'tyResponse'` | 小程序回传 `ty.*` 调用结果（由 SDK 处理）。 |
| `MESSAGE_TYPE_TY_LOG` | `'tyLog'` | WebView 日志发往小程序（由 SDK 处理）。 |
| `MESSAGE_TYPE_TY_CALL` | `'tyCall'` | WebView 调用小程序 API（由 SDK 处理）。 |

当两端需要共用同一类型字符串（如自定义处理或判断）时，使用上述常量。

## API 概览

| 端         | 导出 |
|------------|------|
| WebView    | `ty`、`I18n`、`miniProgram`、`onMiniEnvReady`、`onMiniMessage`、`setAtopMockData`、`setLocales`、`inTyMiniApp`、`inTyDevTool`、`inTyMiniWebview` |
| 小程序     | `setWebViewContext`、`onMessageFromWebView`、`postMessageToWebView` |
| 共用       | `MESSAGE_TYPE_TY_READY`、`MESSAGE_TYPE_TY_RESPONSE`、`MESSAGE_TYPE_TY_LOG`、`MESSAGE_TYPE_TY_CALL` |