fix(realtime): 内置 WCDB sidecar 并切换项目内 DLL

- 桌面端启动时自动拉起 WCDB sidecar,并向后端注入连接参数

- 打包附带 sidecar 脚本与 koffi 运行时

- 改为通过项目内置 WeFlow DLL 处理 realtime 调用,规避宿主校验失败和连接超时
This commit is contained in:
2977094657
2026-04-11 16:56:42 +08:00
Unverified
parent 1e7c02863d
commit 604e01eb50
10 changed files with 1876 additions and 41 deletions
+161
View File
@@ -17,6 +17,7 @@ try {
autoUpdaterLoadError = err;
}
const { spawn, spawnSync } = require("child_process");
const crypto = require("crypto");
const fs = require("fs");
const http = require("http");
const net = require("net");
@@ -33,6 +34,10 @@ const DEFAULT_BACKEND_HOST = String(process.env.WECHAT_TOOL_HOST || "127.0.0.1")
const DEFAULT_BACKEND_PORT = parsePort(process.env.WECHAT_TOOL_PORT) ?? 10392;
let backendProc = null;
let wcdbSidecarProc = null;
let wcdbSidecarPort = null;
let wcdbSidecarUrl = "";
let wcdbSidecarToken = "";
let resolvedDataDir = null;
let mainWindow = null;
let tray = null;
@@ -1452,8 +1457,159 @@ function getPackagedWcdbDllPath() {
return path.join(process.resourcesPath, "backend", "native", "wcdb_api.dll");
}
function getDevWcdbDllPath() {
return path.join(repoRoot(), "src", "wechat_decrypt_tool", "native", "wcdb_api.dll");
}
function getWcdbDllPath() {
return app.isPackaged ? getPackagedWcdbDllPath() : getDevWcdbDllPath();
}
function getWcdbDllDir() {
return path.dirname(getWcdbDllPath());
}
function getWcdbSidecarScriptPath() {
return app.isPackaged
? path.join(process.resourcesPath, "wcdb-sidecar.cjs")
: path.join(__dirname, "wcdb-sidecar.cjs");
}
function getKoffiDir() {
return app.isPackaged ? path.join(process.resourcesPath, "koffi") : path.join(repoRoot(), "desktop", "vendor", "koffi");
}
function getWcdbSidecarStdioLogPath(dataDir) {
return path.join(dataDir, "wcdb-sidecar-stdio.log");
}
function getWcdbSidecarPort() {
if (parsePort(wcdbSidecarPort) != null) return wcdbSidecarPort;
const envPort = parsePort(process.env.WECHAT_TOOL_WCDB_SIDECAR_PORT);
wcdbSidecarPort = envPort ?? Math.min(65535, getBackendPort() + 101);
return wcdbSidecarPort;
}
async function prepareWcdbSidecarPort() {
if (parsePort(wcdbSidecarPort) != null) return wcdbSidecarPort;
const envPort = parsePort(process.env.WECHAT_TOOL_WCDB_SIDECAR_PORT);
if (envPort != null) {
wcdbSidecarPort = envPort;
return wcdbSidecarPort;
}
const preferred = Math.min(65535, getBackendPort() + 101);
wcdbSidecarPort = await chooseAvailablePort(preferred, "127.0.0.1");
if (wcdbSidecarPort == null) wcdbSidecarPort = preferred;
return wcdbSidecarPort;
}
function getWcdbResourcePaths() {
const out = [];
const seen = new Set();
const add = (value) => {
const raw = String(value || "").trim();
if (!raw) return;
const resolved = path.resolve(raw);
const key = resolved.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
out.push(resolved);
};
const dllDir = getWcdbDllDir();
add(dllDir);
add(path.dirname(dllDir));
add(repoRoot());
add(path.join(repoRoot(), "resources"));
const dataDir = resolveDataDir();
if (dataDir) {
add(dataDir);
add(path.join(dataDir, "resources"));
}
return out;
}
function ensureWcdbSidecarEnv(env) {
if (!wcdbSidecarUrl || !wcdbSidecarToken) return env;
env.WECHAT_TOOL_WCDB_SIDECAR_URL = wcdbSidecarUrl;
env.WECHAT_TOOL_WCDB_SIDECAR_TOKEN = wcdbSidecarToken;
return env;
}
function startWcdbSidecar() {
if (process.env.WECHAT_TOOL_WCDB_SIDECAR === "0") return null;
if (wcdbSidecarProc && wcdbSidecarProc.exitCode == null) return wcdbSidecarProc;
if (process.platform !== "win32") return null;
const dllPath = getWcdbDllPath();
const sidecarScript = getWcdbSidecarScriptPath();
const koffiDir = getKoffiDir();
if (!fs.existsSync(dllPath)) {
logMain(`[wcdb-sidecar] skip: missing wcdb_api.dll ${dllPath}`);
return null;
}
if (!fs.existsSync(sidecarScript)) {
logMain(`[wcdb-sidecar] skip: missing sidecar script ${sidecarScript}`);
return null;
}
if (!fs.existsSync(koffiDir)) {
logMain(`[wcdb-sidecar] skip: missing koffi runtime ${koffiDir}`);
return null;
}
const port = getWcdbSidecarPort();
const host = "127.0.0.1";
wcdbSidecarUrl = `http://${host}:${port}`;
wcdbSidecarToken = wcdbSidecarToken || crypto.randomBytes(24).toString("hex");
const env = {
...process.env,
ELECTRON_RUN_AS_NODE: "1",
WECHAT_TOOL_WCDB_SIDECAR_HOST: host,
WECHAT_TOOL_WCDB_SIDECAR_PORT: String(port),
WECHAT_TOOL_WCDB_SIDECAR_TOKEN: wcdbSidecarToken,
WECHAT_TOOL_WCDB_API_DLL_PATH: dllPath,
WECHAT_TOOL_WCDB_DLL_DIR: getWcdbDllDir(),
WECHAT_TOOL_WCDB_RESOURCE_PATHS: JSON.stringify(getWcdbResourcePaths()),
WECHAT_TOOL_KOFFI_DIR: koffiDir,
};
logMain(`[wcdb-sidecar] starting url=${wcdbSidecarUrl} dll=${dllPath}`);
wcdbSidecarProc = spawn(process.execPath, [sidecarScript], {
cwd: path.dirname(sidecarScript),
env,
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
const dataDir = resolveDataDir() || getUserDataDir() || repoRoot();
attachBackendStdio(wcdbSidecarProc, getWcdbSidecarStdioLogPath(dataDir));
const proc = wcdbSidecarProc;
proc.on("exit", (code, signal) => {
if (wcdbSidecarProc === proc) wcdbSidecarProc = null;
logMain(`[wcdb-sidecar] exited code=${code} signal=${signal}`);
});
process.env.WECHAT_TOOL_WCDB_SIDECAR_URL = wcdbSidecarUrl;
process.env.WECHAT_TOOL_WCDB_SIDECAR_TOKEN = wcdbSidecarToken;
return wcdbSidecarProc;
}
function stopWcdbSidecar() {
if (!wcdbSidecarProc) return;
const pid = wcdbSidecarProc.pid;
logMain(`[wcdb-sidecar] stop pid=${pid || "?"}`);
try {
wcdbSidecarProc.kill();
} catch {}
wcdbSidecarProc = null;
}
function startBackend() {
if (backendProc) return backendProc;
startWcdbSidecar();
const env = {
...process.env,
@@ -1462,6 +1618,7 @@ function startBackend() {
// Make sure Python prints UTF-8 to stdout/stderr.
PYTHONIOENCODING: process.env.PYTHONIOENCODING || "utf-8",
};
ensureWcdbSidecarEnv(env);
// In packaged mode we expect to provide the generated Nuxt output dir via env.
if (app.isPackaged && !env.WECHAT_TOOL_UI_DIR) {
@@ -2135,6 +2292,7 @@ async function main() {
await applyPendingOutputDirOnStartup();
ensureOutputLink();
await ensureBackendPortAvailableOnStartup();
await prepareWcdbSidecarPort();
logMain(`[main] app.isPackaged=${app.isPackaged} argv=${JSON.stringify(process.argv)}`);
@@ -2190,6 +2348,7 @@ async function main() {
app.on("window-all-closed", () => {
stopBackend();
stopWcdbSidecar();
if (process.platform !== "darwin") app.quit();
});
@@ -2203,6 +2362,7 @@ app.on("before-quit", () => {
isQuitting = true;
destroyTray();
stopBackend();
stopWcdbSidecar();
});
if (gotSingleInstanceLock) {
@@ -2211,6 +2371,7 @@ if (gotSingleInstanceLock) {
console.error(err);
logMain(`[main] fatal: ${err?.stack || String(err)}`);
stopBackend();
stopWcdbSidecar();
try {
const dir = getUserDataDir();
const outputDir = resolveOutputDir();
+545
View File
@@ -0,0 +1,545 @@
"use strict";
const fs = require("fs");
const http = require("http");
const path = require("path");
const HOST = String(process.env.WECHAT_TOOL_WCDB_SIDECAR_HOST || "127.0.0.1").trim() || "127.0.0.1";
const PORT = Number.parseInt(String(process.env.WECHAT_TOOL_WCDB_SIDECAR_PORT || "0").trim(), 10);
const TOKEN = String(process.env.WECHAT_TOOL_WCDB_SIDECAR_TOKEN || "").trim();
let koffi = null;
let nativeLib = null;
let nativeDllPath = "";
let initialized = false;
let protectionResults = [];
const preloadedLibs = [];
const funcs = Object.create(null);
class ApiError extends Error {
constructor(message, rc = 0, details = null) {
super(message);
this.name = "ApiError";
this.rc = rc;
this.details = details;
}
}
function log(message) {
process.stderr.write(`[wcdb-sidecar] ${new Date().toISOString()} ${message}\n`);
}
function jsonResponse(res, statusCode, payload) {
const body = Buffer.from(JSON.stringify(payload), "utf8");
res.writeHead(statusCode, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": String(body.length),
"Cache-Control": "no-store",
});
res.end(body);
}
function readRequestJson(req) {
return new Promise((resolve, reject) => {
const chunks = [];
let total = 0;
req.on("data", (chunk) => {
total += chunk.length;
if (total > 16 * 1024 * 1024) {
reject(new ApiError("request body too large"));
req.destroy();
return;
}
chunks.push(chunk);
});
req.on("end", () => {
try {
const raw = Buffer.concat(chunks).toString("utf8").trim();
resolve(raw ? JSON.parse(raw) : {});
} catch (err) {
reject(new ApiError(`invalid json: ${err?.message || err}`));
}
});
req.on("error", reject);
});
}
function parseResourcePaths() {
const out = [];
const seen = new Set();
function add(value) {
const raw = String(value || "").trim();
if (!raw) return;
const resolved = path.resolve(raw);
const key = resolved.toLowerCase();
if (seen.has(key)) return;
seen.add(key);
out.push(resolved);
}
const raw = String(process.env.WECHAT_TOOL_WCDB_RESOURCE_PATHS || "").trim();
if (raw) {
try {
const decoded = JSON.parse(raw);
if (Array.isArray(decoded)) {
for (const item of decoded) add(item);
}
} catch {
for (const item of raw.split(path.delimiter)) add(item);
}
}
const dllDir = getDllDir();
add(dllDir);
add(path.dirname(dllDir));
add(process.cwd());
return out;
}
function getDllDir() {
const fromEnv = String(process.env.WECHAT_TOOL_WCDB_DLL_DIR || "").trim();
if (fromEnv) return path.resolve(fromEnv);
const dllPath = String(process.env.WECHAT_TOOL_WCDB_API_DLL_PATH || "").trim();
if (dllPath) return path.dirname(path.resolve(dllPath));
return process.cwd();
}
function getDllPath() {
const fromEnv = String(process.env.WECHAT_TOOL_WCDB_API_DLL_PATH || "").trim();
if (fromEnv) return path.resolve(fromEnv);
return path.join(getDllDir(), "wcdb_api.dll");
}
function loadKoffi() {
if (koffi) return koffi;
const koffiDir = String(process.env.WECHAT_TOOL_KOFFI_DIR || "").trim();
if (koffiDir) {
koffi = require(path.resolve(koffiDir));
} else {
koffi = require("koffi");
}
return koffi;
}
function tryFunc(signature) {
try {
return nativeLib.func(signature);
} catch {
return null;
}
}
function loadNative() {
if (nativeLib) return nativeLib;
const ffi = loadKoffi();
const dllDir = getDllDir();
nativeDllPath = getDllPath();
if (!fs.existsSync(nativeDllPath)) {
throw new ApiError(`wcdb_api.dll not found: ${nativeDllPath}`);
}
try {
process.chdir(dllDir);
} catch {}
for (const dep of ["WCDB.dll", "SDL2.dll", "VoipEngine.dll"]) {
const depPath = path.join(dllDir, dep);
if (!fs.existsSync(depPath)) continue;
try {
preloadedLibs.push(ffi.load(depPath));
log(`preloaded dependency ${depPath}`);
} catch (err) {
log(`preload dependency failed ${depPath}: ${err?.message || err}`);
}
}
nativeLib = ffi.load(nativeDllPath);
funcs.InitProtection = tryFunc("int32 InitProtection(const char* resourcePath)");
funcs.wcdb_init = tryFunc("int32 wcdb_init()");
funcs.wcdb_shutdown = tryFunc("int32 wcdb_shutdown()");
funcs.wcdb_open_account = tryFunc("int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)");
funcs.wcdb_close_account = tryFunc("int32 wcdb_close_account(int64 handle)");
funcs.wcdb_set_my_wxid = tryFunc("int32 wcdb_set_my_wxid(int64 handle, const char* wxid)");
funcs.wcdb_get_sessions = tryFunc("int32 wcdb_get_sessions(int64 handle, _Out_ void** outJson)");
funcs.wcdb_get_messages = tryFunc(
"int32 wcdb_get_messages(int64 handle, const char* username, int32 limit, int32 offset, _Out_ void** outJson)"
);
funcs.wcdb_get_message_count = tryFunc(
"int32 wcdb_get_message_count(int64 handle, const char* username, _Out_ int32* count)"
);
funcs.wcdb_get_display_names = tryFunc(
"int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)"
);
funcs.wcdb_get_avatar_urls = tryFunc(
"int32 wcdb_get_avatar_urls(int64 handle, const char* usernamesJson, _Out_ void** outJson)"
);
funcs.wcdb_get_group_member_count = tryFunc(
"int32 wcdb_get_group_member_count(int64 handle, const char* chatroomId, _Out_ int32* count)"
);
funcs.wcdb_get_group_members = tryFunc(
"int32 wcdb_get_group_members(int64 handle, const char* chatroomId, _Out_ void** outJson)"
);
funcs.wcdb_get_group_nicknames = tryFunc(
"int32 wcdb_get_group_nicknames(int64 handle, const char* chatroomId, _Out_ void** outJson)"
);
funcs.wcdb_exec_query = tryFunc(
"int32 wcdb_exec_query(int64 handle, const char* kind, const char* dbPath, const char* sql, _Out_ void** outJson)"
);
funcs.wcdb_update_message = tryFunc(
"int32 wcdb_update_message(int64 handle, const char* sessionId, int64 localId, int32 createTime, const char* newContent, _Out_ void** outError)"
);
funcs.wcdb_delete_message = tryFunc(
"int32 wcdb_delete_message(int64 handle, const char* sessionId, int64 localId, int32 createTime, const char* dbPathHint, _Out_ void** outError)"
);
funcs.wcdb_get_sns_timeline = tryFunc(
"int32 wcdb_get_sns_timeline(int64 handle, int32 limit, int32 offset, const char* usernamesJson, const char* keyword, int32 startTime, int32 endTime, _Out_ void** outJson)"
);
funcs.wcdb_decrypt_sns_image = tryFunc(
"int32 wcdb_decrypt_sns_image(void* encryptedData, int32 len, const char* key, _Out_ void** outHex)"
);
funcs.wcdb_get_logs = tryFunc("int32 wcdb_get_logs(_Out_ void** outJson)");
funcs.wcdb_free_string = tryFunc("void wcdb_free_string(void* ptr)");
if (!funcs.wcdb_init || !funcs.wcdb_open_account || !funcs.wcdb_get_logs || !funcs.wcdb_free_string) {
throw new ApiError("wcdb_api.dll is missing required exports");
}
log(`loaded ${nativeDllPath}`);
return nativeLib;
}
function requireFunc(name) {
loadNative();
const fn = funcs[name];
if (!fn) throw new ApiError(`${name} not exported`, -404);
return fn;
}
function ptrToString(ptr) {
if (!ptr) return "";
return loadKoffi().decode(ptr, "char", -1);
}
function freeStringPtr(ptr) {
if (!ptr || !funcs.wcdb_free_string) return;
try {
funcs.wcdb_free_string(ptr);
} catch {}
}
function getLogs() {
try {
loadNative();
const out = [null];
const rc = Number(funcs.wcdb_get_logs(out));
try {
if (rc !== 0 || !out[0]) return [];
const payload = ptrToString(out[0]);
const decoded = JSON.parse(payload || "[]");
return Array.isArray(decoded) ? decoded.map((x) => String(x)) : [];
} finally {
freeStringPtr(out[0]);
}
} catch {
return [];
}
}
function callOutJson(name, args) {
const fn = requireFunc(name);
const out = [null];
const rc = Number(fn(...args, out));
try {
if (rc !== 0) throw new ApiError(`${name} failed`, rc);
return ptrToString(out[0]);
} finally {
freeStringPtr(out[0]);
}
}
function callOutError(name, args) {
const fn = requireFunc(name);
const out = [null];
const rc = Number(fn(...args, out));
try {
if (rc !== 0) {
const message = ptrToString(out[0]) || `${name} failed`;
throw new ApiError(message, rc);
}
return null;
} finally {
freeStringPtr(out[0]);
}
}
function ensureInitialized() {
loadNative();
if (initialized) {
return { initialized: true, dllPath: nativeDllPath, protection: protectionResults };
}
protectionResults = [];
if (funcs.InitProtection) {
for (const resourcePath of parseResourcePaths()) {
try {
const rc = Number(funcs.InitProtection(resourcePath));
protectionResults.push({ path: resourcePath, rc });
log(`InitProtection rc=${rc} path=${resourcePath}`);
if (rc === 0) break;
} catch (err) {
protectionResults.push({ path: resourcePath, error: String(err?.message || err) });
}
}
}
const rc = Number(funcs.wcdb_init());
if (rc !== 0) {
throw new ApiError("wcdb_init failed", rc, { protection: protectionResults });
}
initialized = true;
return { initialized: true, dllPath: nativeDllPath, protection: protectionResults };
}
function normalizeHandle(value) {
const n = Number(value || 0);
if (!Number.isFinite(n) || n <= 0) throw new ApiError("invalid handle");
return n;
}
function handleAction(action, payload) {
const data = payload && typeof payload === "object" ? payload : {};
switch (action) {
case "init":
return ensureInitialized();
case "get_logs":
loadNative();
return { logs: getLogs() };
case "open_account": {
ensureInitialized();
const dbPath = String(data.path || "").trim();
const key = String(data.key || "").trim();
if (!dbPath) throw new ApiError("missing account path");
if (key.length !== 64) throw new ApiError("invalid db key");
const out = [0];
const rc = Number(requireFunc("wcdb_open_account")(dbPath, key, out));
const handle = Number(out[0] || 0);
if (rc !== 0 || handle <= 0) throw new ApiError("wcdb_open_account failed", rc);
return { handle };
}
case "close_account": {
const handle = normalizeHandle(data.handle);
try {
requireFunc("wcdb_close_account")(handle);
} catch {}
return { closed: true };
}
case "set_my_wxid": {
const fn = requireFunc("wcdb_set_my_wxid");
const rc = Number(fn(normalizeHandle(data.handle), String(data.wxid || "").trim()));
return { success: rc === 0, rc };
}
case "get_sessions":
return { payload: callOutJson("wcdb_get_sessions", [normalizeHandle(data.handle)]) };
case "get_messages":
return {
payload: callOutJson("wcdb_get_messages", [
normalizeHandle(data.handle),
String(data.username || "").trim(),
Number.parseInt(String(data.limit || 0), 10) || 0,
Number.parseInt(String(data.offset || 0), 10) || 0,
]),
};
case "get_message_count": {
const out = [0];
const rc = Number(
requireFunc("wcdb_get_message_count")(normalizeHandle(data.handle), String(data.username || "").trim(), out)
);
return { count: rc === 0 ? Number(out[0] || 0) : 0, rc };
}
case "get_display_names":
return {
payload: callOutJson("wcdb_get_display_names", [
normalizeHandle(data.handle),
JSON.stringify(Array.isArray(data.usernames) ? data.usernames : []),
]),
};
case "get_avatar_urls":
return {
payload: callOutJson("wcdb_get_avatar_urls", [
normalizeHandle(data.handle),
JSON.stringify(Array.isArray(data.usernames) ? data.usernames : []),
]),
};
case "get_group_members":
return {
payload: callOutJson("wcdb_get_group_members", [
normalizeHandle(data.handle),
String(data.chatroom_id || "").trim(),
]),
};
case "get_group_nicknames":
return {
payload: callOutJson("wcdb_get_group_nicknames", [
normalizeHandle(data.handle),
String(data.chatroom_id || "").trim(),
]),
};
case "exec_query":
return {
payload: callOutJson("wcdb_exec_query", [
normalizeHandle(data.handle),
String(data.kind || "").trim(),
data.path == null ? null : String(data.path || "").trim(),
String(data.sql || ""),
]),
};
case "update_message":
callOutError("wcdb_update_message", [
normalizeHandle(data.handle),
String(data.session_id || "").trim(),
Number(data.local_id || 0),
Number.parseInt(String(data.create_time || 0), 10) || 0,
String(data.new_content || ""),
]);
return { success: true };
case "delete_message":
callOutError("wcdb_delete_message", [
normalizeHandle(data.handle),
String(data.session_id || "").trim(),
Number(data.local_id || 0),
Number.parseInt(String(data.create_time || 0), 10) || 0,
String(data.db_path_hint || ""),
]);
return { success: true };
case "get_sns_timeline":
return {
payload: callOutJson("wcdb_get_sns_timeline", [
normalizeHandle(data.handle),
Number.parseInt(String(data.limit || 0), 10) || 0,
Number.parseInt(String(data.offset || 0), 10) || 0,
JSON.stringify(Array.isArray(data.usernames) ? data.usernames : []),
String(data.keyword || ""),
Number.parseInt(String(data.start_time || 0), 10) || 0,
Number.parseInt(String(data.end_time || 0), 10) || 0,
]),
};
case "decrypt_sns_image": {
ensureInitialized();
const raw = Buffer.from(String(data.data_b64 || ""), "base64");
if (!raw.length) return { data_b64: "" };
const key = String(data.key || "").trim();
if (!key) return { data_b64: raw.toString("base64") };
const out = [null];
const rc = Number(requireFunc("wcdb_decrypt_sns_image")(raw, raw.length, key, out));
try {
if (rc !== 0 || !out[0]) return { data_b64: raw.toString("base64"), rc };
const hex = ptrToString(out[0]).replace(/[^0-9a-f]/gi, "");
if (!hex) return { data_b64: raw.toString("base64"), rc };
return { data_b64: Buffer.from(hex, "hex").toString("base64"), rc };
} finally {
freeStringPtr(out[0]);
}
}
case "shutdown": {
if (nativeLib && initialized && funcs.wcdb_shutdown) {
try {
funcs.wcdb_shutdown();
} finally {
initialized = false;
}
}
return { shutdown: true };
}
default:
throw new ApiError(`unknown action: ${action}`);
}
}
async function handleRequest(req, res) {
try {
if (req.method === "GET" && req.url === "/health") {
jsonResponse(res, 200, { ok: true, initialized, dllPath: nativeDllPath || getDllPath() });
return;
}
if (req.method !== "POST" || req.url !== "/call") {
jsonResponse(res, 404, { ok: false, error: "not found" });
return;
}
if (TOKEN && String(req.headers["x-wcdb-sidecar-token"] || "") !== TOKEN) {
jsonResponse(res, 401, { ok: false, error: "unauthorized" });
return;
}
const body = await readRequestJson(req);
const action = String(body?.action || "").trim();
const result = handleAction(action, body?.payload || {});
jsonResponse(res, 200, { ok: true, result });
} catch (err) {
const rc = Number.isFinite(err?.rc) ? Number(err.rc) : 0;
const error = err?.message || String(err);
log(`error rc=${rc} message=${error}`);
jsonResponse(res, 200, {
ok: false,
error,
rc,
details: err?.details || null,
logs: getLogs().slice(-20),
});
}
}
if (!Number.isInteger(PORT) || PORT <= 0 || PORT > 65535) {
log(`invalid sidecar port: ${process.env.WECHAT_TOOL_WCDB_SIDECAR_PORT || ""}`);
process.exit(2);
}
const server = http.createServer((req, res) => {
void handleRequest(req, res);
});
server.listen(PORT, HOST, () => {
log(`listening http://${HOST}:${PORT} dll=${getDllPath()} koffi=${process.env.WECHAT_TOOL_KOFFI_DIR || "node_modules"}`);
});
process.on("SIGTERM", () => {
try {
server.close(() => process.exit(0));
} catch {
process.exit(0);
}
});
process.on("SIGINT", () => {
try {
server.close(() => process.exit(0));
} catch {
process.exit(0);
}
});