"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TcpTransport = exports.UdpTransport = void 0;
const dgram_1 = require("dgram");
const net_1 = __importDefault(require("net"));
const net_2 = require("net");
const log_1 = require("./log");
const network_1 = require("./network");
const log = (0, log_1.debug)("werift-ice:packages/ice/src/transport.ts");
class UdpTransport {
    constructor(socketType, options = {}) {
        Object.defineProperty(this, "socketType", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: socketType
        });
        Object.defineProperty(this, "options", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: options
        });
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: "udp"
        });
        Object.defineProperty(this, "socket", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "rinfo", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "onData", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => { }
        });
        Object.defineProperty(this, "closed", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "send", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: async (data, addr) => {
                if (addr && !net_1.default.isIP(addr[0])) {
                    // if address is not resolved, need to use send callback to handle dns failure.
                    return new Promise((r, f) => {
                        this.socket.send(data, addr[1], addr[0], (error) => {
                            if (error) {
                                log("send error", addr, data);
                                f(error);
                            }
                            else {
                                r();
                            }
                        });
                    });
                }
                else {
                    addr = addr ?? [this.rinfo?.address, this.rinfo?.port];
                    // a preestablished remote address does not need a callback to verify dns.
                    // this is faster because event loop is not used per packet.
                    this.socket.send(data, addr[1], addr[0]);
                }
            }
        });
        Object.defineProperty(this, "close", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => new Promise((r) => {
                this.closed = true;
                this.socket.once("close", r);
                try {
                    this.socket.close();
                }
                catch (error) {
                    r();
                }
            })
        });
        this.socket = (0, dgram_1.createSocket)(socketType);
        this.socket.on("message", (data, info) => {
            if ((0, network_1.normalizeFamilyNodeV18)(info.family) === 6) {
                [info.address] = info.address.split("%"); // example fe80::1d3a:8751:4ffd:eb80%wlp82s0
            }
            this.rinfo = info;
            try {
                this.onData(data, [info.address, info.port]);
            }
            catch (error) {
                log("onData error", error);
            }
        });
    }
    static async init(type, options = {}) {
        const transport = new UdpTransport(type, options);
        await transport.init();
        return transport;
    }
    async init() {
        const address = (0, network_1.interfaceAddress)(this.socketType, this.options.interfaceAddresses);
        if (this.options.port) {
            this.socket.bind({ port: this.options.port, address });
        }
        else if (this.options.portRange) {
            const port = await (0, network_1.findPort)(this.options.portRange[0], this.options.portRange[1], this.socketType, this.options.interfaceAddresses);
            this.socket.bind({ port, address });
        }
        else {
            this.socket.bind({ address });
        }
        await new Promise((r) => this.socket.once("listening", r));
    }
    get address() {
        return this.socket.address();
    }
    get host() {
        return this.socket.address().address;
    }
    get port() {
        return this.socket.address().port;
    }
}
exports.UdpTransport = UdpTransport;
class TcpTransport {
    constructor(addr) {
        Object.defineProperty(this, "addr", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: addr
        });
        Object.defineProperty(this, "type", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: "tcp"
        });
        Object.defineProperty(this, "connecting", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "client", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "onData", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: () => { }
        });
        Object.defineProperty(this, "closed", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: false
        });
        Object.defineProperty(this, "send", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: async (data, addr) => {
                await this.connecting;
                this.client.write(data, (err) => {
                    if (err) {
                        console.log("err", err);
                    }
                });
            }
        });
        Object.defineProperty(this, "close", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: async () => {
                this.closed = true;
                this.client.destroy();
            }
        });
        this.connect();
    }
    connect() {
        if (this.closed) {
            return;
        }
        if (this.client) {
            this.client.destroy();
        }
        this.connecting = new Promise((r, f) => {
            try {
                this.client = (0, net_2.connect)({ port: this.addr[1], host: this.addr[0] }, r);
            }
            catch (error) {
                f(error);
            }
        });
        this.client.on("data", (data) => {
            const addr = [
                this.client.remoteAddress,
                this.client.remotePort,
            ];
            this.onData(data, addr);
        });
        this.client.on("end", () => {
            this.connect();
        });
        this.client.on("error", (error) => {
            console.log("error", error);
        });
    }
    async init() {
        await this.connecting;
    }
    static async init(addr) {
        const transport = new TcpTransport(addr);
        await transport.init();
        return transport;
    }
    get address() {
        return {};
    }
}
exports.TcpTransport = TcpTransport;
//# sourceMappingURL=transport.js.map