feat(message): implement version-based incremental sync and WS compression
Some checks failed
Frontend CI / ota-android (push) Successful in 1m58s
Frontend CI / ota-ios (push) Successful in 2m2s
Frontend CI / build-android-apk (push) Failing after 3m22s
Frontend CI / build-and-push-web (push) Successful in 4m19s

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:
2026-05-17 23:37:38 +08:00
parent 404b3fabe7
commit fb67fb6d5b
6 changed files with 132 additions and 4 deletions

View File

@@ -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);
}
}
}