| 79 | } |
| 80 | |
| 81 | export class OneWayWebSocket<TData = unknown> |
| 82 | implements OneWayWebSocketApi<TData> |
| 83 | { |
| 84 | readonly #socket: WebSocket; |
| 85 | readonly #errorListeners = new Set<(e: Event) => void>(); |
| 86 | readonly #messageListenerWrappers = new Map< |
| 87 | OneWayEventCallback<TData, "message">, |
| 88 | WebSocketMessageCallback |
| 89 | >(); |
| 90 | |
| 91 | constructor(init: OneWayWebSocketInit) { |
| 92 | const { |
| 93 | apiRoute, |
| 94 | searchParams, |
| 95 | serverProtocols, |
| 96 | binaryType = "blob", |
| 97 | location = window.location, |
| 98 | websocketInit = defaultInit, |
| 99 | } = init; |
| 100 | |
| 101 | if ( |
| 102 | !apiRoute.startsWith("/api/v2/") && |
| 103 | !apiRoute.startsWith("/api/experimental") |
| 104 | ) { |
| 105 | throw new Error( |
| 106 | `API route '${apiRoute}' does not begin with '/api/v2/' or '/api/experimental'`, |
| 107 | ); |
| 108 | } |
| 109 | |
| 110 | const formattedParams = |
| 111 | searchParams instanceof URLSearchParams |
| 112 | ? searchParams |
| 113 | : new URLSearchParams(searchParams); |
| 114 | const paramsString = formattedParams.toString(); |
| 115 | const paramsSuffix = paramsString ? `?${paramsString}` : ""; |
| 116 | const wsProtocol = location.protocol === "https:" ? "wss:" : "ws:"; |
| 117 | const url = `${wsProtocol}//${location.host}${apiRoute}${paramsSuffix}`; |
| 118 | |
| 119 | this.#socket = websocketInit(url, serverProtocols); |
| 120 | this.#socket.binaryType = binaryType; |
| 121 | } |
| 122 | |
| 123 | get url(): string { |
| 124 | return this.#socket.url; |
| 125 | } |
| 126 | |
| 127 | addEventListener<TEvent extends WebSocketEventType>( |
| 128 | event: TEvent, |
| 129 | callback: OneWayEventCallback<TData, TEvent>, |
| 130 | ): void { |
| 131 | if (this.#socket.readyState === WebSocket.CLOSED) { |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | // Not happy about all the type assertions, but there are some nasty |
| 136 | // type contravariance issues if you try to resolve the function types |
| 137 | // properly. This is actually the lesser of two evils |
| 138 | const looseCallback = callback as OneWayEventCallback< |
nothing calls this directly
no outgoing calls
no test coverage detected