feat(im): 添加即时消息SDK核心功能实现
- 实现了聊天消息发送功能,支持文本、图片、视频、音频、文件等多种消息类型 - 集成了文件上传下载功能,支持多媒体文件的传输和管理 - 添加了群组管理功能,包括创建群组、成员管理、权限控制等操作 - 实现了好友系统,支持好友添加、删除、分组等功能 - 集成了黑名单管理,提供用户屏蔽和解除屏蔽功能 - 添加了会话管理功能,支持对话列表、未读消息统计等 - 实现了历史消息查询和搜索功能 - 添加了实时连接状态管理和自动重连机制
这个提交包含在:
父节点
6e42862512
当前提交
f423a2acb2
@ -1,6 +1,5 @@
|
|||||||
export interface SDKConfig {
|
export interface SDKConfig {
|
||||||
appKey: string
|
appKey: string
|
||||||
appSecret: string
|
|
||||||
debug: boolean
|
debug: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import webSocket from '@ohos.net.webSocket'
|
import webSocket from '@ohos.net.webSocket'
|
||||||
|
import http from '@ohos.net.http'
|
||||||
import { HttpClient } from '../core/HttpClient'
|
import { HttpClient } from '../core/HttpClient'
|
||||||
import { SDKContext } from '../core/SDKContext'
|
import { SDKContext } from '../core/SDKContext'
|
||||||
import { DEFAULT_IM_WS_URL } from '../core/Endpoints'
|
import { DEFAULT_API_BASE_URL, DEFAULT_IM_WS_URL } from '../core/Endpoints'
|
||||||
import type {
|
import type {
|
||||||
ChatType,
|
ChatType,
|
||||||
ConversationData,
|
ConversationData,
|
||||||
@ -23,6 +24,7 @@ export interface ImEventDelegate {
|
|||||||
onRead?(msg: ImMessage): void
|
onRead?(msg: ImMessage): void
|
||||||
onRevoke?(data: RevokeData): void
|
onRevoke?(data: RevokeData): void
|
||||||
onError?(message: string): void
|
onError?(message: string): void
|
||||||
|
onConversationsChange?(conversations: ConversationData[]): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RevokeData {
|
export interface RevokeData {
|
||||||
@ -155,8 +157,10 @@ export class ImClient {
|
|||||||
this.delegate?.onRevoke?.({ msgId: message.id, operatorId: message.fromId })
|
this.delegate?.onRevoke?.({ msgId: message.id, operatorId: message.fromId })
|
||||||
}
|
}
|
||||||
this.delegate?.onMessage?.(message)
|
this.delegate?.onMessage?.(message)
|
||||||
|
this.notifyConversations()
|
||||||
} else if (frame.type === 'REVOKE') {
|
} else if (frame.type === 'REVOKE') {
|
||||||
this.delegate?.onRevoke?.(frame.payload as RevokeData)
|
this.delegate?.onRevoke?.(frame.payload as RevokeData)
|
||||||
|
this.notifyConversations()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore malformed frames
|
// ignore malformed frames
|
||||||
@ -705,6 +709,80 @@ export class ImClient {
|
|||||||
return parts.join('&')
|
return parts.join('&')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendImageMessage(toId: string, chatType: ChatType, filePath: string, width?: number, height?: number): Promise<ImMessage> {
|
||||||
|
const uploadResult = await this.uploadFile(filePath)
|
||||||
|
const url = (uploadResult as Record<string, string>)['url'] ?? ''
|
||||||
|
const thumbnailUrl = (uploadResult as Record<string, string>)['thumbnailUrl']
|
||||||
|
const contentObj: Record<string, unknown> = { url }
|
||||||
|
if (width !== undefined) contentObj.width = width
|
||||||
|
if (height !== undefined) contentObj.height = height
|
||||||
|
if (thumbnailUrl) contentObj.thumbnailUrl = thumbnailUrl
|
||||||
|
return this.send({ toId, chatType, msgType: 'IMAGE', content: JSON.stringify(contentObj) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendVideoMessage(toId: string, chatType: ChatType, filePath: string, width?: number, height?: number, duration?: number): Promise<ImMessage> {
|
||||||
|
const uploadResult = await this.uploadFile(filePath)
|
||||||
|
const url = (uploadResult as Record<string, string>)['url'] ?? ''
|
||||||
|
const thumbnailUrl = (uploadResult as Record<string, string>)['thumbnailUrl']
|
||||||
|
const contentObj: Record<string, unknown> = { url }
|
||||||
|
if (width !== undefined) contentObj.width = width
|
||||||
|
if (height !== undefined) contentObj.height = height
|
||||||
|
if (duration !== undefined) contentObj.duration = duration
|
||||||
|
if (thumbnailUrl) contentObj.thumbnailUrl = thumbnailUrl
|
||||||
|
return this.send({ toId, chatType, msgType: 'VIDEO', content: JSON.stringify(contentObj) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendFileMessage(toId: string, chatType: ChatType, filePath: string): Promise<ImMessage> {
|
||||||
|
const uploadResult = await this.uploadFile(filePath)
|
||||||
|
const url = (uploadResult as Record<string, string>)['url'] ?? ''
|
||||||
|
const name = (uploadResult as Record<string, string>)['originalName'] ?? ''
|
||||||
|
const size = (uploadResult as Record<string, number>)['size'] ?? 0
|
||||||
|
const contentObj: Record<string, unknown> = { url, name, size }
|
||||||
|
return this.send({ toId, chatType, msgType: 'FILE', content: JSON.stringify(contentObj) })
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendAudioMessage(toId: string, chatType: ChatType, filePath: string, duration?: number): Promise<ImMessage> {
|
||||||
|
const uploadResult = await this.uploadFile(filePath)
|
||||||
|
const url = (uploadResult as Record<string, string>)['url'] ?? ''
|
||||||
|
const contentObj: Record<string, unknown> = { url }
|
||||||
|
if (duration !== undefined) contentObj.duration = duration
|
||||||
|
return this.send({ toId, chatType, msgType: 'AUDIO', content: JSON.stringify(contentObj) })
|
||||||
|
}
|
||||||
|
|
||||||
|
private async uploadFile(filePath: string): Promise<Object> {
|
||||||
|
const token = SDKContext.getToken()
|
||||||
|
const url = DEFAULT_API_BASE_URL.replace(/\/$/, '') + '/api/file/upload'
|
||||||
|
const client = http.createHttp()
|
||||||
|
try {
|
||||||
|
const header: Record<string, string> = {}
|
||||||
|
if (token) header.Authorization = 'Bearer ' + token
|
||||||
|
const multipartForm = new http.MultipartForm()
|
||||||
|
multipartForm.addFile('file', filePath, filePath.substring(filePath.lastIndexOf('/') + 1))
|
||||||
|
const options: http.HttpRequestOptions = {
|
||||||
|
method: http.RequestMethod.POST,
|
||||||
|
header,
|
||||||
|
extraData: multipartForm,
|
||||||
|
connectTimeout: 60000,
|
||||||
|
readTimeout: 60000,
|
||||||
|
}
|
||||||
|
const res = await client.request(url, options)
|
||||||
|
const json = JSON.parse(res.result as string) as { code: number; message: string; data: Object }
|
||||||
|
if (json.code !== 200) throw new Error(json.message)
|
||||||
|
return json.data
|
||||||
|
} finally {
|
||||||
|
client.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notifyConversations(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const conversations = await this.listConversations()
|
||||||
|
this.delegate?.onConversationsChange?.(conversations)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
this.destroyed = true
|
this.destroyed = true
|
||||||
if (this.reconnectTimer !== null) {
|
if (this.reconnectTimer !== null) {
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户