feat(message): implement version-based incremental sync and WS compression
Implement a more efficient conversation synchronization mechanism using version numbers instead of sequence numbers to reduce bandwidth usage. This includes adding support for Gzip decompression on WebSocket messages to optimize data transfer. - Add `pako` for WebSocket message decompression - Implement `getSyncByVersion` in `MessageService` - Implement `syncByVersion` in `MessageSyncService` to handle incremental updates and conversation state changes - Update `WebSocketService` to support binary frames and Gzip inflation - Add `syncVersion` to `MessageStore` for tracking synchronization state
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import pako from 'pako';
|
||||
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
|
||||
import { api, WS_URL } from './api';
|
||||
@@ -329,7 +331,7 @@ class WebSocketService {
|
||||
private fallbackCheckTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
private getWSUrl(token: string | null): string {
|
||||
return `${WS_URL}?token=${encodeURIComponent(token || '')}`;
|
||||
return `${WS_URL}?token=${encodeURIComponent(token || '')}&compress=1`;
|
||||
}
|
||||
|
||||
async connect(): Promise<boolean> {
|
||||
@@ -354,6 +356,7 @@ class WebSocketService {
|
||||
console.log('[WebSocket] Connecting to:', url.replace(token, '***'));
|
||||
|
||||
this.ws = new WebSocket(url);
|
||||
this.ws.binaryType = 'arraybuffer';
|
||||
|
||||
this.ws.onopen = () => {
|
||||
console.log('[WebSocket] Connected successfully');
|
||||
@@ -392,9 +395,17 @@ class WebSocketService {
|
||||
}
|
||||
}
|
||||
|
||||
private handleIncoming(data: string): void {
|
||||
private handleIncoming(data: string | ArrayBuffer): void {
|
||||
let textData: string;
|
||||
if (typeof data === 'string') {
|
||||
textData = data;
|
||||
} else {
|
||||
// Binary frame — gzip decompress
|
||||
const decompressed = pako.inflate(new Uint8Array(data as ArrayBuffer));
|
||||
textData = new TextDecoder('utf-8').decode(decompressed);
|
||||
}
|
||||
// 后端 writePump 会将多条消息以 \n 分隔合并发送,需要逐条解析
|
||||
const lines = data.split('\n');
|
||||
const lines = textData.split('\n');
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed) continue;
|
||||
@@ -402,7 +413,7 @@ class WebSocketService {
|
||||
const msg: WSServerMessage = JSON.parse(trimmed);
|
||||
this.processMessage(msg);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error, 'raw:', trimmed);
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +476,37 @@ class MessageService {
|
||||
return response.data?.conversations || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 增量同步(基于版本号)
|
||||
* GET /api/v1/conversations/sync?version=N&limit=100
|
||||
*/
|
||||
async getSyncByVersion(version: number, limit: number = 100): Promise<{
|
||||
current_version: number;
|
||||
changes: Array<{
|
||||
conversation_id: string;
|
||||
change_type: string;
|
||||
max_seq?: number;
|
||||
last_message_at?: number;
|
||||
version: number;
|
||||
}>;
|
||||
full_sync: boolean;
|
||||
has_more: boolean;
|
||||
}> {
|
||||
const response = await api.get<{
|
||||
current_version: number;
|
||||
changes: Array<{
|
||||
conversation_id: string;
|
||||
change_type: string;
|
||||
max_seq?: number;
|
||||
last_message_at?: number;
|
||||
version: number;
|
||||
}>;
|
||||
full_sync: boolean;
|
||||
has_more: boolean;
|
||||
}>('/conversations/sync', { version, limit });
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统消息未读数
|
||||
* GET /api/v1/messages/system/unread-count
|
||||
|
||||
Reference in New Issue
Block a user