"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.userspaceContact = exports.makeUserspaceLayer = exports.makeBundledWgQuickLayer = exports.WireguardControl = exports.UserspaceLayer = exports.BundledWgQuickLayer = void 0;
var NodeSocket = _interopRequireWildcard(require("@effect/platform-node/NodeSocket"));
var Command = _interopRequireWildcard(require("@effect/platform/Command"));
var CommandExecutor = _interopRequireWildcard(require("@effect/platform/CommandExecutor"));
var PlatformError = _interopRequireWildcard(require("@effect/platform/Error"));
var FileSystem = _interopRequireWildcard(require("@effect/platform/FileSystem"));
var Path = _interopRequireWildcard(require("@effect/platform/Path"));
var Array = _interopRequireWildcard(require("effect/Array"));
var Chunk = _interopRequireWildcard(require("effect/Chunk"));
var Context = _interopRequireWildcard(require("effect/Context"));
var Effect = _interopRequireWildcard(require("effect/Effect"));
var Function = _interopRequireWildcard(require("effect/Function"));
var Layer = _interopRequireWildcard(require("effect/Layer"));
var Option = _interopRequireWildcard(require("effect/Option"));
var Predicate = _interopRequireWildcard(require("effect/Predicate"));
var Schema = _interopRequireWildcard(require("effect/Schema"));
var Sink = _interopRequireWildcard(require("effect/Sink"));
var Stream = _interopRequireWildcard(require("effect/Stream"));
var String = _interopRequireWildcard(require("effect/String"));
var Tuple = _interopRequireWildcard(require("effect/Tuple"));
var exec = _interopRequireWildcard(require("node:child_process"));
var fs = _interopRequireWildcard(require("node:fs"));
var WireguardConfig = _interopRequireWildcard(require("./WireguardConfig.js"));
var WireguardErrors = _interopRequireWildcard(require("./WireguardErrors.js"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * Wireguard control mechanisms.
 *
 * @since 1.0.0
 */

/**
 * @since 1.0.0
 * @category Tags
 */
const WireguardControl = exports.WireguardControl = /*#__PURE__*/Context.GenericTag("@leonitousconforti/the-wireguard-effect/WireguardControl");
/** @internal */
const userspaceContact = (wireguardInterface, content) => Function.pipe(Stream.make(content), Stream.pipeThroughChannelOrFail(NodeSocket.makeNetChannel({
  path: wireguardInterface.SocketLocation
})), Stream.decodeText(), Stream.flatMap(Function.compose(String.linesIterator, Stream.fromIterable)), Stream.map(String.trimEnd), Stream.filter(String.isNonEmpty), Stream.run(Sink.collectAll()), Effect.tap(Function.flow(Chunk.last, Option.getOrThrow, Schema.decodeUnknown(WireguardErrors.SuccessErrno))), Effect.map(Chunk.join("\n")));
/**
 * @since 1.0.0
 * @category Constructors
 */
exports.userspaceContact = userspaceContact;
const makeUserspaceLayer = () => {
  const up = (wireguardConfig, wireguardInterface) => Effect.request(new WireguardConfig.WireguardSetConfigRequest({
    config: wireguardConfig,
    wireguardInterface
  }), WireguardConfig.WireguardSetConfigResolver);
  const down = (_wireguardConfig, wireguardInterface) => Effect.flatMap(FileSystem.FileSystem, fs => fs.remove(wireguardInterface.SocketLocation)).pipe(Effect.map(Function.constant(wireguardInterface)));
  const upScoped = (wireguardConfig, wireguardInterface) => {
    const _down = down(wireguardConfig, wireguardInterface).pipe(Effect.orDie);
    const _up = up(wireguardConfig, wireguardInterface).pipe(Effect.onError(() => _down));
    return Effect.acquireRelease(_up, () => _down);
  };
  return WireguardControl.of({
    up,
    down,
    upScoped,
    getConfigRequestResolver: WireguardConfig.WireguardGetConfigResolver,
    setConfigRequestResolver: WireguardConfig.WireguardSetConfigResolver
  });
};
/**
 * @since 1.0.0
 * @category Constructors
 */
exports.makeUserspaceLayer = makeUserspaceLayer;
const makeBundledWgQuickLayer = options => {
  // process.platform === "win32" && command.includes("-wireguard-go.exe")
  const execCommandWireguardGoWindows = (command, ...args) => Effect.asyncEffect(resume => Effect.gen(function* () {
    const fileSystem = yield* FileSystem.FileSystem;
    const stdout = yield* fileSystem.makeTempFile();
    const stderr = yield* fileSystem.makeTempFile();
    const {
      fd: stdoutFd
    } = yield* fileSystem.open(stdout, {
      flag: "r+"
    });
    const {
      fd: stderrFd
    } = yield* fileSystem.open(stderr, {
      flag: "r+"
    });
    const subprocess = exec.spawn(command, args, {
      detached: true,
      stdio: ["ignore", stdoutFd, stderrFd]
    });
    subprocess.unref();
    const timer = setInterval(() => {
      const data = fs.readFileSync(stdout, "utf8");
      if (data.includes("UAPI listener started")) {
        onStarted();
      }
    }, 1000);
    function onError(error) {
      clearInterval(timer);
      subprocess.off("exit", onExit);
      subprocess.off("close", onClose);
      subprocess.off("error", onError);
      subprocess.off("disconnect", onDisconnect);
      resume(Effect.fail(PlatformError.SystemError({
        reason: "Unknown",
        module: "Command",
        method: "wireguard-go.exe",
        pathOrDescriptor: command,
        message: `${error.message}\n${fs.readFileSync(stdout, "utf-8")}\n${fs.readFileSync(stderr, "utf8")}`
      })));
    }
    function onExit(code) {
      if (Predicate.isNotNull(code)) {
        return onError(new Error(`Process exited unexpectedly with code ${code}`));
      } else {
        return onError(new Error("Process exited unexpectedly."));
      }
    }
    function onClose(code) {
      if (Predicate.isNotNull(code)) {
        return onError(new Error(`Process closed unexpectedly with code ${code}`));
      } else {
        return onError(new Error("Process closed unexpectedly."));
      }
    }
    function onDisconnect() {
      return onError(new Error("Process disconnected unexpectedly."));
    }
    function onStarted() {
      clearInterval(timer);
      subprocess.off("exit", onExit);
      subprocess.off("close", onClose);
      subprocess.off("error", onError);
      subprocess.off("disconnect", onDisconnect);
      // FIXME: I hate this setTimeout, but it seems needed
      setTimeout(() => resume(Effect.succeed(subprocess)), 5000);
    }
    subprocess.on("exit", onExit);
    subprocess.on("close", onClose);
    subprocess.on("error", onError);
    subprocess.on("disconnect", onDisconnect);
    return Effect.sync(() => {
      clearInterval(timer);
      subprocess.off("exit", onExit);
      subprocess.off("close", onClose);
      subprocess.off("error", onError);
      subprocess.off("disconnect", onDisconnect);
    });
  }).pipe(Effect.scoped)).pipe(Effect.timeout("1 minutes"));
  const execCommand = (command, ...args) => Function.pipe(CommandExecutor.CommandExecutor, Effect.flatMap(executor => executor.start(options.sudo && process.platform !== "win32" ? Command.make("sudo", command, ...args) : Command.make(command, ...args))), Effect.flatMap(process => Effect.all([process.stdout.pipe(Stream.decodeText("utf-8"), Stream.splitLines, Stream.runCollect, Effect.map(Chunk.toReadonlyArray)), process.stderr.pipe(Stream.decodeText("utf-8"), Stream.splitLines, Stream.runCollect, Effect.map(Chunk.toReadonlyArray)), process.exitCode], {
    concurrency: 3
  })), Effect.flatMap(([stdout, stderr, exitCode]) => {
    return exitCode === 0 ? Effect.void : Effect.fail(PlatformError.SystemError({
      reason: "Unknown",
      module: "Command",
      pathOrDescriptor: command,
      method: `${command} ${args.join(" ")}`,
      message: `Process exited with code ${exitCode}: ${stdout.join("\n")}, ${stderr.join("\n")}`
    }));
  }), Effect.timeout("1 minutes"), Effect.scoped);
  const internalUp = (wireguardConfig, wireguardInterface) => Effect.gen(function* () {
    const path = yield* Path.Path;
    const fs = yield* FileSystem.FileSystem;
    const tempDirectory = yield* fs.makeTempDirectory();
    const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`);
    yield* wireguardConfig.writeToFile(file);
    const arch = process.arch === "x64" ? "amd64" : process.arch;
    const extension = process.platform === "win32" ? ".exe" : "";
    const wireguardGoUrl = new URL(`./${process.platform}-${arch}-wireguard-go${extension}`, import.meta.url);
    const bundledWireguardGoExecutablePath = yield* path.fromFileUrl(wireguardGoUrl);
    yield* fs.access(bundledWireguardGoExecutablePath, {
      ok: true
    });
    const wgQuickUrl = new URL(`./${process.platform}-wg-quick`, import.meta.url);
    const bundledWgQuickExecutablePath = yield* path.fromFileUrl(wgQuickUrl);
    if (process.platform !== "win32") yield* fs.access(bundledWgQuickExecutablePath, {
      ok: true
    });
    const wgWindowsUrl = new URL(`./win32-${arch}-wireguard.exe`, import.meta.url);
    const bundledWgWindowsExecutablePath = yield* path.fromFileUrl(wgWindowsUrl);
    if (process.platform === "win32") yield* fs.access(bundledWgWindowsExecutablePath, {
      ok: true
    });
    const wgQuickCommandNix = [bundledWgQuickExecutablePath, "up", file];
    const wgQuickCommandWin = [bundledWgWindowsExecutablePath, "/installtunnelservice", file];
    const wgQuickCommand = process.platform === "win32" ? wgQuickCommandWin : wgQuickCommandNix;
    if (process.platform === "win32") {
      const runningWireguardGoProcess = yield* execCommandWireguardGoWindows(bundledWireguardGoExecutablePath, wireguardInterface.Name);
      // FIXME: current structure assumes that these will never fail
      yield* Effect.request(new WireguardConfig.WireguardSetConfigRequest({
        config: wireguardConfig,
        wireguardInterface
      }), WireguardConfig.WireguardSetConfigResolver);
      yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1));
      return Tuple.make(wireguardInterface, runningWireguardGoProcess);
    } else {
      yield* execCommand(bundledWireguardGoExecutablePath, wireguardInterface.Name);
      yield* Effect.request(new WireguardConfig.WireguardSetConfigRequest({
        config: wireguardConfig,
        wireguardInterface
      }), WireguardConfig.WireguardSetConfigResolver);
      yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1));
      return Tuple.make(wireguardInterface);
    }
  });
  const down = (wireguardConfig, wireguardInterface, wireguardGoSubprocess) => Effect.gen(function* () {
    const path = yield* Path.Path;
    const fs = yield* FileSystem.FileSystem;
    if (wireguardGoSubprocess) wireguardGoSubprocess.kill();
    const tempDirectory = yield* fs.makeTempDirectory();
    const file = path.join(tempDirectory, `${wireguardInterface.Name}.conf`);
    yield* wireguardConfig.writeToFile(file);
    const arch = process.arch === "x64" ? "amd64" : process.arch;
    const wgQuickUrl = new URL(`./${process.platform}-wg-quick`, import.meta.url);
    const bundledWgQuickExecutablePath = yield* path.fromFileUrl(wgQuickUrl);
    if (process.platform !== "win32") yield* fs.access(bundledWgQuickExecutablePath, {
      ok: true
    });
    const wgWindowsUrl = new URL(`./win32-${arch}-wireguard.exe`, import.meta.url);
    const bundledWgWindowsExecutablePath = yield* path.fromFileUrl(wgWindowsUrl);
    if (process.platform === "win32") yield* fs.access(bundledWgWindowsExecutablePath, {
      ok: true
    });
    const wgQuickCommandWin = [bundledWgWindowsExecutablePath, "/uninstalltunnelservice", wireguardInterface.Name];
    const wgQuickCommandNix = [bundledWgQuickExecutablePath, "down", file];
    const wgQuickCommand = process.platform === "win32" ? wgQuickCommandWin : wgQuickCommandNix;
    yield* execCommand(wgQuickCommand[0], ...wgQuickCommand.slice(1));
    return wireguardInterface;
  });
  const upScoped = (wireguardConfig, wireguardInterface) => {
    const _down = wireguardGoSubprocess => down(wireguardConfig, wireguardInterface, wireguardGoSubprocess).pipe(Effect.orDie);
    const _up = internalUp(wireguardConfig, wireguardInterface).pipe(Effect.onError(() => _down()));
    return Effect.acquireRelease(_up, data => {
      if (Array.isArray(data) && Tuple.isTupleOf(data, 2)) {
        const [_, wireguardGoSubprocess] = data;
        return _down(wireguardGoSubprocess);
      } else {
        return _down();
      }
    }).pipe(Effect.map(data => Tuple.isTupleOf(data, 2) ? Tuple.getFirst(data) : data[0]));
  };
  const up = (wireguardConfig, wireguardInterface) => internalUp(wireguardConfig, wireguardInterface).pipe(Effect.map(data => Tuple.isTupleOf(data, 2) ? Tuple.getFirst(data) : data[0]));
  return WireguardControl.of({
    up,
    down,
    upScoped,
    getConfigRequestResolver: WireguardConfig.WireguardGetConfigResolver,
    setConfigRequestResolver: WireguardConfig.WireguardSetConfigResolver
  });
};
/**
 * @since 1.0.0
 * @category Layers
 */
exports.makeBundledWgQuickLayer = makeBundledWgQuickLayer;
const UserspaceLayer = exports.UserspaceLayer = /*#__PURE__*/Layer.sync(WireguardControl, makeUserspaceLayer);
/**
 * @since 1.0.0
 * @category Layers
 */
const BundledWgQuickLayer = exports.BundledWgQuickLayer = /*#__PURE__*/Layer.sync(WireguardControl, () => makeBundledWgQuickLayer({
  sudo: true
}));
//# sourceMappingURL=WireguardControl.js.map