it-ws
Version:
Simple async iterables for websocket client connections
71 lines (62 loc) • 1.96 kB
text/typescript
import sink from './sink.js'
import source from './source.js'
import type { SinkOptions } from './sink.js'
import type WebSocket from './web-socket.js'
import type { Duplex, Source } from 'it-stream-types'
export interface DuplexWebSocket extends Duplex<AsyncGenerator<Uint8Array>, Source<Uint8Array>, Promise<void>> {
connected(): Promise<void>
localAddress?: string
localPort?: number
remoteAddress: string
remotePort: number
close(): Promise<void>
destroy(): void
socket: WebSocket
}
export interface DuplexWebSocketOptions extends SinkOptions {
remoteAddress?: string
remotePort?: number
}
export default (socket: WebSocket, options?: DuplexWebSocketOptions): DuplexWebSocket => {
options = options ?? {}
const connectedSource = source(socket)
let remoteAddress: string | undefined = options.remoteAddress
let remotePort: number | undefined = options.remotePort
if (socket.url != null) {
// only client->server sockets have urls, server->client connections do not
try {
const url = new URL(socket.url)
remoteAddress = url.hostname
remotePort = parseInt(url.port, 10)
} catch {}
}
if (remoteAddress == null || remotePort == null) {
throw new Error('Remote connection did not have address and/or port')
}
const duplex: DuplexWebSocket = {
sink: sink(socket, options),
source: connectedSource,
connected: async () => { await connectedSource.connected() },
close: async () => {
if (socket.readyState === socket.CONNECTING || socket.readyState === socket.OPEN) {
await new Promise<void>((resolve) => {
socket.addEventListener('close', () => {
resolve()
})
socket.close()
})
}
},
destroy: () => {
if (socket.terminate != null) {
socket.terminate()
} else {
socket.close()
}
},
remoteAddress,
remotePort,
socket
}
return duplex
}