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:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user