/** * HTTP处理器 * 负责处理HTTP请求,管理会话和连接,处理信令消息 */ import { Request, Response } from 'express'; import Offer from './offer'; import Answer from './answer'; import Candidate from './candidate'; import { v4 as uuid } from 'uuid'; import { onGetAllConnectionIds } from './websockethandler'; /** * 断开连接记录类 * 用于记录断开连接的信息 */ class Disconnection { id: string; // 连接ID datetime: number; // 断开连接的时间戳 /** * 构造函数 * @param id 连接ID * @param datetime 断开连接的时间戳 */ constructor(id: string, datetime: number) { this.id = id; this.datetime = datetime; } } // 会话超时时间(毫秒) const TimeoutRequestedTime = 10000; // 10sec // 是否为私有模式 let isPrivate: boolean; /** * 客户端会话映射 * 键: 会话ID * 值: 该会话的连接ID集合 */ const clients: Map> = new Map>(); /** * 会话最后请求时间映射 * 键: 会话ID * 值: 最后请求的时间戳 */ const lastRequestedTime: Map = new Map(); /** * 连接对映射 * 键: 连接ID * 值: [会话ID1, 会话ID2] */ const connectionPair: Map = new Map(); // key = connectionId /** * 会话的offer映射 * 键: 会话ID * 值: 该会话的连接ID到Offer对象的映射 */ const offers: Map> = new Map>(); // key = sessionId /** * 会话的answer映射 * 键: 会话ID * 值: 该会话的连接ID到Answer对象的映射 */ const answers: Map> = new Map>(); // key = sessionId /** * 会话的candidate映射 * 键: 会话ID * 值: 该会话的连接ID到Candidate数组的映射 */ const candidates: Map> = new Map>(); // key = sessionId /** * 会话的断开连接记录映射 * 键: 会话ID * 值: 该会话的Disconnection对象数组 */ const disconnections: Map = new Map(); // key = sessionId /** * 获取或创建会话的连接ID集合 * @param sessionId 会话ID * @returns 连接ID的Set集合 */ function getOrCreateConnectionIds(sessionId: string): Set { let connectionIds = null; // 检查会话是否已存在 if (!clients.has(sessionId)) { // 如果不存在,创建新的连接ID集合 connectionIds = new Set(); // 将新的连接ID集合与会话关联 clients.set(sessionId, connectionIds); } // 获取会话的连接ID集合 connectionIds = clients.get(sessionId); // 返回连接ID集合 return connectionIds; } /** * 重置处理器状态 * @param mode 通信模式(public或private) */ function reset(mode: string): void { // 设置是否为私有模式 isPrivate = mode == "private"; // 清空所有映射 clients.clear(); connectionPair.clear(); offers.clear(); answers.clear(); candidates.clear(); disconnections.clear(); } /** * 检查会话ID是否有效 * @param req Express请求对象 * @param res Express响应对象 * @param next 下一个中间件函数 */ function checkSessionId(req: Request, res: Response, next): void { // 如果是根路径,直接通过 if (req.url === '/') { next(); return; } // 从请求头获取会话ID const id: string = req.header('session-id'); // 检查会话是否存在 if (!clients.has(id)) { res.sendStatus(404); return; } // 更新会话的最后请求时间 lastRequestedTime.set(id, Date.now()); // 继续处理请求 next(); } /** * 删除连接 * @param sessionId 会话ID * @param connectionId 连接ID * @param datetime 时间戳 */ function _deleteConnection(sessionId:string, connectionId:string, datetime:number) { // 从会话的连接ID集合中删除连接ID clients.get(sessionId).delete(connectionId); // 处理私有模式 if(isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); // 找到另一个会话ID const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; if (otherSessionId) { if (clients.has(otherSessionId)) { // 从另一个会话的连接ID集合中删除连接ID clients.get(otherSessionId).delete(connectionId); // 向另一个会话的断开连接记录中添加记录 const array1 = disconnections.get(otherSessionId); array1.push(new Disconnection(connectionId, datetime)); } } } } else { // 公共模式:向所有其他会话的断开连接记录中添加记录 disconnections.forEach((array, id) => { if (id == sessionId) return; array.push(new Disconnection(connectionId, datetime)); }); } // 从连接对映射中删除 connectionPair.delete(connectionId); // 从会话的offer映射中删除 offers.get(sessionId).delete(connectionId); // 从会话的answer映射中删除 answers.get(sessionId).delete(connectionId); // 从会话的candidate映射中删除 candidates.get(sessionId).delete(connectionId); // 向当前会话的断开连接记录中添加记录 const array2 = disconnections.get(sessionId); array2.push(new Disconnection(connectionId, datetime)); } /** * 删除会话 * @param sessionId 会话ID */ function _deleteSession(sessionId: string) { // 如果会话存在,删除其所有连接 if(clients.has(sessionId)) { for(const connectionId of Array.from(clients.get(sessionId))) { _deleteConnection(sessionId, connectionId, Date.now()); } } // 从所有映射中删除会话 offers.delete(sessionId); answers.delete(sessionId); candidates.delete(sessionId); clients.delete(sessionId); disconnections.delete(sessionId); } /** * 检查超时会话 */ function _checkForTimedOutSessions(): void { // 遍历所有会话 for (const sessionId of Array.from(clients.keys())) { // 如果会话没有最后请求时间,跳过 if(!lastRequestedTime.has(sessionId)) continue; // 如果会话未超时,跳过 if(lastRequestedTime.get(sessionId) > Date.now() - TimeoutRequestedTime) continue; // 删除超时会话 _deleteSession(sessionId); console.log(`deleted sessionId:${sessionId} by timeout.`); } } /** * 获取会话的连接ID列表 * @param sessionId 会话ID * @returns 连接ID数组 */ function _getConnection(sessionId: string): string[] { // 检查超时会话 _checkForTimedOutSessions(); // 返回会话的连接ID集合的数组形式 return Array.from(clients.get(sessionId)); } /** * 获取会话的断开连接记录 * @param sessionId 会话ID * @param fromTime 起始时间戳 * @returns 断开连接记录数组 */ function _getDisconnection(sessionId: string, fromTime: number): Disconnection[] { // 检查超时会话 _checkForTimedOutSessions(); let arrayDisconnections: Disconnection[] = []; // 如果断开连接记录存在,获取该会话的断开连接记录 if (disconnections.size != 0 && disconnections.has(sessionId)) { arrayDisconnections = disconnections.get(sessionId); } // 如果指定了起始时间,过滤出时间戳大于等于起始时间的记录 if (fromTime > 0) { arrayDisconnections = arrayDisconnections.filter((v) => v.datetime >= fromTime); } return arrayDisconnections; } /** * 获取会话的offer列表 * @param sessionId 会话ID * @param fromTime 起始时间戳 * @returns [连接ID, Offer]数组 */ function _getOffer(sessionId: string, fromTime: number): [string, Offer][] { let arrayOffers: [string, Offer][] = []; // 如果offer映射不为空 if (offers.size != 0) { // 处理私有模式 if (isPrivate) { // 如果会话存在offer记录,获取该会话的offer列表 if (offers.has(sessionId)) { arrayOffers = Array.from(offers.get(sessionId)); } } else { // 公共模式:获取所有其他会话的offer列表 const otherSessionMap = Array.from(offers).filter(x => x[0] != sessionId); arrayOffers = [].concat(...Array.from(otherSessionMap, x => Array.from(x[1], y => [y[0], y[1]]))); } } // 如果指定了起始时间,过滤出时间戳大于等于起始时间的offer if (fromTime > 0) { arrayOffers = arrayOffers.filter((v) => v[1].datetime >= fromTime); } return arrayOffers; } /** * 获取会话的answer列表 * @param sessionId 会话ID * @param fromTime 起始时间戳 * @returns [连接ID, Answer]数组 */ function _getAnswer(sessionId: string, fromTime: number): [string, Answer][] { let arrayAnswers: [string, Answer][] = []; // 如果answer映射不为空且会话存在answer记录,获取该会话的answer列表 if (answers.size != 0 && answers.has(sessionId)) { arrayAnswers = Array.from(answers.get(sessionId)); } // 如果指定了起始时间,过滤出时间戳大于等于起始时间的answer if (fromTime > 0) { arrayAnswers = arrayAnswers.filter((v) => v[1].datetime >= fromTime); } return arrayAnswers; } /** * 获取会话的candidate列表 * @param sessionId 会话ID * @param fromTime 起始时间戳 * @returns [连接ID, Candidate]数组 */ function _getCandidate(sessionId: string, fromTime: number): [string, Candidate][] { // 获取会话的连接ID列表 const connectionIds = Array.from(clients.get(sessionId)); const arr: [string, Candidate][] = []; // 遍历每个连接ID for (const connectionId of connectionIds) { // 获取连接对 const pair = connectionPair.get(connectionId); if (pair == null) { continue; } // 找到另一个会话ID const otherSessionId = sessionId === pair[0] ? pair[1] : pair[0]; // 如果另一个会话不存在candidate记录或该连接ID不存在candidate记录,跳过 if (!candidates.get(otherSessionId) || !candidates.get(otherSessionId).get(connectionId)) { continue; } // 获取该连接ID的candidate列表,并过滤出时间戳大于等于起始时间的candidate const arrayCandidates = candidates.get(otherSessionId).get(connectionId) .filter((v) => v.datetime >= fromTime); // 如果没有符合条件的candidate,跳过 if (arrayCandidates.length === 0) { continue; } // 将符合条件的candidate添加到结果数组中 for (const candidate of arrayCandidates) { arr.push([connectionId, candidate]); } } return arr; } /** * @swagger * /signaling/answer: * get: * summary: 获取answer列表 * description: 获取当前会话的answer信令消息列表 * security: * - sessionAuth: [] * parameters: * - in: query * name: fromtime * schema: * type: number * description: 起始时间戳,用于过滤消息 * responses: * 200: * description: 成功获取answer列表 * content: * application/json: * schema: * type: object * properties: * answers: * type: array * items: * type: object * properties: * connectionId: * type: string * description: 连接ID * sdp: * type: string * description: SDP描述 * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 */ function getAnswer(req: Request, res: Response): void { // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 获取会话的answer列表 const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); // 返回JSON响应,包含answer列表 res.json({ answers: answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime })) }); } /** * @swagger * /signaling/connection: * get: * summary: 获取连接列表 * description: 获取当前会话的连接列表 * security: * - sessionAuth: [] * responses: * 200: * description: 成功获取连接列表 * content: * application/json: * schema: * type: object * properties: * connections: * type: array * items: * type: object * properties: * connectionId: * type: string * description: 连接ID * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 */ function getConnection(req: Request, res: Response): void { // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 获取会话的连接ID列表 const connections = _getConnection(sessionId); // 返回JSON响应,包含连接列表 res.json({ connections: connections.map((v) => ({ connectionId: v, type: "connect", datetime: Date.now() })) }); } /** * @swagger * /signaling/offer: * get: * summary: 获取offer列表 * description: 获取当前会话的offer信令消息列表 * security: * - sessionAuth: [] * parameters: * - in: query * name: fromtime * schema: * type: number * description: 起始时间戳,用于过滤消息 * responses: * 200: * description: 成功获取offer列表 * content: * application/json: * schema: * type: object * properties: * offers: * type: array * items: * type: object * properties: * connectionId: * type: string * description: 连接ID * sdp: * type: string * description: SDP描述 * polite: * type: boolean * description: 是否为polite模式 * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 */ function getOffer(req: Request, res: Response): void { // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 获取会话的offer列表 const offers = _getOffer(sessionId, fromTime); // 返回JSON响应,包含offer列表 res.json({ offers: offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime })) }); } /** * @swagger * /signaling/candidate: * get: * summary: 获取candidate列表 * description: 获取当前会话的candidate信令消息列表 * security: * - sessionAuth: [] * parameters: * - in: query * name: fromtime * schema: * type: number * description: 起始时间戳,用于过滤消息 * responses: * 200: * description: 成功获取candidate列表 * content: * application/json: * schema: * type: object * properties: * candidates: * type: array * items: * type: object * properties: * connectionId: * type: string * description: 连接ID * candidate: * type: string * description: ICE候选者信息 * sdpMLineIndex: * type: number * description: SDP媒体行索引 * sdpMid: * type: string * description: SDP媒体ID * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 */ function getCandidate(req: Request, res: Response): void { // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 获取会话的candidate列表 const candidates = _getCandidate(sessionId, fromTime); // 返回JSON响应,包含candidate列表 res.json({ candidates: candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime })) }); } /** * @swagger * /signaling: * get: * summary: 获取所有信令消息 * description: 获取当前会话的所有信令消息,包括连接、断开连接、offer、answer和candidate * security: * - sessionAuth: [] * parameters: * - in: query * name: fromtime * schema: * type: number * description: 起始时间戳,用于过滤消息 * responses: * 200: * description: 成功获取所有信令消息 * content: * application/json: * schema: * type: object * properties: * messages: * type: array * items: * type: object * properties: * connectionId: * type: string * description: 连接ID * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 * sdp: * type: string * description: SDP描述(仅offer和answer消息) * polite: * type: boolean * description: 是否为polite模式(仅offer消息) * candidate: * type: string * description: ICE候选者信息(仅candidate消息) * sdpMLineIndex: * type: number * description: SDP媒体行索引(仅candidate消息) * sdpMid: * type: string * description: SDP媒体ID(仅candidate消息) * datetime: * type: number * description: 当前时间戳 */ function getAll(req: Request, res: Response): void { // 从请求查询参数中获取`fromtime`参数 const fromTime: number = req.query.fromtime ? Number(req.query.fromtime) : 0; // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 获取各种信令消息 const connections = _getConnection(sessionId); const offers = _getOffer(sessionId, fromTime); const answers: [string, Answer][] = _getAnswer(sessionId, fromTime); const candidates: [string, Candidate][] = _getCandidate(sessionId, fromTime); const disconnections: Disconnection[] = _getDisconnection(sessionId, fromTime); const datetime = lastRequestedTime.get(sessionId); let array: any[] = []; // 合并所有信令消息 array = array.concat(connections.map((v) => ({ connectionId: v, type: "connect", datetime: datetime }))); array = array.concat(offers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, polite: v[1].polite, type: "offer", datetime: v[1].datetime }))); array = array.concat(answers.map((v) => ({ connectionId: v[0], sdp: v[1].sdp, type: "answer", datetime: v[1].datetime }))); array = array.concat(candidates.map((v) => ({ connectionId: v[0], candidate: v[1].candidate, sdpMLineIndex: v[1].sdpMLineIndex, sdpMid: v[1].sdpMid, type: "candidate", datetime: v[1].datetime }))); array = array.concat(disconnections.map((v) => ({ connectionId: v.id, type: "disconnect", datetime: v.datetime }))); // 按时间戳排序 array.sort((a, b) => a.datetime - b.datetime); // 返回JSON响应,包含所有信令消息 res.json({ messages: array, datetime: datetime }); } /** * @swagger * /signaling: * put: * summary: 创建会话 * description: 创建一个新的会话,并返回会话ID * responses: * 200: * description: 成功创建会话 * content: * application/json: * schema: * type: object * properties: * sessionId: * type: string * description: 新创建的会话ID */ function createSession(sessionId: string, res: Response): void; function createSession(req: Request, res: Response): void; function createSession(req: string | Request, res: Response): void { // 确定会话ID const sessionId: string = typeof req === "string" ? req : uuid(); // 为会话创建各种映射 clients.set(sessionId, new Set()); offers.set(sessionId, new Map()); answers.set(sessionId, new Map()); candidates.set(sessionId, new Map()); disconnections.set(sessionId, []); // 返回JSON响应,包含会话ID res.json({ sessionId: sessionId }); } /** * @swagger * /signaling: * delete: * summary: 删除会话 * description: 删除当前会话及其所有连接 * security: * - sessionAuth: [] * responses: * 200: * description: 成功删除会话 */ function deleteSession(req: Request, res: Response): void { // 从请求头获取会话ID const id: string = req.header('session-id'); // 删除会话 _deleteSession(id); // 返回200状态码表示请求处理成功 res.sendStatus(200); } /** * @swagger * /signaling/connection: * put: * summary: 创建连接 * description: 创建一个新的连接,并返回连接信息 * security: * - sessionAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * responses: * 200: * description: 成功创建连接 * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * polite: * type: boolean * description: 是否为polite模式 * type: * type: string * description: 消息类型 * datetime: * type: number * description: 时间戳 * 400: * description: 请求参数错误 */ function createConnection(req: Request, res: Response): void { // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 从请求体获取连接ID const { connectionId } = req.body; // 获取会话的最后请求时间 const datetime = lastRequestedTime.get(sessionId); // 检查连接ID是否存在 if (connectionId == null) { res.status(400).send({ error: new Error(`connectionId is required`) }); return; } let polite = true; // 处理私有模式 if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); // 检查连接ID是否已被使用 if (pair[0] != null && pair[1] != null) { const err = new Error(`${connectionId}: This connection id is already used.`); console.log(err); res.status(400).send({ error: err }); return; } else if (pair[0] != null) { // 找到配对连接 connectionPair.set(connectionId, [pair[0], sessionId]); // 添加连接ID到另一个会话 const map = getOrCreateConnectionIds(pair[0]); map.add(connectionId); } } else { // 创建新的连接对 connectionPair.set(connectionId, [sessionId, null]); polite = false; } } // 添加连接ID到当前会话 const connectionIds = getOrCreateConnectionIds(sessionId); connectionIds.add(connectionId); // 返回JSON响应,包含连接信息 res.json({ connectionId: connectionId, polite: polite, type: "connect", datetime: datetime }); } /** * @swagger * /signaling/connection: * delete: * summary: 删除连接 * description: 删除指定的连接 * security: * - sessionAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * responses: * 200: * description: 成功删除连接 * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID */ function deleteConnection(req: Request, res: Response): void { // 从请求头获取会话ID const sessionId: string = req.header('session-id'); // 从请求体获取连接ID const { connectionId } = req.body; // 获取会话的最后请求时间 const datetime = lastRequestedTime.get(sessionId); // 删除连接 _deleteConnection(sessionId, connectionId, datetime); // 返回JSON响应,包含连接ID res.json({ connectionId: connectionId }); } /** * @swagger * /signaling/offer: * post: * summary: 发送offer信令 * description: 发送offer信令消息 * security: * - sessionAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * sdp: * type: string * description: SDP描述 * responses: * 200: * description: 成功发送offer信令 */ function postOffer(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; const datetime = lastRequestedTime.get(sessionId); let keySessionId = null; let polite = false; if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); keySessionId = pair[0] == sessionId ? pair[1] : pair[0]; if (keySessionId != null) { polite = true; const map = offers.get(keySessionId); map.set(connectionId, new Offer(req.body.sdp, datetime, polite)); } } res.sendStatus(200); return; } if(!connectionPair.has(connectionId)) { connectionPair.set(connectionId, [sessionId, null]); } keySessionId = sessionId; const map = offers.get(keySessionId); map.set(connectionId, new Offer(req.body.sdp, datetime, polite)); res.sendStatus(200); } /** * @swagger * /signaling/answer: * post: * summary: 发送answer信令 * description: 发送answer信令消息 * security: * - sessionAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * sdp: * type: string * description: SDP描述 * responses: * 200: * description: 成功发送answer信令 */ function postAnswer(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; const datetime = lastRequestedTime.get(sessionId); const connectionIds = getOrCreateConnectionIds(sessionId); connectionIds.add(connectionId); if (!connectionPair.has(connectionId)) { res.sendStatus(200); return; } // add connectionPair const pair = connectionPair.get(connectionId); const otherSessionId = pair[0] == sessionId ? pair[1] : pair[0]; if (!clients.has(otherSessionId)) { // already deleted res.sendStatus(200); return; } if (!isPrivate) { connectionPair.set(connectionId, [otherSessionId, sessionId]); } const map = answers.get(otherSessionId); map.set(connectionId, new Answer(req.body.sdp, datetime)); // update datetime for candidates const mapCandidates = candidates.get(otherSessionId); if (mapCandidates) { const arrayCandidates = mapCandidates.get(connectionId); if (arrayCandidates) { for (const candidate of arrayCandidates) { candidate.datetime = datetime; } } } res.sendStatus(200); } /** * @swagger * /signaling/candidate: * post: * summary: 发送candidate信令 * description: 发送candidate信令消息 * security: * - sessionAuth: [] * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * connectionId: * type: string * description: 连接ID * candidate: * type: string * description: ICE候选者信息 * sdpMLineIndex: * type: number * description: SDP媒体行索引 * sdpMid: * type: string * description: SDP媒体ID * responses: * 200: * description: 成功发送candidate信令 */ function postCandidate(req: Request, res: Response): void { const sessionId: string = req.header('session-id'); const { connectionId } = req.body; const datetime = lastRequestedTime.get(sessionId); const map = candidates.get(sessionId); if (!map.has(connectionId)) { map.set(connectionId, []); } const arr = map.get(connectionId); const candidate = new Candidate(req.body.candidate, req.body.sdpMLineIndex, req.body.sdpMid, datetime); arr.push(candidate); res.sendStatus(200); } /** * @swagger * /signaling/rooms: * get: * summary: 获取房间和用户信息 * description: 获取所有房间的信息,包括房间ID和链接的用户 * security: * - sessionAuth: [] * responses: * 200: * description: 成功获取房间和用户信息 * content: * application/json: * schema: * type: object * properties: * rooms: * type: array * items: * type: object * properties: * roomId: * type: string * description: 房间ID * users: * type: array * items: * type: object * properties: * sessionId: * type: string * description: 会话ID * connected: * type: boolean * description: 连接状态 * userCount: * type: number * description: 用户数量 * totalRooms: * type: number * description: 总房间数 */ function onGetConnections(req: Request, res: Response): void { // 收集所有房间ID和链接用户信息 const rooms = []; // 遍历所有连接对 for (const [connectionId, pair] of Array.from(connectionPair.entries())) { // 收集房间中的用户信息 const users = []; // 添加第一个用户 if (pair[0] && clients.has(pair[0])) { users.push({ sessionId: pair[0], connected: true }); } // 添加第二个用户 if (pair[1] && clients.has(pair[1])) { users.push({ sessionId: pair[1], connected: true }); } // 添加房间信息 rooms.push({ roomId: connectionId, users: users, userCount: users.length }); } res.json({ rooms: rooms, totalRooms: rooms.length }); } /** * @swagger * /signaling/connection-ids: * get: * summary: 获取所有连接ID * description: 获取所有当前活跃的连接ID * security: * - sessionAuth: [] * responses: * 200: * description: 成功获取连接ID列表 * content: * application/json: * schema: * type: object * properties: * connectionIds: * type: array * items: * type: string * description: 连接ID * totalCount: * type: number * description: 总连接数 */ function getAllConnectionIds(req: Request, res: Response): void { // 获取所有连接ID const connectionIds = onGetAllConnectionIds(); // 返回JSON响应,包含连接ID列表和总数量 res.json({ connectionIds: connectionIds, totalCount: connectionIds.length }); } /** * 导出HTTP处理器函数 */ export { reset, // 重置处理器状态 checkSessionId, // 检查会话ID是否有效 getAll, // 获取所有信令消息 getConnection, // 获取连接列表 getOffer, // 获取offer列表 getAnswer, // 获取answer列表 getCandidate, // 获取candidate列表 createSession, // 创建会话 deleteSession, // 删除会话 createConnection, // 创建连接 deleteConnection, // 删除连接 postOffer, // 处理offer信令消息 postAnswer, // 处理answer信令消息 postCandidate, // 处理candidate信令消息 onGetConnections, // 获取房间和用户信息 getAllConnectionIds // 获取所有连接ID };