sessix-server 0.1.4 → 0.2.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/dist/index.js +367 -21
- package/dist/server.d.ts +6 -0
- package/dist/server.js +313 -18
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -25,6 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
var import_node_os5 = require("os");
|
|
28
|
+
var import_node_fs2 = require("fs");
|
|
29
|
+
var import_node_path5 = require("path");
|
|
28
30
|
|
|
29
31
|
// src/i18n/locales/zh.ts
|
|
30
32
|
var zh = {
|
|
@@ -43,6 +45,9 @@ var zh = {
|
|
|
43
45
|
autoDiscoveryOn: " \u{1F4A1} \u81EA\u52A8\u53D1\u73B0\u5DF2\u542F\u7528\uFF0C\u540C\u7F51\u6BB5\u624B\u673A\u53EF\u81EA\u52A8\u8FDE\u63A5",
|
|
44
46
|
autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
45
47
|
autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
|
|
48
|
+
pairingOpen: " \u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u5F00\u542F\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF09\u2014 \u6309 p \u91CD\u65B0\u5F00\u542F",
|
|
49
|
+
pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
|
|
50
|
+
updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
|
|
46
51
|
receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
|
|
47
52
|
goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
|
|
48
53
|
shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
|
|
@@ -69,7 +74,8 @@ var zh = {
|
|
|
69
74
|
restarting: "\u91CD\u65B0\u542F\u52A8 {{label}}...",
|
|
70
75
|
activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
|
|
71
76
|
activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
|
|
72
|
-
activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09"
|
|
77
|
+
activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09",
|
|
78
|
+
noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B"
|
|
73
79
|
},
|
|
74
80
|
ws: {
|
|
75
81
|
started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
|
|
@@ -144,6 +150,9 @@ var en = {
|
|
|
144
150
|
autoDiscoveryOn: " Auto-discovery enabled, phones on the same network can connect automatically",
|
|
145
151
|
autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
146
152
|
autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
|
|
153
|
+
pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
|
|
154
|
+
pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
|
|
155
|
+
updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
|
|
147
156
|
receivedSignal: "Received {{signal}}, graceful shutdown...",
|
|
148
157
|
goodbye: "All services closed, goodbye!",
|
|
149
158
|
shutdownError: "Shutdown error:",
|
|
@@ -170,7 +179,8 @@ var en = {
|
|
|
170
179
|
restarting: "Restarting {{label}}...",
|
|
171
180
|
activityPushEnabled: "ActivityKit Push enabled",
|
|
172
181
|
activityPushFailed: "ActivityKit Push init failed:",
|
|
173
|
-
activityPushContinue: "Continuing startup (Live Activity background push unavailable)"
|
|
182
|
+
activityPushContinue: "Continuing startup (Live Activity background push unavailable)",
|
|
183
|
+
noActiveLoginProcess: "No active login process"
|
|
174
184
|
},
|
|
175
185
|
ws: {
|
|
176
186
|
started: "WebSocket server started on port {{port}}",
|
|
@@ -283,11 +293,14 @@ var import_node_child_process2 = require("child_process");
|
|
|
283
293
|
var import_node_util = require("util");
|
|
284
294
|
|
|
285
295
|
// src/providers/ProcessProvider.ts
|
|
286
|
-
var
|
|
296
|
+
var import_child_process2 = require("child_process");
|
|
287
297
|
var import_readline = require("readline");
|
|
288
298
|
var import_events = require("events");
|
|
289
299
|
var import_node_os = require("os");
|
|
290
300
|
var import_uuid = require("uuid");
|
|
301
|
+
|
|
302
|
+
// src/utils/claudePath.ts
|
|
303
|
+
var import_child_process = require("child_process");
|
|
291
304
|
function findClaudePath() {
|
|
292
305
|
try {
|
|
293
306
|
return (0, import_child_process.execSync)("which claude", { encoding: "utf-8" }).trim();
|
|
@@ -307,6 +320,8 @@ function findClaudePath() {
|
|
|
307
320
|
return "claude";
|
|
308
321
|
}
|
|
309
322
|
}
|
|
323
|
+
|
|
324
|
+
// src/providers/ProcessProvider.ts
|
|
310
325
|
var CLAUDE_PATH = findClaudePath();
|
|
311
326
|
var ProcessProvider = class {
|
|
312
327
|
/** 活跃会话映射表:sessionId -> { session, process } */
|
|
@@ -499,7 +514,7 @@ var ProcessProvider = class {
|
|
|
499
514
|
}
|
|
500
515
|
const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
|
|
501
516
|
delete env.CLAUDECODE;
|
|
502
|
-
const proc = (0,
|
|
517
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, args, {
|
|
503
518
|
cwd: projectPath,
|
|
504
519
|
env,
|
|
505
520
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -706,7 +721,7 @@ ${context}`;
|
|
|
706
721
|
return new Promise((resolve, reject) => {
|
|
707
722
|
const env = { ...process.env };
|
|
708
723
|
delete env.CLAUDECODE;
|
|
709
|
-
const proc = (0,
|
|
724
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
710
725
|
cwd: (0, import_node_os.homedir)(),
|
|
711
726
|
env,
|
|
712
727
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1617,6 +1632,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1617
1632
|
alwaysAllowedTools = /* @__PURE__ */ new Set();
|
|
1618
1633
|
/** 获取状态信息的回调(由外部注入) */
|
|
1619
1634
|
statusInfoProvider = null;
|
|
1635
|
+
pairingManager = null;
|
|
1620
1636
|
constructor(options) {
|
|
1621
1637
|
this.token = options.token;
|
|
1622
1638
|
this.port = options.port;
|
|
@@ -1651,6 +1667,10 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1651
1667
|
setStatusInfoProvider(provider) {
|
|
1652
1668
|
this.statusInfoProvider = provider;
|
|
1653
1669
|
}
|
|
1670
|
+
/** 设置配对管理器 */
|
|
1671
|
+
setPairingManager(manager) {
|
|
1672
|
+
this.pairingManager = manager;
|
|
1673
|
+
}
|
|
1654
1674
|
/** 设置会话的 YOLO 模式(服务端拦截,即使手机断连也生效) */
|
|
1655
1675
|
setYoloMode(sessionId, enabled) {
|
|
1656
1676
|
this.yoloSessions.set(sessionId, enabled);
|
|
@@ -1811,6 +1831,8 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1811
1831
|
const pathname = url.pathname;
|
|
1812
1832
|
if (req.method === "POST" && pathname === "/hook/approval") {
|
|
1813
1833
|
this.handleApprovalHook(req, res);
|
|
1834
|
+
} else if (req.method === "POST" && pathname === "/pair") {
|
|
1835
|
+
this.handlePair(req, res);
|
|
1814
1836
|
} else if (req.method === "GET" && pathname === "/health") {
|
|
1815
1837
|
this.handleHealth(req, res);
|
|
1816
1838
|
} else if (req.method === "GET" && pathname === "/token") {
|
|
@@ -1881,6 +1903,23 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1881
1903
|
activeSessions: info.activeSessions
|
|
1882
1904
|
});
|
|
1883
1905
|
}
|
|
1906
|
+
/** 配对端点:配对窗口开放时返回 token */
|
|
1907
|
+
handlePair(_req, res) {
|
|
1908
|
+
if (!this.pairingManager) {
|
|
1909
|
+
this.sendJson(res, 503, { error: "pairing_unavailable" });
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
const result = this.pairingManager.tryPair();
|
|
1913
|
+
if (result) {
|
|
1914
|
+
console.log("[ApprovalProxy] Device paired successfully");
|
|
1915
|
+
this.sendJson(res, 200, result);
|
|
1916
|
+
} else {
|
|
1917
|
+
this.sendJson(res, 403, {
|
|
1918
|
+
error: "pairing_closed",
|
|
1919
|
+
message: "Pairing window is closed. Restart server or press p to reopen."
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1884
1923
|
/** 返回连接 token(仅本机访问) */
|
|
1885
1924
|
handleToken(req, res) {
|
|
1886
1925
|
const remoteAddress = req.socket.remoteAddress;
|
|
@@ -1951,12 +1990,12 @@ var MdnsService = class {
|
|
|
1951
1990
|
wsPort;
|
|
1952
1991
|
httpPort;
|
|
1953
1992
|
version;
|
|
1954
|
-
|
|
1993
|
+
pairing;
|
|
1955
1994
|
constructor(options) {
|
|
1956
1995
|
this.wsPort = options.wsPort;
|
|
1957
1996
|
this.httpPort = options.httpPort;
|
|
1958
1997
|
this.version = options.version ?? "0.1.0";
|
|
1959
|
-
this.
|
|
1998
|
+
this.pairing = options.pairing ?? "closed";
|
|
1960
1999
|
}
|
|
1961
2000
|
/**
|
|
1962
2001
|
* 启动 mDNS 广播
|
|
@@ -1974,7 +2013,8 @@ var MdnsService = class {
|
|
|
1974
2013
|
txt: {
|
|
1975
2014
|
version: this.version,
|
|
1976
2015
|
httpPort: String(this.httpPort),
|
|
1977
|
-
|
|
2016
|
+
wsPort: String(this.wsPort),
|
|
2017
|
+
pairing: this.pairing
|
|
1978
2018
|
}
|
|
1979
2019
|
});
|
|
1980
2020
|
console.log(`[MdnsService] ${t("mdns.started", { port: this.wsPort })}`);
|
|
@@ -1995,6 +2035,34 @@ var MdnsService = class {
|
|
|
1995
2035
|
}
|
|
1996
2036
|
console.log(`[MdnsService] ${t("mdns.closed")}`);
|
|
1997
2037
|
}
|
|
2038
|
+
/**
|
|
2039
|
+
* 更新配对状态(重新发布 mDNS 服务)
|
|
2040
|
+
*/
|
|
2041
|
+
updatePairingState(state) {
|
|
2042
|
+
this.pairing = state;
|
|
2043
|
+
if (!this.bonjour) return;
|
|
2044
|
+
const republish = () => {
|
|
2045
|
+
if (!this.bonjour) return;
|
|
2046
|
+
this.service = this.bonjour.publish({
|
|
2047
|
+
name: "Sessix",
|
|
2048
|
+
type: "sessix",
|
|
2049
|
+
port: this.wsPort,
|
|
2050
|
+
txt: {
|
|
2051
|
+
version: this.version,
|
|
2052
|
+
httpPort: String(this.httpPort),
|
|
2053
|
+
wsPort: String(this.wsPort),
|
|
2054
|
+
pairing: state
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
};
|
|
2058
|
+
if (this.service) {
|
|
2059
|
+
const old = this.service;
|
|
2060
|
+
this.service = null;
|
|
2061
|
+
old.stop?.(() => republish());
|
|
2062
|
+
} else {
|
|
2063
|
+
republish();
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
1998
2066
|
};
|
|
1999
2067
|
|
|
2000
2068
|
// src/hooks/HookInstaller.ts
|
|
@@ -2240,8 +2308,8 @@ var NotificationService = class {
|
|
|
2240
2308
|
if (entry) entry.enabled = enabled;
|
|
2241
2309
|
}
|
|
2242
2310
|
/** 注册手机 push token(连接建立时由 WsBridge 调用) */
|
|
2243
|
-
addPushToken(token) {
|
|
2244
|
-
this.expoChannel?.addToken(token);
|
|
2311
|
+
addPushToken(token, ws) {
|
|
2312
|
+
this.expoChannel?.addToken(token, ws);
|
|
2245
2313
|
}
|
|
2246
2314
|
/** 移除手机 push token(断线时或手机主动注销时调用) */
|
|
2247
2315
|
removePushToken(token) {
|
|
@@ -2481,17 +2549,21 @@ var MacNotificationChannel = class {
|
|
|
2481
2549
|
var EXPO_PUSH_API = "https://exp.host/--/api/v2/push/send";
|
|
2482
2550
|
var ExpoNotificationChannel = class {
|
|
2483
2551
|
tokens = /* @__PURE__ */ new Set();
|
|
2552
|
+
/** push token → WebSocket 连接映射,用于前台抑制 */
|
|
2553
|
+
tokenWsMap = /* @__PURE__ */ new Map();
|
|
2484
2554
|
/** per-token 通知音效偏好 */
|
|
2485
2555
|
soundPreferences = /* @__PURE__ */ new Map();
|
|
2486
2556
|
isAvailable() {
|
|
2487
2557
|
return this.tokens.size > 0;
|
|
2488
2558
|
}
|
|
2489
|
-
addToken(token) {
|
|
2559
|
+
addToken(token, ws) {
|
|
2490
2560
|
this.tokens.add(token);
|
|
2561
|
+
if (ws) this.tokenWsMap.set(token, ws);
|
|
2491
2562
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRegistered", { count: this.tokens.size })}`);
|
|
2492
2563
|
}
|
|
2493
2564
|
removeToken(token) {
|
|
2494
2565
|
this.tokens.delete(token);
|
|
2566
|
+
this.tokenWsMap.delete(token);
|
|
2495
2567
|
this.soundPreferences.delete(token);
|
|
2496
2568
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRemoved", { count: this.tokens.size })}`);
|
|
2497
2569
|
}
|
|
@@ -2504,7 +2576,12 @@ var ExpoNotificationChannel = class {
|
|
|
2504
2576
|
}
|
|
2505
2577
|
async send(payload) {
|
|
2506
2578
|
if (this.tokens.size === 0) return;
|
|
2507
|
-
const
|
|
2579
|
+
const offlineTokens = Array.from(this.tokens).filter((token) => {
|
|
2580
|
+
const ws = this.tokenWsMap.get(token);
|
|
2581
|
+
return !ws || ws.readyState !== ws.OPEN;
|
|
2582
|
+
});
|
|
2583
|
+
if (offlineTokens.length === 0) return;
|
|
2584
|
+
const messages = offlineTokens.map((to) => {
|
|
2508
2585
|
let sound = payload.sound ?? "default";
|
|
2509
2586
|
const prefs = this.soundPreferences.get(to);
|
|
2510
2587
|
if (prefs) {
|
|
@@ -2524,7 +2601,7 @@ var ExpoNotificationChannel = class {
|
|
|
2524
2601
|
};
|
|
2525
2602
|
});
|
|
2526
2603
|
try {
|
|
2527
|
-
console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")}
|
|
2604
|
+
console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")} (${offlineTokens.length}/${this.tokens.size} devices)`, offlineTokens);
|
|
2528
2605
|
const res = await fetch(EXPO_PUSH_API, {
|
|
2529
2606
|
method: "POST",
|
|
2530
2607
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
@@ -3027,6 +3104,180 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3027
3104
|
}
|
|
3028
3105
|
}
|
|
3029
3106
|
|
|
3107
|
+
// src/pairing/PairingManager.ts
|
|
3108
|
+
var PairingManager = class {
|
|
3109
|
+
_state = "closed";
|
|
3110
|
+
timer = null;
|
|
3111
|
+
deadline = 0;
|
|
3112
|
+
token;
|
|
3113
|
+
serverName;
|
|
3114
|
+
version;
|
|
3115
|
+
defaultDuration;
|
|
3116
|
+
onStateChange;
|
|
3117
|
+
constructor(opts) {
|
|
3118
|
+
this.token = opts.token;
|
|
3119
|
+
this.serverName = opts.serverName;
|
|
3120
|
+
this.version = opts.version;
|
|
3121
|
+
this.defaultDuration = opts.defaultDuration ?? 3e5;
|
|
3122
|
+
this.onStateChange = opts.onStateChange;
|
|
3123
|
+
}
|
|
3124
|
+
get state() {
|
|
3125
|
+
return this._state;
|
|
3126
|
+
}
|
|
3127
|
+
open(duration) {
|
|
3128
|
+
const ms = duration ?? this.defaultDuration;
|
|
3129
|
+
if (this.timer) clearTimeout(this.timer);
|
|
3130
|
+
this._state = "open";
|
|
3131
|
+
this.deadline = Date.now() + ms;
|
|
3132
|
+
this.timer = setTimeout(() => this.close(), ms);
|
|
3133
|
+
this.onStateChange("open");
|
|
3134
|
+
}
|
|
3135
|
+
close() {
|
|
3136
|
+
if (this.timer) {
|
|
3137
|
+
clearTimeout(this.timer);
|
|
3138
|
+
this.timer = null;
|
|
3139
|
+
}
|
|
3140
|
+
if (this._state === "closed") return;
|
|
3141
|
+
this._state = "closed";
|
|
3142
|
+
this.deadline = 0;
|
|
3143
|
+
this.onStateChange("closed");
|
|
3144
|
+
}
|
|
3145
|
+
tryPair() {
|
|
3146
|
+
if (this._state !== "open") return null;
|
|
3147
|
+
const result = { token: this.token, serverName: this.serverName, version: this.version };
|
|
3148
|
+
this.close();
|
|
3149
|
+
return result;
|
|
3150
|
+
}
|
|
3151
|
+
getRemainingSeconds() {
|
|
3152
|
+
if (this._state !== "open") return 0;
|
|
3153
|
+
return Math.max(0, Math.ceil((this.deadline - Date.now()) / 1e3));
|
|
3154
|
+
}
|
|
3155
|
+
destroy() {
|
|
3156
|
+
if (this.timer) {
|
|
3157
|
+
clearTimeout(this.timer);
|
|
3158
|
+
this.timer = null;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
|
|
3163
|
+
// src/auth/AuthManager.ts
|
|
3164
|
+
var import_child_process3 = require("child_process");
|
|
3165
|
+
var import_child_process4 = require("child_process");
|
|
3166
|
+
var import_util = require("util");
|
|
3167
|
+
var import_events2 = require("events");
|
|
3168
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3169
|
+
var CLAUDE_PATH2 = findClaudePath();
|
|
3170
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3171
|
+
var AuthManager = class extends import_events2.EventEmitter {
|
|
3172
|
+
loginProcess = null;
|
|
3173
|
+
loginTimeout = null;
|
|
3174
|
+
urlSent = false;
|
|
3175
|
+
/** 检查当前 Claude CLI 认证状态(异步,不阻塞事件循环) */
|
|
3176
|
+
async checkAuth() {
|
|
3177
|
+
try {
|
|
3178
|
+
const { stdout } = await execFileAsync(CLAUDE_PATH2, ["auth", "status"], {
|
|
3179
|
+
timeout: 1e4
|
|
3180
|
+
});
|
|
3181
|
+
const parsed = JSON.parse(stdout.trim());
|
|
3182
|
+
return {
|
|
3183
|
+
loggedIn: !!parsed.loggedIn,
|
|
3184
|
+
email: parsed.email,
|
|
3185
|
+
authMethod: parsed.authMethod
|
|
3186
|
+
};
|
|
3187
|
+
} catch {
|
|
3188
|
+
return { loggedIn: false };
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
/** 启动登录流程,捕获 URL 并通过事件推送 */
|
|
3192
|
+
async startLogin() {
|
|
3193
|
+
if (this.loginProcess) {
|
|
3194
|
+
this.loginProcess.kill();
|
|
3195
|
+
this.loginProcess = null;
|
|
3196
|
+
}
|
|
3197
|
+
this.clearLoginTimeout();
|
|
3198
|
+
this.urlSent = false;
|
|
3199
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3200
|
+
env: { ...process.env, BROWSER: "echo" },
|
|
3201
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3202
|
+
});
|
|
3203
|
+
this.loginProcess = proc;
|
|
3204
|
+
const handleOutput = (data) => {
|
|
3205
|
+
const text = data.toString();
|
|
3206
|
+
console.log(`[AuthManager] login output: ${text.trim()}`);
|
|
3207
|
+
if (!this.urlSent) {
|
|
3208
|
+
const url = this.extractUrl(text);
|
|
3209
|
+
if (url) {
|
|
3210
|
+
this.urlSent = true;
|
|
3211
|
+
console.log(`[AuthManager] \u6355\u83B7\u5230\u767B\u5F55 URL: ${url}`);
|
|
3212
|
+
this.emit("login_url", url);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
};
|
|
3216
|
+
proc.stdout?.on("data", handleOutput);
|
|
3217
|
+
proc.stderr?.on("data", handleOutput);
|
|
3218
|
+
proc.on("exit", (code) => {
|
|
3219
|
+
console.log(`[AuthManager] login process exited with code ${code}`);
|
|
3220
|
+
this.loginProcess = null;
|
|
3221
|
+
this.clearLoginTimeout();
|
|
3222
|
+
this.checkAuth().then((status) => {
|
|
3223
|
+
if (status.loggedIn) {
|
|
3224
|
+
this.emit("login_result", { success: true });
|
|
3225
|
+
} else if (code !== 0) {
|
|
3226
|
+
this.emit("login_result", { success: false, error: `Exit code: ${code}` });
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
});
|
|
3230
|
+
proc.on("error", (err) => {
|
|
3231
|
+
console.error(`[AuthManager] login process error:`, err.message);
|
|
3232
|
+
this.loginProcess = null;
|
|
3233
|
+
this.clearLoginTimeout();
|
|
3234
|
+
this.emit("login_result", { success: false, error: err.message });
|
|
3235
|
+
});
|
|
3236
|
+
this.loginTimeout = setTimeout(() => {
|
|
3237
|
+
if (this.loginProcess) {
|
|
3238
|
+
console.warn("[AuthManager] login process timed out, killing");
|
|
3239
|
+
this.loginProcess.kill();
|
|
3240
|
+
this.loginProcess = null;
|
|
3241
|
+
this.emit("login_result", { success: false, error: "Login timed out" });
|
|
3242
|
+
}
|
|
3243
|
+
}, LOGIN_TIMEOUT_MS);
|
|
3244
|
+
}
|
|
3245
|
+
/** 提交授权码到登录进程的 stdin */
|
|
3246
|
+
submitCode(code) {
|
|
3247
|
+
if (!this.loginProcess?.stdin?.writable) {
|
|
3248
|
+
console.warn("[AuthManager] No active login process");
|
|
3249
|
+
return false;
|
|
3250
|
+
}
|
|
3251
|
+
console.log(`[AuthManager] \u63D0\u4EA4\u6388\u6743\u7801`);
|
|
3252
|
+
this.loginProcess.stdin.write(code + "\n");
|
|
3253
|
+
return true;
|
|
3254
|
+
}
|
|
3255
|
+
/** 是否有登录进程在运行 */
|
|
3256
|
+
get isLoginInProgress() {
|
|
3257
|
+
return this.loginProcess !== null;
|
|
3258
|
+
}
|
|
3259
|
+
/** 清理资源 */
|
|
3260
|
+
destroy() {
|
|
3261
|
+
this.clearLoginTimeout();
|
|
3262
|
+
if (this.loginProcess) {
|
|
3263
|
+
this.loginProcess.kill();
|
|
3264
|
+
this.loginProcess = null;
|
|
3265
|
+
}
|
|
3266
|
+
this.removeAllListeners();
|
|
3267
|
+
}
|
|
3268
|
+
clearLoginTimeout() {
|
|
3269
|
+
if (this.loginTimeout) {
|
|
3270
|
+
clearTimeout(this.loginTimeout);
|
|
3271
|
+
this.loginTimeout = null;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
/** 从文本中提取 URL */
|
|
3275
|
+
extractUrl(text) {
|
|
3276
|
+
const match = text.match(/https?:\/\/[^\s"'<>]+/);
|
|
3277
|
+
return match ? match[0] : null;
|
|
3278
|
+
}
|
|
3279
|
+
};
|
|
3280
|
+
|
|
3030
3281
|
// src/server.ts
|
|
3031
3282
|
var import_promises5 = require("fs/promises");
|
|
3032
3283
|
var WS_PORT = 3745;
|
|
@@ -3105,9 +3356,29 @@ async function start(opts = {}) {
|
|
|
3105
3356
|
HTTP_PORT,
|
|
3106
3357
|
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
3107
3358
|
);
|
|
3359
|
+
let mdnsService = null;
|
|
3360
|
+
const pairingManager = new PairingManager({
|
|
3361
|
+
token,
|
|
3362
|
+
serverName: (0, import_node_os4.hostname)(),
|
|
3363
|
+
version: "0.2.0",
|
|
3364
|
+
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3365
|
+
});
|
|
3366
|
+
approvalProxy.setPairingManager(pairingManager);
|
|
3367
|
+
const authManager = new AuthManager();
|
|
3368
|
+
authManager.on("login_url", (url) => {
|
|
3369
|
+
wsBridge.broadcast({ type: "auth_login_url", url });
|
|
3370
|
+
});
|
|
3371
|
+
authManager.on("login_result", (result) => {
|
|
3372
|
+
wsBridge.broadcast({ type: "auth_login_result", success: result.success, error: result.error });
|
|
3373
|
+
if (result.success) {
|
|
3374
|
+
authManager.checkAuth().then((status) => {
|
|
3375
|
+
wsBridge.broadcast({ type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
});
|
|
3108
3379
|
const unreadSessionIds = /* @__PURE__ */ new Set();
|
|
3109
3380
|
notificationService.setGlobalPendingCountProvider(
|
|
3110
|
-
() => approvalProxy.getPendingCount() + unreadSessionIds.size
|
|
3381
|
+
() => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
|
|
3111
3382
|
);
|
|
3112
3383
|
const broadcastUnreadSessions = () => {
|
|
3113
3384
|
wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
|
|
@@ -3322,7 +3593,7 @@ async function start(opts = {}) {
|
|
|
3322
3593
|
break;
|
|
3323
3594
|
}
|
|
3324
3595
|
case "register_push_token": {
|
|
3325
|
-
notificationService.addPushToken(event.token);
|
|
3596
|
+
notificationService.addPushToken(event.token, ws);
|
|
3326
3597
|
break;
|
|
3327
3598
|
}
|
|
3328
3599
|
case "unregister_push_token": {
|
|
@@ -3361,6 +3632,22 @@ async function start(opts = {}) {
|
|
|
3361
3632
|
approvalProxy.addToClaudeSettings(event.projectPath, event.toolName);
|
|
3362
3633
|
break;
|
|
3363
3634
|
}
|
|
3635
|
+
case "check_auth": {
|
|
3636
|
+
const status = await authManager.checkAuth();
|
|
3637
|
+
wsBridge.send(ws, { type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
|
|
3638
|
+
break;
|
|
3639
|
+
}
|
|
3640
|
+
case "start_auth_login": {
|
|
3641
|
+
await authManager.startLogin();
|
|
3642
|
+
break;
|
|
3643
|
+
}
|
|
3644
|
+
case "submit_auth_code": {
|
|
3645
|
+
const submitted = authManager.submitCode(event.code);
|
|
3646
|
+
if (!submitted) {
|
|
3647
|
+
wsBridge.send(ws, { type: "auth_login_result", success: false, error: t("server.noActiveLoginProcess") });
|
|
3648
|
+
}
|
|
3649
|
+
break;
|
|
3650
|
+
}
|
|
3364
3651
|
default: {
|
|
3365
3652
|
wsBridge.send(ws, {
|
|
3366
3653
|
type: "error",
|
|
@@ -3438,10 +3725,13 @@ async function start(opts = {}) {
|
|
|
3438
3725
|
connections: wsBridge.getConnectionCount(),
|
|
3439
3726
|
activeSessions: sessionManager.getActiveSessions().length
|
|
3440
3727
|
}));
|
|
3441
|
-
let mdnsService = null;
|
|
3442
3728
|
const startMdns = () => {
|
|
3443
3729
|
if (mdnsService) return;
|
|
3444
|
-
mdnsService = new MdnsService({
|
|
3730
|
+
mdnsService = new MdnsService({
|
|
3731
|
+
wsPort: WS_PORT,
|
|
3732
|
+
httpPort: HTTP_PORT,
|
|
3733
|
+
pairing: pairingManager.state
|
|
3734
|
+
});
|
|
3445
3735
|
mdnsService.start();
|
|
3446
3736
|
};
|
|
3447
3737
|
const stopMdns = () => {
|
|
@@ -3449,6 +3739,9 @@ async function start(opts = {}) {
|
|
|
3449
3739
|
mdnsService.stop();
|
|
3450
3740
|
mdnsService = null;
|
|
3451
3741
|
};
|
|
3742
|
+
if (opts.enablePairing !== false) {
|
|
3743
|
+
pairingManager.open();
|
|
3744
|
+
}
|
|
3452
3745
|
if (opts.enableAutoConnect !== false) {
|
|
3453
3746
|
startMdns();
|
|
3454
3747
|
}
|
|
@@ -3476,7 +3769,9 @@ async function start(opts = {}) {
|
|
|
3476
3769
|
errors.push(err);
|
|
3477
3770
|
}
|
|
3478
3771
|
};
|
|
3772
|
+
await attempt(() => authManager.destroy(), "AuthManager");
|
|
3479
3773
|
await attempt(() => stopMdns(), "mDNS");
|
|
3774
|
+
await attempt(() => pairingManager.destroy(), "PairingManager");
|
|
3480
3775
|
await attempt(() => wsBridge.close(), "WebSocket");
|
|
3481
3776
|
await attempt(() => approvalProxy.close(), "ApprovalProxy");
|
|
3482
3777
|
await attempt(() => sessionManager.destroy(), "SessionManager");
|
|
@@ -3504,15 +3799,41 @@ async function start(opts = {}) {
|
|
|
3504
3799
|
} else {
|
|
3505
3800
|
stopMdns();
|
|
3506
3801
|
}
|
|
3507
|
-
}
|
|
3802
|
+
},
|
|
3803
|
+
openPairing: (duration) => pairingManager.open(duration),
|
|
3804
|
+
closePairing: () => pairingManager.close()
|
|
3508
3805
|
};
|
|
3509
3806
|
}
|
|
3510
3807
|
|
|
3511
3808
|
// src/index.ts
|
|
3512
3809
|
var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
3810
|
+
function getPackageVersion() {
|
|
3811
|
+
try {
|
|
3812
|
+
const pkg = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path5.join)(__dirname, "..", "package.json"), "utf8"));
|
|
3813
|
+
return pkg.version ?? "0.0.0";
|
|
3814
|
+
} catch {
|
|
3815
|
+
return "0.0.0";
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
var PKG_VERSION = getPackageVersion();
|
|
3819
|
+
async function fetchLatestVersion() {
|
|
3820
|
+
try {
|
|
3821
|
+
const controller = new AbortController();
|
|
3822
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
3823
|
+
const res = await fetch("https://registry.npmjs.org/sessix-server/latest", {
|
|
3824
|
+
signal: controller.signal
|
|
3825
|
+
});
|
|
3826
|
+
clearTimeout(timer);
|
|
3827
|
+
if (!res.ok) return null;
|
|
3828
|
+
const data = await res.json();
|
|
3829
|
+
return data.version ?? null;
|
|
3830
|
+
} catch {
|
|
3831
|
+
return null;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3513
3834
|
async function main() {
|
|
3514
3835
|
console.log("=".repeat(50));
|
|
3515
|
-
console.log(t("startup.banner"));
|
|
3836
|
+
console.log(`${t("startup.banner")} v${PKG_VERSION}`);
|
|
3516
3837
|
console.log("=".repeat(50));
|
|
3517
3838
|
console.log();
|
|
3518
3839
|
const enableAutoConnect = process.env.SESSIX_AUTO_CONNECT !== "false";
|
|
@@ -3552,6 +3873,15 @@ async function main() {
|
|
|
3552
3873
|
console.log();
|
|
3553
3874
|
console.log(t("startup.waitingConnection"));
|
|
3554
3875
|
console.log();
|
|
3876
|
+
console.log(t("startup.pairingOpen"));
|
|
3877
|
+
console.log();
|
|
3878
|
+
fetchLatestVersion().then((latest) => {
|
|
3879
|
+
if (!latest || latest === PKG_VERSION) return;
|
|
3880
|
+
console.log();
|
|
3881
|
+
console.log(` \u{1F4E6} ${t("startup.updateAvailable", { current: PKG_VERSION, latest })}`);
|
|
3882
|
+
console.log(` npx sessix-server@latest`);
|
|
3883
|
+
console.log();
|
|
3884
|
+
});
|
|
3555
3885
|
const shutdown = async (signal) => {
|
|
3556
3886
|
console.log(`
|
|
3557
3887
|
[Main] ${t("startup.receivedSignal", { signal })}`);
|
|
@@ -3564,8 +3894,24 @@ async function main() {
|
|
|
3564
3894
|
process.exit(1);
|
|
3565
3895
|
}
|
|
3566
3896
|
};
|
|
3567
|
-
process.
|
|
3568
|
-
|
|
3897
|
+
if (process.stdin.isTTY) {
|
|
3898
|
+
process.stdin.setRawMode(true);
|
|
3899
|
+
process.stdin.resume();
|
|
3900
|
+
process.stdin.setEncoding("utf8");
|
|
3901
|
+
process.stdin.on("data", (key) => {
|
|
3902
|
+
if (key === "p" || key === "P") {
|
|
3903
|
+
server.openPairing();
|
|
3904
|
+
console.log(`
|
|
3905
|
+
${t("startup.pairingReopened")}`);
|
|
3906
|
+
}
|
|
3907
|
+
if (key === "") {
|
|
3908
|
+
shutdown("SIGINT");
|
|
3909
|
+
}
|
|
3910
|
+
});
|
|
3911
|
+
} else {
|
|
3912
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
3913
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
3914
|
+
}
|
|
3569
3915
|
}
|
|
3570
3916
|
function getLocalIp() {
|
|
3571
3917
|
const interfaces = (0, import_node_os5.networkInterfaces)();
|
package/dist/server.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ interface ServerInstance {
|
|
|
15
15
|
onServerEvent: (cb: (event: ServerEvent) => void) => () => void;
|
|
16
16
|
/** 运行时切换 mDNS 自动发现 */
|
|
17
17
|
setAutoConnect: (enabled: boolean) => void;
|
|
18
|
+
/** 运行时开启配对窗口 */
|
|
19
|
+
openPairing: (duration?: number) => void;
|
|
20
|
+
/** 运行时关闭配对窗口 */
|
|
21
|
+
closePairing: () => void;
|
|
18
22
|
}
|
|
19
23
|
interface ServerOptions {
|
|
20
24
|
/** 覆盖 token(默认读取 ~/.sessix/token 或自动生成) */
|
|
@@ -30,6 +34,8 @@ interface ServerOptions {
|
|
|
30
34
|
};
|
|
31
35
|
/** 是否启用 mDNS 自动发现(默认 true) */
|
|
32
36
|
enableAutoConnect?: boolean;
|
|
37
|
+
/** 是否启用配对模式(默认 true,Electron 传 false) */
|
|
38
|
+
enablePairing?: boolean;
|
|
33
39
|
}
|
|
34
40
|
declare function start(opts?: ServerOptions): Promise<ServerInstance>;
|
|
35
41
|
|
package/dist/server.js
CHANGED
|
@@ -51,6 +51,9 @@ var zh = {
|
|
|
51
51
|
autoDiscoveryOn: " \u{1F4A1} \u81EA\u52A8\u53D1\u73B0\u5DF2\u542F\u7528\uFF0C\u540C\u7F51\u6BB5\u624B\u673A\u53EF\u81EA\u52A8\u8FDE\u63A5",
|
|
52
52
|
autoDiscoveryHint: " \u5982\u5728\u516C\u5171\u7F51\u7EDC\uFF0C\u5EFA\u8BAE\u5173\u95ED: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
53
53
|
autoDiscoveryOff: " \u2139\uFE0F \u81EA\u52A8\u53D1\u73B0\u5DF2\u5173\u95ED\uFF0C\u624B\u673A\u9700\u624B\u52A8\u8F93\u5165\u5730\u5740\u8FDE\u63A5",
|
|
54
|
+
pairingOpen: " \u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u5F00\u542F\uFF085 \u5206\u949F\u5185\u6709\u6548\uFF09\u2014 \u6309 p \u91CD\u65B0\u5F00\u542F",
|
|
55
|
+
pairingReopened: "\u{1F513} \u914D\u5BF9\u6A21\u5F0F\u5DF2\u91CD\u65B0\u5F00\u542F\uFF085 \u5206\u949F\uFF09",
|
|
56
|
+
updateAvailable: "\u53D1\u73B0\u65B0\u7248\u672C v{{latest}}\uFF08\u5F53\u524D v{{current}}\uFF09\uFF0C\u8FD0\u884C\u4EE5\u4E0B\u547D\u4EE4\u66F4\u65B0\uFF1A",
|
|
54
57
|
receivedSignal: "\u6536\u5230 {{signal}}\uFF0C\u6B63\u5728\u4F18\u96C5\u5173\u95ED...",
|
|
55
58
|
goodbye: "\u6240\u6709\u670D\u52A1\u5DF2\u5173\u95ED\uFF0C\u518D\u89C1\uFF01",
|
|
56
59
|
shutdownError: "\u5173\u95ED\u8FC7\u7A0B\u51FA\u9519:",
|
|
@@ -77,7 +80,8 @@ var zh = {
|
|
|
77
80
|
restarting: "\u91CD\u65B0\u542F\u52A8 {{label}}...",
|
|
78
81
|
activityPushEnabled: "ActivityKit Push \u5DF2\u542F\u7528",
|
|
79
82
|
activityPushFailed: "ActivityKit Push \u521D\u59CB\u5316\u5931\u8D25:",
|
|
80
|
-
activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09"
|
|
83
|
+
activityPushContinue: "\u7EE7\u7EED\u542F\u52A8\uFF08Live Activity \u540E\u53F0\u63A8\u9001\u4E0D\u53EF\u7528\uFF09",
|
|
84
|
+
noActiveLoginProcess: "\u6CA1\u6709\u6D3B\u8DC3\u7684\u767B\u5F55\u8FDB\u7A0B"
|
|
81
85
|
},
|
|
82
86
|
ws: {
|
|
83
87
|
started: "WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 {{port}}",
|
|
@@ -152,6 +156,9 @@ var en = {
|
|
|
152
156
|
autoDiscoveryOn: " Auto-discovery enabled, phones on the same network can connect automatically",
|
|
153
157
|
autoDiscoveryHint: " On public networks, disable with: SESSIX_AUTO_CONNECT=false npx sessix-server",
|
|
154
158
|
autoDiscoveryOff: " Auto-discovery disabled, phone must enter address manually",
|
|
159
|
+
pairingOpen: " \u{1F513} Pairing mode open (5 min) \u2014 press p to reopen",
|
|
160
|
+
pairingReopened: "\u{1F513} Pairing mode reopened (5 min)",
|
|
161
|
+
updateAvailable: "New version v{{latest}} available (current v{{current}}). Update with:",
|
|
155
162
|
receivedSignal: "Received {{signal}}, graceful shutdown...",
|
|
156
163
|
goodbye: "All services closed, goodbye!",
|
|
157
164
|
shutdownError: "Shutdown error:",
|
|
@@ -178,7 +185,8 @@ var en = {
|
|
|
178
185
|
restarting: "Restarting {{label}}...",
|
|
179
186
|
activityPushEnabled: "ActivityKit Push enabled",
|
|
180
187
|
activityPushFailed: "ActivityKit Push init failed:",
|
|
181
|
-
activityPushContinue: "Continuing startup (Live Activity background push unavailable)"
|
|
188
|
+
activityPushContinue: "Continuing startup (Live Activity background push unavailable)",
|
|
189
|
+
noActiveLoginProcess: "No active login process"
|
|
182
190
|
},
|
|
183
191
|
ws: {
|
|
184
192
|
started: "WebSocket server started on port {{port}}",
|
|
@@ -291,11 +299,14 @@ var import_node_child_process2 = require("child_process");
|
|
|
291
299
|
var import_node_util = require("util");
|
|
292
300
|
|
|
293
301
|
// src/providers/ProcessProvider.ts
|
|
294
|
-
var
|
|
302
|
+
var import_child_process2 = require("child_process");
|
|
295
303
|
var import_readline = require("readline");
|
|
296
304
|
var import_events = require("events");
|
|
297
305
|
var import_node_os = require("os");
|
|
298
306
|
var import_uuid = require("uuid");
|
|
307
|
+
|
|
308
|
+
// src/utils/claudePath.ts
|
|
309
|
+
var import_child_process = require("child_process");
|
|
299
310
|
function findClaudePath() {
|
|
300
311
|
try {
|
|
301
312
|
return (0, import_child_process.execSync)("which claude", { encoding: "utf-8" }).trim();
|
|
@@ -315,6 +326,8 @@ function findClaudePath() {
|
|
|
315
326
|
return "claude";
|
|
316
327
|
}
|
|
317
328
|
}
|
|
329
|
+
|
|
330
|
+
// src/providers/ProcessProvider.ts
|
|
318
331
|
var CLAUDE_PATH = findClaudePath();
|
|
319
332
|
var ProcessProvider = class {
|
|
320
333
|
/** 活跃会话映射表:sessionId -> { session, process } */
|
|
@@ -507,7 +520,7 @@ var ProcessProvider = class {
|
|
|
507
520
|
}
|
|
508
521
|
const env = { ...process.env, SESSIX_SESSION_ID: sessionId };
|
|
509
522
|
delete env.CLAUDECODE;
|
|
510
|
-
const proc = (0,
|
|
523
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, args, {
|
|
511
524
|
cwd: projectPath,
|
|
512
525
|
env,
|
|
513
526
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -714,7 +727,7 @@ ${context}`;
|
|
|
714
727
|
return new Promise((resolve, reject) => {
|
|
715
728
|
const env = { ...process.env };
|
|
716
729
|
delete env.CLAUDECODE;
|
|
717
|
-
const proc = (0,
|
|
730
|
+
const proc = (0, import_child_process2.spawn)(CLAUDE_PATH, ["-p", prompt, "--output-format", "text"], {
|
|
718
731
|
cwd: (0, import_node_os.homedir)(),
|
|
719
732
|
env,
|
|
720
733
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1625,6 +1638,7 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1625
1638
|
alwaysAllowedTools = /* @__PURE__ */ new Set();
|
|
1626
1639
|
/** 获取状态信息的回调(由外部注入) */
|
|
1627
1640
|
statusInfoProvider = null;
|
|
1641
|
+
pairingManager = null;
|
|
1628
1642
|
constructor(options) {
|
|
1629
1643
|
this.token = options.token;
|
|
1630
1644
|
this.port = options.port;
|
|
@@ -1659,6 +1673,10 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1659
1673
|
setStatusInfoProvider(provider) {
|
|
1660
1674
|
this.statusInfoProvider = provider;
|
|
1661
1675
|
}
|
|
1676
|
+
/** 设置配对管理器 */
|
|
1677
|
+
setPairingManager(manager) {
|
|
1678
|
+
this.pairingManager = manager;
|
|
1679
|
+
}
|
|
1662
1680
|
/** 设置会话的 YOLO 模式(服务端拦截,即使手机断连也生效) */
|
|
1663
1681
|
setYoloMode(sessionId, enabled) {
|
|
1664
1682
|
this.yoloSessions.set(sessionId, enabled);
|
|
@@ -1819,6 +1837,8 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1819
1837
|
const pathname = url.pathname;
|
|
1820
1838
|
if (req.method === "POST" && pathname === "/hook/approval") {
|
|
1821
1839
|
this.handleApprovalHook(req, res);
|
|
1840
|
+
} else if (req.method === "POST" && pathname === "/pair") {
|
|
1841
|
+
this.handlePair(req, res);
|
|
1822
1842
|
} else if (req.method === "GET" && pathname === "/health") {
|
|
1823
1843
|
this.handleHealth(req, res);
|
|
1824
1844
|
} else if (req.method === "GET" && pathname === "/token") {
|
|
@@ -1889,6 +1909,23 @@ var ApprovalProxy = class _ApprovalProxy {
|
|
|
1889
1909
|
activeSessions: info.activeSessions
|
|
1890
1910
|
});
|
|
1891
1911
|
}
|
|
1912
|
+
/** 配对端点:配对窗口开放时返回 token */
|
|
1913
|
+
handlePair(_req, res) {
|
|
1914
|
+
if (!this.pairingManager) {
|
|
1915
|
+
this.sendJson(res, 503, { error: "pairing_unavailable" });
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
const result = this.pairingManager.tryPair();
|
|
1919
|
+
if (result) {
|
|
1920
|
+
console.log("[ApprovalProxy] Device paired successfully");
|
|
1921
|
+
this.sendJson(res, 200, result);
|
|
1922
|
+
} else {
|
|
1923
|
+
this.sendJson(res, 403, {
|
|
1924
|
+
error: "pairing_closed",
|
|
1925
|
+
message: "Pairing window is closed. Restart server or press p to reopen."
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1892
1929
|
/** 返回连接 token(仅本机访问) */
|
|
1893
1930
|
handleToken(req, res) {
|
|
1894
1931
|
const remoteAddress = req.socket.remoteAddress;
|
|
@@ -1959,12 +1996,12 @@ var MdnsService = class {
|
|
|
1959
1996
|
wsPort;
|
|
1960
1997
|
httpPort;
|
|
1961
1998
|
version;
|
|
1962
|
-
|
|
1999
|
+
pairing;
|
|
1963
2000
|
constructor(options) {
|
|
1964
2001
|
this.wsPort = options.wsPort;
|
|
1965
2002
|
this.httpPort = options.httpPort;
|
|
1966
2003
|
this.version = options.version ?? "0.1.0";
|
|
1967
|
-
this.
|
|
2004
|
+
this.pairing = options.pairing ?? "closed";
|
|
1968
2005
|
}
|
|
1969
2006
|
/**
|
|
1970
2007
|
* 启动 mDNS 广播
|
|
@@ -1982,7 +2019,8 @@ var MdnsService = class {
|
|
|
1982
2019
|
txt: {
|
|
1983
2020
|
version: this.version,
|
|
1984
2021
|
httpPort: String(this.httpPort),
|
|
1985
|
-
|
|
2022
|
+
wsPort: String(this.wsPort),
|
|
2023
|
+
pairing: this.pairing
|
|
1986
2024
|
}
|
|
1987
2025
|
});
|
|
1988
2026
|
console.log(`[MdnsService] ${t("mdns.started", { port: this.wsPort })}`);
|
|
@@ -2003,6 +2041,34 @@ var MdnsService = class {
|
|
|
2003
2041
|
}
|
|
2004
2042
|
console.log(`[MdnsService] ${t("mdns.closed")}`);
|
|
2005
2043
|
}
|
|
2044
|
+
/**
|
|
2045
|
+
* 更新配对状态(重新发布 mDNS 服务)
|
|
2046
|
+
*/
|
|
2047
|
+
updatePairingState(state) {
|
|
2048
|
+
this.pairing = state;
|
|
2049
|
+
if (!this.bonjour) return;
|
|
2050
|
+
const republish = () => {
|
|
2051
|
+
if (!this.bonjour) return;
|
|
2052
|
+
this.service = this.bonjour.publish({
|
|
2053
|
+
name: "Sessix",
|
|
2054
|
+
type: "sessix",
|
|
2055
|
+
port: this.wsPort,
|
|
2056
|
+
txt: {
|
|
2057
|
+
version: this.version,
|
|
2058
|
+
httpPort: String(this.httpPort),
|
|
2059
|
+
wsPort: String(this.wsPort),
|
|
2060
|
+
pairing: state
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
};
|
|
2064
|
+
if (this.service) {
|
|
2065
|
+
const old = this.service;
|
|
2066
|
+
this.service = null;
|
|
2067
|
+
old.stop?.(() => republish());
|
|
2068
|
+
} else {
|
|
2069
|
+
republish();
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2006
2072
|
};
|
|
2007
2073
|
|
|
2008
2074
|
// src/hooks/HookInstaller.ts
|
|
@@ -2248,8 +2314,8 @@ var NotificationService = class {
|
|
|
2248
2314
|
if (entry) entry.enabled = enabled;
|
|
2249
2315
|
}
|
|
2250
2316
|
/** 注册手机 push token(连接建立时由 WsBridge 调用) */
|
|
2251
|
-
addPushToken(token) {
|
|
2252
|
-
this.expoChannel?.addToken(token);
|
|
2317
|
+
addPushToken(token, ws) {
|
|
2318
|
+
this.expoChannel?.addToken(token, ws);
|
|
2253
2319
|
}
|
|
2254
2320
|
/** 移除手机 push token(断线时或手机主动注销时调用) */
|
|
2255
2321
|
removePushToken(token) {
|
|
@@ -2489,17 +2555,21 @@ var MacNotificationChannel = class {
|
|
|
2489
2555
|
var EXPO_PUSH_API = "https://exp.host/--/api/v2/push/send";
|
|
2490
2556
|
var ExpoNotificationChannel = class {
|
|
2491
2557
|
tokens = /* @__PURE__ */ new Set();
|
|
2558
|
+
/** push token → WebSocket 连接映射,用于前台抑制 */
|
|
2559
|
+
tokenWsMap = /* @__PURE__ */ new Map();
|
|
2492
2560
|
/** per-token 通知音效偏好 */
|
|
2493
2561
|
soundPreferences = /* @__PURE__ */ new Map();
|
|
2494
2562
|
isAvailable() {
|
|
2495
2563
|
return this.tokens.size > 0;
|
|
2496
2564
|
}
|
|
2497
|
-
addToken(token) {
|
|
2565
|
+
addToken(token, ws) {
|
|
2498
2566
|
this.tokens.add(token);
|
|
2567
|
+
if (ws) this.tokenWsMap.set(token, ws);
|
|
2499
2568
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRegistered", { count: this.tokens.size })}`);
|
|
2500
2569
|
}
|
|
2501
2570
|
removeToken(token) {
|
|
2502
2571
|
this.tokens.delete(token);
|
|
2572
|
+
this.tokenWsMap.delete(token);
|
|
2503
2573
|
this.soundPreferences.delete(token);
|
|
2504
2574
|
console.log(`[ExpoNotificationChannel] ${t("notification.tokenRemoved", { count: this.tokens.size })}`);
|
|
2505
2575
|
}
|
|
@@ -2512,7 +2582,12 @@ var ExpoNotificationChannel = class {
|
|
|
2512
2582
|
}
|
|
2513
2583
|
async send(payload) {
|
|
2514
2584
|
if (this.tokens.size === 0) return;
|
|
2515
|
-
const
|
|
2585
|
+
const offlineTokens = Array.from(this.tokens).filter((token) => {
|
|
2586
|
+
const ws = this.tokenWsMap.get(token);
|
|
2587
|
+
return !ws || ws.readyState !== ws.OPEN;
|
|
2588
|
+
});
|
|
2589
|
+
if (offlineTokens.length === 0) return;
|
|
2590
|
+
const messages = offlineTokens.map((to) => {
|
|
2516
2591
|
let sound = payload.sound ?? "default";
|
|
2517
2592
|
const prefs = this.soundPreferences.get(to);
|
|
2518
2593
|
if (prefs) {
|
|
@@ -2532,7 +2607,7 @@ var ExpoNotificationChannel = class {
|
|
|
2532
2607
|
};
|
|
2533
2608
|
});
|
|
2534
2609
|
try {
|
|
2535
|
-
console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")}
|
|
2610
|
+
console.log(`[ExpoNotificationChannel] ${t("notification.sendingPush")} (${offlineTokens.length}/${this.tokens.size} devices)`, offlineTokens);
|
|
2536
2611
|
const res = await fetch(EXPO_PUSH_API, {
|
|
2537
2612
|
method: "POST",
|
|
2538
2613
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
@@ -3035,6 +3110,180 @@ async function countJsonlFilesWithMtime(dirPath) {
|
|
|
3035
3110
|
}
|
|
3036
3111
|
}
|
|
3037
3112
|
|
|
3113
|
+
// src/pairing/PairingManager.ts
|
|
3114
|
+
var PairingManager = class {
|
|
3115
|
+
_state = "closed";
|
|
3116
|
+
timer = null;
|
|
3117
|
+
deadline = 0;
|
|
3118
|
+
token;
|
|
3119
|
+
serverName;
|
|
3120
|
+
version;
|
|
3121
|
+
defaultDuration;
|
|
3122
|
+
onStateChange;
|
|
3123
|
+
constructor(opts) {
|
|
3124
|
+
this.token = opts.token;
|
|
3125
|
+
this.serverName = opts.serverName;
|
|
3126
|
+
this.version = opts.version;
|
|
3127
|
+
this.defaultDuration = opts.defaultDuration ?? 3e5;
|
|
3128
|
+
this.onStateChange = opts.onStateChange;
|
|
3129
|
+
}
|
|
3130
|
+
get state() {
|
|
3131
|
+
return this._state;
|
|
3132
|
+
}
|
|
3133
|
+
open(duration) {
|
|
3134
|
+
const ms = duration ?? this.defaultDuration;
|
|
3135
|
+
if (this.timer) clearTimeout(this.timer);
|
|
3136
|
+
this._state = "open";
|
|
3137
|
+
this.deadline = Date.now() + ms;
|
|
3138
|
+
this.timer = setTimeout(() => this.close(), ms);
|
|
3139
|
+
this.onStateChange("open");
|
|
3140
|
+
}
|
|
3141
|
+
close() {
|
|
3142
|
+
if (this.timer) {
|
|
3143
|
+
clearTimeout(this.timer);
|
|
3144
|
+
this.timer = null;
|
|
3145
|
+
}
|
|
3146
|
+
if (this._state === "closed") return;
|
|
3147
|
+
this._state = "closed";
|
|
3148
|
+
this.deadline = 0;
|
|
3149
|
+
this.onStateChange("closed");
|
|
3150
|
+
}
|
|
3151
|
+
tryPair() {
|
|
3152
|
+
if (this._state !== "open") return null;
|
|
3153
|
+
const result = { token: this.token, serverName: this.serverName, version: this.version };
|
|
3154
|
+
this.close();
|
|
3155
|
+
return result;
|
|
3156
|
+
}
|
|
3157
|
+
getRemainingSeconds() {
|
|
3158
|
+
if (this._state !== "open") return 0;
|
|
3159
|
+
return Math.max(0, Math.ceil((this.deadline - Date.now()) / 1e3));
|
|
3160
|
+
}
|
|
3161
|
+
destroy() {
|
|
3162
|
+
if (this.timer) {
|
|
3163
|
+
clearTimeout(this.timer);
|
|
3164
|
+
this.timer = null;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
};
|
|
3168
|
+
|
|
3169
|
+
// src/auth/AuthManager.ts
|
|
3170
|
+
var import_child_process3 = require("child_process");
|
|
3171
|
+
var import_child_process4 = require("child_process");
|
|
3172
|
+
var import_util = require("util");
|
|
3173
|
+
var import_events2 = require("events");
|
|
3174
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process4.execFile);
|
|
3175
|
+
var CLAUDE_PATH2 = findClaudePath();
|
|
3176
|
+
var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3177
|
+
var AuthManager = class extends import_events2.EventEmitter {
|
|
3178
|
+
loginProcess = null;
|
|
3179
|
+
loginTimeout = null;
|
|
3180
|
+
urlSent = false;
|
|
3181
|
+
/** 检查当前 Claude CLI 认证状态(异步,不阻塞事件循环) */
|
|
3182
|
+
async checkAuth() {
|
|
3183
|
+
try {
|
|
3184
|
+
const { stdout } = await execFileAsync(CLAUDE_PATH2, ["auth", "status"], {
|
|
3185
|
+
timeout: 1e4
|
|
3186
|
+
});
|
|
3187
|
+
const parsed = JSON.parse(stdout.trim());
|
|
3188
|
+
return {
|
|
3189
|
+
loggedIn: !!parsed.loggedIn,
|
|
3190
|
+
email: parsed.email,
|
|
3191
|
+
authMethod: parsed.authMethod
|
|
3192
|
+
};
|
|
3193
|
+
} catch {
|
|
3194
|
+
return { loggedIn: false };
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
/** 启动登录流程,捕获 URL 并通过事件推送 */
|
|
3198
|
+
async startLogin() {
|
|
3199
|
+
if (this.loginProcess) {
|
|
3200
|
+
this.loginProcess.kill();
|
|
3201
|
+
this.loginProcess = null;
|
|
3202
|
+
}
|
|
3203
|
+
this.clearLoginTimeout();
|
|
3204
|
+
this.urlSent = false;
|
|
3205
|
+
const proc = (0, import_child_process3.spawn)(CLAUDE_PATH2, ["auth", "login"], {
|
|
3206
|
+
env: { ...process.env, BROWSER: "echo" },
|
|
3207
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3208
|
+
});
|
|
3209
|
+
this.loginProcess = proc;
|
|
3210
|
+
const handleOutput = (data) => {
|
|
3211
|
+
const text = data.toString();
|
|
3212
|
+
console.log(`[AuthManager] login output: ${text.trim()}`);
|
|
3213
|
+
if (!this.urlSent) {
|
|
3214
|
+
const url = this.extractUrl(text);
|
|
3215
|
+
if (url) {
|
|
3216
|
+
this.urlSent = true;
|
|
3217
|
+
console.log(`[AuthManager] \u6355\u83B7\u5230\u767B\u5F55 URL: ${url}`);
|
|
3218
|
+
this.emit("login_url", url);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
proc.stdout?.on("data", handleOutput);
|
|
3223
|
+
proc.stderr?.on("data", handleOutput);
|
|
3224
|
+
proc.on("exit", (code) => {
|
|
3225
|
+
console.log(`[AuthManager] login process exited with code ${code}`);
|
|
3226
|
+
this.loginProcess = null;
|
|
3227
|
+
this.clearLoginTimeout();
|
|
3228
|
+
this.checkAuth().then((status) => {
|
|
3229
|
+
if (status.loggedIn) {
|
|
3230
|
+
this.emit("login_result", { success: true });
|
|
3231
|
+
} else if (code !== 0) {
|
|
3232
|
+
this.emit("login_result", { success: false, error: `Exit code: ${code}` });
|
|
3233
|
+
}
|
|
3234
|
+
});
|
|
3235
|
+
});
|
|
3236
|
+
proc.on("error", (err) => {
|
|
3237
|
+
console.error(`[AuthManager] login process error:`, err.message);
|
|
3238
|
+
this.loginProcess = null;
|
|
3239
|
+
this.clearLoginTimeout();
|
|
3240
|
+
this.emit("login_result", { success: false, error: err.message });
|
|
3241
|
+
});
|
|
3242
|
+
this.loginTimeout = setTimeout(() => {
|
|
3243
|
+
if (this.loginProcess) {
|
|
3244
|
+
console.warn("[AuthManager] login process timed out, killing");
|
|
3245
|
+
this.loginProcess.kill();
|
|
3246
|
+
this.loginProcess = null;
|
|
3247
|
+
this.emit("login_result", { success: false, error: "Login timed out" });
|
|
3248
|
+
}
|
|
3249
|
+
}, LOGIN_TIMEOUT_MS);
|
|
3250
|
+
}
|
|
3251
|
+
/** 提交授权码到登录进程的 stdin */
|
|
3252
|
+
submitCode(code) {
|
|
3253
|
+
if (!this.loginProcess?.stdin?.writable) {
|
|
3254
|
+
console.warn("[AuthManager] No active login process");
|
|
3255
|
+
return false;
|
|
3256
|
+
}
|
|
3257
|
+
console.log(`[AuthManager] \u63D0\u4EA4\u6388\u6743\u7801`);
|
|
3258
|
+
this.loginProcess.stdin.write(code + "\n");
|
|
3259
|
+
return true;
|
|
3260
|
+
}
|
|
3261
|
+
/** 是否有登录进程在运行 */
|
|
3262
|
+
get isLoginInProgress() {
|
|
3263
|
+
return this.loginProcess !== null;
|
|
3264
|
+
}
|
|
3265
|
+
/** 清理资源 */
|
|
3266
|
+
destroy() {
|
|
3267
|
+
this.clearLoginTimeout();
|
|
3268
|
+
if (this.loginProcess) {
|
|
3269
|
+
this.loginProcess.kill();
|
|
3270
|
+
this.loginProcess = null;
|
|
3271
|
+
}
|
|
3272
|
+
this.removeAllListeners();
|
|
3273
|
+
}
|
|
3274
|
+
clearLoginTimeout() {
|
|
3275
|
+
if (this.loginTimeout) {
|
|
3276
|
+
clearTimeout(this.loginTimeout);
|
|
3277
|
+
this.loginTimeout = null;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
/** 从文本中提取 URL */
|
|
3281
|
+
extractUrl(text) {
|
|
3282
|
+
const match = text.match(/https?:\/\/[^\s"'<>]+/);
|
|
3283
|
+
return match ? match[0] : null;
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
|
|
3038
3287
|
// src/server.ts
|
|
3039
3288
|
var import_promises5 = require("fs/promises");
|
|
3040
3289
|
var WS_PORT = 3745;
|
|
@@ -3113,9 +3362,29 @@ async function start(opts = {}) {
|
|
|
3113
3362
|
HTTP_PORT,
|
|
3114
3363
|
() => ApprovalProxy.create({ port: HTTP_PORT, token })
|
|
3115
3364
|
);
|
|
3365
|
+
let mdnsService = null;
|
|
3366
|
+
const pairingManager = new PairingManager({
|
|
3367
|
+
token,
|
|
3368
|
+
serverName: (0, import_node_os4.hostname)(),
|
|
3369
|
+
version: "0.2.0",
|
|
3370
|
+
onStateChange: (state) => mdnsService?.updatePairingState(state)
|
|
3371
|
+
});
|
|
3372
|
+
approvalProxy.setPairingManager(pairingManager);
|
|
3373
|
+
const authManager = new AuthManager();
|
|
3374
|
+
authManager.on("login_url", (url) => {
|
|
3375
|
+
wsBridge.broadcast({ type: "auth_login_url", url });
|
|
3376
|
+
});
|
|
3377
|
+
authManager.on("login_result", (result) => {
|
|
3378
|
+
wsBridge.broadcast({ type: "auth_login_result", success: result.success, error: result.error });
|
|
3379
|
+
if (result.success) {
|
|
3380
|
+
authManager.checkAuth().then((status) => {
|
|
3381
|
+
wsBridge.broadcast({ type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
});
|
|
3116
3385
|
const unreadSessionIds = /* @__PURE__ */ new Set();
|
|
3117
3386
|
notificationService.setGlobalPendingCountProvider(
|
|
3118
|
-
() => approvalProxy.getPendingCount() + unreadSessionIds.size
|
|
3387
|
+
() => approvalProxy.getPendingCount() + sessionManager.getAllPendingQuestions().length + unreadSessionIds.size
|
|
3119
3388
|
);
|
|
3120
3389
|
const broadcastUnreadSessions = () => {
|
|
3121
3390
|
wsBridge.broadcast({ type: "unread_sessions", sessionIds: Array.from(unreadSessionIds) });
|
|
@@ -3330,7 +3599,7 @@ async function start(opts = {}) {
|
|
|
3330
3599
|
break;
|
|
3331
3600
|
}
|
|
3332
3601
|
case "register_push_token": {
|
|
3333
|
-
notificationService.addPushToken(event.token);
|
|
3602
|
+
notificationService.addPushToken(event.token, ws);
|
|
3334
3603
|
break;
|
|
3335
3604
|
}
|
|
3336
3605
|
case "unregister_push_token": {
|
|
@@ -3369,6 +3638,22 @@ async function start(opts = {}) {
|
|
|
3369
3638
|
approvalProxy.addToClaudeSettings(event.projectPath, event.toolName);
|
|
3370
3639
|
break;
|
|
3371
3640
|
}
|
|
3641
|
+
case "check_auth": {
|
|
3642
|
+
const status = await authManager.checkAuth();
|
|
3643
|
+
wsBridge.send(ws, { type: "auth_status", loggedIn: status.loggedIn, email: status.email, authMethod: status.authMethod });
|
|
3644
|
+
break;
|
|
3645
|
+
}
|
|
3646
|
+
case "start_auth_login": {
|
|
3647
|
+
await authManager.startLogin();
|
|
3648
|
+
break;
|
|
3649
|
+
}
|
|
3650
|
+
case "submit_auth_code": {
|
|
3651
|
+
const submitted = authManager.submitCode(event.code);
|
|
3652
|
+
if (!submitted) {
|
|
3653
|
+
wsBridge.send(ws, { type: "auth_login_result", success: false, error: t("server.noActiveLoginProcess") });
|
|
3654
|
+
}
|
|
3655
|
+
break;
|
|
3656
|
+
}
|
|
3372
3657
|
default: {
|
|
3373
3658
|
wsBridge.send(ws, {
|
|
3374
3659
|
type: "error",
|
|
@@ -3446,10 +3731,13 @@ async function start(opts = {}) {
|
|
|
3446
3731
|
connections: wsBridge.getConnectionCount(),
|
|
3447
3732
|
activeSessions: sessionManager.getActiveSessions().length
|
|
3448
3733
|
}));
|
|
3449
|
-
let mdnsService = null;
|
|
3450
3734
|
const startMdns = () => {
|
|
3451
3735
|
if (mdnsService) return;
|
|
3452
|
-
mdnsService = new MdnsService({
|
|
3736
|
+
mdnsService = new MdnsService({
|
|
3737
|
+
wsPort: WS_PORT,
|
|
3738
|
+
httpPort: HTTP_PORT,
|
|
3739
|
+
pairing: pairingManager.state
|
|
3740
|
+
});
|
|
3453
3741
|
mdnsService.start();
|
|
3454
3742
|
};
|
|
3455
3743
|
const stopMdns = () => {
|
|
@@ -3457,6 +3745,9 @@ async function start(opts = {}) {
|
|
|
3457
3745
|
mdnsService.stop();
|
|
3458
3746
|
mdnsService = null;
|
|
3459
3747
|
};
|
|
3748
|
+
if (opts.enablePairing !== false) {
|
|
3749
|
+
pairingManager.open();
|
|
3750
|
+
}
|
|
3460
3751
|
if (opts.enableAutoConnect !== false) {
|
|
3461
3752
|
startMdns();
|
|
3462
3753
|
}
|
|
@@ -3484,7 +3775,9 @@ async function start(opts = {}) {
|
|
|
3484
3775
|
errors.push(err);
|
|
3485
3776
|
}
|
|
3486
3777
|
};
|
|
3778
|
+
await attempt(() => authManager.destroy(), "AuthManager");
|
|
3487
3779
|
await attempt(() => stopMdns(), "mDNS");
|
|
3780
|
+
await attempt(() => pairingManager.destroy(), "PairingManager");
|
|
3488
3781
|
await attempt(() => wsBridge.close(), "WebSocket");
|
|
3489
3782
|
await attempt(() => approvalProxy.close(), "ApprovalProxy");
|
|
3490
3783
|
await attempt(() => sessionManager.destroy(), "SessionManager");
|
|
@@ -3512,7 +3805,9 @@ async function start(opts = {}) {
|
|
|
3512
3805
|
} else {
|
|
3513
3806
|
stopMdns();
|
|
3514
3807
|
}
|
|
3515
|
-
}
|
|
3808
|
+
},
|
|
3809
|
+
openPairing: (duration) => pairingManager.open(duration),
|
|
3810
|
+
closePairing: () => pairingManager.close()
|
|
3516
3811
|
};
|
|
3517
3812
|
}
|
|
3518
3813
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sessix-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"bin": {
|
|
5
5
|
"sessix-server": "./dist/index.js"
|
|
6
6
|
},
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"types": "./dist/server.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/server.d.ts",
|
|
11
12
|
"import": "./dist/server.js",
|
|
12
|
-
"require": "./dist/server.js"
|
|
13
|
-
"types": "./dist/server.d.ts"
|
|
13
|
+
"require": "./dist/server.js"
|
|
14
14
|
},
|
|
15
15
|
"./cli": "./dist/index.js"
|
|
16
16
|
},
|