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/gateway.js
CHANGED
|
@@ -16593,7 +16593,7 @@ function parseGatewayArgs(argv) {
|
|
|
16593
16593
|
// packages/gateway/src/gateway.ts
|
|
16594
16594
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
16595
16595
|
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
16596
|
-
import { basename as basename4, dirname as dirname9, join as
|
|
16596
|
+
import { basename as basename4, dirname as dirname9, join as join13 } from "path";
|
|
16597
16597
|
import { createHost as createHost3 } from "@earendil-works/chord";
|
|
16598
16598
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
16599
16599
|
|
|
@@ -21124,7 +21124,9 @@ var listenerStateSchema = Type5.Union([
|
|
|
21124
21124
|
Type5.Object({
|
|
21125
21125
|
status: Type5.Literal("listening"),
|
|
21126
21126
|
address: Type5.String(),
|
|
21127
|
-
port: Type5.Integer({ minimum: 1, maximum: 65535 })
|
|
21127
|
+
port: Type5.Integer({ minimum: 1, maximum: 65535 }),
|
|
21128
|
+
bindAddress: Type5.Optional(Type5.String()),
|
|
21129
|
+
mode: Type5.Optional(Type5.Union([Type5.Literal("direct"), Type5.Literal("wsl-relay")]))
|
|
21128
21130
|
}),
|
|
21129
21131
|
Type5.Object({ status: Type5.Literal("error"), error: Type5.String() }),
|
|
21130
21132
|
Type5.Object({ status: Type5.Literal("stopping") })
|
|
@@ -21153,7 +21155,11 @@ var NekoMobileAdmin = contract("neko.mobile-admin", {
|
|
|
21153
21155
|
Type5.Null()
|
|
21154
21156
|
]),
|
|
21155
21157
|
devices: Type5.Array(adminDeviceSchema),
|
|
21156
|
-
lastConnectedAddress: Type5.Union([Type5.String(), Type5.Null()])
|
|
21158
|
+
lastConnectedAddress: Type5.Union([Type5.String(), Type5.Null()]),
|
|
21159
|
+
wsl: Type5.Optional(Type5.Union([
|
|
21160
|
+
Type5.Object({ interop: Type5.Boolean(), error: Type5.Union([Type5.String(), Type5.Null()]) }),
|
|
21161
|
+
Type5.Null()
|
|
21162
|
+
]))
|
|
21157
21163
|
})
|
|
21158
21164
|
],
|
|
21159
21165
|
enable: [
|
|
@@ -21164,7 +21170,9 @@ var NekoMobileAdmin = contract("neko.mobile-admin", {
|
|
|
21164
21170
|
Type5.Object({
|
|
21165
21171
|
status: Type5.Literal("listening"),
|
|
21166
21172
|
address: Type5.String(),
|
|
21167
|
-
port: Type5.Integer({ minimum: 1, maximum: 65535 })
|
|
21173
|
+
port: Type5.Integer({ minimum: 1, maximum: 65535 }),
|
|
21174
|
+
bindAddress: Type5.Optional(Type5.String()),
|
|
21175
|
+
mode: Type5.Optional(Type5.Union([Type5.Literal("direct"), Type5.Literal("wsl-relay")]))
|
|
21168
21176
|
})
|
|
21169
21177
|
],
|
|
21170
21178
|
disable: [Type5.Null(), listenerStateSchema],
|
|
@@ -21250,7 +21258,7 @@ function createMobileAdminFactory(deps) {
|
|
|
21250
21258
|
// packages/gateway/src/mobile/listener.ts
|
|
21251
21259
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
21252
21260
|
import { createServer } from "https";
|
|
21253
|
-
import { hostname
|
|
21261
|
+
import { hostname } from "os";
|
|
21254
21262
|
import { createHost as createHost2, memoryLinkPair } from "@earendil-works/chord";
|
|
21255
21263
|
import { WebSocketServer } from "ws";
|
|
21256
21264
|
|
|
@@ -21813,7 +21821,7 @@ import { Check as Check2 } from "typebox/value";
|
|
|
21813
21821
|
import { createHost } from "@earendil-works/chord";
|
|
21814
21822
|
|
|
21815
21823
|
// packages/core/src/application-version.ts
|
|
21816
|
-
var RUNNING_VERSION = "0.4.
|
|
21824
|
+
var RUNNING_VERSION = "0.4.2";
|
|
21817
21825
|
var APPLICATION_PROTOCOL = 4;
|
|
21818
21826
|
function stableVersion(value) {
|
|
21819
21827
|
return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
|
|
@@ -23749,6 +23757,70 @@ class MobileCertificate {
|
|
|
23749
23757
|
}
|
|
23750
23758
|
}
|
|
23751
23759
|
|
|
23760
|
+
// packages/gateway/src/mobile/network.ts
|
|
23761
|
+
import { networkInterfaces } from "os";
|
|
23762
|
+
var VIRTUAL_INTERFACE_PREFIXES = [
|
|
23763
|
+
"anpi",
|
|
23764
|
+
"awdl",
|
|
23765
|
+
"bridge",
|
|
23766
|
+
"docker",
|
|
23767
|
+
"gif",
|
|
23768
|
+
"ipsec",
|
|
23769
|
+
"llw",
|
|
23770
|
+
"ppp",
|
|
23771
|
+
"stf",
|
|
23772
|
+
"tap",
|
|
23773
|
+
"tun",
|
|
23774
|
+
"utun",
|
|
23775
|
+
"vboxnet",
|
|
23776
|
+
"veth",
|
|
23777
|
+
"virbr",
|
|
23778
|
+
"vmnet"
|
|
23779
|
+
];
|
|
23780
|
+
var VIRTUAL_INTERFACE_KEYWORDS = ["hyper-v", "virtual", "virtualbox", "vmware", "vpn", "wsl"];
|
|
23781
|
+
function isVirtualInterfaceName(name) {
|
|
23782
|
+
const lower = name.toLowerCase();
|
|
23783
|
+
if (VIRTUAL_INTERFACE_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
23784
|
+
return true;
|
|
23785
|
+
return VIRTUAL_INTERFACE_KEYWORDS.some((keyword) => lower.includes(keyword));
|
|
23786
|
+
}
|
|
23787
|
+
function isBenchmarkAddress(address) {
|
|
23788
|
+
return address.startsWith("198.18.") || address.startsWith("198.19.");
|
|
23789
|
+
}
|
|
23790
|
+
function collectListenCandidates(interfaces, skipVirtual) {
|
|
23791
|
+
const candidates = [];
|
|
23792
|
+
for (const [name, infos] of Object.entries(interfaces)) {
|
|
23793
|
+
if (infos === undefined)
|
|
23794
|
+
continue;
|
|
23795
|
+
if (skipVirtual && isVirtualInterfaceName(name))
|
|
23796
|
+
continue;
|
|
23797
|
+
for (const info of infos) {
|
|
23798
|
+
if (info.family !== "IPv4" || info.internal)
|
|
23799
|
+
continue;
|
|
23800
|
+
if (info.address.startsWith("169.254."))
|
|
23801
|
+
continue;
|
|
23802
|
+
if (skipVirtual && isBenchmarkAddress(info.address))
|
|
23803
|
+
continue;
|
|
23804
|
+
candidates.push(info.address);
|
|
23805
|
+
}
|
|
23806
|
+
}
|
|
23807
|
+
return candidates.sort();
|
|
23808
|
+
}
|
|
23809
|
+
function mobileListenCandidates(interfaces) {
|
|
23810
|
+
const source = interfaces ?? networkInterfaces();
|
|
23811
|
+
const filtered = collectListenCandidates(source, true);
|
|
23812
|
+
return filtered.length > 0 ? filtered : collectListenCandidates(source, false);
|
|
23813
|
+
}
|
|
23814
|
+
function prioritizeListenCandidate(candidates, lastConnectedAddress) {
|
|
23815
|
+
if (lastConnectedAddress === null || !candidates.includes(lastConnectedAddress))
|
|
23816
|
+
return [...candidates];
|
|
23817
|
+
return [lastConnectedAddress, ...candidates.filter((candidate) => candidate !== lastConnectedAddress)];
|
|
23818
|
+
}
|
|
23819
|
+
function isIpv4Literal(address) {
|
|
23820
|
+
const parts = address.split(".");
|
|
23821
|
+
return parts.length === 4 && parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
|
|
23822
|
+
}
|
|
23823
|
+
|
|
23752
23824
|
// packages/gateway/src/mobile/pairing.ts
|
|
23753
23825
|
import { createHash as createHash5, randomBytes as randomBytes3 } from "crypto";
|
|
23754
23826
|
var PAIRING_CODE_TTL_MS = 120000;
|
|
@@ -23942,71 +24014,376 @@ class MobilePairing {
|
|
|
23942
24014
|
}
|
|
23943
24015
|
}
|
|
23944
24016
|
|
|
23945
|
-
// packages/gateway/src/mobile/
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
24017
|
+
// packages/gateway/src/mobile/relay.ts
|
|
24018
|
+
import { spawn as nodeSpawn } from "child_process";
|
|
24019
|
+
class RelayError extends Error {
|
|
24020
|
+
code;
|
|
24021
|
+
constructor(code, message) {
|
|
24022
|
+
super(message);
|
|
24023
|
+
this.name = "RelayError";
|
|
24024
|
+
this.code = code;
|
|
24025
|
+
}
|
|
23949
24026
|
}
|
|
23950
|
-
var
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
|
|
23958
|
-
|
|
23959
|
-
|
|
23960
|
-
|
|
23961
|
-
|
|
23962
|
-
|
|
23963
|
-
|
|
23964
|
-
|
|
23965
|
-
|
|
23966
|
-
|
|
23967
|
-
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
|
|
23973
|
-
|
|
24027
|
+
var RELAY_HEARTBEAT_INTERVAL_MS = 2000;
|
|
24028
|
+
var RELAY_WATCHDOG_MS = 8000;
|
|
24029
|
+
var START_TIMEOUT_MS = 20000;
|
|
24030
|
+
var STOP_GRACE_MS = 1000;
|
|
24031
|
+
var STDERR_TAIL_BYTES = 2000;
|
|
24032
|
+
var RELAY_SOURCE = `using System;
|
|
24033
|
+
using System.Diagnostics;
|
|
24034
|
+
using System.Net;
|
|
24035
|
+
using System.Net.Sockets;
|
|
24036
|
+
using System.Threading;
|
|
24037
|
+
|
|
24038
|
+
public static class NekoRelay
|
|
24039
|
+
{
|
|
24040
|
+
static readonly Stopwatch Clock = Stopwatch.StartNew();
|
|
24041
|
+
static long lastBeatMs = 0;
|
|
24042
|
+
|
|
24043
|
+
static void Touch() { Interlocked.Exchange(ref lastBeatMs, Clock.ElapsedMilliseconds); }
|
|
24044
|
+
static string Flat(string text) { return text.Replace("\\r", " ").Replace("\\n", " "); }
|
|
24045
|
+
static void Close(TcpClient client) { if (client != null) { try { client.Close(); } catch (Exception) { } } }
|
|
24046
|
+
|
|
24047
|
+
static void Copy(TcpClient from, TcpClient to)
|
|
24048
|
+
{
|
|
24049
|
+
byte[] buffer = new byte[16384];
|
|
24050
|
+
try
|
|
24051
|
+
{
|
|
24052
|
+
System.IO.Stream source = from.GetStream();
|
|
24053
|
+
System.IO.Stream sink = to.GetStream();
|
|
24054
|
+
int count;
|
|
24055
|
+
while ((count = source.Read(buffer, 0, buffer.Length)) > 0) { sink.Write(buffer, 0, count); }
|
|
24056
|
+
}
|
|
24057
|
+
catch (Exception) { }
|
|
24058
|
+
try { to.Client.Shutdown(SocketShutdown.Send); } catch (Exception) { }
|
|
24059
|
+
}
|
|
24060
|
+
|
|
24061
|
+
static void Pump(object state)
|
|
24062
|
+
{
|
|
24063
|
+
object[] args = (object[])state;
|
|
24064
|
+
TcpClient client = (TcpClient)args[0];
|
|
24065
|
+
TcpClient target = null;
|
|
24066
|
+
try
|
|
24067
|
+
{
|
|
24068
|
+
target = new TcpClient();
|
|
24069
|
+
target.NoDelay = true;
|
|
24070
|
+
client.NoDelay = true;
|
|
24071
|
+
target.Connect((string)args[1], (int)args[2]);
|
|
24072
|
+
Thread upstream = new Thread(delegate() { Copy(client, target); });
|
|
24073
|
+
upstream.IsBackground = true;
|
|
24074
|
+
upstream.Start();
|
|
24075
|
+
Copy(target, client);
|
|
24076
|
+
}
|
|
24077
|
+
catch (Exception) { }
|
|
24078
|
+
finally { Close(client); Close(target); }
|
|
24079
|
+
}
|
|
24080
|
+
|
|
24081
|
+
public static void Run(string listenIp, int listenPort, string targetIp, int targetPort, int watchdogMs)
|
|
24082
|
+
{
|
|
24083
|
+
TcpListener listener = new TcpListener(IPAddress.Parse(listenIp), listenPort);
|
|
24084
|
+
listener.ExclusiveAddressUse = true;
|
|
24085
|
+
try { listener.Start(128); }
|
|
24086
|
+
catch (SocketException e)
|
|
24087
|
+
{
|
|
24088
|
+
string code = (e.SocketErrorCode == SocketError.AddressAlreadyInUse || e.SocketErrorCode == SocketError.AccessDenied) ? "port_in_use" : "listen_failed";
|
|
24089
|
+
Console.Out.WriteLine("ERROR " + code + " " + Flat(e.Message));
|
|
24090
|
+
Console.Out.Flush();
|
|
24091
|
+
Environment.Exit(2);
|
|
24092
|
+
}
|
|
24093
|
+
try
|
|
24094
|
+
{
|
|
24095
|
+
TcpClient probe = new TcpClient();
|
|
24096
|
+
IAsyncResult pending = probe.BeginConnect(targetIp, targetPort, null, null);
|
|
24097
|
+
if (!pending.AsyncWaitHandle.WaitOne(3000)) { throw new TimeoutException("connect timed out"); }
|
|
24098
|
+
probe.EndConnect(pending);
|
|
24099
|
+
probe.Close();
|
|
24100
|
+
}
|
|
24101
|
+
catch (Exception e)
|
|
24102
|
+
{
|
|
24103
|
+
Console.Out.WriteLine("ERROR target_unreachable " + Flat(e.Message));
|
|
24104
|
+
Console.Out.Flush();
|
|
24105
|
+
Environment.Exit(3);
|
|
24106
|
+
}
|
|
24107
|
+
Touch();
|
|
24108
|
+
Thread input = new Thread(delegate()
|
|
24109
|
+
{
|
|
24110
|
+
try { while (Console.In.ReadLine() != null) { Touch(); } } catch (Exception) { }
|
|
24111
|
+
Environment.Exit(0);
|
|
24112
|
+
});
|
|
24113
|
+
input.IsBackground = true;
|
|
24114
|
+
input.Start();
|
|
24115
|
+
Thread watchdog = new Thread(delegate()
|
|
24116
|
+
{
|
|
24117
|
+
while (true)
|
|
24118
|
+
{
|
|
24119
|
+
Thread.Sleep(1000);
|
|
24120
|
+
if (Clock.ElapsedMilliseconds - Interlocked.Read(ref lastBeatMs) > watchdogMs) { Environment.Exit(0); }
|
|
24121
|
+
}
|
|
24122
|
+
});
|
|
24123
|
+
watchdog.IsBackground = true;
|
|
24124
|
+
watchdog.Start();
|
|
24125
|
+
Console.Out.WriteLine("READY");
|
|
24126
|
+
Console.Out.Flush();
|
|
24127
|
+
while (true)
|
|
24128
|
+
{
|
|
24129
|
+
TcpClient accepted;
|
|
24130
|
+
try { accepted = listener.AcceptTcpClient(); }
|
|
24131
|
+
catch (Exception) { Environment.Exit(4); return; }
|
|
24132
|
+
ThreadPool.QueueUserWorkItem(Pump, new object[] { accepted, targetIp, targetPort });
|
|
24133
|
+
}
|
|
24134
|
+
}
|
|
24135
|
+
}`;
|
|
24136
|
+
function assertPort2(port, label) {
|
|
24137
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
24138
|
+
throw new Error(`relay ${label} port is invalid`);
|
|
23974
24139
|
}
|
|
23975
|
-
function
|
|
23976
|
-
|
|
24140
|
+
function buildRelayScript(endpoints, watchdogMs = RELAY_WATCHDOG_MS) {
|
|
24141
|
+
if (!isIpv4Literal(endpoints.listenAddress) || !isIpv4Literal(endpoints.targetAddress)) {
|
|
24142
|
+
throw new Error("relay addresses must be IPv4 literals");
|
|
24143
|
+
}
|
|
24144
|
+
assertPort2(endpoints.listenPort, "listen");
|
|
24145
|
+
assertPort2(endpoints.targetPort, "target");
|
|
24146
|
+
if (!Number.isInteger(watchdogMs) || watchdogMs < 1000)
|
|
24147
|
+
throw new Error("relay watchdog is invalid");
|
|
24148
|
+
return [
|
|
24149
|
+
"$ErrorActionPreference='Stop'",
|
|
24150
|
+
`$src=@'
|
|
24151
|
+
${RELAY_SOURCE}
|
|
24152
|
+
'@`,
|
|
24153
|
+
"Add-Type -TypeDefinition $src -Language CSharp",
|
|
24154
|
+
`[NekoRelay]::Run('${endpoints.listenAddress}',${endpoints.listenPort},'${endpoints.targetAddress}',${endpoints.targetPort},${watchdogMs})`
|
|
24155
|
+
].join(`
|
|
24156
|
+
`);
|
|
23977
24157
|
}
|
|
23978
|
-
function
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
24158
|
+
function encodeRelayCommand(script) {
|
|
24159
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
24160
|
+
}
|
|
24161
|
+
function describeExit(child) {
|
|
24162
|
+
return child.signalCode !== null ? `signal ${child.signalCode}` : `exit code ${child.exitCode}`;
|
|
24163
|
+
}
|
|
24164
|
+
async function startWindowsRelay(options) {
|
|
24165
|
+
const watchdogMs = options.watchdogMs ?? RELAY_WATCHDOG_MS;
|
|
24166
|
+
const script = buildRelayScript(options, watchdogMs);
|
|
24167
|
+
const spawn = options.spawn ?? nodeSpawn;
|
|
24168
|
+
const child = spawn(options.powershell, ["-NoProfile", "-NonInteractive", "-EncodedCommand", encodeRelayCommand(script)], { stdio: ["pipe", "pipe", "pipe"], windowsHide: true });
|
|
24169
|
+
let stderrTail = "";
|
|
24170
|
+
child.stderr?.on("data", (chunk) => {
|
|
24171
|
+
stderrTail = (stderrTail + chunk.toString("utf8")).slice(-STDERR_TAIL_BYTES);
|
|
24172
|
+
});
|
|
24173
|
+
child.stdin?.on("error", () => {});
|
|
24174
|
+
const exited = new Promise((resolve) => {
|
|
24175
|
+
child.once("error", (error) => resolve(`spawn error: ${error.message}`));
|
|
24176
|
+
child.once("exit", () => resolve(describeExit(child)));
|
|
24177
|
+
});
|
|
24178
|
+
let heartbeat;
|
|
24179
|
+
let stopping;
|
|
24180
|
+
const stop = () => {
|
|
24181
|
+
stopping ??= (async () => {
|
|
24182
|
+
clearInterval(heartbeat);
|
|
24183
|
+
child.stdin?.end();
|
|
24184
|
+
const grace = options.stopGraceMs ?? STOP_GRACE_MS;
|
|
24185
|
+
const done = await Promise.race([exited.then(() => true), delay2(grace).then(() => false)]);
|
|
24186
|
+
if (!done) {
|
|
24187
|
+
child.kill("SIGKILL");
|
|
24188
|
+
await Promise.race([exited, delay2(grace)]);
|
|
24189
|
+
}
|
|
24190
|
+
})();
|
|
24191
|
+
return stopping;
|
|
24192
|
+
};
|
|
24193
|
+
const ready = new Promise((resolve, reject) => {
|
|
24194
|
+
let buffer = "";
|
|
24195
|
+
const timer = setTimeout(() => reject(new RelayError("timeout", "Windows relay did not become ready in time")), options.startTimeoutMs ?? START_TIMEOUT_MS);
|
|
24196
|
+
const finish = (action) => {
|
|
24197
|
+
clearTimeout(timer);
|
|
24198
|
+
action();
|
|
24199
|
+
};
|
|
24200
|
+
child.stdout?.on("data", (chunk) => {
|
|
24201
|
+
buffer += chunk.toString("utf8");
|
|
24202
|
+
for (;; ) {
|
|
24203
|
+
const newline = buffer.indexOf(`
|
|
24204
|
+
`);
|
|
24205
|
+
if (newline < 0)
|
|
24206
|
+
break;
|
|
24207
|
+
const line = buffer.slice(0, newline).trim();
|
|
24208
|
+
buffer = buffer.slice(newline + 1);
|
|
24209
|
+
if (line === "READY")
|
|
24210
|
+
finish(resolve);
|
|
24211
|
+
else if (line.startsWith("ERROR ")) {
|
|
24212
|
+
const [, code = "start_failed", ...rest] = line.split(" ");
|
|
24213
|
+
const known = ["port_in_use", "listen_failed", "target_unreachable"];
|
|
24214
|
+
const mapped = known.find((item) => item === code) ?? "start_failed";
|
|
24215
|
+
finish(() => reject(new RelayError(mapped, rest.join(" ") || code)));
|
|
24216
|
+
}
|
|
24217
|
+
}
|
|
24218
|
+
});
|
|
24219
|
+
exited.then((reason) => {
|
|
24220
|
+
const detail = stderrTail.trim();
|
|
24221
|
+
finish(() => reject(new RelayError("start_failed", `Windows relay exited before it was ready (${reason})${detail === "" ? "" : `: ${detail}`}`)));
|
|
24222
|
+
});
|
|
24223
|
+
});
|
|
24224
|
+
try {
|
|
24225
|
+
await ready;
|
|
24226
|
+
} catch (error) {
|
|
24227
|
+
await stop();
|
|
24228
|
+
throw error;
|
|
24229
|
+
}
|
|
24230
|
+
heartbeat = setInterval(() => {
|
|
24231
|
+
child.stdin?.write(`
|
|
24232
|
+
`);
|
|
24233
|
+
}, options.heartbeatMs ?? RELAY_HEARTBEAT_INTERVAL_MS);
|
|
24234
|
+
heartbeat.unref?.();
|
|
24235
|
+
return { exited, stop };
|
|
24236
|
+
}
|
|
24237
|
+
function delay2(ms) {
|
|
24238
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
24239
|
+
}
|
|
24240
|
+
|
|
24241
|
+
// packages/gateway/src/mobile/wsl.ts
|
|
24242
|
+
import { execFile } from "child_process";
|
|
24243
|
+
import { accessSync as accessSync2, constants as constants2, readFileSync as readFileSync2 } from "fs";
|
|
24244
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
24245
|
+
import { delimiter, join as join8 } from "path";
|
|
24246
|
+
var POWERSHELL_FALLBACK = "/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe";
|
|
24247
|
+
var defaultProbeDeps = {
|
|
24248
|
+
platform: process.platform,
|
|
24249
|
+
env: process.env,
|
|
24250
|
+
readProcVersion() {
|
|
24251
|
+
try {
|
|
24252
|
+
return readFileSync2("/proc/version", "utf8");
|
|
24253
|
+
} catch {
|
|
24254
|
+
return;
|
|
24255
|
+
}
|
|
24256
|
+
},
|
|
24257
|
+
isExecutable(path) {
|
|
24258
|
+
try {
|
|
24259
|
+
accessSync2(path, constants2.X_OK);
|
|
24260
|
+
return true;
|
|
24261
|
+
} catch {
|
|
24262
|
+
return false;
|
|
24263
|
+
}
|
|
24264
|
+
}
|
|
24265
|
+
};
|
|
24266
|
+
function detectWsl(overrides = {}) {
|
|
24267
|
+
const deps = { ...defaultProbeDeps, ...overrides };
|
|
24268
|
+
if (deps.platform !== "linux")
|
|
24269
|
+
return;
|
|
24270
|
+
const { env } = deps;
|
|
24271
|
+
const version = deps.readProcVersion() ?? "";
|
|
24272
|
+
const inWsl = (env.WSL_DISTRO_NAME ?? "") !== "" || (env.WSL_INTEROP ?? "") !== "" || /microsoft|wsl/i.test(version);
|
|
24273
|
+
if (!inWsl)
|
|
24274
|
+
return;
|
|
24275
|
+
const candidates = [
|
|
24276
|
+
...(env.PATH ?? "").split(delimiter).filter((dir) => dir !== "").map((dir) => join8(dir, "powershell.exe")),
|
|
24277
|
+
POWERSHELL_FALLBACK
|
|
24278
|
+
];
|
|
24279
|
+
return { powershell: candidates.find((candidate) => deps.isExecutable(candidate)) };
|
|
24280
|
+
}
|
|
24281
|
+
var WINDOWS_ADDRESSES_SCRIPT = [
|
|
24282
|
+
"$ErrorActionPreference='Stop'",
|
|
24283
|
+
"$r=@()",
|
|
24284
|
+
"foreach($n in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()){",
|
|
24285
|
+
"if($n.OperationalStatus -ne 'Up'){continue}",
|
|
24286
|
+
"if($n.NetworkInterfaceType -eq 'Loopback' -or $n.NetworkInterfaceType -eq 'Tunnel'){continue}",
|
|
24287
|
+
"foreach($a in $n.GetIPProperties().UnicastAddresses){",
|
|
24288
|
+
"if($a.Address.AddressFamily -eq 'InterNetwork'){$r+=[pscustomobject]@{name=$n.Name;address=$a.Address.IPAddressToString}}",
|
|
24289
|
+
"}}",
|
|
24290
|
+
"ConvertTo-Json -InputObject @($r) -Compress"
|
|
24291
|
+
].join(`
|
|
24292
|
+
`);
|
|
24293
|
+
var WSL_ADAPTER_PATTERN = /wsl|hyper-v|vethernet/i;
|
|
24294
|
+
var runPowerShell = (powershell, script, timeoutMs) => new Promise((resolve, reject) => {
|
|
24295
|
+
execFile(powershell, ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: timeoutMs, windowsHide: true, maxBuffer: 1024 * 1024, encoding: "utf8" }, (error, stdout) => error ? reject(error) : resolve(stdout));
|
|
24296
|
+
});
|
|
24297
|
+
function parseWindowsInterfaces(output) {
|
|
24298
|
+
const text = output.replace(/^\uFEFF/, "").trim();
|
|
24299
|
+
if (text === "")
|
|
24300
|
+
return [];
|
|
24301
|
+
const parsed = JSON.parse(text);
|
|
24302
|
+
const items = Array.isArray(parsed) ? parsed : [parsed];
|
|
24303
|
+
const result = [];
|
|
24304
|
+
for (const item of items) {
|
|
24305
|
+
if (typeof item !== "object" || item === null)
|
|
23984
24306
|
continue;
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
if (info.address.startsWith("169.254."))
|
|
23989
|
-
continue;
|
|
23990
|
-
if (skipVirtual && isBenchmarkAddress(info.address))
|
|
23991
|
-
continue;
|
|
23992
|
-
candidates.push(info.address);
|
|
24307
|
+
const { name, address } = item;
|
|
24308
|
+
if (typeof name === "string" && typeof address === "string" && isIpv4Literal(address)) {
|
|
24309
|
+
result.push({ name, address });
|
|
23993
24310
|
}
|
|
23994
24311
|
}
|
|
23995
|
-
return
|
|
24312
|
+
return result;
|
|
23996
24313
|
}
|
|
23997
|
-
function
|
|
23998
|
-
const
|
|
23999
|
-
const
|
|
24000
|
-
|
|
24314
|
+
function toInterfaceDict(interfaces) {
|
|
24315
|
+
const dict = {};
|
|
24316
|
+
for (const { name, address } of interfaces) {
|
|
24317
|
+
const list = dict[name] ?? [];
|
|
24318
|
+
dict[name] = list;
|
|
24319
|
+
list.push({
|
|
24320
|
+
address,
|
|
24321
|
+
netmask: "255.255.255.0",
|
|
24322
|
+
family: "IPv4",
|
|
24323
|
+
mac: "00:00:00:00:00:00",
|
|
24324
|
+
internal: false,
|
|
24325
|
+
cidr: `${address}/24`
|
|
24326
|
+
});
|
|
24327
|
+
}
|
|
24328
|
+
return dict;
|
|
24001
24329
|
}
|
|
24002
|
-
|
|
24003
|
-
|
|
24004
|
-
|
|
24005
|
-
|
|
24330
|
+
|
|
24331
|
+
class WindowsAddressDiscovery {
|
|
24332
|
+
powershell;
|
|
24333
|
+
run;
|
|
24334
|
+
cacheMs;
|
|
24335
|
+
timeoutMs;
|
|
24336
|
+
now;
|
|
24337
|
+
cached;
|
|
24338
|
+
pending;
|
|
24339
|
+
constructor(options) {
|
|
24340
|
+
this.powershell = options.powershell;
|
|
24341
|
+
this.run = options.run ?? runPowerShell;
|
|
24342
|
+
this.cacheMs = options.cacheMs ?? 15000;
|
|
24343
|
+
this.timeoutMs = options.timeoutMs ?? 1e4;
|
|
24344
|
+
this.now = options.now ?? Date.now;
|
|
24345
|
+
}
|
|
24346
|
+
invalidate() {
|
|
24347
|
+
this.cached = undefined;
|
|
24348
|
+
}
|
|
24349
|
+
candidates() {
|
|
24350
|
+
if (this.cached !== undefined && this.now() - this.cached.at < this.cacheMs) {
|
|
24351
|
+
return Promise.resolve([...this.cached.addresses]);
|
|
24352
|
+
}
|
|
24353
|
+
this.pending ??= this.read().finally(() => {
|
|
24354
|
+
this.pending = undefined;
|
|
24355
|
+
});
|
|
24356
|
+
return this.pending.then((addresses) => [...addresses]);
|
|
24357
|
+
}
|
|
24358
|
+
async read() {
|
|
24359
|
+
const output = await this.run(this.powershell, WINDOWS_ADDRESSES_SCRIPT, this.timeoutMs);
|
|
24360
|
+
const usable = parseWindowsInterfaces(output).filter((item) => !WSL_ADAPTER_PATTERN.test(item.name));
|
|
24361
|
+
const addresses = mobileListenCandidates(toInterfaceDict(usable));
|
|
24362
|
+
this.cached = { at: this.now(), addresses };
|
|
24363
|
+
return addresses;
|
|
24364
|
+
}
|
|
24006
24365
|
}
|
|
24007
|
-
function
|
|
24008
|
-
const
|
|
24009
|
-
|
|
24366
|
+
function wslOwnAddresses(interfaces = networkInterfaces2()) {
|
|
24367
|
+
const result = [];
|
|
24368
|
+
for (const infos of Object.values(interfaces)) {
|
|
24369
|
+
for (const info of infos ?? []) {
|
|
24370
|
+
if (info.family === "IPv4" && !info.internal)
|
|
24371
|
+
result.push(info.address);
|
|
24372
|
+
}
|
|
24373
|
+
}
|
|
24374
|
+
return result;
|
|
24375
|
+
}
|
|
24376
|
+
function primaryWslAddress(interfaces = networkInterfaces2()) {
|
|
24377
|
+
const eth0 = (interfaces.eth0 ?? []).find((info) => info.family === "IPv4" && !info.internal);
|
|
24378
|
+
return eth0?.address ?? wslOwnAddresses(interfaces)[0];
|
|
24379
|
+
}
|
|
24380
|
+
|
|
24381
|
+
// packages/gateway/src/mobile/listener.ts
|
|
24382
|
+
var RELAY_PORT_RETRY_MS = 1e4;
|
|
24383
|
+
var RELAY_PORT_RETRY_INTERVAL_MS = 1000;
|
|
24384
|
+
function defaultServerName() {
|
|
24385
|
+
const name = hostname().trim();
|
|
24386
|
+
return (name === "" ? "neko-computer" : name).slice(0, 64);
|
|
24010
24387
|
}
|
|
24011
24388
|
function assertListenAddress(address) {
|
|
24012
24389
|
if (!isIpv4Literal(address))
|
|
@@ -24018,6 +24395,23 @@ function assertListenAddress(address) {
|
|
|
24018
24395
|
function toError(error) {
|
|
24019
24396
|
return error instanceof Error ? error : new Error(String(error));
|
|
24020
24397
|
}
|
|
24398
|
+
var 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";
|
|
24399
|
+
function describeRelayError(error, address, port, bindAddress) {
|
|
24400
|
+
if (!(error instanceof RelayError))
|
|
24401
|
+
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${toMessage(error)}`;
|
|
24402
|
+
switch (error.code) {
|
|
24403
|
+
case "port_in_use":
|
|
24404
|
+
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`;
|
|
24405
|
+
case "target_unreachable":
|
|
24406
|
+
return `Windows \u65E0\u6CD5\u8FDE\u63A5\u5230 WSL \u5185\u7684\u76D1\u542C\u5730\u5740 ${bindAddress}:${port}\uFF08${error.message}\uFF09\u3002`;
|
|
24407
|
+
case "timeout":
|
|
24408
|
+
return "Windows \u4E2D\u7EE7\u542F\u52A8\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
24409
|
+
case "listen_failed":
|
|
24410
|
+
return `Windows \u4E2D\u7EE7\u65E0\u6CD5\u5728 ${address}:${port} \u4E0A\u76D1\u542C\uFF1A${error.message}`;
|
|
24411
|
+
default:
|
|
24412
|
+
return `Windows \u4E2D\u7EE7\u542F\u52A8\u5931\u8D25\uFF1A${error.message}`;
|
|
24413
|
+
}
|
|
24414
|
+
}
|
|
24021
24415
|
function isMobilePrincipal2(principal) {
|
|
24022
24416
|
return typeof principal === "object" && principal !== null && principal.kind === "mobile";
|
|
24023
24417
|
}
|
|
@@ -24047,7 +24441,26 @@ class MobileGateway {
|
|
|
24047
24441
|
stateValue;
|
|
24048
24442
|
queue = Promise.resolve();
|
|
24049
24443
|
closing;
|
|
24050
|
-
|
|
24444
|
+
relay;
|
|
24445
|
+
wslEnvironment;
|
|
24446
|
+
windowsAddresses;
|
|
24447
|
+
startRelay;
|
|
24448
|
+
localInterfaces;
|
|
24449
|
+
relayPortRetryMs;
|
|
24450
|
+
relayPortRetryIntervalMs;
|
|
24451
|
+
constructor(serverId, serverName, onError, chord, store, storeFailure, initial, network = {}) {
|
|
24452
|
+
this.wslEnvironment = network.wsl === null ? undefined : network.wsl ?? detectWsl();
|
|
24453
|
+
const powershell = this.wslEnvironment?.powershell;
|
|
24454
|
+
if (network.windowsCandidates !== undefined)
|
|
24455
|
+
this.windowsAddresses = network.windowsCandidates;
|
|
24456
|
+
else if (powershell !== undefined) {
|
|
24457
|
+
const discovery = new WindowsAddressDiscovery({ powershell });
|
|
24458
|
+
this.windowsAddresses = () => discovery.candidates();
|
|
24459
|
+
}
|
|
24460
|
+
this.startRelay = network.startRelay ?? startWindowsRelay;
|
|
24461
|
+
this.localInterfaces = network.localInterfaces;
|
|
24462
|
+
this.relayPortRetryMs = network.relayPortRetryMs ?? RELAY_PORT_RETRY_MS;
|
|
24463
|
+
this.relayPortRetryIntervalMs = network.relayPortRetryIntervalMs ?? RELAY_PORT_RETRY_INTERVAL_MS;
|
|
24051
24464
|
this.serverId = serverId;
|
|
24052
24465
|
this.serverName = serverName;
|
|
24053
24466
|
this.onError = onError;
|
|
@@ -24064,7 +24477,7 @@ class MobileGateway {
|
|
|
24064
24477
|
} catch (error) {
|
|
24065
24478
|
storeFailure = toMessage(error);
|
|
24066
24479
|
}
|
|
24067
|
-
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}` });
|
|
24480
|
+
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);
|
|
24068
24481
|
gateway.runtime = options.runtime;
|
|
24069
24482
|
if (store !== undefined) {
|
|
24070
24483
|
const config = store.getState();
|
|
@@ -24136,12 +24549,30 @@ class MobileGateway {
|
|
|
24136
24549
|
}
|
|
24137
24550
|
return false;
|
|
24138
24551
|
}
|
|
24139
|
-
adminState() {
|
|
24552
|
+
async adminState() {
|
|
24140
24553
|
const store = this.requireStore();
|
|
24141
24554
|
const config = store.getState();
|
|
24555
|
+
let candidates;
|
|
24556
|
+
let wsl = null;
|
|
24557
|
+
if (this.wslEnvironment === undefined) {
|
|
24558
|
+
candidates = mobileListenCandidates();
|
|
24559
|
+
} else {
|
|
24560
|
+
candidates = [];
|
|
24561
|
+
let error = null;
|
|
24562
|
+
if (this.windowsAddresses === undefined)
|
|
24563
|
+
error = WSL_INTEROP_UNAVAILABLE;
|
|
24564
|
+
else {
|
|
24565
|
+
try {
|
|
24566
|
+
candidates = await this.windowsAddresses();
|
|
24567
|
+
} catch (caught) {
|
|
24568
|
+
error = `\u8BFB\u53D6 Windows \u5C40\u57DF\u7F51\u5730\u5740\u5931\u8D25\uFF1A${toMessage(caught)}`;
|
|
24569
|
+
}
|
|
24570
|
+
}
|
|
24571
|
+
wsl = { interop: this.windowsAddresses !== undefined, error };
|
|
24572
|
+
}
|
|
24142
24573
|
return {
|
|
24143
24574
|
state: this.stateValue,
|
|
24144
|
-
candidates: prioritizeListenCandidate(
|
|
24575
|
+
candidates: prioritizeListenCandidate(candidates, config.lastConnectedAddress),
|
|
24145
24576
|
port: config.port,
|
|
24146
24577
|
certificate: this.certificate === undefined ? null : { fingerprint: this.certificate.fingerprint, expiresAt: this.certificate.expiresAt },
|
|
24147
24578
|
devices: config.devices.map((device) => ({
|
|
@@ -24151,7 +24582,8 @@ class MobileGateway {
|
|
|
24151
24582
|
lastSeenAt: device.lastSeenAt,
|
|
24152
24583
|
online: this.deviceOnline(device.deviceId)
|
|
24153
24584
|
})),
|
|
24154
|
-
lastConnectedAddress: config.lastConnectedAddress
|
|
24585
|
+
lastConnectedAddress: config.lastConnectedAddress,
|
|
24586
|
+
wsl
|
|
24155
24587
|
};
|
|
24156
24588
|
}
|
|
24157
24589
|
async regenerateCertificate() {
|
|
@@ -24231,9 +24663,10 @@ class MobileGateway {
|
|
|
24231
24663
|
if (store?.getState().devices.some((device) => device.deviceId === auth.deviceId)) {
|
|
24232
24664
|
store.touchDevice(auth.deviceId, Date.now()).catch((error) => this.onError?.(toError(error)));
|
|
24233
24665
|
}
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24666
|
+
const learned = this.advertisedAddress(localAddress);
|
|
24667
|
+
if (store !== undefined && typeof learned === "string" && isIpv4Literal(learned)) {
|
|
24668
|
+
if (store.getState().lastConnectedAddress !== learned) {
|
|
24669
|
+
store.rememberConnectedAddress(learned).catch((error) => this.onError?.(toError(error)));
|
|
24237
24670
|
}
|
|
24238
24671
|
}
|
|
24239
24672
|
const clientHost = createHost2({
|
|
@@ -24317,6 +24750,13 @@ class MobileGateway {
|
|
|
24317
24750
|
for (const connection of this.connections.values())
|
|
24318
24751
|
connection.sendNotification(method, params);
|
|
24319
24752
|
}
|
|
24753
|
+
advertisedAddress(localAddress) {
|
|
24754
|
+
const state = this.stateValue;
|
|
24755
|
+
if (state.status === "listening" && state.mode === "wsl-relay" && localAddress === state.bindAddress) {
|
|
24756
|
+
return state.address;
|
|
24757
|
+
}
|
|
24758
|
+
return localAddress;
|
|
24759
|
+
}
|
|
24320
24760
|
requireStore() {
|
|
24321
24761
|
if (this.storeHandle === undefined)
|
|
24322
24762
|
throw new Error(`mobile store is unavailable: ${this.storeFailure ?? "unknown error"}`);
|
|
@@ -24329,6 +24769,7 @@ class MobileGateway {
|
|
|
24329
24769
|
await this.stopListening();
|
|
24330
24770
|
this.stateValue = { status: "starting" };
|
|
24331
24771
|
try {
|
|
24772
|
+
const plan = await this.planListen(address);
|
|
24332
24773
|
const certificate = await this.certificateValue();
|
|
24333
24774
|
const server = createServer({ cert: certificate.certPem, key: certificate.keyPem }, (request, response) => {
|
|
24334
24775
|
this.handleRequest(request, response);
|
|
@@ -24356,11 +24797,10 @@ class MobileGateway {
|
|
|
24356
24797
|
};
|
|
24357
24798
|
server.once("error", onFailure);
|
|
24358
24799
|
server.once("listening", onListening);
|
|
24359
|
-
server.listen(port,
|
|
24800
|
+
server.listen(port, plan.bindAddress);
|
|
24360
24801
|
});
|
|
24361
24802
|
this.server = server;
|
|
24362
24803
|
this.wss = wss;
|
|
24363
|
-
this.stateValue = { status: "listening", address, port };
|
|
24364
24804
|
server.on("error", (error) => {
|
|
24365
24805
|
this.stateValue = { status: "error", error: toMessage(error) };
|
|
24366
24806
|
this.onError?.(error);
|
|
@@ -24368,6 +24808,9 @@ class MobileGateway {
|
|
|
24368
24808
|
return;
|
|
24369
24809
|
});
|
|
24370
24810
|
});
|
|
24811
|
+
if (plan.mode === "wsl-relay")
|
|
24812
|
+
await this.startRelayFor(address, port, plan.bindAddress);
|
|
24813
|
+
this.stateValue = plan.mode === "wsl-relay" ? { status: "listening", address, port, mode: "wsl-relay", bindAddress: plan.bindAddress } : { status: "listening", address, port };
|
|
24371
24814
|
} catch (error) {
|
|
24372
24815
|
await this.stopListening().catch(() => {
|
|
24373
24816
|
return;
|
|
@@ -24376,6 +24819,66 @@ class MobileGateway {
|
|
|
24376
24819
|
throw error;
|
|
24377
24820
|
}
|
|
24378
24821
|
}
|
|
24822
|
+
async planListen(address) {
|
|
24823
|
+
if (this.wslEnvironment === undefined)
|
|
24824
|
+
return { mode: "direct", bindAddress: address };
|
|
24825
|
+
if (this.windowsAddresses === undefined)
|
|
24826
|
+
throw new Error(WSL_INTEROP_UNAVAILABLE);
|
|
24827
|
+
let candidates;
|
|
24828
|
+
try {
|
|
24829
|
+
candidates = await this.windowsAddresses();
|
|
24830
|
+
} catch (error) {
|
|
24831
|
+
throw new Error(`\u8BFB\u53D6 Windows \u5C40\u57DF\u7F51\u5730\u5740\u5931\u8D25\uFF1A${toMessage(error)}`);
|
|
24832
|
+
}
|
|
24833
|
+
if (!candidates.includes(address)) {
|
|
24834
|
+
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`);
|
|
24835
|
+
}
|
|
24836
|
+
const interfaces = this.localInterfaces?.();
|
|
24837
|
+
if (wslOwnAddresses(interfaces).includes(address))
|
|
24838
|
+
return { mode: "direct", bindAddress: address };
|
|
24839
|
+
const bindAddress = primaryWslAddress(interfaces);
|
|
24840
|
+
if (bindAddress === undefined)
|
|
24841
|
+
throw new Error("WSL \u5185\u6CA1\u6709\u53EF\u7528\u7684\u7F51\u7EDC\u5730\u5740\uFF0C\u65E0\u6CD5\u5EFA\u7ACB\u624B\u673A\u5165\u53E3\u3002");
|
|
24842
|
+
return { mode: "wsl-relay", bindAddress };
|
|
24843
|
+
}
|
|
24844
|
+
async startRelayFor(address, port, bindAddress) {
|
|
24845
|
+
const deadline = Date.now() + this.relayPortRetryMs;
|
|
24846
|
+
let relay;
|
|
24847
|
+
for (;; ) {
|
|
24848
|
+
try {
|
|
24849
|
+
relay = await this.startRelay({
|
|
24850
|
+
powershell: this.wslEnvironment?.powershell ?? "powershell.exe",
|
|
24851
|
+
listenAddress: address,
|
|
24852
|
+
listenPort: port,
|
|
24853
|
+
targetAddress: bindAddress,
|
|
24854
|
+
targetPort: port
|
|
24855
|
+
});
|
|
24856
|
+
break;
|
|
24857
|
+
} catch (error) {
|
|
24858
|
+
if (error instanceof RelayError && error.code === "port_in_use" && Date.now() + this.relayPortRetryIntervalMs < deadline) {
|
|
24859
|
+
await new Promise((resolve) => setTimeout(resolve, this.relayPortRetryIntervalMs));
|
|
24860
|
+
continue;
|
|
24861
|
+
}
|
|
24862
|
+
throw new Error(describeRelayError(error, address, port, bindAddress));
|
|
24863
|
+
}
|
|
24864
|
+
}
|
|
24865
|
+
this.relay = relay;
|
|
24866
|
+
relay.exited.then((reason) => this.onRelayExited(relay, reason));
|
|
24867
|
+
}
|
|
24868
|
+
onRelayExited(relay, reason) {
|
|
24869
|
+
this.enqueue(async () => {
|
|
24870
|
+
if (this.relay !== relay)
|
|
24871
|
+
return;
|
|
24872
|
+
this.relay = undefined;
|
|
24873
|
+
this.pairingValue?.invalidate();
|
|
24874
|
+
await this.stopListening().catch(() => {
|
|
24875
|
+
return;
|
|
24876
|
+
});
|
|
24877
|
+
const message = `Windows \u4E2D\u7EE7\u610F\u5916\u9000\u51FA\uFF08${reason}\uFF09\uFF0C\u8BF7\u91CD\u65B0\u5F00\u542F\u624B\u673A\u8BBF\u95EE\u3002`;
|
|
24878
|
+
this.stateValue = { status: "error", error: message };
|
|
24879
|
+
this.onError?.(new Error(message));
|
|
24880
|
+
});
|
|
24881
|
+
}
|
|
24379
24882
|
async certificateValue() {
|
|
24380
24883
|
if (this.certificate === undefined) {
|
|
24381
24884
|
this.certificate = await MobileCertificate.load(this.requireStore(), this.serverId);
|
|
@@ -24383,6 +24886,12 @@ class MobileGateway {
|
|
|
24383
24886
|
return this.certificate;
|
|
24384
24887
|
}
|
|
24385
24888
|
async stopListening() {
|
|
24889
|
+
const relay = this.relay;
|
|
24890
|
+
this.relay = undefined;
|
|
24891
|
+
if (relay !== undefined)
|
|
24892
|
+
await relay.stop().catch(() => {
|
|
24893
|
+
return;
|
|
24894
|
+
});
|
|
24386
24895
|
for (const connection of [...this.connections.values()])
|
|
24387
24896
|
connection.close(1001, "listener_stopped");
|
|
24388
24897
|
const server = this.server;
|
|
@@ -24490,9 +24999,9 @@ ${JSON.stringify({ code, message })}`);
|
|
|
24490
24999
|
|
|
24491
25000
|
// packages/gateway/src/port-file.ts
|
|
24492
25001
|
import { readFile as readFile5, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
24493
|
-
import { join as
|
|
25002
|
+
import { join as join9 } from "path";
|
|
24494
25003
|
function gatewayPortFilePath(nekoDir, serverId) {
|
|
24495
|
-
return
|
|
25004
|
+
return join9(nekoDir, `${serverId}.port`);
|
|
24496
25005
|
}
|
|
24497
25006
|
async function writeGatewayPortFile(nekoDir, serverId, wsUrl) {
|
|
24498
25007
|
await writeFile2(gatewayPortFilePath(nekoDir, serverId), `${wsUrl}
|
|
@@ -24516,7 +25025,7 @@ import {
|
|
|
24516
25025
|
// packages/core/src/application-updates.ts
|
|
24517
25026
|
import { mkdir as mkdir3, readFile as readFile6, rename as rename3, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
24518
25027
|
import { homedir as homedir5 } from "os";
|
|
24519
|
-
import { dirname as dirname5, join as
|
|
25028
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
24520
25029
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24521
25030
|
async function abortable(work, signal) {
|
|
24522
25031
|
signal.throwIfAborted();
|
|
@@ -24580,7 +25089,7 @@ class UpdateChecker {
|
|
|
24580
25089
|
return this.#work;
|
|
24581
25090
|
}
|
|
24582
25091
|
get #cachePath() {
|
|
24583
|
-
return this.#options.cachePath ??
|
|
25092
|
+
return this.#options.cachePath ?? join10(homedir5(), ".neko", "update-check.json");
|
|
24584
25093
|
}
|
|
24585
25094
|
async#check(force) {
|
|
24586
25095
|
await this.local();
|
|
@@ -25360,7 +25869,7 @@ function serveSessions(host, runtime, context) {
|
|
|
25360
25869
|
import { createHash as createHash6, randomUUID as randomUUID3 } from "crypto";
|
|
25361
25870
|
import { mkdir as mkdir4, readdir as readdir2, rename as rename4, rm as rm4, stat as stat2, writeFile as writeFile4 } from "fs/promises";
|
|
25362
25871
|
import { homedir as homedir6 } from "os";
|
|
25363
|
-
import { dirname as dirname6, join as
|
|
25872
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
25364
25873
|
|
|
25365
25874
|
// node_modules/fflate/esm/index.mjs
|
|
25366
25875
|
import { createRequire } from "module";
|
|
@@ -25783,8 +26292,8 @@ class SkillMarketError extends Error {
|
|
|
25783
26292
|
var CATALOG_TTL_MS = 60000;
|
|
25784
26293
|
var ZIP_CACHE_MAX = 16;
|
|
25785
26294
|
var MAX_PACKAGE_BYTES = 32 * 1024 * 1024;
|
|
25786
|
-
var userSkillsDir2 = (home) =>
|
|
25787
|
-
var projectSkillsDir = (workspaceRoot) =>
|
|
26295
|
+
var userSkillsDir2 = (home) => join11(home, ".agents", "skills");
|
|
26296
|
+
var projectSkillsDir = (workspaceRoot) => join11(workspaceRoot, ".agents", "skills");
|
|
25788
26297
|
var activeLevelOf = (state) => state.project ? "project" : state.user ? "user" : state.builtin ? "builtin" : null;
|
|
25789
26298
|
var normalizeEntry = (raw) => {
|
|
25790
26299
|
const row = raw ?? {};
|
|
@@ -25918,11 +26427,11 @@ function createSkillMarketClient(options) {
|
|
|
25918
26427
|
} catch {
|
|
25919
26428
|
return;
|
|
25920
26429
|
}
|
|
25921
|
-
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm4(
|
|
26430
|
+
await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm4(join11(parent, n), { recursive: true, force: true }).catch(() => {})));
|
|
25922
26431
|
};
|
|
25923
26432
|
const install = async ({ name, scope, workspaceRoot, overwrite }) => {
|
|
25924
26433
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
25925
|
-
const target =
|
|
26434
|
+
const target = join11(skillsDir, name);
|
|
25926
26435
|
const exists = await stat2(target).catch(() => {
|
|
25927
26436
|
return;
|
|
25928
26437
|
}) !== undefined;
|
|
@@ -25931,10 +26440,10 @@ function createSkillMarketClient(options) {
|
|
|
25931
26440
|
const skill = await detail(name);
|
|
25932
26441
|
const files = unpack(skill, await download(skill));
|
|
25933
26442
|
await sweepTempDirs(skillsDir);
|
|
25934
|
-
const temp =
|
|
26443
|
+
const temp = join11(dirname6(skillsDir), `.tmp-${randomUUID3()}`);
|
|
25935
26444
|
try {
|
|
25936
26445
|
for (const [relative, bytes] of files) {
|
|
25937
|
-
const dest =
|
|
26446
|
+
const dest = join11(temp, ...relative.split("/"));
|
|
25938
26447
|
await mkdir4(dirname6(dest), { recursive: true });
|
|
25939
26448
|
await writeFile4(dest, bytes);
|
|
25940
26449
|
}
|
|
@@ -25947,7 +26456,7 @@ function createSkillMarketClient(options) {
|
|
|
25947
26456
|
}
|
|
25948
26457
|
let shadowedBy;
|
|
25949
26458
|
if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
|
|
25950
|
-
const projectDir =
|
|
26459
|
+
const projectDir = join11(projectSkillsDir(workspaceRoot), name);
|
|
25951
26460
|
if (await stat2(projectDir).catch(() => {
|
|
25952
26461
|
return;
|
|
25953
26462
|
}) !== undefined)
|
|
@@ -25957,7 +26466,7 @@ function createSkillMarketClient(options) {
|
|
|
25957
26466
|
};
|
|
25958
26467
|
const uninstall = async ({ name, scope, workspaceRoot }) => {
|
|
25959
26468
|
const skillsDir = skillsDirFor(scope, workspaceRoot);
|
|
25960
|
-
const target =
|
|
26469
|
+
const target = join11(skillsDir, name);
|
|
25961
26470
|
if (await stat2(target).catch(() => {
|
|
25962
26471
|
return;
|
|
25963
26472
|
}) === undefined)
|
|
@@ -25991,9 +26500,9 @@ function createSkillMarketClient(options) {
|
|
|
25991
26500
|
}
|
|
25992
26501
|
|
|
25993
26502
|
// packages/gateway/src/workspaces.ts
|
|
25994
|
-
import { constants as
|
|
26503
|
+
import { constants as constants3 } from "fs";
|
|
25995
26504
|
import { access, readdir as readdir3, stat as stat3 } from "fs/promises";
|
|
25996
|
-
import { dirname as dirname7, isAbsolute as isAbsolute3, join as
|
|
26505
|
+
import { dirname as dirname7, isAbsolute as isAbsolute3, join as join12 } from "path";
|
|
25997
26506
|
async function browseWorkspaces(options) {
|
|
25998
26507
|
const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
|
|
25999
26508
|
if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
|
|
@@ -26020,13 +26529,13 @@ async function browseWorkspaces(options) {
|
|
|
26020
26529
|
let next = offset;
|
|
26021
26530
|
for (;next < entries.length && directories.length < 100; next++) {
|
|
26022
26531
|
const entry = entries[next];
|
|
26023
|
-
const target =
|
|
26532
|
+
const target = join12(path, entry.name);
|
|
26024
26533
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
26025
26534
|
continue;
|
|
26026
26535
|
try {
|
|
26027
26536
|
if (!(await stat3(target)).isDirectory())
|
|
26028
26537
|
continue;
|
|
26029
|
-
await access(target,
|
|
26538
|
+
await access(target, constants3.R_OK | constants3.X_OK);
|
|
26030
26539
|
directories.push({ name: entry.name, path: target });
|
|
26031
26540
|
} catch {
|
|
26032
26541
|
directories.push({ name: entry.name, path: target, error: "\u76EE\u5F55\u4E0D\u53EF\u8BBF\u95EE" });
|
|
@@ -26505,7 +27014,7 @@ async function startGateway(options = {}) {
|
|
|
26505
27014
|
const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
|
|
26506
27015
|
const nekoDir = dirname9(storagePath);
|
|
26507
27016
|
const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
|
|
26508
|
-
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null :
|
|
27017
|
+
const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join13(nekoDir, `${serverId}.sock`);
|
|
26509
27018
|
const runtime = await createNekoRuntime({
|
|
26510
27019
|
context,
|
|
26511
27020
|
onReport: (error) => options.onError?.(error instanceof Error ? error : new Error(String(error))),
|
|
@@ -26665,7 +27174,7 @@ function serverIdFromSocketPath(socketPath) {
|
|
|
26665
27174
|
return isServerId(candidate) ? candidate : undefined;
|
|
26666
27175
|
}
|
|
26667
27176
|
async function loadOrCreateServerId(nekoDir) {
|
|
26668
|
-
const file =
|
|
27177
|
+
const file = join13(nekoDir, SERVER_ID_FILENAME);
|
|
26669
27178
|
try {
|
|
26670
27179
|
const existing = (await readFile7(file, "utf8")).trim();
|
|
26671
27180
|
if (existing !== "")
|
|
@@ -26678,12 +27187,79 @@ async function loadOrCreateServerId(nekoDir) {
|
|
|
26678
27187
|
return created;
|
|
26679
27188
|
}
|
|
26680
27189
|
|
|
27190
|
+
// packages/gateway/src/shutdown.ts
|
|
27191
|
+
var DEFAULT_SHUTDOWN_TIMEOUT_MS = 5000;
|
|
27192
|
+
function shutdownSignals(platform) {
|
|
27193
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
27194
|
+
if (platform === "win32")
|
|
27195
|
+
signals.push("SIGBREAK");
|
|
27196
|
+
return signals;
|
|
27197
|
+
}
|
|
27198
|
+
function installShutdown(options = {}) {
|
|
27199
|
+
const proc = options.process ?? process;
|
|
27200
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
27201
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
|
|
27202
|
+
const controller = new AbortController;
|
|
27203
|
+
let cleanup;
|
|
27204
|
+
let cleaning = false;
|
|
27205
|
+
let timer;
|
|
27206
|
+
const runCleanup = () => {
|
|
27207
|
+
if (cleaning || cleanup === undefined)
|
|
27208
|
+
return;
|
|
27209
|
+
cleaning = true;
|
|
27210
|
+
cleanup().then(() => exit(0), (error) => {
|
|
27211
|
+
options.log?.(error instanceof Error ? error.message : String(error));
|
|
27212
|
+
exit(1);
|
|
27213
|
+
});
|
|
27214
|
+
};
|
|
27215
|
+
const onSignal = () => {
|
|
27216
|
+
if (controller.signal.aborted) {
|
|
27217
|
+
options.log?.("\u518D\u6B21\u6536\u5230\u7EC8\u6B62\u4FE1\u53F7\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
27218
|
+
exit(130);
|
|
27219
|
+
return;
|
|
27220
|
+
}
|
|
27221
|
+
options.log?.(`
|
|
27222
|
+
\u6B63\u5728\u5173\u95ED\u2026`);
|
|
27223
|
+
controller.abort();
|
|
27224
|
+
timer = setTimeout(() => {
|
|
27225
|
+
options.log?.("\u5173\u95ED\u8D85\u65F6\uFF0C\u5F3A\u5236\u9000\u51FA\u3002");
|
|
27226
|
+
exit(1);
|
|
27227
|
+
}, timeoutMs);
|
|
27228
|
+
timer.unref?.();
|
|
27229
|
+
runCleanup();
|
|
27230
|
+
};
|
|
27231
|
+
const installed = [];
|
|
27232
|
+
for (const signal of shutdownSignals(proc.platform)) {
|
|
27233
|
+
try {
|
|
27234
|
+
proc.on(signal, onSignal);
|
|
27235
|
+
installed.push(signal);
|
|
27236
|
+
} catch {}
|
|
27237
|
+
}
|
|
27238
|
+
return {
|
|
27239
|
+
signal: controller.signal,
|
|
27240
|
+
setCleanup(next) {
|
|
27241
|
+
cleanup = next;
|
|
27242
|
+
if (controller.signal.aborted)
|
|
27243
|
+
runCleanup();
|
|
27244
|
+
},
|
|
27245
|
+
dispose() {
|
|
27246
|
+
clearTimeout(timer);
|
|
27247
|
+
for (const signal of installed)
|
|
27248
|
+
proc.off(signal, onSignal);
|
|
27249
|
+
}
|
|
27250
|
+
};
|
|
27251
|
+
}
|
|
27252
|
+
|
|
26681
27253
|
// packages/gateway/src/main.ts
|
|
26682
27254
|
var argv = process.argv.slice(2);
|
|
26683
27255
|
if (argv.includes("-h") || argv.includes("--help")) {
|
|
26684
27256
|
console.log(GATEWAY_USAGE);
|
|
26685
27257
|
process.exit(0);
|
|
26686
27258
|
}
|
|
27259
|
+
var shutdown = installShutdown({
|
|
27260
|
+
log: (message) => console.log(message),
|
|
27261
|
+
exit: (code) => process.exit(code)
|
|
27262
|
+
});
|
|
26687
27263
|
var options = parseGatewayArgs(argv);
|
|
26688
27264
|
var gateway = await startGateway({
|
|
26689
27265
|
...options,
|
|
@@ -26695,15 +27271,4 @@ if (gateway.socketPath !== undefined)
|
|
|
26695
27271
|
if (gateway.wsUrl !== undefined)
|
|
26696
27272
|
console.log(` WebSocket: ${gateway.wsUrl}`);
|
|
26697
27273
|
console.log("\u6309 Ctrl+C \u505C\u6B62\u3002");
|
|
26698
|
-
|
|
26699
|
-
var stop = () => {
|
|
26700
|
-
if (stopping)
|
|
26701
|
-
return;
|
|
26702
|
-
stopping = true;
|
|
26703
|
-
gateway.close().then(() => process.exit(0)).catch((error) => {
|
|
26704
|
-
console.error("\u5173\u95ED\u5931\u8D25:", error instanceof Error ? error.message : String(error));
|
|
26705
|
-
process.exit(1);
|
|
26706
|
-
});
|
|
26707
|
-
};
|
|
26708
|
-
process.on("SIGINT", stop);
|
|
26709
|
-
process.on("SIGTERM", stop);
|
|
27274
|
+
shutdown.setCleanup(() => gateway.close());
|