Migrate frontend realtime messaging to SSE.

Switch service integrations and screen/store consumers from websocket events to SSE, and ignore generated dist-web artifacts.

Made-with: Cursor
This commit is contained in:
2026-03-10 12:58:23 +08:00
parent 63e32b15a3
commit be84c01abd
25 changed files with 974 additions and 1305 deletions

View File

@@ -8,7 +8,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { CommonActions } from '@react-navigation/native';
import Constants from 'expo-constants';
// 生产地址 https://bbs.littlelan.cn
const getBaseUrl = () => {
const configuredBaseUrl = Constants.expoConfig?.extra?.apiBaseUrl;
if (typeof configuredBaseUrl === 'string' && configuredBaseUrl.trim().length > 0) {
@@ -17,16 +17,8 @@ const getBaseUrl = () => {
return 'https://bbs.littlelan.cn/api/v1';
};
const getWsUrl = () => {
const configuredWsUrl = Constants.expoConfig?.extra?.wsUrl;
if (typeof configuredWsUrl === 'string' && configuredWsUrl.trim().length > 0) {
return configuredWsUrl;
}
return 'wss://bbs.littlelan.cn/ws';
};
const BASE_URL = getBaseUrl();
const WS_URL = getWsUrl();
const SSE_URL = `${BASE_URL.replace(/\/+$/, '')}/realtime/sse`;
// Token 存储键
const TOKEN_KEY = 'auth_token';
@@ -187,15 +179,45 @@ class ApiClient {
return this.request(method, path, params, body);
}
// 解析响应
const data: ApiResponse<T> = await response.json();
// 解析响应(兼容非 JSON 返回,避免 SyntaxError 被误判为网络错误)
const contentType = response.headers.get('content-type') || '';
let parsedBody: any = null;
let rawText = '';
// 处理业务错误
if (data.code !== 0) {
throw new ApiError(data.code, data.message);
if (contentType.includes('application/json')) {
parsedBody = await response.json();
} else {
rawText = await response.text();
if (rawText) {
try {
parsedBody = JSON.parse(rawText);
} catch {
parsedBody = null;
}
}
}
return data;
// 优先处理标准 API 结构
if (parsedBody && typeof parsedBody === 'object' && 'code' in parsedBody) {
const data = parsedBody as ApiResponse<T>;
if (data.code !== 0) {
throw new ApiError(data.code, data.message || '请求失败');
}
return data;
}
// 非标准结构:先按 HTTP 状态处理失败
if (!response.ok) {
const fallbackMessage = rawText || response.statusText || `请求失败(${response.status})`;
throw new ApiError(response.status, fallbackMessage);
}
// 非标准结构但 HTTP 成功:兜底为成功响应(兼容部分纯文本成功接口)
return {
code: 0,
message: 'success',
data: (parsedBody as T) ?? (undefined as T),
};
} catch (error) {
// 如果是 ApiError直接抛出
if (error instanceof ApiError) {
@@ -312,5 +334,4 @@ class ApiClient {
// 导出 API 客户端实例
export const api = new ApiClient(BASE_URL);
// 导出 WebSocket URL
export { WS_URL, TOKEN_KEY, REFRESH_TOKEN_KEY };
export { SSE_URL, TOKEN_KEY, REFRESH_TOKEN_KEY };