feat(sdk): 添加即时通讯和推送功能

- 新增 ApiClient 类用于处理 API 请求和响应
- 实现 ImClient 类支持 WebSocket 连接和消息收发
- 添加 ImSDK 类提供完整的即时通讯功能接口
- 定义 ImTypes.swift 包含聊天类型、消息类型等相关数据结构
- 实现 PushSDK 类支持推送通知令牌注册
- 添加基础的 UpdateSDK 框架结构
- 集成登录认证和聊天室订阅功能
- 实现群组管理、好友关系和会话功能
- 支持多种消息类型包括文本、图片、视频、音频等
- 提供历史消息查询和黑名单管理功能
这个提交包含在:
XuqmGroup 2026-04-28 10:27:23 +08:00
父节点 edbffce815
当前提交 9de1059e25
共有 6 个文件被更改,包括 729 次插入279 次删除

查看文件

@ -1,3 +1,4 @@
import { _getToken, getConfig } from '@xuqm/rn-common'
import type { ImEventListener, ImMessage, SendMessageParams } from './types'
interface StompFrame {
@ -14,15 +15,32 @@ export class ImClient {
private shouldReconnect = true
private readonly subscriptionId = 'sub-user-queue'
private groupSubscriptions = new Set<string>()
private activeWsUrl: string | null = null
private activeToken: string | null = null
private activeAppId: string | null = null
constructor(
private readonly wsUrl: string,
private readonly token: string,
private readonly appId: string,
private readonly wsUrl?: string,
private readonly token?: string,
private readonly appId?: string,
) {}
connect() {
async connect() {
this.shouldReconnect = true
this.activeWsUrl = this.wsUrl ?? getConfig().imWsUrl
this.activeToken = this.token ?? null
this.activeAppId = this.appId ?? getConfig().appId
if (!this.activeToken) {
this.activeToken = await _getToken()
}
if (!this.activeWsUrl) {
this.listeners.forEach(listener => listener.onError?.('IM websocket URL not configured'))
return
}
if (!this.activeToken) {
this.listeners.forEach(listener => listener.onError?.('IM token not configured'))
return
}
this.openSocket()
}
@ -46,6 +64,9 @@ export class ImClient {
if (this.ws?.readyState !== WebSocket.OPEN) {
throw new Error('IM not connected')
}
if (!this.activeAppId) {
throw new Error('IM appId not configured')
}
this.sendFrame(
'SEND',
@ -54,7 +75,7 @@ export class ImClient {
'content-type': 'application/json',
},
JSON.stringify({
appId: this.appId,
appId: this.activeAppId,
toId: params.toId,
chatType: params.chatType,
msgType: params.msgType,
@ -68,6 +89,9 @@ export class ImClient {
if (this.ws?.readyState !== WebSocket.OPEN) {
throw new Error('IM not connected')
}
if (!this.activeAppId) {
throw new Error('IM appId not configured')
}
this.sendFrame(
'SEND',
@ -76,19 +100,28 @@ export class ImClient {
'content-type': 'application/json',
},
JSON.stringify({
appId: this.appId,
appId: this.activeAppId,
messageId,
}),
)
}
subscribeGroup(groupId: string) {
const alreadySubscribed = this.groupSubscriptions.has(groupId)
this.groupSubscriptions.add(groupId)
if (this.ws?.readyState === WebSocket.OPEN) {
if (alreadySubscribed) return
this.subscribe(`/topic/group/${groupId}`, `group-${groupId}`)
}
}
unsubscribeGroup(groupId: string) {
this.groupSubscriptions.delete(groupId)
if (this.ws?.readyState === WebSocket.OPEN) {
this.sendFrame('UNSUBSCRIBE', { id: `group-${groupId}` })
}
}
addListener(listener: ImEventListener) {
this.listeners.push(listener)
}
@ -112,12 +145,26 @@ export class ImClient {
}
private openSocket() {
this.ws = new WebSocket(this.wsUrl)
if (!this.activeWsUrl) return
this.ws = new WebSocket(this.activeWsUrl)
this.ws.onopen = () => {
if (!this.activeToken) {
void _getToken().then(token => {
this.activeToken = token
if (token) {
this.sendFrame('CONNECT', {
'accept-version': '1.2',
Authorization: `Bearer ${this.token}`,
Authorization: `Bearer ${token}`,
'heart-beat': '10000,10000',
})
}
})
return
}
this.sendFrame('CONNECT', {
'accept-version': '1.2',
Authorization: `Bearer ${this.activeToken}`,
'heart-beat': '10000,10000',
})
}

文件差异内容过多而无法显示 加载差异

查看文件

@ -243,6 +243,28 @@ export const ImDatabase = {
}
},
async deleteConversation(appId: string, targetId: string, chatType: string, currentUserId: string): Promise<void> {
const db = getDb()
const convId = conversationId(appId, currentUserId, targetId, chatType)
const convs = await db
.get<ConversationModel>('im_conversations')
.query(Q.where('app_id', appId), Q.where('target_id', targetId))
.fetch()
const messages = await db
.get<MessageModel>('im_messages')
.query(Q.where('conversation_id', convId))
.fetch()
await db.write(async () => {
for (const message of messages) {
await message.destroyPermanently()
}
for (const conversation of convs) {
await conversation.destroyPermanently()
}
})
},
isInitialized(): boolean {
return _db !== null
},

查看文件

@ -1,5 +1,4 @@
export { ImSDK } from './ImSDK'
export type { ConversationData } from './ImSDK'
import { ImSDK as _ImSDK } from './ImSDK'
// Convenience named exports for friend APIs
@ -12,6 +11,10 @@ export type { MessageSearchParams } from './db/ImDatabase'
export type {
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
ImEventListener, SendMessageParams,
ConversationData,
FriendRequest,
GroupJoinRequest,
BlacklistEntry,
} from './types'
export { uploadFile } from './upload'
export type { UploadResult } from './upload'

查看文件

@ -12,10 +12,12 @@ export type MsgType =
| 'RICH_TEXT'
| 'CALL_AUDIO'
| 'CALL_VIDEO'
| 'QUOTE'
| 'MERGE'
| 'REVOKED'
| 'FORWARD'
export type MsgStatus = 'SENT' | 'DELIVERED' | 'READ' | 'REVOKED'
export type MsgStatus = 'SENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED' | 'REVOKED'
export interface ImMessage {
id: string
@ -27,7 +29,8 @@ export interface ImMessage {
content: string
status: MsgStatus
mentionedUserIds?: string
createdAt: string
groupReadCount?: number
createdAt: number
}
export interface ImEventListener {
@ -50,8 +53,51 @@ export interface ImGroup {
id: string
appId: string
name: string
groupType?: string
creatorId: string
memberIds: string
adminIds: string
createdAt: string
announcement?: string | null
createdAt: number
}
export interface ConversationData {
targetId: string
chatType: ChatType
lastMsgContent?: string | null
lastMsgType?: string | null
lastMsgTime: number
unreadCount: number
isMuted: boolean
isPinned: boolean
}
export interface FriendRequest {
id: string
appId: string
fromUserId: string
toUserId: string
remark?: string | null
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
createdAt: number
reviewedAt?: number | null
}
export interface GroupJoinRequest {
id: string
appId: string
groupId: string
requesterId: string
remark?: string | null
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
createdAt: number
reviewedAt?: number | null
}
export interface BlacklistEntry {
id: string
appId: string
userId: string
blockedUserId: string
createdAt: number
}

查看文件

@ -15,6 +15,9 @@ export type {
ImMessage, ImGroup, ChatType, MsgType, MsgStatus,
ImEventListener, SendMessageParams,
ConversationData,
FriendRequest,
GroupJoinRequest,
BlacklistEntry,
MessageSearchParams,
UploadResult,
} from '../packages/im/src'