From 4a18d06c63c62ee01ef2a25a3aeb5c1c0369a2ac Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Tue, 16 Jun 2026 12:14:54 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20CLAUDE.md=20+=20?= =?UTF-8?q?=E5=90=84=20module=20README=20+=20sdk-webview=20JSBridge=20?= =?UTF-8?q?=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent 7 + Agent 8: - CLAUDE.md 项目上下文 - 各 module README (core/im/push/update/webview/log) - XWebViewStandardHandlers.kt 补全标准 JSBridge handler --- CLAUDE.md | 36 +++--- sdk-core/README.md | 93 +++++++++++++++ sdk-im/README.md | 56 +++++++++ sdk-license/README.md | 107 +++++------------- sdk-log/README.md | 55 +++++++++ sdk-log/build.gradle.kts | 3 +- sdk-push/README.md | 34 ++++++ sdk-update/README.md | 49 ++++++++ sdk-webview/README.md | 69 +++++++++++ .../sdk/webview/XWebViewStandardHandlers.kt | 82 ++++++++++++++ 10 files changed, 488 insertions(+), 96 deletions(-) create mode 100644 sdk-core/README.md create mode 100644 sdk-im/README.md create mode 100644 sdk-log/README.md create mode 100644 sdk-push/README.md create mode 100644 sdk-update/README.md create mode 100644 sdk-webview/README.md create mode 100644 sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt diff --git a/CLAUDE.md b/CLAUDE.md index 3ac94ca..9c73399 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,10 +2,12 @@ ## 项目定位 -XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、**日志追踪**。 +XuqmGroup Android SDK,Gradle multi-module 项目。为集成宿主 App(如 YwxMobileApp)提供:初始化、用户认证、OTA 更新、WebView、IM、推送、证书、日志追踪。 +- Git 远端:`https://xuqinmin.com/xuqmGroup/XuqmGroup-AndroidSDK.git` - Nexus Maven 发布:`https://nexus.xuqinmin.com/repository/android-hosted/` - groupId:`com.xuqm` +- 技术栈:Kotlin 2.3.10,AGP 9.1.0,minSdk 24,compileSdk 36,Java 21 ## 模块结构 @@ -59,13 +61,6 @@ XuqmSDK.logEnabled // 是否开启日志 XuqmSDK.appKey // 当前 appKey ``` -## 向下兼容约束 - -- `XuqmSDK.initialize()` 现有参数不得删除或修改类型 -- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除 -- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`) -- 旧服务端不返回新字段时,客户端使用合理默认值 - ## 日志 SDK(sdk-log) ```kotlin @@ -86,16 +81,18 @@ XLog.defineFunnel("checkout", listOf("cart_view", "checkout_start", "payment_don `logApiUrl` 由 SDK 在 init 后从平台配置自动获取,无需 App 传入。 +## 向下兼容约束 + +- `XuqmSDK.initialize()` 现有参数不得删除或修改类型 +- `XuqmSDK.setUserInfo()` 现有 `XuqmUserInfo` 字段不得删除 +- `SdkPlatformConfig` 新增字段一律为可选(`val xxx: Type? = null`) +- 旧服务端不返回新字段时,客户端使用合理默认值 + ## 集成验证项目 YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp` 使用 Nexus Maven 引入 SDK,开发阶段可改为 `project()` 本地引用。 -## 任务文档 - -- Agent 7 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent7-android-sdk.md` -- Agent 8 任务:`/Users/xuqinmin/Projects/XuqmProjects/YiwangxinApp4/docs/agent-tasks/agent8-docs.md` - ## 常用命令 ```bash @@ -103,4 +100,17 @@ YwxMobileApp:`/Users/xuqinmin/Projects/TrustProjects/Pad/YwxMobileApp` ./gradlew :sdk-core:assembleDebug ./gradlew :sample-app:installDebug ./gradlew publish # 发布所有模块到 Nexus + +# 发布单个模块 +./gradlew :sdk-core:publish -PSDK_CORE_VERSION=1.0.0 ``` + +## 发版配置 + +在 `gradle.properties` 中配置: +```properties +NEXUS_USER=your_username +NEXUS_PASSWORD=your_password +``` + +发布至 `https://nexus.xuqinmin.com/repository/android-hosted/`,版本号在各模块 `build.gradle.kts` 中维护。 diff --git a/sdk-core/README.md b/sdk-core/README.md new file mode 100644 index 0000000..498344e --- /dev/null +++ b/sdk-core/README.md @@ -0,0 +1,93 @@ +# sdk-core + +XuqmGroup Android SDK 核心模块。提供 SDK 初始化、HTTP 请求、Token 管理、文件操作、通用工具。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-core:VERSION") +``` + +## 初始化 + +### 方式 A — ContentProvider 自动初始化(推荐) + +将 `config.xuqm` 放入 `src/main/assets/xuqm/`,SDK 在 App 启动时自动读取并初始化。 + +```kotlin +// XuqmInitializerProvider 自动触发,无需代码 +``` + +### 方式 B — 手动初始化 + +```kotlin +// Application.onCreate() 中: +XuqmSDK.initialize(context, appKey = "xxx") +XuqmSDK.initialize(context, appKey = "xxx", platformUrl = "https://xxx") +``` + +## API + +### XuqmSDK(全局单例) + +| API | 说明 | +|-----|------| +| `XuqmSDK.initialize(context, appKey, platformUrl?, logLevel?)` | 手动初始化 | +| `XuqmSDK.awaitInitialization()` | 等待平台配置拉取(coroutine) | +| `XuqmSDK.isInitialized()` | 检查是否已初始化 | +| `XuqmSDK.setUserInfo(info)` | 设置用户信息,触发所有子模块登录 | +| `XuqmSDK.setUserInfo(null)` | 登出,触发全局登出 | +| `XuqmSDK.getUserId()` | 获取当前 userId | +| `XuqmSDK.getUserInfo()` | 获取当前用户信息 | +| `XuqmSDK.appKey` | 当前 appKey | +| `XuqmSDK.platformConfig` | 平台配置(init 后可用) | +| `XuqmSDK.logApiUrl` | 日志服务地址(从平台配置获取) | +| `XuqmSDK.logEnabled` | 是否启用日志上报 | +| `XuqmSDK.useLocalServiceEndpoints(ip)` | 切换本地联调环境 | +| `XuqmSDK.tokenStore` | Token 存储(EncryptedSharedPreferences) | + +### XuqmUserInfo + +```kotlin +data class XuqmUserInfo( + val userId: String, // 必填 + val userSig: String? = null, // IM 登录凭证(可选) + val name: String? = null, + val phone: String? = null, + val avatar: String? = null, +) +``` + +### TokenStore + +基于 `EncryptedSharedPreferences` 持久化存储。 + +```kotlin +XuqmSDK.tokenStore.saveToken("eyJ...") +val token = XuqmSDK.tokenStore.getToken() +XuqmSDK.tokenStore.clear() +``` + +### ApiClient + +基于 OkHttp 5 + Retrofit 3,自动附加 Bearer Token。 + +```kotlin +val service = RetrofitFactory.create(MyApiService::class.java) +``` + +### FileSDK + +文件上传、下载、打开的统一入口(`com.xuqm.sdk.file`)。 + +```kotlin +// 上传 +val result = FileSDK.upload(context, uri, onProgress = { /* 0-100 */ }) +val result = FileSDK.uploadBytes(fileName, mimeType, bytes, onProgress) + +// 下载 +val file = FileSDK.download(context, url, fileName, destination, notificationTitle, onProgress) + +// 打开 +FileSDK.openFile(context, file) +``` diff --git a/sdk-im/README.md b/sdk-im/README.md new file mode 100644 index 0000000..e16e593 --- /dev/null +++ b/sdk-im/README.md @@ -0,0 +1,56 @@ +# sdk-im + +XuqmGroup Android SDK IM 模块。提供 WebSocket 实时通信、消息收发、群组管理。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-im:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 +``` + +## 使用 + +**无需手动初始化。** `XuqmSDK.login(userId, userSig)` 成功后自动完成 IM 登录。 + +```kotlin +// ImClient 直接使用 +val imClient = ImClient() +imClient.listener = object : ImEventListener { + override fun onConnected() { } + override fun onMessage(msg: ImMessage) { } + override fun onRevoke(msgId: String, operatorId: String) { } +} + +imClient.connect() +imClient.send(SendMessageParams( + toId = "user_002", + chatType = ChatType.SINGLE, + msgType = MsgType.TEXT, + content = "Hello!" +)) +``` + +## API + +### ImClient + +| API | 说明 | +|-----|------| +| `imClient.connect()` | 连接 WebSocket | +| `imClient.disconnect()` | 断开连接 | +| `imClient.send(params)` | 发送消息 | +| `imClient.revoke(msgId)` | 撤回消息 | +| `imClient.listener` | 事件监听器 | + +### 消息类型 + +`TEXT` / `IMAGE` / `VIDEO` / `AUDIO` / `FILE` / `CUSTOM` / `LOCATION` / `NOTIFY` / `RICH_TEXT` / `CALL_AUDIO` / `CALL_VIDEO` / `FORWARD` / `QUOTE` / `MERGE` + +### 自动重连 + +断线后指数退避重连,初始间隔 3 秒,最大间隔 30 秒。调用 `disconnect()` 后停止。 + +### 群聊 + +支持 `@userId` 提及,写入 `mentionedUserIds`。群组管理 API 包含:创建、解散、禁言、设置管理员、转让群主。 diff --git a/sdk-license/README.md b/sdk-license/README.md index 308e7af..55e1e2e 100644 --- a/sdk-license/README.md +++ b/sdk-license/README.md @@ -1,94 +1,39 @@ -# Xuqm License SDK +# sdk-license -Android SDK for device license registration and verification. +XuqmGroup Android SDK 证书授权模块。提供设备 License 验证能力。 -## Features - -- **Stable Device ID**: Uses `ANDROID_ID` (stable per app signing key + device + user), with UUID fallback -- **Token Caching**: Caches valid status for configurable window (default 10 min) to reduce network calls -- **Auto Recovery**: Automatically re-registers when token is invalid or revoked -- **Offline Support**: Returns cached OK status when network is unavailable -- **Encrypted License File**: Reads `assets/xuqm/license.xuqm`; the file does not expose appKey or server URL as readable text -- **AppKey Change Detection**: Automatically clears old data when decrypted appKey changes - -## Integration +## 依赖 ```kotlin -dependencies { - implementation("com.xuqm:sdk-license:0.1.0-SNAPSHOT") -} +implementation("com.xuqm:sdk-license:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 ``` -## Usage +## 使用 -### License File - -Download the encrypted License file from the tenant platform and place it at: - -```text -app/src/main/assets/xuqm/license.xuqm -``` - -The SDK also accepts a tenant-platform downloaded file such as -`app/src/main/assets/xuqm/MyApp.xuqmlicense`. - -No explicit initialization code is required. - -### Check License +**无需独立初始化。** 内部自动等待 `XuqmSDK` 就绪。 ```kotlin -LicenseSDK.checkLicense { isValid -> - if (isValid) { - // allow app usage - } else { - // block app usage or show warning +// 验证 License +LicenseSDK.checkLicense(context, callback = object : LicenseCallback { + override fun onResult(result: LicenseResult) { + when (result) { + is LicenseResult.Success -> { /* 验证通过 */ } + is LicenseResult.Denied -> { /* 验证失败 */ } + is LicenseResult.Error -> { /* 错误 */ } + } } -} +}) + +// 获取状态 +val status = LicenseSDK.getStatus() ``` -Optional user metadata can be uploaded during the same check. All fields are optional. +## API -```kotlin -LicenseSDK.checkLicense( - userInfo = LicenseUserInfo( - userId = "u_10001", - name = "张三", - email = "zhangsan@example.com", - phone = "13800000000", - ), -) { isValid -> - // allow or block app usage -} -``` - -### Get Status (no network) - -```kotlin -val status = LicenseSDK.getStatus() // OK, DENIED, or UNKNOWN -``` - -### Re-register (force) - -```kotlin -lifecycleScope.launch { - val result = LicenseSDK.reRegister() -} -``` - -### Get Device ID - -```kotlin -val deviceId = LicenseSDK.getDeviceId() -``` - -## Configuration - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `appKey` | from encrypted file | AppKey from tenant platform | -| `deviceName` | `${MANUFACTURER} ${MODEL}` | Device display name | -| `baseUrl` | `https://auth.dev.xuqinmin.com/` | License server base URL | - -## Data Storage - -All data (device ID, token, status) is stored in `EncryptedSharedPreferences` with AES-256 encryption. +| API | 说明 | +|-----|------| +| `LicenseSDK.checkLicense(context, callback)` | 验证设备 License | +| `LicenseSDK.getStatus()` | 获取当前 License 状态 | +| `LicenseSDK.getDeviceId()` | 获取设备 ID | +| `LicenseSDK.clear()` | 清除本地 License 缓存 | diff --git a/sdk-log/README.md b/sdk-log/README.md new file mode 100644 index 0000000..cc735dc --- /dev/null +++ b/sdk-log/README.md @@ -0,0 +1,55 @@ +# sdk-log + +XuqmGroup Android SDK 日志模块。提供日志采集、Crash 捕获、漏斗分析能力。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-log:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 +``` + +## 快速开始 + +```kotlin +// Application.onCreate() 中(XuqmSDK.initialize 之后): +XLog.setLogLevel(LogLevel.INFO) +XLog.setEnvironment("production") +XLog.startCrashCapture() + +// 业务代码中 +XLog.event("page_view", mapOf("page" to "home")) +XLog.captureError(exception) +``` + +## API + +### XLog 对象 + +| API | 说明 | +|-----|------| +| `XLog.setLogLevel(level)` | 设置日志级别(`DEBUG` / `INFO` / `WARN` / `ERROR`) | +| `XLog.setEnvironment(env)` | 设置环境标签 | +| `XLog.startCrashCapture()` | 开启全局 UncaughtExceptionHandler | +| `XLog.event(name, properties?)` | 记录自定义事件 | +| `XLog.captureError(error, metadata?)` | 上报异常 | +| `XLog.warn(message, metadata?)` | 记录警告 | +| `XLog.info(message, metadata?)` | 记录信息 | +| `XLog.defineFunnel(id, steps)` | 定义漏斗 | + +### LogLevel + +```kotlin +enum class LogLevel { DEBUG, INFO, WARN, ERROR, NONE } +``` + +### 工作原理 + +- **LogQueue**:事件进入内存队列,按批次异步上传到 `logApiUrl` +- **CrashCapture**:注册 `Thread.UncaughtExceptionHandler`,捕获 Native Crash 并写入文件,下次启动时上传 +- **Fingerprint**:为错误生成指纹(基于 message + stack),服务端去重聚合 +- **FunnelTracker**:客户端维护漏斗进度,服务端跨 session 聚合 + +### 配置 + +`logApiUrl` 和 `logEnabled` 由 `sdk-core` 在 init 后从平台配置自动获取,无需 App 传入。 diff --git a/sdk-log/build.gradle.kts b/sdk-log/build.gradle.kts index 558243a..6a8635d 100644 --- a/sdk-log/build.gradle.kts +++ b/sdk-log/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("com.android.library") - id("org.jetbrains.kotlin.android") id("maven-publish") } @@ -8,7 +7,7 @@ group = rootProject.group android { namespace = "com.xuqm.sdk.log" - compileSdk = 35 + compileSdk = 36 defaultConfig { minSdk = 24 } publishing { singleVariant("release") { withSourcesJar() } } diff --git a/sdk-push/README.md b/sdk-push/README.md new file mode 100644 index 0000000..6cefec0 --- /dev/null +++ b/sdk-push/README.md @@ -0,0 +1,34 @@ +# sdk-push + +XuqmGroup Android SDK 推送模块。支持多厂商推送(华为/小米/OPPO/vivo/荣耀/FCM/APNs)。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-push:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 +``` + +## 使用 + +**无需手动初始化。** `XuqmSDK.login()` 成功后自动完成推送 token 注册。`logout()` 自动解绑。 + +```kotlin +// 开启/关闭接收推送 +PushSDK.setReceivePush(context, enabled = false) +PushSDK.setReceivePush(context, enabled = true) +``` + +## 支持厂商 + +HUAWEI / XIAOMI / OPPO / VIVO / HONOR / FCM / APNS + +## 与 IM 联动 + +IM 和推送服务均已开启时,IM 离线消息自动触发推送通知。 + +## 注意事项 + +- 需要各厂商 SDK 配置(App ID / App Secret) +- Firebase Messaging 需提供 `google-services.json` +- FCM token 通过 `FirebaseMessagingService.onNewToken()` 自动接收 diff --git a/sdk-update/README.md b/sdk-update/README.md new file mode 100644 index 0000000..9c2fccd --- /dev/null +++ b/sdk-update/README.md @@ -0,0 +1,49 @@ +# sdk-update + +XuqmGroup Android SDK 更新模块。提供 App 版本检查和 APK 下载安装能力。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-update:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 +``` + +## 快速开始 + +```kotlin +val result = UpdateSDK.checkUpdate( + appKey = XuqmSDK.appKey, + platform = Platform.ANDROID, +) + +if (result.needsUpdate) { + UpdateSDK.downloadAndInstall(context, result.downloadUrl) +} +``` + +## API + +| API | 说明 | +|-----|------| +| `UpdateSDK.checkUpdate(appKey, platform)` | 检查 App 更新 | +| `UpdateSDK.downloadAndInstall(context, url)` | 下载 APK 并调起系统安装器 | + +### UpdateResult + +```kotlin +data class UpdateResult( + val needsUpdate: Boolean, + val versionName: String?, + val versionCode: Int?, + val downloadUrl: String?, + val changeLog: String?, + val forceUpdate: Boolean?, +) +``` + +## 工作原理 + +- `downloadAndInstall` 将 APK 下载到 `getExternalFilesDir(null)`,通过 `FileProvider` 触发系统安装 +- AndroidManifest 中已配置 `@xml/file_paths`(`external-files-path`) +- 支持 WebSocket 实时推送更新通知 diff --git a/sdk-webview/README.md b/sdk-webview/README.md new file mode 100644 index 0000000..e67536c --- /dev/null +++ b/sdk-webview/README.md @@ -0,0 +1,69 @@ +# sdk-webview + +XuqmGroup Android SDK WebView 模块。提供嵌入式 WebView 组件和独立页面,内置 JSBridge、文件选择、下载拦截。 + +## 依赖 + +```kotlin +implementation("com.xuqm:sdk-webview:VERSION") +implementation("com.xuqm:sdk-core:VERSION") // 必须 +``` + +## 使用 + +### 嵌入式组件 + +```kotlin +import com.xuqm.sdk.webview.XWebViewView +import com.xuqm.sdk.webview.XWebViewConfig + +XWebViewView( + config = XWebViewConfig( + url = "https://example.com", + title = "嵌入式网页", + ), +) +``` + +### 独立页面 + +```kotlin +import com.xuqm.sdk.webview.openXWebView +import com.xuqm.sdk.webview.XWebViewConfig + +openXWebView(XWebViewConfig(url = "https://example.com", title = "独立页面")) +// navigate("xwebview") 后使用 XWebViewScreen() +``` + +## XWebViewConfig 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `url` | String | `""` | 初始加载地址 | +| `title` | String | `""` | 页面标题 | +| `hideToolbar` | Boolean | `false` | 隐藏顶栏 | +| `hideStatusBar` | Boolean | `false` | 隐藏状态栏 | +| `userAgent` | String? | `null` | 自定义 User-Agent | +| `injectedJavaScript` | String? | `null` | 注入的 JS | +| `jsBridgeName` | String | `"XWebViewBridge"` | JS 桥接对象名 | +| `debugEnabled` | Boolean | `false` | 开启远程调试 | +| `downloadDestination` | FileDownloadDestination | `Sandbox` | 下载存储目标 | +| `downloadNotificationTitle` | String? | `null` | 通知栏下载进度 | +| `onMessage` | (String) -> Unit? | `null` | H5 消息回调 | + +## JSBridge 通信 + +```javascript +// H5 → Native +window.XWebViewBridge.postMessage(JSON.stringify({ type: 'login', token: '...' })) + +// 监听下载进度 +window.addEventListener('__xwvDownloadProgress', (e) => { ... }) +window.addEventListener('__xwvDownloadDone', (e) => { ... }) +``` + +## 文件选择与拍照 + +- `` + `accept="image/*"` + `capture` → 系统相机 +- `` + `.docx/.xlsx` → 文件选择器 +- `getUserMedia()` WebRTC → 自动请求 CAMERA 权限 diff --git a/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt new file mode 100644 index 0000000..e505fb0 --- /dev/null +++ b/sdk-webview/src/main/java/com/xuqm/sdk/webview/XWebViewStandardHandlers.kt @@ -0,0 +1,82 @@ +package com.xuqm.sdk.webview + +import android.os.Build +import android.widget.Toast +import com.xuqm.sdk.XuqmSDK +import org.json.JSONObject + +/** + * Standard JSBridge handlers for xuqm.* messages. + * These provide default implementations for hosts that don't use a custom protocol. + * + * Usage: register in XWebViewView's xwvMessageHandler or onMessage callback. + */ +object XWebViewStandardHandlers { + + fun handle(message: String, webView: XWebViewController?): String { + val json = runCatching { JSONObject(message) }.getOrNull() ?: return errorResponse("invalid JSON") + val name = json.optString("name", "") + return when (name) { + "xuqm.getDeviceInfo" -> handleGetDeviceInfo() + "xuqm.getToken" -> handleGetToken() + "xuqm.getUserInfo" -> handleGetUserInfo() + "xuqm.closeWebView" -> handleCloseWebView(webView) + "xuqm.showToast" -> handleShowToast(json) + else -> errorResponse("unknown handler: $name") + } + } + + private fun handleGetDeviceInfo(): String { + val data = JSONObject().apply { + put("platform", "android") + put("osVersion", Build.VERSION.RELEASE) + put("sdkVersion", Build.VERSION.SDK_INT) + put("manufacturer", Build.MANUFACTURER) + put("model", Build.MODEL) + put("brand", Build.BRAND) + } + return successResponse(data) + } + + private fun handleGetToken(): String { + val token = XuqmSDK.currentLoginSession?.userSig ?: "" + val data = JSONObject().apply { put("token", token) } + return successResponse(data) + } + + private fun handleGetUserInfo(): String { + val userInfo = XuqmSDK.getUserInfo() + if (userInfo == null) return errorResponse("not logged in") + val data = JSONObject().apply { + put("userId", userInfo.userId ?: "") + put("nickname", userInfo.nickname ?: "") + put("avatar", userInfo.avatar ?: "") + put("phone", userInfo.phone ?: "") + } + return successResponse(data) + } + + private fun handleCloseWebView(webView: XWebViewController?): String { + webView?.let { wv -> + if (wv.canGoBack()) wv.goBack() else wv.loadUrl("about:blank") + } + return successResponse(JSONObject()) + } + + private fun handleShowToast(json: JSONObject): String { + val message = json.optString("message", "") + if (message.isNotBlank()) { + val ctx = XuqmSDK.appContext + android.os.Handler(android.os.Looper.getMainLooper()).post { + Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() + } + } + return successResponse(JSONObject()) + } + + private fun successResponse(data: JSONObject): String = + JSONObject().apply { put("code", 0); put("data", data) }.toString() + + private fun errorResponse(message: String): String = + JSONObject().apply { put("code", -1); put("message", message) }.toString() +}