English | [简体中文](./README-zh_CN.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)

> Develop mini-games inside the mini-program WebView. The SDK bridges the WebView (game) and the mini-program page via `postMessage`, so the game can call mini-program APIs through `ty` and receive sync data / i18n via `tyReady`.

## Features

- **WebView side**: Call mini-program APIs via `ty.*`, use `I18n`, and wait for env ready with `onMiniEnvReady`.
- **Mini-program side**: Inject WebView context, handle messages from WebView, and send `tyReady` (syncData + i18n) to the game.
- **Shared**: Message type constants (`MESSAGE_TYPE_TY_READY`, etc.) for type-safe messaging.

## Communication principle

The SDK connects **mini-program page** and **WebView (game)** via the `<web-view>` message channel:

1. **Page loads** → Mini-program calls sync APIs (`getSystemInfoSync`, etc.) and async APIs (e.g. `getLangContent`), then `setData({ src, syncData, i18n })` so the WebView URL and data are ready.
2. **WebView loads** → Mini-program receives `bind:load` (`onWebViewLoad`) and sends **tyReady** with `postMessageToWebView({ type: 'tyReady', data: { syncData, language, locales } })`. The WebView SDK receives this, writes to internal `syncData` / `I18n`, and runs all `onMiniEnvReady` callbacks so the game can start.
3. **Game calls mini-program API** → WebView uses `ty.xxx(options)`. The SDK sends **tyCall** to the mini-program, which runs `ty[xxx](options)` and sends back **tyResponse** with the result. The WebView SDK then invokes the `success` / `fail` callbacks.

So: **tyReady** = one-time bootstrap (sync data + i18n) from mini-program to WebView; **tyCall / tyResponse** = request/response for each `ty.*` call. When debugging, check that tyReady is sent after WebView load and that `bind:message` is wired to `onMessageFromWebView`.

## Demo & Template

[Mini Game Component (Tuya Developer)](https://developer.tuya.com/material/library_hKiOVClc/component?code=MiniGame)

## Installation

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

## Usage

### 1. WebView (game) entry

In your game entry (e.g. `webview/main.js`):

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

// Wait for mini-program env; then start the game.
onMiniEnvReady(() => {
  // game.start();
});

// Call mini-program APIs in the WebView.
ty.getUserInfo({ success: console.log });
ty.getSystemInfo({ success: console.log });

// Use i18n (language/locales come from tyReady).
I18n.t('start_btn');
```

### 2. Mini-program page

The mini-program page must (1) prepare **syncData** and **i18n** before rendering the WebView, (2) inject the WebView context in `onReady`, (3) send **tyReady** in the WebView’s `load` handler so the game receives env and i18n.

**Why `handleSync`?** The WebView runs in a separate JS context and cannot call the mini-program’s synchronous APIs (e.g. `getSystemInfoSync`) directly. So the page calls these sync APIs once in `onLoad`, puts the results into `syncData`, and passes `syncData` to the WebView via tyReady. The WebView SDK then exposes them so that calls like `ty.getSystemInfoSync()` in the game return the cached result without a round-trip. You can add or remove sync API names in `handleSync` to match what your game needs.

**Page logic** (`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,
      },
    });
  },

  // Collect sync API results once; WebView will use them for ty.getXxxSync() without round-trips.
  handleSync() {
    return {
      getSystemInfoSync: ty.getSystemInfoSync(),
      getAccountInfoSync: ty.getAccountInfoSync(),
      getEnterOptionsSync: ty.getEnterOptionsSync(),
      getLaunchOptionsSync: ty.getLaunchOptionsSync(),
    };
  },
});
```

**Page template** (`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. Message types (optional)

When you need the same type string on both sides (e.g. custom handlers), use the exported constants:

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

## Main API usage

### WebView side (game code)

| API | Usage |
|-----|--------|
| **`ty`** | Proxy to call mini-program APIs. Use only inside `onMiniEnvReady` or after. Same options as mini-program API (e.g. `success`, `fail`, `complete`). |
| **`onMiniEnvReady(cb)`** | Register a callback when the mini-program env is ready. Call `game.start()` or similar inside. In non–mini-program env the callback runs immediately. |
| **`I18n`** | `I18n.t(key, { defaultValue?: string })` returns the localized string. `I18n.language` and `I18n.locales` are set by `tyReady`. |
| **`miniProgram.postMessage(data)`** | Send a message to the mini-program. `data` must be a plain object with a `type` field. Used internally for `ty.*` and logs. |
| **`onMiniMessage(cb)`** | Register a callback for messages that are not `tyReady` or `tyResponse` (e.g. custom app messages). |
| **`setAtopMockData(data)`** | In non–mini-program env, set mock data for `ty.apiRequestByAtop` (keyed by API name). |
| **`setLocales(locales)`** | In non–mini-program env, set `I18n.language` and `I18n.locales` manually. |
| **`inTyMiniApp`** / **`inTyDevTool`** / **`inTyMiniWebview`** | Booleans: whether the code runs inside the mini-program, devtools, or mini-program WebView. |

**Example (WebView):**

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

onMiniEnvReady(() => {
  // ty is safe to use here
  ty.getSystemInfo({ success: (res) => console.log(res) });
});
I18n.t('start_btn'); // key from tyReady locales
```

### Mini-program side (page code)

| API | Usage |
|-----|--------|
| **`setWebViewContext(context)`** | Call once in `onReady` with `ty.createWebviewContext('webviewContainer')` so the SDK can send messages to the WebView. |
| **`onMessageFromWebView(event)`** | Bind to the `<web-view>` `message` event. The SDK handles `tyLog` (print) and `tyCall` (invoke mini-program API and reply with `tyResponse`). |
| **`postMessageToWebView(data)`** | Send a message to the WebView. Use `type: MESSAGE_TYPE_TY_READY` and `data: { syncData, language, locales }` when the WebView loads so the game gets env and i18n. |

**Example (mini-program):**

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

Page({
  onReady() {
    setWebViewContext(ty.createWebviewContext('webviewContainer'));
  },
  onMessageFromWebView, // bind to 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 },
    });
  },
});
```

### Shared (constants)

| Constant | Value | Use when |
|----------|--------|----------|
| `MESSAGE_TYPE_TY_READY` | `'tyReady'` | Sending env + i18n from mini-program to WebView. |
| `MESSAGE_TYPE_TY_RESPONSE` | `'tyResponse'` | Mini-program replying to a `ty.*` call (handled by SDK). |
| `MESSAGE_TYPE_TY_LOG` | `'tyLog'` | WebView log to mini-program (handled by SDK). |
| `MESSAGE_TYPE_TY_CALL` | `'tyCall'` | WebView calling mini-program API (handled by SDK). |

Use these constants when you need the same type string on both sides (e.g. custom handlers or checks).

## API overview

| Side        | Exports |
|------------|--------|
| WebView    | `ty`, `I18n`, `miniProgram`, `onMiniEnvReady`, `onMiniMessage`, `setAtopMockData`, `setLocales`, `inTyMiniApp`, `inTyDevTool`, `inTyMiniWebview` |
| Mini-program | `setWebViewContext`, `onMessageFromWebView`, `postMessageToWebView` |
| Shared     | `MESSAGE_TYPE_TY_READY`, `MESSAGE_TYPE_TY_RESPONSE`, `MESSAGE_TYPE_TY_LOG`, `MESSAGE_TYPE_TY_CALL` |