feat: OpsxAPI v1.0 全协议接口工作台

- 六协议调试:HTTP/GraphQL/WebSocket/SSE/Socket.IO/MQTT
- 集合树/多标签/多环境变量/历史记录,localStorage 持久化
- 导入:OpenAPI 2/3/3.1、Postman、Apifox、Apipost、cURL、HAR、
  Insomnia、Hoppscotch、Markdown(自动识别,21 个单测覆盖)
- 导出:OpenAPI 3.0 JSON/YAML、Postman v2.1、Markdown 文档、
  全量备份;单请求生成 cURL/fetch/Python/Go 代码
- Gin 服务:HTTP 代理、NDJSON 流式代理(SSE 透传)、
  WebSocket 隧道(浏览器自定义握手 Header)
- 内置产品官网 /home 与用户文档站 /docs(SSR,随二进制交付)
- lucide 图标体系 + 品牌标识(Logo/favicon),明暗双主题
- 环境变量:彩色徽标切换器、管理弹窗、{{ 自动补全
This commit is contained in:
logan committed 2026-09-18 00:16:59 +08:00
commit dec66db5aa
182 files changed
+23377

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
/**
* Socket.IO 客户端封装(动态加载 socket.io-client)。
*/
export type SocketIoStatus = "connecting" | "open" | "closed" | "error";
export type SocketIoSession = {
emit(event: string, args: unknown[]): void;
close(): void;
};
export type SocketIoOptions = {
url: string;
/** 附加选项 JSON:{ path?, transports?, auth?, ... } */
optionsJson?: string;
/** 监听事件列表;空数组 = onAny 全收 */
listenEvents: string[];
onEvent(event: string, args: unknown[]): void;
onStatus?(s: SocketIoStatus, detail?: string): void;
onError?(msg: string): void;
};
type IoSocket = {
emit(ev: string, ...args: unknown[]): void;
on(ev: string, cb: (...args: unknown[]) => void): void;
onAny(cb: (ev: string, ...args: unknown[]) => void): void;
disconnect(): void;
};
export function connectSocketIo(opts: SocketIoOptions): SocketIoSession {
let socket: IoSocket | null = null;
let disposed = false;
void (async () => {
try {
opts.onStatus?.("connecting");
const { io } = await import("socket.io-client");
const extra: Record<string, unknown> = {};
if (opts.optionsJson?.trim()) {
try {
Object.assign(extra, JSON.parse(opts.optionsJson));
} catch {
opts.onError?.("连接选项不是合法 JSON,已忽略");
}
}
const s: IoSocket = io(opts.url, {
transports: ["websocket", "polling"],
reconnection: false,
...extra,
}) as unknown as IoSocket;
socket = s;
s.on("connect", () => opts.onStatus?.("open"));
s.on("disconnect", (...args: unknown[]) =>
opts.onStatus?.("closed", String(args[0] ?? "")),
);
s.on("connect_error", (...args: unknown[]) => {
const err = args[0];
const msg = err instanceof Error ? err.message : String(err);
opts.onError?.(msg);
opts.onStatus?.("error", msg);
});
const events = opts.listenEvents.map((e) => e.trim()).filter(Boolean);
if (events.length === 0 || events.includes("*")) {
s.onAny((ev, ...args) => opts.onEvent(ev, args));
} else {
for (const ev of events) {
s.on(ev, (...args: unknown[]) => opts.onEvent(ev, args));
}
}
if (disposed) s.disconnect();
} catch (e) {
opts.onError?.(`socket.io-client 加载/连接失败:${e instanceof Error ? e.message : String(e)}`);
opts.onStatus?.("error");
}
})();
return {
emit(event, args) {
if (!socket) {
opts.onError?.("Socket.IO 尚未连接");
return;
}
socket.emit(event, ...args);
},
close() {
disposed = true;
socket?.disconnect();
opts.onStatus?.("closed");
},
};
}