WSConnection.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import { BehaviorSubject, Subject } from "rxjs"
  2. import { logHoppRequestRunToAnalytics } from "../fb/analytics"
  3. export type WSErrorMessage = SyntaxError | Event
  4. export type WSEvent = { time: number } & (
  5. | { type: "CONNECTING" }
  6. | { type: "CONNECTED" }
  7. | { type: "MESSAGE_SENT"; message: string }
  8. | { type: "MESSAGE_RECEIVED"; message: string }
  9. | { type: "DISCONNECTED"; manual: boolean }
  10. | { type: "ERROR"; error: WSErrorMessage }
  11. )
  12. export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
  13. export class WSConnection {
  14. connectionState$: BehaviorSubject<ConnectionState>
  15. event$: Subject<WSEvent> = new Subject()
  16. socket: WebSocket | undefined
  17. constructor() {
  18. this.connectionState$ = new BehaviorSubject<ConnectionState>("DISCONNECTED")
  19. }
  20. private addEvent(event: WSEvent) {
  21. this.event$.next(event)
  22. }
  23. connect(url: string, protocols: string[]) {
  24. try {
  25. this.connectionState$.next("CONNECTING")
  26. this.socket = new WebSocket(url, protocols)
  27. this.addEvent({
  28. time: Date.now(),
  29. type: "CONNECTING",
  30. })
  31. this.socket.onopen = () => {
  32. this.connectionState$.next("CONNECTED")
  33. this.addEvent({
  34. type: "CONNECTED",
  35. time: Date.now(),
  36. })
  37. }
  38. this.socket.onerror = (error) => {
  39. this.handleError(error)
  40. }
  41. this.socket.onclose = () => {
  42. this.connectionState$.next("DISCONNECTED")
  43. this.addEvent({
  44. type: "DISCONNECTED",
  45. time: Date.now(),
  46. manual: true,
  47. })
  48. }
  49. this.socket.onmessage = ({ data }) => {
  50. this.addEvent({
  51. time: Date.now(),
  52. type: "MESSAGE_RECEIVED",
  53. message: data,
  54. })
  55. }
  56. } catch (error) {
  57. // We will have SyntaxError if anything goes wrong with WebSocket constructor
  58. // See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#exceptions
  59. this.handleError(error as SyntaxError)
  60. }
  61. logHoppRequestRunToAnalytics({
  62. platform: "wss",
  63. })
  64. }
  65. private handleError(error: WSErrorMessage) {
  66. this.disconnect()
  67. this.addEvent({
  68. time: Date.now(),
  69. type: "ERROR",
  70. error,
  71. })
  72. }
  73. sendMessage(event: { message: string; eventName: string }) {
  74. if (this.connectionState$.value === "DISCONNECTED") return
  75. const { message } = event
  76. this.socket?.send(message)
  77. this.addEvent({
  78. time: Date.now(),
  79. type: "MESSAGE_SENT",
  80. message,
  81. })
  82. }
  83. disconnect() {
  84. this.socket?.close()
  85. }
  86. }