Files
18xchat/assets/Scripts/chat18x/network/client/ApiClient.ts
T
2025-09-09 00:33:29 +08:00

85 lines
3.3 KiB
TypeScript

import { HttpManager, HttpMethod } from "../transport/HttpManager";
import { JsonCodec } from "../codec/JsonCodec";
import { ProtoCodec } from "../codec/ProtoCodec";
import type { Endpoint } from "./endpoints";
import { ApiCode, type ApiResponse } from "./types";
import { DataManager, DataId } from "db://assets/Scripts/chat18x/data/DataManager";
import { LoginData } from "db://assets/Scripts/chat18x/data/LoginData";
import { AppConfig } from "db://assets/Scripts/chat18x/config/env/appConfig";
export class ApiClient {
private static _I: ApiClient | null = null;
public static get I(): ApiClient {
if (!ApiClient._I) ApiClient._I = new ApiClient();
return ApiClient._I;
}
private constructor(private http = HttpManager.I) {}
private get baseUrl() { return AppConfig.I.getHttpBase() }
private get token() { return DataManager.I.getDataById<LoginData>(DataId.Login).token;}
public async call<Req, Res>(ep: Endpoint<Req, Res>, req: Req): Promise<ApiResponse<Res>> {
const url = `${this.baseUrl}/${ep.path.replace(/^\/+/, "")}`;
const headers: Record<string, string> = {};
let responseType: XMLHttpRequestResponseType | undefined;
let rawBody: XMLHttpRequestBodyInit | null | undefined;
let jsonData: any;
if (ep.needsAuth && this.token) {
headers["Authorization"] = `${this.token}`;
}
// 编码
if (ep.codec === "json") {
jsonData = (ep.method === "GET") ? req : JsonCodec.encode(req);
if (ep.method !== "GET") headers["Content-Type"] = "application/json";
} else if (ep.codec === "proto") {
if (!ep.reqType || !ep.resType) {
return { code: ApiCode.UNKNOWN, msg: "proto types missing" };
}
rawBody = ProtoCodec.encode(ep.reqType, req);
headers["Content-Type"] = "application/x-protobuf";
headers["Accept"] = "application/x-protobuf";
responseType = "arraybuffer";
}
// 发送
const r = await this.http.request(url, jsonData, ep.method, { headers, responseType, rawBody });
// HTTP/网络错误 → 统一返回
if (!r.ok) {
if (r.status === 401) {
return { code: ApiCode.AUTH_EXPIRED, msg: "auth expired" };
}
if (r.error === "timeout") {
return { code: ApiCode.TIMEOUT, msg: "request timeout" };
}
if (r.error === "network") {
return { code: ApiCode.NETWORK_ERROR, msg: "network error" };
}
return { code: ApiCode.HTTP_ERROR, msg: `http error: ${r.status}` };
}
// 解码 + 统一化
try {
if (ep.codec === "json") {
const parsed = JsonCodec.decode<any>(r.body);
// 如果后端本来就返回 {code,msg,data},直接透传(并确保有默认 msg)
if (parsed && typeof parsed === "object" && typeof parsed.code === "number") {
return {
code: parsed.code,
msg: parsed.msg ?? (parsed.code === 0 ? "ok" : "error"),
data: parsed.data as Res
};
}
// 否则我们包一层
return { code: ApiCode.OK, msg: "ok", data: parsed.data as Res };
} else if (ep.codec === "proto") {
const data = ProtoCodec.decode<Res>(ep.resType!, r.body);
// proto 场景一般没有 {code,msg} 包装,我们统一包一层
return { code: ApiCode.OK, msg: "ok", data };
}
} catch (e) {
return { code: ApiCode.DECODE_ERROR, msg: "decode error" };
}
}
}