"use strict";
const path = require("node:path");
const fs = require("node:fs");
const { platform } = require("node:os");
const nodeProcess = require("node:process");
let koffi;
try {
koffi = require("koffi");
} catch (e) {
throw new Error(
"@engyon/enprot requires the 'koffi' package. Run `npm install` in the package directory."
);
}
const ENPROT_OK = 0;
const ENPROT_ERR_PARSE = 1;
const ENPROT_ERR_CRYPTO = 2;
const ENPROT_ERR_IO = 3;
const ENPROT_ERR_INVALID = 4;
const ERROR_CATEGORY = {
[ENPROT_ERR_PARSE]: "parse",
[ENPROT_ERR_CRYPTO]: "crypto",
[ENPROT_ERR_IO]: "io",
[ENPROT_ERR_INVALID]: "invalid",
};
class EnprotError extends Error {
constructor(code, message) {
super(`[enprot ${ERROR_CATEGORY[code] || "unknown"}] ${message}`);
this.name = "EnprotError";
this.code = code;
this.category = ERROR_CATEGORY[code] || "unknown";
}
}
function libName() {
switch (platform()) {
case "win32":
return "enprot.dll";
case "darwin":
return "libenprot.dylib";
default:
return "libenprot.so";
}
}
function findLibrary() {
const candidates = [];
if (nodeProcess.env.ENPROT_LIB) {
candidates.push(nodeProcess.env.ENPROT_LIB);
}
const repoRoot = path.resolve(__dirname, "..", "..");
candidates.push(path.join(repoRoot, "target", "release", libName()));
candidates.push(path.join(repoRoot, "target", "debug", libName()));
for (const c of candidates) {
if (fs.existsSync(c)) {
return c;
}
}
try {
return koffi.locate("enprot");
} catch {
throw new EnprotError(
ENPROT_ERR_INVALID,
`could not locate ${libName()}. Set ENPROT_LIB or build the enprot crate with \`cargo build --release\` (cdylib). Searched: ${candidates.join(", ")}`
);
}
}
const lib = koffi.load(findLibrary());
const EnprotResult = koffi.struct("EnprotResult", {
code: "int32",
error: "void *",
});
const enprot_process = lib.func("enprot_process", EnprotResult, ["str"]);
const enprot_version = lib.func("enprot_version", "str", []);
const enprot_free_error = lib.func("enprot_free_error", "void", ["void *"]);
function processImpl(config) {
if (config === null || typeof config !== "object" || Array.isArray(config)) {
throw new TypeError("config must be a plain object");
}
const payload = JSON.stringify(config);
const result = enprot_process(payload);
if (result.code !== ENPROT_OK) {
let msg = "(no message)";
if (result.error) {
try {
const buf = koffi.decode(result.error, "char[2048]");
if (buf) msg = Buffer.from(buf).toString("utf8").split("\0")[0];
} finally {
enprot_free_error(result.error);
}
}
throw new EnprotError(result.code, msg);
}
}
function versionImpl() {
return enprot_version() || "";
}
function run(operation, file, opts = {}) {
const config = { operation, file: String(file), ...opts };
for (const k of Object.keys(config)) {
if (config[k] === undefined || config[k] === null) delete config[k];
}
api.process(config);
}
const api = {
EnprotError,
ENPROT_OK,
ENPROT_ERR_PARSE,
ENPROT_ERR_CRYPTO,
ENPROT_ERR_IO,
ENPROT_ERR_INVALID,
version: versionImpl,
process: processImpl,
encrypt(file, opts = {}) {
run("encrypt", file, opts);
},
decrypt(file, opts = {}) {
run("decrypt", file, opts);
},
store(file, opts = {}) {
run("store", file, opts);
},
fetch(file, opts = {}) {
run("fetch", file, opts);
},
};
module.exports = api;