{"version":3,"sources":["../../src/websocket-server.ts"],"sourcesContent":["import {\n  Message,\n  MessageType,\n  parseMessage,\n} from \"@mocky-balboa/websocket-messages\";\nimport { WebSocketServer, type RawData } from \"ws\";\nimport { logger } from \"./logger.js\";\nimport {\n  connections,\n  type WebSocketConnectionState,\n} from \"./connection-state.js\";\nimport { DefaultWebSocketServerPort } from \"@mocky-balboa/shared-config\";\n\n/**\n * Options for the WebSocket server\n */\nexport interface WebSocketServerOptions {\n  /**\n   * WebSocket server port\n   *\n   * @default {@link DefaultWebSocketServerPort}\n   */\n  port?: number;\n}\n\n/**\n * Handles the first incoming message from a client. Expects the message to be of type {@link MessageType.IDENTIFY}.\n *\n * @remarks\n * If the message is not of type {@link MessageType.IDENTIFY}, the connection is closed.\n */\nconst connectionOnMessage =\n  (connectionState: WebSocketConnectionState) => (data: RawData) => {\n    try {\n      const message = parseMessage(data.toString());\n\n      switch (message.type) {\n        case MessageType.IDENTIFY:\n          connectionState.clientIdentity = message.payload.id;\n          connections.set(connectionState.clientIdentity, connectionState);\n\n          connectionState.ws.send(\n            new Message(MessageType.ACK, {}, message.messageId).toString(),\n          );\n          connectionState.ws.send(message.toString());\n          break;\n\n        default:\n          throw new Error(`Unhandled message type: ${message.type}`);\n      }\n    } catch (error) {\n      logger.error(\"Error handling message\", error);\n      connectionState.ws.close();\n    }\n  };\n\n/**\n * Callback to close the WebSocket server\n */\nexport type CloseWebSocketServer = () => Promise<void>;\n\n/**\n * Start the WebSocket server. Manages the first message sent by the client and the client pool ({@link connections}).\n *\n * @throws {Error} If the WebSocket server fails to start within 3 seconds.\n */\nexport const startWebSocketServer = async ({\n  port = DefaultWebSocketServerPort,\n}: WebSocketServerOptions): Promise<CloseWebSocketServer> => {\n  const wss = new WebSocketServer({ port });\n\n  await new Promise<void>((resolve, reject) => {\n    const timeout = setTimeout(() => {\n      reject(new Error(\"WebSocket server failed to start\"));\n    }, 3000);\n\n    wss.on(\"listening\", () => {\n      clearTimeout(timeout);\n      resolve();\n    });\n  });\n\n  wss.on(\"connection\", (ws) => {\n    let connectionState: WebSocketConnectionState = { ws };\n    ws.on(\"error\", logger.error);\n\n    // Listen once for identity message which should be the first message sent by the client\n    const messageHandler = connectionOnMessage(connectionState);\n    ws.once(\"message\", messageHandler);\n\n    ws.on(\"close\", () => {\n      if (connectionState.clientIdentity) {\n        connections.delete(connectionState.clientIdentity);\n      }\n    });\n  });\n\n  return async () => {\n    return new Promise<void>((resolve, reject) => {\n      wss.close((error) => {\n        if (error) {\n          logger.error(\"Error closing WebSocket server\", error);\n          reject(error);\n        } else {\n          connections.clear();\n          resolve();\n        }\n      });\n    });\n  };\n};\n"],"names":["startWebSocketServer","connectionOnMessage","connectionState","data","message","parseMessage","toString","type","MessageType","IDENTIFY","clientIdentity","payload","id","connections","set","ws","send","Message","ACK","messageId","Error","error","logger","close","port","DefaultWebSocketServerPort","wss","WebSocketServer","Promise","resolve","reject","timeout","setTimeout","on","clearTimeout","messageHandler","once","delete","clear"],"mappings":";;;;+BAkEaA;;;eAAAA;;;mCA9DN;oBACuC;wBACvB;iCAIhB;8BACoC;AAc3C;;;;;CAKC,GACD,MAAMC,sBACJ,CAACC,kBAA8C,CAACC;QAC9C,IAAI;YACF,MAAMC,UAAUC,IAAAA,+BAAY,EAACF,KAAKG,QAAQ;YAE1C,OAAQF,QAAQG,IAAI;gBAClB,KAAKC,8BAAW,CAACC,QAAQ;oBACvBP,gBAAgBQ,cAAc,GAAGN,QAAQO,OAAO,CAACC,EAAE;oBACnDC,4BAAW,CAACC,GAAG,CAACZ,gBAAgBQ,cAAc,EAAER;oBAEhDA,gBAAgBa,EAAE,CAACC,IAAI,CACrB,IAAIC,0BAAO,CAACT,8BAAW,CAACU,GAAG,EAAE,CAAC,GAAGd,QAAQe,SAAS,EAAEb,QAAQ;oBAE9DJ,gBAAgBa,EAAE,CAACC,IAAI,CAACZ,QAAQE,QAAQ;oBACxC;gBAEF;oBACE,MAAM,IAAIc,MAAM,CAAC,wBAAwB,EAAEhB,QAAQG,IAAI,EAAE;YAC7D;QACF,EAAE,OAAOc,OAAO;YACdC,cAAM,CAACD,KAAK,CAAC,0BAA0BA;YACvCnB,gBAAgBa,EAAE,CAACQ,KAAK;QAC1B;IACF;AAYK,MAAMvB,uBAAuB,OAAO,EACzCwB,OAAOC,wCAA0B,EACV;IACvB,MAAMC,MAAM,IAAIC,mBAAe,CAAC;QAAEH;IAAK;IAEvC,MAAM,IAAII,QAAc,CAACC,SAASC;QAChC,MAAMC,UAAUC,WAAW;YACzBF,OAAO,IAAIV,MAAM;QACnB,GAAG;QAEHM,IAAIO,EAAE,CAAC,aAAa;YAClBC,aAAaH;YACbF;QACF;IACF;IAEAH,IAAIO,EAAE,CAAC,cAAc,CAAClB;QACpB,IAAIb,kBAA4C;YAAEa;QAAG;QACrDA,GAAGkB,EAAE,CAAC,SAASX,cAAM,CAACD,KAAK;QAE3B,wFAAwF;QACxF,MAAMc,iBAAiBlC,oBAAoBC;QAC3Ca,GAAGqB,IAAI,CAAC,WAAWD;QAEnBpB,GAAGkB,EAAE,CAAC,SAAS;YACb,IAAI/B,gBAAgBQ,cAAc,EAAE;gBAClCG,4BAAW,CAACwB,MAAM,CAACnC,gBAAgBQ,cAAc;YACnD;QACF;IACF;IAEA,OAAO;QACL,OAAO,IAAIkB,QAAc,CAACC,SAASC;YACjCJ,IAAIH,KAAK,CAAC,CAACF;gBACT,IAAIA,OAAO;oBACTC,cAAM,CAACD,KAAK,CAAC,kCAAkCA;oBAC/CS,OAAOT;gBACT,OAAO;oBACLR,4BAAW,CAACyB,KAAK;oBACjBT;gBACF;YACF;QACF;IACF;AACF"}