{"version":3,"file":"connection.mjs","names":[],"sources":["../../src/client/connection.ts"],"sourcesContent":["import { createInterface } from \"node:readline\";\n\nimport type { Transport } from \"./transport/transport\";\nimport type { ResponseAwaiter } from \"~/await/await-response\";\nimport type { ClientConfiguration } from \"~/config/config\";\nimport type { ConnectionMixin } from \"~/mixins/base-mixin\";\nimport { BaseClient } from \"./base-client\";\nimport { ConnectionError, ProtocolError } from \"./errors\";\nimport { makeTransport } from \"./transport/make-transport\";\nimport { handleReconnectMessage } from \"~/functionalities/handle-reconnect-message\";\nimport { replyToServerPing } from \"~/functionalities/reply-to-ping\";\nimport { sendClientPings } from \"~/functionalities/send-pings\";\nimport { parseTwitchMessage } from \"~/message/parser/twitch-message\";\nimport { sendLogin } from \"~/operations/login\";\nimport { requestCapabilities } from \"~/operations/request-capabilities\";\nimport { anyCauseInstanceof } from \"~/utils/any-cause-instanceof\";\nimport { debugLogger } from \"~/utils/debug-logger\";\nimport { validateIRCCommand } from \"~/validation/irc-command\";\n\nlet connectionIdCounter = 0;\n\nexport class SingleConnection extends BaseClient {\n  public readonly connectionId = connectionIdCounter++;\n\n  public readonly wantedChannels: Set<string> = new Set<string>();\n  public readonly joinedChannels: Set<string> = new Set<string>();\n\n  public readonly pendingResponses: ResponseAwaiter[] = [];\n  public readonly transport: Transport;\n\n  protected readonly log = debugLogger(\n    `dank-twitch-irc:connection:${this.connectionId}`,\n  );\n\n  public constructor(configuration?: ClientConfiguration) {\n    super(configuration);\n\n    this.on(\"error\", (error) => {\n      if (anyCauseInstanceof(error, ConnectionError)) {\n        process.nextTick(() => {\n          this.emitClosed(error);\n          this.transport.stream.destroy(error);\n        });\n      }\n    });\n    this.on(\"connect\", this.onConnect.bind(this));\n\n    this.transport = makeTransport(this.configuration.connection);\n\n    this.transport.stream.on(\"close\", () => {\n      this.emitClosed();\n    });\n    this.transport.stream.on(\"error\", (error) => {\n      const emittedError = new ConnectionError(\n        \"Error occurred in transport layer\",\n        error,\n      );\n      this.emitError(emittedError);\n      this.emitClosed(emittedError);\n      this.transport.stream.destroy(emittedError);\n    });\n\n    createInterface({ input: this.transport.stream, crlfDelay: Infinity })\n      .on(\"line\", this.handleLine.bind(this))\n      // we need to have an error handler to prevent unhandled \"error\" events on the readline interface, which would cause the process to crash\n      // eslint-disable-next-line ts/no-empty-function\n      .on(\"error\", () => {});\n\n    replyToServerPing(this);\n    handleReconnectMessage(this);\n\n    this.on(\"message\", (message) => {\n      for (const awaiter of this.pendingResponses) {\n        const stop = awaiter.onConnectionMessage(message);\n        if (stop) {\n          break;\n        }\n      }\n    });\n  }\n\n  public connect(): void {\n    if (!this.unconnected) {\n      throw new Error(\n        \"connect() may only be called on unconnected connections\",\n      );\n    }\n\n    this.emitConnecting();\n\n    if (this.configuration.connection.preSetup) {\n      this.once(\"connect\", () => {\n        process.nextTick(() => this.emitReady());\n      });\n    } else {\n      const promises = [\n        requestCapabilities(\n          this,\n          this.configuration.requestMembershipCapability,\n        ),\n        sendLogin(\n          this,\n          this.configuration.username,\n          this.configuration.password,\n        ),\n      ];\n\n      Promise.all(promises).then(\n        () => this.emitReady(),\n        // eslint-disable-next-line ts/no-empty-function\n        () => {},\n      );\n    }\n\n    this.transport.connect(() => this.emitConnected());\n  }\n\n  public close(): void {\n    // -> close is emitted\n    this.transport.stream.destroy();\n  }\n\n  public destroy(error?: Error): void {\n    this.transport.stream.destroy(error);\n  }\n\n  public sendRaw(command: string): void {\n    validateIRCCommand(command);\n    this.emit(\"rawCommmand\", command);\n    this.log.debug(\">\", command);\n    this.transport.stream.write(`${command}\\r\\n`);\n  }\n\n  public onConnect(): void {\n    sendClientPings(this);\n  }\n\n  public use(mixin: ConnectionMixin): void {\n    mixin.applyToConnection(this);\n  }\n\n  private handleLine(line: string): void {\n    if (line.length <= 0) {\n      // ignore empty lines (allowed in IRC)\n      return;\n    }\n\n    this.log.debug(\"<\", line);\n\n    let message;\n    try {\n      message = parseTwitchMessage(line);\n    } catch (error) {\n      this.emitError(\n        new ProtocolError(\n          `Error while parsing IRC message from line \"${line}\"`,\n          error as Error,\n        ),\n      );\n      return;\n    }\n    this.emitMessage(message);\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,IAAI,sBAAsB;AAE1B,IAAa,mBAAb,cAAsC,WAAW;CAC/C,eAA+B;CAE/B,iCAA8C,IAAI,KAAa;CAC/D,iCAA8C,IAAI,KAAa;CAE/D,mBAAsD,EAAE;CACxD;CAEA,MAAyB,YACvB,8BAA8B,KAAK,eACpC;CAED,YAAmB,eAAqC;AACtD,QAAM,cAAc;AAEpB,OAAK,GAAG,UAAU,UAAU;AAC1B,OAAI,mBAAmB,OAAO,gBAAgB,CAC5C,SAAQ,eAAe;AACrB,SAAK,WAAW,MAAM;AACtB,SAAK,UAAU,OAAO,QAAQ,MAAM;KACpC;IAEJ;AACF,OAAK,GAAG,WAAW,KAAK,UAAU,KAAK,KAAK,CAAC;AAE7C,OAAK,YAAY,cAAc,KAAK,cAAc,WAAW;AAE7D,OAAK,UAAU,OAAO,GAAG,eAAe;AACtC,QAAK,YAAY;IACjB;AACF,OAAK,UAAU,OAAO,GAAG,UAAU,UAAU;GAC3C,MAAM,eAAe,IAAI,gBACvB,qCACA,MACD;AACD,QAAK,UAAU,aAAa;AAC5B,QAAK,WAAW,aAAa;AAC7B,QAAK,UAAU,OAAO,QAAQ,aAAa;IAC3C;AAEF,kBAAgB;GAAE,OAAO,KAAK,UAAU;GAAQ,WAAW;GAAU,CAAC,CACnE,GAAG,QAAQ,KAAK,WAAW,KAAK,KAAK,CAAC,CAGtC,GAAG,eAAe,GAAG;AAExB,oBAAkB,KAAK;AACvB,yBAAuB,KAAK;AAE5B,OAAK,GAAG,YAAY,YAAY;AAC9B,QAAK,MAAM,WAAW,KAAK,iBAEzB,KADa,QAAQ,oBAAoB,QAAQ,CAE/C;IAGJ;;CAGJ,UAAuB;AACrB,MAAI,CAAC,KAAK,YACR,OAAM,IAAI,MACR,0DACD;AAGH,OAAK,gBAAgB;AAErB,MAAI,KAAK,cAAc,WAAW,SAChC,MAAK,KAAK,iBAAiB;AACzB,WAAQ,eAAe,KAAK,WAAW,CAAC;IACxC;OACG;GACL,MAAM,WAAW,CACf,oBACE,MACA,KAAK,cAAc,4BACpB,EACD,UACE,MACA,KAAK,cAAc,UACnB,KAAK,cAAc,SACpB,CACF;AAED,WAAQ,IAAI,SAAS,CAAC,WACd,KAAK,WAAW,QAEhB,GACP;;AAGH,OAAK,UAAU,cAAc,KAAK,eAAe,CAAC;;CAGpD,QAAqB;AAEnB,OAAK,UAAU,OAAO,SAAS;;CAGjC,QAAe,OAAqB;AAClC,OAAK,UAAU,OAAO,QAAQ,MAAM;;CAGtC,QAAe,SAAuB;AACpC,qBAAmB,QAAQ;AAC3B,OAAK,KAAK,eAAe,QAAQ;AACjC,OAAK,IAAI,MAAM,KAAK,QAAQ;AAC5B,OAAK,UAAU,OAAO,MAAM,GAAG,QAAQ,MAAM;;CAG/C,YAAyB;AACvB,kBAAgB,KAAK;;CAGvB,IAAW,OAA8B;AACvC,QAAM,kBAAkB,KAAK;;CAG/B,WAAmB,MAAoB;AACrC,MAAI,KAAK,UAAU,EAEjB;AAGF,OAAK,IAAI,MAAM,KAAK,KAAK;EAEzB,IAAI;AACJ,MAAI;AACF,aAAU,mBAAmB,KAAK;WAC3B,OAAO;AACd,QAAK,UACH,IAAI,cACF,8CAA8C,KAAK,IACnD,MACD,CACF;AACD;;AAEF,OAAK,YAAY,QAAQ"}