SIOConnection.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { BehaviorSubject, Subject } from "rxjs"
  2. import { logHoppRequestRunToAnalytics } from "../fb/analytics"
  3. import { SIOClientV2, SIOClientV3, SIOClientV4, SIOClient } from "./SIOClients"
  4. import { SIOClientVersion } from "~/newstore/SocketIOSession"
  5. export const SOCKET_CLIENTS = {
  6. v2: SIOClientV2,
  7. v3: SIOClientV3,
  8. v4: SIOClientV4,
  9. } as const
  10. type SIOAuth = { type: "None" } | { type: "Bearer"; token: string }
  11. export type ConnectionOption = {
  12. url: string
  13. path: string
  14. clientVersion: SIOClientVersion
  15. auth: SIOAuth | undefined
  16. }
  17. export type SIOMessage = {
  18. eventName: string
  19. value: unknown
  20. }
  21. type SIOErrorType = "CONNECTION" | "RECONNECT_ERROR" | "UNKNOWN"
  22. export type SIOError = {
  23. type: SIOErrorType
  24. value: unknown
  25. }
  26. export type SIOEvent = { time: number } & (
  27. | { type: "CONNECTING" }
  28. | { type: "CONNECTED" }
  29. | { type: "MESSAGE_SENT"; message: SIOMessage }
  30. | { type: "MESSAGE_RECEIVED"; message: SIOMessage }
  31. | { type: "DISCONNECTED"; manual: boolean }
  32. | { type: "ERROR"; error: SIOError }
  33. )
  34. export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
  35. export class SIOConnection {
  36. connectionState$: BehaviorSubject<ConnectionState>
  37. event$: Subject<SIOEvent> = new Subject()
  38. socket: SIOClient | undefined
  39. constructor() {
  40. this.connectionState$ = new BehaviorSubject<ConnectionState>("DISCONNECTED")
  41. }
  42. private addEvent(event: SIOEvent) {
  43. this.event$.next(event)
  44. }
  45. connect({ url, path, clientVersion, auth }: ConnectionOption) {
  46. this.connectionState$.next("CONNECTING")
  47. this.addEvent({
  48. time: Date.now(),
  49. type: "CONNECTING",
  50. })
  51. try {
  52. this.socket = new SOCKET_CLIENTS[clientVersion]()
  53. if (auth?.type === "Bearer") {
  54. this.socket.connect(url, {
  55. path,
  56. auth: {
  57. token: auth.token,
  58. },
  59. })
  60. } else {
  61. this.socket.connect(url)
  62. }
  63. this.socket.on("connect", () => {
  64. this.connectionState$.next("CONNECTED")
  65. this.addEvent({
  66. type: "CONNECTED",
  67. time: Date.now(),
  68. })
  69. })
  70. this.socket.on("*", ({ data }: { data: string[] }) => {
  71. const [eventName, message] = data
  72. this.addEvent({
  73. message: { eventName, value: message },
  74. type: "MESSAGE_RECEIVED",
  75. time: Date.now(),
  76. })
  77. })
  78. this.socket.on("connect_error", (error: unknown) => {
  79. this.handleError(error, "CONNECTION")
  80. })
  81. this.socket.on("reconnect_error", (error: unknown) => {
  82. this.handleError(error, "RECONNECT_ERROR")
  83. })
  84. this.socket.on("error", (error: unknown) => {
  85. this.handleError(error, "UNKNOWN")
  86. })
  87. this.socket.on("disconnect", () => {
  88. this.connectionState$.next("DISCONNECTED")
  89. this.addEvent({
  90. type: "DISCONNECTED",
  91. time: Date.now(),
  92. manual: true,
  93. })
  94. })
  95. } catch (error) {
  96. this.handleError(error, "CONNECTION")
  97. }
  98. logHoppRequestRunToAnalytics({
  99. platform: "socketio",
  100. })
  101. }
  102. private handleError(error: unknown, type: SIOErrorType) {
  103. this.disconnect()
  104. this.addEvent({
  105. time: Date.now(),
  106. type: "ERROR",
  107. error: {
  108. type,
  109. value: error,
  110. },
  111. })
  112. }
  113. sendMessage(event: { message: string; eventName: string }) {
  114. if (this.connectionState$.value === "DISCONNECTED") return
  115. const { message, eventName } = event
  116. this.socket?.emit(eventName, message, (data) => {
  117. // receive response from server
  118. this.addEvent({
  119. time: Date.now(),
  120. type: "MESSAGE_RECEIVED",
  121. message: {
  122. eventName,
  123. value: data,
  124. },
  125. })
  126. })
  127. this.addEvent({
  128. time: Date.now(),
  129. type: "MESSAGE_SENT",
  130. message: {
  131. eventName,
  132. value: message,
  133. },
  134. })
  135. }
  136. disconnect() {
  137. this.socket?.close()
  138. this.connectionState$.next("DISCONNECTED")
  139. }
  140. }