neko-harness 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/client/assets/index-C4UO2YJF.js +391 -0
- package/dist/client/assets/index-DPSmVlrG.css +2 -0
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +668 -103
- package/dist/neko.js +833 -157
- package/package.json +1 -1
- package/dist/client/assets/index-0Q-trLXg.js +0 -135
- package/dist/client/assets/index-BYawicLw.css +0 -2
package/dist/neko.js
CHANGED
|
@@ -80,7 +80,7 @@ function updateAction(input) {
|
|
|
80
80
|
return "install";
|
|
81
81
|
return "none";
|
|
82
82
|
}
|
|
83
|
-
var RUNNING_VERSION = "0.4.
|
|
83
|
+
var RUNNING_VERSION = "0.4.2", APPLICATION_PROTOCOL = 4, ApplicationProtocolError;
|
|
84
84
|
var init_application_version = __esm(() => {
|
|
85
85
|
ApplicationProtocolError = class ApplicationProtocolError extends Error {
|
|
86
86
|
constructor() {
|
|
@@ -20276,7 +20276,9 @@ var init_admin = __esm(() => {
|
|
|
20276
20276
|
Type6.Object({
|
|
20277
20277
|
status: Type6.Literal("listening"),
|
|
20278
20278
|
address: Type6.String(),
|
|
20279
|
-
port: Type6.Integer({ minimum: 1, maximum: 65535 })
|
|
20279
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
20280
|
+
bindAddress: Type6.Optional(Type6.String()),
|
|
20281
|
+
mode: Type6.Optional(Type6.Union([Type6.Literal("direct"), Type6.Literal("wsl-relay")]))
|
|
20280
20282
|
}),
|
|
20281
20283
|
Type6.Object({ status: Type6.Literal("error"), error: Type6.String() }),
|
|
20282
20284
|
Type6.Object({ status: Type6.Literal("stopping") })
|
|
@@ -20305,7 +20307,11 @@ var init_admin = __esm(() => {
|
|
|
20305
20307
|
Type6.Null()
|
|
20306
20308
|
]),
|
|
20307
20309
|
devices: Type6.Array(adminDeviceSchema),
|
|
20308
|
-
lastConnectedAddress: Type6.Union([Type6.String(), Type6.Null()])
|
|
20310
|
+
lastConnectedAddress: Type6.Union([Type6.String(), Type6.Null()]),
|
|
20311
|
+
wsl: Type6.Optional(Type6.Union([
|
|
20312
|
+
Type6.Object({ interop: Type6.Boolean(), error: Type6.Union([Type6.String(), Type6.Null()]) }),
|
|
20313
|
+
Type6.Null()
|
|
20314
|
+
]))
|
|
20309
20315
|
})
|
|
20310
20316
|
],
|
|
20311
20317
|
enable: [
|
|
@@ -20316,7 +20322,9 @@ var init_admin = __esm(() => {
|
|
|
20316
20322
|
Type6.Object({
|
|
20317
20323
|
status: Type6.Literal("listening"),
|
|
20318
20324
|
address: Type6.String(),
|
|
20319
|
-
port: Type6.Integer({ minimum: 1, maximum: 65535 })
|
|
20325
|
+
port: Type6.Integer({ minimum: 1, maximum: 65535 }),
|
|
20326
|
+
bindAddress: Type6.Optional(Type6.String()),
|
|
20327
|
+
mode: Type6.Optional(Type6.Union([Type6.Literal("direct"), Type6.Literal("wsl-relay")]))
|
|
20320
20328
|
})
|
|
20321
20329
|
],
|
|
20322
20330
|
disable: [Type6.Null(), listenerStateSchema],
|
|
@@ -38553,6 +38561,73 @@ var init_certificate = __esm(() => {
|
|
|
38553
38561
|
CERT_VALIDITY_MS = 825 * 24 * 60 * 60 * 1000;
|
|
38554
38562
|
});
|
|
38555
38563
|
|
|
38564
|
+
// packages/gateway/src/mobile/network.ts
|
|
38565
|
+
import { networkInterfaces } from "os";
|
|
38566
|
+
function isVirtualInterfaceName(name) {
|
|
38567
|
+
const lower = name.toLowerCase();
|
|
38568
|
+
if (VIRTUAL_INTERFACE_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
38569
|
+
return true;
|
|
38570
|
+
return VIRTUAL_INTERFACE_KEYWORDS.some((keyword) => lower.includes(keyword));
|
|
38571
|
+
}
|
|
38572
|
+
function isBenchmarkAddress(address) {
|
|
38573
|
+
return address.startsWith("198.18.") || address.startsWith("198.19.");
|
|
38574
|
+
}
|
|
38575
|
+
function collectListenCandidates(interfaces, skipVirtual) {
|
|
38576
|
+
const candidates = [];
|
|
38577
|
+
for (const [name, infos] of Object.entries(interfaces)) {
|
|
38578
|
+
if (infos === undefined)
|
|
38579
|
+
continue;
|
|
38580
|
+
if (skipVirtual && isVirtualInterfaceName(name))
|
|
38581
|
+
continue;
|
|
38582
|
+
for (const info of infos) {
|
|
38583
|
+
if (info.family !== "IPv4" || info.internal)
|
|
38584
|
+
continue;
|
|
38585
|
+
if (info.address.startsWith("169.254."))
|
|
38586
|
+
continue;
|
|
38587
|
+
if (skipVirtual && isBenchmarkAddress(info.address))
|
|
38588
|
+
continue;
|
|
38589
|
+
candidates.push(info.address);
|
|
38590
|
+
}
|
|
38591
|
+
}
|
|
38592
|
+
return candidates.sort();
|
|
38593
|
+
}
|
|
38594
|
+
function mobileListenCandidates(interfaces) {
|
|
38595
|
+
const source = interfaces ?? networkInterfaces();
|
|
38596
|
+
const filtered = collectListenCandidates(source, true);
|
|
38597
|
+
return filtered.length > 0 ? filtered : collectListenCandidates(source, false);
|
|
38598
|
+
}
|
|
38599
|
+
function prioritizeListenCandidate(candidates, lastConnectedAddress) {
|
|
38600
|
+
if (lastConnectedAddress === null || !candidates.includes(lastConnectedAddress))
|
|
38601
|
+
return [...candidates];
|
|
38602
|
+
return [lastConnectedAddress, ...candidates.filter((candidate) => candidate !== lastConnectedAddress)];
|
|
38603
|
+
}
|
|
38604
|
+
function isIpv4Literal(address) {
|
|
38605
|
+
const parts = address.split(".");
|
|
38606
|
+
return parts.length === 4 && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
38607
|
+
}
|
|
38608
|
+
var VIRTUAL_INTERFACE_PREFIXES, VIRTUAL_INTERFACE_KEYWORDS;
|
|
38609
|
+
var init_network = __esm(() => {
|
|
38610
|
+
VIRTUAL_INTERFACE_PREFIXES = [
|
|
38611
|
+
"anpi",
|
|
38612
|
+
"awdl",
|
|
38613
|
+
"bridge",
|
|
38614
|
+
"docker",
|
|
38615
|
+
"gif",
|
|
38616
|
+
"ipsec",
|
|
38617
|
+
"llw",
|
|
38618
|
+
"ppp",
|
|
38619
|
+
"stf",
|
|
38620
|
+
"tap",
|
|
38621
|
+
"tun",
|
|
38622
|
+
"utun",
|
|
38623
|
+
"vboxnet",
|
|
38624
|
+
"veth",
|
|
38625
|
+
"virbr",
|
|
38626
|
+
"vmnet"
|
|
38627
|
+
];
|
|
38628
|
+
VIRTUAL_INTERFACE_KEYWORDS = ["hyper-v", "virtual", "virtualbox", "vmware", "vpn", "wsl"];
|
|
38629
|
+
});
|
|
38630
|
+
|
|
38556
38631
|
// packages/gateway/src/mobile/pairing.ts
|
|
38557
38632
|
import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
|
|
38558
38633
|
function sha256Hex2(value) {
|
|
@@ -38745,57 +38820,379 @@ var init_pairing = __esm(() => {
|
|
|
38745
38820
|
BASE64URL_32_BYTES = /^[A-Za-z0-9_-]{43}$/;
|
|
38746
38821
|
});
|
|
38747
38822
|
|
|
38748
|
-
// packages/gateway/src/mobile/
|
|
38749
|
-
import {
|
|
38750
|
-
|
|
38751
|
-
|
|
38752
|
-
|
|
38753
|
-
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
38754
|
-
function defaultServerName() {
|
|
38755
|
-
const name = hostname().trim();
|
|
38756
|
-
return (name === "" ? "neko-computer" : name).slice(0, 64);
|
|
38823
|
+
// packages/gateway/src/mobile/relay.ts
|
|
38824
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
38825
|
+
function assertPort2(port, label) {
|
|
38826
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
38827
|
+
throw new Error(`relay ${label} port is invalid`);
|
|
38757
38828
|
}
|
|
38758
|
-
function
|
|
38759
|
-
|
|
38760
|
-
|
|
38761
|
-
|
|
38762
|
-
|
|
38829
|
+
function buildRelayScript(endpoints, watchdogMs = RELAY_WATCHDOG_MS) {
|
|
38830
|
+
if (!isIpv4Literal(endpoints.listenAddress) || !isIpv4Literal(endpoints.targetAddress)) {
|
|
38831
|
+
throw new Error("relay addresses must be IPv4 literals");
|
|
38832
|
+
}
|
|
38833
|
+
assertPort2(endpoints.listenPort, "listen");
|
|
38834
|
+
assertPort2(endpoints.targetPort, "target");
|
|
38835
|
+
if (!Number.isInteger(watchdogMs) || watchdogMs < 1000)
|
|
38836
|
+
throw new Error("relay watchdog is invalid");
|
|
38837
|
+
return [
|
|
38838
|
+
"$ErrorActionPreference='Stop'",
|
|
38839
|
+
`$src=@'
|
|
38840
|
+
${RELAY_SOURCE}
|
|
38841
|
+
'@`,
|
|
38842
|
+
"Add-Type -TypeDefinition $src -Language CSharp",
|
|
38843
|
+
`[NekoRelay]::Run('${endpoints.listenAddress}',${endpoints.listenPort},'${endpoints.targetAddress}',${endpoints.targetPort},${watchdogMs})`
|
|
38844
|
+
].join(`
|
|
38845
|
+
`);
|
|
38763
38846
|
}
|
|
38764
|
-
function
|
|
38765
|
-
return
|
|
38847
|
+
function encodeRelayCommand(script) {
|
|
38848
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
38766
38849
|
}
|
|
38767
|
-
function
|
|
38768
|
-
|
|
38769
|
-
|
|
38770
|
-
|
|
38771
|
-
|
|
38772
|
-
|
|
38850
|
+
function describeExit(child) {
|
|
38851
|
+
return child.signalCode !== null ? `signal ${child.signalCode}` : `exit code ${child.exitCode}`;
|
|
38852
|
+
}
|
|
38853
|
+
async function startWindowsRelay(options) {
|
|
38854
|
+
const watchdogMs = options.watchdogMs ?? RELAY_WATCHDOG_MS;
|
|
38855
|
+
const script = buildRelayScript(options, watchdogMs);
|
|
38856
|
+
const spawn = options.spawn ?? nodeSpawn;
|
|
38857
|
+
const child = spawn(options.powershell, ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodeRelayCommand(script)], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
38858
|
+
let stderrTail = "";
|
|
38859
|
+
child.stderr?.on("data", (chunk) => {
|
|
38860
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-STDERR_TAIL_BYTES);
|
|
38861
|
+
});
|
|
38862
|
+
child.stdin?.on("error", () => {});
|
|
38863
|
+
const exited = new Promise((resolve) => {
|
|
38864
|
+
child.once("error", (error) => resolve(`spawn error: ${error.message}`));
|
|
38865
|
+
child.once("exit", () => resolve(describeExit(child)));
|
|
38866
|
+
});
|
|
38867
|
+
let heartbeat;
|
|
38868
|
+
let stopping;
|
|
38869
|
+
const stop = () => {
|
|
38870
|
+
stopping ??= (async () => {
|
|
38871
|
+
clearInterval(heartbeat);
|
|
38872
|
+
child.stdin?.end();
|
|
38873
|
+
const grace = options.stopGraceMs ?? STOP_GRACE_MS;
|
|
38874
|
+
const done = await Promise.race([exited.then(() => true), delay2(grace).then(() => false)]);
|
|
38875
|
+
if (!done) {
|
|
38876
|
+
child.kill("SIGKILL");
|
|
38877
|
+
await Promise.race([exited, delay2(grace)]);
|
|
38878
|
+
}
|
|
38879
|
+
})();
|
|
38880
|
+
return stopping;
|
|
38881
|
+
};
|
|
38882
|
+
const ready = new Promise((resolve, reject) => {
|
|
38883
|
+
let buffer = "";
|
|
38884
|
+
const timer = setTimeout(() => reject(new RelayError("timeout", "Windows relay did not become ready in time")), options.startTimeoutMs ?? START_TIMEOUT_MS);
|
|
38885
|
+
const finish = (action) => {
|
|
38886
|
+
clearTimeout(timer);
|
|
38887
|
+
action();
|
|
38888
|
+
};
|
|
38889
|
+
child.stdout?.on("data", (chunk) => {
|
|
38890
|
+
buffer += chunk.toString("utf8");
|
|
38891
|
+
for (;; ) {
|
|
38892
|
+
const newline = buffer.indexOf(`
|
|
38893
|
+
`);
|
|
38894
|
+
if (newline < 0)
|
|
38895
|
+
break;
|
|
38896
|
+
const line = buffer.slice(0, newline).trim();
|
|
38897
|
+
buffer = buffer.slice(newline + 1);
|
|
38898
|
+
if (line === "READY")
|
|
38899
|
+
finish(resolve);
|
|
38900
|
+
else if (line.startsWith("ERROR ")) {
|
|
38901
|
+
const [, code = "start_failed", ...rest] = line.split(" ");
|
|
38902
|
+
const known = ["port_in_use", "listen_failed", "target_unreachable"];
|
|
38903
|
+
const mapped = known.find((item) => item === code) ?? "start_failed";
|
|
38904
|
+
finish(() => reject(new RelayError(mapped, rest.join(" ") || code)));
|
|
38905
|
+
}
|
|
38906
|
+
}
|
|
38907
|
+
});
|
|
38908
|
+
exited.then((reason) => {
|
|
38909
|
+
const detail = stderrTail.trim();
|
|
38910
|
+
finish(() => reject(new RelayError("start_failed", `Windows relay exited before it was ready (${reason})${detail === "" ? "" : `: ${detail}`}`)));
|
|
38911
|
+
});
|
|
38912
|
+
});
|
|
38913
|
+
try {
|
|
38914
|
+
await ready;
|
|
38915
|
+
} catch (error) {
|
|
38916
|
+
await stop();
|
|
38917
|
+
throw error;
|
|
38918
|
+
}
|
|
38919
|
+
heartbeat = setInterval(() => {
|
|
38920
|
+
child.stdin?.write(`
|
|
38921
|
+
`);
|
|
38922
|
+
}, options.heartbeatMs ?? RELAY_HEARTBEAT_INTERVAL_MS);
|
|
38923
|
+
heartbeat.unref?.();
|
|
38924
|
+
return { exited, stop };
|
|
38925
|
+
}
|
|
38926
|
+
function delay2(ms) {
|
|
38927
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
38928
|
+
}
|
|
38929
|
+
var RelayError, RELAY_HEARTBEAT_INTERVAL_MS = 2000, RELAY_WATCHDOG_MS = 8000, START_TIMEOUT_MS = 20000, STOP_GRACE_MS = 1000, STDERR_TAIL_BYTES = 2000, RELAY_SOURCE = `using System;
|
|
38930
|
+
using System.Diagnostics;
|
|
38931
|
+
using System.Net;
|
|
38932
|
+
using System.Net.Sockets;
|
|
38933
|
+
using System.Threading;
|
|
38934
|
+
|
|
38935
|
+
public static class NekoRelay
|
|
38936
|
+
{
|
|
38937
|
+
static readonly Stopwatch Clock = Stopwatch.StartNew();
|
|
38938
|
+
static long lastBeatMs = 0;
|
|
38939
|
+
|
|
38940
|
+
static void Touch() { Interlocked.Exchange(ref lastBeatMs, Clock.ElapsedMilliseconds); }
|
|
38941
|
+
static string Flat(string text) { return text.Replace("\\r", " ").Replace("\\n", " "); }
|
|
38942
|
+
static void Close(TcpClient client) { if (client != null) { try { client.Close(); } catch (Exception) { } } }
|
|
38943
|
+
|
|
38944
|
+
static void Copy(TcpClient from, TcpClient to)
|
|
38945
|
+
{
|
|
38946
|
+
byte[] buffer = new byte[16384];
|
|
38947
|
+
try
|
|
38948
|
+
{
|
|
38949
|
+
System.IO.Stream source = from.GetStream();
|
|
38950
|
+
System.IO.Stream sink = to.GetStream();
|
|
38951
|
+
int count;
|
|
38952
|
+
while ((count = source.Read(buffer, 0, buffer.Length)) > 0) { sink.Write(buffer, 0, count); }
|
|
38953
|
+
}
|
|
38954
|
+
catch (Exception) { }
|
|
38955
|
+
try { to.Client.Shutdown(SocketShutdown.Send); } catch (Exception) { }
|
|
38956
|
+
}
|
|
38957
|
+
|
|
38958
|
+
static void Pump(object state)
|
|
38959
|
+
{
|
|
38960
|
+
object[] args = (object[])state;
|
|
38961
|
+
TcpClient client = (TcpClient)args[0];
|
|
38962
|
+
TcpClient target = null;
|
|
38963
|
+
try
|
|
38964
|
+
{
|
|
38965
|
+
target = new TcpClient();
|
|
38966
|
+
target.NoDelay = true;
|
|
38967
|
+
client.NoDelay = true;
|
|
38968
|
+
target.Connect((string)args[1], (int)args[2]);
|
|
38969
|
+
Thread upstream = new Thread(delegate() { Copy(client, target); });
|
|
38970
|
+
upstream.IsBackground = true;
|
|
38971
|
+
upstream.Start();
|
|
38972
|
+
Copy(target, client);
|
|
38973
|
+
}
|
|
38974
|
+
catch (Exception) { }
|
|
38975
|
+
finally { Close(client); Close(target); }
|
|
38976
|
+
}
|
|
38977
|
+
|
|
38978
|
+
public static void Run(string listenIp, int listenPort, string targetIp, int targetPort, int watchdogMs)
|
|
38979
|
+
{
|
|
38980
|
+
TcpListener listener = new TcpListener(IPAddress.Parse(listenIp), listenPort);
|
|
38981
|
+
listener.ExclusiveAddressUse = true;
|
|
38982
|
+
try { listener.Start(128); }
|
|
38983
|
+
catch (SocketException e)
|
|
38984
|
+
{
|
|
38985
|
+
string code = (e.SocketErrorCode == SocketError.AddressAlreadyInUse || e.SocketErrorCode == SocketError.AccessDenied) ? "port_in_use" : "listen_failed";
|
|
38986
|
+
Console.Out.WriteLine("ERROR " + code + " " + Flat(e.Message));
|
|
38987
|
+
Console.Out.Flush();
|
|
38988
|
+
Environment.Exit(2);
|
|
38989
|
+
}
|
|
38990
|
+
try
|
|
38991
|
+
{
|
|
38992
|
+
TcpClient probe = new TcpClient();
|
|
38993
|
+
IAsyncResult pending = probe.BeginConnect(targetIp, targetPort, null, null);
|
|
38994
|
+
if (!pending.AsyncWaitHandle.WaitOne(3000)) { throw new TimeoutException("connect timed out"); }
|
|
38995
|
+
probe.EndConnect(pending);
|
|
38996
|
+
probe.Close();
|
|
38997
|
+
}
|
|
38998
|
+
catch (Exception e)
|
|
38999
|
+
{
|
|
39000
|
+
Console.Out.WriteLine("ERROR target_unreachable " + Flat(e.Message));
|
|
39001
|
+
Console.Out.Flush();
|
|
39002
|
+
Environment.Exit(3);
|
|
39003
|
+
}
|
|
39004
|
+
Touch();
|
|
39005
|
+
Thread input = new Thread(delegate()
|
|
39006
|
+
{
|
|
39007
|
+
try { while (Console.In.ReadLine() != null) { Touch(); } } catch (Exception) { }
|
|
39008
|
+
Environment.Exit(0);
|
|
39009
|
+
});
|
|
39010
|
+
input.IsBackground = true;
|
|
39011
|
+
input.Start();
|
|
39012
|
+
Thread watchdog = new Thread(delegate()
|
|
39013
|
+
{
|
|
39014
|
+
while (true)
|
|
39015
|
+
{
|
|
39016
|
+
Thread.Sleep(1000);
|
|
39017
|
+
if (Clock.ElapsedMilliseconds - Interlocked.Read(ref lastBeatMs) > watchdogMs) { Environment.Exit(0); }
|
|
39018
|
+
}
|
|
39019
|
+
});
|
|
39020
|
+
watchdog.IsBackground = true;
|
|
39021
|
+
watchdog.Start();
|
|
39022
|
+
Console.Out.WriteLine("READY");
|
|
39023
|
+
Console.Out.Flush();
|
|
39024
|
+
while (true)
|
|
39025
|
+
{
|
|
39026
|
+
TcpClient accepted;
|
|
39027
|
+
try { accepted = listener.AcceptTcpClient(); }
|
|
39028
|
+
catch (Exception) { Environment.Exit(4); return; }
|
|
39029
|
+
ThreadPool.QueueUserWorkItem(Pump, new object[] { accepted, targetIp, targetPort });
|
|
39030
|
+
}
|
|
39031
|
+
}
|
|
39032
|
+
}`;
|
|
39033
|
+
var init_relay = __esm(() => {
|
|
39034
|
+
init_network();
|
|
39035
|
+
RelayError = class RelayError extends Error {
|
|
39036
|
+
code;
|
|
39037
|
+
constructor(code, message) {
|
|
39038
|
+
super(message);
|
|
39039
|
+
this.name = "RelayError";
|
|
39040
|
+
this.code = code;
|
|
39041
|
+
}
|
|
39042
|
+
};
|
|
39043
|
+
});
|
|
39044
|
+
|
|
39045
|
+
// packages/gateway/src/mobile/wsl.ts
|
|
39046
|
+
import { execFile as execFile2 } from "child_process";
|
|
39047
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync2 } from "fs";
|
|
39048
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
39049
|
+
import { delimiter, join as join10 } from "path";
|
|
39050
|
+
function detectWsl(overrides = {}) {
|
|
39051
|
+
const deps = { ...defaultProbeDeps, ...overrides };
|
|
39052
|
+
if (deps.platform !== "linux")
|
|
39053
|
+
return;
|
|
39054
|
+
const { env } = deps;
|
|
39055
|
+
const version = deps.readProcVersion() ?? "";
|
|
39056
|
+
const inWsl = (env.WSL_DISTRO_NAME ?? "") !== "" || (env.WSL_INTEROP ?? "") !== "" || /microsoft|wsl/i.test(version);
|
|
39057
|
+
if (!inWsl)
|
|
39058
|
+
return;
|
|
39059
|
+
const candidates = [
|
|
39060
|
+
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) => join10(dir, "powershell.exe")),
|
|
39061
|
+
POWERSHELL_FALLBACK
|
|
39062
|
+
];
|
|
39063
|
+
return { powershell: candidates.find((candidate) => deps.isExecutable(candidate)) };
|
|
39064
|
+
}
|
|
39065
|
+
function parseWindowsInterfaces(output) {
|
|
39066
|
+
const text = output.replace(/^\uFEFF/, "").trim();
|
|
39067
|
+
if (text === "")
|
|
39068
|
+
return [];
|
|
39069
|
+
const parsed = JSON.parse(text);
|
|
39070
|
+
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
39071
|
+
const result = [];
|
|
39072
|
+
for (const item of items) {
|
|
39073
|
+
if (typeof item !== "object" || item === null)
|
|
38773
39074
|
continue;
|
|
38774
|
-
|
|
38775
|
-
|
|
38776
|
-
|
|
38777
|
-
if (info.address.startsWith("169.254."))
|
|
38778
|
-
continue;
|
|
38779
|
-
if (skipVirtual && isBenchmarkAddress(info.address))
|
|
38780
|
-
continue;
|
|
38781
|
-
candidates.push(info.address);
|
|
39075
|
+
const { name, address } = item;
|
|
39076
|
+
if (typeof name === "string" && typeof address === "string" && isIpv4Literal(address)) {
|
|
39077
|
+
result.push({ name, address });
|
|
38782
39078
|
}
|
|
38783
39079
|
}
|
|
38784
|
-
return
|
|
39080
|
+
return result;
|
|
38785
39081
|
}
|
|
38786
|
-
function
|
|
38787
|
-
const
|
|
38788
|
-
const
|
|
38789
|
-
|
|
39082
|
+
function toInterfaceDict(interfaces) {
|
|
39083
|
+
const dict = {};
|
|
39084
|
+
for (const { name, address } of interfaces) {
|
|
39085
|
+
const list = dict[name] ?? [];
|
|
39086
|
+
dict[name] = list;
|
|
39087
|
+
list.push({
|
|
39088
|
+
address,
|
|
39089
|
+
netmask: "255.255.255.0",
|
|
39090
|
+
family: "IPv4",
|
|
39091
|
+
mac: "00:00:00:00:00:00",
|
|
39092
|
+
internal: false,
|
|
39093
|
+
cidr: `${address}/24`
|
|
39094
|
+
});
|
|
39095
|
+
}
|
|
39096
|
+
return dict;
|
|
38790
39097
|
}
|
|
38791
|
-
|
|
38792
|
-
|
|
38793
|
-
|
|
38794
|
-
|
|
39098
|
+
|
|
39099
|
+
class WindowsAddressDiscovery {
|
|
39100
|
+
powershell;
|
|
39101
|
+
run;
|
|
39102
|
+
cacheMs;
|
|
39103
|
+
timeoutMs;
|
|
39104
|
+
now;
|
|
39105
|
+
cached;
|
|
39106
|
+
pending;
|
|
39107
|
+
constructor(options) {
|
|
39108
|
+
this.powershell = options.powershell;
|
|
39109
|
+
this.run = options.run ?? runPowerShell;
|
|
39110
|
+
this.cacheMs = options.cacheMs ?? 15000;
|
|
39111
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
39112
|
+
this.now = options.now ?? Date.now;
|
|
39113
|
+
}
|
|
39114
|
+
invalidate() {
|
|
39115
|
+
this.cached = undefined;
|
|
39116
|
+
}
|
|
39117
|
+
candidates() {
|
|
39118
|
+
if (this.cached !== undefined && this.now() - this.cached.at < this.cacheMs) {
|
|
39119
|
+
return Promise.resolve([...this.cached.addresses]);
|
|
39120
|
+
}
|
|
39121
|
+
this.pending ??= this.read().finally(() => {
|
|
39122
|
+
this.pending = undefined;
|
|
39123
|
+
});
|
|
39124
|
+
return this.pending.then((addresses) => [...addresses]);
|
|
39125
|
+
}
|
|
39126
|
+
async read() {
|
|
39127
|
+
const output = await this.run(this.powershell, WINDOWS_ADDRESSES_SCRIPT, this.timeoutMs);
|
|
39128
|
+
const usable = parseWindowsInterfaces(output).filter((item) => !WSL_ADAPTER_PATTERN.test(item.name));
|
|
39129
|
+
const addresses = mobileListenCandidates(toInterfaceDict(usable));
|
|
39130
|
+
this.cached = { at: this.now(), addresses };
|
|
39131
|
+
return addresses;
|
|
39132
|
+
}
|
|
38795
39133
|
}
|
|
38796
|
-
function
|
|
38797
|
-
const
|
|
38798
|
-
|
|
39134
|
+
function wslOwnAddresses(interfaces = networkInterfaces2()) {
|
|
39135
|
+
const result = [];
|
|
39136
|
+
for (const infos of Object.values(interfaces)) {
|
|
39137
|
+
for (const info of infos ?? []) {
|
|
39138
|
+
if (info.family === "IPv4" && !info.internal)
|
|
39139
|
+
result.push(info.address);
|
|
39140
|
+
}
|
|
39141
|
+
}
|
|
39142
|
+
return result;
|
|
39143
|
+
}
|
|
39144
|
+
function primaryWslAddress(interfaces = networkInterfaces2()) {
|
|
39145
|
+
const eth0 = (interfaces.eth0 ?? []).find((info) => info.family === "IPv4" && !info.internal);
|
|
39146
|
+
return eth0?.address ?? wslOwnAddresses(interfaces)[0];
|
|
39147
|
+
}
|
|
39148
|
+
var POWERSHELL_FALLBACK = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", defaultProbeDeps, WINDOWS_ADDRESSES_SCRIPT, WSL_ADAPTER_PATTERN, runPowerShell = (powershell, script, timeoutMs) => new Promise((resolve, reject) => {
|
|
39149
|
+
execFile2(powershell, ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024, encoding: "utf8" }, (error, stdout) => error ? reject(error) : resolve(stdout));
|
|
39150
|
+
});
|
|
39151
|
+
var init_wsl = __esm(() => {
|
|
39152
|
+
init_network();
|
|
39153
|
+
defaultProbeDeps = {
|
|
39154
|
+
platform: process.platform,
|
|
39155
|
+
env: process.env,
|
|
39156
|
+
readProcVersion() {
|
|
39157
|
+
try {
|
|
39158
|
+
return readFileSync2("/proc/version", "utf8");
|
|
39159
|
+
} catch {
|
|
39160
|
+
return;
|
|
39161
|
+
}
|
|
39162
|
+
},
|
|
39163
|
+
isExecutable(path) {
|
|
39164
|
+
try {
|
|
39165
|
+
accessSync2(path, constants2.X_OK);
|
|
39166
|
+
return true;
|
|
39167
|
+
} catch {
|
|
39168
|
+
return false;
|
|
39169
|
+
}
|
|
39170
|
+
}
|
|
39171
|
+
};
|
|
39172
|
+
WINDOWS_ADDRESSES_SCRIPT = [
|
|
39173
|
+
"$ErrorActionPreference='Stop'",
|
|
39174
|
+
"$r=@()",
|
|
39175
|
+
"foreach($n in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()){",
|
|
39176
|
+
"if($n.OperationalStatus -ne 'Up'){continue}",
|
|
39177
|
+
"if($n.NetworkInterfaceType -eq 'Loopback' -or $n.NetworkInterfaceType -eq 'Tunnel'){continue}",
|
|
39178
|
+
"foreach($a in $n.GetIPProperties().UnicastAddresses){",
|
|
39179
|
+
"if($a.Address.AddressFamily -eq 'InterNetwork'){$r+=[pscustomobject]@{name=$n.Name;address=$a.Address.IPAddressToString}}",
|
|
39180
|
+
"}}",
|
|
39181
|
+
"ConvertTo-Json -InputObject @($r) -Compress"
|
|
39182
|
+
].join(`
|
|
39183
|
+
`);
|
|
39184
|
+
WSL_ADAPTER_PATTERN = /wsl|hyper-v|vethernet/i;
|
|
39185
|
+
});
|
|
39186
|
+
|
|
39187
|
+
// packages/gateway/src/mobile/listener.ts
|
|
39188
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
39189
|
+
import { createServer as createServer2 } from "https";
|
|
39190
|
+
import { hostname } from "os";
|
|
39191
|
+
import { createHost as createHost2, memoryLinkPair } from "@earendil-works/chord";
|
|
39192
|
+
import { WebSocketServer as WebSocketServer2 } from "ws";
|
|
39193
|
+
function defaultServerName() {
|
|
39194
|
+
const name = hostname().trim();
|
|
39195
|
+
return (name === "" ? "neko-computer" : name).slice(0, 64);
|
|
38799
39196
|
}
|
|
38800
39197
|
function assertListenAddress(address) {
|
|
38801
39198
|
if (!isIpv4Literal(address))
|
|
@@ -38807,6 +39204,22 @@ function assertListenAddress(address) {
|
|
|
38807
39204
|
function toError(error) {
|
|
38808
39205
|
return error instanceof Error ? error : new Error(String(error));
|
|
38809
39206
|
}
|
|
39207
|
+
function describeRelayError(error, address, port, bindAddress) {
|
|
39208
|
+
if (!(error instanceof RelayError))
|
|
39209
|
+
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${toMessage(error)}`;
|
|
39210
|
+
switch (error.code) {
|
|
39211
|
+
case "port_in_use":
|
|
39212
|
+
return `Windows \u4E0A ${address}:${port} \u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u66F4\u6362\u7AEF\u53E3\u6216\u91CA\u653E\u8BE5\u7AEF\u53E3\u540E\u91CD\u8BD5\u3002`;
|
|
39213
|
+
case "target_unreachable":
|
|
39214
|
+
return `Windows \u65E0\u6CD5\u8FDE\u63A5\u5230 WSL \u5185\u7684\u76D1\u542C\u5730\u5740 ${bindAddress}:${port}\uFF08${error.message}\uFF09\u3002`;
|
|
39215
|
+
case "timeout":
|
|
39216
|
+
return "Windows \u4E2D\u7EE7\u542F\u52A8\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
39217
|
+
case "listen_failed":
|
|
39218
|
+
return `Windows \u4E2D\u7EE7\u65E0\u6CD5\u5728 ${address}:${port} \u4E0A\u76D1\u542C\uFF1A${error.message}`;
|
|
39219
|
+
default:
|
|
39220
|
+
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${error.message}`;
|
|
39221
|
+
}
|
|
39222
|
+
}
|
|
38810
39223
|
function isMobilePrincipal2(principal) {
|
|
38811
39224
|
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
38812
39225
|
}
|
|
@@ -38836,7 +39249,26 @@ class MobileGateway {
|
|
|
38836
39249
|
stateValue;
|
|
38837
39250
|
queue = Promise.resolve();
|
|
38838
39251
|
closing;
|
|
38839
|
-
|
|
39252
|
+
relay;
|
|
39253
|
+
wslEnvironment;
|
|
39254
|
+
windowsAddresses;
|
|
39255
|
+
startRelay;
|
|
39256
|
+
localInterfaces;
|
|
39257
|
+
relayPortRetryMs;
|
|
39258
|
+
relayPortRetryIntervalMs;
|
|
39259
|
+
constructor(serverId, serverName, onError, chord, store, storeFailure, initial, network = {}) {
|
|
39260
|
+
this.wslEnvironment = network.wsl === null ? undefined : network.wsl ?? detectWsl();
|
|
39261
|
+
const powershell = this.wslEnvironment?.powershell;
|
|
39262
|
+
if (network.windowsCandidates !== undefined)
|
|
39263
|
+
this.windowsAddresses = network.windowsCandidates;
|
|
39264
|
+
else if (powershell !== undefined) {
|
|
39265
|
+
const discovery = new WindowsAddressDiscovery({ powershell });
|
|
39266
|
+
this.windowsAddresses = () => discovery.candidates();
|
|
39267
|
+
}
|
|
39268
|
+
this.startRelay = network.startRelay ?? startWindowsRelay;
|
|
39269
|
+
this.localInterfaces = network.localInterfaces;
|
|
39270
|
+
this.relayPortRetryMs = network.relayPortRetryMs ?? RELAY_PORT_RETRY_MS;
|
|
39271
|
+
this.relayPortRetryIntervalMs = network.relayPortRetryIntervalMs ?? RELAY_PORT_RETRY_INTERVAL_MS;
|
|
38840
39272
|
this.serverId = serverId;
|
|
38841
39273
|
this.serverName = serverName;
|
|
38842
39274
|
this.onError = onError;
|
|
@@ -38853,7 +39285,7 @@ class MobileGateway {
|
|
|
38853
39285
|
} catch (error) {
|
|
38854
39286
|
storeFailure = toMessage(error);
|
|
38855
39287
|
}
|
|
38856
|
-
const gateway = new MobileGateway(options.serverId, options.serverName ?? defaultServerName(), options.onError, options.chord, store, storeFailure, storeFailure === undefined ? { status: "disabled" } : { status: "error", error: `mobile store unavailable: ${storeFailure}` });
|
|
39288
|
+
const gateway = new MobileGateway(options.serverId, options.serverName ?? defaultServerName(), options.onError, options.chord, store, storeFailure, storeFailure === undefined ? { status: "disabled" } : { status: "error", error: `mobile store unavailable: ${storeFailure}` }, options.network);
|
|
38857
39289
|
gateway.runtime = options.runtime;
|
|
38858
39290
|
if (store !== undefined) {
|
|
38859
39291
|
const config = store.getState();
|
|
@@ -38925,12 +39357,30 @@ class MobileGateway {
|
|
|
38925
39357
|
}
|
|
38926
39358
|
return false;
|
|
38927
39359
|
}
|
|
38928
|
-
adminState() {
|
|
39360
|
+
async adminState() {
|
|
38929
39361
|
const store = this.requireStore();
|
|
38930
39362
|
const config = store.getState();
|
|
39363
|
+
let candidates;
|
|
39364
|
+
let wsl = null;
|
|
39365
|
+
if (this.wslEnvironment === undefined) {
|
|
39366
|
+
candidates = mobileListenCandidates();
|
|
39367
|
+
} else {
|
|
39368
|
+
candidates = [];
|
|
39369
|
+
let error = null;
|
|
39370
|
+
if (this.windowsAddresses === undefined)
|
|
39371
|
+
error = WSL_INTEROP_UNAVAILABLE;
|
|
39372
|
+
else {
|
|
39373
|
+
try {
|
|
39374
|
+
candidates = await this.windowsAddresses();
|
|
39375
|
+
} catch (caught) {
|
|
39376
|
+
error = `\u8BFB\u53D6 Windows \u5C40\u57DF\u7F51\u5730\u5740\u5931\u8D25\uFF1A${toMessage(caught)}`;
|
|
39377
|
+
}
|
|
39378
|
+
}
|
|
39379
|
+
wsl = { interop: this.windowsAddresses !== undefined, error };
|
|
39380
|
+
}
|
|
38931
39381
|
return {
|
|
38932
39382
|
state: this.stateValue,
|
|
38933
|
-
candidates: prioritizeListenCandidate(
|
|
39383
|
+
candidates: prioritizeListenCandidate(candidates, config.lastConnectedAddress),
|
|
38934
39384
|
port: config.port,
|
|
38935
39385
|
certificate: this.certificate === undefined ? null : { fingerprint: this.certificate.fingerprint, expiresAt: this.certificate.expiresAt },
|
|
38936
39386
|
devices: config.devices.map((device) => ({
|
|
@@ -38940,7 +39390,8 @@ class MobileGateway {
|
|
|
38940
39390
|
lastSeenAt: device.lastSeenAt,
|
|
38941
39391
|
online: this.deviceOnline(device.deviceId)
|
|
38942
39392
|
})),
|
|
38943
|
-
lastConnectedAddress: config.lastConnectedAddress
|
|
39393
|
+
lastConnectedAddress: config.lastConnectedAddress,
|
|
39394
|
+
wsl
|
|
38944
39395
|
};
|
|
38945
39396
|
}
|
|
38946
39397
|
async regenerateCertificate() {
|
|
@@ -39020,9 +39471,10 @@ class MobileGateway {
|
|
|
39020
39471
|
if (store?.getState().devices.some((device) => device.deviceId === auth.deviceId)) {
|
|
39021
39472
|
store.touchDevice(auth.deviceId, Date.now()).catch((error) => this.onError?.(toError(error)));
|
|
39022
39473
|
}
|
|
39023
|
-
|
|
39024
|
-
|
|
39025
|
-
|
|
39474
|
+
const learned = this.advertisedAddress(localAddress);
|
|
39475
|
+
if (store !== undefined && typeof learned === "string" && isIpv4Literal(learned)) {
|
|
39476
|
+
if (store.getState().lastConnectedAddress !== learned) {
|
|
39477
|
+
store.rememberConnectedAddress(learned).catch((error) => this.onError?.(toError(error)));
|
|
39026
39478
|
}
|
|
39027
39479
|
}
|
|
39028
39480
|
const clientHost = createHost2({
|
|
@@ -39106,6 +39558,13 @@ class MobileGateway {
|
|
|
39106
39558
|
for (const connection of this.connections.values())
|
|
39107
39559
|
connection.sendNotification(method, params);
|
|
39108
39560
|
}
|
|
39561
|
+
advertisedAddress(localAddress) {
|
|
39562
|
+
const state = this.stateValue;
|
|
39563
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && localAddress === state.bindAddress) {
|
|
39564
|
+
return state.address;
|
|
39565
|
+
}
|
|
39566
|
+
return localAddress;
|
|
39567
|
+
}
|
|
39109
39568
|
requireStore() {
|
|
39110
39569
|
if (this.storeHandle === undefined)
|
|
39111
39570
|
throw new Error(`mobile store is unavailable: ${this.storeFailure ?? "unknown error"}`);
|
|
@@ -39118,6 +39577,7 @@ class MobileGateway {
|
|
|
39118
39577
|
await this.stopListening();
|
|
39119
39578
|
this.stateValue = { status: "starting" };
|
|
39120
39579
|
try {
|
|
39580
|
+
const plan = await this.planListen(address);
|
|
39121
39581
|
const certificate = await this.certificateValue();
|
|
39122
39582
|
const server = createServer2({ cert: certificate.certPem, key: certificate.keyPem }, (request, response) => {
|
|
39123
39583
|
this.handleRequest(request, response);
|
|
@@ -39145,11 +39605,10 @@ class MobileGateway {
|
|
|
39145
39605
|
};
|
|
39146
39606
|
server.once("error", onFailure);
|
|
39147
39607
|
server.once("listening", onListening);
|
|
39148
|
-
server.listen(port,
|
|
39608
|
+
server.listen(port, plan.bindAddress);
|
|
39149
39609
|
});
|
|
39150
39610
|
this.server = server;
|
|
39151
39611
|
this.wss = wss;
|
|
39152
|
-
this.stateValue = { status: "listening", address, port };
|
|
39153
39612
|
server.on("error", (error) => {
|
|
39154
39613
|
this.stateValue = { status: "error", error: toMessage(error) };
|
|
39155
39614
|
this.onError?.(error);
|
|
@@ -39157,6 +39616,9 @@ class MobileGateway {
|
|
|
39157
39616
|
return;
|
|
39158
39617
|
});
|
|
39159
39618
|
});
|
|
39619
|
+
if (plan.mode === "wsl-relay")
|
|
39620
|
+
await this.startRelayFor(address, port, plan.bindAddress);
|
|
39621
|
+
this.stateValue = plan.mode === "wsl-relay" ? { status: "listening", address, port, mode: "wsl-relay", bindAddress: plan.bindAddress } : { status: "listening", address, port };
|
|
39160
39622
|
} catch (error) {
|
|
39161
39623
|
await this.stopListening().catch(() => {
|
|
39162
39624
|
return;
|
|
@@ -39165,6 +39627,66 @@ class MobileGateway {
|
|
|
39165
39627
|
throw error;
|
|
39166
39628
|
}
|
|
39167
39629
|
}
|
|
39630
|
+
async planListen(address) {
|
|
39631
|
+
if (this.wslEnvironment === undefined)
|
|
39632
|
+
return { mode: "direct", bindAddress: address };
|
|
39633
|
+
if (this.windowsAddresses === undefined)
|
|
39634
|
+
throw new Error(WSL_INTEROP_UNAVAILABLE);
|
|
39635
|
+
let candidates;
|
|
39636
|
+
try {
|
|
39637
|
+
candidates = await this.windowsAddresses();
|
|
39638
|
+
} catch (error) {
|
|
39639
|
+
throw new Error(`\u8BFB\u53D6 Windows \u5C40\u57DF\u7F51\u5730\u5740\u5931\u8D25\uFF1A${toMessage(error)}`);
|
|
39640
|
+
}
|
|
39641
|
+
if (!candidates.includes(address)) {
|
|
39642
|
+
throw new Error(`${address} \u4E0D\u662F\u5F53\u524D\u53EF\u7528\u7684 Windows \u5C40\u57DF\u7F51\u5730\u5740\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u540E\u518D\u5F00\u542F\u3002`);
|
|
39643
|
+
}
|
|
39644
|
+
const interfaces = this.localInterfaces?.();
|
|
39645
|
+
if (wslOwnAddresses(interfaces).includes(address))
|
|
39646
|
+
return { mode: "direct", bindAddress: address };
|
|
39647
|
+
const bindAddress = primaryWslAddress(interfaces);
|
|
39648
|
+
if (bindAddress === undefined)
|
|
39649
|
+
throw new Error("WSL \u5185\u6CA1\u6709\u53EF\u7528\u7684\u7F51\u7EDC\u5730\u5740\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u624B\u673A\u5165\u53E3\u3002");
|
|
39650
|
+
return { mode: "wsl-relay", bindAddress };
|
|
39651
|
+
}
|
|
39652
|
+
async startRelayFor(address, port, bindAddress) {
|
|
39653
|
+
const deadline = Date.now() + this.relayPortRetryMs;
|
|
39654
|
+
let relay;
|
|
39655
|
+
for (;; ) {
|
|
39656
|
+
try {
|
|
39657
|
+
relay = await this.startRelay({
|
|
39658
|
+
powershell: this.wslEnvironment?.powershell ?? "powershell.exe",
|
|
39659
|
+
listenAddress: address,
|
|
39660
|
+
listenPort: port,
|
|
39661
|
+
targetAddress: bindAddress,
|
|
39662
|
+
targetPort: port
|
|
39663
|
+
});
|
|
39664
|
+
break;
|
|
39665
|
+
} catch (error) {
|
|
39666
|
+
if (error instanceof RelayError && error.code === "port_in_use" && Date.now() + this.relayPortRetryIntervalMs < deadline) {
|
|
39667
|
+
await new Promise((resolve) => setTimeout(resolve, this.relayPortRetryIntervalMs));
|
|
39668
|
+
continue;
|
|
39669
|
+
}
|
|
39670
|
+
throw new Error(describeRelayError(error, address, port, bindAddress));
|
|
39671
|
+
}
|
|
39672
|
+
}
|
|
39673
|
+
this.relay = relay;
|
|
39674
|
+
relay.exited.then((reason) => this.onRelayExited(relay, reason));
|
|
39675
|
+
}
|
|
39676
|
+
onRelayExited(relay, reason) {
|
|
39677
|
+
this.enqueue(async () => {
|
|
39678
|
+
if (this.relay !== relay)
|
|
39679
|
+
return;
|
|
39680
|
+
this.relay = undefined;
|
|
39681
|
+
this.pairingValue?.invalidate();
|
|
39682
|
+
await this.stopListening().catch(() => {
|
|
39683
|
+
return;
|
|
39684
|
+
});
|
|
39685
|
+
const message = `Windows \u4E2D\u7EE7\u610F\u5916\u9000\u51FA\uFF08${reason}\uFF09\uFF0C\u8BF7\u91CD\u65B0\u5F00\u542F\u624B\u673A\u8BBF\u95EE\u3002`;
|
|
39686
|
+
this.stateValue = { status: "error", error: message };
|
|
39687
|
+
this.onError?.(new Error(message));
|
|
39688
|
+
});
|
|
39689
|
+
}
|
|
39168
39690
|
async certificateValue() {
|
|
39169
39691
|
if (this.certificate === undefined) {
|
|
39170
39692
|
this.certificate = await MobileCertificate.load(this.requireStore(), this.serverId);
|
|
@@ -39172,6 +39694,12 @@ class MobileGateway {
|
|
|
39172
39694
|
return this.certificate;
|
|
39173
39695
|
}
|
|
39174
39696
|
async stopListening() {
|
|
39697
|
+
const relay = this.relay;
|
|
39698
|
+
this.relay = undefined;
|
|
39699
|
+
if (relay !== undefined)
|
|
39700
|
+
await relay.stop().catch(() => {
|
|
39701
|
+
return;
|
|
39702
|
+
});
|
|
39175
39703
|
for (const connection of [...this.connections.values()])
|
|
39176
39704
|
connection.close(1001, "listener_stopped");
|
|
39177
39705
|
const server = this.server;
|
|
@@ -39276,42 +39804,26 @@ ${JSON.stringify({ code, message })}`);
|
|
|
39276
39804
|
return run;
|
|
39277
39805
|
}
|
|
39278
39806
|
}
|
|
39279
|
-
var
|
|
39807
|
+
var RELAY_PORT_RETRY_MS = 1e4, RELAY_PORT_RETRY_INTERVAL_MS = 1000, WSL_INTEROP_UNAVAILABLE = "WSL \u7684 Windows \u4E92\u64CD\u4F5C\u4E0D\u53EF\u7528\uFF08\u627E\u4E0D\u5230 powershell.exe\uFF09\uFF0C\u65E0\u6CD5\u8BFB\u53D6 Windows \u5C40\u57DF\u7F51\u5730\u5740\u3002\u8BF7\u542F\u7528 WSL \u4E92\u64CD\u4F5C\uFF0C\u6216\u76F4\u63A5\u5728 Windows \u4E0A\u8FD0\u884C neko\u3002";
|
|
39280
39808
|
var init_listener = __esm(() => {
|
|
39281
39809
|
init_contract();
|
|
39282
39810
|
init_link();
|
|
39283
39811
|
init_adapter();
|
|
39284
39812
|
init_auth();
|
|
39285
39813
|
init_certificate();
|
|
39814
|
+
init_network();
|
|
39286
39815
|
init_pairing();
|
|
39287
39816
|
init_protocol();
|
|
39817
|
+
init_relay();
|
|
39288
39818
|
init_store();
|
|
39289
|
-
|
|
39290
|
-
"anpi",
|
|
39291
|
-
"awdl",
|
|
39292
|
-
"bridge",
|
|
39293
|
-
"docker",
|
|
39294
|
-
"gif",
|
|
39295
|
-
"ipsec",
|
|
39296
|
-
"llw",
|
|
39297
|
-
"ppp",
|
|
39298
|
-
"stf",
|
|
39299
|
-
"tap",
|
|
39300
|
-
"tun",
|
|
39301
|
-
"utun",
|
|
39302
|
-
"vboxnet",
|
|
39303
|
-
"veth",
|
|
39304
|
-
"virbr",
|
|
39305
|
-
"vmnet"
|
|
39306
|
-
];
|
|
39307
|
-
VIRTUAL_INTERFACE_KEYWORDS = ["hyper-v", "virtual", "virtualbox", "vmware", "vpn"];
|
|
39819
|
+
init_wsl();
|
|
39308
39820
|
});
|
|
39309
39821
|
|
|
39310
39822
|
// packages/gateway/src/port-file.ts
|
|
39311
39823
|
import { readFile as readFile7, rm as rm4, writeFile as writeFile4 } from "fs/promises";
|
|
39312
|
-
import { join as
|
|
39824
|
+
import { join as join11 } from "path";
|
|
39313
39825
|
function gatewayPortFilePath(nekoDir, serverId) {
|
|
39314
|
-
return
|
|
39826
|
+
return join11(nekoDir, `${serverId}.port`);
|
|
39315
39827
|
}
|
|
39316
39828
|
async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
39317
39829
|
await writeFile4(gatewayPortFilePath(nekoDir, serverId), `${wsUrl}
|
|
@@ -40428,7 +40940,7 @@ var init_esm = __esm(() => {
|
|
|
40428
40940
|
import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
|
|
40429
40941
|
import { mkdir as mkdir6, readdir as readdir2, rename as rename4, rm as rm5, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
40430
40942
|
import { homedir as homedir7 } from "os";
|
|
40431
|
-
import { dirname as dirname8, join as
|
|
40943
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
40432
40944
|
function createSkillMarketClient(options) {
|
|
40433
40945
|
const home = options.home ?? homedir7();
|
|
40434
40946
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -40545,11 +41057,11 @@ function createSkillMarketClient(options) {
|
|
|
40545
41057
|
} catch {
|
|
40546
41058
|
return;
|
|
40547
41059
|
}
|
|
40548
|
-
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(
|
|
41060
|
+
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm5(join12(parent, n), { recursive: true, force: true }).catch(() => {})));
|
|
40549
41061
|
};
|
|
40550
41062
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
40551
41063
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
40552
|
-
const target =
|
|
41064
|
+
const target = join12(skillsDir, name);
|
|
40553
41065
|
const exists = await stat3(target).catch(() => {
|
|
40554
41066
|
return;
|
|
40555
41067
|
}) !== undefined;
|
|
@@ -40558,10 +41070,10 @@ function createSkillMarketClient(options) {
|
|
|
40558
41070
|
const skill = await detail(name);
|
|
40559
41071
|
const files = unpack(skill, await download(skill));
|
|
40560
41072
|
await sweepTempDirs(skillsDir);
|
|
40561
|
-
const temp =
|
|
41073
|
+
const temp = join12(dirname8(skillsDir), `.tmp-${randomUUID3()}`);
|
|
40562
41074
|
try {
|
|
40563
41075
|
for (const [relative, bytes] of files) {
|
|
40564
|
-
const dest =
|
|
41076
|
+
const dest = join12(temp, ...relative.split("/"));
|
|
40565
41077
|
await mkdir6(dirname8(dest), { recursive: true });
|
|
40566
41078
|
await writeFile5(dest, bytes);
|
|
40567
41079
|
}
|
|
@@ -40574,7 +41086,7 @@ function createSkillMarketClient(options) {
|
|
|
40574
41086
|
}
|
|
40575
41087
|
let shadowedBy;
|
|
40576
41088
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
40577
|
-
const projectDir =
|
|
41089
|
+
const projectDir = join12(projectSkillsDir(workspaceRoot), name);
|
|
40578
41090
|
if (await stat3(projectDir).catch(() => {
|
|
40579
41091
|
return;
|
|
40580
41092
|
}) !== undefined)
|
|
@@ -40584,7 +41096,7 @@ function createSkillMarketClient(options) {
|
|
|
40584
41096
|
};
|
|
40585
41097
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
40586
41098
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
40587
|
-
const target =
|
|
41099
|
+
const target = join12(skillsDir, name);
|
|
40588
41100
|
if (await stat3(target).catch(() => {
|
|
40589
41101
|
return;
|
|
40590
41102
|
}) === undefined)
|
|
@@ -40616,7 +41128,7 @@ function createSkillMarketClient(options) {
|
|
|
40616
41128
|
uninstall
|
|
40617
41129
|
};
|
|
40618
41130
|
}
|
|
40619
|
-
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) =>
|
|
41131
|
+
var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) => join12(home, ".agents", "skills"), projectSkillsDir = (workspaceRoot) => join12(workspaceRoot, ".agents", "skills"), activeLevelOf = (state) => state.project ? "project" : state.user ? "user" : state.builtin ? "builtin" : null, normalizeEntry = (raw) => {
|
|
40620
41132
|
const row = raw ?? {};
|
|
40621
41133
|
const metadata = row.metadata;
|
|
40622
41134
|
const source = metadata !== null && typeof metadata === "object" ? metadata.source : undefined;
|
|
@@ -40647,9 +41159,9 @@ var init_skill_market = __esm(() => {
|
|
|
40647
41159
|
});
|
|
40648
41160
|
|
|
40649
41161
|
// packages/gateway/src/workspaces.ts
|
|
40650
|
-
import { constants as
|
|
41162
|
+
import { constants as constants3 } from "fs";
|
|
40651
41163
|
import { access, readdir as readdir3, stat as stat4 } from "fs/promises";
|
|
40652
|
-
import { dirname as dirname9, isAbsolute as isAbsolute3, join as
|
|
41164
|
+
import { dirname as dirname9, isAbsolute as isAbsolute3, join as join13 } from "path";
|
|
40653
41165
|
async function browseWorkspaces(options) {
|
|
40654
41166
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
40655
41167
|
if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
|
|
@@ -40676,13 +41188,13 @@ async function browseWorkspaces(options) {
|
|
|
40676
41188
|
let next = offset;
|
|
40677
41189
|
for (;next < entries.length && directories.length < 100; next++) {
|
|
40678
41190
|
const entry = entries[next];
|
|
40679
|
-
const target =
|
|
41191
|
+
const target = join13(path, entry.name);
|
|
40680
41192
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
40681
41193
|
continue;
|
|
40682
41194
|
try {
|
|
40683
41195
|
if (!(await stat4(target)).isDirectory())
|
|
40684
41196
|
continue;
|
|
40685
|
-
await access(target,
|
|
41197
|
+
await access(target, constants3.R_OK | constants3.X_OK);
|
|
40686
41198
|
directories.push({ name: entry.name, path: target });
|
|
40687
41199
|
} catch {
|
|
40688
41200
|
directories.push({ name: entry.name, path: target, error: "\u76EE\u5F55\u4E0D\u53EF\u8BBF\u95EE" });
|
|
@@ -40961,7 +41473,7 @@ var init_server_services = __esm(() => {
|
|
|
40961
41473
|
// packages/gateway/src/gateway.ts
|
|
40962
41474
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
40963
41475
|
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
40964
|
-
import { basename as basename6, dirname as dirname10, join as
|
|
41476
|
+
import { basename as basename6, dirname as dirname10, join as join14 } from "path";
|
|
40965
41477
|
import { createHost as createHost3 } from "@earendil-works/chord";
|
|
40966
41478
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
40967
41479
|
async function startGateway(options = {}) {
|
|
@@ -40974,7 +41486,7 @@ async function startGateway(options = {}) {
|
|
|
40974
41486
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
40975
41487
|
const nekoDir = dirname10(storagePath);
|
|
40976
41488
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
40977
|
-
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null :
|
|
41489
|
+
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join14(nekoDir, `${serverId}.sock`);
|
|
40978
41490
|
const runtime = await createNekoRuntime({
|
|
40979
41491
|
context,
|
|
40980
41492
|
onReport: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
@@ -41134,7 +41646,7 @@ function serverIdFromSocketPath(socketPath) {
|
|
|
41134
41646
|
return isServerId(candidate) ? candidate : undefined;
|
|
41135
41647
|
}
|
|
41136
41648
|
async function loadOrCreateServerId(nekoDir) {
|
|
41137
|
-
const file =
|
|
41649
|
+
const file = join14(nekoDir, SERVER_ID_FILENAME);
|
|
41138
41650
|
try {
|
|
41139
41651
|
const existing = (await readFile8(file, "utf8")).trim();
|
|
41140
41652
|
if (existing !== "")
|
|
@@ -41170,20 +41682,89 @@ function gatewayCommand(args) {
|
|
|
41170
41682
|
]
|
|
41171
41683
|
};
|
|
41172
41684
|
}
|
|
41173
|
-
async function stopGatewayProcess(child) {
|
|
41685
|
+
async function stopGatewayProcess(child, options = {}) {
|
|
41174
41686
|
if (child.pid === undefined || child.exitCode !== null || child.signalCode !== null)
|
|
41175
41687
|
return;
|
|
41176
41688
|
await new Promise((resolve) => {
|
|
41177
|
-
|
|
41689
|
+
let forcedTimer;
|
|
41690
|
+
const timer = setTimeout(() => {
|
|
41691
|
+
child.kill("SIGKILL");
|
|
41692
|
+
forcedTimer = setTimeout(resolve, options.forcedMs ?? FORCED_STOP_MS);
|
|
41693
|
+
}, options.gracefulMs ?? GRACEFUL_STOP_MS);
|
|
41178
41694
|
child.once("exit", () => {
|
|
41179
41695
|
clearTimeout(timer);
|
|
41696
|
+
clearTimeout(forcedTimer);
|
|
41180
41697
|
resolve();
|
|
41181
41698
|
});
|
|
41182
41699
|
child.kill("SIGTERM");
|
|
41183
41700
|
});
|
|
41184
41701
|
}
|
|
41702
|
+
var GRACEFUL_STOP_MS = 3000, FORCED_STOP_MS = 2000;
|
|
41185
41703
|
var init_process = () => {};
|
|
41186
41704
|
|
|
41705
|
+
// packages/gateway/src/shutdown.ts
|
|
41706
|
+
function shutdownSignals(platform) {
|
|
41707
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
41708
|
+
if (platform === "win32")
|
|
41709
|
+
signals.push("SIGBREAK");
|
|
41710
|
+
return signals;
|
|
41711
|
+
}
|
|
41712
|
+
function installShutdown(options = {}) {
|
|
41713
|
+
const proc = options.process ?? process;
|
|
41714
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
41715
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
41716
|
+
const controller = new AbortController;
|
|
41717
|
+
let cleanup;
|
|
41718
|
+
let cleaning = false;
|
|
41719
|
+
let timer;
|
|
41720
|
+
const runCleanup = () => {
|
|
41721
|
+
if (cleaning || cleanup === undefined)
|
|
41722
|
+
return;
|
|
41723
|
+
cleaning = true;
|
|
41724
|
+
cleanup().then(() => exit(0), (error) => {
|
|
41725
|
+
options.log?.(error instanceof Error ? error.message : String(error));
|
|
41726
|
+
exit(1);
|
|
41727
|
+
});
|
|
41728
|
+
};
|
|
41729
|
+
const onSignal = () => {
|
|
41730
|
+
if (controller.signal.aborted) {
|
|
41731
|
+
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
41732
|
+
exit(130);
|
|
41733
|
+
return;
|
|
41734
|
+
}
|
|
41735
|
+
options.log?.(`
|
|
41736
|
+
\u6B63\u5728\u5173\u95ED\u2026`);
|
|
41737
|
+
controller.abort();
|
|
41738
|
+
timer = setTimeout(() => {
|
|
41739
|
+
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
41740
|
+
exit(1);
|
|
41741
|
+
}, timeoutMs);
|
|
41742
|
+
timer.unref?.();
|
|
41743
|
+
runCleanup();
|
|
41744
|
+
};
|
|
41745
|
+
const installed = [];
|
|
41746
|
+
for (const signal of shutdownSignals(proc.platform)) {
|
|
41747
|
+
try {
|
|
41748
|
+
proc.on(signal, onSignal);
|
|
41749
|
+
installed.push(signal);
|
|
41750
|
+
} catch {}
|
|
41751
|
+
}
|
|
41752
|
+
return {
|
|
41753
|
+
signal: controller.signal,
|
|
41754
|
+
setCleanup(next) {
|
|
41755
|
+
cleanup = next;
|
|
41756
|
+
if (controller.signal.aborted)
|
|
41757
|
+
runCleanup();
|
|
41758
|
+
},
|
|
41759
|
+
dispose() {
|
|
41760
|
+
clearTimeout(timer);
|
|
41761
|
+
for (const signal of installed)
|
|
41762
|
+
proc.off(signal, onSignal);
|
|
41763
|
+
}
|
|
41764
|
+
};
|
|
41765
|
+
}
|
|
41766
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
41767
|
+
|
|
41187
41768
|
// packages/gateway/src/index.ts
|
|
41188
41769
|
var init_src2 = __esm(() => {
|
|
41189
41770
|
init_client();
|
|
@@ -41199,13 +41780,13 @@ var init_src2 = __esm(() => {
|
|
|
41199
41780
|
|
|
41200
41781
|
// packages/tui/src/gateway.ts
|
|
41201
41782
|
import { spawn as spawn3 } from "child_process";
|
|
41202
|
-
import { existsSync as existsSync3, readFileSync as
|
|
41203
|
-
import { basename as basename7, dirname as dirname11, join as
|
|
41783
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
41784
|
+
import { basename as basename7, dirname as dirname11, join as join15 } from "path";
|
|
41204
41785
|
async function connectToGateway(options = {}) {
|
|
41205
41786
|
const config = loadNekoConfig(options.configDir);
|
|
41206
41787
|
const storagePath = options.storagePath ?? config.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
41207
41788
|
const nekoDir = dirname11(storagePath);
|
|
41208
|
-
const fileServerId = readServerId(
|
|
41789
|
+
const fileServerId = readServerId(join15(nekoDir, SERVER_ID_FILENAME2));
|
|
41209
41790
|
const platform = options.platform ?? process.platform;
|
|
41210
41791
|
if (platform === "win32") {
|
|
41211
41792
|
if (fileServerId !== undefined) {
|
|
@@ -41222,8 +41803,8 @@ async function connectToGateway(options = {}) {
|
|
|
41222
41803
|
}
|
|
41223
41804
|
return spawnAndConnect({ ...options, platform, storagePath, socketPath: undefined, nekoDir, fileServerId });
|
|
41224
41805
|
}
|
|
41225
|
-
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ?
|
|
41226
|
-
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(
|
|
41806
|
+
const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join15(nekoDir, `${fileServerId}.sock`) : undefined);
|
|
41807
|
+
const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join15(nekoDir, SERVER_ID_FILENAME2));
|
|
41227
41808
|
if (socketPath !== undefined && existsSync3(socketPath)) {
|
|
41228
41809
|
const serverId = serverIdFor(socketPath);
|
|
41229
41810
|
if (serverId !== undefined) {
|
|
@@ -41257,7 +41838,7 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41257
41838
|
const timeoutMs = plan.spawnTimeoutMs ?? 20000;
|
|
41258
41839
|
if (plan.platform === "win32") {
|
|
41259
41840
|
const timeoutAt = Date.now() + timeoutMs;
|
|
41260
|
-
const serverId = plan.fileServerId ?? await waitForValue2(() => readServerId(
|
|
41841
|
+
const serverId = plan.fileServerId ?? await waitForValue2(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41261
41842
|
let lastError = new Error("Gateway port file unavailable");
|
|
41262
41843
|
while (Date.now() < timeoutAt) {
|
|
41263
41844
|
if (child.exitCode !== null || child.signalCode !== null || spawnError() !== undefined) {
|
|
@@ -41294,11 +41875,11 @@ async function connectSpawned(plan, child, outputTail, spawnError) {
|
|
|
41294
41875
|
}
|
|
41295
41876
|
let socketPath = plan.socketPath;
|
|
41296
41877
|
if (socketPath === undefined) {
|
|
41297
|
-
const generated = await waitForValue2(() => readServerId(
|
|
41298
|
-
socketPath =
|
|
41878
|
+
const generated = await waitForValue2(() => readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
|
|
41879
|
+
socketPath = join15(plan.nekoDir, `${generated}.sock`);
|
|
41299
41880
|
}
|
|
41300
41881
|
await waitForValue2(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
|
|
41301
|
-
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(
|
|
41882
|
+
const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join15(plan.nekoDir, SERVER_ID_FILENAME2));
|
|
41302
41883
|
if (serverId === undefined) {
|
|
41303
41884
|
throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
|
|
41304
41885
|
}
|
|
@@ -41350,7 +41931,7 @@ function gatewayArgs(plan) {
|
|
|
41350
41931
|
}
|
|
41351
41932
|
function readServerId(file) {
|
|
41352
41933
|
try {
|
|
41353
|
-
const value =
|
|
41934
|
+
const value = readFileSync3(file, "utf8").trim();
|
|
41354
41935
|
return isServerId(value) ? value : undefined;
|
|
41355
41936
|
} catch {
|
|
41356
41937
|
return;
|
|
@@ -41554,7 +42135,7 @@ var init_help_ui = __esm(() => {
|
|
|
41554
42135
|
});
|
|
41555
42136
|
|
|
41556
42137
|
// packages/tui/src/image-input.ts
|
|
41557
|
-
import { execFile as
|
|
42138
|
+
import { execFile as execFile3 } from "child_process";
|
|
41558
42139
|
import { readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
41559
42140
|
import { homedir as homedir8 } from "os";
|
|
41560
42141
|
import { basename as basename8, resolve as resolve5 } from "path";
|
|
@@ -41657,7 +42238,7 @@ function isClipboardShortcut(data, platform = process.platform) {
|
|
|
41657
42238
|
}
|
|
41658
42239
|
var exec;
|
|
41659
42240
|
var init_image_input = __esm(() => {
|
|
41660
|
-
exec = promisify2(
|
|
42241
|
+
exec = promisify2(execFile3);
|
|
41661
42242
|
});
|
|
41662
42243
|
|
|
41663
42244
|
// packages/tui/src/layout.ts
|
|
@@ -43648,12 +44229,12 @@ var init_main = __esm(async () => {
|
|
|
43648
44229
|
});
|
|
43649
44230
|
|
|
43650
44231
|
// packages/web/src/open-browser.ts
|
|
43651
|
-
import { execFile as
|
|
44232
|
+
import { execFile as execFile4 } from "child_process";
|
|
43652
44233
|
function openBrowser(url) {
|
|
43653
44234
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32.exe" : "xdg-open";
|
|
43654
44235
|
const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
43655
44236
|
return new Promise((resolve, reject) => {
|
|
43656
|
-
|
|
44237
|
+
execFile4(command, args, { timeout: 8000, windowsHide: true }, (error) => {
|
|
43657
44238
|
if (error)
|
|
43658
44239
|
reject(error);
|
|
43659
44240
|
else
|
|
@@ -43667,7 +44248,7 @@ var init_open_browser = () => {};
|
|
|
43667
44248
|
import { spawn as spawn4 } from "child_process";
|
|
43668
44249
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
43669
44250
|
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
|
|
43670
|
-
import { dirname as dirname12, join as
|
|
44251
|
+
import { dirname as dirname12, join as join16 } from "path";
|
|
43671
44252
|
async function ensureNekoGateway(options) {
|
|
43672
44253
|
const timeoutMs = options.spawnTimeoutMs ?? 30000;
|
|
43673
44254
|
const timeoutAt = Date.now() + timeoutMs;
|
|
@@ -43678,6 +44259,7 @@ async function ensureNekoGateway(options) {
|
|
|
43678
44259
|
if (discovered !== undefined && await probeWs(discovered, serverId, timeoutAt)) {
|
|
43679
44260
|
return makeHandle(serverId, undefined, discovered, null, nekoDir);
|
|
43680
44261
|
}
|
|
44262
|
+
throwIfAborted(options.signal);
|
|
43681
44263
|
const child = spawnGatewayProcess(options, null, serverId);
|
|
43682
44264
|
pipeChildLogs(child, options.onLog);
|
|
43683
44265
|
let spawnError;
|
|
@@ -43687,6 +44269,7 @@ async function ensureNekoGateway(options) {
|
|
|
43687
44269
|
try {
|
|
43688
44270
|
let lastError = new Error("gateway \u672A\u5C31\u7EEA");
|
|
43689
44271
|
while (Date.now() < timeoutAt) {
|
|
44272
|
+
throwIfAborted(options.signal);
|
|
43690
44273
|
if (spawnError !== undefined)
|
|
43691
44274
|
throw spawnError;
|
|
43692
44275
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
@@ -43703,7 +44286,7 @@ async function ensureNekoGateway(options) {
|
|
|
43703
44286
|
lastError = error;
|
|
43704
44287
|
}
|
|
43705
44288
|
}
|
|
43706
|
-
await sleep2(Math.max(0, Math.min(PROBE_INTERVAL_MS, timeoutAt - Date.now())));
|
|
44289
|
+
await sleep2(Math.max(0, Math.min(PROBE_INTERVAL_MS, timeoutAt - Date.now())), options.signal);
|
|
43707
44290
|
}
|
|
43708
44291
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
43709
44292
|
} catch (error) {
|
|
@@ -43711,7 +44294,7 @@ async function ensureNekoGateway(options) {
|
|
|
43711
44294
|
throw error;
|
|
43712
44295
|
}
|
|
43713
44296
|
}
|
|
43714
|
-
const socketPath = options.socketPath ??
|
|
44297
|
+
const socketPath = options.socketPath ?? join16(nekoDir, `${serverId}.sock`);
|
|
43715
44298
|
const host = options.host ?? "127.0.0.1";
|
|
43716
44299
|
const port = options.port ?? 8321;
|
|
43717
44300
|
const wsUrl = `ws://${host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${port}`;
|
|
@@ -43721,6 +44304,7 @@ async function ensureNekoGateway(options) {
|
|
|
43721
44304
|
}
|
|
43722
44305
|
return makeHandle(serverId, socketPath, wsUrl, null, nekoDir);
|
|
43723
44306
|
}
|
|
44307
|
+
throwIfAborted(options.signal);
|
|
43724
44308
|
const child = spawnGatewayProcess(options, socketPath, serverId);
|
|
43725
44309
|
pipeChildLogs(child, options.onLog);
|
|
43726
44310
|
let spawnError;
|
|
@@ -43730,6 +44314,7 @@ async function ensureNekoGateway(options) {
|
|
|
43730
44314
|
try {
|
|
43731
44315
|
let lastError = new Error("gateway \u672A\u5C31\u7EEA");
|
|
43732
44316
|
while (Date.now() < timeoutAt) {
|
|
44317
|
+
throwIfAborted(options.signal);
|
|
43733
44318
|
if (spawnError !== undefined)
|
|
43734
44319
|
throw spawnError;
|
|
43735
44320
|
if (child.exitCode !== null || child.signalCode !== null) {
|
|
@@ -43744,7 +44329,7 @@ async function ensureNekoGateway(options) {
|
|
|
43744
44329
|
throw error;
|
|
43745
44330
|
lastError = error;
|
|
43746
44331
|
}
|
|
43747
|
-
await sleep2(Math.max(0, Math.min(PROBE_INTERVAL_MS, timeoutAt - Date.now())));
|
|
44332
|
+
await sleep2(Math.max(0, Math.min(PROBE_INTERVAL_MS, timeoutAt - Date.now())), options.signal);
|
|
43748
44333
|
}
|
|
43749
44334
|
throw new Error(`\u7B49\u5F85 neko gateway \u5C31\u7EEA\u8D85\u65F6\uFF08${options.spawnTimeoutMs ?? 30000}ms\uFF09\uFF1A${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
43750
44335
|
} catch (error) {
|
|
@@ -43831,7 +44416,7 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
|
|
|
43831
44416
|
};
|
|
43832
44417
|
}
|
|
43833
44418
|
async function loadOrCreateServerId2(nekoDir) {
|
|
43834
|
-
const file =
|
|
44419
|
+
const file = join16(nekoDir, SERVER_ID_FILENAME3);
|
|
43835
44420
|
try {
|
|
43836
44421
|
const existing = (await readFile10(file, "utf8")).trim();
|
|
43837
44422
|
if (existing !== "")
|
|
@@ -43843,8 +44428,22 @@ async function loadOrCreateServerId2(nekoDir) {
|
|
|
43843
44428
|
`, { mode: 384 });
|
|
43844
44429
|
return created;
|
|
43845
44430
|
}
|
|
43846
|
-
function
|
|
43847
|
-
|
|
44431
|
+
function throwIfAborted(signal) {
|
|
44432
|
+
if (signal?.aborted)
|
|
44433
|
+
throw new Error("neko gateway \u542F\u52A8\u88AB\u4E2D\u65AD");
|
|
44434
|
+
}
|
|
44435
|
+
function sleep2(ms, signal) {
|
|
44436
|
+
return new Promise((resolve) => {
|
|
44437
|
+
if (signal?.aborted)
|
|
44438
|
+
return resolve();
|
|
44439
|
+
const timer = setTimeout(done, ms);
|
|
44440
|
+
function done() {
|
|
44441
|
+
clearTimeout(timer);
|
|
44442
|
+
signal?.removeEventListener("abort", done);
|
|
44443
|
+
resolve();
|
|
44444
|
+
}
|
|
44445
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
44446
|
+
});
|
|
43848
44447
|
}
|
|
43849
44448
|
var SERVER_ID_FILENAME3 = "gateway-server-id", PROBE_INTERVAL_MS = 400, PROBE_TIMEOUT_MS = 5000;
|
|
43850
44449
|
var init_gateway_spawn = __esm(() => {
|
|
@@ -43855,7 +44454,7 @@ var init_gateway_spawn = __esm(() => {
|
|
|
43855
44454
|
// packages/web/src/mobile-admin.ts
|
|
43856
44455
|
import { randomBytes as randomBytes4, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
43857
44456
|
import { ChordError as ChordError7, createHost as createHost4 } from "@earendil-works/chord";
|
|
43858
|
-
function createMobileAdminCaller(gateway, nekoDir) {
|
|
44457
|
+
function createMobileAdminCaller(gateway, nekoDir, timeouts = {}) {
|
|
43859
44458
|
const serverId = gateway.serverId;
|
|
43860
44459
|
let tokenPromise;
|
|
43861
44460
|
const readToken = () => {
|
|
@@ -43865,10 +44464,18 @@ function createMobileAdminCaller(gateway, nekoDir) {
|
|
|
43865
44464
|
});
|
|
43866
44465
|
return tokenPromise;
|
|
43867
44466
|
};
|
|
43868
|
-
|
|
44467
|
+
const run = async (method, params, signal) => {
|
|
43869
44468
|
const token = await readToken();
|
|
44469
|
+
signal.throwIfAborted();
|
|
43870
44470
|
const link = gateway.socketPath !== undefined ? await createUnixLink(gateway.socketPath) : createWsLink(gateway.wsUrl);
|
|
43871
44471
|
const host = createHost4({ kind: "client", upstream: identifiedLink(link, serverId), onError: () => {} });
|
|
44472
|
+
const release = () => {
|
|
44473
|
+
host.close().catch(() => {
|
|
44474
|
+
return;
|
|
44475
|
+
});
|
|
44476
|
+
link.close("aborted");
|
|
44477
|
+
};
|
|
44478
|
+
signal.addEventListener("abort", release, { once: true });
|
|
43872
44479
|
try {
|
|
43873
44480
|
const admin = host.get(NekoMobileAdmin, { token }, { at: serverId });
|
|
43874
44481
|
const call = admin[method];
|
|
@@ -43884,12 +44491,42 @@ function createMobileAdminCaller(gateway, nekoDir) {
|
|
|
43884
44491
|
admin.release?.();
|
|
43885
44492
|
}
|
|
43886
44493
|
} finally {
|
|
44494
|
+
signal.removeEventListener("abort", release);
|
|
43887
44495
|
await host.close().catch(() => {
|
|
43888
44496
|
return;
|
|
43889
44497
|
});
|
|
43890
44498
|
link.close("done");
|
|
43891
44499
|
}
|
|
43892
44500
|
};
|
|
44501
|
+
const inflight = new Set;
|
|
44502
|
+
const caller = async (method, params) => {
|
|
44503
|
+
const timeoutMs = method === "enable" ? timeouts.enableTimeoutMs ?? MOBILE_ADMIN_ENABLE_TIMEOUT_MS : timeouts.callTimeoutMs ?? MOBILE_ADMIN_CALL_TIMEOUT_MS;
|
|
44504
|
+
const controller = new AbortController;
|
|
44505
|
+
inflight.add(controller);
|
|
44506
|
+
const timer = setTimeout(() => controller.abort(new MobileAdminTimeoutError(method, timeoutMs)), timeoutMs);
|
|
44507
|
+
try {
|
|
44508
|
+
const work = run(method, params, controller.signal);
|
|
44509
|
+
work.catch(() => {
|
|
44510
|
+
return;
|
|
44511
|
+
});
|
|
44512
|
+
return await new Promise((resolve, reject) => {
|
|
44513
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
44514
|
+
if (controller.signal.aborted)
|
|
44515
|
+
onAbort();
|
|
44516
|
+
else
|
|
44517
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
44518
|
+
work.then(resolve, reject);
|
|
44519
|
+
});
|
|
44520
|
+
} finally {
|
|
44521
|
+
clearTimeout(timer);
|
|
44522
|
+
inflight.delete(controller);
|
|
44523
|
+
}
|
|
44524
|
+
};
|
|
44525
|
+
caller.abortAll = () => {
|
|
44526
|
+
for (const controller of [...inflight])
|
|
44527
|
+
controller.abort(new MobileAdminAbortedError);
|
|
44528
|
+
};
|
|
44529
|
+
return caller;
|
|
43893
44530
|
}
|
|
43894
44531
|
function writeJson(response, status, body) {
|
|
43895
44532
|
response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -43966,7 +44603,7 @@ async function handleMobileAdminRequest(request, response, context) {
|
|
|
43966
44603
|
if (error instanceof GatewayUnsupportedError)
|
|
43967
44604
|
supported = false;
|
|
43968
44605
|
else
|
|
43969
|
-
|
|
44606
|
+
return respondWithCaller(response, Promise.reject(error));
|
|
43970
44607
|
}
|
|
43971
44608
|
writeJson(response, 200, { csrf: context.csrf, supported });
|
|
43972
44609
|
return true;
|
|
@@ -44024,6 +44661,14 @@ async function respondWithCaller(response, result) {
|
|
|
44024
44661
|
writeJson(response, 502, { code: error.code, message: error.message });
|
|
44025
44662
|
return true;
|
|
44026
44663
|
}
|
|
44664
|
+
if (error instanceof MobileAdminTimeoutError) {
|
|
44665
|
+
writeJson(response, 504, { code: "gateway_timeout", message: error.message });
|
|
44666
|
+
return true;
|
|
44667
|
+
}
|
|
44668
|
+
if (error instanceof MobileAdminAbortedError) {
|
|
44669
|
+
writeJson(response, 503, { code: "shutting_down", message: error.message });
|
|
44670
|
+
return true;
|
|
44671
|
+
}
|
|
44027
44672
|
throw error;
|
|
44028
44673
|
}
|
|
44029
44674
|
return true;
|
|
@@ -44041,7 +44686,7 @@ function createMobileAdminContext(options) {
|
|
|
44041
44686
|
allowedHosts: hosts
|
|
44042
44687
|
};
|
|
44043
44688
|
}
|
|
44044
|
-
var BODY_LIMIT_BYTES = 4096, GatewayUnsupportedError, WRITE_ROUTES, READ_ROUTES;
|
|
44689
|
+
var BODY_LIMIT_BYTES = 4096, GatewayUnsupportedError, MobileAdminTimeoutError, MobileAdminAbortedError, MOBILE_ADMIN_CALL_TIMEOUT_MS = 1e4, MOBILE_ADMIN_ENABLE_TIMEOUT_MS = 30000, WRITE_ROUTES, READ_ROUTES;
|
|
44045
44690
|
var init_mobile_admin = __esm(() => {
|
|
44046
44691
|
init_src2();
|
|
44047
44692
|
GatewayUnsupportedError = class GatewayUnsupportedError extends Error {
|
|
@@ -44050,6 +44695,18 @@ var init_mobile_admin = __esm(() => {
|
|
|
44050
44695
|
this.name = "GatewayUnsupportedError";
|
|
44051
44696
|
}
|
|
44052
44697
|
};
|
|
44698
|
+
MobileAdminTimeoutError = class MobileAdminTimeoutError extends Error {
|
|
44699
|
+
constructor(method, timeoutMs) {
|
|
44700
|
+
super(`mobile admin call "${method}" timed out after ${timeoutMs}ms`);
|
|
44701
|
+
this.name = "MobileAdminTimeoutError";
|
|
44702
|
+
}
|
|
44703
|
+
};
|
|
44704
|
+
MobileAdminAbortedError = class MobileAdminAbortedError extends Error {
|
|
44705
|
+
constructor() {
|
|
44706
|
+
super("mobile admin call was cancelled because the web server is shutting down");
|
|
44707
|
+
this.name = "MobileAdminAbortedError";
|
|
44708
|
+
}
|
|
44709
|
+
};
|
|
44053
44710
|
WRITE_ROUTES = {
|
|
44054
44711
|
"/api/mobile/enable": "enable",
|
|
44055
44712
|
"/api/mobile/disable": "disable",
|
|
@@ -44075,9 +44732,11 @@ async function startWebServer(options = {}) {
|
|
|
44075
44732
|
const storagePath = options.gateway?.storagePath ?? DEFAULT_STORAGE_PATH;
|
|
44076
44733
|
const gateway = await ensureNekoGateway({
|
|
44077
44734
|
...options.gateway ?? {},
|
|
44078
|
-
storagePath
|
|
44735
|
+
storagePath,
|
|
44736
|
+
...options.signal !== undefined ? { signal: options.signal } : {}
|
|
44079
44737
|
});
|
|
44080
44738
|
let mobileAdmin;
|
|
44739
|
+
let closing;
|
|
44081
44740
|
const server = createServer3((request, response) => {
|
|
44082
44741
|
const context = mobileAdmin;
|
|
44083
44742
|
if (context === undefined) {
|
|
@@ -44098,12 +44757,24 @@ async function startWebServer(options = {}) {
|
|
|
44098
44757
|
response.end(error instanceof Error ? error.message : String(error));
|
|
44099
44758
|
});
|
|
44100
44759
|
});
|
|
44760
|
+
const sockets = new Set;
|
|
44761
|
+
server.on("connection", (socket) => {
|
|
44762
|
+
sockets.add(socket);
|
|
44763
|
+
socket.once("close", () => sockets.delete(socket));
|
|
44764
|
+
});
|
|
44101
44765
|
const port = options.port ?? DEFAULT_PORT2;
|
|
44102
44766
|
const host = options.host ?? DEFAULT_HOST2;
|
|
44103
|
-
|
|
44104
|
-
|
|
44105
|
-
|
|
44106
|
-
|
|
44767
|
+
try {
|
|
44768
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
44769
|
+
server.once("error", rejectListen);
|
|
44770
|
+
server.listen(port, host, resolveListen);
|
|
44771
|
+
});
|
|
44772
|
+
} catch (error) {
|
|
44773
|
+
await gateway.close().catch(() => {
|
|
44774
|
+
return;
|
|
44775
|
+
});
|
|
44776
|
+
throw error;
|
|
44777
|
+
}
|
|
44107
44778
|
const address = server.address();
|
|
44108
44779
|
const actualPort = typeof address === "object" && address !== null ? address.port : port;
|
|
44109
44780
|
mobileAdmin = createMobileAdminContext({
|
|
@@ -44116,11 +44787,16 @@ async function startWebServer(options = {}) {
|
|
|
44116
44787
|
url: `http://${host === "0.0.0.0" ? "127.0.0.1" : host === "::" ? "[::1]" : host.includes(":") && !host.startsWith("[") ? `[${host}]` : host}:${actualPort}`,
|
|
44117
44788
|
port: actualPort,
|
|
44118
44789
|
gateway,
|
|
44119
|
-
|
|
44120
|
-
|
|
44121
|
-
|
|
44122
|
-
|
|
44123
|
-
|
|
44790
|
+
close() {
|
|
44791
|
+
closing ??= (async () => {
|
|
44792
|
+
mobileAdmin?.caller.abortAll?.();
|
|
44793
|
+
const closed = new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
44794
|
+
for (const socket of sockets)
|
|
44795
|
+
socket.destroy();
|
|
44796
|
+
await closed;
|
|
44797
|
+
await gateway.close();
|
|
44798
|
+
})();
|
|
44799
|
+
return closing;
|
|
44124
44800
|
}
|
|
44125
44801
|
};
|
|
44126
44802
|
}
|
|
@@ -44342,13 +45018,22 @@ var USAGE2 = `\u7528\u6CD5: neko web [\u9009\u9879]
|
|
|
44342
45018
|
--no-open \u4E0D\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF08\u9002\u7528\u4E8E\u8FDC\u7A0B\u6216\u65E0\u684C\u9762\u73AF\u5883\uFF09
|
|
44343
45019
|
-h, --help \u663E\u793A\u6B64\u5E2E\u52A9
|
|
44344
45020
|
|
|
44345
|
-
\u53EF\u5728 Web \u8BBE\u7F6E\u9875\u914D\u7F6E Key\uFF1B\u7F51\u5173\u5B50\u8FDB\u7A0B\u4E5F\u4F1A\u7EE7\u627F DEEPSEEK_API_KEY\u3002
|
|
45021
|
+
\u53EF\u5728 Web \u8BBE\u7F6E\u9875\u914D\u7F6E Key\uFF1B\u7F51\u5173\u5B50\u8FDB\u7A0B\u4E5F\u4F1A\u7EE7\u627F DEEPSEEK_API_KEY\u3002`, server, shutdown;
|
|
44346
45022
|
var init_main2 = __esm(async () => {
|
|
45023
|
+
init_src2();
|
|
44347
45024
|
init_open_browser();
|
|
44348
45025
|
init_server();
|
|
45026
|
+
shutdown = installShutdown({
|
|
45027
|
+
log: (message) => console.log(message),
|
|
45028
|
+
exit: (code) => {
|
|
45029
|
+
server?.gateway.spawned?.kill("SIGKILL");
|
|
45030
|
+
process.exit(code);
|
|
45031
|
+
}
|
|
45032
|
+
});
|
|
44349
45033
|
try {
|
|
44350
45034
|
const options = parseArgs2(process.argv.slice(2));
|
|
44351
|
-
|
|
45035
|
+
server = await startWebServer({
|
|
45036
|
+
signal: shutdown.signal,
|
|
44352
45037
|
...options.port !== undefined ? { port: options.port } : {},
|
|
44353
45038
|
...options.host !== undefined ? { host: options.host } : {},
|
|
44354
45039
|
gateway: {
|
|
@@ -44361,27 +45046,23 @@ var init_main2 = __esm(async () => {
|
|
|
44361
45046
|
onLog: (line) => console.log(`[gateway] ${line}`)
|
|
44362
45047
|
}
|
|
44363
45048
|
});
|
|
44364
|
-
|
|
44365
|
-
|
|
45049
|
+
const running = server;
|
|
45050
|
+
shutdown.setCleanup(() => running.close());
|
|
45051
|
+
console.log(`[web] neko web \u5DF2\u542F\u52A8: ${running.url}`);
|
|
45052
|
+
console.log(`[web] gateway: ${running.gateway.wsUrl}\uFF08serverId: ${running.gateway.serverId}\uFF09`);
|
|
44366
45053
|
console.log("[web] \u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
44367
|
-
const shutdown = () => {
|
|
44368
|
-
console.log(`
|
|
44369
|
-
\u6B63\u5728\u5173\u95ED\u2026`);
|
|
44370
|
-
server.close().then(() => process.exit(0)).catch((error) => {
|
|
44371
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
44372
|
-
process.exit(1);
|
|
44373
|
-
});
|
|
44374
|
-
};
|
|
44375
|
-
process.on("SIGINT", shutdown);
|
|
44376
|
-
process.on("SIGTERM", shutdown);
|
|
44377
45054
|
if (options.open) {
|
|
44378
|
-
openBrowser(
|
|
44379
|
-
console.warn(`\u65E0\u6CD5\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE: ${
|
|
45055
|
+
openBrowser(running.url).catch(() => {
|
|
45056
|
+
console.warn(`\u65E0\u6CD5\u81EA\u52A8\u6253\u5F00\u6D4F\u89C8\u5668\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE: ${running.url}`);
|
|
44380
45057
|
});
|
|
44381
45058
|
}
|
|
44382
45059
|
} catch (error) {
|
|
45060
|
+
if (shutdown.signal.aborted) {
|
|
45061
|
+
process.exit(0);
|
|
45062
|
+
}
|
|
44383
45063
|
console.error(error instanceof Error ? error.message : String(error));
|
|
44384
45064
|
process.exitCode = 1;
|
|
45065
|
+
shutdown.dispose();
|
|
44385
45066
|
}
|
|
44386
45067
|
});
|
|
44387
45068
|
|
|
@@ -44489,15 +45170,7 @@ var GATEWAY_USAGE = `\u7528\u6CD5: neko serve [\u9009\u9879]
|
|
|
44489
45170
|
\u53EF\u901A\u8FC7\u524D\u7AEF\u8BBE\u7F6E\u6216 DEEPSEEK_API_KEY \u914D\u7F6E\u51ED\u636E\u3002`;
|
|
44490
45171
|
|
|
44491
45172
|
// packages/gateway/src/main.ts
|
|
44492
|
-
var argv, options, gateway
|
|
44493
|
-
if (stopping)
|
|
44494
|
-
return;
|
|
44495
|
-
stopping = true;
|
|
44496
|
-
gateway.close().then(() => process.exit(0)).catch((error) => {
|
|
44497
|
-
console.error("\u5173\u95ED\u5931\u8D25:", error instanceof Error ? error.message : String(error));
|
|
44498
|
-
process.exit(1);
|
|
44499
|
-
});
|
|
44500
|
-
};
|
|
45173
|
+
var argv, shutdown2, options, gateway;
|
|
44501
45174
|
var init_main3 = __esm(async () => {
|
|
44502
45175
|
init_gateway();
|
|
44503
45176
|
argv = process.argv.slice(2);
|
|
@@ -44505,6 +45178,10 @@ var init_main3 = __esm(async () => {
|
|
|
44505
45178
|
console.log(GATEWAY_USAGE);
|
|
44506
45179
|
process.exit(0);
|
|
44507
45180
|
}
|
|
45181
|
+
shutdown2 = installShutdown({
|
|
45182
|
+
log: (message) => console.log(message),
|
|
45183
|
+
exit: (code) => process.exit(code)
|
|
45184
|
+
});
|
|
44508
45185
|
options = parseGatewayArgs(argv);
|
|
44509
45186
|
gateway = await startGateway({
|
|
44510
45187
|
...options,
|
|
@@ -44516,8 +45193,7 @@ var init_main3 = __esm(async () => {
|
|
|
44516
45193
|
if (gateway.wsUrl !== undefined)
|
|
44517
45194
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
44518
45195
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
44519
|
-
|
|
44520
|
-
process.on("SIGTERM", stop);
|
|
45196
|
+
shutdown2.setCleanup(() => gateway.close());
|
|
44521
45197
|
});
|
|
44522
45198
|
|
|
44523
45199
|
// packages/cli/src/main.ts
|