fix: 功能缺陷修复 — ImSDK login await WebSocket、fetchHistory 支持 GROUP、License 改用 apiRequest、LogQueue 重试上限

这个提交包含在:
XuqmGroup 2026-06-16 13:49:30 +08:00
父节点 a0c54ae191
当前提交 611f2ba95d
共有 4 个文件被更改,包括 23 次插入19 次删除

查看文件

@ -18,6 +18,7 @@ export class ImClient {
private activeWsUrl: string | null = null private activeWsUrl: string | null = null
private activeToken: string | null = null private activeToken: string | null = null
private activeAppKey: string | null = null private activeAppKey: string | null = null
private connectedResolvers: Array<() => void> = []
constructor( constructor(
private readonly wsUrl?: string, private readonly wsUrl?: string,
@ -25,7 +26,7 @@ export class ImClient {
private readonly appKey?: string, private readonly appKey?: string,
) {} ) {}
async connect() { async connect(): Promise<void> {
this.shouldReconnect = true this.shouldReconnect = true
this.activeWsUrl = this.wsUrl ?? getConfig().imWsUrl this.activeWsUrl = this.wsUrl ?? getConfig().imWsUrl
this.activeToken = this.token ?? null this.activeToken = this.token ?? null
@ -42,6 +43,10 @@ export class ImClient {
return return
} }
this.openSocket() this.openSocket()
// 等待 STOMP CONNECTED 帧
await new Promise<void>(resolve => {
this.connectedResolvers.push(resolve)
})
} }
sendMessage( sendMessage(
@ -222,6 +227,8 @@ export class ImClient {
this.subscribe(`/topic/group/${groupId}`, `group-${groupId}`) this.subscribe(`/topic/group/${groupId}`, `group-${groupId}`)
}) })
this.sendSync() this.sendSync()
this.connectedResolvers.forEach(resolve => resolve())
this.connectedResolvers = []
this.listeners.forEach(listener => listener.onConnected?.()) this.listeners.forEach(listener => listener.onConnected?.())
return return
} }

查看文件

@ -320,7 +320,7 @@ export const ImSDK = {
_syncHistoryForAllConversations().catch(() => {}) _syncHistoryForAllConversations().catch(() => {})
}, },
}) })
void client.connect() await client.connect()
}, },
async reconnect(): Promise<void> { async reconnect(): Promise<void> {
@ -328,17 +328,18 @@ export const ImSDK = {
const token = await _getToken() const token = await _getToken()
if (!token) throw new Error('[ImSDK] No active session — call login() first.') if (!token) throw new Error('[ImSDK] No active session — call login() first.')
client = new ImClient(config.imWsUrl, token, config.appKey) client = new ImClient(config.imWsUrl, token, config.appKey)
void client.connect() await client.connect()
}, },
async fetchHistory( async fetchHistory(
toId: string, toId: string,
page = 0, page = 0,
size = 20, size = 20,
chatType: ChatType = 'SINGLE',
): Promise<ImMessage[]> { ): Promise<ImMessage[]> {
const config = getConfig() const config = getConfig()
if (ImDatabase.isInitialized() && page === 0 && _currentUserId) { if (ImDatabase.isInitialized() && page === 0 && _currentUserId) {
const local = await ImDatabase.getMessages(config.appKey, toId, 'SINGLE', _currentUserId, size) const local = await ImDatabase.getMessages(config.appKey, toId, chatType, _currentUserId, size)
if (local.length > 0) { if (local.length > 0) {
return local.map(model => ({ return local.map(model => ({
id: model.serverId, id: model.serverId,

查看文件

@ -1,5 +1,5 @@
import { Platform } from 'react-native' import { Platform } from 'react-native'
import { getDeviceId as getCommonDeviceId, getDeviceInfo, awaitInitialization, getConfig, getUserId } from '@xuqm/rn-common' import { getDeviceId as getCommonDeviceId, getDeviceInfo, awaitInitialization, getConfig, getUserId, apiRequest } from '@xuqm/rn-common'
import * as store from './store' import * as store from './store'
import type { LicenseResult, LicenseStatus, LicenseUserInfo, RegisterRequest, RegisterResponse, VerifyRequest, VerifyResponse } from './models' import type { LicenseResult, LicenseStatus, LicenseUserInfo, RegisterRequest, RegisterResponse, VerifyRequest, VerifyResponse } from './models'
@ -101,15 +101,5 @@ async function _persistStatus(status: string): Promise<void> {
} }
async function _post<T>(url: string, body: unknown): Promise<T> { async function _post<T>(url: string, body: unknown): Promise<T> {
const res = await fetch(url, { return apiRequest<T>(url, { method: 'POST', body })
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (!res.ok) {
const err = await res.json().catch(() => ({ message: res.statusText })) as { message?: string }
throw new Error(err.message ?? `HTTP ${res.status}`)
}
const json = await res.json() as { data?: T }
return (json.data ?? json) as T
} }

查看文件

@ -5,9 +5,11 @@ const QUEUE_KEY = '@xuqm_log:queue'
const BATCH_SIZE = 30 const BATCH_SIZE = 30
const FLUSH_INTERVAL_MS = 10_000 // 10 seconds const FLUSH_INTERVAL_MS = 10_000 // 10 seconds
const MAX_QUEUE_SIZE = 500 const MAX_QUEUE_SIZE = 500
const MAX_RETRY = 3
export class LogQueue { export class LogQueue {
private flushTimer: ReturnType<typeof setInterval> | null = null private flushTimer: ReturnType<typeof setInterval> | null = null
private retryCount = 0
constructor(private cfg: { logApiUrl: string; appKey: string }) { constructor(private cfg: { logApiUrl: string; appKey: string }) {
this.flushTimer = setInterval(() => { this.flushTimer = setInterval(() => {
@ -35,11 +37,15 @@ export class LogQueue {
try { try {
if (issues.length > 0) await this._post('/log/v1/issues/batch', issues) if (issues.length > 0) await this._post('/log/v1/issues/batch', issues)
if (events.length > 0) await this._post('/log/v1/events/batch', events) if (events.length > 0) await this._post('/log/v1/events/batch', events)
this.retryCount = 0
} catch { } catch {
// On failure, push batch back to front of queue (retry once on next flush) this.retryCount++
if (this.retryCount < MAX_RETRY) {
const current = await this._read() const current = await this._read()
await this._write([...batch, ...current]) await this._write([...batch, ...current])
} }
// 超过重试次数,丢弃该批次
}
} }
destroy(): void { destroy(): void {