# rnwalletman 接入文档 ## 1. Android 宿主接入 ### 1.1 新建 Service 子类 宿主 App 必须继承 `BaseProxyService` 并实现 `registerWallet`: ```java package com.yourapp; import android.content.Context; import android.content.SharedPreferences; import com.rnwalletman.BaseProxyService; import org.json.JSONObject; import okhttp3.*; import java.io.IOException; import java.util.concurrent.TimeUnit; public class AppProxyService extends BaseProxyService { private static final OkHttpClient HTTP = new OkHttpClient.Builder() .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); @Override protected void registerWallet(Context ctx, String walletId, String walletType, String phone, JSONObject params) throws Exception { SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String baseUrl = prefs.getString("rebind_baseUrl", null); // JS 侧通过 syncRebindConfig 写入 int userId = prefs.getInt("rebind_userId", 0); JSONObject body = new JSONObject(); body.put("walletType", walletType); body.put("params", params); Request req = new Request.Builder() .url(baseUrl + "/register") .header("X-User-ID", String.valueOf(userId)) .header("Content-Type", "application/json") .post(RequestBody.create(body.toString(), MediaType.parse("application/json"))) .build(); try (Response resp = HTTP.newCall(req).execute()) { String body2 = resp.body() != null ? resp.body().string() : ""; JSONObject json = new JSONObject(body2); if (!json.optBoolean("success", false)) { throw new IOException(json.optString("message", "register failed")); } } } } ``` ### 1.2 AndroidManifest.xml ```xml ``` ### 1.3 Application.onCreate 注册 ```java @Override public void onCreate() { super.onCreate(); // 指定宿主 Service 类 BaseProxyService.setServiceClass(AppProxyService.class); // 指定来电全屏 Activity(有来电功能时) BaseProxyService.setCallActivityClass(IncomingCallActivity.class); } ``` --- ## 2. FCM 消息格式 服务端发 **data-only** FCM(无 `notification` 块),data 字段: | 字段 | 类型 | 说明 | |------|------|------| | `cmd` | string | 命令类型(见下表) | | `msg` | string | 可选,通知文案(非空则弹系统通知) | | `params` | string (JSON) | 命令参数 | ### 命令列表 #### `wake_proxy` 唤醒后台 WebSocket 服务。`params` 为空 `{}`。 --- #### `rebind_wallet` 触发钱包 token 重绑(AIDL 拉取 → `registerWallet` 回调)。 `params` 字段: | 字段 | 类型 | 必填 | 默认 | 说明 | |------|------|------|------|------| | `walletId` | string | ✅ | — | 钱包 ID | | `walletType` | string | ✅ | — | `phonepe` / `paytm` / `mobikwik` 等 | | `phone` | string | — | `""` | 绑定手机号 | | `retry` | bool | — | `false` | 是否允许重试 | | `tryGtkAnyway` | bool | — | `false` | PhonePe 强制拉 GTK | 示例: ```json { "cmd": "rebind_wallet", "params": "{\"walletId\":\"abc123\",\"walletType\":\"phonepe\",\"phone\":\"9876543210\",\"retry\":true}" } ``` --- #### `incoming_call` 触发来电通知 / 全屏来电界面。 `params` 字段: | 字段 | 类型 | 必填 | 默认 | 说明 | |------|------|------|------|------| | `caller` | string | — | `"Unknown"` | 来电显示名称 | | `ringCount` | int | — | `1` | 呼叫次数(≥1) | | `ringDuration` | int | — | `30` | 每次响铃时长(秒,≥5) | | `mode` | string | — | `"fullscreen"` | `"fullscreen"` 或 `"notification"` | | `extra` | object | — | — | 透传给 JS 的任意字段 | 示例: ```json { "cmd": "incoming_call", "params": "{\"caller\":\"客服\",\"ringCount\":2,\"ringDuration\":30,\"mode\":\"fullscreen\",\"extra\":{\"orderId\":\"ORD001\"}}" } ``` --- ## 3. JS/TS 层 API ### 3.1 启动服务 ```typescript import { proxyBackgroundService } from 'rnwalletman'; await proxyBackgroundService.start({ wsUrl: 'wss://your-server/ws', clientId: 'device-unique-id', userId: 12345, heartbeatInterval: 20000, // ms,默认 20000 reconnectInterval: 5000, // ms onConnected: () => {}, onDisconnected: () => {}, onError: (err) => {}, onCustomCommand: (msg) => {}, // 可选:上报 FCM token 到服务端 registerFcmToken: async (clientId, fcmToken) => { await api.post('/fcm/register', { clientId, fcmToken }); }, }); ``` ### 3.2 配置重绑(native 侧 AIDL 用) ```typescript await proxyBackgroundService.syncRebindConfig({ baseUrl: 'https://your-server', userId: 12345, userToken: 'jwt-token', onRebound: () => { /* 刷新 UI */ }, }); ``` ### 3.3 来电结果回调 接听 / 拒接 / 超时 均通过此回调返回,**与通知点击回调相互独立**: ```typescript proxyBackgroundService.setCallResultHandler((payload) => { const { action, caller, mode, ringDuration, ringCount, extra } = payload; // action: 'accept' | 'decline' if (action === 'accept') { // 进入游戏 / 业务逻辑 } }); ``` payload 类型: | 字段 | 类型 | 说明 | |------|------|------| | `action` | `'accept' \| 'decline'` | 操作结果 | | `caller` | string | 来电显示名 | | `mode` | string | `fullscreen` / `notification` | | `ringDuration` | number | 响铃时长(秒) | | `ringCount` | number | 呼叫次数 | | `extra` | object? | 服务端透传的额外参数 | ### 3.4 FCM 通知点击回调 ```typescript proxyBackgroundService.setNotificationTapHandler((payload) => { const { cmd, params } = payload; // cmd: 'rebind_wallet' 等 }); ``` ### 3.5 其他方法 ```typescript await proxyBackgroundService.stop(); // 引导用户忽略电池优化(Android) ProxyBackgroundService.openBatteryOptimizationSettings(); ``` --- ## 4. JS 事件(DeviceEventEmitter) | 事件名 | payload | 说明 | |--------|---------|------| | `ProxyServiceConnected` | — | WS 已连接 | | `ProxyServiceDisconnected` | — | WS 断开 | | `ProxyServiceError` | `{ data: string }` | 错误信息 | | `ProxyServiceCustomCommand` | `{ data: string (JSON) }` | 服务端自定义消息 | | `ProxyServiceTick` | — | 每 ~60s 一次心跳 tick | | `ProxyServiceRebound` | `{ data: walletId }` | 重绑成功 | | `ProxyServiceNotificationTap` | `{ data: JSON }` | FCM 通知被点击 | | `ProxyServiceFcmToken` | `{ data: token }` | FCM token 更新 | | `ProxyServiceCallResult` | `{ data: JSON }` | 来电结果(accept/decline) | --- ## 5. 服务端发来电接口(phecda_hall gateway) ``` POST http://gateway:12881/fcm/send-call Content-Type: application/json { "clientId": "device-client-id", // 或 "userId": 12345 "caller": "客服", "ringCount": 1, "ringDuration": 30, "mode": "fullscreen", // "fullscreen" | "notification" "extra": { "orderId": "ORD001" } // 可选,透传到 JS } ``` FCM token 注册: ``` POST http://gateway:12881/fcm/register Content-Type: application/json { "clientId": "device-client-id", "fcmToken": "firebase-device-token" } ``` > **Firebase 服务账号**:放到 `config/fcm-service-account.json`,或设环境变量 `FCM_SERVICE_ACCOUNT=/path/to/file.json` --- ## 6. walletman 服务端 HTTP 接口(rnpay,端口 16000) ### FCM 管理 | 方法 | 路径 | 说明 | |------|------|------| | POST | `/fcm/register` | 注册设备 FCM token | | POST | `/fcm/send-wake` | 发送唤醒指令 | | POST | `/fcm/send-rebind` | 发送钱包重绑指令 | | POST | `/fcm/send-call` | 发送来电推送 | | GET | `/fcm/clients` | 列出所有已注册设备 | #### `POST /fcm/register` ```json { "clientId": "device-id", "fcmToken": "firebase-token" } ``` #### `POST /fcm/send-wake` ```json { "clientId": "device-id" } ``` #### `POST /fcm/send-rebind` ```json { "clientId": "device-id", // 可选,不传则按 walletId 查找绑定设备 "walletId": "abc123", // 必填 "walletType": "phonepe", // 可选,不传则自动查找 "phone": "9876543210", // 可选 "retry": false, // 是否重试(phonepe 强制 true) "notify": false // true=弹系统通知,false=静默重绑 } ``` #### `POST /fcm/send-call` ```json { "clientId": "device-id", // 必填 "caller": "客服", // 默认 "Customer Service" "ringCount": 1, // 默认 1 "ringDuration": 30, // 秒,默认 30 "mode": "fullscreen", // "fullscreen" | "notification" "extra": { "key": "val" } // 可选,透传到 JS } ``` --- ## 7. WebSocket 协议(`wss://server/ws`) ### 客户端 → 服务端 所有消息格式: ```json { "type": "<类型>", "messageId": "<唯一ID>", "clientId": "<设备ID>", "data": {} } ``` | type | data 字段 | 说明 | |------|-----------|------| | `register` | `{ userId, fcmToken }` | 连接后首条消息,注册设备身份 | | `ping` | — | 心跳,服务端回 `pong` | | `echo` | 任意 | 测试回显 | | `proxyReady` | — | 通知服务端 TCP 代理隧道就绪 | | `proxyData` | `{ data: base64 }` | 转发代理数据(按 `messageId` 路由) | | `proxyClose` | — | 通知代理数据发送完毕 | ### 服务端 → 客户端 | type | data 字段 | 说明 | |------|-----------|------| | `response` | `{ success, message, data }` | 通用应答 | | `echo` | 原样 | 回显 | | `proxyRequest` | `{ host, port }` | 服务端请求建立 TCP 代理隧道 | | `proxyData` | `{ data: base64 }` | 服务端 → 客户端代理数据 | | `proxyClose` | — | 服务端通知关闭代理隧道 | ### register 示例 ```json { "type": "register", "messageId": "init-001", "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "data": { "userId": 12345, "fcmToken": "firebase-device-token" } } ```