Files
hoppscotch/packages/hoppscotch-app/helpers/realtime/SIOConnection.ts
Anwarul Islam f6950bac0f refactor: real-time system (#2228)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
Co-authored-by: liyasthomas <liyascthomas@gmail.com>
2022-05-28 15:05:41 +05:30

164 lines
3.9 KiB
TypeScript

import { BehaviorSubject, Subject } from "rxjs"
import { logHoppRequestRunToAnalytics } from "../fb/analytics"
import { SIOClientV2, SIOClientV3, SIOClientV4, SIOClient } from "./SIOClients"
import { SIOClientVersion } from "~/newstore/SocketIOSession"
export const SOCKET_CLIENTS = {
v2: SIOClientV2,
v3: SIOClientV3,
v4: SIOClientV4,
} as const
type SIOAuth = { type: "None" } | { type: "Bearer"; token: string }
export type ConnectionOption = {
url: string
path: string
clientVersion: SIOClientVersion
auth: SIOAuth | undefined
}
export type SIOMessage = {
eventName: string
value: unknown
}
type SIOErrorType = "CONNECTION" | "RECONNECT_ERROR" | "UNKNOWN"
export type SIOError = {
type: SIOErrorType
value: unknown
}
export type SIOEvent = { time: number } & (
| { type: "CONNECTING" }
| { type: "CONNECTED" }
| { type: "MESSAGE_SENT"; message: SIOMessage }
| { type: "MESSAGE_RECEIVED"; message: SIOMessage }
| { type: "DISCONNECTED"; manual: boolean }
| { type: "ERROR"; error: SIOError }
)
export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
export class SIOConnection {
connectionState$: BehaviorSubject<ConnectionState>
event$: Subject<SIOEvent> = new Subject()
socket: SIOClient | undefined
constructor() {
this.connectionState$ = new BehaviorSubject<ConnectionState>("DISCONNECTED")
}
private addEvent(event: SIOEvent) {
this.event$.next(event)
}
connect({ url, path, clientVersion, auth }: ConnectionOption) {
this.connectionState$.next("CONNECTING")
this.addEvent({
time: Date.now(),
type: "CONNECTING",
})
try {
this.socket = new SOCKET_CLIENTS[clientVersion]()
if (auth?.type === "Bearer") {
this.socket.connect(url, {
path,
auth: {
token: auth.token,
},
})
} else {
this.socket.connect(url)
}
this.socket.on("connect", () => {
this.connectionState$.next("CONNECTED")
this.addEvent({
type: "CONNECTED",
time: Date.now(),
})
})
this.socket.on("*", ({ data }: { data: string[] }) => {
const [eventName, message] = data
this.addEvent({
message: { eventName, value: message },
type: "MESSAGE_RECEIVED",
time: Date.now(),
})
})
this.socket.on("connect_error", (error: unknown) => {
this.handleError(error, "CONNECTION")
})
this.socket.on("reconnect_error", (error: unknown) => {
this.handleError(error, "RECONNECT_ERROR")
})
this.socket.on("error", (error: unknown) => {
this.handleError(error, "UNKNOWN")
})
this.socket.on("disconnect", () => {
this.connectionState$.next("DISCONNECTED")
this.addEvent({
type: "DISCONNECTED",
time: Date.now(),
manual: true,
})
})
} catch (error) {
this.handleError(error, "CONNECTION")
}
logHoppRequestRunToAnalytics({
platform: "socketio",
})
}
private handleError(error: unknown, type: SIOErrorType) {
this.disconnect()
this.addEvent({
time: Date.now(),
type: "ERROR",
error: {
type,
value: error,
},
})
}
sendMessage(event: { message: string; eventName: string }) {
if (this.connectionState$.value === "DISCONNECTED") return
const { message, eventName } = event
this.socket?.emit(eventName, message, (data) => {
// receive response from server
this.addEvent({
time: Date.now(),
type: "MESSAGE_RECEIVED",
message: {
eventName,
value: data,
},
})
})
this.addEvent({
time: Date.now(),
type: "MESSAGE_SENT",
message: {
eventName,
value: message,
},
})
}
disconnect() {
this.socket?.close()
this.connectionState$.next("DISCONNECTED")
}
}