///
import * as dgram from "node:dgram"
import type {BindOptions, RemoteInfo, Socket, SocketOptions} from "node:dgram"
import type {AddressInfo} from "node:net"
import {TimeoutError} from "./timeout-error.js"
export interface IncomingPacket {
msg: Buffer
rinfo: RemoteInfo
}
export interface SocketAsPromisedOptions extends SocketOptions {
dgram?: typeof dgram
}
export class SocketAsPromised implements AsyncIterable {
private _closed?: boolean
private _errored?: Error
private _timeout = 0
constructor(public readonly socket: Socket) {
socket.on("close", this.closeHandler)
socket.on("error", this.errorHandler)
}
get timeout(): number {
return this._timeout
}
bind(port?: number, address?: string): Promise
bind(options: BindOptions): Promise
bind(arg1?: any, arg2?: any): Promise {
const socket = this.socket
return new Promise((resolve, reject) => {
if (this._errored) {
const err = this._errored
this._errored = undefined
return reject(err)
}
const errorHandler = (err: Error) => {
removeListeners()
reject(err)
}
const listeningHandler = () => {
const address = socket.address() as AddressInfo
removeListeners()
resolve(address)
}
const removeListeners = () => {
socket.removeListener("error", errorHandler)
socket.removeListener("listening", listeningHandler)
}
socket.on("error", errorHandler)
socket.on("listening", listeningHandler)
try {
socket.bind(arg1, arg2)
} catch (e) {
removeListeners()
reject(e)
}
})
}
addMembership(multicastAddress: string, multicastInterface?: string): this {
this.socket.addMembership(multicastAddress, multicastInterface)
return this
}
close(): Promise {
return new Promise((resolve, reject) => {
try {
this.socket.once("close", () => {
this._closed = true
resolve()
})
this.socket.close()
} catch (err) {
reject(err)
}
})
}
send(msg: Buffer | string | Uint8Array | any[], port: number, address: string): Promise
send(
msg: Buffer | string | Uint8Array,
offset: number,
length: number,
port: number,
address: string,
): Promise
send(arg1: any, arg2: any, arg3: any, arg4?: any, arg5?: any): Promise {
return new Promise((resolve, reject) => {
if (this._errored) {
const err = this._errored
this._errored = undefined
return reject(err)
}
try {
if (arg4 !== undefined) {
this.socket.send(arg1, arg2, arg3, arg4, arg5, (err, sent) => {
if (err) {
reject(err)
} else {
resolve(sent)
}
})
} else {
this.socket.send(arg1, arg2, arg3, (err, sent) => {
if (err) {
reject(err)
} else {
resolve(sent)
}
})
}
} catch (err) {
reject(err)
}
})
}
recv(): Promise {
const socket = this.socket
let timeoutId: NodeJS.Timeout | undefined
return new Promise((resolve, reject) => {
if (this._errored) {
const err = this._errored
this._errored = undefined
return reject(err)
}
if (this._closed) {
this._closed = undefined
return resolve(undefined)
}
if (this.timeout) {
timeoutId = setTimeout(() => {
socket.emit("error", new TimeoutError())
}, this.timeout)
}
const closeHandler = () => {
removeListeners()
resolve(undefined)
}
const errorHandler = (err: Error) => {
removeListeners()
reject(err)
}
const messageHandler = (msg: Buffer, rinfo: RemoteInfo) => {
removeListeners()
resolve({msg, rinfo})
}
const removeListeners = () => {
socket.removeListener("close", closeHandler)
socket.removeListener("error", errorHandler)
socket.removeListener("message", messageHandler)
if (timeoutId) {
clearTimeout(timeoutId)
timeoutId = undefined
}
}
socket.on("close", closeHandler)
socket.on("error", errorHandler)
socket.on("message", messageHandler)
})
}
address(): AddressInfo {
return this.socket.address()
}
setBroadcast(flag: boolean): this {
this.socket.setBroadcast(flag)
return this
}
setTTL(ttl: number): this {
this.socket.setTTL(ttl)
return this
}
setMulticastTTL(ttl: number): this {
this.socket.setMulticastTTL(ttl)
return this
}
setMulticastInterface(multicastInterface: string): this {
this.socket.setMulticastInterface(multicastInterface)
return this
}
setMulticastLoopback(flag: boolean): this {
this.socket.setMulticastLoopback(flag)
return this
}
dropMembership(multicastAddress: string, multicastInterface?: string): this {
this.socket.dropMembership(multicastAddress, multicastInterface)
return this
}
ref(): this {
this.socket.ref()
return this
}
unref(): this {
this.socket.unref()
return this
}
setRecvBufferSize(size: number): this {
this.socket.setRecvBufferSize(size)
return this
}
setSendBufferSize(size: number): this {
this.socket.setSendBufferSize(size)
return this
}
getRecvBufferSize(): number {
return this.socket.getRecvBufferSize()
}
getSendBufferSize(): number {
return this.getSendBufferSize()
}
setTimeout(timeout: number): this {
this._timeout = timeout
return this
}
iterate(): AsyncIterableIterator {
const socketAsPromised = this
let wasEof = false
return {
[Symbol.asyncIterator](): AsyncIterableIterator {
return this
},
async next(): Promise> {
if (wasEof) {
return {value: "", done: true}
} else {
const value = await socketAsPromised.recv()
if (value === undefined) {
wasEof = true
return {value: "", done: true}
} else {
return {value, done: false}
}
}
},
}
}
[Symbol.asyncIterator](): AsyncIterableIterator {
return this.iterate()
}
destroy(): this {
const socket = this.socket
if (socket) {
socket.removeListener("close", this.closeHandler)
socket.removeListener("error", this.errorHandler)
}
return this
}
private readonly closeHandler = (): void => {
this._closed = true
}
private readonly errorHandler = (err: Error): void => {
this._errored = err
}
}
export default SocketAsPromised