docs(rn): rebuild public sdk documentation

这个提交包含在:
XuqmGroup 2026-07-29 02:43:17 +08:00
父节点 94e5ee0ec4
当前提交 eb2baae07e
共有 16 个文件被更改,包括 1601 次插入853 次删除

查看文件

@ -58,11 +58,15 @@ export default defineConfig({
],
'/rn/': [
{ text: '概览', link: '/rn/' },
{ text: '安装配置', link: '/rn/setup' },
{ text: '安装与自动配置', link: '/rn/setup' },
{ text: 'Common 基础能力', link: '/rn/common' },
{ text: '会话与隐私授权', link: '/rn/session' },
{ text: '版本与插件更新', link: '/rn/update' },
{ text: 'BugCollect', link: '/rn/bugcollect' },
{ text: 'XWebView', link: '/rn/xwebview' },
{ text: 'IM 接入', link: '/rn/im' },
{ text: '群聊', link: '/rn/group' },
{ text: '推送接入', link: '/rn/push' },
{ text: '版本管理', link: '/rn/update' },
{ text: '错误处理与排障', link: '/rn/troubleshooting' },
],
'/vue3/': [
{ text: '概览', link: '/vue3/' },

查看文件

@ -0,0 +1,128 @@
# BugCollect
`@xuqm/rn-bugcollect` 用于采集 JS 异常、原生崩溃、业务面包屑和自定义事件。它共享
Common 的自动配置与会话,不提供第二套初始化或登录入口。
## 安装
```bash
pnpm add @xuqm/rn-bugcollect @xuqm/rn-common
```
完成 [自动配置](./setup) 后无需在 App 入口调用 BugCollect 初始化方法。
## 隐私前置条件
只有宿主明确同步用户授权后才允许采集:
```ts
import { XuqmSDK } from '@xuqm/rn-common'
await XuqmSDK.setPrivacyConsent(true)
```
撤回授权:
```ts
await XuqmSDK.setPrivacyConsent(false)
```
SDK 不负责展示隐私声明,也不会把一次历史授权永久推断为当前授权。
## 自动采集
满足以下条件时,Release 应用自动启用异常采集:
- 租户平台已开启 BugCollect。
- 当前配置有效。
- 用户已同意隐私声明。
- App 构建没有使用应急关闭选项。
服务关闭、网络失败、限流或上传失败不会阻断宿主业务。服务端明确返回关闭状态后,SDK 会
停止后续采集,直到获取到新的有效平台配置。
## 手动记录
```ts
import { BugCollect } from '@xuqm/rn-bugcollect'
BugCollect.addBreadcrumb({
type: 'navigation',
category: 'order',
message: '进入订单详情',
data: { orderType: 'outpatient' },
})
BugCollect.info('开始提交订单')
BugCollect.warn('接口响应较慢', { elapsedMs: 2200 })
try {
await submitOrder()
} catch (error) {
BugCollect.captureError(error, {
scene: 'submit-order',
})
}
```
不要把密码、Token、身份证号、手机号、病历正文或完整请求响应放入 metadata。
## 自定义事件与漏斗
```ts
BugCollect.event('checkout_started', {
source: 'cart',
})
BugCollect.defineFunnel({
id: 'checkout',
steps: ['checkout_started', 'payment_started', 'payment_completed'],
})
const progress = BugCollect.getFunnelProgress('checkout')
```
事件名应保持稳定,属性只记录分析所需的非敏感枚举和数值。
## 日志级别与环境
```ts
BugCollect.setLogLevel(__DEV__ ? 'debug' : 'warn')
BugCollect.setEnvironment(__DEV__ ? 'development' : 'production')
BugCollect.setSampleRate(0.2)
```
采样率只影响非致命事件,致命异常不参与客户端采样。线上采样与服务端限流共同生效,宿主
不需要自行实现重试队列。
## Debug 验证
Debug 默认不自动上传。开发者工具页可以在用户已同意隐私声明后显式执行:
```ts
await BugCollect.sendTestErrorAndFlush('BugCollect integration test')
```
该方法用于验证配置和上传链路,不应放在正常业务流程中。
## 主动刷新
```ts
await BugCollect.flush()
```
通常不需要主动调用。应用即将退出且确实需要等待当前队列时才使用;上传失败仍应当允许
宿主继续退出。
## 应急关闭
当 BugCollect 服务不可用且影响生产打包时,宿主可以通过自己的 Release 构建参数关闭本次
构建的 BugCollect。该设置只用于应急,不写入平台下发配置,也不允许业务页面在运行时
反向开启平台已关闭的服务。
## 数据与错误边界
- SDK 会对常见敏感字段进行脱敏,但调用方仍须避免主动传入敏感正文。
- 网络和上传错误不会抛到全局导致 App 崩溃。
- 未授权、服务关闭和配置不可用时,自定义记录调用安全跳过。
- `sendTestErrorAndFlush` 是诊断方法,会把错误明确返回给开发者页面。

157
docs-site/docs/rn/common.md 普通文件
查看文件

@ -0,0 +1,157 @@
# Common 基础能力
`@xuqm/rn-common` 是所有 RN 扩展包共享的基础依赖,也可以不接入平台配置、不开启登录而
独立使用。
## 网络请求
Common-only 工程可以设置自己的业务地址:
```ts
import {
apiRequest,
configureHttp,
setGlobalApiErrorHandler,
} from '@xuqm/rn-common'
configureHttp({
baseUrl: 'https://api.example.com',
})
setGlobalApiErrorHandler((report) => {
// report 只包含经过脱敏的请求诊断信息
console.warn(report)
})
const profile = await apiRequest<{ name: string }>('/profile', {
timeoutMs: 15_000,
})
```
安装扩展包并完成自动配置后,扩展模块共享平台地址和当前会话。页面不要重复创建平台
Axios 实例,也不要逐层传递用户 ID 或 Token。
React 页面需要请求状态和运行时响应校验时,可以使用 `useRequest`、`useApi` 和
`usePageApi`。这些 Hooks 接收 URL、请求方法、参数和可选的 Zod Schema,具体类型由
TypeScript 自动提示。
## 文件与下载
```ts
import {
downloadFileToPath,
fileExists,
openLocalFile,
resolveAvailableFilePath,
} from '@xuqm/rn-common'
const target = await resolveAvailableFilePath('/downloads', 'report.pdf', 'rename')
const task = downloadFileToPath('https://example.com/report.pdf', target, {
onProgress(progress) {
console.log(progress.percent)
},
})
await task.result
if (await fileExists(target)) await openLocalFile(target)
```
常用能力还包括:
- `ensureDirectory`
- `readFileAsBase64`
- `writeBase64File`
- `deleteFile`
- `registerAndroidDownloadedFile`
- `fileNameFromUrl`
- `parseContentDispositionFileName`
- `sanitizeFileName`
- `inferMimeType`
下载任务支持取消,页面卸载时可以调用 `task.cancel()`
## 日期与过期状态
```ts
import {
expirationStatus,
formatDateTime,
millisecondsUntil,
toTimestamp,
} from '@xuqm/rn-common'
const status = expirationStatus(endTime)
if (status === 'expired') {
// 已过期
}
const remaining = millisecondsUntil(endTime)
const display = formatDateTime(endTime)
const timestamp = toTimestamp(endTime)
```
协议要求固定数字格式时使用 `formatNumericDateTime`,不要依赖设备 Locale 的标点顺序。
## 版本判断
```ts
import {
compareVersions,
isVersionUpgrade,
satisfiesVersion,
} from '@xuqm/rn-common'
compareVersions('1.2.0', '1.10.0')
isVersionUpgrade('1.0.0', '1.1.0')
satisfiesVersion('1.2.3', '>=1.0.0 <2.0.0')
```
## 设备信息
```ts
import { getDeviceId, getDeviceInfo, detectPushVendor } from '@xuqm/rn-common'
const deviceId = await getDeviceId()
const device = await getDeviceInfo()
const vendor = detectPushVendor(device.brand)
```
## 公共提示
```ts
import { showAlert, showConfirm, showToast } from '@xuqm/rn-common'
showToast('保存成功')
await showAlert({ title: '提示', message: '操作已完成' })
showConfirm({
title: '确认删除',
message: '删除后无法恢复',
onConfirm() {
// 执行删除
},
})
```
宿主可在应用入口统一调用 `configureToast` 对接自己的提示组件,业务页面不应复制多套
Alert/Toast 样式。
## 错误处理
```ts
import { XuqmError } from '@xuqm/rn-common'
try {
await someOperation()
} catch (error) {
if (error instanceof XuqmError) {
switch (error.code) {
case 'XUQM_NOT_READY':
case 'XUQM_SERVICE_DISABLED':
// 非阻断提示或跳过功能
break
}
}
}
```
不要根据英文错误文案判断业务分支,统一使用结构化错误码。

查看文件

@ -1,109 +0,0 @@
# React Native 群聊
基于 `@xuqm/rn-im` 模块实现群组相关功能。
---
## 创建群聊
```ts
import { ImSDK } from '@xuqm/rn-im'
const group = await ImSDK.createGroup('项目讨论', ['user_002', 'user_003'])
// group.id — 群 ID
// group.name — 群名称
// group.creatorId — 创建者
```
> `createGroup` 第二个参数为初始成员列表,创建者自动加入。
---
## 邀请成员
```ts
// 添加单个成员
await ImSDK.addGroupMember('group_xxx', 'user_004')
// 批量添加成员
await ImSDK.batchAddGroupMembers('group_xxx', ['user_004', 'user_005'])
```
---
## 发送群消息
```ts
const msg = await ImSDK.sendMessage(
'group_xxx', // toId
'GROUP', // chatType
'TEXT', // msgType
'大家好!' // content
)
```
发送多媒体群消息:
```ts
// 图片
await ImSDK.sendImageMessage('group_xxx', 'GROUP', '/path/to/image.jpg', 800, 600)
```
---
## 群成员管理
```ts
// 移除成员
await ImSDK.removeGroupMember('group_xxx', 'user_004')
// 批量移除
await ImSDK.batchRemoveGroupMembers('group_xxx', ['user_004', 'user_005'])
// 退出群聊
await ImSDK.leaveGroup('group_xxx')
// 设置管理员角色(示例)
await ImSDK.setGroupRole('group_xxx', 'user_004', 'ADMIN')
// 禁言成员(示例)
await ImSDK.muteGroupMember('group_xxx', 'user_004', 60)
// 转让群主(示例)
await ImSDK.transferGroupOwner('group_xxx', 'user_002')
// 解散群聊(示例)
await ImSDK.dismissGroup('group_xxx')
```
---
## 群信息查询
```ts
// 群列表(仅返回当前用户所在的群)
const groups = await ImSDK.listGroups()
// 群详情
const group = await ImSDK.getGroupInfo('group_xxx')
// 群成员
const members = await ImSDK.listGroupMembers('group_xxx')
// 群历史消息
const history = await ImSDK.fetchGroupHistory('group_xxx', page, size)
```
---
## 群类型
创建群时可指定 `groupType`
| 类型 | 说明 |
|------|------|
| `WORK` | 工作群(默认)|
| `PUBLIC` | 公开群 |
| `PRIVATE` | 私有群 |
[→ 返回 RN IM 接入文档](./im)

查看文件

@ -1,199 +1,232 @@
# React Native IM 接入
# IM 接入
基于 `@xuqm/rn-im` 模块实现即时通讯功能,使用 WatermelonDB 进行本地消息存储。
`@xuqm/rn-im` 提供单聊、群聊、会话、好友关系、黑名单和离线消息能力。宿主仍然只通过
Common 同步一次登录状态。
---
## 安装
## 初始化与登录
### 初始化
```ts
import { XuqmSDK } from '@xuqm/rn-common'
await XuqmSDK.initialize({
appKey: 'your_app_key',
logLevel: __DEV__ ? 'debug' : 'warn',
})
```bash
pnpm add @xuqm/rn-common @xuqm/rn-im @nozbe/watermelondb
```
### 登录
## 登录与连接
```ts
import { XuqmSDK } from '@xuqm/rn-common'
await XuqmSDK.login({
userId: 'user_001',
userSig: 'your_user_sig_jwt',
userId: 'user-001',
accessToken: 'host-access-token',
userSig: 'im-user-signature',
})
```
> 如果集成了 `rn-im`,`XuqmSDK.login` 会自动触发 `ImSDK` 连接,业务侧无需单独调用 `ImSDK.login`
安装 IM 后,公共会话会自动建立连接。不要再调用第二套 IM 登录方法
---
## 消息收发
### 监听实时消息
连接状态:
```ts
import { ImSDK } from '@xuqm/rn-im'
ImSDK.addEventListener('message', (msg) => {
console.log('收到消息:', msg.msgType, msg.content)
})
ImSDK.addEventListener('read', (msg) => {
console.log('已读回执:', msg.id)
})
ImSDK.addEventListener('revoke', (data) => {
console.log('消息被撤回:', data.msgId)
})
```
### 发送文本消息
```ts
const msg = await ImSDK.sendMessage(
'user_002', // toId
'SINGLE', // chatType: 'SINGLE' | 'GROUP'
'TEXT', // msgType
'Hello!' // content
)
```
### 发送图片
```ts
const msg = await ImSDK.sendImageMessage(
'user_002',
'SINGLE',
'/path/to/image.jpg', // 本地 URI
800, // 宽
600 // 高
)
```
### 获取历史消息
```ts
// 单聊历史
const history = await ImSDK.fetchHistory('user_002', page, size)
// 群历史
const groupHistory = await ImSDK.fetchGroupHistory('group_xxx', page, size)
```
---
## WatermelonDB 本地存储
`rn-im` 使用 WatermelonDBSQLite存储本地消息,按 `appKey + userId` 自动隔离。
```ts
import { ImDatabase } from '@xuqm/rn-im'
// 消息搜索(本地)
const params: MessageSearchParams = {
keyword: '会议',
toId: 'user_002',
chatType: 'SINGLE',
msgTypes: ['TEXT'],
limit: 20,
if (!ImSDK.isConnected()) {
await ImSDK.reconnect()
}
const results = await ImSDK.searchMessages(params)
```
---
## 群聊
## 事件监听
```ts
// 创建群
const group = await ImSDK.createGroup('项目讨论', ['user_002', 'user_003'])
const listener = {
onConnected() {
console.log('IM 已连接')
},
onDisconnected(reason?: string) {
console.log('IM 已断开', reason)
},
onMessage(message) {
appendMessage(message)
},
onGroupMessage(message) {
appendGroupMessage(message)
},
onRead(message) {
markMessageRead(message.id)
},
onRevoke({ msgId }) {
markMessageRevoked(msgId)
},
onError(error) {
console.warn(error)
},
}
// 群列表
const groups = await ImSDK.listGroups()
ImSDK.addListener(listener)
// 添加成员
await ImSDK.addGroupMember('group_xxx', 'user_004')
// 移除成员
await ImSDK.removeGroupMember('group_xxx', 'user_004')
// 退出群聊
await ImSDK.leaveGroup('group_xxx')
// 页面或服务销毁时
ImSDK.removeListener(listener)
```
详见 [RN 群聊文档 →](./group)
同一个 listener 对象用于添加和移除。
---
## 发送消息
文本:
```ts
const message = await ImSDK.sendTextMessage(
'user-002',
'SINGLE',
'你好',
)
```
群聊:
```ts
await ImSDK.sendTextMessage('group-001', 'GROUP', '大家好')
```
图片:
```ts
await ImSDK.sendImageMessage(
'user-002',
'SINGLE',
imageUri,
imageWidth,
imageHeight,
)
```
文件、音频和视频使用对应方法:
```ts
await ImSDK.sendFileMessage(
'user-002',
'SINGLE',
fileUri,
'report.pdf',
fileSize,
)
await ImSDK.sendAudioMessage('user-002', 'SINGLE', audioUri, durationSeconds)
await ImSDK.sendVideoMessage(
'user-002',
'SINGLE',
videoUri,
thumbnailUri,
durationSeconds,
)
```
自定义、位置、富文本、通知、引用、合并转发和音视频通话信令也有对应的类型化方法。消息
业务内容由双方约定,不要在普通文本字段中拼接不可验证的脚本。
撤回与编辑:
```ts
await ImSDK.revokeMessage(message.id)
await ImSDK.editMessage(message.id, '修改后的内容')
```
## 历史和离线消息
```ts
const history = await ImSDK.fetchHistory(
'user-002',
0,
20,
'SINGLE',
)
const filtered = await ImSDK.fetchHistoryWithFilters('user-002', {
keyword: '会议',
msgType: 'TEXT',
startTime: '2026-07-01 00:00:00',
endTime: '2026-07-31 23:59:59',
page: 0,
size: 20,
})
const offlineCount = await ImSDK.offlineMessageCount()
const offlineMessages = await ImSDK.syncOfflineMessages(100)
```
SDK 负责当前登录用户的本地消息缓存与同步,宿主不需要依赖其内部存储结构。
## 会话列表
```ts
// 订阅会话变化
const unsub = ImSDK.subscribeConversations((conversations) => {
console.log(conversations)
const conversations = await ImSDK.listConversations()
const unsubscribe = ImSDK.subscribeConversations((next) => {
setConversations(next)
})
// 置顶
await ImSDK.setConversationPinned('user_002', 'SINGLE', true)
await ImSDK.markRead('user-002', 'SINGLE')
await ImSDK.setConversationPinned('user-002', 'SINGLE', true)
await ImSDK.setConversationMuted('user-002', 'SINGLE', true)
await ImSDK.setDraft('user-002', 'SINGLE', '稍后继续')
// 免打扰
await ImSDK.setConversationMuted('group_xxx', 'GROUP', true)
// 标记已读
await ImSDK.markRead('user_002')
unsubscribe()
```
---
还可以隐藏、删除会话或将会话加入自定义分组。
## 离线消息同步
## 群聊
```ts
// 查询离线消息数量(示例)
const count = await ImSDK.offlineMessageCount()
const group = await ImSDK.createGroup(
'项目讨论',
['user-002', 'user-003'],
'WORK',
)
// 同步离线消息(示例)
const messages = await ImSDK.syncOfflineMessages()
const groups = await ImSDK.listGroups()
const detail = await ImSDK.getGroupInfo(group.id)
const members = await ImSDK.listGroupMembers(group.id)
await ImSDK.addGroupMember(group.id, 'user-004')
await ImSDK.removeGroupMember(group.id, 'user-004')
await ImSDK.leaveGroup(group.id)
```
---
管理员能力包括群资料、角色、禁言、群主转让、入群申请和解散群聊。调用失败时使用服务端
返回的权限错误,不要仅靠前端角色隐藏来替代服务端校验。
## 断开连接
## 好友和黑名单
```ts
ImSDK.disconnect()
const friends = await ImSDK.listFriends()
await ImSDK.addFriend('user-002')
await ImSDK.removeFriend('user-002')
const incoming = await ImSDK.listFriendRequests('incoming')
const request = await ImSDK.sendFriendRequest('user-003', '我是张医生')
await ImSDK.acceptFriendRequest(request.id)
await ImSDK.addToBlacklist('user-004')
const blocked = await ImSDK.checkBlacklist('user-004')
await ImSDK.removeFromBlacklist('user-004')
```
---
## 类型参考
## 用户与消息搜索
```ts
interface ImMessage {
id: string
fromId: string
toId: string
chatType: 'SINGLE' | 'GROUP'
msgType: 'TEXT' | 'IMAGE' | 'AUDIO' | 'VIDEO' | 'FILE' |
'LOCATION' | 'NOTIFY' | 'CUSTOM' | 'RICH_TEXT' |
'CALL_AUDIO' | 'CALL_VIDEO' | 'FORWARD' | 'REVOKED'
content: string
status: 'SENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED' | 'REVOKED'
createdAt: number
}
interface ConversationData {
targetId: string
chatType: 'SINGLE' | 'GROUP'
lastMsgContent: string
lastMsgType: string
lastMsgTime: number
unreadCount: number
isMuted: boolean
isPinned: boolean
}
const users = await ImSDK.searchUsers('张医生')
const groups = await ImSDK.searchGroups('项目')
const messages = await ImSDK.searchMessages({
keyword: '会议',
chatType: 'SINGLE',
toId: 'user-002',
msgTypes: ['TEXT'],
limit: 20,
})
```
## 登出
```ts
await XuqmSDK.logout()
```
公共登出会自动断开 IM。`ImSDK.disconnect()` 仅用于 IM 专用诊断或明确的临时断开场景。

查看文件

@ -1,288 +1,88 @@
# React Native SDK 接入指南
# React Native SDK
**包名**`@xuqm/rn-sdk` · **版本**0.2.x内部基础包,业务方不直接引用
XuqmGroup React Native SDK 由一个可独立使用的基础包和多个按需安装的扩展包组成。宿主只需
维护一份平台下发的配置文件,并在登录成功后同步一次会话;已安装的扩展包会共享这份配置
和会话状态。
> **注意**`rn-sdk` 作为内部基础包存在,业务方正常接入时使用 `@xuqm/rn-common` 和各业务模块即可。
## 选择需要的模块
---
| 包 | 用途 | 是否需要平台配置 |
| --- | --- | --- |
| `@xuqm/rn-common` | 网络、文件、时间、设备、通用 UI 与 API Hooks | 独立使用时不需要 |
| `@xuqm/rn-update` | Android 整包更新与 RN 插件更新 | 需要 |
| `@xuqm/rn-bugcollect` | JS 异常、原生崩溃、面包屑和自定义事件 | 需要 |
| `@xuqm/rn-xwebview` | 独立 WebView 页面、导航栏、权限、下载和宿主 Bridge | 使用页面能力时需要 |
| `@xuqm/rn-im` | 单聊、群聊、会话、好友和离线消息 | 需要,并需要登录 |
| `@xuqm/rn-push` | 厂商推送绑定、离线推送和免打扰 | 需要,并需要登录 |
## 功能模块
只安装实际使用的模块。扩展包会声明对 `rn-common` 的依赖范围,不需要宿主再创建第二套
初始化器、网络层或用户 Store。
| 包 | 功能 |
|----|------|
| `@xuqm/rn-common` | 初始化、网络、设备信息,可独立使用 |
| `@xuqm/rn-im` | 单聊、群聊、消息收发、本地 DBWatermelonDB|
| `@xuqm/rn-push` | 推送设备 Token 上报 |
| `@xuqm/rn-update` | App 版本检查、RN Bundle 热更新 |
| `@xuqm/rn-sdk` | 内部基础包,随 IM / Push / Update 自动安装,不建议业务方直接引用 |
## 两种接入方式
---
### 只使用 Common
## 安装
不放置 `.xuqmconfig`,也不包装 Metro
在项目根目录创建 `.npmrc`
```ts
import { configureHttp, apiRequest, expirationStatus } from '@xuqm/rn-common'
```
@xuqm:registry=https://nexus.xuqinmin.com/repository/npm-hosted/
configureHttp({ baseUrl: 'https://api.example.com' })
const health = await apiRequest('/health', { skipAuth: true })
const certState = expirationStatus('2027-12-31T23:59:59+08:00')
```
只使用基础能力时,直接安装 `rn-common`,不会带入 IM / Push / Update
这种模式不需要初始化和登录。
```bash
yarn add @xuqm/rn-common
### 使用任意扩展包
1. 从租户平台下载当前应用的 `.xuqmconfig`
2. 将唯一一份配置放到 `src/assets/config/`
3. 使用 SDK 提供的 Metro 包装器。
4. 宿主隐私声明获得用户同意后调用一次 `setPrivacyConsent(true)`
5. 宿主登录成功后调用一次 `XuqmSDK.login(...)`
SDK 不要求页面逐层传递 AppKey、平台地址、用户 ID 或登录凭证。
## 最小示例
```js
// metro.config.js
const { getDefaultConfig } = require('@react-native/metro-config')
const { withXuqmConfig } = require('@xuqm/rn-common/metro')
module.exports = withXuqmConfig(getDefaultConfig(__dirname))
```
按需安装模块时,`rn-im` / `rn-push` / `rn-update` 都会自动带上 `rn-common``rn-sdk`
```bash
yarn add @xuqm/rn-common @xuqm/rn-im
```
`rn-sdk` 不作为业务方直接安装入口;它会随着 `rn-im` / `rn-push` / `rn-update` 自动进入依赖树。
---
## 快速接入(当前 v0.2.x
### 1. 初始化
初始化只需传入 `appKey`,平台地址由 SDK 内置,开发者无需额外配置。
```ts
import { XuqmSDK } from '@xuqm/rn-common'
// App 入口(如 App.tsx 的顶层)
await XuqmSDK.initialize({
appKey: 'your_app_key',
logLevel: __DEV__ ? 'debug' : 'warn',
})
```
await XuqmSDK.setPrivacyConsent(true)
> SDK 内部自动处理服务器地址、IM 实时连接、文件服务等配置,开发者无需关心。
### 2. 统一登录
```ts
import { XuqmSDK } from '@xuqm/rn-common'
import { ImSDK } from '@xuqm/rn-im'
// 登录只需要 userId + userSig
// nickname / avatar 不再通过登录接口传入
await XuqmSDK.login({
userId: 'user_001',
userSig: 'your_user_sig_jwt',
userId: 'user-001',
accessToken: 'host-access-token',
// 仅使用 IM 时传入:
userSig: 'im-user-signature',
})
// 如果集成了 rn-im,XuqmSDK.login 会自动触发 ImSDK 连接
// 业务侧无需单独调用 ImSDK.login
```
### 3. 消息收发
## 默认行为
```ts
// 监听实时消息
ImSDK.addEventListener('message', (msg) => {
console.log('收到消息:', msg.msgType, msg.content)
})
- 配置由构建工具自动发现,运行时没有手动初始化 API。
- 配置错误会阻断构建,避免生成安装后才无法初始化的包。
- 扩展服务初始化失败不会阻断宿主应用自身启动;具体操作会返回结构化状态或错误。
- Update、BugCollect 等服务是否启用由租户平台控制。
- Debug 不会自动检查更新,也不会自动上报 BugCollect。
- Common 独立模式不会访问 XuqmGroup 服务。
// 发送文本消息
const msg = await ImSDK.sendMessage(
'user_002', // toId
'SINGLE', // chatType: 'SINGLE' | 'GROUP'
'TEXT', // msgType
'Hello!' // content
)
## 下一步
// 发送图片(自动上传到 file-service
const msg = await ImSDK.sendImageMessage(
'user_002', 'SINGLE',
'/path/to/image.jpg', // 本地 URI
800, 600 // 宽高(可选)
)
// 获取历史消息(单聊)
const history = await ImSDK.fetchHistory('user_002', page, size)
// 获取群历史消息
const groupHistory = await ImSDK.fetchGroupHistory('group_xxx', page, size)
```
### 4. 会话列表
```ts
// 订阅会话变化
const unsub = ImSDK.subscribeConversations((conversations) => {
// conversations: ConversationData[]
console.log(conversations)
})
// 置顶会话
await ImSDK.setConversationPinned('user_002', 'SINGLE', true)
// 免打扰
await ImSDK.setConversationMuted('group_xxx', 'GROUP', true)
// 标记已读
await ImSDK.markRead('user_002')
```
### 5. 群组管理
```ts
// 创建群组
const group = await ImSDK.createGroup('项目讨论', ['user_002', 'user_003'])
// 群组列表(仅返回当前用户所在的群)
const groups = await ImSDK.listGroups()
// 添加成员
await ImSDK.addGroupMember('group_xxx', 'user_004')
// 移除成员(需要管理员权限)
await ImSDK.removeGroupMember('group_xxx', 'user_004')
// 退出群聊
await ImSDK.leaveGroup('group_xxx')
```
### 6. 好友关系
```ts
// 好友列表
const friends = await ImSDK.listFriends()
// 添加好友(双向)
await ImSDK.addFriend('user_002')
// 删除好友(双向)
await ImSDK.removeFriend('user_002')
```
### 7. 消息搜索(本地)
```ts
import { ImSDK } from '@xuqm/rn-im'
import type { MessageSearchParams } from '@xuqm/rn-im'
const params: MessageSearchParams = {
keyword: '会议', // 关键词搜索
toId: 'user_002', // 限定会话(可选)
chatType: 'SINGLE', // 限定类型(可选)
msgTypes: ['TEXT'], // 限定消息类型(可选)
limit: 20,
}
const results = await ImSDK.searchMessages(params)
```
### 8. 推送 SDK
登录成功后 SDK **自动**注册设备 Token,业务侧无需手动调用。
如需自定义 Push 行为,可引入 `@xuqm/rn-push`
```ts
import { PushSDK } from '@xuqm/rn-push'
// 手动设置接收推送开关
await PushSDK.setReceivePush(false)
```
### 9. 版本更新 SDK
```ts
import { UpdateSDK } from '@xuqm/rn-update'
// 检查 App 原生更新
const appUpdate = await UpdateSDK.checkAppUpdate()
if (appUpdate?.needsUpdate) {
console.log('新版本:', appUpdate.versionName)
// Linking.openURL(appUpdate.downloadUrl)
}
// 检查 RN Bundle 热更新
const rnUpdate = await UpdateSDK.checkRnUpdate('your_module_id')
if (rnUpdate?.needsUpdate) {
await UpdateSDK.downloadAndApplyBundle(rnUpdate)
}
```
### 10. 断开连接
```ts
ImSDK.disconnect()
```
---
## TypeScript 类型参考
```ts
// 消息
interface ImMessage {
id: string
fromId: string
toId: string
chatType: 'SINGLE' | 'GROUP'
msgType: 'TEXT' | 'IMAGE' | 'AUDIO' | 'VIDEO' | 'FILE' |
'LOCATION' | 'NOTIFY' | 'CUSTOM' | 'RICH_TEXT' |
'CALL_AUDIO' | 'CALL_VIDEO' | 'FORWARD' | 'REVOKED'
content: string // JSON 字符串,结构按 msgType 不同
status: 'SENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED' | 'REVOKED'
createdAt: number // Unix 毫秒
}
// 会话
interface ConversationData {
targetId: string
chatType: 'SINGLE' | 'GROUP'
lastMsgContent: string
lastMsgType: string
lastMsgTime: number
unreadCount: number
isMuted: boolean
isPinned: boolean
}
// 群组
interface ImGroup {
id: string
name: string
creatorId: string
memberIds: string // JSON 数组字符串
adminIds: string
createdAt: number
}
```
---
## 当前接入
当前接入方式如下:
```ts
// 初始化
await XuqmSDK.initialize({ appKey: 'xxx' }) // 平台地址内置,无需传入
// IM 登录UserSig 由业务服务端签发)
const userSig = await yourServer.getUserSig(userId)
await ImSDK.login(userId, userSig)
// dbName 自动由 appKey + userId 派生,无需传入
```
---
## 常见问题
**Q: 如何获取 appKey?**
在 [平台控制台](https://dev.xuqinmin.com) 注册账号后,创建应用即可获得 AppKey。
**Q: 为什么不需要传平台地址参数?**
XuqmGroup 是托管平台,服务地址统一管理,与腾讯云 IM 等平台的设计一致,开发者只需关心业务逻辑。
**Q: UserSig 是什么?**
UserSig 是您的业务服务端用 AppSecret 为用户签发的安全凭证。当前项目的 IM 登录不做过期功能,只校验 `userId + UserSig` 是否匹配。AppSecret 绝不下发到客户端。
**Q: 本地消息存储在哪里?**
使用 WatermelonDBSQLite,按 `appKey + userId` 自动隔离,多账号切换安全。
- [安装与自动配置](./setup)
- [Common 基础能力](./common)
- [会话与隐私授权](./session)
- [版本与插件更新](./update)
- [BugCollect](./bugcollect)
- [XWebView](./xwebview)
- [IM 接入](./im)
- [推送接入](./push)

查看文件

@ -1,138 +1,80 @@
# React Native 推送接入指南
# 推送接入
**包名**`@xuqm/rn-push` · **支持**华为、小米、OPPO、vivo、荣耀、APNsiOS
`@xuqm/rn-push` 负责把宿主登录用户同步给原生推送模块,并提供离线推送开关与免打扰
设置。厂商识别、Token 获取和绑定由原生实现完成,RN 页面不接收或上传厂商 Token。
---
## 1. 安装
## 安装
```bash
yarn add @xuqm/rn-push
pnpm add @xuqm/rn-common @xuqm/rn-push
```
iOS 需要执行 `pod install`
Android 原生推送依赖按租户平台为当前应用生成的配置接入。宿主只启用实际使用的厂商
渠道,不要把其他项目的厂商 App ID 或密钥复制过来。
```bash
cd ios && pod install
```
## 登录后自动绑定
---
## 2. Android 厂商推送集成
各厂商推送 SDK 需在原生 Android 层集成。在 `android/app/build.gradle` 中按需添加:
```gradle
dependencies {
// 华为 HMS Push
implementation 'com.huawei.hms:push:6.9.0.300'
// 小米 Push
implementation 'com.xiaomi.mipush:sdk:5.0.6'
// OPPO Push
implementation 'com.heytap.msp:push:3.5.0'
// vivo Push
implementation 'com.vivo.push:sdk:3.0.0.4_484'
// 荣耀 Push
implementation 'com.hihonor.mcs:push:7.0.41.301'
}
```
---
## 3. 请求原生推送权限并注册
```ts
import { PushSDK } from '@xuqm/rn-push'
// 触发原生推送注册Android 请求厂商 token;iOS 请求 APNs 权限)
await PushSDK.requestNativeRegistration()
```
---
## 4. 监听推送 Token
```ts
import { PushSDK } from '@xuqm/rn-push'
// 在 App 启动时监听 token 回调,并向服务端注册
const unsubscribe = PushSDK.onPushToken(async (token, vendor) => {
console.log('获取到 Token:', token, '厂商:', vendor)
// 登录后调用注册接口
await PushSDK.setDeviceToken(token, vendor as PushVendor)
})
// 在组件卸载时取消监听
unsubscribe()
```
---
## 5. 手动注册 Token
```ts
import { PushSDK } from '@xuqm/rn-push'
import type { PushVendor } from '@xuqm/rn-push'
// 登录成功后,将 token 注册到服务端
await PushSDK.registerToken('user_001', 'device_token_here', 'HUAWEI')
```
---
## 6. 登出时注销 Token
```ts
await PushSDK.unregisterToken('user_001')
// 或简写
await PushSDK.logout('user_001')
```
---
## 7. 多模块统一登录
Push 模块与 IM、Update 模块共享同一套登录态:
宿主仍然只调用公共登录入口:
```ts
import { XuqmSDK } from '@xuqm/rn-common'
await XuqmSDK.login({
userId: 'user-001',
accessToken: 'host-access-token',
})
```
已安装 Push 模块时,会话变化自动同步到原生层。不要调用不存在的
Token 注册方法,也不要建立第二套 Push 登录 API。
登出:
```ts
await XuqmSDK.logout()
```
## 离线推送开关
```ts
import { PushSDK } from '@xuqm/rn-push'
await XuqmSDK.initialize({ appKey: 'your_app_key' })
await XuqmSDK.login({ userId: 'user_001', userSig: 'your_user_sig' })
// ↓ 登录后调用 PushSDK.initialize() 完成 token 注册
await PushSDK.initialize('user_001')
await PushSDK.setOfflinePushEnabled(false)
await PushSDK.setOfflinePushEnabled(true)
```
---
该开关同步到当前登录用户。未登录时不要在业务页面展示为已经保存成功。
## 8. iOS APNs 配置
## 免打扰
`AppDelegate.m``AppDelegate.swift` 中:
```objc
// AppDelegate.m
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
// 转发 token,由 rn-push 原生模块处理
[RCTEventEmitter ...] // 通过 Bridge 传至 JS
}
```ts
await PushSDK.setQuietHours('22:00', '08:00')
```
> 使用 `PushSDK.requestNativeRegistration()` 会自动触发 iOS APNs 注册流程,无需额外原生代码(前提是 RN 0.76+ 自动链接)。
时间使用 24 小时制的 `HH:mm`。清除设置:
---
```ts
await PushSDK.clearQuietHours()
```
## 9. 厂商渠道自动检测
## 权限和厂商配置
`@xuqm/rn-common``detectPushVendor` 会根据 `device.brand` 自动识别厂商:
- Android 13 及以上由宿主在合适的业务时机申请通知权限。
- 厂商推送应用标识和服务端密钥在租户平台维护。
- 客户端只包含厂商要求的公开配置,不得包含服务端密钥。
- Push 服务关闭或注册失败不能阻断宿主登录。
- 重新登录或切换用户后,SDK 会使用新的公共会话重新绑定。
| 品牌关键字 | 识别厂商 |
|-----------|---------|
| xiaomi / redmi | XIAOMI |
| huawei | HUAWEI |
| honor | HONOR |
| oppo / realme | OPPO |
| vivo / iqoo | VIVO |
| iOS | APNS |
| 其他 | FCM |
## 常见错误
### 登录成功但没有离线通知
依次检查:
1. 系统通知权限是否开启。
2. 当前构建是否包含目标设备厂商通道。
3. 租户平台是否启用 Push。
4. App 包名、签名和厂商控制台配置是否一致。
5. 当前用户是否关闭了离线推送或设置了免打扰。
不要在 RN 页面手工缓存 Token 作为“绑定成功”的判断依据。

查看文件

@ -0,0 +1,83 @@
# 会话与隐私授权
使用扩展模块的应用只有一个公共会话入口。Common 独立模式不需要登录。
## 隐私授权
SDK 不展示隐私弹窗,也不会自行推断用户是否同意。宿主在自己的隐私声明获得授权后调用:
```ts
import { XuqmSDK } from '@xuqm/rn-common'
await XuqmSDK.setPrivacyConsent(true)
```
用户撤回授权时:
```ts
await XuqmSDK.setPrivacyConsent(false)
```
撤回后 BugCollect 会停止采集并清理尚未发送的数据。宿主业务是否继续运行由宿主决定。
## 登录
宿主业务登录成功后调用一次:
```ts
await XuqmSDK.login({
userId: 'user-001',
accessToken: 'host-access-token',
name: '张医生',
phone: '13800000000',
avatar: 'https://example.com/avatar.png',
// 只有使用 IM 时才需要:
userSig: 'im-user-signature',
})
```
字段说明:
| 字段 | 必填 | 用途 |
| --- | --- | --- |
| `userId` | 是 | XuqmGroup 扩展服务共享的用户标识 |
| `accessToken` | 按业务 | 公共 HTTP Bearer Token |
| `userSig` | 仅 IM | IM 登录凭证 |
| `name/email/phone/avatar` | 否 | 扩展模块需要时使用的用户资料 |
`accessToken``userSig` 是两种不同凭证,不要互相替代。
## 登出
```ts
await XuqmSDK.logout()
```
登出会通知所有已安装扩展包清理当前会话。业务页面不需要分别调用 Update、Push 或 IM 的
登录/登出方法。
## 获取当前状态
```ts
const userId = XuqmSDK.getUserId()
const userInfo = XuqmSDK.getUserInfo()
const initState = XuqmSDK.getInitializationState()
```
这些方法适合应用入口、调试页或基础设施层读取。业务组件应从宿主自己的用户领域 Store
展示资料,不要把 SDK 当作宿主用户资料数据库。
## 登录门禁与宿主行为
租户平台可以把 Update 等服务配置为“登录后检查”。未登录时 SDK 会返回
`LOGIN_REQUIRED`,宿主应当跳过更新提示并继续正常使用应用:
```ts
const result = await UpdateSDK.checkAppUpdate()
if (result.status === 'LOGIN_REQUIRED') {
return // 不弹错误、不阻断启动
}
```
平台临时不可用、服务关闭或登录要求发生变化,都不能替代宿主自身的登录和启动流程。

查看文件

@ -1,147 +1,156 @@
# React Native 安装配置
# 安装与自动配置
**包名**`@xuqm/rn-sdk` · **版本**0.2.x · **RN 版本**:≥ 0.76.0
## 环境要求
> `rn-sdk` 作为内部基础包存在,业务方正常接入时使用 `@xuqm/rn-common` 和各业务模块即可。
- React Native `0.76` 或更高版本
- Node.js 当前 LTS
- Android `minSdk 24` 或更高版本
- 使用原生模块时,需要标准 React Native 原生工程或完成 Prebuild 的 Expo 工程
---
SDK 当前以 Android 为完整验证平台。页面中明确标记为 Android 的能力不能据此推断为
iOS 已支持。
## npm / yarn 安装
## 配置 npm 仓库
在项目根目录创建 `.npmrc`
宿主从只读聚合仓库安装依赖
```
@xuqm:registry=https://nexus.xuqinmin.com/repository/npm-hosted/
```ini
# .npmrc
@xuqm:registry=https://nexus.xuqinmin.com/repository/npm/
```
只使用基础能力时,直接安装 `rn-common`
请使用租户平台为当前应用推荐的精确版本。开发和测试阶段可能使用带 `alpha`、`beta`、
`rc``SNAPSHOT` 标识的预发布版本,不要自行改成无预发布标识的版本。
## 按需安装
只使用通用基础能力:
```bash
yarn add @xuqm/rn-common
pnpm add @xuqm/rn-common axios \
@react-native-async-storage/async-storage
```
按需安装模块时,`rn-im` / `rn-push` / `rn-update` 都会自动带上 `rn-common``rn-sdk`
使用版本更新
```bash
yarn add @xuqm/rn-common @xuqm/rn-im
# 或全量安装
yarn add @xuqm/rn-common @xuqm/rn-im @xuqm/rn-push @xuqm/rn-update @xuqm/rn-bugcollect
pnpm add @xuqm/rn-common @xuqm/rn-update \
@react-native-async-storage/async-storage
```
---
## 自动 / 手动链接
React Native 0.60+ 支持**自动链接**,安装后执行:
使用 WebView
```bash
cd ios && pod install
pnpm add @xuqm/rn-common @xuqm/rn-xwebview \
react-native-webview react-native-safe-area-context react-native-svg
```
> 若使用 Expo,需先执行 `expo prebuild` 生成原生项目后再执行 `pod install`
---
## iOS 配置
### Pod 安装
其他模块同样按需安装:
```bash
cd ios
pod install
pnpm add @xuqm/rn-bugcollect
pnpm add @xuqm/rn-im @nozbe/watermelondb
pnpm add @xuqm/rn-push
```
### 权限声明
项目使用 npm 或 Yarn 时,可以使用等价安装命令;不要同时维护多份锁文件。
`Info.plist` 中添加:
## 获取配置文件
```xml
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风</string>
使用扩展包时,在租户平台进入目标应用并下载配置文件。将下载文件原名放到:
```text
src/assets/config/<平台生成的文件名>.xuqmconfig
```
---
约束:
## Android 配置
- 目录中只能存在一个 `.xuqmconfig`
- 不要重命名内容、手工编辑或提交到公共仓库。
- 公有化和私有化平台生成的配置不能混用。
- 重新生成配置后,下一次构建必须使用新文件。
- Common 独立模式不要放置配置文件。
### Gradle 仓库
建议加入忽略规则:
`android/build.gradle` 中确保包含:
```gradle
allprojects {
repositories {
maven { url "https://nexus.xuqinmin.com/repository/android/" }
google()
mavenCentral()
}
}
```gitignore
src/assets/config/*.xuqmconfig
```
### 最低版本
## 配置 Metro
- `minSdkVersion = 24`
- `compileSdkVersion = 34`
---
## 依赖关系
```
@xuqm/rn-sdkmeta-package,不建议业务方直接引用
├── @xuqm/rn-common ← 初始化、网络、设备信息
├── @xuqm/rn-im ← IM 模块(依赖 WatermelonDB
├── @xuqm/rn-push ← Push 模块
├── @xuqm/rn-update ← 更新模块
└── @xuqm/rn-bugcollect ← 异常采集模块崩溃、ANR 上报)
```
---
## Metro 配置
使用 `withXuqmConfig` Metro 插件替代手动 alias 配置。该插件会自动处理 RN SDK 的模块解析,并注入 `.xuqmconfig` 配置文件:
没有启用插件化时,只包装一次 Common
```js
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
const { withXuqmConfig } = require('@xuqm/rn-common/metro')
module.exports = withXuqmConfig({
// 你的原始 Metro 配置
const config = mergeConfig(getDefaultConfig(__dirname), {
// 宿主自己的 Metro 配置
})
module.exports = withXuqmConfig(config)
```
> 不再需要手动配置 `resolver.extraNodeModules` 或 alias。
启用 RN 插件化时使用 Update 提供的组合器,不要再叠加 `withXuqmConfig`
---
```js
const { getDefaultConfig } = require('@react-native/metro-config')
const { withXuqmModuleConfig } = require('@xuqm/rn-update/metro')
## 配置文件
从平台控制台下载应用的 `.xuqmconfig` 配置文件(例如 `识校宝.xuqmconfig`),放置到 `src/assets/config/` 目录。`withXuqmConfig` 插件会自动发现并注入该配置,SDK 运行时自动读取,无需手动指定路径。
文件名不限,只要扩展名为 `.xuqmconfig` 即可。
---
## BugCollect
异常采集模块可自动收集 JS 异常、原生崩溃日志并上报到服务端。
```bash
yarn add @xuqm/rn-bugcollect
module.exports = withXuqmModuleConfig(getDefaultConfig(__dirname))
```
集成了 `withXuqmConfig` 插件后,BugCollect 会随 SDK 自动初始化,无需额外代码。采集服务地址从 `.xuqmconfig` 配置中自动读取
两种配置二选一。项目只能存在一个 Metro 配置出口。
---
## Android 仓库
## 下一步
原生依赖从 XuqmGroup Android 聚合仓库读取:
- [RN IM 接入 →](./im)
- [RN 群聊 →](./group)
- [RN 推送接入 →](./push)
- [RN 版本更新 →](./update)
```kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven("https://nexus.xuqinmin.com/repository/android/")
google()
mavenCentral()
}
}
```
React Native 自动链接会处理已安装模块。只有 Update 插件化宿主需要按其页面说明应用
一次 Bundle 构建脚本。
## 启动前检查
```ts
import { XuqmSDK } from '@xuqm/rn-common'
const state = XuqmSDK.getInitializationState()
// idle | initializing | ready | degraded | failed
```
一般业务无需等待初始化。确实需要在首屏执行扩展操作时:
```ts
await XuqmSDK.awaitInitialization()
```
持续失败时按错误码展示非阻断提示,不要让 SDK 服务异常替代宿主首页。
## Debug 策略
- `pnpm start``pnpm android` 使用标准 Metro 与 Fast Refresh。
- Debug 默认不自动检查整包或插件更新。
- Debug 默认不自动上传 BugCollect。
- 需要验证服务时,通过对应模块的显式测试入口触发。
## 验收清单
- 工程中只有一份 `.xuqmconfig`
- Metro 只使用一个 Xuqm 包装器。
- Common-only 工程不要求初始化。
- App 启动时没有手写 AppKey、平台地址或内部服务地址。
- 登录只调用一次公共会话入口。
- Release 构建使用租户平台当前有效配置。

查看文件

@ -0,0 +1,142 @@
# 错误处理与排障
## 公共错误码
需要初始化的扩展能力可能抛出 `XuqmError`
| 错误码 | 含义 | 宿主处理 |
| --- | --- | --- |
| `XUQM_NOT_READY` | 自动初始化尚未完成或没有启动 | 检查配置和 Metro;不要阻断宿主首页 |
| `XUQM_NOT_LOGGED_IN` | 当前操作要求登录 | 完成宿主登录后重试 |
| `XUQM_CONFIG_INVALID` | 配置格式或签名无效 | 重新从租户平台下载配置并构建 |
| `XUQM_CONFIG_EXPIRED` | 构建使用的配置已过期 | 重新生成并构建 |
| `XUQM_CONFIG_REVOKED` | 配置已被租户平台撤销 | 使用平台当前有效配置重新构建 |
| `XUQM_SERVICE_DISABLED` | 对应服务未开启 | 隐藏入口或跳过功能 |
示例:
```ts
import { XuqmError } from '@xuqm/rn-common'
try {
await operation()
} catch (error) {
if (error instanceof XuqmError) {
reportNonBlockingError(error.code)
return
}
throw error
}
```
## App 启动正常,但扩展功能不可用
1. 确认 `src/assets/config/` 只有一个 `.xuqmconfig`
2. 确认 Metro 只使用一个 Xuqm 包装器。
3. 确认配置来自当前公有化或私有化租户平台,不能混用。
4. 查看 `XuqmSDK.getInitializationState()`
5. 检查租户平台是否已开启对应服务。
6. 如果服务要求登录,确认已经调用公共 `login`
扩展服务失败不应导致宿主白屏。不要在根组件渲染前无限等待网络初始化。
## Common-only 工程提示未初始化
Common 的网络、文件、时间和 UI 能力不要求初始化。出现未初始化错误通常表示调用了
需要平台配置的扩展 API,或直接调用了 `getConfig()`
Common-only 工程应当:
- 不放 `.xuqmconfig`
- 不使用 Metro 自动配置包装器。
- 不调用 `XuqmSDK.awaitInitialization()`
- 业务网络使用绝对 URL 或 `configureHttp`
## 配置构建失败
构建期失败是预期的安全门禁,常见原因:
- 配置文件超过一个。
- 文件内容被编辑、损坏或不属于当前应用。
- 配置过期或已撤销。
- 公有化配置用于私有化应用,或反向混用。
不要通过跳过校验生成安装包,应从租户平台重新下载配置。
## Update 不弹更新
先检查 `checkAppUpdate()` 的结构化状态:
- `LOGIN_REQUIRED`:未登录时正常跳过。
- `SERVICE_DISABLED`:租户平台未启用 Update。
- `NO_UPDATE`:没有适用于当前版本、设备或灰度身份的更新。
- `FAILED`:查看 `errorCode``message`,但不要阻断应用。
网络状态随检查结果返回,SDK 不替宿主决定是否允许移动网络下载。
## 插件打不开
1. Debug 确认 Metro 属于当前工程并能正常 Fast Refresh。
2. 运行 `pnpm run xuqm:doctor`
3. 确认模块 ID 与 `xuqm.modules.json` 一致。
4. 检查插件入口是否调用 `definePlugin`
5. 更新检查失败时,确认宿主仍会尝试打开本地版本。
6. 只有插件实际启动失败才记录启动失败,网络检查失败不是插件启动失败。
## BugCollect 没有数据
确认:
- 用户已经同意隐私声明。
- 租户平台已开启 BugCollect。
- 当前不是默认禁止自动上报的 Debug 行为。
- Release 构建没有启用应急关闭。
- 测试页调用 `sendTestErrorAndFlush` 时能看到明确错误。
BugCollect 失败不能影响宿主功能,不要通过抛出全局异常验证生产采集。
## XWebView 摄像头或麦克风失败
同时满足以下条件才会授权:
- 宿主声明并获得系统权限。
- `permissions.camera``permissions.microphone` 已开启。
- 当前网页 Origin 精确命中 `allowedOrigins`
- 自定义 `onRequest` 没有拒绝。
协议、端口或子域名变化都会形成不同 Origin。
## Push 没有通知
检查系统通知权限、设备厂商通道、包名与签名、租户平台服务状态、用户离线推送开关和免
打扰时间。RN 页面不应以是否缓存到厂商 Token 判断最终绑定状态。
## 从历史接入方式迁移
当前版本不再使用以下方式:
- 手动传 AppKey 初始化。
- 页面或子模块分别初始化、登录。
- 宿主手工传入应用商店地址。
- Update 的旧 Bundle 下载与应用方法。
- RN 页面手工注册推送 Token。
- XWebView 的扁平参数和全局控制器。
迁移步骤:
1. 下载并放置唯一配置文件。
2. 使用一个 Metro 包装器。
3. 隐私授权同步一次。
4. 登录和登出只调用 Common。
5. 按本文各模块页面替换旧 API。
6. 删除宿主重复的 SDK 状态、地址和 Token 缓存。
## 提交问题时提供
- SDK 包名与精确版本。
- React Native、Android/iOS 和设备系统版本。
- 公有化或私有化类型,不提供服务端秘密。
- 初始化状态、结构化错误码和最小复现路径。
- 已脱敏日志。
不要上传 `.xuqmconfig`、Token、用户隐私数据或完整业务响应。

查看文件

@ -1,161 +1,233 @@
# React Native 版本更新接入指南
# 版本与插件更新
**包名**`@xuqm/rn-update` · **功能**App 版本检查、RN Bundle 热更新、打开商店
`@xuqm/rn-update` 提供两类能力:
---
- Android 整包检查、下载、安装和应用商店跳转。
- 可选的 RN 插件检查、安装和启动。
## 1. 安装
SDK 返回更新信息和当前网络状态,是否弹窗、是否允许使用移动网络以及何时开始下载由宿主
决定。
```bash
yarn add @xuqm/rn-update
```
## 整包更新
`rn-update` 会自动依赖 `@xuqm/rn-common``@xuqm/rn-sdk`
---
## 2. App 版本检查
### 检查
```ts
import { UpdateSDK } from '@xuqm/rn-update'
const appUpdate = await UpdateSDK.checkAppUpdate()
if (appUpdate.needsUpdate) {
console.log('新版本:', appUpdate.versionName)
console.log('更新日志:', appUpdate.changeLog)
console.log('下载地址:', appUpdate.downloadUrl)
const result = await UpdateSDK.checkAppUpdate()
if (appUpdate.forceUpdate) {
// 强制更新
showForceUpdateModal(appUpdate)
} else {
// 可选更新
showOptionalUpdateModal(appUpdate)
}
switch (result.status) {
case 'UPDATE_AVAILABLE':
showUpdateDialog(result.update, result.network)
break
case 'LOGIN_REQUIRED':
case 'SERVICE_DISABLED':
case 'NO_UPDATE':
// 正常跳过,不阻断应用
break
case 'FAILED':
// 记录一次非阻断诊断
console.warn(result.errorCode, result.message)
break
}
```
App 版本号自动从原生模块读取,无需手动传入。开发/模拟器环境可调用 `_devSetAppVersion` 覆盖:
检查状态:
| 状态 | 宿主建议 |
| --- | --- |
| `UPDATE_AVAILABLE` | 根据 `forceUpdate` 展示宿主自己的更新弹窗 |
| `NO_UPDATE` | 继续启动 |
| `LOGIN_REQUIRED` | 未登录时跳过,登录后可再次检查 |
| `SERVICE_DISABLED` | 服务未启用,继续运行 |
| `FAILED` | 记录诊断,不阻断宿主 |
`result.network` 包含连接类型、是否已连接和是否按流量计费。租户平台不替宿主配置网络
策略。
### 下载并安装
用户确认后调用:
```ts
// 仅限开发环境使用
UpdateSDK._devSetAppVersion(100, '1.0.0')
```
---
## 3. 打开商店
```ts
await UpdateSDK.openStore(appUpdate.appStoreUrl, appUpdate.marketUrl)
```
- iOS打开 `appStoreUrl`
- Android打开 `marketUrl`
---
## 4. RN 插件更新
插件更新以服务端返回的 release-set 为最小事务单位。一个 release-set 只包含目标
app/buz 及其 Common 依赖;SDK 会统一完成签名、宿主身份、整包版本、原生基线、
SHA-256 和最终依赖闭包校验。宿主不能分别下载后自行拼接版本。
进入 buz 前自动检查并安装:
```ts
await UpdateSDK.checkAndInstall('home', {
onProgress(moduleId, progress) {
console.log(moduleId, progress.percent)
await UpdateSDK.downloadAndInstallApp(result.update!, {
destination: 'SDK_PRIVATE',
onProgress(progress) {
setProgress(progress.percent)
},
})
```
需要由宿主展示确认弹窗时,检查与安装分开调用
需要把安装包保留在系统 Downloads
```ts
const plan = await UpdateSDK.check('home')
if (plan && (await showPluginUpdateDialog(plan))) {
await UpdateSDK.install(plan)
}
await UpdateSDK.downloadAndInstallApp(result.update!, {
destination: 'PUBLIC_DOWNLOADS',
})
```
`check` 只检查,不下载、不修改本地状态。安装阶段会再次验证签名和检查时
记录的本地基线;计划已过期时必须重新检查。插件版本、路径和事务状态由原生层统一
管理,宿主不得另建一套缓存版本表。
下载失败或应用退出后不会在下次启动时自动继续。
---
## 5. 强制更新处理
`appUpdate.forceUpdate``true` 时,建议业务层:
1. 弹出不可关闭的 Modal
2. 只允许用户点击「立即更新」
3. 调用 `UpdateSDK.openStore()` 跳转商店
### 只下载、稍后安装
```ts
function showForceUpdateModal(update: AppUpdateInfo) {
// 使用 RN Modal 组件,设置不可取消
Alert.alert(
'发现重要更新',
`当前版本已不可用,请升级至 ${update.versionName}`,
[
{
text: '立即更新',
onPress: () => UpdateSDK.openStore(update.appStoreUrl, update.marketUrl),
},
],
{ cancelable: false }
)
}
import { NativeAppUpdate, UpdateSDK } from '@xuqm/rn-update'
const downloaded = await UpdateSDK.downloadApk(result.update!, {
destination: 'SDK_PRIVATE',
})
console.log(downloaded.localPath)
await NativeAppUpdate.installDownloaded({
versionCode: result.update!.versionCode!,
sha256: result.update!.sha256,
})
```
---
## 6. 多模块统一登录
Update 模块与 IM、Push 模块共享同一套登录态:
取消当前下载:
```ts
await XuqmSDK.initialize({ appKey: 'your_app_key' })
await XuqmSDK.login({ userId: 'user_001', userSig: 'your_user_sig_jwt' })
// UpdateSDK 在 checkAppUpdate 时自动携带 appKey,无需额外登录操作
await NativeAppUpdate.cancel()
```
---
如果系统禁止安装未知来源应用:
## 7. 一键打包上传
```ts
await NativeAppUpdate.openInstallPermissionSettings()
```
SDK 提供 `xuqm_release.mjs` 脚本,可一键完成 RN Bundle 构建并上传至 XuqmGroup 版本管理服务:
### 跳转应用商店
宿主不传商店地址,只传检查接口返回的更新对象:
```ts
const opened = await UpdateSDK.openStore(result.update!)
```
Android 会优先打开当前设备对应的应用商店,不可用时使用平台提供的通用下载页。iOS 或
HarmonyOS 没有配置商店地址时返回 `false`,宿主应提示当前无法提供下载服务。
### 忽略可选版本
```ts
await UpdateSDK.ignoreAppVersion(result.update!.versionCode!)
const ignoredVersion = await UpdateSDK.getIgnoredAppVersion()
await UpdateSDK.clearIgnoredAppVersion()
```
强制更新不能被忽略。
## 不启用插件化
普通项目可以只使用上述整包更新 API,不创建 `common/app/buz` 模块,也不需要接入插件
运行时。后续需要插件化时再执行初始化命令即可。
## 启用插件化
在项目根目录执行交互式初始化:
```bash
XUQM_API_TOKEN=*** node scripts/xuqm_release.mjs --platform android --publish
pnpm exec xuqm-rn init
pnpm run xuqm:doctor
```
脚本执行流程:
1. 按模块构建 JS/Hermes Bundle 和 Source Map
2. 生成根目录唯一的 `rn-manifest.json`
3. 生成 `<moduleId>.<platform>.xuqm.zip`
4. 上传归档并由服务端校验、签名
5. 按用户选择发布,返回不可变发布记录
初始化工具会创建 `xuqm.modules.json`、补充必要脚本并提示宿主完成最少原生配置。不要
手工复制其他项目的模块表。
常用参数:
Metro 使用:
| 参数 | 说明 |
|------|------|
| `--platform` | 平台:`android` / `ios` |
| `--module` | 可重复传入,只构建或发布指定模块 |
| `--note` | 本次发布说明 |
| `--publish` | 上传后立即发布 |
| `--bugcollect` | `platform` 上传 Source Map;`disabled` 仅保留本地产物 |
```js
const { getDefaultConfig } = require('@react-native/metro-config')
const { withXuqmModuleConfig } = require('@xuqm/rn-update/metro')
`package.json` 中添加快捷命令:
module.exports = withXuqmModuleConfig(getDefaultConfig(__dirname))
```
```json
{
"scripts": {
"xuqm:release": "node scripts/xuqm_release.mjs --platform android --publish"
}
Android 宿主应用一次构建脚本:
```groovy
apply from: file("../../node_modules/@xuqm/rn-update/android/xuqm-bundles.gradle")
```
### 注册宿主运行时
```ts
import { XuqmRuntime } from '@xuqm/rn-update'
XuqmRuntime.configure({
plugins: pluginRegistrations,
context: {
navigate(route, params) {
navigationRef.navigate(route, params)
},
emit(event, payload) {
hostEvents.emit(event, payload)
},
getHostState() {
return appStore.getState()
},
},
async loadBundle(_moduleId, bundlePath) {
await loadNativeBundle(bundlePath)
},
})
```
模块入口注册:
```ts
import { definePlugin } from '@xuqm/rn-update'
definePlugin({
moduleId: 'orders',
activate(context) {
context.navigate('OrdersHome')
},
})
```
### 检查与安装插件
自动完成检查和安装:
```ts
const plan = await UpdateSDK.checkAndInstallPlugin('orders', {
onProgress(moduleId, progress) {
console.log(moduleId, progress.percent)
},
})
await XuqmRuntime.activate('orders', {
reloadBundle: plan !== null,
})
```
宿主需要先展示确认弹窗时:
```ts
const plan = await UpdateSDK.checkPluginRelease('orders')
if (plan && (await showPluginUpdateDialog(plan))) {
await UpdateSDK.installPluginRelease(plan)
}
```
只检查不会下载或修改本地状态。插件检查失败时,宿主应继续打开已有的本地健康版本。
## 启动策略
- 启动时先检查整包更新。
- 不需要整包更新时,再检查 App 插件。
- 业务插件在进入前分别检查。
- Debug 默认不自动检查;开发页可以显式使用 `UpdateSDK.developer` 下的测试入口。
- 更新 SDK 不绘制业务弹窗,所有提示和确认交互由宿主实现。
## 打包与发布
初始化生成的脚本支持 Debug、Release、插件构建和上传。发布令牌只通过 CI 环境变量
`XUQM_API_TOKEN` 注入,不写入项目配置、源码或产物。
SDK 自身制品发布与应用/插件发布是不同操作。应用开发者不需要发布 SDK,也不得把 SDK
发布凭据加入宿主工程。

查看文件

@ -0,0 +1,249 @@
# XWebView
`@xuqm/rn-xwebview` 提供可直接打开的独立 WebView 页面,也支持在业务页面中嵌入浏览器
内核。SDK 只提供通用容器和 Bridge 接入入口,不内置任何宿主业务协议。
## 安装
```bash
pnpm add @xuqm/rn-common @xuqm/rn-xwebview \
react-native-webview react-native-svg \
@react-native-clipboard/clipboard react-native-safe-area-context
```
这些原生依赖需要由宿主直接声明,以便 React Native 自动链接。
## 根部挂载 Host
独立页面模式需要在应用根部挂载一次:
```tsx
import { XWebViewHost } from '@xuqm/rn-xwebview'
export function App() {
return (
<>
<RootNavigation />
<XWebViewHost />
</>
)
}
```
全局只能存在一个 `XWebViewHost`
## 打开独立页面
```ts
import { openWebView } from '@xuqm/rn-xwebview'
const page = openWebView({
source: {
uri: 'https://example.com',
headers: {
'X-From-App': 'true',
},
},
navigationBar: {
title: {
text: '服务页面',
mode: 'web',
},
},
statusBar: {
translucent: true,
contentStyle: 'dark-content',
},
})
const result = await page.closed
console.log(result.reason, result.data)
```
返回的页面 Handle 可以:
```ts
page.reload()
page.goBack()
page.goForward()
page.postMessage(JSON.stringify({ type: 'refresh' }))
page.evaluateJavaScript('window.refreshPage?.()')
page.suspend()
page.resume()
page.close('api', { completed: true })
```
`suspend()` 用于临时展示宿主原生页面,页面实例和网页历史会保留;完成后调用
`resume()`
## 默认导航和返回规则
- 左上角返回按钮只在当前网页存在历史页面时显示,只负责网页后退。
- 右上角关闭按钮始终显示,点击立即关闭当前 WebView 页面。
- Android 物理返回键优先返回网页历史。
- 没有网页历史时,物理返回键需要在一秒内再次按下才关闭页面。
- 默认菜单包含刷新、复制链接和在系统浏览器打开,不包含关闭页面。
## 导航栏与状态栏
```ts
openWebView({
source: { uri: 'https://example.com' },
navigationBar: {
background: {
gradient: ['#1769E0', '#55A8FF'],
gradientAngle: 90,
// 也可以使用 color 或 image
},
title: {
text: '渐变导航栏',
color: '#FFFFFF',
mode: 'fixed',
},
backButton: { color: '#FFFFFF' },
closeButton: { color: '#FFFFFF' },
},
statusBar: {
backgroundColor: '#1769E0',
contentStyle: 'light-content',
translucent: true,
},
})
```
网络背景图片异步加载,不会阻塞 `openWebView` 返回。
完整替换导航栏时使用 `navigationBar.render(context)`。自定义返回按钮应调用
`context.onBackPress()`,以保留统一的网页后退规则。
## 自定义菜单
```tsx
openWebView({
source: { uri: 'https://example.com' },
navigationBar: {
menu: {
visible: true,
items: [
{
id: 'feedback',
label: '问题反馈',
onPress({ navigationState }) {
openFeedback({ url: navigationState.url })
},
},
],
},
},
})
```
不需要菜单时设置 `menu.visible: false`
## 摄像头和麦克风
宿主先声明系统权限,然后为具体网页配置精确 Origin
```ts
openWebView({
source: { uri: 'https://verify.example.com/start' },
permissions: {
allowedOrigins: ['https://verify.example.com'],
camera: true,
microphone: true,
},
})
```
Android Manifest 示例:
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
Origin 必须包含协议和主机,跨域后的页面不会继承原页面授权。宿主需要自定义决策时:
```ts
permissions: {
allowedOrigins: ['https://verify.example.com'],
camera: true,
microphone: true,
onRequest(request) {
if (canUseMedia(request.origin)) request.grant()
else request.deny()
},
}
```
## 文件下载
```ts
openWebView({
source: { uri: 'https://example.com/files' },
downloads: {
auto: true,
destination: 'publicDownloads',
conflict: 'rename',
onProgress(progress) {
console.log(progress.percentage)
},
onComplete(result) {
console.log(result.filePath)
},
onError(url, error) {
console.warn(url, error)
},
},
})
```
默认保存到应用沙盒。保存到系统 Downloads 只在目标平台支持时生效。
## 宿主 Bridge
宿主与 H5 自行约定消息协议,通过 `bridge` 注入:
```ts
openWebView({
source: { uri: 'https://example.com' },
bridge: {
async onMessage(raw, context) {
const message = JSON.parse(raw)
if (message.method === 'close') {
context.close('h5_close', message.data)
return true
}
if (message.method === 'setTitle') {
context.updateNavigation({
title: { text: String(message.title ?? '') },
})
return true
}
return false
},
},
})
```
XWebView 不规定方法名、App ID、厂商、用户或业务参数。协议校验、权限和业务结果由宿主
自己的 Bridge 负责。
## 内嵌 WebView
不需要独立页面和内置导航栏时:
```tsx
import { XWebViewView } from '@xuqm/rn-xwebview'
<XWebViewView
config={{
source: { uri: 'https://example.com' },
}}
/>
```
内嵌模式由所在页面负责尺寸、导航和关闭交互。

查看文件

@ -4,6 +4,8 @@
"private": true,
"type": "module",
"scripts": {
"check:rn-contract": "node scripts/check-rn-docs.mjs",
"prebuild": "node scripts/check-rn-docs.mjs",
"dev": "vitepress dev docs",
"build": "vitepress build docs",
"preview": "vitepress preview docs"

查看文件

@ -0,0 +1,171 @@
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const docsSiteRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const rnDocsRoot = path.join(docsSiteRoot, 'docs', 'rn')
const vitepressConfig = path.join(docsSiteRoot, 'docs', '.vitepress', 'config.ts')
const requiredPages = [
'index.md',
'setup.md',
'common.md',
'session.md',
'update.md',
'bugcollect.md',
'xwebview.md',
'im.md',
'push.md',
'troubleshooting.md',
]
const forbiddenPatterns = [
[/@xuqm\/rn-sdk\b/g, '内部聚合包不应进入对外文档'],
[/\bXuqmSDK\.initialize\s*\(/g, '已删除的手动初始化 API'],
[/\bcheckRnUpdate\s*\(/g, '已删除的旧 Update API'],
[/\bdownloadAndApplyBundle\s*\(/g, '已删除的旧 Bundle API'],
[/\brequestNativeRegistration\s*\(/g, '已删除的 RN Push Token API'],
[/\bregisterToken\s*\(/g, '已删除的 RN Push Token API'],
[/\bsetDeviceToken\s*\(/g, '已删除的 RN Push Token API'],
[/@xuqm\/rn-common\/internal(?:-security)?\b/g, '官方扩展专用内部入口'],
[/\b(?:release[- ]set|Ed25519|NativeBundle|planReleaseSet)\b/gi, '内部更新实现细节'],
[/(?:原生基线|签名算法|数据库隔离规则|服务拓扑)/g, '内部架构细节'],
]
const requiredPackagePages = new Map([
['@xuqm/rn-common', ['index.md', 'setup.md', 'common.md', 'session.md']],
['@xuqm/rn-update', ['update.md']],
['@xuqm/rn-bugcollect', ['bugcollect.md']],
['@xuqm/rn-xwebview', ['xwebview.md']],
['@xuqm/rn-im', ['im.md']],
['@xuqm/rn-push', ['push.md']],
])
const failures = []
const sourceByPage = new Map()
for (const page of requiredPages) {
const file = path.join(rnDocsRoot, page)
if (!fs.existsSync(file)) {
failures.push(`缺少 RN 对外文档页面:${page}`)
continue
}
sourceByPage.set(page, fs.readFileSync(file, 'utf8'))
}
const actualMarkdownPages = fs
.readdirSync(rnDocsRoot, { withFileTypes: true })
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
.map(entry => entry.name)
.sort()
for (const page of actualMarkdownPages) {
if (!requiredPages.includes(page)) {
failures.push(`存在未纳入公开信息架构的 RN 页面:${page}`)
}
}
for (const [page, source] of sourceByPage) {
for (const [pattern, reason] of forbiddenPatterns) {
pattern.lastIndex = 0
if (pattern.test(source)) {
failures.push(`${page} 包含禁止内容(${reason}${pattern}`)
}
}
}
for (const [packageName, pages] of requiredPackagePages) {
for (const page of pages) {
if (!sourceByPage.get(page)?.includes(packageName)) {
failures.push(`${page} 未明确记录公开包 ${packageName}`)
}
}
}
const navigation = fs.readFileSync(vitepressConfig, 'utf8')
for (const page of requiredPages) {
const route = page === 'index.md' ? "/rn/'" : `/rn/${page.replace(/\.md$/, '')}'`
if (!navigation.includes(route)) {
failures.push(`VitePress RN 导航缺少 ${page}`)
}
}
if (navigation.includes('/rn/group')) {
failures.push('VitePress RN 导航仍引用已经合并的重复群聊页面')
}
// 本地多仓工作区存在 RN SDK 源码时,继续核对文档中的命名导入确实由 package index 导出。
// Jenkins 单仓构建没有兄弟仓库时跳过此增强检查,但上面的静态契约仍然生效。
const sdkRoot =
process.env.XUQM_RN_SDK_DIR ??
path.resolve(docsSiteRoot, '..', '..', 'XuqmGroup-RNSDK')
if (fs.existsSync(path.join(sdkRoot, 'packages'))) {
const indexByPackage = new Map()
for (const packageDir of ['common', 'update', 'bugcollect', 'xwebview', 'im', 'push']) {
const packageJson = JSON.parse(
fs.readFileSync(path.join(sdkRoot, 'packages', packageDir, 'package.json'), 'utf8'),
)
indexByPackage.set(
packageJson.name,
fs.readFileSync(path.join(sdkRoot, 'packages', packageDir, 'src', 'index.ts'), 'utf8'),
)
}
const namedImport =
/import\s*\{([\s\S]*?)\}\s*from\s*['"](@xuqm\/rn-(?:common|update|bugcollect|xwebview|im|push))['"]/g
for (const [page, source] of sourceByPage) {
for (const match of source.matchAll(namedImport)) {
const packageSource = indexByPackage.get(match[2])
const symbols = match[1]
.split(',')
.map(value => value.trim().replace(/^type\s+/, '').split(/\s+as\s+/)[0])
.filter(value => /^[A-Za-z_$][\w$]*$/.test(value))
for (const symbol of symbols) {
const exported = new RegExp(
`(?:export\\s+(?:const|class|function|type|interface)\\s+${symbol}\\b|export\\s*\\{[\\s\\S]*?\\b${symbol}\\b[\\s\\S]*?\\})`,
).test(packageSource)
if (!exported) {
failures.push(`${page}${match[2]} 导入未公开的符号:${symbol}`)
}
}
}
}
const objectSources = new Map([
['XuqmSDK', path.join(sdkRoot, 'packages', 'common', 'src', 'sdk.ts')],
['UpdateSDK', path.join(sdkRoot, 'packages', 'update', 'src', 'UpdateSDK.ts')],
[
'NativeAppUpdate',
path.join(sdkRoot, 'packages', 'update', 'src', 'NativeAppUpdate.ts'),
],
['XuqmRuntime', path.join(sdkRoot, 'packages', 'update', 'src', 'PluginRuntime.ts')],
['BugCollect', path.join(sdkRoot, 'packages', 'bugcollect', 'src', 'BugCollect.ts')],
['ImSDK', path.join(sdkRoot, 'packages', 'im', 'src', 'ImSDK.ts')],
['PushSDK', path.join(sdkRoot, 'packages', 'push', 'src', 'PushSDK.ts')],
])
for (const [objectName, sourceFile] of objectSources) {
const implementation = fs.readFileSync(sourceFile, 'utf8')
const usage = new RegExp(`\\b${objectName}\\.([A-Za-z_$][\\w$]*)\\s*\\(`, 'g')
for (const [page, source] of sourceByPage) {
for (const match of source.matchAll(usage)) {
const method = match[1]
const declared = new RegExp(
`(?:^|\\n)\\s*(?:async\\s+)?${method}\\s*(?:\\([^\\n]*|:)`,
).test(implementation)
if (!declared) {
failures.push(`${page} 调用 ${objectName} 中不存在的公开方法:${method}`)
}
}
}
}
}
if (failures.length > 0) {
console.error('RN 对外文档契约检查失败:')
for (const failure of failures) console.error(`- ${failure}`)
process.exit(1)
}
console.log(`RN 对外文档契约检查通过:${requiredPages.length} 个页面,6 个公开模块`)

查看文件

@ -0,0 +1,62 @@
# RN 对外文档边界
## 目的
`docs-site/docs/rn/` 是面向宿主集成方的公开文档,只描述安装、配置、公开 API、宿主交互、
错误处理、平台差异和可执行的排障步骤。内部设计继续保留在对应 SDK 仓库,不进入公开
站点导航、搜索索引或示例。
## 事实来源
文档修改按以下优先级核对:
1. `XuqmGroup-RNSDK/packages/*/src/index.ts` 的真实公开导出。
2. 公开类型定义和公开对象方法。
3. 当前测试、可运行示例和构建脚本。
4. 租户平台当前实际提供的配置与功能。
包内 README、历史文档、注释和旧站点内容只能作为线索,不能直接复制到公开站点。
## 当前公开模块
- `@xuqm/rn-common`
- `@xuqm/rn-update`
- `@xuqm/rn-bugcollect`
- `@xuqm/rn-xwebview`
- `@xuqm/rn-im`
- `@xuqm/rn-push`
未实现、未发布或没有稳定公开契约的模块和 API 不得预先出现在文档站。
## 可以公开
- 安装依赖和只读制品仓库。
- 平台配置文件的下载位置与宿主放置方式。
- 自动配置、公共登录和隐私授权的调用方式。
- 宿主需要调用的公开 API、参数、返回状态和错误码。
- Android/iOS 已验证范围及权限、Manifest、Info.plist 要求。
- 插件化的创建、打包、上传和宿主调用方式。
- 不涉及安全实现的默认行为、升级说明与排障清单。
## 禁止公开
- 官方扩展包专用的 internal 子路径和内部聚合包。
- 配置解密、签名算法、可信密钥、不可变清单和校验实现。
- 插件安装事务、内部状态表、回滚存储和版本规划算法。
- 数据库选型、表结构、用户隔离键和内部缓存格式。
- 服务拓扑、内部接口、内部令牌、私有化部署脚本和运维地址。
- App4、医网信或其他单一宿主的页面、厂商、域名和业务协议。
- 凭据、Token、配置文件正文、用户隐私数据和真实业务响应。
- 没有源码依据的示例 API。
## 自动检查
`yarn workspace @xuqm/docs-site check:rn-contract` 检查:
- 十个 RN 公开页面和导航完整性。
- 六个公开包是否均有入口。
- 历史 API、internal 入口和内部实现词汇。
- 在本地多仓工作区中,文档命名导入和对象方法是否确实由当前 RN SDK 源码公开。
文档站构建前自动执行同一检查。Jenkins 单仓构建无法读取兄弟 SDK 仓库时仍执行静态
边界检查;合并前必须在完整本地工作区至少执行一次源码增强检查。

查看文件

@ -1,6 +1,8 @@
# 租户平台 SDK V2 页面接手说明
SDK 平台的完整服务端契约见 Server 仓库 `docs/SDK_PLATFORM_V2_HANDOFF.md`
RN 对外文档的内容边界、事实来源和自动检查见
[`RN_PUBLIC_DOCUMENTATION_POLICY.md`](./RN_PUBLIC_DOCUMENTATION_POLICY.md)。
当前 Web 变更:
@ -25,6 +27,7 @@ SDK 平台的完整服务端契约见 Server 仓库 `docs/SDK_PLATFORM_V2_HANDOF
yarn install --frozen-lockfile
yarn build:tenant
yarn build:ops
yarn workspace @xuqm/docs-site check:rn-contract
yarn workspace @xuqm/docs-site build
yarn test:web-cache
nginx -t -p "$PWD/" -c scripts/nginx-test.conf