diff --git a/README.md b/README.md index fa99e55..52e8112 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,359 @@ # XuqmGroup React Native SDK -`rn-sdk` 的稳定入口是 `src/index.ts`,统一登录/登出层在 `src/sdk.ts`。 -旧的 `src/core`、`src/im`、`src/push`、`src/update` 目录已清理,避免继续引用废弃实现。 +Modular React Native SDK providing IM, Push, App/Plugin Update, WebView, and License management for XuqmGroup platform applications. -## 当前结构 +## Table of Contents -```text -XuqmGroup-RNSDK/ +- [Overview](#overview) +- [Package Structure](#package-structure) +- [Installation](#installation) +- [Quick Start](#quick-start) +- [Package Details](#package-details) +- [Development](#development) +- [Plugin Scaffolding](#plugin-scaffolding) + +--- + +## Overview + +`@xuqm/rn-sdk` is the meta-package that re-exports all sub-modules. It provides a unified `XuqmSDK` entry point with coordinated login/logout that wires up IM, Push, and token management in a single call. + +You can install the meta-package for full functionality, or pick individual packages as needed. + +**Peer Dependencies** (required in host app): + +| Package | Version | +|---------|---------| +| `react` | >= 18.0.0 | +| `react-native` | >= 0.76.0 | +| `@react-native-async-storage/async-storage` | >= 1.21.0 | + +--- + +## Package Structure + +``` +XuqmGroup-RNSDK/ # @xuqm/rn-sdk (meta-package, private) ├── src/ -│ ├── index.ts # 对外聚合入口 -│ └── sdk.ts # 统一登录 / 登出封装 +│ ├── index.ts # Re-exports all sub-packages +│ └── sdk.ts # Unified login / logout ├── packages/ -│ ├── common/ # 初始化、网络、设备、Token、基础组件 -│ ├── im/ # IM、会话、历史、群组、关系链 -│ ├── push/ # 推送设备注册 -│ ├── update/ # App 更新 / RN 热更新 -│ └── xwebview/ # 内置 WebView 浏览器 -└── README.md +│ ├── common/ @xuqm/rn-common # Init, network, device, token, HTTP, auto-init +│ ├── 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 ``` -## 安装 +All packages are at version **0.2.2**. + +--- + +## Installation + +### 1. Configure the npm registry + +```bash +# In your host project root +cat > .npmrc << 'EOF' +registry=https://nexus.xuqinmin.com/repository/npm/ +legacy-peer-deps=true +EOF +``` + +### 2. Install + +**Option A -- Meta-package (all modules):** ```bash yarn add @xuqm/rn-sdk yarn add @react-native-async-storage/async-storage - -# 如需按模块拆分接入,也可以直接安装 -yarn add @xuqm/rn-common @xuqm/rn-im @xuqm/rn-push @xuqm/rn-update @xuqm/rn-xwebview ``` -## 入口 +**Option B -- Individual packages:** -- `XuqmSDK.initialize({ appKey, logLevel })` -- `XuqmSDK.login({ userId, userSig })` -- `XuqmSDK.logout()` -- `ImSDK` -- `PushSDK` -- `UpdateSDK` -- `XWebViewScreen` -- `XWebViewView` +```bash +yarn add @xuqm/rn-common @xuqm/rn-im @xuqm/rn-push @xuqm/rn-update @xuqm/rn-xwebview @xuqm/rn-license +yarn add @react-native-async-storage/async-storage +``` -详细用法见 [docs/rn-sdk/README.md](../docs/rn-sdk/README.md)。 +### 3. Install optional peer dependencies + +```bash +# IM module (local database) +yarn add @nozbe/watermelondb + +# XWebView module +yarn add react-native-webview react-native-blob-util react-native-svg + +# License / encrypted config +yarn add react-native-quick-crypto +``` + +--- + +## Quick Start + +### Zero-config initialization (recommended) + +Place an encrypted `.xuqmconfig` file (XUQM-CONFIG-V1 format) in your project, then add a Metro alias so the SDK can auto-discover it: + +```js +// metro.config.js +const { getDefaultConfig } = require('metro-config'); +module.exports = (async () => { + const config = await getDefaultConfig(__dirname); + config.resolver.extraNodeModules = { + '@xuqm/autoinit-config': './path/to/your.xuqmconfig', + }; + return config; +})(); +``` + +That is it. When `@xuqm/rn-common` is imported, it silently calls `XuqmSDK.initWithConfigFile()` using the encrypted file. No explicit init code needed. + +### Manual initialization + +```ts +import { XuqmSDK } from '@xuqm/rn-sdk'; + +// Init with appKey (fetches config from server) +await XuqmSDK.initialize({ appKey: 'your-app-key' }); + +// Login (wires up IM + Push automatically) +await XuqmSDK.login({ userId: 'user123', userSig: '...' }); + +// Logout +await XuqmSDK.logout(); +``` + +### Using individual modules + +```ts +import { ImSDK } from '@xuqm/rn-im'; +import { PushSDK } from '@xuqm/rn-push'; +import { UpdateSDK } from '@xuqm/rn-update'; +import { XWebViewScreen } from '@xuqm/rn-xwebview'; +``` + +--- + +## Package Details + +### @xuqm/rn-common + +Core module. Handles SDK initialization, HTTP requests, token persistence, device identification, and auto-initialization from encrypted config files. + +| Export | Description | +|--------|-------------| +| `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.awaitInitialization()` | Wait for async init to complete | +| `XuqmSDK.setUserId / getUserId` | Manage current user ID | +| `XuqmSDK.setUserInfo / getUserInfo` | Manage current user profile | +| `apiRequest(url, options?)` | HTTP request with Bearer token auth | +| `configureHttp(opts)` | Override HTTP base URL or headers | +| `getDeviceId()` | Stable per-install UUID | +| `getDeviceInfo()` | Device brand/model/OS info | +| `detectPushVendor()` | Detect push vendor from device brand | +| `ScaledImage` | Image component with aspect-ratio scaling | +| `isInitialized()` | Check if SDK is ready | +| `getConfig()` | Get resolved config | + +**Auto-init mechanism:** On import, `autoInit.ts` tries `require('@xuqm/autoinit-config')`. If the Metro alias is configured, it decrypts the file and calls `initWithConfigFile` silently. If the alias is not configured, it skips without error. + +**Encrypted config format:** `XUQM-CONFIG-V1.{base64url-salt}.{base64url-iv}.{base64url-ciphertext}` -- decrypted via PBKDF2 (120k iterations, SHA-256) + AES-256-GCM using `react-native-quick-crypto`. + +--- + +### @xuqm/rn-im + +Full-featured IM module. WebSocket/STOMP transport with WatermelonDB local persistence. Supports single and group chat with 15 message types. + +**Message types:** `TEXT`, `IMAGE`, `VIDEO`, `AUDIO`, `FILE`, `CUSTOM`, `LOCATION`, `NOTIFY`, `RICH_TEXT`, `CALL_AUDIO`, `CALL_VIDEO`, `QUOTE`, `MERGE`, `REVOKED`, `FORWARD` + +| Export | Description | +|--------|-------------| +| `ImSDK.login(userId, userSig)` | Connect to IM server | +| `ImSDK.disconnect()` | Disconnect | +| `ImSDK.sendMessage(params)` | Send a message | +| `ImSDK.getConversations()` | List conversations | +| `ImSDK.getHistory(query)` | Fetch message history | +| `ImSDK.createGroup(...)` | Create a group | +| `ImSDK.joinGroup(groupId)` | Join a group | +| `ImSDK.getGroupInfo(groupId)` | Get group details | +| `ImSDK.getGroupMembers(groupId)` | List group members | +| `listFriends / addFriend / removeFriend` | Friend management | +| `setFriendGroup / listFriendGroups` | Friend grouping | +| `checkBlacklist` | Blacklist check | +| `searchUsers / searchGroups / searchMessages` | Search | +| `editMessage` | Edit a sent message | +| `setConversationHidden / setConversationGroup` | Conversation management | +| `locateHistoryPage / locateGroupHistoryPage` | Jump to specific history page | +| `syncOfflineMessages / offlineMessageCount` | Offline message handling | +| `ImClient` | Low-level STOMP client | +| `ImDatabase` | WatermelonDB wrapper | +| `uploadFile` | File upload for IM | + +**Additional peer dependency:** `@nozbe/watermelondb >= 0.27.0` + +--- + +### @xuqm/rn-push + +Multi-vendor push registration. Automatically detects the device vendor (Huawei, Xiaomi, OPPO, vivo, Honor, FCM, APNS) and bridges to the native push module. + +| Export | Description | +|--------|-------------| +| `PushSDK.initialize(userId?)` | Initialize push and register device token | +| `PushSDK.registerToken(userId, token, vendor?)` | Register a push token with server | +| `PushSDK.unregisterToken(userId)` | Unregister push token | +| `PushSDK.setDeviceToken(token, vendor?)` | Manually set device token | +| `PushSDK.onPushToken(callback)` | Listen for push token updates | +| `PushSDK.logout(userId?)` | Unregister and clean up | +| `isNativePushAvailable()` | Check if native push module is linked | +| `detectVendorNative()` | Detect vendor via native module | +| `registerPushNative()` | Trigger native vendor registration | + +**Native module:** Requires `NativeModules.XuqmPushModule` to be linked in the host app. + +--- + +### @xuqm/rn-update + +App update check and RN plugin (bundle) hot-update. Checks for new app versions and downloads/caches RN bundles for OTA delivery. + +| Export | Description | +|--------|-------------| +| `UpdateSDK.registerPlugin(meta)` | Register plugin metadata at bundle load time | +| `UpdateSDK.checkAppUpdate(bypassIgnore?)` | Check for app-level update | +| `UpdateSDK.openStore(url?, marketUrl?)` | Open app store page | +| `UpdateSDK.checkPluginUpdate(moduleId)` | Check if a plugin bundle has an update | +| `UpdateSDK.downloadPluginBundle(url)` | Download bundle source text | +| `UpdateSDK.cachePluginBundle(id, ver, md5, src)` | Cache bundle to AsyncStorage | +| `UpdateSDK.getCachedPluginBundle(id)` | Read cached bundle | +| `UpdateSDK.checkAndCachePlugin(id)` | One-step check + download + cache | +| `UpdateSDK.getRegisteredPlugins()` | List all registered plugins | +| `UpdateSDK.getAppVersionCode / getAppVersionName` | Get host app version | + +**Types:** `PluginMeta`, `AppUpdateInfo`, `PluginUpdateInfo`, `CachedRnBundle` + +--- + +### @xuqm/rn-xwebview + +Enhanced WebView with JSBridge for bidirectional communication between RN and web content. Includes progress bar, inline view, and full-screen screen components. + +| Export | Description | +|--------|-------------| +| `XWebViewScreen` | Full-screen WebView (React Navigation screen) | +| `XWebViewView` | Inline WebView component | +| `XWebViewProgress` | Progress bar component | +| `openXWebView(url, options?)` | Open WebView programmatically | +| `setXWebViewController(controller)` | Set global WebView controller | +| `getXWebViewConfig()` | Get current WebView config | + +**Additional dependencies:** `react-native-webview`, `react-native-blob-util`, `react-native-svg` + +**Additional peer dependency:** `@react-navigation/native >= 7.0.0` + +--- + +### @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 + +- Node.js >= 18 +- Yarn (workspaces) +- React Native development environment + +### Commands + +```bash +# Type-check all packages +yarn typecheck + +# Type-check a single package +cd packages/im && yarn typecheck +``` + +### Publishing + +Packages are published to the private npm registry at `https://nexus.xuqinmin.com/repository/npm-hosted/`. + +```bash +# Publish a single package +cd packages/common +npm publish + +# Publish all packages (from each package directory) +for pkg in common im push update xwebview license; do + cd packages/$pkg && npm publish && cd ../.. +done +``` + +### Project layout + +``` +packages// +├── src/ +│ └── index.ts # Public API +├── package.json +├── tsconfig.json +└── README.md +``` + +Each package has its own `package.json`, `tsconfig.json`, and `publishConfig` pointing to the hosted registry. The root `package.json` is a private meta-package that depends on all sub-packages. + +--- + +## Plugin Scaffolding + +The `create-plugin.mjs` script generates a new UpdateSDK plugin skeleton and auto-registers it into the host project. + +### Usage + +```bash +# Interactive mode +node packages/update/scripts/create-plugin.mjs + +# CLI mode +node packages/update/scripts/create-plugin.mjs [title] [subtitle] [accentColor] +``` + +### What it generates + +- `bundle.ts` -- Plugin entry point +- `{PascalCase}Screen.tsx` -- Screen component +- `plugin.json` -- Plugin metadata + +### What it auto-registers + +- `pluginCatalog.ts` -- Adds plugin to catalog +- `debugPlugins.ts` -- Adds to debug plugin list +- `package.json` -- Adds build script for the new bundle +- `metro.split.config.js` -- Adds bundle entry +- `babel.config.js` -- Adds module alias +- `tsconfig.json` -- Adds path mapping + +**Module ID rules:** Lowercase alphanumeric, must be unique across all plugins. diff --git a/docs/SDK-API参考.md b/docs/SDK-API参考.md new file mode 100644 index 0000000..9e52f98 --- /dev/null +++ b/docs/SDK-API参考.md @@ -0,0 +1,846 @@ +# XuqmGroup RN SDK API 参考 + +> 版本:基于源码自动生成,最后更新 2026-06-15 +> +> 包名:`@xuqm/rn-common` · `@xuqm/rn-update` · `@xuqm/rn-xwebview` · `@xuqm/rn-push` · `@xuqm/rn-im` · `@xuqm/rn-license` + +--- + +## 1. XuqmSDK(common) + +> `import { XuqmSDK } from '@xuqm/rn-common'` + +SDK 核心初始化模块。所有其他 SDK 模块依赖 XuqmSDK 完成初始化后才能正常工作。 + +### 1.1 类型定义 + +```ts +interface XuqmInitOptions { + appKey: string; // 应用标识(从租户平台获取) + debug?: boolean; // 是否开启调试日志 +} + +interface XuqmConfig { + appKey: string; + apiUrl: string; + imWsUrl: string; + fileServiceUrl: string; + debug: boolean; +} + +interface XuqmUserInfo { + userId?: string; + name?: string; + email?: string; + phone?: string; +} +``` + +### 1.2 API 方法 + +#### `XuqmSDK.initialize(options): Promise` + +异步初始化。从租户平台拉取远程配置(IM WebSocket URL、文件服务 URL 等),失败时回退到内置默认地址。 + +```ts +await XuqmSDK.initialize({ appKey: "your-app-key", debug: __DEV__ }); +``` + +#### `XuqmSDK.init(options): void` + +同步初始化(不拉取远程配置,直接使用内置默认 URL)。适用于网络不可用或无需远程配置的场景。 + +```ts +XuqmSDK.init({ appKey: "your-app-key" }); +``` + +#### `XuqmSDK.initializeFromLicense(file, options?): void` + +从已解密的 License 文件对象初始化。通常配合 License SDK 使用。 + +```ts +interface LicenseFile { + appKey: string; + baseUrl?: string; + serverUrl?: string; +} +XuqmSDK.initializeFromLicense(decryptedFile, { debug: false }); +``` + +#### `XuqmSDK.initWithConfigFile(encryptedContent, options?): Promise` + +从加密配置文件(`.xuqmconfig`)初始化。SDK 自动解密并初始化,支持 `XUQM-CONFIG-V1` 和 `XUQM-LICENSE-V1` 两种格式。 + +```ts +import config from "./assets/app.xuqmconfig"; +await XuqmSDK.initWithConfigFile(config); +``` + +#### `XuqmSDK.awaitInitialization(): Promise` + +等待初始化完成。在异步初始化场景下,其他模块可调用此方法确保 SDK 已就绪。 + +```ts +await XuqmSDK.awaitInitialization(); +``` + +#### `XuqmSDK.setUserId(userId): void` + +设置当前用户 ID。登录成功后调用。 + +```ts +XuqmSDK.setUserId("user-123"); +``` + +#### `XuqmSDK.getUserId(): string | null` + +获取当前用户 ID。 + +#### `XuqmSDK.setUserInfo(info): void` + +设置用户信息(用于灰度发布和 License 验证)。登录成功后调用,同时会自动同步 `userId`。 + +```ts +XuqmSDK.setUserInfo({ userId: "user-123", name: "张三", phone: "13800138000" }); +``` + +#### `XuqmSDK.getUserInfo(): XuqmUserInfo | null` + +获取当前用户信息。 + +### 1.3 独立导出 + +```ts +import { awaitInitialization } from "@xuqm/rn-common"; +// 等同于 XuqmSDK.awaitInitialization() +``` + +--- + +## 2. UpdateSDK(update) + +> `import { UpdateSDK } from '@xuqm/rn-update'` + +应用整包更新 + RN 插件(Bundle)热更新。 + +### 2.1 类型定义 + +```ts +interface PluginMeta { + moduleId: string; // 插件唯一标识,如 'buz1' + version: string; // 当前 bundle 版本号 +} + +interface AppUpdateInfo { + needsUpdate: boolean; + versionName?: string; // 最新版本名,如 '2.1.0' + versionCode?: number; // 最新版本号(整数) + downloadUrl?: string; // APK 直接下载地址 + changeLog?: string; // 更新日志 + forceUpdate?: boolean; // 是否强制更新 + appStoreUrl?: string; // iOS App Store 地址 + marketUrl?: string; // Android 应用商店地址 + requiresLogin?: boolean; // 服务端要求登录后才能检查 + alreadyDownloaded?: boolean; // APK 是否已下载(仅 Android) + apkHash?: string | null; // APK SHA-256 校验值 +} + +interface PluginUpdateInfo { + needsUpdate: boolean; + latestVersion: string; + downloadUrl: string; + md5: string; + minCommonVersion: string; // 要求的最低 common bundle 版本 + note: string; +} + +interface CachedRnBundle { + moduleId: string; + version: string; + md5: string; + downloadedAt: string; + source: string; +} +``` + +### 2.2 API 方法 + +#### `UpdateSDK.registerPlugin(meta): void` + +注册插件元数据。在插件 bundle 入口文件顶部调用。 + +```ts +// src/plugins/buz1/bundle.ts +UpdateSDK.registerPlugin({ moduleId: "buz1", version: "1.0.0" }); +``` + +#### `UpdateSDK.getRegisteredPluginVersion(moduleId): string | undefined` + +获取已注册的插件版本号。 + +#### `UpdateSDK.getRegisteredPlugins(): PluginMeta[]` + +获取所有已注册的插件列表。 + +#### `UpdateSDK.checkAppUpdate(bypassIgnore?): Promise` + +检查 App 整包更新。自动检测平台(iOS/Android)并传给服务端。 + +- `bypassIgnore = false`(默认):静默检查,跳过用户已忽略的版本 +- `bypassIgnore = true`:用户主动检查,不跳过 + +```ts +const info = await UpdateSDK.checkAppUpdate(true); +if (info.needsUpdate && info.forceUpdate) { + // 强制更新 +} +``` + +#### `UpdateSDK.openStore(appStoreUrl?, marketUrl?): Promise` + +打开应用商店。iOS 使用 `appStoreUrl`,Android 使用 `marketUrl`。 + +#### `UpdateSDK.checkPluginUpdate(moduleId): Promise` + +检查指定插件的更新。插件必须已通过 `registerPlugin()` 注册。 + +```ts +const info = await UpdateSDK.checkPluginUpdate("buz1"); +if (info.needsUpdate) { + // 下载并缓存 +} +``` + +#### `UpdateSDK.downloadPluginBundle(downloadUrl): Promise` + +下载插件 bundle 源码文本。 + +#### `UpdateSDK.cachePluginBundle(moduleId, version, md5, source): Promise` + +缓存插件 bundle 到本地(AsyncStorage)。 + +#### `UpdateSDK.getCachedPluginBundle(moduleId): Promise` + +读取已缓存的插件 bundle。 + +#### `UpdateSDK.checkAndCachePlugin(moduleId): Promise` + +检查并下载插件更新(一步完成)。有更新时下载并缓存,无更新返回 `null`。调用方需在下次启动时通过 `reloadPlugin()` 加载新版本。 + +```ts +const cached = await UpdateSDK.checkAndCachePlugin("buz1"); +if (cached) { + // 下次启动生效 +} +``` + +#### `UpdateSDK.getAppVersionCode(): number` + +当前 App versionCode(原生读取)。 + +#### `UpdateSDK.getAppVersionName(): string` + +当前 App versionName(原生读取)。 + +#### `UpdateSDK._devSetAppVersion(versionCode, versionName?): void` + +开发环境手动设置版本号(仅调试用,生产环境不要调用)。 + +--- + +## 3. XWebView(xwebview) + +> `import { XWebViewScreen, XWebViewView, XWebViewConfig, ... } from '@xuqm/rn-xwebview'` + +WebView 容器组件,支持导航栏、文件下载、JS Bridge 等。 + +### 3.1 类型定义 + +```ts +type XWebViewConfig = { + showTopBar?: boolean; // 是否显示顶部导航栏 + showStatusBar?: boolean; // 是否显示状态栏 + doubleBackExit?: boolean; // 双击返回退出 + title?: string; // 自定义标题 + showTitle?: boolean; // 是否显示标题 + autoTitle?: boolean; // 自动从网页获取标题 + showMenu?: boolean; // 是否显示菜单按钮 + openForBrowser?: boolean; // 是否用外部浏览器打开 + clickMenu?: XWebViewClickMenu; // 自定义菜单 + url?: string; // 加载的 URL + content?: string; // 加载的 HTML 内容 + onMessage?: (event: XWebViewMessageEvent) => void; // 接收 Web 消息 + injectedJavaScript?: string; // 注入的 JS + onPermissionRequest?: (request: XWebViewPermissionRequest) => void; + autoDownload?: boolean; // 自动下载 + onDownloadStart?: (request: XWebViewDownloadRequest) => void; + onDownloadProgress?: (progress: XWebViewDownloadProgress) => void; + onDownloadComplete?: (result: XWebViewDownloadResult) => void; + onDownloadError?: (url: string, error: string) => void; + onDownloadDecide?: ( + request: XWebViewDownloadRequest, + ) => XWebViewDownloadDecision | Promise; + downloadConflict?: "rename" | "overwrite"; + onClose?: () => void; +}; + +type XWebViewControllerAPI = { + refresh: () => void; + close: () => void; + goBack: () => void; + goForward: () => void; + copyUrl: () => void; + postMessageToWeb: (jsString: string) => void; + getTitle: () => string; +}; + +type XWebViewClickMenu = { view?: React.ReactNode; onClick: () => void }; +type XWebViewMessageEvent = { nativeEvent: { data: string } }; +type XWebViewPermissionRequest = { + origin: string; + resources: string[]; + grant: (resources?: string[]) => void; + deny: () => void; +}; +type XWebViewDownloadRequest = { + url: string; + suggestedFilename: string; + mimeType?: string; + fileSize?: number; +}; +type XWebViewDownloadDecision = { + allowed: boolean; + filename?: string; + savePath?: string; +}; +type XWebViewDownloadProgress = { + url: string; + filename: string; + received: number; + total: number; + percentage: number; +}; +type XWebViewDownloadResult = { + url: string; + filename: string; + filePath: string; + fileSize: number; +}; +``` + +### 3.2 组件 + +#### `` + +全屏 WebView 页面组件,带顶部导航栏(返回、关闭、菜单)。作为独立屏幕使用,通过导航跳转进入。 + +#### `` + +嵌入式 WebView 组件,无顶部导航栏。适用于嵌入到其他页面中。 + +### 3.3 桥接函数 + +#### `openXWebView(navigate, config): void` + +打开 XWebView 页面。调用后会设置配置并执行导航。 + +```ts +import { openXWebView } from "@xuqm/rn-xwebview"; + +openXWebView(navigation.navigate, { + url: "https://example.com", + title: "详情", + showTopBar: true, +}); +``` + +#### `getXWebViewConfig(): XWebViewConfig` + +获取当前 XWebView 配置(由 `openXWebView` 设置)。 + +#### `setXWebViewController(controller: XWebViewControllerAPI | null): void` + +设置 XWebView 控制器实例(由组件内部调用)。 + +#### `XWebViewControl: XWebViewControllerAPI` + +全局 WebView 控制代理。可调用 `refresh()`、`close()`、`goBack()`、`goForward()`、`copyUrl()`、`postMessageToWeb(js)`、`getTitle()` 等方法控制当前 WebView。 + +```ts +import { XWebViewControl } from "@xuqm/rn-xwebview"; + +XWebViewControl.postMessageToWeb('window.dispatchEvent(new Event("refresh"))'); +XWebViewControl.close(); +``` + +### 3.4 子组件 + +#### `XWebViewProgress` + +WebView 加载进度条组件。 + +--- + +## 4. PushSDK(push) + +> `import { PushSDK } from '@xuqm/rn-push'` + +推送服务 SDK,支持华为、小米、OPPO、vivo、荣耀、FCM(Android)和 APNs(iOS)。 + +### 4.1 类型定义 + +```ts +type PushVendor = + | "HUAWEI" + | "XIAOMI" + | "OPPO" + | "VIVO" + | "HONOR" + | "FCM" + | "APNS"; +``` + +### 4.2 API 方法 + +#### `PushSDK.initialize(userId?): Promise` + +初始化推送服务。如果已有缓存的 token 且用户匹配,自动注册到服务端。 + +```ts +await PushSDK.initialize("user-123"); +``` + +#### `PushSDK.setPendingToken(token, vendor?): void` + +缓存设备 token(不立即注册)。适用于原生层在用户登录前就收到 token 的场景。 + +```ts +PushSDK.setPendingToken(token, "HUAWEI"); +``` + +#### `PushSDK.getPendingToken(): { token: string; vendor?: PushVendor } | null` + +获取当前缓存的 pending token。 + +#### `PushSDK.setDeviceToken(token, vendor?): Promise` + +设置设备 token 并立即尝试注册到服务端。 + +#### `PushSDK.requestNativeRegistration(): Promise` + +触发原生推送注册。Android 尝试注册厂商 SDK,iOS 请求 APNs 注册。通过 `onPushToken()` 监听 token 回调。 + +#### `PushSDK.onPushToken(callback): () => void` + +监听原生层推送 token。返回取消监听函数。 + +```ts +const unsubscribe = PushSDK.onPushToken((token, vendor) => { + PushSDK.setDeviceToken(token, vendor as PushVendor); +}); +``` + +#### `PushSDK.registerToken(userId, token, vendor?): Promise` + +注册推送设备 token 到服务端。vendor 缺省时自动检测设备品牌。 + +```ts +await PushSDK.registerToken("user-123", deviceToken, "HUAWEI"); +``` + +#### `PushSDK.unregisterToken(userId): Promise` + +注销用户的推送 token。 + +#### `PushSDK.logout(userId?): Promise` + +登出推送服务(内部调用 `unregisterToken`)。userId 缺省时使用当前用户。 + +--- + +## 5. ImSDK(im) + +> `import { ImSDK } from '@xuqm/rn-im'` + +即时通讯 SDK,提供登录、消息收发、会话管理、群组、好友等功能。 + +### 5.1 核心类型 + +```ts +type ChatType = "SINGLE" | "GROUP"; +type MsgType = + | "TEXT" + | "IMAGE" + | "VIDEO" + | "AUDIO" + | "FILE" + | "CUSTOM" + | "LOCATION" + | "NOTIFY" + | "RICH_TEXT" + | "CALL_AUDIO" + | "CALL_VIDEO" + | "QUOTE" + | "MERGE" + | "REVOKED" + | "FORWARD"; +type MsgStatus = + | "SENDING" + | "SENT" + | "DELIVERED" + | "READ" + | "FAILED" + | "REVOKED"; + +interface ImMessage { + id: string; + appKey: string; + fromUserId: string; + fromId?: string; + toId: string; + chatType: ChatType; + msgType: MsgType; + content: string; + status: MsgStatus; + mentionedUserIds?: string; + groupReadCount?: number; + revoked?: boolean; + createdAt: number; + editedAt?: number | null; +} + +interface ImGroup { + id: string; + appKey: string; + name: string; + groupType?: string; + creatorId: string; + memberIds: string; + adminIds: string; + announcement?: string | null; + memberInfo?: string | null; + extAttributes?: string | null; + createdAt: number; +} + +interface ConversationData { + targetId: string; + chatType: ChatType; + lastMsgContent?: string | null; + lastMsgType?: string | null; + lastMsgTime: number; + unreadCount: number; + isMuted: boolean; + isPinned: boolean; + conversationGroup?: string | null; +} + +interface UserProfile { + id?: string; + appKey?: string; + userId: string; + nickname?: string | null; + avatar?: string | null; + gender?: string | null; + status?: string | null; + createdAt?: number | null; +} + +interface FriendRequest { + id: string; + appKey: string; + fromUserId: string; + toUserId: string; + remark?: string | null; + status: "PENDING" | "ACCEPTED" | "REJECTED"; + createdAt: number; + reviewedAt?: number | null; +} + +interface GroupJoinRequest { + id: string; + appKey: string; + groupId: string; + requesterId: string; + remark?: string | null; + status: "PENDING" | "ACCEPTED" | "REJECTED"; + createdAt: number; + reviewedAt?: number | null; +} + +interface ImEventListener { + onConnected?: () => void; + onDisconnected?: (reason?: string) => void; + onMessage?: (msg: ImMessage) => void; + onGroupMessage?: (msg: ImMessage) => void; + onSystemMessage?: (msg: ImMessage) => void; + onRead?: (msg: ImMessage) => void; + onRevoke?: (data: { msgId: string; operatorId: string }) => void; + onError?: (error: string) => void; +} +``` + +### 5.2 连接与认证 + +| 方法 | 签名 | 说明 | +| ------------- | ---------------------------------------------------- | --------------------------------- | +| `login` | `(userId: string, userSig: string) => Promise` | 登录 IM 服务,建立 WebSocket 连接 | +| `reconnect` | `() => Promise` | 重新连接(使用已保存的 token) | +| `disconnect` | `() => void` | 断开连接并清理状态 | +| `isConnected` | `() => boolean` | 是否已连接 | + +### 5.3 消息发送 + +| 方法 | 签名 | 说明 | +| ---------------------- | ------------------------------------------------------------------ | ------------ | +| `sendMessage` | `(params: SendMessageParams) => Promise` | 通用发送 | +| `sendTextMessage` | `(toId, chatType, text) => Promise` | 文本消息 | +| `sendImageMessage` | `(toId, chatType, imageUri) => Promise` | 图片消息 | +| `sendVideoMessage` | `(toId, chatType, videoUri) => Promise` | 视频消息 | +| `sendAudioMessage` | `(toId, chatType, audioUri) => Promise` | 语音消息 | +| `sendFileMessage` | `(toId, chatType, fileUri) => Promise` | 文件消息 | +| `sendNotifyMessage` | `(toId, chatType, content) => Promise` | 通知消息 | +| `sendQuoteMessage` | `(toId, chatType, content, quotedMsgId) => Promise` | 引用消息 | +| `sendMergeMessage` | `(toId, chatType, title, summary, messages) => Promise` | 合并转发 | +| `sendCallAudioMessage` | `(toId, chatType, content) => Promise` | 语音通话消息 | +| `sendCallVideoMessage` | `(toId, chatType, content) => Promise` | 视频通话消息 | +| `sendCustomMessage` | `(toId, chatType, content) => Promise` | 自定义消息 | +| `sendLocationMessage` | `(toId, chatType, ...) => Promise` | 位置消息 | +| `sendRichTextMessage` | `(toId, chatType, content) => Promise` | 富文本消息 | +| `sendForwardMessage` | `(toId, chatType, messageId) => Promise` | 转发消息 | + +### 5.4 消息操作 + +| 方法 | 签名 | 说明 | +| ------------------------------ | --------------------------------------------------------------- | --------------------- | +| `revokeMessage` | `(messageId: string) => Promise` | 撤回消息 | +| `editMessage` | `(messageId: string, content: string) => Promise` | 编辑消息 | +| `fetchHistory` | `(toId, page?, size?) => Promise` | 获取单聊历史消息 | +| `fetchHistoryWithFilters` | `(toId, params) => Promise>` | 带筛选的历史消息 | +| `fetchGroupHistory` | `(groupId, page?, size?) => Promise` | 获取群聊历史消息 | +| `fetchGroupHistoryWithFilters` | `(groupId, params) => Promise>` | 带筛选的群聊历史 | +| `locateHistoryPage` | `(toId, messageId, size?) => Promise>` | 定位到指定消息所在页 | +| `locateGroupHistoryPage` | `(groupId, messageId, size?) => Promise>` | 群聊定位到指定消息 | +| `syncOfflineMessages` | `(maxCount?) => Promise` | 同步离线消息 | +| `offlineMessageCount` | `() => Promise` | 离线消息数量 | +| `searchMessages` | `(params) => Promise<...>` | 搜索本地消息(需 DB) | + +### 5.5 会话管理 + +| 方法 | 签名 | 说明 | +| -------------------------------- | --------------------------------------------------- | ---------------------- | +| `listConversations` | `() => Promise` | 获取会话列表 | +| `subscribeConversations` | `(callback) => () => void` | 订阅会话列表变化 | +| `markRead` | `(targetId, chatType?) => Promise` | 标记已读 | +| `setConversationMuted` | `(targetId, chatType, muted) => Promise` | 设置免打扰 | +| `setConversationPinned` | `(targetId, chatType, pinned) => Promise` | 设置置顶 | +| `setDraft` | `(targetId, chatType, draft) => Promise` | 保存草稿 | +| `getDraft` | `(targetId, chatType) => Promise` | 获取草稿 | +| `setConversationHidden` | `(targetId, chatType, hidden) => Promise` | 隐藏会话 | +| `setConversationGroup` | `(targetId, chatType, groupName?) => Promise` | 设置会话分组 | +| `listConversationGroups` | `() => Promise` | 获取会话分组列表 | +| `listConversationGroupItems` | `(groupName) => Promise` | 获取分组下的会话 | +| `deleteConversation` | `(targetId, chatType) => Promise` | 删除会话 | +| `getTotalUnreadCount` | `() => Promise` | 获取总未读数 | +| `syncHistoryForAllConversations` | `() => Promise` | 同步所有会话的历史消息 | + +### 5.6 群组管理 + +| 方法 | 签名 | 说明 | +| ------------------------- | ------------------------------------------------------------- | -------------------- | +| `createGroup` | `(name, memberIds, groupType?) => Promise` | 创建群组 | +| `listGroups` | `() => Promise` | 获取已加入的群组列表 | +| `listPublicGroups` | `(keyword?) => Promise` | 搜索公开群组 | +| `getGroupInfo` | `(groupId) => Promise` | 获取群组详情 | +| `listGroupMembers` | `(groupId) => Promise` | 获取群成员列表 | +| `searchGroupMembers` | `(groupId, keyword, size?) => Promise` | 搜索群成员 | +| `updateGroupInfo` | `(groupId, name?, announcement?) => Promise` | 更新群信息 | +| `addGroupMember` | `(groupId, userId) => Promise` | 添加群成员 | +| `removeGroupMember` | `(groupId, targetUserId) => Promise` | 移除群成员 | +| `batchAddGroupMembers` | `(groupId, userIds) => Promise` | 批量添加群成员 | +| `batchRemoveGroupMembers` | `(groupId, userIds) => Promise` | 批量移除群成员 | +| `leaveGroup` | `(groupId) => Promise` | 退出群组 | +| `setGroupRole` | `(groupId, userId, role) => Promise` | 设置群成员角色 | +| `muteGroupMember` | `(groupId, userId, minutes) => Promise` | 禁言群成员 | +| `transferGroupOwner` | `(groupId, newOwnerId) => Promise` | 转让群主 | +| `updateGroupAttributes` | `(groupId, attributes) => Promise` | 更新群属性 | +| `removeGroupAttributes` | `(groupId, keys) => Promise` | 删除群属性 | +| `dismissGroup` | `(groupId) => Promise` | 解散群组 | +| `modifyGroupMemberInfo` | `(groupId, userId, nickname?, role?) => Promise` | 修改群成员信息 | +| `adminGroupReadReceipts` | `(groupId, messageIds) => Promise` | 群消息已读回执 | + +### 5.7 群组加入请求 + +| 方法 | 签名 | 说明 | +| ------------------------------ | --------------------------------------------------- | ---------------- | +| `sendGroupJoinRequest` | `(groupId, remark?) => Promise` | 发送入群申请 | +| `listGroupJoinRequests` | `(groupId) => Promise` | 获取入群申请列表 | +| `acceptGroupJoinRequest` | `(groupId, requestId) => Promise` | 同意入群申请 | +| `rejectGroupJoinRequest` | `(groupId, requestId) => Promise` | 拒绝入群申请 | +| `batchAcceptGroupJoinRequests` | `(groupId, requestIds) => Promise` | 批量同意 | +| `batchRejectGroupJoinRequests` | `(groupId, requestIds) => Promise` | 批量拒绝 | + +### 5.8 好友管理 + +| 方法 | 签名 | 说明 | +| --------------------------- | ----------------------------------------------- | ---------------- | +| `listFriends` | `() => Promise` | 获取好友列表 | +| `addFriend` | `(friendId) => Promise` | 添加好友 | +| `removeFriend` | `(friendId) => Promise` | 删除好友 | +| `removeAllFriends` | `() => Promise` | 删除所有好友 | +| `batchAddFriends` | `(friendIds) => Promise` | 批量添加好友 | +| `batchRemoveFriends` | `(friendIds) => Promise` | 批量删除好友 | +| `setFriendGroup` | `(friendId, groupName?) => Promise` | 设置好友分组 | +| `listFriendGroups` | `() => Promise` | 获取好友分组列表 | +| `listFriendsByGroup` | `(groupName) => Promise` | 按分组获取好友 | +| `listFriendRequests` | `(direction?) => Promise` | 获取好友请求列表 | +| `sendFriendRequest` | `(toUserId, remark?) => Promise` | 发送好友请求 | +| `acceptFriendRequest` | `(requestId) => Promise` | 同意好友请求 | +| `rejectFriendRequest` | `(requestId) => Promise` | 拒绝好友请求 | +| `batchAcceptFriendRequests` | `(requestIds) => Promise` | 批量同意 | +| `batchRejectFriendRequests` | `(requestIds) => Promise` | 批量拒绝 | + +### 5.9 黑名单 + +| 方法 | 签名 | 说明 | +| --------------------- | ------------------------------------------------- | -------------- | +| `listBlacklist` | `() => Promise` | 获取黑名单 | +| `addToBlacklist` | `(blockedUserId) => Promise` | 加入黑名单 | +| `removeFromBlacklist` | `(blockedUserId) => Promise` | 移出黑名单 | +| `checkBlacklist` | `(targetUserId) => Promise` | 检查黑名单状态 | + +### 5.10 用户资料 + +| 方法 | 签名 | 说明 | +| --------------- | --------------------------------------------------------------- | ------------ | +| `getProfile` | `(userId) => Promise` | 获取用户资料 | +| `updateProfile` | `(userId, nickname?, avatar?, gender?) => Promise` | 更新用户资料 | +| `searchUsers` | `(keyword, size?) => Promise` | 搜索用户 | + +### 5.11 事件监听 + +| 方法 | 签名 | 说明 | +| ------------------ | ------------------------------------- | ---------------- | +| `addListener` | `(listener: ImEventListener) => void` | 添加事件监听器 | +| `removeListener` | `(listener: ImEventListener) => void` | 移除事件监听器 | +| `subscribeGroup` | `(groupId) => void` | 订阅群组消息 | +| `unsubscribeGroup` | `(groupId) => void` | 取消订阅群组消息 | + +--- + +## 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` + +从加密 License 文件初始化。自动解密并提取 `appKey` 和 `baseUrl`。 + +```ts +import licenseFile from "./assets/license.xuqmconfig"; +await License.initializeFromFile(licenseFile); +``` + +#### `checkLicense(userInfo?): Promise` + +检查 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` + +获取当前 License 状态(`'ok'` / `'denied'` / `'unknown'`)。 + +#### `getDeviceId(): Promise` + +获取设备 ID(首次生成后持久化存储)。 + +#### `clear(): Promise` + +清除所有 License 缓存(状态、token、设备 ID)。 + +--- + +## 快速参考 + +### 初始化顺序 + +```ts +// 1. 初始化核心 SDK +await XuqmSDK.initialize({ appKey: "xxx" }); +// 或从加密文件初始化 +await XuqmSDK.initWithConfigFile(encryptedConfig); + +// 2. 登录后设置用户信息 +XuqmSDK.setUserInfo({ userId, name, phone }); + +// 3. 初始化推送 +PushSDK.onPushToken((token, vendor) => { + PushSDK.setDeviceToken(token, vendor); +}); +await PushSDK.initialize(userId); + +// 4. 登录 IM +await ImSDK.login(userId, userSig); + +// 5. 检查更新 +const appUpdate = await UpdateSDK.checkAppUpdate(); +const pluginUpdate = await UpdateSDK.checkAndCachePlugin("buz1"); +``` + +### 包依赖关系 + +``` +@xuqm/rn-common ← XuqmSDK, 配置, HTTP, 工具函数(其他包的底层依赖) +@xuqm/rn-im ← ImSDK(依赖 common) +@xuqm/rn-push ← PushSDK(依赖 common) +@xuqm/rn-update ← UpdateSDK(依赖 common) +@xuqm/rn-xwebview ← XWebView 组件(依赖 common) +@xuqm/rn-license ← License(依赖 common) +``` diff --git a/docs/插件脚手架.md b/docs/插件脚手架.md new file mode 100644 index 0000000..fb5a7b8 --- /dev/null +++ b/docs/插件脚手架.md @@ -0,0 +1,164 @@ +# 插件脚手架工具 + +> 脚本位置:`packages/update/scripts/create-plugin.mjs` +> 最后更新:2026-06-15 + +--- + +## 概述 + +`create-plugin.mjs` 是 UpdateSDK 提供的插件脚手架工具,用于一键创建完整的插件骨架项目。 + +**功能**: + +- 交互式或命令行输入插件参数 +- 校验 moduleId 唯一性 +- 自动生成插件文件(bundle.ts / Screen / plugin.json) +- 自动注册到宿主(pluginCatalog / debugPlugins / build scripts / metro config / babel alias / tsconfig) + +--- + +## 用法 + +### 命令行模式(推荐) + +```bash +node scripts/create-plugin.mjs [title] [subtitle] [accentColor] +``` + +示例: + +```bash +# 完整参数 +node scripts/create-plugin.mjs buz4 "IM 消息" "即时通讯业务组件" "#E74C3C" + +# 最简(title/subtitle/accentColor 使用默认值) +node scripts/create-plugin.mjs buz5 +``` + +### 交互模式 + +```bash +node scripts/create-plugin.mjs +``` + +按提示依次输入 moduleId、title、subtitle、accentColor。 + +--- + +## 参数说明 + +| 参数 | 必填 | 格式 | 默认值 | 说明 | +| ----------- | ---- | ------------------ | ----------------- | ----------------------- | +| moduleId | ✅ | `^[a-z][a-z0-9]*$` | — | 插件唯一标识,如 `buz4` | +| title | ❌ | 任意文本 | = moduleId | 插件标题 | +| subtitle | ❌ | 任意文本 | `{title}业务组件` | 插件副标题 | +| accentColor | ❌ | CSS 颜色值 | `#0E84FA` | 主题色 | + +--- + +## 自动生成的文件 + +### 插件目录 `src/plugins/{moduleId}/` + +#### `bundle.ts` — 入口文件 + +```typescript +import { UpdateSDK } from "@xuqm/rn-update"; +import { registerPluginFromBridge } from "@plugins/runtimeBridge"; +import { Buz4Screen } from "@buz4/Buz4Screen"; + +// 自动注册插件版本(bundle 加载时执行) +UpdateSDK.registerPlugin({ moduleId: "buz4", version: "1.0.0" }); + +// 注册 UI 组件到宿主运行时 +registerPluginFromBridge({ + id: "buz4", + title: "IM 消息", + subtitle: "即时通讯业务组件", + accentColor: "#E74C3C", + Component: Buz4Screen, +}); +``` + +#### `{ModuleId}Screen.tsx` — Screen 骨架 + +```typescript +import { StyleSheet, Text, View } from 'react-native'; + +export function Buz4Screen() { + return ( + + buz4 + TODO: 实现buz4功能 + + ); +} +``` + +#### `plugin.json` — 插件元数据 + +```json +{ "moduleId": "buz4", "version": "1.0.0" } +``` + +--- + +## 自动更新的宿主文件 + +| 文件 | 变更内容 | +| ------------------------------- | --------------------------------------------------------------- | +| `src/app/pluginCatalog.ts` | 添加插件目录条目(id/title/summary/accentColor) | +| `src/bootstrap/debugPlugins.ts` | 注册 debug loader(`require('../plugins/{id}/bundle')`) | +| `package.json` | 添加 `build:android:{id}` / `build:ios:{id}` 脚本,更新聚合脚本 | +| `metro.split.config.js` | 添加 moduleId 的 offset 分支 | +| `babel.config.js` | 添加 `@{id}` alias | +| `tsconfig.json` | 添加 `@{id}/*` path mapping | + +--- + +## 唯一性校验 + +脚本会检查两处来确保 moduleId 唯一: + +1. `src/app/pluginCatalog.ts` 中已注册的 id +2. `src/plugins/` 目录下已存在的目录名 + +如果 moduleId 已存在,脚本会报错并退出: + +``` +❌ moduleId "buz4" 已存在,请使用其他 ID +``` + +--- + +## moduleId 命名规范 + +- 必须以**小写字母**开头 +- 只能包含**小写字母和数字** +- 推荐格式:`buz{N}`(如 `buz4`、`buz5`) + +Metro module ID offset 自动计算:`buz1` → 11M,`buz2` → 12M,`buzN` → (10+N)M。 + +--- + +## 创建后的开发流程 + +```bash +# 1. 生成插件骨架 +node scripts/create-plugin.mjs buz4 "IM 消息" "即时通讯业务组件" "#E74C3C" + +# 2. 实现业务 UI +# 编辑 src/plugins/buz4/Buz4Screen.tsx + +# 3. 创建子目录 +mkdir -p src/plugins/buz4/services +mkdir -p src/plugins/buz4/pages + +# 4. 验证 +yarn validate + +# 5. 构建 +yarn build:android:buz4 +yarn build:ios:buz4 +``` diff --git a/docs/配置文件规范.md b/docs/配置文件规范.md new file mode 100644 index 0000000..3cecb7a --- /dev/null +++ b/docs/配置文件规范.md @@ -0,0 +1,182 @@ +# 配置文件规范 + +> 最后更新:2026-06-15 + +--- + +## 概述 + +XuqmGroup SDK 使用加密配置文件完成自动初始化,对齐 Android SDK 的 ContentProvider 模式。 + +**核心流程**: + +1. 宿主把加密配置文件放到标准位置 +2. SDK 在 common bundle 加载时自动读取、解密、初始化 +3. 宿主代码零初始化调用 + +--- + +## 配置文件格式 + +### 加密格式 + +``` +XUQM-CONFIG-V1.{base64urlSalt}.{base64urlIV}.{base64urlCiphertext} +``` + +| 部分 | 说明 | +| --------------------- | ------------------------------------------- | +| `XUQM-CONFIG-V1` | 固定 magic header | +| `base64urlSalt` | PBKDF2 盐值(16 字节,base64url 编码) | +| `base64urlIV` | AES-GCM 初始向量(12 字节,base64url 编码) | +| `base64urlCiphertext` | AES-256-GCM 密文 + 16 字节 auth tag | + +### 加密参数 + +| 参数 | 值 | +| -------- | ------------------ | +| 算法 | AES-256-GCM | +| 密钥派生 | PBKDF2-HMAC-SHA256 | +| 迭代次数 | 120,000 | +| 密钥长度 | 256 位 | +| Auth Tag | 128 位(16 字节) | + +### 解密后 JSON 结构 + +```json +{ + "appKey": "yiwangxin", + "appName": "医网信", + "companyName": "BJCA", + "baseUrl": "https://www.51trust.com", + "serverUrl": "https://www.51trust.com", + "packageName": "cn.org.bjca.wcert.ywq", + "iosBundleId": "com.bjca.ywq", + "issuedAt": "2026-01-01T00:00:00Z", + "expiresAt": "2028-01-01T00:00:00Z" +} +``` + +| 字段 | 必填 | 说明 | +| ------------- | ---- | -------------------------------- | +| `appKey` | ✅ | 应用唯一标识 | +| `appName` | ❌ | 应用名称 | +| `companyName` | ❌ | 公司名称 | +| `baseUrl` | ❌ | API 服务地址(覆盖默认值) | +| `serverUrl` | ❌ | 私有部署地址(覆盖所有服务端点) | +| `packageName` | ❌ | Android 包名(用于校验) | +| `iosBundleId` | ❌ | iOS Bundle ID | +| `issuedAt` | ❌ | 签发时间 | +| `expiresAt` | ❌ | 过期时间 | + +--- + +## 宿主集成方式 + +### React Native(Metro bundler) + +由于 Metro 只能 bundle JS/TS 模块,配置文件使用 `.ts` 扩展名。 + +**文件位置**:`src/assets/xuqm/config.xuqmconfig.ts` + +```typescript +export const ENCRYPTED_CONFIG = "XUQM-CONFIG-V1.{salt}.{iv}.{ciphertext}"; +``` + +**Metro alias**(`metro.config.js`): + +```javascript +const alias = { + "@xuqm/autoinit-config": path.resolve( + projectRoot, + "src/assets/xuqm/config.xuqmconfig", + ), +}; +``` + +**Babel alias**(`babel.config.js`): + +```javascript +'@xuqm/autoinit-config': './src/assets/xuqm/config.xuqmconfig' +``` + +**TypeScript path**(`tsconfig.json`): + +```json +"@xuqm/autoinit-config": ["src/assets/xuqm/config.xuqmconfig"] +``` + +### Android(原生) + +配置文件位置:`src/main/assets/xuqm/config.xuqm` + +Android SDK 通过 ContentProvider 自动读取,无需额外配置。 + +### iOS(原生) + +配置文件位置:`{app}/xuqm/config.xuqm` + +--- + +## 自动初始化流程 + +``` +common bundle 加载 + → import '@xuqm/rn-common' + → packages/common/src/index.ts + → import './autoInit' (副作用) + → tryAutoInit() + → require('@xuqm/autoinit-config') + → Metro 解析 alias → 宿主 config.xuqmconfig.ts + → XuqmSDK.initWithConfigFile(encrypted) + → configCrypto.decryptConfigFile(encrypted) + → PBKDF2 派生密钥 + → AES-256-GCM 解密 + → initializeFromLicense(decrypted) + → configureHttp(baseUrl || serverUrl) + → markInitialized() +``` + +### 失败处理 + +对齐 Android SDK 的 `runCatching + Log.w` 行为: + +- 配置文件不存在 → 静默跳过 +- 解密失败 → 静默跳过 +- SDK 保持未初始化状态 +- `awaitInitialization()` 会超时 +- 不崩溃、不阻塞 app 启动 + +--- + +## 降级机制 + +如果自动初始化失败,宿主可以手动调用: + +```typescript +import { XuqmSDK } from "@xuqm/rn-common"; + +// 使用默认 URL 初始化 +XuqmSDK.init({ appKey: "yiwangxin", debug: __DEV__ }); +``` + +--- + +## 安全注意事项 + +1. **不要提交解密后的明文配置**到版本控制 +2. **不要在日志中打印**配置文件内容或解密后的数据 +3. 配置文件的 `appKey` 用于 API 调用,**不要硬编码**在业务代码中 +4. `serverUrl` 字段会覆盖所有服务端点,确保指向正确的环境 + +--- + +## 与 Android SDK 的对齐 + +| 环节 | Android SDK | RNSDK | +| ------------ | -------------------------- | --------------------------------------------- | +| 配置文件位置 | `assets/xuqm/config.xuqm` | `src/assets/xuqm/config.xuqmconfig.ts` | +| 自动初始化 | ContentProvider.onCreate() | common 包 import 时 autoInit | +| 解密 | ConfigFileCrypto (Java) | configCrypto (JS + react-native-quick-crypto) | +| 失败处理 | runCatching + Log.w | try/catch + 静默跳过 | +| 服务端点覆盖 | configurePrivateServer() | initializeFromLicense(serverUrl) | diff --git a/package-lock.json b/package-lock.json index f7f0273..9cd54b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@xuqm/rn-sdk", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@xuqm/rn-sdk", - "version": "0.2.0", + "version": "0.3.0", "license": "UNLICENSED", "workspaces": [ "packages/*" @@ -14,6 +14,7 @@ "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" @@ -722,6 +723,10 @@ "resolved": "packages/im", "link": true }, + "node_modules/@xuqm/rn-license": { + "resolved": "packages/license", + "link": true + }, "node_modules/@xuqm/rn-push": { "resolved": "packages/push", "link": true @@ -3479,7 +3484,7 @@ }, "packages/common": { "name": "@xuqm/rn-common", - "version": "0.2.3", + "version": "0.3.2", "license": "UNLICENSED", "devDependencies": { "@react-native-async-storage/async-storage": "^2.1.2", @@ -3493,7 +3498,7 @@ }, "packages/im": { "name": "@xuqm/rn-im", - "version": "0.2.1", + "version": "0.2.2", "license": "UNLICENSED", "dependencies": { "@xuqm/rn-common": ">=0.2.2" @@ -3507,9 +3512,26 @@ "react-native": ">=0.76.0" } }, + "packages/license": { + "name": "@xuqm/rn-license", + "version": "0.3.0", + "license": "UNLICENSED", + "dependencies": { + "@xuqm/rn-common": ">=0.2.2" + }, + "devDependencies": { + "@types/react-native": "^0.73.0", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": ">=1.21.0", + "react-native": ">=0.76.0", + "react-native-quick-crypto": ">=0.7.0" + } + }, "packages/push": { "name": "@xuqm/rn-push", - "version": "0.2.1", + "version": "0.2.2", "license": "UNLICENSED", "dependencies": { "@xuqm/rn-common": ">=0.2.2" @@ -3524,10 +3546,10 @@ }, "packages/update": { "name": "@xuqm/rn-update", - "version": "0.2.3", + "version": "0.3.0", "license": "UNLICENSED", "dependencies": { - "@xuqm/rn-common": ">=0.2.1" + "@xuqm/rn-common": ">=0.2.2" }, "devDependencies": { "@types/react-native": "^0.73.0", @@ -3540,7 +3562,7 @@ }, "packages/xwebview": { "name": "@xuqm/rn-xwebview", - "version": "0.2.1", + "version": "0.2.2", "license": "UNLICENSED", "dependencies": { "@xuqm/rn-common": ">=0.2.2", diff --git a/package.json b/package.json index 3256c8b..0d35f96 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,18 @@ { "name": "@xuqm/rn-sdk", - "version": "0.2.2", + "version": "0.3.0", "description": "XuqmGroup React Native SDK — meta-package (IM, Push, Update, Common)", "license": "UNLICENSED", "main": "src/index.ts", "react-native": "src/index.ts", "types": "src/index.ts", "private": true, - "files": ["src"], - "workspaces": ["packages/*"], + "files": [ + "src" + ], + "workspaces": [ + "packages/*" + ], "publishConfig": { "registry": "https://nexus.xuqinmin.com/repository/npm-hosted/" }, diff --git a/packages/common/package.json b/packages/common/package.json index 7270a11..925dad8 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@xuqm/rn-common", - "version": "0.2.2", + "version": "0.3.2", "description": "XuqmGroup RN SDK — core: init, network, token management", "license": "UNLICENSED", "main": "src/index.ts", @@ -10,7 +10,9 @@ "publishConfig": { "registry": "https://nexus.xuqinmin.com/repository/npm-hosted/" }, - "scripts": { "typecheck": "tsc --noEmit" }, + "scripts": { + "typecheck": "tsc --noEmit" + }, "peerDependencies": { "react-native": ">=0.76.0", "@react-native-async-storage/async-storage": ">=1.21.0" diff --git a/packages/common/src/autoInit.ts b/packages/common/src/autoInit.ts new file mode 100644 index 0000000..4fab9ee --- /dev/null +++ b/packages/common/src/autoInit.ts @@ -0,0 +1,46 @@ +/** + * SDK 自动初始化模块。 + * + * 对齐 Android SDK 的 ContentProvider 模式。 + * + * React Native 没有 ContentProvider,所以用 Metro moduleNameMapper 桥接: + * 1. 宿主把加密配置放到 src/assets/xuqm/config.xuqmconfig.ts + * 2. 宿主 babel.config.js 添加 alias: '@xuqm/autoinit-config' → 该文件 + * 3. 本模块 tryRequire('@xuqm/autoinit-config') 自动读取并初始化 + * 4. 宿主代码零初始化调用 + * + * 如果 alias 未配置(require 失败),静默跳过,不崩溃。 + */ + +import {isInitialized} from './config' +import {XuqmSDK} from './sdk' + +let _autoInitAttempted = false + +function tryRequireConfig(): string | null { + try { + // Metro moduleNameMapper 将此路径映射到宿主的配置文件 + // eslint-disable-next-line @typescript-eslint/no-require-imports + const mod = require('@xuqm/autoinit-config') as {ENCRYPTED_CONFIG?: string; default?: string} + return mod?.ENCRYPTED_CONFIG ?? mod?.default ?? null + } catch { + return null + } +} + +/** + * 尝试自动初始化。幂等,只执行一次。 + * 失败时静默跳过(对齐 Android SDK 的 runCatching + Log.w 行为)。 + */ +export function tryAutoInit(): void { + if (_autoInitAttempted) return + _autoInitAttempted = true + if (isInitialized()) return + + const encrypted = tryRequireConfig() + if (!encrypted) return + + XuqmSDK.initWithConfigFile(encrypted, {debug: __DEV__}).catch(() => { + // 静默降级 + }) +} diff --git a/packages/common/src/config.ts b/packages/common/src/config.ts index 3be5091..f282717 100644 --- a/packages/common/src/config.ts +++ b/packages/common/src/config.ts @@ -14,6 +14,15 @@ export interface XuqmConfig { let _config: XuqmConfig | null = null let _userId: string | null = null +export interface XuqmUserInfo { + userId?: string + name?: string + email?: string + phone?: string +} + +let _userInfo: XuqmUserInfo | null = null + export function initConfigFromRemote( options: XuqmInitOptions, remote: { imWsUrl: string; fileServiceUrl: string; apiUrl: string }, @@ -43,3 +52,15 @@ export function getUserId(): string | null { export function isInitialized(): boolean { return _config !== null } + +export function setUserInfo(info: XuqmUserInfo | null): void { + _userInfo = info + // Sync userId for backward compatibility + if (info?.userId) { + _userId = info.userId + } +} + +export function getUserInfo(): XuqmUserInfo | null { + return _userInfo +} diff --git a/packages/common/src/configCrypto.ts b/packages/common/src/configCrypto.ts new file mode 100644 index 0000000..07b96be --- /dev/null +++ b/packages/common/src/configCrypto.ts @@ -0,0 +1,83 @@ +/** + * 内置配置文件解密。 + * + * 支持格式: + * XUQM-LICENSE-V1.{salt}.{iv}.{ciphertext} + * XUQM-CONFIG-V1.{salt}.{iv}.{ciphertext} + * + * 使用 react-native-quick-crypto 的 SubtleCrypto(peer dependency)。 + */ + +const MAGIC_LICENSE = 'XUQM-LICENSE-V1' +const MAGIC_CONFIG = 'XUQM-CONFIG-V1' +const PASSPHRASE_CONFIG = 'xuqm-config-file-v1.2026.internal' +const PASSPHRASE_LICENSE = 'xuqm-license-file-v1.2026.internal' +const PBKDF2_ITERATIONS = 120_000 + +function getPassphrase(magic: string): string { + return magic === MAGIC_CONFIG ? PASSPHRASE_CONFIG : PASSPHRASE_LICENSE +} + +export interface DecryptedConfig { + appKey: string + appName?: string + companyName?: string + baseUrl?: string + serverUrl?: string + issuedAt?: string + expiresAt?: string +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function getSubtle(): any { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const qc = require('react-native-quick-crypto') as any + const subtle = qc.subtle ?? qc.default?.subtle + if (!subtle) throw new Error('[XuqmSDK] react-native-quick-crypto not available') + return subtle +} + +function base64UrlDecode(s: string): Uint8Array { + const padded = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (s.length % 4)) % 4) + const binary = atob(padded) + return Uint8Array.from({ length: binary.length }, (_, i) => binary.charCodeAt(i)) +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function deriveKey(salt: Uint8Array, passphrase: string): Promise { + const subtle = getSubtle() + const passphraseKey = await subtle.importKey( + 'raw', + new TextEncoder().encode(passphrase), + { name: 'PBKDF2' }, + false, + ['deriveKey'], + ) + return subtle.deriveKey( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { name: 'PBKDF2', salt: salt as any, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, + passphraseKey, + { name: 'AES-GCM', length: 256 }, + false, + ['decrypt'], + ) +} + +export async function decryptConfigFile(content: string): Promise { + const parts = content.trim().split('.') + const magic = parts[0] + if (parts.length !== 4 || (magic !== MAGIC_CONFIG && magic !== MAGIC_LICENSE)) { + throw new Error('[XuqmSDK] Invalid config/license file format') + } + const salt = base64UrlDecode(parts[1]) + const iv = base64UrlDecode(parts[2]) + const ciphertext = base64UrlDecode(parts[3]) + + const passphrase = getPassphrase(magic) + const key = await deriveKey(salt, passphrase) + const subtle = getSubtle() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plainBuffer = await subtle.decrypt({ name: 'AES-GCM', iv } as any, key, ciphertext as any) + const json = new TextDecoder().decode(plainBuffer) + return JSON.parse(json) as DecryptedConfig +} diff --git a/packages/common/src/crypto-types.d.ts b/packages/common/src/crypto-types.d.ts new file mode 100644 index 0000000..2ecb954 --- /dev/null +++ b/packages/common/src/crypto-types.d.ts @@ -0,0 +1,42 @@ +/** + * Web Crypto API 类型声明(Hermes 引擎提供实现)。 + * + * TypeScript 默认 lib 不包含这些类型, + * 但 Hermes (React Native 0.71+) 在运行时提供 crypto.subtle。 + */ + +interface SubtleCrypto { + importKey( + format: string, + keyData: BufferSource, + algorithm: AlgorithmIdentifier, + extractable: boolean, + keyUsages: string[], + ): Promise + deriveKey( + algorithm: AlgorithmIdentifier, + baseKey: CryptoKey, + derivedKeyType: AlgorithmIdentifier, + extractable: boolean, + keyUsages: string[], + ): Promise + decrypt( + algorithm: AlgorithmIdentifier, + key: CryptoKey, + data: BufferSource, + ): Promise +} + +interface CryptoKey { + readonly type: string + readonly extractable: boolean + readonly algorithm: AlgorithmIdentifier + readonly usages: string[] +} + +interface Crypto { + readonly subtle: SubtleCrypto +} + +declare var crypto: Crypto +declare function atob(data: string): string diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index c806ed2..6e44901 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,5 +1,8 @@ +// 自动初始化(对齐 Android ContentProvider 模式) +import './autoInit' + export { XuqmSDK } from './sdk' -export type { XuqmInitOptions, XuqmConfig } from './config' +export type { XuqmInitOptions, XuqmConfig, XuqmUserInfo } from './config' export { getConfig, isInitialized, setUserId, getUserId } from './config' export { awaitInitialization } from './sdk' export { apiRequest, configureHttp, _getToken, _saveToken, _clearToken } from './http' diff --git a/packages/common/src/sdk.ts b/packages/common/src/sdk.ts index 124981d..481060f 100644 --- a/packages/common/src/sdk.ts +++ b/packages/common/src/sdk.ts @@ -1,6 +1,7 @@ -import { initConfigFromRemote, isInitialized, type XuqmInitOptions, setUserId as setCommonUserId, getUserId as getCommonUserId } from './config' +import { initConfigFromRemote, isInitialized, type XuqmInitOptions, type XuqmUserInfo, setUserId as setCommonUserId, getUserId as getCommonUserId, setUserInfo as setCommonUserInfo, getUserInfo as getCommonUserInfo } from './config' import { DEFAULT_IM_WS_URL, DEFAULT_TENANT_PLATFORM_URL } from './constants' import { configureHttp } from './http' +import { decryptConfigFile } from './configCrypto' let _initPromise: Promise | null = null let _initResolve: (() => void) | null = null @@ -76,7 +77,6 @@ export const XuqmSDK = { /** * Initialize from a decrypted license file object. - * Use @xuqm/rn-license's decryptLicenseFile() to decrypt the raw file content first. */ initializeFromLicense(file: { appKey: string; baseUrl?: string; serverUrl?: string }, options?: { debug?: boolean }): void { if (isInitialized()) return @@ -90,6 +90,26 @@ export const XuqmSDK = { markInitialized() }, + /** + * 从加密配置文件初始化 SDK。 + * + * 宿主只需传入 .xuqmconfig 文件的加密内容,SDK 自动解密并初始化。 + * 支持 XUQM-CONFIG-V1 和 XUQM-LICENSE-V1 两种格式。 + * + * @param encryptedContent 加密配置文件的完整内容 + * @param options.debug 是否开启调试日志 + * + * @example + * // common bundle 入口 + * import config from './assets/app.xuqmconfig' + * await XuqmSDK.initWithConfigFile(config) + */ + async initWithConfigFile(encryptedContent: string, options?: { debug?: boolean }): Promise { + if (isInitialized()) return + const file = await decryptConfigFile(encryptedContent) + this.initializeFromLicense(file, options) + }, + /** * Wait for initialization to complete. */ @@ -105,6 +125,18 @@ export const XuqmSDK = { getUserId(): string | null { return getCommonUserId() }, + + /** + * Set user info for gray release targeting and license verification. + * Call this after user login. + */ + setUserInfo(info: XuqmUserInfo | null): void { + setCommonUserInfo(info) + }, + + getUserInfo(): XuqmUserInfo | null { + return getCommonUserInfo() + }, } export async function awaitInitialization(): Promise { diff --git a/packages/license/package.json b/packages/license/package.json index 6a44da3..a04e1fa 100644 --- a/packages/license/package.json +++ b/packages/license/package.json @@ -1,6 +1,6 @@ { "name": "@xuqm/rn-license", - "version": "0.2.2", + "version": "0.3.0", "description": "XuqmGroup RN SDK — License module (device registration & verification)", "license": "UNLICENSED", "main": "src/index.ts", @@ -10,7 +10,9 @@ "publishConfig": { "registry": "https://nexus.xuqinmin.com/repository/npm-hosted/" }, - "scripts": { "typecheck": "tsc --noEmit" }, + "scripts": { + "typecheck": "tsc --noEmit" + }, "dependencies": { "@xuqm/rn-common": ">=0.2.2" }, diff --git a/packages/license/src/crypto.ts b/packages/license/src/crypto.ts index 35f615b..3d5c29d 100644 --- a/packages/license/src/crypto.ts +++ b/packages/license/src/crypto.ts @@ -1,6 +1,8 @@ import type { LicenseFile } from './models' -const MAGIC = 'XUQM-LICENSE-V1' +const MAGIC_LICENSE = 'XUQM-LICENSE-V1' +const MAGIC_CONFIG = 'XUQM-CONFIG-V1' +const SUPPORTED_MAGICS = [MAGIC_LICENSE, MAGIC_CONFIG] const PASSPHRASE = 'xuqm-license-file-v1.2026.internal' const PBKDF2_ITERATIONS = 120_000 @@ -31,7 +33,8 @@ async function deriveKey(salt: Uint8Array): Promise { ['deriveKey'], ) return subtle.deriveKey( - { name: 'PBKDF2', salt, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + { name: 'PBKDF2', salt: salt as any, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, passphraseKey, { name: 'AES-GCM', length: 256 }, false, @@ -39,12 +42,15 @@ async function deriveKey(salt: Uint8Array): Promise { ) } -// Decrypts license file content. Format: MAGIC.base64UrlSalt.base64UrlIV.base64UrlCiphertext +// Decrypts license/config file content. +// Supported formats: +// XUQM-LICENSE-V1.{salt}.{iv}.{ciphertext} +// XUQM-CONFIG-V1.{salt}.{iv}.{ciphertext} // Ciphertext includes the 16-byte GCM tag appended (same as Android JCE output). export async function decryptLicenseFile(content: string): Promise { const parts = content.trim().split('.') - if (parts.length !== 4 || parts[0] !== MAGIC) { - throw new Error('[XuqmLicense] Invalid license file format') + if (parts.length !== 4 || !SUPPORTED_MAGICS.includes(parts[0])) { + throw new Error('[XuqmLicense] Invalid license/config file format') } const salt = base64UrlDecode(parts[1]) const iv = base64UrlDecode(parts[2]) @@ -53,7 +59,11 @@ export async function decryptLicenseFile(content: string): Promise const key = await deriveKey(salt) const subtle = getSubtle() // Web Crypto AES-GCM decrypt expects ciphertext with tag appended — matches Android JCE output - const plainBuffer = await subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const plainBuffer = await subtle.decrypt({ name: 'AES-GCM', iv } as any, key, ciphertext as any) const json = new TextDecoder().decode(plainBuffer) return JSON.parse(json) as LicenseFile } + +/** Alias for decryptLicenseFile — makes intent clearer when reading .xuqmconfig files. */ +export const decryptConfigFile = decryptLicenseFile diff --git a/packages/license/src/index.ts b/packages/license/src/index.ts index b74b48a..9f0a744 100644 --- a/packages/license/src/index.ts +++ b/packages/license/src/index.ts @@ -1,2 +1,3 @@ export { initialize, initializeFromFile, checkLicense, getStatus, getDeviceId, clear } from './license' +export { decryptLicenseFile, decryptConfigFile } from './crypto' export type { LicenseFile, LicenseUserInfo, LicenseStatus, LicenseResult } from './models' diff --git a/packages/update/package.json b/packages/update/package.json index 3b076c6..c2d282f 100644 --- a/packages/update/package.json +++ b/packages/update/package.json @@ -1,6 +1,6 @@ { "name": "@xuqm/rn-update", - "version": "0.2.2", + "version": "0.3.0", "description": "XuqmGroup RN SDK — Update module (App update, RN plugin hot-update)", "license": "UNLICENSED", "main": "src/index.ts", @@ -10,7 +10,9 @@ "publishConfig": { "registry": "https://nexus.xuqinmin.com/repository/npm-hosted/" }, - "scripts": { "typecheck": "tsc --noEmit" }, + "scripts": { + "typecheck": "tsc --noEmit" + }, "dependencies": { "@xuqm/rn-common": ">=0.2.2" }, diff --git a/packages/update/scripts/create-plugin.mjs b/packages/update/scripts/create-plugin.mjs new file mode 100644 index 0000000..a348503 --- /dev/null +++ b/packages/update/scripts/create-plugin.mjs @@ -0,0 +1,427 @@ +#!/usr/bin/env node + +/** + * create-plugin.mjs — UpdateSDK 插件脚手架工具 + * + * 用法: + * node scripts/create-plugin.mjs + * npx @xuqm/rn-update create-plugin + * + * 功能: + * 1. 交互式输入插件参数(moduleId / title / subtitle / accentColor) + * 2. 校验 moduleId 唯一性 + * 3. 自动生成完整插件骨架(bundle.ts / Screen / plugin.json) + * 4. 自动注册到宿主(pluginCatalog / debugPlugins / build scripts / metro config) + */ + +import fs from 'fs'; +import path from 'path'; +import readline from 'readline'; + +// ─── 交互工具 ──────────────────────────────────────────────────────────────── + +function createRL() { + return readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); +} + +function ask(rl, question) { + return new Promise(resolve => { + rl.question(question, answer => resolve(answer.trim())); + }); +} + +// ─── 项目路径检测 ──────────────────────────────────────────────────────────── + +function findProjectRoot() { + let dir = process.cwd(); + while (dir !== path.dirname(dir)) { + if (fs.existsSync(path.join(dir, 'package.json'))) return dir; + dir = path.dirname(dir); + } + console.error('❌ 未找到 package.json,请在 RN 项目根目录下运行此脚本。'); + process.exit(1); +} + +// ─── 唯一性校验 ────────────────────────────────────────────────────────────── + +function getExistingPluginIds(root) { + const ids = new Set(); + + // 从 pluginCatalog.ts 读取 + const catalogPath = path.join(root, 'src/app/pluginCatalog.ts'); + if (fs.existsSync(catalogPath)) { + const content = fs.readFileSync(catalogPath, 'utf-8'); + const matches = content.matchAll(/id:\s*'([^']+)'/g); + for (const m of matches) ids.add(m[1]); + } + + // 从 plugins 目录读取 + const pluginsDir = path.join(root, 'src/plugins'); + if (fs.existsSync(pluginsDir)) { + for (const entry of fs.readdirSync(pluginsDir)) { + const full = path.join(pluginsDir, entry); + if (fs.statSync(full).isDirectory()) ids.add(entry); + } + } + + return ids; +} + +// ─── 文件生成 ──────────────────────────────────────────────────────────────── + +function pascalCase(str) { + return str + .replace(/[-_]+(\w)/g, (_, c) => c.toUpperCase()) + .replace(/^(\w)/, (_, c) => c.toUpperCase()); +} + +function generateBundleTs(moduleId, title, subtitle, accentColor) { + const screenName = `${pascalCase(moduleId)}Screen`; + return `import { UpdateSDK } from '@xuqm/rn-update'; +import { registerPluginFromBridge } from '@plugins/runtimeBridge'; +import { ${screenName} } from '@${moduleId}/${screenName}'; + +// 自动注册插件版本(bundle 加载时执行) +UpdateSDK.registerPlugin({ moduleId: '${moduleId}', version: '1.0.0' }); + +// 注册 UI 组件到宿主运行时 +registerPluginFromBridge({ + id: '${moduleId}', + title: '${title}', + subtitle: '${subtitle}', + accentColor: '${accentColor}', + Component: ${screenName}, +}); +`; +} + +function generateScreenTs(moduleId) { + const screenName = `${pascalCase(moduleId)}Screen`; + return `import { StyleSheet, Text, View } from 'react-native'; + +export function ${screenName}() { + return ( + + ${moduleId} + TODO: 实现${moduleId}功能 + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5F7FA' }, + title: { fontSize: 24, fontWeight: '700', color: '#17171A' }, + subtitle: { fontSize: 14, color: '#666666', marginTop: 8 }, +}); +`; +} + +function generatePluginJson(moduleId) { + return JSON.stringify({ moduleId, version: '1.0.0' }, null, 2) + '\n'; +} + +// ─── 宿主文件更新 ──────────────────────────────────────────────────────────── + +function updatePluginCatalog(root, moduleId, title, accentColor) { + const filePath = path.join(root, 'src/app/pluginCatalog.ts'); + if (!fs.existsSync(filePath)) { + console.warn('⚠️ src/app/pluginCatalog.ts 不存在,跳过'); + return; + } + + let content = fs.readFileSync(filePath, 'utf-8'); + + // 检查是否已存在 + if (content.includes(`id: '${moduleId}'`)) { + console.log(` ℹ️ pluginCatalog 已包含 ${moduleId},跳过`); + return; + } + + const newEntry = ` { + id: '${moduleId}', + title: '${title}', + summary: '${title}业务模块', + description: '点击后异步加载 ${moduleId} bundle。', + accentColor: '${accentColor}', + },`; + + // 在最后一个 ] 之前插入 + const lastBracket = content.lastIndexOf('];'); + if (lastBracket === -1) { + console.warn('⚠️ pluginCatalog.ts 格式异常,跳过'); + return; + } + + content = + content.slice(0, lastBracket) + newEntry + '\n' + content.slice(lastBracket); + fs.writeFileSync(filePath, content, 'utf-8'); +} + +function updateDebugPlugins(root, moduleId) { + const filePath = path.join(root, 'src/bootstrap/debugPlugins.ts'); + if (!fs.existsSync(filePath)) { + console.warn('⚠️ src/bootstrap/debugPlugins.ts 不存在,跳过'); + return; + } + + let content = fs.readFileSync(filePath, 'utf-8'); + + if (content.includes(`'${moduleId}'`)) { + console.log(` ℹ️ debugPlugins 已包含 ${moduleId},跳过`); + return; + } + + const newLoader = ` +registerDebugPluginLoader('${moduleId}', async () => { + require('../plugins/${moduleId}/bundle'); +});`; + + content = content.trimEnd() + newLoader + '\n'; + fs.writeFileSync(filePath, content, 'utf-8'); +} + +function updatePackageJson(root, moduleId) { + const filePath = path.join(root, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(filePath, 'utf-8')); + + const androidKey = `build:android:${moduleId}`; + const iosKey = `build:ios:${moduleId}`; + + if (pkg.scripts[androidKey]) { + console.log(` ℹ️ package.json 已包含 ${androidKey},跳过`); + return; + } + + // 添加构建脚本 + pkg.scripts[androidKey] = + `mkdir -p bundle/android/${moduleId} && react-native bundle --platform android --dev false --entry-file ./src/plugins/${moduleId}/bundle.ts --bundle-output ./bundle/android/${moduleId}/${moduleId}.android.bundle --assets-dest ./bundle/android/${moduleId} --config metro.split.config.js --reset-cache`; + pkg.scripts[iosKey] = + `mkdir -p bundle/ios/${moduleId} && react-native bundle --platform ios --dev false --entry-file ./src/plugins/${moduleId}/bundle.ts --bundle-output ./bundle/ios/${moduleId}/${moduleId}.ios.bundle --assets-dest ./bundle/ios/${moduleId} --config metro.split.config.js --reset-cache`; + + // 更新 plugins 聚合脚本 + for (const platform of ['android', 'ios']) { + const key = `build:${platform}:plugins`; + if (pkg.scripts[key] && !pkg.scripts[key].includes(`${platform}:${moduleId}`)) { + pkg.scripts[key] = pkg.scripts[key] + ` && yarn build:${platform}:${moduleId}`; + } + } + + // 更新 embedded 脚本 + for (const platform of ['android', 'ios']) { + const key = `build:${platform}:embedded`; + if (pkg.scripts[key] && !pkg.scripts[key].includes(`${platform}:${moduleId}`)) { + // 在 prepare-embedded-bundles 之前插入 + pkg.scripts[key] = pkg.scripts[key].replace( + / && node \.\/scripts\/prepare/, + ` && yarn build:${platform}:${moduleId} && node ./scripts/prepare`, + ); + } + } + + fs.writeFileSync(filePath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8'); +} + +function updateMetroConfig(root, moduleId) { + const filePath = path.join(root, 'metro.split.config.js'); + if (!fs.existsSync(filePath)) { + console.warn('⚠️ metro.split.config.js 不存在,跳过'); + return; + } + + let content = fs.readFileSync(filePath, 'utf-8'); + + if (content.includes(`'${moduleId}'`)) { + console.log(` ℹ️ metro.split.config 已包含 ${moduleId},跳过`); + return; + } + + // 计算 offset:buz1=11M, buz2=12M, buzN=(10+N)M + const num = parseInt(moduleId.replace(/\D/g, ''), 10); + const offset = Number.isFinite(num) ? (10 + num) * 1_000_000 : 30_000_000; + + // 在 inferOffset 函数中添加新插件的分支 + const insertPoint = content.lastIndexOf('return MODULE_OFFSETS'); + if (insertPoint === -1) { + console.warn('⚠️ metro.split.config.js 格式异常,跳过'); + return; + } + + const newBranch = ` if (modulePath.includes(\`\${path.sep}${moduleId}\${path.sep}\`)) { + return ${offset}; + } + + `; + content = content.slice(0, insertPoint) + newBranch + content.slice(insertPoint); + fs.writeFileSync(filePath, content, 'utf-8'); +} + +function updateBabelAlias(root, moduleId) { + const filePath = path.join(root, 'babel.config.js'); + if (!fs.existsSync(filePath)) return; + + let content = fs.readFileSync(filePath, 'utf-8'); + if (content.includes(`'@${moduleId}'`)) { + console.log(` ℹ️ babel alias 已包含 @${moduleId},跳过`); + return; + } + + // 在 alias 对象最后一个条目后添加 + content = content.replace( + /('@utils':\s*'[^']+',?\s*)}/, + `$1'@${moduleId}': './src/plugins/${moduleId}',\n }}`, + ); + fs.writeFileSync(filePath, content, 'utf-8'); +} + +function updateTsconfig(root, moduleId) { + const filePath = path.join(root, 'tsconfig.json'); + if (!fs.existsSync(filePath)) return; + + let content = fs.readFileSync(filePath, 'utf-8'); + if (content.includes(`"@${moduleId}/*"`)) { + console.log(` ℹ️ tsconfig 已包含 @${moduleId},跳过`); + return; + } + + content = content.replace( + /("@utils\/\*":\s*\["src\/utils\/\*"\],?)/, + `$1\n "@${moduleId}/*": ["src/plugins/${moduleId}/*"]`, + ); + fs.writeFileSync(filePath, content, 'utf-8'); +} + +// ─── 主流程 ────────────────────────────────────────────────────────────────── + +function printUsage() { + console.log(''); + console.log('用法:'); + console.log(' 交互模式: node scripts/create-plugin.mjs'); + console.log(' 命令行: node scripts/create-plugin.mjs [title] [subtitle] [accentColor]'); + console.log(''); + console.log('示例:'); + console.log(' node scripts/create-plugin.mjs buz4 "IM 消息" "即时通讯业务组件" "#E74C3C"'); + console.log(' node scripts/create-plugin.mjs buz5'); + console.log(''); +} + +async function main() { + console.log(''); + console.log('╔══════════════════════════════════════════╗'); + console.log('║ XuqmGroup UpdateSDK — 插件脚手架工具 ║'); + console.log('╚══════════════════════════════════════════╝'); + + const root = findProjectRoot(); + const args = process.argv.slice(2); + + // ── 解析参数(支持 CLI 和交互两种模式)── + let moduleId, title, subtitle, accentColor; + + if (args.length > 0) { + // CLI 模式 + [moduleId, title, subtitle, accentColor] = args; + } else { + // 交互模式 + const rl = createRL(); + try { + moduleId = await ask(rl, '\n插件 ID(如 buz4): '); + title = await ask(rl, '插件标题(如 IM 消息): '); + subtitle = await ask(rl, '插件副标题(如 即时通讯业务组件): '); + accentColor = await ask(rl, '主题色(如 #0E84FA): '); + } finally { + rl.close(); + } + } + + // ── 校验 ── + if (!moduleId || !/^[a-z][a-z0-9]*$/.test(moduleId)) { + console.error('\n❌ moduleId 必须以小写字母开头,只包含小写字母和数字'); + printUsage(); + process.exit(1); + } + + title = title || moduleId; + subtitle = subtitle || `${title}业务组件`; + accentColor = accentColor || '#0E84FA'; + + // 唯一性校验 + const existing = getExistingPluginIds(root); + if (existing.has(moduleId)) { + console.error(`\n❌ moduleId "${moduleId}" 已存在,请使用其他 ID`); + process.exit(1); + } + + console.log(''); + console.log(`📦 创建插件: ${moduleId}`); + console.log(` 标题: ${title}`); + console.log(` 副标题: ${subtitle}`); + console.log(` 主题色: ${accentColor}`); + console.log(''); + + // ── 创建目录 ── + const pluginDir = path.join(root, 'src/plugins', moduleId); + fs.mkdirSync(pluginDir, { recursive: true }); + + // ── 生成文件 ── + const files = [ + { + path: path.join(pluginDir, 'bundle.ts'), + content: generateBundleTs(moduleId, title, subtitle, accentColor), + desc: 'bundle 入口(自动注册 UpdateSDK + pluginRuntime)', + }, + { + path: path.join(pluginDir, `${pascalCase(moduleId)}Screen.tsx`), + content: generateScreenTs(moduleId), + desc: 'Screen 骨架', + }, + { + path: path.join(pluginDir, 'plugin.json'), + content: generatePluginJson(moduleId), + desc: '插件元数据', + }, + ]; + + for (const file of files) { + fs.writeFileSync(file.path, file.content, 'utf-8'); + console.log(` ✅ ${path.relative(root, file.path)} — ${file.desc}`); + } + + // ── 更新宿主配置 ── + console.log(''); + console.log('🔗 更新宿主配置:'); + + updatePluginCatalog(root, moduleId, title, accentColor); + console.log(' ✅ pluginCatalog.ts'); + + updateDebugPlugins(root, moduleId); + console.log(' ✅ debugPlugins.ts'); + + updatePackageJson(root, moduleId); + console.log(' ✅ package.json(构建脚本)'); + + updateMetroConfig(root, moduleId); + console.log(' ✅ metro.split.config.js'); + + updateBabelAlias(root, moduleId); + console.log(' ✅ babel.config.js'); + + updateTsconfig(root, moduleId); + console.log(' ✅ tsconfig.json'); + + // ── 完成 ── + console.log(''); + console.log('═══════════════════════════════════════════'); + console.log(`✅ 插件 ${moduleId} 创建完成!`); + console.log(''); + console.log('下一步:'); + console.log(` 1. 编辑 src/plugins/${moduleId}/${pascalCase(moduleId)}Screen.tsx 实现业务 UI`); + console.log(` 2. 在 src/plugins/${moduleId}/ 下创建 services/、pages/ 等目录`); + console.log(` 3. yarn validate 确认无错误`); + console.log(` 4. yarn build:android:${moduleId} 构建 Android bundle`); + console.log('═══════════════════════════════════════════'); + console.log(''); +} + +main(); diff --git a/packages/update/src/UpdateSDK.ts b/packages/update/src/UpdateSDK.ts index a001644..1654db5 100644 --- a/packages/update/src/UpdateSDK.ts +++ b/packages/update/src/UpdateSDK.ts @@ -4,31 +4,71 @@ import { apiRequest, getConfig, getUserId } from '@xuqm/rn-common' import { getAppVersionCode, getAppVersionName, _devSetAppVersion } from './NativeVersion' import { awaitInitialization } from '@xuqm/rn-common' +// ─── Types ──────────────────────────────────────────────────────────────────── + +/** 插件注册元数据 */ export interface PluginMeta { + /** 插件唯一标识,如 'buz1'、'buz2' */ moduleId: string + /** 当前 bundle 版本号 */ version: string } +/** + * App 整包更新信息。 + * + * 对齐 Android SDK 的 UpdateInfo 模型。 + * SDK 只返回数据,UI 由 app 层自行处理。 + */ export interface AppUpdateInfo { + /** 是否需要更新 */ needsUpdate: boolean + /** 最新版本名,如 '2.1.0' */ versionName?: string + /** 最新版本号(整数) */ versionCode?: number + /** APK/安装包直接下载地址(Android 有此字段时优先下载) */ downloadUrl?: string + /** 更新日志 */ changeLog?: string + /** 是否强制更新(true 时不允许跳过) */ forceUpdate?: boolean + /** iOS App Store 地址 */ appStoreUrl?: string + /** Android 应用商店地址(华为/小米/OPPO 等) */ marketUrl?: string + /** 服务端要求登录后才能检查(true 时 needsUpdate 通常为 false) */ + requiresLogin?: boolean + /** SDK 内部标记:APK 是否已下载到本地(仅 Android) */ + alreadyDownloaded?: boolean + /** APK 文件 SHA-256 校验值 */ + apkHash?: string | null } -export interface RnUpdateInfo { +/** + * 插件(RN Bundle)更新信息。 + * + * 插件唯一标识 = appKey + platform + moduleId。 + * - appKey:应用标识(来自配置文件) + * - platform:ANDROID / IOS + * - moduleId:插件 ID(如 'buz1') + */ +export interface PluginUpdateInfo { + /** 是否需要更新 */ needsUpdate: boolean + /** 最新版本号 */ latestVersion: string + /** bundle 下载地址 */ downloadUrl: string + /** bundle 文件 MD5 */ md5: string + /** 要求的最低 common bundle 版本 */ minCommonVersion: string + /** 更新说明 */ note: string } +/** 已缓存的 bundle 元数据 */ export interface CachedRnBundle { moduleId: string version: string @@ -37,6 +77,8 @@ export interface CachedRnBundle { source: string } +// ─── Internal ───────────────────────────────────────────────────────────────── + const _pluginRegistry = new Map() function bundleCacheKey(moduleId: string) { @@ -59,32 +101,57 @@ function normalizeDownloadUrl(rawUrl?: string): string | undefined { return rawUrl } +// ─── UpdateSDK ──────────────────────────────────────────────────────────────── + export const UpdateSDK = { + + // ── 插件注册 ────────────────────────────────────────────────────────────── + /** - * Register a plugin's metadata. Call this at the top of the plugin's bundle entry file. + * 注册插件元数据。在插件 bundle 入口文件顶部调用。 + * + * 插件唯一标识 = moduleId(如 'buz1')。 + * 完整定位 = appKey(配置文件)+ platform(自动检测)+ moduleId。 * * @example - * // In your plugin's index.ts: - * import meta from './plugin.json' - * UpdateSDK.registerPlugin(meta) + * // src/plugins/buz1/bundle.ts + * UpdateSDK.registerPlugin({ moduleId: 'buz1', version: '1.0.0' }) */ registerPlugin(meta: PluginMeta): void { _pluginRegistry.set(meta.moduleId, meta) }, /** - * For dev/simulator environments where the native XuqmVersionModule is not linked. - * Do NOT call this in production — the native module provides the value automatically. + * 获取已注册的插件版本号。 */ - _devSetAppVersion(versionCode: number, versionName?: string): void { - _devSetAppVersion(versionCode, versionName) + getRegisteredPluginVersion(moduleId: string): string | undefined { + return _pluginRegistry.get(moduleId)?.version }, /** - * Check if there is a newer App version available. - * App version is read automatically from native code (XuqmVersionModule). + * 获取所有已注册的插件列表。 */ - async checkAppUpdate(): Promise { + getRegisteredPlugins(): PluginMeta[] { + return Array.from(_pluginRegistry.values()) + }, + + // ── App 整包更新 ────────────────────────────────────────────────────────── + + /** + * 检查 App 整包更新。 + * + * SDK 自动检测平台(iOS/Android)并传给服务端。 + * 服务端根据平台返回对应的版本信息和下载/商店地址。 + * + * 对齐 Android SDK UpdateSDK.checkAppUpdate(): + * - 返回完整更新信息,app 层自行决定 UI + * - Android:有 downloadUrl 时下载 APK;无 downloadUrl 时用 marketUrl 跳转商店 + * - iOS:用 appStoreUrl 跳转 App Store + * + * @param bypassIgnore false(默认)= 静默检查,跳过用户已忽略的版本 + * true = 用户主动检查,不跳过 + */ + async checkAppUpdate(bypassIgnore?: boolean): Promise { await awaitInitialization() const config = getConfig() const currentVersionCode = getAppVersionCode() @@ -94,9 +161,9 @@ export const UpdateSDK = { platform: Platform.OS === 'android' ? 'ANDROID' : 'IOS', currentVersionCode: String(currentVersionCode), } - if (userId) { - params.userId = userId - } + if (userId) params.userId = userId + if (bypassIgnore) params.bypassIgnore = 'true' + const result = await apiRequest('/api/v1/updates/app/check', { skipAuth: true, params, @@ -104,16 +171,29 @@ export const UpdateSDK = { return { ...result, downloadUrl: normalizeDownloadUrl(result.downloadUrl) } }, + /** + * 打开应用商店。 + * iOS 使用 appStoreUrl,Android 使用 marketUrl。 + */ async openStore(appStoreUrl?: string, marketUrl?: string): Promise { const url = Platform.OS === 'ios' ? appStoreUrl : marketUrl if (url) await Linking.openURL(url) }, + // ── 插件更新 ────────────────────────────────────────────────────────────── + /** - * Check if a newer RN bundle exists for the given plugin. - * The plugin must have been registered via registerPlugin() first. + * 检查指定插件的更新。 + * + * 插件唯一标识 = appKey + platform + moduleId。 + * 插件必须已通过 registerPlugin() 注册。 + * + * @param moduleId 插件 ID(如 'buz1') + * @returns 更新信息,包含 latestVersion / downloadUrl / md5 等 + * @throws 插件未注册时抛出错误 */ - async checkRnUpdate(moduleId: string): Promise { + async checkPluginUpdate(moduleId: string): Promise { + await awaitInitialization() const config = getConfig() const meta = _pluginRegistry.get(moduleId) if (!meta) { @@ -122,7 +202,7 @@ export const UpdateSDK = { 'Call UpdateSDK.registerPlugin({ moduleId, version }) at bundle load time.', ) } - const result = await apiRequest('/api/v1/rn/update/check', { + const result = await apiRequest('/api/v1/rn/update/check', { skipAuth: true, params: { appKey: config.appKey, @@ -134,13 +214,19 @@ export const UpdateSDK = { return { ...result, downloadUrl: normalizeDownloadUrl(result.downloadUrl) ?? result.downloadUrl } }, - async downloadRnBundle(downloadUrl: string): Promise { + /** + * 下载插件 bundle 源码文本。 + */ + async downloadPluginBundle(downloadUrl: string): Promise { const response = await fetch(downloadUrl) if (!response.ok) throw new Error(`[UpdateSDK] Bundle download failed: ${response.status}`) return response.text() }, - async cacheRnBundle(moduleId: string, version: string, md5: string, source: string): Promise { + /** + * 缓存插件 bundle 到本地(AsyncStorage)。 + */ + async cachePluginBundle(moduleId: string, version: string, md5: string, source: string): Promise { const payload: CachedRnBundle = { moduleId, version, md5, source, downloadedAt: new Date().toISOString(), @@ -149,17 +235,41 @@ export const UpdateSDK = { return payload }, - async getCachedRnBundle(moduleId: string): Promise { + /** + * 读取已缓存的插件 bundle。 + */ + async getCachedPluginBundle(moduleId: string): Promise { const raw = await AsyncStorage.getItem(bundleCacheKey(moduleId)) return raw ? (JSON.parse(raw) as CachedRnBundle) : null }, - /** Returns the currently running version of a registered plugin. */ - getRegisteredPluginVersion(moduleId: string): string | undefined { - return _pluginRegistry.get(moduleId)?.version + /** + * 检查并下载插件更新(一步完成)。 + * + * 如果有更新,下载 bundle 并缓存到本地。 + * 调用方需要在下次启动时通过 reloadPlugin() 加载新版本。 + * + * @returns 如果有更新,返回缓存的 bundle 信息;否则返回 null + */ + async checkAndCachePlugin(moduleId: string): Promise { + const info = await this.checkPluginUpdate(moduleId) + if (!info.needsUpdate) return null + + const source = await this.downloadPluginBundle(info.downloadUrl) + return this.cachePluginBundle(moduleId, info.latestVersion, info.md5, source) }, - /** Returns the current app versionCode (read from native). */ + // ── 版本信息 ────────────────────────────────────────────────────────────── + + /** 当前 App versionCode(原生读取) */ getAppVersionCode, + /** 当前 App versionName(原生读取) */ getAppVersionName, + + /** + * 开发环境手动设置版本号(仅调试用,生产环境不要调用)。 + */ + _devSetAppVersion(versionCode: number, versionName?: string): void { + _devSetAppVersion(versionCode, versionName) + }, } diff --git a/packages/update/src/index.ts b/packages/update/src/index.ts index 60c95e5..aa1106b 100644 --- a/packages/update/src/index.ts +++ b/packages/update/src/index.ts @@ -1,2 +1,2 @@ export { UpdateSDK } from './UpdateSDK' -export type { PluginMeta, AppUpdateInfo, RnUpdateInfo, CachedRnBundle } from './UpdateSDK' +export type { PluginMeta, AppUpdateInfo, PluginUpdateInfo, CachedRnBundle } from './UpdateSDK' diff --git a/src/index.ts b/src/index.ts index 7e15a90..4a53c62 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ export { XuqmSDK } from './sdk' export type { UnifiedLoginOptions } from './sdk' -export type { XuqmInitOptions, DeviceInfo } from '@xuqm/rn-common' +export type { XuqmInitOptions, XuqmUserInfo, DeviceInfo } from '@xuqm/rn-common' export { getDeviceId, getDeviceInfo, detectPushVendor, setUserId, getUserId } from '@xuqm/rn-common' export { ScaledImage } from '@xuqm/rn-common' export { apiRequest } from '@xuqm/rn-common' @@ -24,7 +24,9 @@ export type { export { PushSDK } from '@xuqm/rn-push' export type { PushVendor } from '@xuqm/rn-push' export { UpdateSDK } from '@xuqm/rn-update' -export type { PluginMeta, AppUpdateInfo, RnUpdateInfo, CachedRnBundle } 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, diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..866aa43 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1928 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.25.2": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.29.0", "@babel/generator@^7.29.1": + version "7.29.1" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + +"@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.2" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz" + integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/runtime@^7.25.0": + version "7.29.2" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz" + integrity sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== + +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@isaacs/ttlcache@^1.4.1": + version "1.4.1" + resolved "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz" + integrity sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA== + +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/source-map@^0.3.3": + version "0.3.11" + resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz" + integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@react-native-async-storage/async-storage@^1.21.0": + version "1.24.0" + resolved "https://nexus.xuqinmin.com/repository/npm/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz" + integrity sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g== + dependencies: + merge-options "^3.0.4" + +"@react-native-async-storage/async-storage@^2.1.2": + version "2.2.0" + resolved "https://nexus.xuqinmin.com/repository/npm/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz" + integrity sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw== + dependencies: + merge-options "^3.0.4" + +"@react-native/assets-registry@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.85.2.tgz" + integrity sha512-kauC/oPaxklU4Y+u9gBfCBJm51qX6WBZq4xx0USCdimtp+G8+554kpygfSWIjoqCJa2o06bWxBEjesiuCv+LzA== + +"@react-native/codegen@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.2.tgz" + integrity sha512-XCginmxh0//++EXVOEJHBVZxHla294FzLCFF6jXwAUjvXVhqyIKyxhABfz+r4OOmaiuWk4Rtd4arqdAzeHeprg== + dependencies: + "@babel/core" "^7.25.2" + "@babel/parser" "^7.29.0" + hermes-parser "0.33.3" + invariant "^2.2.4" + nullthrows "^1.1.1" + tinyglobby "^0.2.15" + yargs "^17.6.2" + +"@react-native/community-cli-plugin@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.85.2.tgz" + integrity sha512-3KLgSg1kHvBpr93zMaQhvfYTgnCw7yZRED+3J4dMcYjfSjtD0Wf8SofU6uBmAw9JaVYvP43lpdwUpI4p0+ABsg== + dependencies: + "@react-native/dev-middleware" "0.85.2" + debug "^4.4.0" + invariant "^2.2.4" + metro "^0.84.0" + metro-config "^0.84.0" + metro-core "^0.84.0" + semver "^7.1.3" + +"@react-native/debugger-frontend@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.85.2.tgz" + integrity sha512-j+0b9H5f5hGTLQxHIhJU/b/W6ijuxJF+ZTLHB0se2kzUBNxFKd7DkIc6753qk3CJdiv55vxG3XDgmlpbHxOpmA== + +"@react-native/debugger-shell@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.2.tgz" + integrity sha512-r5BkhqPMfg3LmaZS5zadHmBNVH5h4bhSpv4BEPGfK4gat9HABAMzUzybi+2wpgU3SoHxnyKGdExEJvoqVcjeRg== + dependencies: + cross-spawn "^7.0.6" + debug "^4.4.0" + fb-dotslash "0.5.8" + +"@react-native/dev-middleware@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.85.2.tgz" + integrity sha512-3J+NaDUg+QEfDeLAUzgaWhpaxEg78g+KwbydlDCewh2G6WnHpsty8XooruxNHzyAsqVWywZMrzmbn78Ctc1O9Q== + dependencies: + "@isaacs/ttlcache" "^1.4.1" + "@react-native/debugger-frontend" "0.85.2" + "@react-native/debugger-shell" "0.85.2" + chrome-launcher "^0.15.2" + chromium-edge-launcher "^0.3.0" + connect "^3.6.5" + debug "^4.4.0" + invariant "^2.2.4" + nullthrows "^1.1.1" + open "^7.0.3" + serve-static "^1.16.2" + ws "^7.5.10" + +"@react-native/gradle-plugin@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.85.2.tgz" + integrity sha512-YXBOLeAqFrv7XwUeBPTKZeOV1FIxn4AW7UAEitScf3ibC8bu8+6NpJu4HWgbNQHg7vDbbTZVbcOl8EwGxsSq2w== + +"@react-native/js-polyfills@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.2.tgz" + integrity sha512-esGEAmKVM40DV/yVmNljCKZTIeUo7qXqc+Hwffkv3TG+b3E24xyFovHrbP98gGxZr2ZsEyx+2sKLdXF5asY5nw== + +"@react-native/normalize-colors@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.85.2.tgz" + integrity sha512-svuOLtjbFGXDdHsriHXuND5FgHg7XlkOXCbH/8+X4t76YLH6qSTffSIQQrKLDL5mn4EFU+Oh/PNO0/FfpnTOTg== + +"@react-native/virtualized-lists@0.85.2": + version "0.85.2" + resolved "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.2.tgz" + integrity sha512-wmVKpAlcr+UB0L5SpbrV865EdleUP7I5+X+48e1aRsQK8q+wsTRBXeUwWVip/1l+HZwlZFeO8iOILJ16VRu0Cw== + dependencies: + invariant "^2.2.4" + nullthrows "^1.1.1" + +"@react-navigation/core@^7.17.4": + version "7.17.4" + resolved "https://nexus.xuqinmin.com/repository/npm/@react-navigation/core/-/core-7.17.4.tgz" + integrity sha512-Rv9E2oNNQEkPGpmu9q+vJwGJRSQR6LBg5L+Yo1QHjtwGbHUbjkIKOdYymDZoZYgNzX2OD4rAIlfuzbDKa3cCeA== + dependencies: + "@react-navigation/routers" "^7.5.5" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + query-string "^7.1.3" + react-is "^19.1.0" + use-latest-callback "^0.2.4" + use-sync-external-store "^1.5.0" + +"@react-navigation/native@^7.0.0": + version "7.2.4" + resolved "https://nexus.xuqinmin.com/repository/npm/@react-navigation/native/-/native-7.2.4.tgz" + integrity sha512-eWC2D3JjhYLId2fVTZhhCiUpWIaPhO9XyEb7Wq8ElmOHyIODlbOzgZ0rKia02OIsDKr9BzZl2sK1dL70yMxDaw== + dependencies: + "@react-navigation/core" "^7.17.4" + escape-string-regexp "^4.0.0" + fast-deep-equal "^3.1.3" + nanoid "^3.3.11" + use-latest-callback "^0.2.4" + +"@react-navigation/routers@^7.5.5": + version "7.5.5" + resolved "https://nexus.xuqinmin.com/repository/npm/@react-navigation/routers/-/routers-7.5.5.tgz" + integrity sha512-9/hhMte12Kgu+pMnLfA4EWJ0OQmIEAMVMX06FPH2yGkEQSQ3JhhCN/GkcRikzQhtEi97VYYQA15umptBUShcOQ== + dependencies: + nanoid "^3.3.11" + +"@sinclair/typebox@^0.27.8": + version "0.27.10" + resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz" + integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.6" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz" + integrity sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w== + +"@types/istanbul-lib-report@*": + version "3.0.3" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz" + integrity sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz" + integrity sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/node@*": + version "25.6.0" + resolved "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz" + integrity sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ== + dependencies: + undici-types "~7.19.0" + +"@types/react-native@^0.73.0": + version "0.73.0" + resolved "https://registry.npmjs.org/@types/react-native/-/react-native-0.73.0.tgz" + integrity sha512-6ZRPQrYM72qYKGWidEttRe6M5DZBEV5F+MHMHqd4TTYx0tfkcdrUFGdef6CCxY0jXU7wldvd/zA/b0A/kTeJmA== + dependencies: + react-native "*" + +"@types/react@^19.0.0": + version "19.2.14" + resolved "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz" + integrity sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== + dependencies: + csstype "^3.2.2" + +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.8": + version "17.0.35" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== + 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" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + +accepts@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz" + integrity sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng== + dependencies: + mime-types "^3.0.0" + negotiator "^1.0.0" + +acorn@^8.15.0: + version "8.16.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + +agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + +anser@^1.4.9: + version "1.4.10" + resolved "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz" + integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== + +ansi-regex@^5.0.0, ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +asap@~2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" + integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== + +babel-plugin-syntax-hermes-parser@0.33.3: + version "0.33.3" + resolved "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz" + integrity sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA== + dependencies: + hermes-parser "0.33.3" + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://nexus.xuqinmin.com/repository/npm/balanced-match/-/balanced-match-4.0.4.tgz" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +base-64@0.1.0: + version "0.1.0" + resolved "https://nexus.xuqinmin.com/repository/npm/base-64/-/base-64-0.1.0.tgz" + integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA== + +base64-js@^1.5.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +baseline-browser-mapping@^2.10.12: + version "2.10.21" + resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz" + integrity sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA== + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://nexus.xuqinmin.com/repository/npm/boolbase/-/boolbase-1.0.0.tgz" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^5.0.5: + version "5.0.6" + resolved "https://nexus.xuqinmin.com/repository/npm/brace-expansion/-/brace-expansion-5.0.6.tgz" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== + dependencies: + balanced-match "^4.0.2" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.24.0: + version "4.28.2" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + dependencies: + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001782: + version "1.0.30001790" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz" + integrity sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chrome-launcher@^0.15.2: + version "0.15.2" + resolved "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz" + integrity sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + +chromium-edge-launcher@^0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz" + integrity sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA== + dependencies: + "@types/node" "*" + escape-string-regexp "^4.0.0" + is-wsl "^2.2.0" + lighthouse-logger "^1.0.0" + mkdirp "^1.0.4" + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +commander@^12.0.0: + version "12.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +connect@^3.6.5: + version "3.7.0" + resolved "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz" + integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== + dependencies: + debug "2.6.9" + finalhandler "1.1.2" + parseurl "~1.3.3" + utils-merge "1.0.1" + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-select@^5.1.0: + version "5.2.2" + resolved "https://nexus.xuqinmin.com/repository/npm/css-select/-/css-select-5.2.2.tgz" + integrity sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.3: + version "1.1.3" + resolved "https://nexus.xuqinmin.com/repository/npm/css-tree/-/css-tree-1.1.3.tgz" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.1.0: + version "6.2.2" + resolved "https://nexus.xuqinmin.com/repository/npm/css-what/-/css-what-6.2.2.tgz" + integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== + +csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +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: + 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" + +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: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://nexus.xuqinmin.com/repository/npm/dom-serializer/-/dom-serializer-2.0.0.tgz" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://nexus.xuqinmin.com/repository/npm/domelementtype/-/domelementtype-2.3.0.tgz" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://nexus.xuqinmin.com/repository/npm/domhandler/-/domhandler-5.0.3.tgz" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.2.2" + resolved "https://nexus.xuqinmin.com/repository/npm/domutils/-/domutils-3.2.2.tgz" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.5.328: + version "1.5.344" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz" + integrity sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +entities@^4.2.0: + version "4.5.0" + resolved "https://nexus.xuqinmin.com/repository/npm/entities/-/entities-4.5.0.tgz" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + +escalade@^3.1.1, escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + +exponential-backoff@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz" + integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://nexus.xuqinmin.com/repository/npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fb-dotslash@0.5.8: + version "0.5.8" + resolved "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz" + integrity sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA== + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://nexus.xuqinmin.com/repository/npm/filter-obj/-/filter-obj-1.1.0.tgz" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + +finalhandler@1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.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" + integrity sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw== + +fresh@~0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +gensync@^1.0.0-beta.2: + version "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: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob@13.0.1: + version "13.0.1" + resolved "https://nexus.xuqinmin.com/repository/npm/glob/-/glob-13.0.1.tgz" + integrity sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w== + dependencies: + minimatch "^10.1.2" + minipass "^7.1.2" + path-scurry "^2.0.0" + +graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hermes-compiler@250829098.0.10: + version "250829098.0.10" + resolved "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.10.tgz" + integrity sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w== + +hermes-estree@0.33.3: + version "0.33.3" + resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz" + integrity sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg== + +hermes-estree@0.35.0: + version "0.35.0" + resolved "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz" + integrity sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg== + +hermes-parser@0.33.3: + version "0.33.3" + resolved "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz" + integrity sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA== + dependencies: + hermes-estree "0.33.3" + +hermes-parser@0.35.0: + version "0.35.0" + resolved "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz" + integrity sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA== + dependencies: + hermes-estree "0.35.0" + +http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + +https-proxy-agent@^7.0.5: + version "7.0.6" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +image-size@^1.0.2: + version "1.2.1" + resolved "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz" + integrity sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw== + dependencies: + queue "6.0.2" + +inherits@~2.0.3, inherits@~2.0.4: + version "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: + version "2.2.4" + resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-docker@^2.0.0: + version "2.2.1" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://nexus.xuqinmin.com/repository/npm/is-plain-obj/-/is-plain-obj-2.1.0.tgz" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== + +jest-util@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz" + integrity sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw== + dependencies: + "@jest/types" "^29.6.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^29.6.3" + leven "^3.1.0" + pretty-format "^29.7.0" + +jest-worker@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz" + integrity sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw== + dependencies: + "@types/node" "*" + jest-util "^29.7.0" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsc-safe-url@^0.2.2: + version "0.2.4" + resolved "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz" + integrity sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +lighthouse-logger@^1.0.0: + version "1.4.2" + resolved "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz" + integrity sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g== + dependencies: + debug "^2.6.9" + marky "^1.2.2" + +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: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lru-cache@^11.0.0: + version "11.3.6" + resolved "https://nexus.xuqinmin.com/repository/npm/lru-cache/-/lru-cache-11.3.6.tgz" + integrity sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +marky@^1.2.2: + version "1.3.0" + resolved "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz" + integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ== + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://nexus.xuqinmin.com/repository/npm/mdn-data/-/mdn-data-2.0.14.tgz" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +memoize-one@^5.0.0: + version "5.2.1" + resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + +merge-options@^3.0.4: + version "3.0.4" + resolved "https://nexus.xuqinmin.com/repository/npm/merge-options/-/merge-options-3.0.4.tgz" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +metro-babel-transformer@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.3.tgz" + integrity sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w== + dependencies: + "@babel/core" "^7.25.2" + flow-enums-runtime "^0.0.6" + hermes-parser "0.35.0" + metro-cache-key "0.84.3" + nullthrows "^1.1.1" + +metro-cache-key@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.3.tgz" + integrity sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-cache@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.3.tgz" + integrity sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA== + dependencies: + exponential-backoff "^3.1.1" + flow-enums-runtime "^0.0.6" + https-proxy-agent "^7.0.5" + metro-core "0.84.3" + +metro-config@^0.84.0, metro-config@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz" + integrity sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew== + dependencies: + connect "^3.6.5" + flow-enums-runtime "^0.0.6" + jest-validate "^29.7.0" + metro "0.84.3" + metro-cache "0.84.3" + metro-core "0.84.3" + metro-runtime "0.84.3" + yaml "^2.6.1" + +metro-core@^0.84.0, metro-core@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz" + integrity sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw== + dependencies: + flow-enums-runtime "^0.0.6" + lodash.throttle "^4.1.1" + metro-resolver "0.84.3" + +metro-file-map@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.3.tgz" + integrity sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA== + dependencies: + debug "^4.4.0" + fb-watchman "^2.0.0" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + invariant "^2.2.4" + jest-worker "^29.7.0" + micromatch "^4.0.4" + nullthrows "^1.1.1" + walker "^1.0.7" + +metro-minify-terser@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.3.tgz" + integrity sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ== + dependencies: + flow-enums-runtime "^0.0.6" + terser "^5.15.0" + +metro-resolver@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.3.tgz" + integrity sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA== + dependencies: + flow-enums-runtime "^0.0.6" + +metro-runtime@^0.84.0, metro-runtime@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz" + integrity sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ== + dependencies: + "@babel/runtime" "^7.25.0" + flow-enums-runtime "^0.0.6" + +metro-source-map@^0.84.0, metro-source-map@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz" + integrity sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ== + dependencies: + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-symbolicate "0.84.3" + nullthrows "^1.1.1" + ob1 "0.84.3" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-symbolicate@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz" + integrity sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g== + dependencies: + flow-enums-runtime "^0.0.6" + invariant "^2.2.4" + metro-source-map "0.84.3" + nullthrows "^1.1.1" + source-map "^0.5.6" + vlq "^1.0.0" + +metro-transform-plugins@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.3.tgz" + integrity sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + flow-enums-runtime "^0.0.6" + nullthrows "^1.1.1" + +metro-transform-worker@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.3.tgz" + integrity sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA== + dependencies: + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + flow-enums-runtime "^0.0.6" + metro "0.84.3" + metro-babel-transformer "0.84.3" + metro-cache "0.84.3" + metro-cache-key "0.84.3" + metro-minify-terser "0.84.3" + metro-source-map "0.84.3" + metro-transform-plugins "0.84.3" + nullthrows "^1.1.1" + +metro@^0.84.0, metro@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz" + integrity sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/core" "^7.25.2" + "@babel/generator" "^7.29.1" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + accepts "^2.0.0" + chalk "^4.0.0" + ci-info "^2.0.0" + connect "^3.6.5" + debug "^4.4.0" + error-stack-parser "^2.0.6" + flow-enums-runtime "^0.0.6" + graceful-fs "^4.2.4" + hermes-parser "0.35.0" + image-size "^1.0.2" + invariant "^2.2.4" + jest-worker "^29.7.0" + jsc-safe-url "^0.2.2" + lodash.throttle "^4.1.1" + metro-babel-transformer "0.84.3" + metro-cache "0.84.3" + metro-cache-key "0.84.3" + metro-config "0.84.3" + metro-core "0.84.3" + metro-file-map "0.84.3" + metro-resolver "0.84.3" + metro-runtime "0.84.3" + metro-source-map "0.84.3" + metro-symbolicate "0.84.3" + metro-transform-plugins "0.84.3" + metro-transform-worker "0.84.3" + mime-types "^3.0.1" + nullthrows "^1.1.1" + serialize-error "^2.1.0" + source-map "^0.5.6" + throat "^5.0.0" + ws "^7.5.10" + yargs "^17.6.2" + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + +mime-types@^3.0.0, mime-types@^3.0.1: + version "3.0.2" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz" + integrity sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A== + dependencies: + mime-db "^1.54.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +minimatch@^10.1.2: + version "10.2.5" + resolved "https://nexus.xuqinmin.com/repository/npm/minimatch/-/minimatch-10.2.5.tgz" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +minipass@^7.1.2: + version "7.1.3" + resolved "https://nexus.xuqinmin.com/repository/npm/minipass/-/minipass-7.1.3.tgz" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +mkdirp@^1.0.4: + version "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== + +nanoid@^3.3.11: + version "3.3.12" + resolved "https://nexus.xuqinmin.com/repository/npm/nanoid/-/nanoid-3.3.12.tgz" + integrity sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.36: + version "2.0.38" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz" + integrity sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw== + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://nexus.xuqinmin.com/repository/npm/nth-check/-/nth-check-2.1.1.tgz" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + +ob1@0.84.3: + version "0.84.3" + resolved "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz" + integrity sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ== + dependencies: + flow-enums-runtime "^0.0.6" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" + integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== + dependencies: + ee-first "1.1.1" + +on-finished@~2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +open@^7.0.3: + version "7.4.2" + resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +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-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-scurry@^2.0.0: + version "2.0.2" + resolved "https://nexus.xuqinmin.com/repository/npm/path-scurry/-/path-scurry-2.0.2.tgz" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== + +picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +pretty-format@^29.7.0: + version "29.7.0" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" + integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== + dependencies: + "@jest/schemas" "^29.6.3" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +promise@^8.3.0: + version "8.3.0" + resolved "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz" + integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg== + dependencies: + asap "~2.0.6" + +query-string@^7.1.3: + version "7.1.3" + resolved "https://nexus.xuqinmin.com/repository/npm/query-string/-/query-string-7.1.3.tgz" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + +queue@6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz" + integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== + dependencies: + inherits "~2.0.3" + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +react-devtools-core@^6.1.5: + version "6.1.5" + resolved "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz" + integrity sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA== + dependencies: + shell-quote "^1.6.1" + ws "^7" + +react-is@^18.0.0: + version "18.3.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react-is@^19.1.0: + version "19.2.6" + resolved "https://nexus.xuqinmin.com/repository/npm/react-is/-/react-is-19.2.6.tgz" + integrity sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw== + +react-native-blob-util@^0.24.7: + version "0.24.7" + resolved "https://nexus.xuqinmin.com/repository/npm/react-native-blob-util/-/react-native-blob-util-0.24.7.tgz" + integrity sha512-3vgn3hblfJh0+LIoqEhYRqCtwKh1xID2LtXHdTrUml3rYh4xj69eN+lvWU235AL0FRbX5uKrS1c4lIYexSgtWQ== + dependencies: + base-64 "0.1.0" + glob "13.0.1" + +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" + integrity sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ== + +react-native-svg@^15.15.4: + version "15.15.5" + resolved "https://nexus.xuqinmin.com/repository/npm/react-native-svg/-/react-native-svg-15.15.5.tgz" + integrity sha512-L4go5jA+GWutdJ/JucuN20cjAbMg1HmMtAP+wZ+3JLCf6Jd0bhXQHxciRP/AQm/FlrIEZwkMcHNZP+FXAiic0w== + dependencies: + css-select "^5.1.0" + css-tree "^1.1.3" + +react-native-webview@^13.16.1: + version "13.16.1" + resolved "https://nexus.xuqinmin.com/repository/npm/react-native-webview/-/react-native-webview-13.16.1.tgz" + integrity sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw== + dependencies: + escape-string-regexp "^4.0.0" + invariant "2.2.4" + +react-native@*: + version "0.85.2" + resolved "https://registry.npmjs.org/react-native/-/react-native-0.85.2.tgz" + integrity sha512-GFWEPwLYirfj5X8gMtXOWtqX0cqUEURRHETZfFk37VCa4++izrKvGvv24anvuyulXV87NAhVkfNw93rLg3HByw== + dependencies: + "@react-native/assets-registry" "0.85.2" + "@react-native/codegen" "0.85.2" + "@react-native/community-cli-plugin" "0.85.2" + "@react-native/gradle-plugin" "0.85.2" + "@react-native/js-polyfills" "0.85.2" + "@react-native/normalize-colors" "0.85.2" + "@react-native/virtualized-lists" "0.85.2" + abort-controller "^3.0.0" + anser "^1.4.9" + ansi-regex "^5.0.0" + babel-plugin-syntax-hermes-parser "0.33.3" + base64-js "^1.5.1" + commander "^12.0.0" + flow-enums-runtime "^0.0.6" + hermes-compiler "250829098.0.10" + invariant "^2.2.4" + memoize-one "^5.0.0" + metro-runtime "^0.84.0" + metro-source-map "^0.84.0" + nullthrows "^1.1.1" + pretty-format "^29.7.0" + promise "^8.3.0" + react-devtools-core "^6.1.5" + react-refresh "^0.14.0" + regenerator-runtime "^0.13.2" + scheduler "0.27.0" + semver "^7.1.3" + stacktrace-parser "^0.1.10" + tinyglobby "^0.2.15" + whatwg-fetch "^3.0.0" + ws "^7.5.10" + yargs "^17.6.2" + +react-refresh@^0.14.0: + version "0.14.2" + resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz" + integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== + +regenerator-runtime@^0.13.2: + version "0.13.11" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + +require-directory@^2.1.1: + version "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== + +scheduler@0.27.0: + version "0.27.0" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.1.3: + version "7.7.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +send@~0.19.1: + version "0.19.2" + resolved "https://registry.npmjs.org/send/-/send-0.19.2.tgz" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "~0.5.2" + http-errors "~2.0.1" + mime "1.6.0" + ms "2.1.3" + on-finished "~2.4.1" + range-parser "~1.2.1" + statuses "~2.0.2" + +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz" + integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== + +serve-static@^1.16.2: + version "1.16.3" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "~0.19.1" + +setprototypeof@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1: + version "1.8.3" + resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz" + integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0: + 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" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +stacktrace-parser@^0.1.10: + version "0.1.11" + resolved "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz" + integrity sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg== + dependencies: + type-fest "^0.7.1" + +statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://nexus.xuqinmin.com/repository/npm/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +terser@^5.15.0: + version "5.46.1" + resolved "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz" + integrity sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.15.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + +tinyglobby@^0.2.15: + version "0.2.16" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@~1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +type-fest@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz" + integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== + +typescript@^5.9.3: + version "5.9.3" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +undici-types@~7.19.0: + version "7.19.2" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz" + integrity sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg== + +unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +use-latest-callback@^0.2.4: + version "0.2.6" + resolved "https://nexus.xuqinmin.com/repository/npm/use-latest-callback/-/use-latest-callback-0.2.6.tgz" + integrity sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg== + +use-sync-external-store@^1.5.0: + version "1.6.0" + resolved "https://nexus.xuqinmin.com/repository/npm/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +vlq@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz" + integrity sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w== + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +whatwg-fetch@^3.0.0: + version "3.6.20" + resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +ws@^7, ws@^7.5.10: + version "7.5.10" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yaml@^2.6.1: + version "2.8.3" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz" + integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== + +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@^17.6.2: + version "17.7.2" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1"