11 KiB
11 KiB
rnwalletman 接入文档
1. Android 宿主接入
1.1 新建 Service 子类
宿主 App 必须继承 BaseProxyService 并实现 registerWallet:
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
<!-- 前台服务 -->
<service
android:name=".AppProxyService"
android:exported="false"
android:foregroundServiceType="dataSync"
android:stopWithTask="false" />
<!-- 来电 Activity(仅 fullscreen 模式需要) -->
<activity
android:name=".IncomingCallActivity"
android:exported="false"
android:theme="@style/AppCallTheme"
android:showWhenLocked="true"
android:turnScreenOn="true"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:screenOrientation="portrait" />
<!-- 来电通知按钮接收器 -->
<receiver
android:name=".CallActionReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.yourapp.CALL_ACCEPT" />
<action android:name="com.yourapp.CALL_DECLINE" />
</intent-filter>
</receiver>
<!-- FCM 消息接收 -->
<service
android:name="com.rnwalletman.ProxyFcmService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- 权限 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
1.3 Application.onCreate 注册
@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 |
示例:
{
"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 的任意字段 |
示例:
{
"cmd": "incoming_call",
"params": "{\"caller\":\"客服\",\"ringCount\":2,\"ringDuration\":30,\"mode\":\"fullscreen\",\"extra\":{\"orderId\":\"ORD001\"}}"
}
3. JS/TS 层 API
3.1 启动服务
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 用)
await proxyBackgroundService.syncRebindConfig({
baseUrl: 'https://your-server',
userId: 12345,
userToken: 'jwt-token',
onRebound: () => { /* 刷新 UI */ },
});
3.3 来电结果回调
接听 / 拒接 / 超时 均通过此回调返回,与通知点击回调相互独立:
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 通知点击回调
proxyBackgroundService.setNotificationTapHandler((payload) => {
const { cmd, params } = payload;
// cmd: 'rebind_wallet' 等
});
3.5 其他方法
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
{ "clientId": "device-id", "fcmToken": "firebase-token" }
POST /fcm/send-wake
{ "clientId": "device-id" }
POST /fcm/send-rebind
{
"clientId": "device-id", // 可选,不传则按 walletId 查找绑定设备
"walletId": "abc123", // 必填
"walletType": "phonepe", // 可选,不传则自动查找
"phone": "9876543210", // 可选
"retry": false, // 是否重试(phonepe 强制 true)
"notify": false // true=弹系统通知,false=静默重绑
}
POST /fcm/send-call
{
"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)
客户端 → 服务端
所有消息格式:
{ "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 示例
{
"type": "register",
"messageId": "init-001",
"clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"data": {
"userId": 12345,
"fcmToken": "firebase-device-token"
}
}