feat: T-B01~B04 — XuqmBundleModule + onProgress + JSBridge/厂商文档
T-B01: XuqmBundleModule 原生模块 - Android: XuqmBundleModule.java(文件读写/manifest/路径) - iOS: XuqmBundleModule.m(对应实现) - JS: NativeBundle.ts 封装 - 注册到 XuqmUpdatePackage T-B02: downloadPluginBundle 添加 onProgress - 使用 ReadableStream 实现下载进度追踪 - checkAndCachePlugin 同步支持 onProgress T-B03: XWebView JSBridge 标准接口文档 - docs/XWebView-JSBridge.md - H5→RN 消息协议 / RN→H5 通信 - 下载处理 / Dialog 覆盖 / 标准 Bridge 接口 T-B04: PushSDK Android 厂商集成文档 - docs/PushSDK-厂商集成.md - 6 厂商配置步骤 / ProGuard 规则 / 调试指南
这个提交包含在:
父节点
ab30b28f3d
当前提交
07b08a4f5a
31
README.md
31
README.md
@ -42,8 +42,7 @@ XuqmGroup-RNSDK/ # @xuqm/rn-sdk (meta-package, private)
|
||||
│ ├── im/ @xuqm/rn-im # IM messaging (WebSocket/STOMP + WatermelonDB)
|
||||
│ ├── push/ @xuqm/rn-push # Multi-vendor push registration
|
||||
│ ├── update/ @xuqm/rn-update # App update check + RN plugin hot-update
|
||||
│ ├── xwebview/ @xuqm/rn-xwebview # Enhanced WebView with JSBridge
|
||||
│ └── license/ @xuqm/rn-license # Encrypted license file decryption & verification
|
||||
│ └── xwebview/ @xuqm/rn-xwebview # Enhanced WebView with JSBridge
|
||||
```
|
||||
|
||||
All packages are at version **0.2.2**.
|
||||
@ -74,20 +73,20 @@ yarn add @react-native-async-storage/async-storage
|
||||
**Option B -- Individual packages:**
|
||||
|
||||
```bash
|
||||
yarn add @xuqm/rn-common @xuqm/rn-im @xuqm/rn-push @xuqm/rn-update @xuqm/rn-xwebview @xuqm/rn-license
|
||||
yarn add @xuqm/rn-common @xuqm/rn-update @xuqm/rn-xwebview
|
||||
yarn add @react-native-async-storage/async-storage
|
||||
```
|
||||
|
||||
### 3. Install optional peer dependencies
|
||||
|
||||
```bash
|
||||
# IM module (local database)
|
||||
# IM module (if using @xuqm/rn-im)
|
||||
yarn add @nozbe/watermelondb
|
||||
|
||||
# XWebView module
|
||||
yarn add react-native-webview react-native-blob-util react-native-svg
|
||||
|
||||
# License / encrypted config
|
||||
# Config file decryption (auto-init)
|
||||
yarn add react-native-quick-crypto
|
||||
```
|
||||
|
||||
@ -149,7 +148,7 @@ Core module. Handles SDK initialization, HTTP requests, token persistence, devic
|
||||
|--------|-------------|
|
||||
| `XuqmSDK.initialize(opts)` | Init with `appKey`, fetches remote config |
|
||||
| `XuqmSDK.initWithConfigFile(encrypted)` | Init from `XUQM-CONFIG-V1` encrypted file |
|
||||
| `XuqmSDK.initializeFromLicense(licenseFile)` | Init from decrypted license data |
|
||||
| `XuqmSDK.initializeFromLicense(file)` | Init from decrypted config data |
|
||||
| `XuqmSDK.awaitInitialization()` | Wait for async init to complete |
|
||||
| `XuqmSDK.setUserId / getUserId` | Manage current user ID |
|
||||
| `XuqmSDK.setUserInfo / getUserInfo` | Manage current user profile |
|
||||
@ -261,24 +260,6 @@ Enhanced WebView with JSBridge for bidirectional communication between RN and we
|
||||
|
||||
---
|
||||
|
||||
### @xuqm/rn-license
|
||||
|
||||
Encrypted license file decryption and device license verification with server. Supports `XUQM-CONFIG-V1` and `XUQM-LICENSE-V1` encrypted formats.
|
||||
|
||||
| Export | Description |
|
||||
|--------|-------------|
|
||||
| `initialize(appKey, options?)` | Configure license with appKey and optional baseUrl |
|
||||
| `initializeFromFile(encryptedContent)` | Decrypt and init from encrypted file |
|
||||
| `checkLicense(userInfo?)` | Verify device license with server (10-min cache) |
|
||||
| `getStatus()` | Returns `'ok'`, `'denied'`, or `'unknown'` |
|
||||
| `getDeviceId()` | Get device ID for license check |
|
||||
| `decryptLicenseFile(content)` | Decrypt `XUQM-LICENSE-V1` format |
|
||||
| `decryptConfigFile(content)` | Decrypt `XUQM-CONFIG-V1` format (alias) |
|
||||
|
||||
**Additional peer dependency:** `react-native-quick-crypto >= 0.7.0`
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
@ -307,7 +288,7 @@ cd packages/common
|
||||
npm publish
|
||||
|
||||
# Publish all packages (from each package directory)
|
||||
for pkg in common im push update xwebview license; do
|
||||
for pkg in common im push update xwebview; do
|
||||
cd packages/$pkg && npm publish && cd ../..
|
||||
done
|
||||
```
|
||||
|
||||
292
docs/PushSDK-厂商集成.md
普通文件
292
docs/PushSDK-厂商集成.md
普通文件
@ -0,0 +1,292 @@
|
||||
# PushSDK Android 厂商推送集成指南
|
||||
|
||||
> 最后更新:2026-06-15
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
PushSDK 支持 Android 多厂商推送 SDK,通过反射机制自动检测设备厂商并调用对应的推送注册 API。宿主应用只需配置 `AndroidManifest.xml` 中的 meta-data,无需编写额外的 Java/Kotlin 代码。
|
||||
|
||||
**支持的厂商**:
|
||||
|
||||
| 厂商 | SDK | 自动检测条件 |
|
||||
|------|-----|------------|
|
||||
| 华为 | HMS Push (HmsInstanceId) | `Build.MANUFACTURER == "HUAWEI"` |
|
||||
| 小米 | MiPush (MiPushClient) | `Build.MANUFACTURER == "XIAOMI" \| "REDMI"` |
|
||||
| OPPO | Heytap Push (PushManager) | `Build.MANUFACTURER == "OPPO" \| "REALME"` |
|
||||
| vivo | PushClient | `Build.MANUFACTURER == "VIVO" \| "IQOO"` |
|
||||
| 荣耀 | HonorPush | `Build.MANUFACTURER == "HONOR"` |
|
||||
| Google | FCM (FirebaseMessaging) | 其他厂商 |
|
||||
| Apple | APNs | iOS 自动 |
|
||||
|
||||
---
|
||||
|
||||
## 一、集成步骤
|
||||
|
||||
### 1.1 添加依赖
|
||||
|
||||
宿主应用的 `android/app/build.gradle`:
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
// 华为 HMS Push(可选)
|
||||
implementation 'com.huawei.hms:push:6.12.0.300'
|
||||
|
||||
// 小米 MiPush(可选)
|
||||
implementation 'com.xiaomi.mipush:mipush-sdk:5.9.9'
|
||||
|
||||
// OPPO Heytap Push(可选)
|
||||
implementation 'com.heytap.mcssdk:mcssdk:3.0.2'
|
||||
|
||||
// vivo Push(可选)
|
||||
implementation 'com.vivo.push:push:3.0.0.4'
|
||||
|
||||
// 荣耀 Push(可选)
|
||||
implementation 'com.hihonor.push:honor-push:7.0.3.300'
|
||||
|
||||
// Google FCM(推荐作为兜底)
|
||||
implementation 'com.google.firebase:firebase-messaging:23.4.0'
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:只需添加目标设备对应的依赖。PushSDK 通过反射调用,不添加对应依赖时该厂商注册会静默跳过。
|
||||
|
||||
### 1.2 配置 AndroidManifest.xml
|
||||
|
||||
在 `android/app/src/main/AndroidManifest.xml` 的 `<application>` 标签内添加 meta-data:
|
||||
|
||||
```xml
|
||||
<application>
|
||||
<!-- 小米 MiPush -->
|
||||
<meta-data
|
||||
android:name="XUQM_XIAOMI_APP_ID"
|
||||
android:value="@string/xiaomi_app_id" />
|
||||
<meta-data
|
||||
android:name="XUQM_XIAOMI_APP_KEY"
|
||||
android:value="@string/xiaomi_app_key" />
|
||||
|
||||
<!-- OPPO Heytap Push -->
|
||||
<meta-data
|
||||
android:name="XUQM_OPPO_APP_KEY"
|
||||
android:value="@string/oppo_app_key" />
|
||||
<meta-data
|
||||
android:name="XUQM_OPPO_APP_SECRET"
|
||||
android:value="@string/oppo_app_secret" />
|
||||
|
||||
<!-- 华为 HMS Push(从 agconnect-services.json 自动读取,无需额外配置) -->
|
||||
|
||||
<!-- vivo Push(从 AndroidManifest 自动读取,无需额外配置) -->
|
||||
|
||||
<!-- 荣耀 Push(从 AndroidManifest 自动读取,无需额外配置) -->
|
||||
|
||||
<!-- FCM(从 google-services.json 自动读取,无需额外配置) -->
|
||||
</application>
|
||||
```
|
||||
|
||||
在 `android/app/src/main/res/values/strings.xml` 中添加:
|
||||
|
||||
```xml
|
||||
<resources>
|
||||
<!-- 小米 -->
|
||||
<string name="xiaomi_app_id">YOUR_XIAOMI_APP_ID</string>
|
||||
<string name="xiaomi_app_key">YOUR_XIAOMI_APP_KEY</string>
|
||||
|
||||
<!-- OPPO -->
|
||||
<string name="oppo_app_key">YOUR_OPPO_APP_KEY</string>
|
||||
<string name="oppo_app_secret">YOUR_OPPO_APP_SECRET</string>
|
||||
</resources>
|
||||
```
|
||||
|
||||
### 1.3 华为 HMS 配置
|
||||
|
||||
华为推送需要 `agconnect-services.json` 文件:
|
||||
|
||||
1. 在 [华为开发者联盟](https://developer.huawei.com/) 创建应用
|
||||
2. 下载 `agconnect-services.json`
|
||||
3. 放置到 `android/app/` 目录
|
||||
|
||||
### 1.4 Google FCM 配置
|
||||
|
||||
FCM 需要 `google-services.json` 文件:
|
||||
|
||||
1. 在 [Firebase Console](https://console.firebase.google.com/) 创建项目
|
||||
2. 下载 `google-services.json`
|
||||
3. 放置到 `android/app/` 目录
|
||||
4. 确保 `android/build.gradle` 添加了 Google Services 插件:
|
||||
|
||||
```groovy
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath 'com.google.gms:google-services:4.4.0'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、工作流程
|
||||
|
||||
```
|
||||
App 启动
|
||||
→ PushSDK.onPushToken(callback) ← 注册监听器
|
||||
→ PushSDK.requestNativeRegistration()
|
||||
→ XuqmPushModule.registerPush()
|
||||
→ 检测厂商 (detectVendor)
|
||||
→ 调用对应厂商 SDK 注册
|
||||
→ 厂商 SDK 返回 token
|
||||
→ emitToken(token, vendor) ← 通过事件发送给 JS
|
||||
→ callback(token, vendor) 被调用
|
||||
→ PushSDK.setDeviceToken(token, vendor)
|
||||
→ 注册到服务器
|
||||
|
||||
用户登录
|
||||
→ PushSDK.initialize(userId)
|
||||
→ 将 pending token 绑定到 userId
|
||||
|
||||
用户登出
|
||||
→ PushSDK.logout(userId)
|
||||
→ 从服务器注销 token
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、厂商 SDK 详细说明
|
||||
|
||||
### 3.1 华为 HMS Push
|
||||
|
||||
**注册方式**:`HmsInstanceId.getInstance(context).getToken(appId, "HCM")`
|
||||
|
||||
**AppID 来源**:`AGConnectServicesConfig.fromContext(context).getString("client/app_id")`
|
||||
|
||||
**Token 事件**:HMS 6.x 版本 token 通过 `getToken()` 同步返回。如果返回空,需要实现 `HmsMessageService.onNewToken()` 异步接收。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.huawei.hms.** { *; }
|
||||
-dontwarn com.huawei.hms.**
|
||||
```
|
||||
|
||||
### 3.2 小米 MiPush
|
||||
|
||||
**注册方式**:`MiPushClient.registerPush(context, appId, appKey)`
|
||||
|
||||
**配置来源**:`AndroidManifest.xml` 中的 `XUQM_XIAOMI_APP_ID` 和 `XUQM_XIAOMI_APP_KEY`
|
||||
|
||||
**Token 事件**:通过 `MiPushMessageReceiver.onReceiveRegisterResult()` 回调。PushSDK 通过反射监听。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.xiaomi.mipush.** { *; }
|
||||
-dontwarn com.xiaomi.mipush.**
|
||||
```
|
||||
|
||||
### 3.3 OPPO Heytap Push
|
||||
|
||||
**注册方式**:`PushManager.getInstance().register(context, appKey, appSecret, pushCallback)`
|
||||
|
||||
**配置来源**:`AndroidManifest.xml` 中的 `XUQM_OPPO_APP_KEY` 和 `XUQM_OPPO_APP_SECRET`
|
||||
|
||||
**Token 事件**:通过 `PushCallback.onRegister(regId)` 回调。PushSDK 使用 `Proxy.newProxyInstance` 动态代理。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.heytap.mcssdk.** { *; }
|
||||
-dontwarn com.heytap.mcssdk.**
|
||||
```
|
||||
|
||||
### 3.4 vivo Push
|
||||
|
||||
**注册方式**:`PushClient.getInstance().initialize(context)`
|
||||
|
||||
**配置来源**:`AndroidManifest.xml` 中的 `com.vivo.push.api_key` 和 `com.vivo.push.app_id`
|
||||
|
||||
**Token 事件**:通过 `PushClientReceiver.onReceiveRegId()` 回调。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.vivo.push.** { *; }
|
||||
-dontwarn com.vivo.push.**
|
||||
```
|
||||
|
||||
### 3.5 荣耀 Honor Push
|
||||
|
||||
**注册方式**:`HonorPushClient.getInstance().getToken()`
|
||||
|
||||
**配置来源**:`AndroidManifest.xml` 中的 `com.hihonor.push.app_id`
|
||||
|
||||
**Token 事件**:通过 `HonorPushService.onNewToken()` 回调。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.hihonor.push.** { *; }
|
||||
-dontwarn com.hihonor.push.**
|
||||
```
|
||||
|
||||
### 3.6 Google FCM
|
||||
|
||||
**注册方式**:`FirebaseMessaging.getInstance().getToken()`
|
||||
|
||||
**配置来源**:`google-services.json`
|
||||
|
||||
**Token 事件**:通过 `FirebaseMessagingService.onNewToken()` 回调。
|
||||
|
||||
**ProGuard 规则**:
|
||||
```
|
||||
-keep class com.google.firebase.** { *; }
|
||||
-dontwarn com.google.firebase.**
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、iOS APNs 配置
|
||||
|
||||
iOS 推送使用原生 APNs,无需额外 SDK 依赖。
|
||||
|
||||
```objective-c
|
||||
// ios/YourApp/AppDelegate.mm
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
|
||||
- (void)application:(UIApplication *)application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
||||
// PushSDK 自动处理
|
||||
}
|
||||
```
|
||||
|
||||
在 Xcode 中:
|
||||
1. 打开 Capabilities → Push Notifications
|
||||
2. 启用 Push Notifications
|
||||
3. 配置 APNs Auth Key(在 Apple Developer Portal)
|
||||
|
||||
---
|
||||
|
||||
## 五、调试
|
||||
|
||||
### 5.1 检查厂商检测
|
||||
|
||||
```typescript
|
||||
import { PushSDK } from '@xuqm/rn-push';
|
||||
// 或通过 NativeModules
|
||||
const vendor = await NativeModules.XuqmPushModule.detectVendor();
|
||||
console.log('Detected vendor:', vendor);
|
||||
```
|
||||
|
||||
### 5.2 检查 Token 注册
|
||||
|
||||
```typescript
|
||||
PushSDK.onPushToken((token, vendor) => {
|
||||
console.log('Push token:', token);
|
||||
console.log('Vendor:', vendor);
|
||||
});
|
||||
|
||||
await PushSDK.requestNativeRegistration();
|
||||
```
|
||||
|
||||
### 5.3 常见问题
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| Token 为空 | 厂商 SDK 未正确配置 | 检查 meta-data / json 文件 |
|
||||
| 注册失败 | 网络问题或 SDK 版本不兼容 | 检查 Logcat 错误日志 |
|
||||
| 华为 token 获取失败 | HMS Core 版本过低 | 更新 HMS Core 到最新版 |
|
||||
| 小米注册无反应 | appId/appKey 错误 | 检查 strings.xml 配置 |
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
> 版本:基于源码自动生成,最后更新 2026-06-15
|
||||
>
|
||||
> 包名:`@xuqm/rn-common` · `@xuqm/rn-update` · `@xuqm/rn-xwebview` · `@xuqm/rn-push` · `@xuqm/rn-im` · `@xuqm/rn-license`
|
||||
> 包名:`@xuqm/rn-common` · `@xuqm/rn-update` · `@xuqm/rn-xwebview` · `@xuqm/rn-push` · `@xuqm/rn-im`
|
||||
|
||||
---
|
||||
|
||||
@ -728,85 +728,6 @@ interface ImEventListener {
|
||||
|
||||
---
|
||||
|
||||
## 6. License(license)
|
||||
|
||||
> `import * as License from '@xuqm/rn-license'`
|
||||
|
||||
设备 License 验证模块,支持离线缓存和自动注册。
|
||||
|
||||
### 6.1 类型定义
|
||||
|
||||
```ts
|
||||
interface LicenseFile {
|
||||
appKey: string;
|
||||
appName?: string;
|
||||
companyName?: string;
|
||||
baseUrl?: string;
|
||||
issuedAt?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
interface LicenseUserInfo {
|
||||
userId?: string;
|
||||
name?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
type LicenseStatus = "ok" | "denied" | "unknown";
|
||||
|
||||
type LicenseResult =
|
||||
| { type: "success"; reason: string }
|
||||
| { type: "error"; message: string };
|
||||
```
|
||||
|
||||
### 6.2 API 方法
|
||||
|
||||
#### `initialize(appKey, options?): void`
|
||||
|
||||
初始化 License SDK。
|
||||
|
||||
```ts
|
||||
License.initialize("your-app-key", {
|
||||
baseUrl: "https://auth.xuqinmin.com",
|
||||
deviceName: "My Device",
|
||||
});
|
||||
```
|
||||
|
||||
#### `initializeFromFile(encryptedContent): Promise<void>`
|
||||
|
||||
从加密 License 文件初始化。自动解密并提取 `appKey` 和 `baseUrl`。
|
||||
|
||||
```ts
|
||||
import licenseFile from "./assets/license.xuqmconfig";
|
||||
await License.initializeFromFile(licenseFile);
|
||||
```
|
||||
|
||||
#### `checkLicense(userInfo?): Promise<LicenseResult>`
|
||||
|
||||
检查 License 状态。优先使用内存缓存(10 分钟有效期),其次持久化缓存,最后请求服务端验证。服务端流程:先尝试 verify(已有 token),失败则 register(注册新设备)。
|
||||
|
||||
```ts
|
||||
const result = await License.checkLicense({ userId: "user-123", name: "张三" });
|
||||
if (result.type === "error") {
|
||||
console.error(result.message);
|
||||
}
|
||||
```
|
||||
|
||||
#### `getStatus(): Promise<LicenseStatus>`
|
||||
|
||||
获取当前 License 状态(`'ok'` / `'denied'` / `'unknown'`)。
|
||||
|
||||
#### `getDeviceId(): Promise<string>`
|
||||
|
||||
获取设备 ID(首次生成后持久化存储)。
|
||||
|
||||
#### `clear(): Promise<void>`
|
||||
|
||||
清除所有 License 缓存(状态、token、设备 ID)。
|
||||
|
||||
---
|
||||
|
||||
## 快速参考
|
||||
|
||||
### 初始化顺序
|
||||
@ -842,5 +763,4 @@ const pluginUpdate = await UpdateSDK.checkAndCachePlugin("buz1");
|
||||
@xuqm/rn-push ← PushSDK(依赖 common)
|
||||
@xuqm/rn-update ← UpdateSDK(依赖 common)
|
||||
@xuqm/rn-xwebview ← XWebView 组件(依赖 common)
|
||||
@xuqm/rn-license ← License(依赖 common)
|
||||
```
|
||||
|
||||
211
docs/XWebView-JSBridge.md
普通文件
211
docs/XWebView-JSBridge.md
普通文件
@ -0,0 +1,211 @@
|
||||
# XWebView JSBridge 标准接口
|
||||
|
||||
> 最后更新:2026-06-15
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
XWebView 内置 JSBridge,支持 H5 页面与 React Native 宿主之间的双向通信。
|
||||
|
||||
**通信机制**:
|
||||
- H5 → RN:`window.ReactNativeWebView.postMessage(JSON.stringify(data))`
|
||||
- RN → H5:`webViewRef.injectJavaScript(jsString)`
|
||||
|
||||
---
|
||||
|
||||
## 一、H5 → RN 消息协议
|
||||
|
||||
### 1.1 内置消息(__xwv 前缀)
|
||||
|
||||
XWebView 自动拦截以下浏览器行为,通过 `postMessage` 发送给 RN:
|
||||
|
||||
| 消息类型 | 触发场景 | 数据格式 |
|
||||
|---------|---------|---------|
|
||||
| `alert` | `window.alert(msg)` | `{__xwv: 'alert', msg: string}` |
|
||||
| `confirm` | `window.confirm(msg)` | `{__xwv: 'confirm', msg: string}` |
|
||||
| `prompt` | `window.prompt(msg, def)` | `{__xwv: 'prompt', msg: string, def: string}` |
|
||||
| `download` | 点击下载链接 | `{__xwv: 'download', url: string, filename: string}` |
|
||||
| `blobdownload` | blob URL 下载 | `{__xwv: 'blobdownload', url: string, filename: string, data: base64}` |
|
||||
| `bloberror` | blob 读取失败 | `{__xwv: 'bloberror', msg: string}` |
|
||||
| `log` | 调试日志 | `{__xwv: 'log', msg: string}` |
|
||||
|
||||
### 1.2 自定义消息
|
||||
|
||||
H5 发送非 `__xwv` 前缀的消息时,XWebView 将其转发给 `onMessage` 回调:
|
||||
|
||||
```javascript
|
||||
// H5 端
|
||||
window.ReactNativeWebView.postMessage(JSON.stringify({
|
||||
type: 'customEvent',
|
||||
payload: { key: 'value' }
|
||||
}));
|
||||
```
|
||||
|
||||
```typescript
|
||||
// RN 端
|
||||
<XWebViewView
|
||||
onMessage={(event) => {
|
||||
const data = JSON.parse(event.nativeEvent.data);
|
||||
if (data.type === 'customEvent') {
|
||||
// 处理自定义消息
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、RN → H5 通信
|
||||
|
||||
### 2.1 注入 JavaScript
|
||||
|
||||
```typescript
|
||||
// 通过 controller API
|
||||
controller.postMessageToWeb('window.dispatchEvent(new CustomEvent("rnMessage", {detail: {type: "update", data: "hello"}}))');
|
||||
|
||||
// 通过 ref
|
||||
webViewRef.injectJavaScript('window.handleRNMessage("hello")');
|
||||
```
|
||||
|
||||
### 2.2 Controller API
|
||||
|
||||
通过 `setXWebViewController` 获取的 controller 对象提供以下方法:
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `refresh()` | 刷新当前页面 |
|
||||
| `close()` | 关闭 WebView |
|
||||
| `goBack()` | 返回上一页 |
|
||||
| `goForward()` | 前进 |
|
||||
| `copyUrl()` | 复制当前 URL 到剪贴板 |
|
||||
| `postMessageToWeb(jsString)` | 向 H5 注入并执行 JS |
|
||||
| `getTitle()` | 获取当前页面标题 |
|
||||
|
||||
---
|
||||
|
||||
## 三、下载处理
|
||||
|
||||
### 3.1 自动下载拦截
|
||||
|
||||
XWebView 自动拦截以下文件扩展名的链接点击:
|
||||
|
||||
```
|
||||
.pdf .zip .rar .tar .gz .apk .ipa .doc .docx .xls .xlsx .ppt .pptx .mp4 .mp3 .mov .exe .dmg
|
||||
```
|
||||
|
||||
### 3.2 下载回调
|
||||
|
||||
```typescript
|
||||
<XWebViewView
|
||||
autoDownload={true}
|
||||
onDownloadStart={(request) => {
|
||||
// request: { url, suggestedFilename, mimeType?, fileSize? }
|
||||
console.log('Download started:', request.suggestedFilename);
|
||||
}}
|
||||
onDownloadProgress={(progress) => {
|
||||
// progress: { url, filename, received, total, percentage }
|
||||
console.log(`Download: ${progress.percentage}%`);
|
||||
}}
|
||||
onDownloadComplete={(result) => {
|
||||
// result: { url, filename, filePath, fileSize }
|
||||
console.log('Download complete:', result.filePath);
|
||||
}}
|
||||
onDownloadError={(url, error) => {
|
||||
console.error('Download failed:', url, error);
|
||||
}}
|
||||
onDownloadDecide={(request) => {
|
||||
// 返回下载决策
|
||||
return { allowed: true, filename: request.suggestedFilename };
|
||||
}}
|
||||
downloadConflict="rename" // 'rename' | 'overwrite'
|
||||
/>
|
||||
```
|
||||
|
||||
### 3.3 Blob URL 下载
|
||||
|
||||
XWebView 自动处理 blob URL 下载:
|
||||
1. 拦截 `<a href="blob:...">` 点击
|
||||
2. 通过 `FileReader` 读取 blob 数据为 base64
|
||||
3. 通过 `saveBase64File` 保存到本地
|
||||
|
||||
---
|
||||
|
||||
## 四、内置 Dialog 覆盖
|
||||
|
||||
XWebView 注入 `DIALOG_OVERRIDE_JS`,覆盖浏览器原生对话框:
|
||||
|
||||
| 原生方法 | 覆盖行为 |
|
||||
|---------|---------|
|
||||
| `window.alert(msg)` | 发送 `{__xwv: 'alert', msg}` 消息 |
|
||||
| `window.confirm(msg)` | 发送 `{__xwv: 'confirm', msg}` 消息,始终返回 `true` |
|
||||
| `window.prompt(msg, def)` | 发送 `{__xwv: 'prompt', msg, def}` 消息,返回默认值 |
|
||||
| `window.open(url)` | 发送日志消息,然后 `window.location.href = url` |
|
||||
|
||||
RN 端收到这些消息后,可以展示原生 Alert/Confirm/Prompt 对话框。
|
||||
|
||||
---
|
||||
|
||||
## 五、标准 Bridge 接口(宿主实现)
|
||||
|
||||
以下接口由宿主应用通过 `bridgeHandlers` 实现,H5 通过 `xuqm.call(method, params)` 调用:
|
||||
|
||||
### 5.1 会话管理
|
||||
|
||||
| 接口 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `getSession` | — | `{token, userId}` | 获取当前登录会话 |
|
||||
| `getUserInfo` | — | `{userId, name, phone, avatar}` | 获取用户信息 |
|
||||
|
||||
### 5.2 导航
|
||||
|
||||
| 接口 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `navigate` | `{screen, params?}` | — | 跳转宿主页面 |
|
||||
| `close` | — | — | 关闭当前 WebView |
|
||||
| `goBack` | — | — | 返回上一页 |
|
||||
|
||||
### 5.2 UI 交互
|
||||
|
||||
| 接口 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `showToast` | `{message}` | — | 显示 Toast |
|
||||
| `showAlert` | `{title?, message}` | — | 显示 Alert 对话框 |
|
||||
| `showConfirm` | `{title?, message, confirmText?, cancelText?}` | `{confirmed: boolean}` | 显示确认对话框 |
|
||||
|
||||
### 5.3 文件操作
|
||||
|
||||
| 接口 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `uploadFile` | `{accept?}` | `{url, name, size}` | 选择并上传文件 |
|
||||
| `openCamera` | — | `{url, base64}` | 打开相机拍照 |
|
||||
| `scanQR` | — | `{result}` | 扫描二维码 |
|
||||
|
||||
### 5.4 设备信息
|
||||
|
||||
| 接口 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `getDeviceInfo` | — | `{platform, version, model}` | 获取设备信息 |
|
||||
| `getLocation` | — | `{latitude, longitude}` | 获取当前位置 |
|
||||
|
||||
---
|
||||
|
||||
## 六、H5 端调用示例
|
||||
|
||||
```javascript
|
||||
// 获取会话
|
||||
xuqm.call('getSession').then(session => {
|
||||
console.log('Token:', session.token);
|
||||
});
|
||||
|
||||
// 跳转宿主页面
|
||||
xuqm.call('navigate', { screen: 'Settings' });
|
||||
|
||||
// 显示 Toast
|
||||
xuqm.call('showToast', { message: '操作成功' });
|
||||
|
||||
// 上传文件
|
||||
xuqm.call('uploadFile', { accept: 'image/*' }).then(file => {
|
||||
console.log('Uploaded:', file.url);
|
||||
});
|
||||
```
|
||||
11
package.json
11
package.json
@ -21,21 +21,22 @@
|
||||
"typecheck:all": "yarn workspaces run typecheck"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-async-storage/async-storage": ">=1.21.0",
|
||||
"react": ">=18.0.0",
|
||||
"react-native": ">=0.76.0",
|
||||
"@react-native-async-storage/async-storage": ">=1.21.0"
|
||||
"react-native": ">=0.76.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@xuqm/rn-common": ">=0.2.0",
|
||||
"@xuqm/rn-im": ">=0.2.0",
|
||||
"@xuqm/rn-license": ">=0.2.0",
|
||||
"@xuqm/rn-push": ">=0.2.0",
|
||||
"@xuqm/rn-update": ">=0.2.0",
|
||||
"@xuqm/rn-xwebview": ">=0.2.0",
|
||||
"@xuqm/rn-license": ">=0.2.0"
|
||||
"react-native-qrcode-svg": "^6.3.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.9.3",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-native": "^0.73.0"
|
||||
"@types/react-native": "^0.73.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
package com.xuqm.update;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* XuqmBundleModule — 原生 bundle 文件管理。
|
||||
*
|
||||
* 提供 bundle 文件的读取、写入、清单管理和热重载能力。
|
||||
* 宿主应用通过此模块实现插件化 bundle 加载。
|
||||
*
|
||||
* 注意:bundle 注入到运行中的 RN host 需要宿主侧配合实现
|
||||
* (RN 0.84 不暴露公开的 loadBundle API)。
|
||||
* 本模块提供文件管理能力,注入逻辑由宿主的 BundleRuntime 负责。
|
||||
*/
|
||||
public class XuqmBundleModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String TAG = "XuqmBundleModule";
|
||||
private static final String BUNDLE_DIR = "rn-bundles";
|
||||
|
||||
public XuqmBundleModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getName() {
|
||||
return "XuqmBundleModule";
|
||||
}
|
||||
|
||||
// ── Bundle 文件操作 ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 检查指定 bundle 文件是否存在。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void bundleExists(String moduleId, Promise promise) {
|
||||
try {
|
||||
File file = getBundleFile(moduleId);
|
||||
promise.resolve(file.exists());
|
||||
} catch (Exception e) {
|
||||
promise.reject("BUNDLE_EXISTS_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 bundle 文件内容(JS 源码文本)。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void readBundle(String moduleId, Promise promise) {
|
||||
try {
|
||||
File file = getBundleFile(moduleId);
|
||||
if (!file.exists()) {
|
||||
promise.reject("BUNDLE_NOT_FOUND", "Bundle not found: " + moduleId);
|
||||
return;
|
||||
}
|
||||
byte[] bytes = readBytes(file);
|
||||
promise.resolve(new String(bytes, StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
promise.reject("BUNDLE_READ_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 bundle 源码写入本地文件。
|
||||
*
|
||||
* @param moduleId 插件 ID
|
||||
* @param source JS 源码文本
|
||||
* @param md5 文件 MD5 校验值
|
||||
*/
|
||||
@ReactMethod
|
||||
public void writeBundle(String moduleId, String source, String md5, Promise promise) {
|
||||
try {
|
||||
File file = getBundleFile(moduleId);
|
||||
File dir = file.getParentFile();
|
||||
if (dir != null && !dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
fos.write(source.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("BUNDLE_WRITE_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定 bundle 文件。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void deleteBundle(String moduleId, Promise promise) {
|
||||
try {
|
||||
File file = getBundleFile(moduleId);
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("BUNDLE_DELETE_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 bundle 文件路径。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getBundlePath(String moduleId, Promise promise) {
|
||||
try {
|
||||
File file = getBundleFile(moduleId);
|
||||
promise.resolve(file.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
promise.reject("BUNDLE_PATH_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Manifest 操作 ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 读取 manifest.json 内容。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void getManifest(Promise promise) {
|
||||
try {
|
||||
File manifestFile = new File(getBundleDir(), "manifest.json");
|
||||
if (!manifestFile.exists()) {
|
||||
promise.resolve("{}");
|
||||
return;
|
||||
}
|
||||
byte[] bytes = readBytes(manifestFile);
|
||||
promise.resolve(new String(bytes, StandardCharsets.UTF_8));
|
||||
} catch (Exception e) {
|
||||
promise.reject("MANIFEST_READ_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入 manifest.json。
|
||||
*/
|
||||
@ReactMethod
|
||||
public void writeManifest(String manifestJson, Promise promise) {
|
||||
try {
|
||||
File manifestFile = new File(getBundleDir(), "manifest.json");
|
||||
try (FileOutputStream fos = new FileOutputStream(manifestFile)) {
|
||||
fos.write(manifestJson.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("MANIFEST_WRITE_ERROR", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 辅助方法 ─────────────────────────────────────────────────────────────
|
||||
|
||||
private File getBundleDir() {
|
||||
Context context = getReactApplicationContext();
|
||||
File dir = new File(context.getFilesDir(), BUNDLE_DIR);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
private File getBundleFile(String moduleId) {
|
||||
String platform = "android";
|
||||
String filename = moduleId + "." + platform + ".bundle";
|
||||
return new File(getBundleDir(), filename);
|
||||
}
|
||||
|
||||
private byte[] readBytes(File file) throws IOException {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
byte[] bytes = new byte[(int) file.length()];
|
||||
fis.read(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,10 @@ public class XuqmUpdatePackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
return Arrays.asList(new XuqmVersionModule(reactContext));
|
||||
return Arrays.asList(
|
||||
new XuqmVersionModule(reactContext),
|
||||
new XuqmBundleModule(reactContext)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -0,0 +1,116 @@
|
||||
#import <React/RCTBridgeModule.h>
|
||||
|
||||
@interface XuqmBundleModule : NSObject <RCTBridgeModule>
|
||||
@end
|
||||
|
||||
@implementation XuqmBundleModule
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (NSString *)bundleDir {
|
||||
NSString *appSupport = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSString *dir = [appSupport stringByAppendingPathComponent:@"rn-bundles"];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
return dir;
|
||||
}
|
||||
|
||||
- (NSString *)bundlePathForModule:(NSString *)moduleId {
|
||||
NSString *filename = [NSString stringWithFormat:@"%@.ios.bundle", moduleId];
|
||||
return [[self bundleDir] stringByAppendingPathComponent:filename];
|
||||
}
|
||||
|
||||
// ── Bundle 文件操作 ──────────────────────────────────────────────────────────
|
||||
|
||||
RCT_EXPORT_METHOD(bundleExists:(NSString *)moduleId
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [self bundlePathForModule:moduleId];
|
||||
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
|
||||
resolve(@(exists));
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(readBundle:(NSString *)moduleId
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [self bundlePathForModule:moduleId];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
reject(@"BUNDLE_NOT_FOUND", [NSString stringWithFormat:@"Bundle not found: %@", moduleId], nil);
|
||||
return;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
reject(@"BUNDLE_READ_ERROR", error.localizedDescription, error);
|
||||
return;
|
||||
}
|
||||
resolve(content);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(writeBundle:(NSString *)moduleId
|
||||
source:(NSString *)source
|
||||
md5:(NSString *)md5
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [self bundlePathForModule:moduleId];
|
||||
NSError *error = nil;
|
||||
[source writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
reject(@"BUNDLE_WRITE_ERROR", error.localizedDescription, error);
|
||||
return;
|
||||
}
|
||||
resolve(@(YES));
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(deleteBundle:(NSString *)moduleId
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [self bundlePathForModule:moduleId];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
NSError *error = nil;
|
||||
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
|
||||
if (error) {
|
||||
reject(@"BUNDLE_DELETE_ERROR", error.localizedDescription, error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
resolve(@(YES));
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getBundlePath:(NSString *)moduleId
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
resolve([self bundlePathForModule:moduleId]);
|
||||
}
|
||||
|
||||
// ── Manifest 操作 ────────────────────────────────────────────────────────────
|
||||
|
||||
RCT_EXPORT_METHOD(getManifest:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [[self bundleDir] stringByAppendingPathComponent:@"manifest.json"];
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||
resolve(@"{}");
|
||||
return;
|
||||
}
|
||||
NSError *error = nil;
|
||||
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
reject(@"MANIFEST_READ_ERROR", error.localizedDescription, error);
|
||||
return;
|
||||
}
|
||||
resolve(content);
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(writeManifest:(NSString *)manifestJson
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject) {
|
||||
NSString *path = [[self bundleDir] stringByAppendingPathComponent:@"manifest.json"];
|
||||
NSError *error = nil;
|
||||
[manifestJson writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
reject(@"MANIFEST_WRITE_ERROR", error.localizedDescription, error);
|
||||
return;
|
||||
}
|
||||
resolve(@(YES));
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* XuqmBundleModule JS 封装。
|
||||
*
|
||||
* 提供 bundle 文件的原生管理能力:
|
||||
* - 读取/写入/删除 bundle 文件
|
||||
* - manifest 管理
|
||||
* - bundle 路径查询
|
||||
*
|
||||
* 注意:bundle 注入到运行中的 RN host(loadBundle)
|
||||
* 由宿主的 BundleRuntime 负责(RN 0.84 不暴露公开 API)。
|
||||
*/
|
||||
|
||||
import {NativeModules, Platform} from 'react-native'
|
||||
|
||||
interface XuqmBundleModuleInterface {
|
||||
bundleExists(moduleId: string): Promise<boolean>
|
||||
readBundle(moduleId: string): Promise<string>
|
||||
writeBundle(moduleId: string, source: string, md5: string): Promise<boolean>
|
||||
deleteBundle(moduleId: string): Promise<boolean>
|
||||
getBundlePath(moduleId: string): Promise<string>
|
||||
getManifest(): Promise<string>
|
||||
writeManifest(manifestJson: string): Promise<boolean>
|
||||
}
|
||||
|
||||
function getModule(): XuqmBundleModuleInterface {
|
||||
const mod = NativeModules.XuqmBundleModule
|
||||
if (!mod) {
|
||||
throw new Error('[XuqmBundleModule] Native module not available')
|
||||
}
|
||||
return mod as XuqmBundleModuleInterface
|
||||
}
|
||||
|
||||
export const NativeBundle = {
|
||||
/**
|
||||
* 检查指定 bundle 文件是否存在。
|
||||
*/
|
||||
async exists(moduleId: string): Promise<boolean> {
|
||||
return getModule().bundleExists(moduleId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 读取 bundle 文件内容(JS 源码文本)。
|
||||
*/
|
||||
async read(moduleId: string): Promise<string> {
|
||||
return getModule().readBundle(moduleId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 将 bundle 源码写入本地文件。
|
||||
*
|
||||
* @param moduleId 插件 ID
|
||||
* @param source JS 源码文本
|
||||
* @param md5 文件 MD5 校验值
|
||||
*/
|
||||
async write(moduleId: string, source: string, md5: string): Promise<void> {
|
||||
await getModule().writeBundle(moduleId, source, md5)
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除指定 bundle 文件。
|
||||
*/
|
||||
async remove(moduleId: string): Promise<void> {
|
||||
await getModule().deleteBundle(moduleId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取 bundle 文件的绝对路径。
|
||||
*/
|
||||
async getPath(moduleId: string): Promise<string> {
|
||||
return getModule().getBundlePath(moduleId)
|
||||
},
|
||||
|
||||
/**
|
||||
* 读取 manifest.json 内容。
|
||||
*/
|
||||
async getManifest(): Promise<string> {
|
||||
return getModule().getManifest()
|
||||
},
|
||||
|
||||
/**
|
||||
* 写入 manifest.json。
|
||||
*/
|
||||
async writeManifest(manifestJson: string): Promise<void> {
|
||||
await getModule().writeManifest(manifestJson)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取 bundle 文件名(平台相关)。
|
||||
*/
|
||||
getFileName(moduleId: string): string {
|
||||
return `${moduleId}.${Platform.OS}.bundle`
|
||||
},
|
||||
}
|
||||
@ -216,11 +216,35 @@ export const UpdateSDK = {
|
||||
|
||||
/**
|
||||
* 下载插件 bundle 源码文本。
|
||||
*
|
||||
* @param downloadUrl 下载地址
|
||||
* @param onProgress 下载进度回调 (0~1)
|
||||
*/
|
||||
async downloadPluginBundle(downloadUrl: string): Promise<string> {
|
||||
async downloadPluginBundle(downloadUrl: string, onProgress?: (progress: number) => void): Promise<string> {
|
||||
const response = await fetch(downloadUrl)
|
||||
if (!response.ok) throw new Error(`[UpdateSDK] Bundle download failed: ${response.status}`)
|
||||
return response.text()
|
||||
|
||||
if (!onProgress) return response.text()
|
||||
|
||||
const contentLength = Number(response.headers.get('Content-Length') ?? '0')
|
||||
const reader = response.body?.getReader()
|
||||
if (!reader) return response.text()
|
||||
|
||||
const chunks: Uint8Array[] = []
|
||||
let received = 0
|
||||
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
chunks.push(value)
|
||||
received += value.length
|
||||
if (contentLength > 0) {
|
||||
onProgress(received / contentLength)
|
||||
}
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder()
|
||||
return chunks.map(chunk => decoder.decode(chunk, { stream: true })).join('') + decoder.decode()
|
||||
},
|
||||
|
||||
/**
|
||||
@ -249,13 +273,15 @@ export const UpdateSDK = {
|
||||
* 如果有更新,下载 bundle 并缓存到本地。
|
||||
* 调用方需要在下次启动时通过 reloadPlugin() 加载新版本。
|
||||
*
|
||||
* @param moduleId 插件 ID
|
||||
* @param onProgress 下载进度回调 (0~1)
|
||||
* @returns 如果有更新,返回缓存的 bundle 信息;否则返回 null
|
||||
*/
|
||||
async checkAndCachePlugin(moduleId: string): Promise<CachedRnBundle | null> {
|
||||
async checkAndCachePlugin(moduleId: string, onProgress?: (progress: number) => void): Promise<CachedRnBundle | null> {
|
||||
const info = await this.checkPluginUpdate(moduleId)
|
||||
if (!info.needsUpdate) return null
|
||||
|
||||
const source = await this.downloadPluginBundle(info.downloadUrl)
|
||||
const source = await this.downloadPluginBundle(info.downloadUrl, onProgress)
|
||||
return this.cachePluginBundle(moduleId, info.latestVersion, info.md5, source)
|
||||
},
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export { UpdateSDK } from './UpdateSDK'
|
||||
export type { PluginMeta, AppUpdateInfo, PluginUpdateInfo, CachedRnBundle } from './UpdateSDK'
|
||||
export { NativeBundle } from './NativeBundle'
|
||||
|
||||
@ -25,8 +25,6 @@ export { PushSDK } from '@xuqm/rn-push'
|
||||
export type { PushVendor } from '@xuqm/rn-push'
|
||||
export { UpdateSDK } from '@xuqm/rn-update'
|
||||
export type { PluginMeta, AppUpdateInfo, PluginUpdateInfo, CachedRnBundle } from '@xuqm/rn-update'
|
||||
export { decryptLicenseFile, decryptConfigFile, initialize as initializeLicense, initializeFromFile as initializeLicenseFromFile, checkLicense } from '@xuqm/rn-license'
|
||||
export type { LicenseFile, LicenseUserInfo, LicenseStatus, LicenseResult } from '@xuqm/rn-license'
|
||||
export {
|
||||
XWebViewControl,
|
||||
XWebViewScreen,
|
||||
|
||||
245
yarn.lock
245
yarn.lock
@ -401,43 +401,6 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@xuqm/rn-common@>=0.2.2", "@xuqm/rn-common@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/common":
|
||||
version "0.3.2"
|
||||
resolved "file:packages/common"
|
||||
|
||||
"@xuqm/rn-im@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/im":
|
||||
version "0.2.2"
|
||||
resolved "file:packages/im"
|
||||
dependencies:
|
||||
"@xuqm/rn-common" ">=0.2.2"
|
||||
|
||||
"@xuqm/rn-license@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/license":
|
||||
version "0.3.0"
|
||||
resolved "file:packages/license"
|
||||
dependencies:
|
||||
"@xuqm/rn-common" ">=0.2.2"
|
||||
|
||||
"@xuqm/rn-push@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/push":
|
||||
version "0.2.2"
|
||||
resolved "file:packages/push"
|
||||
dependencies:
|
||||
"@xuqm/rn-common" ">=0.2.2"
|
||||
|
||||
"@xuqm/rn-update@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/update":
|
||||
version "0.3.0"
|
||||
resolved "file:packages/update"
|
||||
dependencies:
|
||||
"@xuqm/rn-common" ">=0.2.2"
|
||||
|
||||
"@xuqm/rn-xwebview@file:/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-RNSDK/packages/xwebview":
|
||||
version "0.2.2"
|
||||
resolved "file:packages/xwebview"
|
||||
dependencies:
|
||||
"@xuqm/rn-common" ">=0.2.2"
|
||||
react-native-blob-util "^0.24.7"
|
||||
react-native-svg "^15.15.4"
|
||||
react-native-webview "^13.16.1"
|
||||
|
||||
abort-controller@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
|
||||
@ -559,6 +522,11 @@ buffer-from@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
camelcase@^5.0.0:
|
||||
version "5.3.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
camelcase@^6.2.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz"
|
||||
@ -608,6 +576,15 @@ ci-info@^3.2.0:
|
||||
resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz"
|
||||
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
||||
|
||||
cliui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
|
||||
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
cliui@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
|
||||
@ -692,33 +669,31 @@ csstype@^3.2.2:
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz"
|
||||
integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==
|
||||
|
||||
debug@^2.6.9:
|
||||
debug@2.6.9, debug@^2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^4.1.0, debug@^4.3.1, debug@^4.4.0, debug@4:
|
||||
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.4.0:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz"
|
||||
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
|
||||
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
decode-uri-component@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/decode-uri-component/-/decode-uri-component-0.2.2.tgz"
|
||||
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
|
||||
|
||||
depd@~2.0.0, depd@2.0.0:
|
||||
depd@2.0.0, depd@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
|
||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
||||
@ -728,6 +703,11 @@ destroy@1.2.0:
|
||||
resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz"
|
||||
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
|
||||
integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
|
||||
|
||||
dom-serializer@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/dom-serializer/-/dom-serializer-2.0.0.tgz"
|
||||
@ -872,6 +852,14 @@ finalhandler@1.1.2:
|
||||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
find-up@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
|
||||
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
|
||||
dependencies:
|
||||
locate-path "^5.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
flow-enums-runtime@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz"
|
||||
@ -887,7 +875,7 @@ gensync@^1.0.0-beta.2:
|
||||
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
|
||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
@ -971,7 +959,7 @@ inherits@~2.0.3, inherits@~2.0.4:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
invariant@^2.2.4, invariant@2.2.4:
|
||||
invariant@2.2.4, invariant@^2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz"
|
||||
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
|
||||
@ -1082,12 +1070,19 @@ lighthouse-logger@^1.0.0:
|
||||
debug "^2.6.9"
|
||||
marky "^1.2.2"
|
||||
|
||||
locate-path@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
|
||||
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash.throttle@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz"
|
||||
integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
loose-envify@^1.0.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
@ -1168,7 +1163,7 @@ metro-cache@0.84.3:
|
||||
https-proxy-agent "^7.0.5"
|
||||
metro-core "0.84.3"
|
||||
|
||||
metro-config@^0.84.0, metro-config@0.84.3:
|
||||
metro-config@0.84.3, metro-config@^0.84.0:
|
||||
version "0.84.3"
|
||||
resolved "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz"
|
||||
integrity sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==
|
||||
@ -1182,7 +1177,7 @@ metro-config@^0.84.0, metro-config@0.84.3:
|
||||
metro-runtime "0.84.3"
|
||||
yaml "^2.6.1"
|
||||
|
||||
metro-core@^0.84.0, metro-core@0.84.3:
|
||||
metro-core@0.84.3, metro-core@^0.84.0:
|
||||
version "0.84.3"
|
||||
resolved "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz"
|
||||
integrity sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==
|
||||
@ -1221,7 +1216,7 @@ metro-resolver@0.84.3:
|
||||
dependencies:
|
||||
flow-enums-runtime "^0.0.6"
|
||||
|
||||
metro-runtime@^0.84.0, metro-runtime@0.84.3:
|
||||
metro-runtime@0.84.3, metro-runtime@^0.84.0:
|
||||
version "0.84.3"
|
||||
resolved "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz"
|
||||
integrity sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==
|
||||
@ -1229,7 +1224,7 @@ metro-runtime@^0.84.0, metro-runtime@0.84.3:
|
||||
"@babel/runtime" "^7.25.0"
|
||||
flow-enums-runtime "^0.0.6"
|
||||
|
||||
metro-source-map@^0.84.0, metro-source-map@0.84.3:
|
||||
metro-source-map@0.84.3, metro-source-map@^0.84.0:
|
||||
version "0.84.3"
|
||||
resolved "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz"
|
||||
integrity sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==
|
||||
@ -1287,7 +1282,7 @@ metro-transform-worker@0.84.3:
|
||||
metro-transform-plugins "0.84.3"
|
||||
nullthrows "^1.1.1"
|
||||
|
||||
metro@^0.84.0, metro@0.84.3:
|
||||
metro@0.84.3, metro@^0.84.0:
|
||||
version "0.84.3"
|
||||
resolved "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz"
|
||||
integrity sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==
|
||||
@ -1375,16 +1370,16 @@ mkdirp@^1.0.4:
|
||||
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
|
||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
ms@^2.1.3, ms@2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
|
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||
|
||||
ms@2.1.3, ms@^2.1.3:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
nanoid@^3.3.11:
|
||||
version "3.3.12"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/nanoid/-/nanoid-3.3.12.tgz"
|
||||
@ -1424,6 +1419,11 @@ ob1@0.84.3:
|
||||
dependencies:
|
||||
flow-enums-runtime "^0.0.6"
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"
|
||||
@ -1446,11 +1446,35 @@ open@^7.0.3:
|
||||
is-docker "^2.0.0"
|
||||
is-wsl "^2.1.1"
|
||||
|
||||
p-limit@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
|
||||
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
|
||||
dependencies:
|
||||
p-limit "^2.2.0"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
|
||||
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
|
||||
|
||||
path-key@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
|
||||
@ -1479,6 +1503,11 @@ picomatch@^4.0.4:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz"
|
||||
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
|
||||
pretty-format@^29.7.0:
|
||||
version "29.7.0"
|
||||
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
|
||||
@ -1495,6 +1524,24 @@ promise@^8.3.0:
|
||||
dependencies:
|
||||
asap "~2.0.6"
|
||||
|
||||
prop-types@^15.8.0:
|
||||
version "15.8.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.13.1"
|
||||
|
||||
qrcode@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88"
|
||||
integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==
|
||||
dependencies:
|
||||
dijkstrajs "^1.0.1"
|
||||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
query-string@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/query-string/-/query-string-7.1.3.tgz"
|
||||
@ -1525,6 +1572,11 @@ react-devtools-core@^6.1.5:
|
||||
shell-quote "^1.6.1"
|
||||
ws "^7"
|
||||
|
||||
react-is@^16.13.1:
|
||||
version "16.13.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
|
||||
@ -1543,6 +1595,15 @@ react-native-blob-util@^0.24.7:
|
||||
base-64 "0.1.0"
|
||||
glob "13.0.1"
|
||||
|
||||
react-native-qrcode-svg@^6.3.21:
|
||||
version "6.3.21"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/react-native-qrcode-svg/-/react-native-qrcode-svg-6.3.21.tgz#af873cf8e5b9fc68315a2c267ff6563d55c56abb"
|
||||
integrity sha512-6vcj4rcdpWedvphDR+NSJcudJykNuLgNGFwm2p4xYjR8RdyTzlrELKI5LkO4ANS9cQUbqsfkpippPv64Q2tUtA==
|
||||
dependencies:
|
||||
prop-types "^15.8.0"
|
||||
qrcode "^1.5.4"
|
||||
text-encoding "^0.7.0"
|
||||
|
||||
react-native-safe-area-context@^5.4.0:
|
||||
version "5.7.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz"
|
||||
@ -1617,6 +1678,11 @@ require-directory@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz"
|
||||
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
scheduler@0.27.0:
|
||||
version "0.27.0"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz"
|
||||
@ -1666,6 +1732,11 @@ serve-static@^1.16.2:
|
||||
parseurl "~1.3.3"
|
||||
send "~0.19.1"
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
setprototypeof@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz"
|
||||
@ -1701,16 +1772,11 @@ source-map@^0.5.6:
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
|
||||
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
|
||||
|
||||
source-map@^0.6.0:
|
||||
source-map@^0.6.0, source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/source-map/-/source-map-0.6.1.tgz"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
split-on-first@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/split-on-first/-/split-on-first-1.1.0.tgz"
|
||||
@ -1783,6 +1849,11 @@ terser@^5.15.0:
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-encoding@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643"
|
||||
integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==
|
||||
|
||||
throat@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz"
|
||||
@ -1873,6 +1944,11 @@ whatwg-fetch@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz"
|
||||
integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==
|
||||
|
||||
which-module@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
|
||||
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
|
||||
|
||||
which@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
|
||||
@ -1880,6 +1956,15 @@ which@^2.0.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wrap-ansi@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
|
||||
@ -1894,6 +1979,11 @@ ws@^7, ws@^7.5.10:
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
|
||||
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz"
|
||||
@ -1909,11 +1999,36 @@ yaml@^2.6.1:
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz"
|
||||
integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==
|
||||
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs-parser@^21.1.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs@^15.3.1:
|
||||
version "15.4.1"
|
||||
resolved "https://nexus.xuqinmin.com/repository/npm/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yargs@^17.6.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户