tmex-cli 0.3.1 → 0.4.1
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/runtime/server.js +1163 -405
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-n4JoyDed.js +26 -0
- package/resources/fe-dist/assets/DevicePage-n4JoyDed.js.map +1 -0
- package/resources/fe-dist/assets/{DevicesPage-C76Xejy5.js → DevicesPage-BwLKaiUR.js} +2 -2
- package/resources/fe-dist/assets/{DevicesPage-C76Xejy5.js.map → DevicesPage-BwLKaiUR.js.map} +1 -1
- package/resources/fe-dist/assets/SettingsPage-hS99lHcp.js +17 -0
- package/resources/fe-dist/assets/SettingsPage-hS99lHcp.js.map +1 -0
- package/resources/fe-dist/assets/index-CJaX5rlK.css +1 -0
- package/resources/fe-dist/assets/{index-Bmahx5fj.js → index-CJyFlAt8.js} +120 -119
- package/resources/fe-dist/assets/index-CJyFlAt8.js.map +1 -0
- package/resources/fe-dist/assets/{select-Wn7lKWHQ.js → select-DGBwxGiK.js} +2 -2
- package/resources/fe-dist/assets/{select-Wn7lKWHQ.js.map → select-DGBwxGiK.js.map} +1 -1
- package/resources/fe-dist/assets/{switch-JVIhfemP.js → switch-CWUBjs7N.js} +2 -2
- package/resources/fe-dist/assets/{switch-JVIhfemP.js.map → switch-CWUBjs7N.js.map} +1 -1
- package/resources/fe-dist/assets/{useValueChanged-DU---PIl.js → useValueChanged-DwJ_SDCu.js} +2 -2
- package/resources/fe-dist/assets/{useValueChanged-DU---PIl.js.map → useValueChanged-DwJ_SDCu.js.map} +1 -1
- package/resources/fe-dist/index.html +2 -2
- package/resources/gateway-drizzle/0002_broad_vengeance.sql +3 -0
- package/resources/gateway-drizzle/meta/0002_snapshot.json +535 -0
- package/resources/gateway-drizzle/meta/_journal.json +7 -0
- package/resources/fe-dist/assets/DevicePage-BTbDSWYN.js +0 -26
- package/resources/fe-dist/assets/DevicePage-BTbDSWYN.js.map +0 -1
- package/resources/fe-dist/assets/SettingsPage-DQ9W4fOo.js +0 -17
- package/resources/fe-dist/assets/SettingsPage-DQ9W4fOo.js.map +0 -1
- package/resources/fe-dist/assets/index-Bmahx5fj.js.map +0 -1
- package/resources/fe-dist/assets/index-CyKyNcdz.css +0 -1
package/dist/runtime/server.js
CHANGED
|
@@ -20281,8 +20281,8 @@ var require_lib3 = __commonJS((exports, module) => {
|
|
|
20281
20281
|
});
|
|
20282
20282
|
|
|
20283
20283
|
// src/runtime/server.ts
|
|
20284
|
-
import { existsSync as
|
|
20285
|
-
import { extname, join as
|
|
20284
|
+
import { existsSync as existsSync4 } from "fs";
|
|
20285
|
+
import { extname, join as join5, normalize, resolve as resolve2, sep } from "path";
|
|
20286
20286
|
|
|
20287
20287
|
// ../../apps/gateway/src/crypto/errors.ts
|
|
20288
20288
|
function contextLabel(context) {
|
|
@@ -20446,8 +20446,11 @@ var I18N_RESOURCES = {
|
|
|
20446
20446
|
siteUrl: "Site URL",
|
|
20447
20447
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
20448
20448
|
bellThrottle: "Bell Throttle (seconds)",
|
|
20449
|
+
notificationThrottle: "Notification Throttle (seconds)",
|
|
20449
20450
|
enableBrowserBellToast: "Enable Browser Bell Toast",
|
|
20451
|
+
enableBrowserNotificationToast: "Enable Browser Notification Toast",
|
|
20450
20452
|
enableTelegramBellPush: "Enable Telegram Bell Push",
|
|
20453
|
+
enableTelegramNotificationPush: "Enable Telegram Notification Push",
|
|
20451
20454
|
sshReconnectRetries: "SSH Reconnect Retries",
|
|
20452
20455
|
sshReconnectDelay: "SSH Reconnect Delay (seconds)",
|
|
20453
20456
|
language: "Language",
|
|
@@ -20591,6 +20594,7 @@ Time: {{time}}`,
|
|
|
20591
20594
|
clickToJump: "Click to jump to corresponding pane",
|
|
20592
20595
|
eventType: {
|
|
20593
20596
|
terminal_bell: "\uD83D\uDD14 Terminal Bell",
|
|
20597
|
+
terminal_notification: "\uD83D\uDD14 Terminal Notification",
|
|
20594
20598
|
tmux_window_close: "\uD83E\uDE9F Window Closed",
|
|
20595
20599
|
tmux_pane_close: "\uD83D\uDCF1 Pane Closed",
|
|
20596
20600
|
device_tmux_missing: "\u26A0\uFE0F Tmux Missing",
|
|
@@ -20609,7 +20613,8 @@ Time: {{time}}`,
|
|
|
20609
20613
|
title: "\uD83D\uDD14 Bell from {{siteName}}: {{terminalTopbarLabel}}",
|
|
20610
20614
|
viewLink: "Click to view",
|
|
20611
20615
|
terminalTopbarLabel: "Window {{window}} \xB7 Pane {{pane}} @ {{device}}"
|
|
20612
|
-
}
|
|
20616
|
+
},
|
|
20617
|
+
telegramNotification: {}
|
|
20613
20618
|
},
|
|
20614
20619
|
sidebar: {
|
|
20615
20620
|
noWindows: "No windows",
|
|
@@ -20773,8 +20778,11 @@ Time: {{time}}`,
|
|
|
20773
20778
|
siteUrl: "\u7AD9\u70B9\u8BBF\u95EE URL",
|
|
20774
20779
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
20775
20780
|
bellThrottle: "Bell \u9891\u63A7\uFF08\u79D2\uFF09",
|
|
20781
|
+
notificationThrottle: "\u901A\u77E5\u9891\u63A7\uFF08\u79D2\uFF09",
|
|
20776
20782
|
enableBrowserBellToast: "\u5F00\u542F\u6D4F\u89C8\u5668 Bell Toast",
|
|
20783
|
+
enableBrowserNotificationToast: "\u5F00\u542F\u6D4F\u89C8\u5668\u901A\u77E5 Toast",
|
|
20777
20784
|
enableTelegramBellPush: "\u5F00\u542F Telegram Bell \u63A8\u9001",
|
|
20785
|
+
enableTelegramNotificationPush: "\u5F00\u542F Telegram \u901A\u77E5\u63A8\u9001",
|
|
20778
20786
|
sshReconnectRetries: "SSH \u91CD\u8FDE\u6B21\u6570",
|
|
20779
20787
|
sshReconnectDelay: "SSH \u91CD\u8FDE\u7B49\u5F85\uFF08\u79D2\uFF09",
|
|
20780
20788
|
language: "\u8BED\u8A00",
|
|
@@ -20918,6 +20926,7 @@ Bot\uFF1A{{botName}}
|
|
|
20918
20926
|
clickToJump: "\u70B9\u51FB\u8DF3\u8F6C\u5230\u5BF9\u5E94 Pane",
|
|
20919
20927
|
eventType: {
|
|
20920
20928
|
terminal_bell: "\uD83D\uDD14 \u7EC8\u7AEF Bell",
|
|
20929
|
+
terminal_notification: "\uD83D\uDD14 \u7EC8\u7AEF\u901A\u77E5",
|
|
20921
20930
|
tmux_window_close: "\uD83E\uDE9F \u7A97\u53E3\u5173\u95ED",
|
|
20922
20931
|
tmux_pane_close: "\uD83D\uDCF1 Pane \u5173\u95ED",
|
|
20923
20932
|
device_tmux_missing: "\u26A0\uFE0F Tmux \u4E0D\u53EF\u7528",
|
|
@@ -20936,7 +20945,8 @@ Bot\uFF1A{{botName}}
|
|
|
20936
20945
|
title: "\uD83D\uDD14 \u6765\u81EA {{siteName}} \u7684 Bell\uFF1A{{terminalTopbarLabel}}",
|
|
20937
20946
|
viewLink: "\u70B9\u51FB\u67E5\u770B",
|
|
20938
20947
|
terminalTopbarLabel: "\u7A97\u53E3 {{window}} \xB7 Pane {{pane}} @ {{device}}"
|
|
20939
|
-
}
|
|
20948
|
+
},
|
|
20949
|
+
telegramNotification: {}
|
|
20940
20950
|
},
|
|
20941
20951
|
sidebar: {
|
|
20942
20952
|
noWindows: "\u6682\u65E0\u7A97\u53E3",
|
|
@@ -21100,8 +21110,11 @@ Bot\uFF1A{{botName}}
|
|
|
21100
21110
|
siteUrl: "\u30B5\u30A4\u30C8 URL",
|
|
21101
21111
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
21102
21112
|
bellThrottle: "\u30D9\u30EB\u5236\u9650\uFF08\u79D2\uFF09",
|
|
21113
|
+
notificationThrottle: "\u901A\u77E5\u5236\u9650\uFF08\u79D2\uFF09",
|
|
21103
21114
|
enableBrowserBellToast: "\u30D6\u30E9\u30A6\u30B6\u30D9\u30EB Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21115
|
+
enableBrowserNotificationToast: "\u30D6\u30E9\u30A6\u30B6\u901A\u77E5 Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21104
21116
|
enableTelegramBellPush: "Telegram \u30D9\u30EB\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21117
|
+
enableTelegramNotificationPush: "Telegram \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21105
21118
|
sshReconnectRetries: "SSH \u518D\u63A5\u7D9A\u8A66\u884C\u56DE\u6570",
|
|
21106
21119
|
sshReconnectDelay: "SSH \u518D\u63A5\u7D9A\u5F85\u6A5F\uFF08\u79D2\uFF09",
|
|
21107
21120
|
language: "\u8A00\u8A9E",
|
|
@@ -21245,6 +21258,7 @@ Bot\uFF1A{{botName}}
|
|
|
21245
21258
|
clickToJump: "\u5BFE\u5FDC\u3059\u308B\u30DA\u30A4\u30F3\u306B\u30B8\u30E3\u30F3\u30D7",
|
|
21246
21259
|
eventType: {
|
|
21247
21260
|
terminal_bell: "\uD83D\uDD14 \u30BF\u30FC\u30DF\u30CA\u30EB\u30D9\u30EB",
|
|
21261
|
+
terminal_notification: "\uD83D\uDD14 \u30BF\u30FC\u30DF\u30CA\u30EB\u901A\u77E5",
|
|
21248
21262
|
tmux_window_close: "\uD83E\uDE9F \u30A6\u30A3\u30F3\u30C9\u30A6\u9589\u3058\u308B",
|
|
21249
21263
|
tmux_pane_close: "\uD83D\uDCF1 \u30DA\u30A4\u30F3\u9589\u3058\u308B",
|
|
21250
21264
|
device_tmux_missing: "\u26A0\uFE0F Tmux \u304C\u3042\u308A\u307E\u305B\u3093",
|
|
@@ -21263,7 +21277,8 @@ Bot\uFF1A{{botName}}
|
|
|
21263
21277
|
title: "\uD83D\uDD14 {{siteName}} \u304B\u3089\u306E\u30D9\u30EB\uFF1A{{terminalTopbarLabel}}",
|
|
21264
21278
|
viewLink: "\u8868\u793A",
|
|
21265
21279
|
terminalTopbarLabel: "\u30A6\u30A3\u30F3\u30C9\u30A6 {{window}} \xB7 \u30DA\u30A4\u30F3 {{pane}} @ {{device}}"
|
|
21266
|
-
}
|
|
21280
|
+
},
|
|
21281
|
+
telegramNotification: {}
|
|
21267
21282
|
},
|
|
21268
21283
|
sidebar: {
|
|
21269
21284
|
noWindows: "\u30A6\u30A3\u30F3\u30C9\u30A6\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
@@ -21550,6 +21565,7 @@ __export(exports_schema, {
|
|
|
21550
21565
|
OptionU32Schema: () => OptionU32Schema,
|
|
21551
21566
|
OptionU16Schema: () => OptionU16Schema,
|
|
21552
21567
|
OptionStringSchema: () => OptionStringSchema,
|
|
21568
|
+
NotificationEventSchema: () => NotificationEventSchema,
|
|
21553
21569
|
LiveResumeSchema: () => LiveResumeSchema,
|
|
21554
21570
|
LayoutChangeEventSchema: () => LayoutChangeEventSchema,
|
|
21555
21571
|
HelloS2CSchema: () => HelloS2CSchema,
|
|
@@ -21776,6 +21792,16 @@ var BellEventSchema = import_zorsh.b.struct({
|
|
|
21776
21792
|
paneIndex: OptionU16Schema,
|
|
21777
21793
|
paneUrl: OptionStringSchema
|
|
21778
21794
|
});
|
|
21795
|
+
var NotificationEventSchema = import_zorsh.b.struct({
|
|
21796
|
+
source: import_zorsh.b.u8(),
|
|
21797
|
+
title: OptionStringSchema,
|
|
21798
|
+
body: import_zorsh.b.string(),
|
|
21799
|
+
windowId: OptionStringSchema,
|
|
21800
|
+
paneId: OptionStringSchema,
|
|
21801
|
+
windowIndex: OptionU16Schema,
|
|
21802
|
+
paneIndex: OptionU16Schema,
|
|
21803
|
+
paneUrl: OptionStringSchema
|
|
21804
|
+
});
|
|
21779
21805
|
// ../shared/src/ws-borsh/codec.ts
|
|
21780
21806
|
var MAGIC = new Uint8Array([84, 88]);
|
|
21781
21807
|
var CURRENT_VERSION = 1;
|
|
@@ -21988,6 +22014,16 @@ function resetChunkStreamId() {
|
|
|
21988
22014
|
nextChunkStreamId = 1;
|
|
21989
22015
|
}
|
|
21990
22016
|
// ../shared/src/ws-borsh/convert.ts
|
|
22017
|
+
var notificationSourceToU8 = {
|
|
22018
|
+
osc9: 1,
|
|
22019
|
+
osc777: 2,
|
|
22020
|
+
osc1337: 3
|
|
22021
|
+
};
|
|
22022
|
+
var notificationSourceFromU8 = {
|
|
22023
|
+
1: "osc9",
|
|
22024
|
+
2: "osc777",
|
|
22025
|
+
3: "osc1337"
|
|
22026
|
+
};
|
|
21991
22027
|
function encodeDeviceEventPayload(payload) {
|
|
21992
22028
|
const eventTypeMap = {
|
|
21993
22029
|
"tmux-missing": 1,
|
|
@@ -22015,7 +22051,8 @@ function encodeTmuxEventPayload(payload) {
|
|
|
22015
22051
|
"pane-active": 7,
|
|
22016
22052
|
"layout-change": 8,
|
|
22017
22053
|
bell: 9,
|
|
22018
|
-
output: 10
|
|
22054
|
+
output: 10,
|
|
22055
|
+
notification: 11
|
|
22019
22056
|
};
|
|
22020
22057
|
const eventData = encodeEventData(payload.type, payload.data);
|
|
22021
22058
|
const wireData = {
|
|
@@ -22083,6 +22120,19 @@ function encodeEventData(type, data) {
|
|
|
22083
22120
|
}
|
|
22084
22121
|
case "output":
|
|
22085
22122
|
return new Uint8Array;
|
|
22123
|
+
case "notification": {
|
|
22124
|
+
const d = data;
|
|
22125
|
+
return NotificationEventSchema.serialize({
|
|
22126
|
+
source: notificationSourceToU8[d.source],
|
|
22127
|
+
title: d.title ?? null,
|
|
22128
|
+
body: d.body,
|
|
22129
|
+
windowId: d.windowId ?? null,
|
|
22130
|
+
paneId: d.paneId ?? null,
|
|
22131
|
+
windowIndex: d.windowIndex ?? null,
|
|
22132
|
+
paneIndex: d.paneIndex ?? null,
|
|
22133
|
+
paneUrl: d.paneUrl ?? null
|
|
22134
|
+
});
|
|
22135
|
+
}
|
|
22086
22136
|
default:
|
|
22087
22137
|
return new Uint8Array;
|
|
22088
22138
|
}
|
|
@@ -22149,9 +22199,13 @@ function decodeTmuxEventPayload(data) {
|
|
|
22149
22199
|
7: "pane-active",
|
|
22150
22200
|
8: "layout-change",
|
|
22151
22201
|
9: "bell",
|
|
22152
|
-
10: "output"
|
|
22202
|
+
10: "output",
|
|
22203
|
+
11: "notification"
|
|
22153
22204
|
};
|
|
22154
|
-
const type = eventTypeMap[wire.eventType]
|
|
22205
|
+
const type = eventTypeMap[wire.eventType];
|
|
22206
|
+
if (!type) {
|
|
22207
|
+
throw new Error(`Unknown tmux event type: ${wire.eventType}`);
|
|
22208
|
+
}
|
|
22155
22209
|
return {
|
|
22156
22210
|
deviceId: wire.deviceId,
|
|
22157
22211
|
type,
|
|
@@ -22189,6 +22243,19 @@ function decodeEventData(type, data) {
|
|
|
22189
22243
|
paneUrl: bell.paneUrl ?? undefined
|
|
22190
22244
|
};
|
|
22191
22245
|
}
|
|
22246
|
+
case "notification": {
|
|
22247
|
+
const notification = NotificationEventSchema.deserialize(data);
|
|
22248
|
+
return {
|
|
22249
|
+
source: notificationSourceFromU8[notification.source] ?? "osc9",
|
|
22250
|
+
title: notification.title ?? undefined,
|
|
22251
|
+
body: notification.body,
|
|
22252
|
+
windowId: notification.windowId ?? undefined,
|
|
22253
|
+
paneId: notification.paneId ?? undefined,
|
|
22254
|
+
windowIndex: notification.windowIndex ?? undefined,
|
|
22255
|
+
paneIndex: notification.paneIndex ?? undefined,
|
|
22256
|
+
paneUrl: notification.paneUrl ?? undefined
|
|
22257
|
+
};
|
|
22258
|
+
}
|
|
22192
22259
|
default:
|
|
22193
22260
|
return {};
|
|
22194
22261
|
}
|
|
@@ -22308,6 +22375,13 @@ var runtimeController = new RuntimeController;
|
|
|
22308
22375
|
function getEnv(key, defaultValue) {
|
|
22309
22376
|
return process.env[key] ?? defaultValue;
|
|
22310
22377
|
}
|
|
22378
|
+
function getBooleanEnv(key, defaultValue) {
|
|
22379
|
+
const value = process.env[key];
|
|
22380
|
+
if (value === undefined) {
|
|
22381
|
+
return defaultValue;
|
|
22382
|
+
}
|
|
22383
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
22384
|
+
}
|
|
22311
22385
|
var config = {
|
|
22312
22386
|
masterKey: process.env.TMEX_MASTER_KEY,
|
|
22313
22387
|
port: Number.parseInt(getEnv("GATEWAY_PORT", "9663"), 10),
|
|
@@ -22315,6 +22389,8 @@ var config = {
|
|
|
22315
22389
|
siteNameDefault: getEnv("TMEX_SITE_NAME", "tmex"),
|
|
22316
22390
|
databaseUrl: getEnv("DATABASE_URL", "./tmex.db"),
|
|
22317
22391
|
bellThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_BELL_THROTTLE_SECONDS", "6"), 10),
|
|
22392
|
+
notificationThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_NOTIFICATION_THROTTLE_SECONDS", "3"), 10),
|
|
22393
|
+
tmuxAllowPassthrough: getBooleanEnv("TMEX_TMUX_ALLOW_PASSTHROUGH", false),
|
|
22318
22394
|
sshReconnectMaxRetriesDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_MAX_RETRIES", "2"), 10),
|
|
22319
22395
|
sshReconnectDelaySecondsDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_DELAY_SECONDS", "10"), 10),
|
|
22320
22396
|
languageDefault: getEnv("TMEX_DEFAULT_LANGUAGE", "en_US"),
|
|
@@ -28753,8 +28829,13 @@ var siteSettings = sqliteTable("site_settings", {
|
|
|
28753
28829
|
siteName: text("site_name").notNull(),
|
|
28754
28830
|
siteUrl: text("site_url").notNull(),
|
|
28755
28831
|
bellThrottleSeconds: integer("bell_throttle_seconds").notNull(),
|
|
28832
|
+
notificationThrottleSeconds: integer("notification_throttle_seconds").notNull().default(3),
|
|
28756
28833
|
enableBrowserBellToast: integer("enable_browser_bell_toast", { mode: "boolean" }).notNull().default(true),
|
|
28834
|
+
enableBrowserNotificationToast: integer("enable_browser_notification_toast", { mode: "boolean" }).notNull().default(true),
|
|
28757
28835
|
enableTelegramBellPush: integer("enable_telegram_bell_push", { mode: "boolean" }).notNull().default(true),
|
|
28836
|
+
enableTelegramNotificationPush: integer("enable_telegram_notification_push", {
|
|
28837
|
+
mode: "boolean"
|
|
28838
|
+
}).notNull().default(true),
|
|
28758
28839
|
sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
|
|
28759
28840
|
sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
|
|
28760
28841
|
language: text("language").notNull().default("en_US"),
|
|
@@ -28869,8 +28950,11 @@ function toSiteSettings(row) {
|
|
|
28869
28950
|
siteName: row.siteName,
|
|
28870
28951
|
siteUrl: row.siteUrl,
|
|
28871
28952
|
bellThrottleSeconds: row.bellThrottleSeconds,
|
|
28953
|
+
notificationThrottleSeconds: row.notificationThrottleSeconds,
|
|
28872
28954
|
enableBrowserBellToast: row.enableBrowserBellToast,
|
|
28955
|
+
enableBrowserNotificationToast: row.enableBrowserNotificationToast,
|
|
28873
28956
|
enableTelegramBellPush: row.enableTelegramBellPush,
|
|
28957
|
+
enableTelegramNotificationPush: row.enableTelegramNotificationPush,
|
|
28874
28958
|
sshReconnectMaxRetries: row.sshReconnectMaxRetries,
|
|
28875
28959
|
sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
|
|
28876
28960
|
language: normalizeLocale(row.language),
|
|
@@ -28921,8 +29005,11 @@ function ensureSiteSettingsInitialized() {
|
|
|
28921
29005
|
siteName: config.siteNameDefault,
|
|
28922
29006
|
siteUrl: config.baseUrl,
|
|
28923
29007
|
bellThrottleSeconds: config.bellThrottleSecondsDefault,
|
|
29008
|
+
notificationThrottleSeconds: config.notificationThrottleSecondsDefault,
|
|
28924
29009
|
enableBrowserBellToast: true,
|
|
29010
|
+
enableBrowserNotificationToast: true,
|
|
28925
29011
|
enableTelegramBellPush: true,
|
|
29012
|
+
enableTelegramNotificationPush: true,
|
|
28926
29013
|
sshReconnectMaxRetries: config.sshReconnectMaxRetriesDefault,
|
|
28927
29014
|
sshReconnectDelaySeconds: config.sshReconnectDelaySecondsDefault,
|
|
28928
29015
|
language: normalizeLocale(config.languageDefault),
|
|
@@ -29048,8 +29135,11 @@ function updateSiteSettings(updates) {
|
|
|
29048
29135
|
siteName: updates.siteName ?? current.siteName,
|
|
29049
29136
|
siteUrl: updates.siteUrl ?? current.siteUrl,
|
|
29050
29137
|
bellThrottleSeconds: updates.bellThrottleSeconds ?? current.bellThrottleSeconds,
|
|
29138
|
+
notificationThrottleSeconds: updates.notificationThrottleSeconds ?? current.notificationThrottleSeconds,
|
|
29051
29139
|
enableBrowserBellToast: updates.enableBrowserBellToast ?? current.enableBrowserBellToast,
|
|
29140
|
+
enableBrowserNotificationToast: updates.enableBrowserNotificationToast ?? current.enableBrowserNotificationToast,
|
|
29052
29141
|
enableTelegramBellPush: updates.enableTelegramBellPush ?? current.enableTelegramBellPush,
|
|
29142
|
+
enableTelegramNotificationPush: updates.enableTelegramNotificationPush ?? current.enableTelegramNotificationPush,
|
|
29053
29143
|
sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
|
|
29054
29144
|
sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
|
|
29055
29145
|
language: updates.language ? normalizeLocale(updates.language) : current.language,
|
|
@@ -29060,8 +29150,11 @@ function updateSiteSettings(updates) {
|
|
|
29060
29150
|
siteName: next.siteName,
|
|
29061
29151
|
siteUrl: next.siteUrl,
|
|
29062
29152
|
bellThrottleSeconds: next.bellThrottleSeconds,
|
|
29153
|
+
notificationThrottleSeconds: next.notificationThrottleSeconds,
|
|
29063
29154
|
enableBrowserBellToast: next.enableBrowserBellToast,
|
|
29155
|
+
enableBrowserNotificationToast: next.enableBrowserNotificationToast,
|
|
29064
29156
|
enableTelegramBellPush: next.enableTelegramBellPush,
|
|
29157
|
+
enableTelegramNotificationPush: next.enableTelegramNotificationPush,
|
|
29065
29158
|
sshReconnectMaxRetries: next.sshReconnectMaxRetries,
|
|
29066
29159
|
sshReconnectDelaySeconds: next.sshReconnectDelaySeconds,
|
|
29067
29160
|
language: next.language,
|
|
@@ -51725,6 +51818,7 @@ class EventNotifier {
|
|
|
51725
51818
|
lastRefresh = 0;
|
|
51726
51819
|
REFRESH_INTERVAL = 60000;
|
|
51727
51820
|
bellThrottleMap = new Map;
|
|
51821
|
+
notificationThrottleMap = new Map;
|
|
51728
51822
|
refreshConfig() {
|
|
51729
51823
|
const now = Date.now();
|
|
51730
51824
|
if (now - this.lastRefresh < this.REFRESH_INTERVAL)
|
|
@@ -51740,8 +51834,14 @@ class EventNotifier {
|
|
|
51740
51834
|
eventType,
|
|
51741
51835
|
timestamp: new Date().toISOString()
|
|
51742
51836
|
};
|
|
51743
|
-
if (eventType === "terminal_bell"
|
|
51744
|
-
|
|
51837
|
+
if (eventType === "terminal_bell") {
|
|
51838
|
+
if (!this.shouldPassBellThrottle(fullEvent)) {
|
|
51839
|
+
return;
|
|
51840
|
+
}
|
|
51841
|
+
} else if (eventType === "terminal_notification") {
|
|
51842
|
+
if (!this.shouldPassNotificationThrottle(fullEvent)) {
|
|
51843
|
+
return;
|
|
51844
|
+
}
|
|
51745
51845
|
}
|
|
51746
51846
|
await Promise.all([
|
|
51747
51847
|
this.sendWebhooks(eventType, fullEvent),
|
|
@@ -51763,6 +51863,22 @@ class EventNotifier {
|
|
|
51763
51863
|
this.bellThrottleMap.set(key, now);
|
|
51764
51864
|
return true;
|
|
51765
51865
|
}
|
|
51866
|
+
shouldPassNotificationThrottle(event) {
|
|
51867
|
+
const settings = getSiteSettings();
|
|
51868
|
+
const throttleMs = Math.max(0, settings.notificationThrottleSeconds) * 1000;
|
|
51869
|
+
if (throttleMs === 0) {
|
|
51870
|
+
return true;
|
|
51871
|
+
}
|
|
51872
|
+
const source = typeof event.payload?.source === "string" ? event.payload.source : "unknown";
|
|
51873
|
+
const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:notification:${source}`;
|
|
51874
|
+
const now = Date.now();
|
|
51875
|
+
const previous = this.notificationThrottleMap.get(key) ?? 0;
|
|
51876
|
+
if (now - previous < throttleMs) {
|
|
51877
|
+
return false;
|
|
51878
|
+
}
|
|
51879
|
+
this.notificationThrottleMap.set(key, now);
|
|
51880
|
+
return true;
|
|
51881
|
+
}
|
|
51766
51882
|
async sendWebhooks(eventType, event) {
|
|
51767
51883
|
const targets = this.webhooks.filter((w) => w.eventMask.includes(eventType));
|
|
51768
51884
|
await Promise.all(targets.map(async (webhook) => {
|
|
@@ -51800,6 +51916,14 @@ class EventNotifier {
|
|
|
51800
51916
|
await telegramService.sendToAuthorizedChats({ text: bellMessage, parseMode: "HTML" });
|
|
51801
51917
|
return;
|
|
51802
51918
|
}
|
|
51919
|
+
if (eventType === "terminal_notification") {
|
|
51920
|
+
if (!settings.enableTelegramNotificationPush) {
|
|
51921
|
+
return;
|
|
51922
|
+
}
|
|
51923
|
+
const notificationMessage = this.formatTelegramNotificationMessage(event);
|
|
51924
|
+
await telegramService.sendToAuthorizedChats({ text: notificationMessage, parseMode: "HTML" });
|
|
51925
|
+
return;
|
|
51926
|
+
}
|
|
51803
51927
|
const message = this.formatTelegramMessage(event, settings);
|
|
51804
51928
|
await telegramService.sendToAuthorizedChats({ text: message });
|
|
51805
51929
|
}
|
|
@@ -51824,11 +51948,34 @@ class EventNotifier {
|
|
|
51824
51948
|
lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(t2("notification.telegramBell.viewLink"))}</a>`);
|
|
51825
51949
|
}
|
|
51826
51950
|
return lines.join(`
|
|
51951
|
+
`);
|
|
51952
|
+
}
|
|
51953
|
+
formatTelegramNotificationMessage(event) {
|
|
51954
|
+
const title = typeof event.payload?.title === "string" ? event.payload.title : "";
|
|
51955
|
+
const body = typeof event.payload?.message === "string" ? event.payload.message : "";
|
|
51956
|
+
const lines = [];
|
|
51957
|
+
if (title) {
|
|
51958
|
+
lines.push(escapeTelegramHtmlText(title));
|
|
51959
|
+
}
|
|
51960
|
+
if (body) {
|
|
51961
|
+
lines.push(escapeTelegramHtmlText(body));
|
|
51962
|
+
}
|
|
51963
|
+
const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
|
|
51964
|
+
const topbarLabel = this.buildTerminalTopbarLabel(event);
|
|
51965
|
+
const footer = `from ${event.site.name}: ${topbarLabel}`;
|
|
51966
|
+
if (paneUrl) {
|
|
51967
|
+
const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
|
|
51968
|
+
lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
|
|
51969
|
+
} else {
|
|
51970
|
+
lines.push("", escapeTelegramHtmlText(footer));
|
|
51971
|
+
}
|
|
51972
|
+
return lines.join(`
|
|
51827
51973
|
`);
|
|
51828
51974
|
}
|
|
51829
51975
|
formatTelegramMessage(event, settings) {
|
|
51830
51976
|
const emojiMap = {
|
|
51831
51977
|
terminal_bell: "\uD83D\uDD14",
|
|
51978
|
+
terminal_notification: "\uD83D\uDD14",
|
|
51832
51979
|
tmux_window_close: "\uD83E\uDE9F",
|
|
51833
51980
|
tmux_pane_close: "\uD83D\uDCF1",
|
|
51834
51981
|
device_tmux_missing: "\u26A0\uFE0F",
|
|
@@ -52075,12 +52222,80 @@ function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
|
52075
52222
|
return chunks;
|
|
52076
52223
|
}
|
|
52077
52224
|
|
|
52078
|
-
// ../../apps/gateway/src/tmux-client/pane-
|
|
52225
|
+
// ../../apps/gateway/src/tmux-client/pane-stream-parser.ts
|
|
52079
52226
|
var decoder = new TextDecoder;
|
|
52080
|
-
|
|
52227
|
+
var MAX_OSC_KIND_BYTES = 16;
|
|
52228
|
+
var MAX_OSC_PAYLOAD_BYTES = 8 * 1024;
|
|
52229
|
+
function createPaneStreamParser(options) {
|
|
52081
52230
|
let phase = "normal";
|
|
52082
52231
|
let oscKind = "";
|
|
52232
|
+
let oscPayloadBytes = [];
|
|
52083
52233
|
let titleBytes = [];
|
|
52234
|
+
let warnedOscPayloadOverflow = false;
|
|
52235
|
+
function resetOscState() {
|
|
52236
|
+
oscKind = "";
|
|
52237
|
+
oscPayloadBytes = [];
|
|
52238
|
+
}
|
|
52239
|
+
function appendOscPayloadByte(byte) {
|
|
52240
|
+
if (oscPayloadBytes.length >= MAX_OSC_PAYLOAD_BYTES) {
|
|
52241
|
+
if (!warnedOscPayloadOverflow) {
|
|
52242
|
+
warnedOscPayloadOverflow = true;
|
|
52243
|
+
console.warn("[tmex] pane stream parser dropped oversized OSC payload");
|
|
52244
|
+
}
|
|
52245
|
+
oscPayloadBytes = [];
|
|
52246
|
+
phase = "osc-body-ignore";
|
|
52247
|
+
return false;
|
|
52248
|
+
}
|
|
52249
|
+
oscPayloadBytes.push(byte);
|
|
52250
|
+
return true;
|
|
52251
|
+
}
|
|
52252
|
+
function emitTitle(bytes) {
|
|
52253
|
+
const title = decoder.decode(new Uint8Array(bytes)).trim();
|
|
52254
|
+
if (!title) {
|
|
52255
|
+
return;
|
|
52256
|
+
}
|
|
52257
|
+
options.onTitle(title);
|
|
52258
|
+
}
|
|
52259
|
+
function emitOsc() {
|
|
52260
|
+
const payload = decoder.decode(new Uint8Array(oscPayloadBytes));
|
|
52261
|
+
switch (oscKind) {
|
|
52262
|
+
case "0":
|
|
52263
|
+
case "1":
|
|
52264
|
+
case "2":
|
|
52265
|
+
emitTitle(oscPayloadBytes);
|
|
52266
|
+
return;
|
|
52267
|
+
case "9":
|
|
52268
|
+
if (/^4(;|$)/.test(payload)) {
|
|
52269
|
+
return;
|
|
52270
|
+
}
|
|
52271
|
+
options.onNotification({ source: "osc9", body: payload });
|
|
52272
|
+
return;
|
|
52273
|
+
case "777": {
|
|
52274
|
+
const verbSeparatorIndex = payload.indexOf(";");
|
|
52275
|
+
const verb = verbSeparatorIndex >= 0 ? payload.slice(0, verbSeparatorIndex) : payload;
|
|
52276
|
+
if (verb !== "notify") {
|
|
52277
|
+
return;
|
|
52278
|
+
}
|
|
52279
|
+
const rest = verbSeparatorIndex >= 0 ? payload.slice(verbSeparatorIndex + 1) : "";
|
|
52280
|
+
const titleSeparatorIndex = rest.indexOf(";");
|
|
52281
|
+
const title = titleSeparatorIndex >= 0 ? rest.slice(0, titleSeparatorIndex) : rest;
|
|
52282
|
+
const body = titleSeparatorIndex >= 0 ? rest.slice(titleSeparatorIndex + 1) : "";
|
|
52283
|
+
options.onNotification({
|
|
52284
|
+
source: "osc777",
|
|
52285
|
+
title: title || undefined,
|
|
52286
|
+
body
|
|
52287
|
+
});
|
|
52288
|
+
return;
|
|
52289
|
+
}
|
|
52290
|
+
case "1337":
|
|
52291
|
+
if (/^RequestAttention=(yes|once|fireworks|true)$/i.test(payload)) {
|
|
52292
|
+
options.onNotification({ source: "osc1337", body: "RequestAttention" });
|
|
52293
|
+
}
|
|
52294
|
+
return;
|
|
52295
|
+
default:
|
|
52296
|
+
return;
|
|
52297
|
+
}
|
|
52298
|
+
}
|
|
52084
52299
|
return {
|
|
52085
52300
|
push(data) {
|
|
52086
52301
|
const output = [];
|
|
@@ -52088,36 +52303,57 @@ function createPaneTitleParser(options) {
|
|
|
52088
52303
|
if (phase === "normal") {
|
|
52089
52304
|
if (byte === 27) {
|
|
52090
52305
|
phase = "esc";
|
|
52091
|
-
|
|
52092
|
-
output.push(byte);
|
|
52306
|
+
continue;
|
|
52093
52307
|
}
|
|
52308
|
+
if (byte === 7) {
|
|
52309
|
+
options.onBell();
|
|
52310
|
+
continue;
|
|
52311
|
+
}
|
|
52312
|
+
output.push(byte);
|
|
52094
52313
|
continue;
|
|
52095
52314
|
}
|
|
52096
52315
|
if (phase === "esc") {
|
|
52097
52316
|
if (byte === 93) {
|
|
52098
|
-
|
|
52099
|
-
|
|
52317
|
+
resetOscState();
|
|
52318
|
+
phase = "osc-params";
|
|
52319
|
+
continue;
|
|
52320
|
+
}
|
|
52321
|
+
if (byte === 107) {
|
|
52100
52322
|
titleBytes = [];
|
|
52323
|
+
phase = "screen-title";
|
|
52101
52324
|
continue;
|
|
52102
52325
|
}
|
|
52103
52326
|
output.push(27, byte);
|
|
52104
52327
|
phase = "normal";
|
|
52105
52328
|
continue;
|
|
52106
52329
|
}
|
|
52107
|
-
if (phase === "osc") {
|
|
52330
|
+
if (phase === "osc-params") {
|
|
52108
52331
|
if (byte === 59) {
|
|
52109
|
-
phase = oscKind === "0" || oscKind === "2" ? "osc-
|
|
52110
|
-
|
|
52111
|
-
|
|
52112
|
-
|
|
52332
|
+
phase = oscKind === "0" || oscKind === "1" || oscKind === "2" || oscKind === "9" || oscKind === "777" || oscKind === "1337" ? "osc-body" : "osc-body-ignore";
|
|
52333
|
+
continue;
|
|
52334
|
+
}
|
|
52335
|
+
if (byte === 7) {
|
|
52336
|
+
emitOsc();
|
|
52337
|
+
resetOscState();
|
|
52338
|
+
phase = "normal";
|
|
52339
|
+
continue;
|
|
52340
|
+
}
|
|
52341
|
+
if (byte === 27) {
|
|
52342
|
+
phase = "osc-st";
|
|
52343
|
+
continue;
|
|
52344
|
+
}
|
|
52345
|
+
if (oscKind.length >= MAX_OSC_KIND_BYTES) {
|
|
52346
|
+
resetOscState();
|
|
52347
|
+
phase = "osc-body-ignore";
|
|
52113
52348
|
continue;
|
|
52114
52349
|
}
|
|
52115
52350
|
oscKind += String.fromCharCode(byte);
|
|
52116
52351
|
continue;
|
|
52117
52352
|
}
|
|
52118
|
-
if (phase === "osc-
|
|
52353
|
+
if (phase === "osc-body") {
|
|
52119
52354
|
if (byte === 7) {
|
|
52120
|
-
|
|
52355
|
+
emitOsc();
|
|
52356
|
+
resetOscState();
|
|
52121
52357
|
phase = "normal";
|
|
52122
52358
|
continue;
|
|
52123
52359
|
}
|
|
@@ -52125,31 +52361,69 @@ function createPaneTitleParser(options) {
|
|
|
52125
52361
|
phase = "osc-st";
|
|
52126
52362
|
continue;
|
|
52127
52363
|
}
|
|
52364
|
+
appendOscPayloadByte(byte);
|
|
52365
|
+
continue;
|
|
52366
|
+
}
|
|
52367
|
+
if (phase === "osc-body-ignore") {
|
|
52368
|
+
if (byte === 7) {
|
|
52369
|
+
resetOscState();
|
|
52370
|
+
phase = "normal";
|
|
52371
|
+
continue;
|
|
52372
|
+
}
|
|
52373
|
+
if (byte === 27) {
|
|
52374
|
+
phase = "osc-st-ignore";
|
|
52375
|
+
}
|
|
52376
|
+
continue;
|
|
52377
|
+
}
|
|
52378
|
+
if (phase === "osc-st") {
|
|
52379
|
+
if (byte === 92) {
|
|
52380
|
+
emitOsc();
|
|
52381
|
+
resetOscState();
|
|
52382
|
+
phase = "normal";
|
|
52383
|
+
continue;
|
|
52384
|
+
}
|
|
52385
|
+
if (!appendOscPayloadByte(27)) {
|
|
52386
|
+
continue;
|
|
52387
|
+
}
|
|
52388
|
+
appendOscPayloadByte(byte);
|
|
52389
|
+
continue;
|
|
52390
|
+
}
|
|
52391
|
+
if (phase === "osc-st-ignore") {
|
|
52392
|
+
if (byte === 92) {
|
|
52393
|
+
resetOscState();
|
|
52394
|
+
phase = "normal";
|
|
52395
|
+
continue;
|
|
52396
|
+
}
|
|
52397
|
+
phase = "osc-body-ignore";
|
|
52398
|
+
continue;
|
|
52399
|
+
}
|
|
52400
|
+
if (phase === "screen-title") {
|
|
52401
|
+
if (byte === 7) {
|
|
52402
|
+
emitTitle(titleBytes);
|
|
52403
|
+
titleBytes = [];
|
|
52404
|
+
phase = "normal";
|
|
52405
|
+
continue;
|
|
52406
|
+
}
|
|
52407
|
+
if (byte === 27) {
|
|
52408
|
+
phase = "screen-title-st";
|
|
52409
|
+
continue;
|
|
52410
|
+
}
|
|
52128
52411
|
titleBytes.push(byte);
|
|
52129
52412
|
continue;
|
|
52130
52413
|
}
|
|
52131
52414
|
if (byte === 92) {
|
|
52132
|
-
emitTitle(
|
|
52415
|
+
emitTitle(titleBytes);
|
|
52416
|
+
titleBytes = [];
|
|
52133
52417
|
phase = "normal";
|
|
52134
52418
|
continue;
|
|
52135
52419
|
}
|
|
52136
52420
|
titleBytes.push(27, byte);
|
|
52137
|
-
phase = "
|
|
52421
|
+
phase = "screen-title";
|
|
52138
52422
|
}
|
|
52139
52423
|
return new Uint8Array(output);
|
|
52140
52424
|
}
|
|
52141
52425
|
};
|
|
52142
52426
|
}
|
|
52143
|
-
function emitTitle(onTitle, titleBytes) {
|
|
52144
|
-
const title = decoder.decode(new Uint8Array(titleBytes)).trim();
|
|
52145
|
-
if (!title) {
|
|
52146
|
-
return;
|
|
52147
|
-
}
|
|
52148
|
-
onTitle(title);
|
|
52149
|
-
}
|
|
52150
|
-
function encoderFromString(value) {
|
|
52151
|
-
return new TextEncoder().encode(value);
|
|
52152
|
-
}
|
|
52153
52427
|
|
|
52154
52428
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
52155
52429
|
function hasRenderableTerminalContent(value) {
|
|
@@ -52193,8 +52467,7 @@ class LocalExternalTmuxConnection {
|
|
|
52193
52467
|
pendingPaneTitles = new Map;
|
|
52194
52468
|
snapshotSession = null;
|
|
52195
52469
|
snapshotWindows = new Map;
|
|
52196
|
-
|
|
52197
|
-
pipeReadAbort = null;
|
|
52470
|
+
paneReaders = new Map;
|
|
52198
52471
|
pipeTransition = Promise.resolve();
|
|
52199
52472
|
inputTransition = Promise.resolve();
|
|
52200
52473
|
hookReadAbort = null;
|
|
@@ -52231,6 +52504,7 @@ class LocalExternalTmuxConnection {
|
|
|
52231
52504
|
});
|
|
52232
52505
|
this.ensureRuntimeDirs();
|
|
52233
52506
|
await this.ensureSession();
|
|
52507
|
+
await this.configureSessionOptions();
|
|
52234
52508
|
if (this.deps.enableHooks) {
|
|
52235
52509
|
await this.startHooks();
|
|
52236
52510
|
}
|
|
@@ -52248,7 +52522,7 @@ class LocalExternalTmuxConnection {
|
|
|
52248
52522
|
}
|
|
52249
52523
|
this.manualDisconnect = true;
|
|
52250
52524
|
this.connected = false;
|
|
52251
|
-
this.
|
|
52525
|
+
this.stopAllPipeReaders();
|
|
52252
52526
|
if (this.deps.enableHooks) {
|
|
52253
52527
|
this.stopHooks();
|
|
52254
52528
|
}
|
|
@@ -52310,7 +52584,7 @@ class LocalExternalTmuxConnection {
|
|
|
52310
52584
|
if (!this.connected) {
|
|
52311
52585
|
return;
|
|
52312
52586
|
}
|
|
52313
|
-
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
52587
|
+
const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
|
|
52314
52588
|
this.runAndRefresh(argv).catch((error) => {
|
|
52315
52589
|
this.callbacks.onError(error);
|
|
52316
52590
|
});
|
|
@@ -52346,6 +52620,26 @@ class LocalExternalTmuxConnection {
|
|
|
52346
52620
|
}
|
|
52347
52621
|
await this.runTmux(["new-session", "-d", "-c", homedir(), "-s", this.sessionName]);
|
|
52348
52622
|
}
|
|
52623
|
+
async configureSessionOptions() {
|
|
52624
|
+
await this.runTmuxAllowFailure([
|
|
52625
|
+
"set-option",
|
|
52626
|
+
"-t",
|
|
52627
|
+
this.sessionName,
|
|
52628
|
+
"-s",
|
|
52629
|
+
"allow-passthrough",
|
|
52630
|
+
config.tmuxAllowPassthrough ? "on" : "off"
|
|
52631
|
+
]);
|
|
52632
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
|
|
52633
|
+
await this.runTmuxAllowFailure([
|
|
52634
|
+
"set-option",
|
|
52635
|
+
"-t",
|
|
52636
|
+
this.sessionName,
|
|
52637
|
+
"-s",
|
|
52638
|
+
"extended-keys-format",
|
|
52639
|
+
"csi-u"
|
|
52640
|
+
]);
|
|
52641
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "on"]);
|
|
52642
|
+
}
|
|
52349
52643
|
ensureRuntimeDirs() {
|
|
52350
52644
|
mkdirSync(this.fsPaths.rootDir, { recursive: true, mode: 448 });
|
|
52351
52645
|
mkdirSync(this.fsPaths.panesDir, { recursive: true, mode: 448 });
|
|
@@ -52381,14 +52675,16 @@ class LocalExternalTmuxConnection {
|
|
|
52381
52675
|
readerProcess.kill();
|
|
52382
52676
|
rmSync(fifoPath, { force: true });
|
|
52383
52677
|
};
|
|
52384
|
-
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
52385
52678
|
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
52386
52679
|
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
52680
|
+
await this.installHook("after-new-window", ["refresh"]);
|
|
52681
|
+
await this.installHook("after-split-window", ["refresh"]);
|
|
52387
52682
|
}
|
|
52388
52683
|
async stopHooks() {
|
|
52389
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
52390
52684
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
52391
52685
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
52686
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
52687
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
52392
52688
|
this.hookReadAbort?.();
|
|
52393
52689
|
this.hookReadAbort = null;
|
|
52394
52690
|
this.hookBuffer = "";
|
|
@@ -52419,22 +52715,9 @@ class LocalExternalTmuxConnection {
|
|
|
52419
52715
|
}
|
|
52420
52716
|
const [type, windowId, paneId] = line.split("\t");
|
|
52421
52717
|
if (type === "bell") {
|
|
52422
|
-
const key = paneId || windowId || "-";
|
|
52423
|
-
const previous = this.bellDedup.get(key) ?? 0;
|
|
52424
|
-
const now = Date.now();
|
|
52425
|
-
if (now - previous >= BELL_DEDUP_WINDOW_MS) {
|
|
52426
|
-
this.bellDedup.set(key, now);
|
|
52427
|
-
this.callbacks.onEvent({
|
|
52428
|
-
type: "bell",
|
|
52429
|
-
data: {
|
|
52430
|
-
windowId: windowId || undefined,
|
|
52431
|
-
paneId: paneId || this.activePaneId || undefined
|
|
52432
|
-
}
|
|
52433
|
-
});
|
|
52434
|
-
}
|
|
52435
52718
|
continue;
|
|
52436
52719
|
}
|
|
52437
|
-
if (type === "pane-exited" || type === "pane-died") {
|
|
52720
|
+
if (type === "pane-exited" || type === "pane-died" || type === "refresh") {
|
|
52438
52721
|
this.requestSnapshot();
|
|
52439
52722
|
}
|
|
52440
52723
|
}
|
|
@@ -52465,7 +52748,6 @@ class LocalExternalTmuxConnection {
|
|
|
52465
52748
|
this.activePaneId = paneId;
|
|
52466
52749
|
await this.runTmux(["select-window", "-t", windowId], true);
|
|
52467
52750
|
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
52468
|
-
await this.startPipeForPane(paneId);
|
|
52469
52751
|
if (size) {
|
|
52470
52752
|
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
52471
52753
|
}
|
|
@@ -52479,8 +52761,8 @@ class LocalExternalTmuxConnection {
|
|
|
52479
52761
|
async capturePaneHistory(paneId) {
|
|
52480
52762
|
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
52481
52763
|
const alternateScreen = mode === "1";
|
|
52482
|
-
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-p"], true)).stdout;
|
|
52483
|
-
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-p", "-q"], true)).stdout;
|
|
52764
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true)).stdout;
|
|
52765
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true)).stdout;
|
|
52484
52766
|
const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
|
|
52485
52767
|
if (history) {
|
|
52486
52768
|
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
@@ -52520,6 +52802,7 @@ class LocalExternalTmuxConnection {
|
|
|
52520
52802
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
52521
52803
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
52522
52804
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
52805
|
+
await this.syncPipeReaders();
|
|
52523
52806
|
this.emitSnapshot();
|
|
52524
52807
|
}
|
|
52525
52808
|
parseSnapshotSession(lines) {
|
|
@@ -52617,69 +52900,122 @@ class LocalExternalTmuxConnection {
|
|
|
52617
52900
|
}
|
|
52618
52901
|
return null;
|
|
52619
52902
|
}
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52903
|
+
recordBell(paneId, windowId) {
|
|
52904
|
+
const key = paneId || windowId || "-";
|
|
52905
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
52906
|
+
const now = Date.now();
|
|
52907
|
+
if (now - previous < BELL_DEDUP_WINDOW_MS) {
|
|
52908
|
+
return;
|
|
52909
|
+
}
|
|
52910
|
+
this.bellDedup.set(key, now);
|
|
52911
|
+
this.callbacks.onEvent({
|
|
52912
|
+
type: "bell",
|
|
52913
|
+
data: {
|
|
52914
|
+
windowId,
|
|
52915
|
+
paneId: paneId || this.activePaneId || undefined
|
|
52916
|
+
}
|
|
52917
|
+
});
|
|
52918
|
+
}
|
|
52919
|
+
emitNotification(paneId, notification) {
|
|
52920
|
+
this.callbacks.onEvent({
|
|
52921
|
+
type: "notification",
|
|
52922
|
+
data: {
|
|
52923
|
+
paneId,
|
|
52924
|
+
...notification
|
|
52925
|
+
}
|
|
52926
|
+
});
|
|
52927
|
+
}
|
|
52928
|
+
getExpectedPaneIds() {
|
|
52929
|
+
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
52930
|
+
}
|
|
52931
|
+
async startPipeForPaneNow(paneId) {
|
|
52932
|
+
if (this.paneReaders.has(paneId)) {
|
|
52933
|
+
return;
|
|
52934
|
+
}
|
|
52935
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
52936
|
+
this.ensureRuntimeDirs();
|
|
52937
|
+
rmSync(fifoPath, { force: true });
|
|
52938
|
+
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
52939
|
+
const parser = createPaneStreamParser({
|
|
52940
|
+
onTitle: (title) => {
|
|
52941
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
52942
|
+
this.requestSnapshot();
|
|
52943
|
+
},
|
|
52944
|
+
onBell: () => {
|
|
52945
|
+
this.recordBell(paneId);
|
|
52946
|
+
},
|
|
52947
|
+
onNotification: (notification) => {
|
|
52948
|
+
this.emitNotification(paneId, notification);
|
|
52624
52949
|
}
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52950
|
+
});
|
|
52951
|
+
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `cat ${quoteShellArg(fifoPath)}`], {
|
|
52952
|
+
stdout: "pipe",
|
|
52953
|
+
stderr: "pipe"
|
|
52954
|
+
});
|
|
52955
|
+
const reader = readerProcess.stdout.getReader();
|
|
52956
|
+
const stopReader = () => {
|
|
52957
|
+
reader.releaseLock();
|
|
52958
|
+
readerProcess.kill();
|
|
52628
52959
|
rmSync(fifoPath, { force: true });
|
|
52629
|
-
|
|
52630
|
-
|
|
52631
|
-
|
|
52632
|
-
|
|
52633
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
|
|
52637
|
-
stdout: "pipe",
|
|
52638
|
-
stderr: "pipe"
|
|
52639
|
-
});
|
|
52640
|
-
const reader = readerProcess.stdout.getReader();
|
|
52641
|
-
(async () => {
|
|
52642
|
-
try {
|
|
52643
|
-
while (true) {
|
|
52644
|
-
const chunk2 = await reader.read();
|
|
52645
|
-
if (chunk2.done) {
|
|
52646
|
-
break;
|
|
52647
|
-
}
|
|
52648
|
-
const raw = chunk2.value;
|
|
52649
|
-
const output = parser.push(raw);
|
|
52650
|
-
if (Array.from(raw).includes(7)) {
|
|
52651
|
-
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
52652
|
-
}
|
|
52653
|
-
if (output.length > 0) {
|
|
52654
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
52655
|
-
}
|
|
52960
|
+
};
|
|
52961
|
+
this.paneReaders.set(paneId, { paneId, fifoPath, stopReader });
|
|
52962
|
+
(async () => {
|
|
52963
|
+
try {
|
|
52964
|
+
while (true) {
|
|
52965
|
+
const chunk2 = await reader.read();
|
|
52966
|
+
if (chunk2.done) {
|
|
52967
|
+
break;
|
|
52656
52968
|
}
|
|
52657
|
-
|
|
52658
|
-
if (
|
|
52659
|
-
this.callbacks.
|
|
52969
|
+
const output = parser.push(chunk2.value);
|
|
52970
|
+
if (output.length > 0) {
|
|
52971
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
52660
52972
|
}
|
|
52661
52973
|
}
|
|
52662
|
-
}
|
|
52663
|
-
|
|
52664
|
-
|
|
52665
|
-
|
|
52666
|
-
|
|
52667
|
-
|
|
52974
|
+
} catch (error) {
|
|
52975
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
52976
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
52977
|
+
}
|
|
52978
|
+
}
|
|
52979
|
+
})();
|
|
52980
|
+
try {
|
|
52668
52981
|
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
52669
|
-
|
|
52670
|
-
|
|
52671
|
-
|
|
52672
|
-
|
|
52673
|
-
|
|
52982
|
+
} catch (error) {
|
|
52983
|
+
this.paneReaders.delete(paneId);
|
|
52984
|
+
stopReader();
|
|
52985
|
+
throw error;
|
|
52986
|
+
}
|
|
52674
52987
|
}
|
|
52675
|
-
async
|
|
52676
|
-
const
|
|
52677
|
-
|
|
52678
|
-
|
|
52679
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
52988
|
+
async stopPipeForPaneNow(paneId) {
|
|
52989
|
+
const handle = this.paneReaders.get(paneId);
|
|
52990
|
+
if (!handle) {
|
|
52991
|
+
return;
|
|
52680
52992
|
}
|
|
52681
|
-
this.
|
|
52682
|
-
this.
|
|
52993
|
+
this.paneReaders.delete(paneId);
|
|
52994
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
52995
|
+
handle.stopReader();
|
|
52996
|
+
}
|
|
52997
|
+
async syncPipeReaders() {
|
|
52998
|
+
const expectedPaneIds = this.getExpectedPaneIds();
|
|
52999
|
+
const expectedSet = new Set(expectedPaneIds);
|
|
53000
|
+
await this.queuePipeTransition(async () => {
|
|
53001
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53002
|
+
if (!expectedSet.has(paneId)) {
|
|
53003
|
+
await this.stopPipeForPaneNow(paneId);
|
|
53004
|
+
}
|
|
53005
|
+
}
|
|
53006
|
+
for (const paneId of expectedPaneIds) {
|
|
53007
|
+
if (!this.paneReaders.has(paneId)) {
|
|
53008
|
+
await this.startPipeForPaneNow(paneId);
|
|
53009
|
+
}
|
|
53010
|
+
}
|
|
53011
|
+
});
|
|
53012
|
+
}
|
|
53013
|
+
async stopAllPipeReaders() {
|
|
53014
|
+
await this.queuePipeTransition(async () => {
|
|
53015
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53016
|
+
await this.stopPipeForPaneNow(paneId);
|
|
53017
|
+
}
|
|
53018
|
+
});
|
|
52683
53019
|
}
|
|
52684
53020
|
queuePipeTransition(task) {
|
|
52685
53021
|
const next = this.pipeTransition.catch(() => {
|
|
@@ -52733,6 +53069,10 @@ class LocalExternalTmuxConnection {
|
|
|
52733
53069
|
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
52734
53070
|
var import_ssh2 = __toESM(require_lib3(), 1);
|
|
52735
53071
|
|
|
53072
|
+
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
53073
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
53074
|
+
import { join as join4 } from "path";
|
|
53075
|
+
|
|
52736
53076
|
// ../../apps/gateway/src/tmux/ssh-auth.ts
|
|
52737
53077
|
function normalizeEnvValue(value) {
|
|
52738
53078
|
const trimmed = value?.trim();
|
|
@@ -52765,6 +53105,219 @@ function resolveSshAgentSocket(authMode, env = process.env) {
|
|
|
52765
53105
|
return;
|
|
52766
53106
|
}
|
|
52767
53107
|
|
|
53108
|
+
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
53109
|
+
function defaultRunSync2(cmd) {
|
|
53110
|
+
const result = Bun.spawnSync(cmd, {
|
|
53111
|
+
env: process.env,
|
|
53112
|
+
stdout: "pipe",
|
|
53113
|
+
stderr: "pipe"
|
|
53114
|
+
});
|
|
53115
|
+
return {
|
|
53116
|
+
exitCode: result.exitCode,
|
|
53117
|
+
stdout: Buffer.from(result.stdout).toString("utf8"),
|
|
53118
|
+
stderr: Buffer.from(result.stderr).toString("utf8")
|
|
53119
|
+
};
|
|
53120
|
+
}
|
|
53121
|
+
function expandHomePath(value, env) {
|
|
53122
|
+
const trimmed = value.trim();
|
|
53123
|
+
if (trimmed === "~") {
|
|
53124
|
+
return env.HOME?.trim() || trimmed;
|
|
53125
|
+
}
|
|
53126
|
+
if (trimmed.startsWith("~/") && env.HOME?.trim()) {
|
|
53127
|
+
return join4(env.HOME.trim(), trimmed.slice(2));
|
|
53128
|
+
}
|
|
53129
|
+
return trimmed;
|
|
53130
|
+
}
|
|
53131
|
+
function parseSshConfigOutput(stdout, env) {
|
|
53132
|
+
let host = "";
|
|
53133
|
+
let port;
|
|
53134
|
+
let username;
|
|
53135
|
+
let identityAgent;
|
|
53136
|
+
const identityFiles = [];
|
|
53137
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
53138
|
+
const line = rawLine.trim();
|
|
53139
|
+
if (!line) {
|
|
53140
|
+
continue;
|
|
53141
|
+
}
|
|
53142
|
+
const firstSpace = line.indexOf(" ");
|
|
53143
|
+
if (firstSpace <= 0) {
|
|
53144
|
+
continue;
|
|
53145
|
+
}
|
|
53146
|
+
const key = line.slice(0, firstSpace).trim().toLowerCase();
|
|
53147
|
+
const value = line.slice(firstSpace + 1).trim();
|
|
53148
|
+
if (!value) {
|
|
53149
|
+
continue;
|
|
53150
|
+
}
|
|
53151
|
+
switch (key) {
|
|
53152
|
+
case "hostname":
|
|
53153
|
+
host = value;
|
|
53154
|
+
break;
|
|
53155
|
+
case "port": {
|
|
53156
|
+
const parsedPort = Number.parseInt(value, 10);
|
|
53157
|
+
port = Number.isNaN(parsedPort) ? undefined : parsedPort;
|
|
53158
|
+
break;
|
|
53159
|
+
}
|
|
53160
|
+
case "user":
|
|
53161
|
+
username = value;
|
|
53162
|
+
break;
|
|
53163
|
+
case "identityagent":
|
|
53164
|
+
identityAgent = value;
|
|
53165
|
+
break;
|
|
53166
|
+
case "identityfile":
|
|
53167
|
+
identityFiles.push(expandHomePath(value, env));
|
|
53168
|
+
break;
|
|
53169
|
+
}
|
|
53170
|
+
}
|
|
53171
|
+
if (!host) {
|
|
53172
|
+
throw new Error("ssh_config_ref_invalid: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230 hostname");
|
|
53173
|
+
}
|
|
53174
|
+
return {
|
|
53175
|
+
host,
|
|
53176
|
+
port,
|
|
53177
|
+
username,
|
|
53178
|
+
identityAgent,
|
|
53179
|
+
identityFiles
|
|
53180
|
+
};
|
|
53181
|
+
}
|
|
53182
|
+
function toSshAuthEnv(env) {
|
|
53183
|
+
return {
|
|
53184
|
+
SSH_AUTH_SOCK: env.SSH_AUTH_SOCK,
|
|
53185
|
+
USER: env.USER,
|
|
53186
|
+
LOGNAME: env.LOGNAME
|
|
53187
|
+
};
|
|
53188
|
+
}
|
|
53189
|
+
function resolveAgentFromConfig(identityAgent, deps) {
|
|
53190
|
+
const trimmed = identityAgent?.trim();
|
|
53191
|
+
if (!trimmed || trimmed.toLowerCase() === "none") {
|
|
53192
|
+
return;
|
|
53193
|
+
}
|
|
53194
|
+
if (trimmed === "SSH_AUTH_SOCK" || trimmed === "$SSH_AUTH_SOCK") {
|
|
53195
|
+
return resolveSshAgentSocket("auto", toSshAuthEnv(deps.env));
|
|
53196
|
+
}
|
|
53197
|
+
const expanded = expandHomePath(trimmed, deps.env);
|
|
53198
|
+
return deps.fileExists(expanded) ? expanded : undefined;
|
|
53199
|
+
}
|
|
53200
|
+
function resolvePrivateKeyFromConfig(identityFiles, deps) {
|
|
53201
|
+
for (const identityFile of identityFiles) {
|
|
53202
|
+
if (!deps.fileExists(identityFile)) {
|
|
53203
|
+
continue;
|
|
53204
|
+
}
|
|
53205
|
+
return deps.readTextFile(identityFile);
|
|
53206
|
+
}
|
|
53207
|
+
return;
|
|
53208
|
+
}
|
|
53209
|
+
function resolveSshConfigRef(device, deps) {
|
|
53210
|
+
const ref = device.sshConfigRef?.trim();
|
|
53211
|
+
if (!ref) {
|
|
53212
|
+
return null;
|
|
53213
|
+
}
|
|
53214
|
+
const result = deps.runSync(["ssh", "-G", ref]);
|
|
53215
|
+
if (result.exitCode !== 0) {
|
|
53216
|
+
const detail = result.stderr.trim() || result.stdout.trim() || ref;
|
|
53217
|
+
throw new Error(`ssh_config_ref_resolve_failed: ${detail}`);
|
|
53218
|
+
}
|
|
53219
|
+
return parseSshConfigOutput(result.stdout, deps.env);
|
|
53220
|
+
}
|
|
53221
|
+
async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
|
|
53222
|
+
const deps = {
|
|
53223
|
+
env: inputDeps.env ?? process.env,
|
|
53224
|
+
runSync: inputDeps.runSync ?? defaultRunSync2,
|
|
53225
|
+
fileExists: inputDeps.fileExists ?? existsSync2,
|
|
53226
|
+
readTextFile: inputDeps.readTextFile ?? ((path) => readFileSync(path, "utf8"))
|
|
53227
|
+
};
|
|
53228
|
+
const sshEnv = toSshAuthEnv(deps.env);
|
|
53229
|
+
const resolvedConfig = resolveSshConfigRef(device, deps);
|
|
53230
|
+
const host = resolvedConfig?.host ?? device.host;
|
|
53231
|
+
const port = resolvedConfig?.port ?? device.port ?? 22;
|
|
53232
|
+
const username = resolvedConfig?.username ?? resolveSshUsername(device.username, device.authMode, sshEnv);
|
|
53233
|
+
if (!host) {
|
|
53234
|
+
throw new Error("SSH device missing host");
|
|
53235
|
+
}
|
|
53236
|
+
const authConfig = {
|
|
53237
|
+
host,
|
|
53238
|
+
port,
|
|
53239
|
+
username
|
|
53240
|
+
};
|
|
53241
|
+
const configAgent = resolveAgentFromConfig(resolvedConfig?.identityAgent, deps);
|
|
53242
|
+
const envAgent = resolveSshAgentSocket("auto", sshEnv);
|
|
53243
|
+
const configPrivateKey = resolvePrivateKeyFromConfig(resolvedConfig?.identityFiles ?? [], deps);
|
|
53244
|
+
switch (device.authMode) {
|
|
53245
|
+
case "password": {
|
|
53246
|
+
if (!device.passwordEnc) {
|
|
53247
|
+
throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
|
|
53248
|
+
}
|
|
53249
|
+
authConfig.password = await decrypt2(device.passwordEnc, {
|
|
53250
|
+
scope: "device",
|
|
53251
|
+
entityId: device.id,
|
|
53252
|
+
field: "password_enc"
|
|
53253
|
+
});
|
|
53254
|
+
break;
|
|
53255
|
+
}
|
|
53256
|
+
case "key": {
|
|
53257
|
+
if (!device.privateKeyEnc) {
|
|
53258
|
+
throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
|
|
53259
|
+
}
|
|
53260
|
+
authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
|
|
53261
|
+
scope: "device",
|
|
53262
|
+
entityId: device.id,
|
|
53263
|
+
field: "private_key_enc"
|
|
53264
|
+
});
|
|
53265
|
+
if (device.privateKeyPassphraseEnc) {
|
|
53266
|
+
authConfig.passphrase = await decrypt2(device.privateKeyPassphraseEnc, {
|
|
53267
|
+
scope: "device",
|
|
53268
|
+
entityId: device.id,
|
|
53269
|
+
field: "private_key_passphrase_enc"
|
|
53270
|
+
});
|
|
53271
|
+
}
|
|
53272
|
+
break;
|
|
53273
|
+
}
|
|
53274
|
+
case "agent": {
|
|
53275
|
+
authConfig.agent = configAgent ?? resolveSshAgentSocket("agent", sshEnv);
|
|
53276
|
+
break;
|
|
53277
|
+
}
|
|
53278
|
+
case "configRef": {
|
|
53279
|
+
if (!resolvedConfig) {
|
|
53280
|
+
throw new Error("ssh_config_ref_missing: SSH Config \u5F15\u7528\u4E0D\u80FD\u4E3A\u7A7A");
|
|
53281
|
+
}
|
|
53282
|
+
if (configAgent ?? envAgent) {
|
|
53283
|
+
authConfig.agent = configAgent ?? envAgent;
|
|
53284
|
+
}
|
|
53285
|
+
if (configPrivateKey) {
|
|
53286
|
+
authConfig.privateKey = configPrivateKey;
|
|
53287
|
+
}
|
|
53288
|
+
if (!authConfig.agent && !authConfig.privateKey) {
|
|
53289
|
+
throw new Error("ssh_config_ref_auth_missing: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08IdentityAgent / IdentityFile / SSH_AUTH_SOCK\uFF09");
|
|
53290
|
+
}
|
|
53291
|
+
break;
|
|
53292
|
+
}
|
|
53293
|
+
case "auto": {
|
|
53294
|
+
if (configAgent ?? envAgent) {
|
|
53295
|
+
authConfig.agent = configAgent ?? envAgent;
|
|
53296
|
+
}
|
|
53297
|
+
if (device.privateKeyEnc) {
|
|
53298
|
+
authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
|
|
53299
|
+
scope: "device",
|
|
53300
|
+
entityId: device.id,
|
|
53301
|
+
field: "private_key_enc"
|
|
53302
|
+
});
|
|
53303
|
+
} else if (configPrivateKey) {
|
|
53304
|
+
authConfig.privateKey = configPrivateKey;
|
|
53305
|
+
} else if (device.passwordEnc) {
|
|
53306
|
+
authConfig.password = await decrypt2(device.passwordEnc, {
|
|
53307
|
+
scope: "device",
|
|
53308
|
+
entityId: device.id,
|
|
53309
|
+
field: "password_enc"
|
|
53310
|
+
});
|
|
53311
|
+
}
|
|
53312
|
+
break;
|
|
53313
|
+
}
|
|
53314
|
+
}
|
|
53315
|
+
if (device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
|
|
53316
|
+
throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
|
|
53317
|
+
}
|
|
53318
|
+
return authConfig;
|
|
53319
|
+
}
|
|
53320
|
+
|
|
52768
53321
|
// ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
|
|
52769
53322
|
function buildSshBootstrapScript() {
|
|
52770
53323
|
return [
|
|
@@ -52810,6 +53363,31 @@ function hasRenderableTerminalContent2(value) {
|
|
|
52810
53363
|
}
|
|
52811
53364
|
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
52812
53365
|
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
53366
|
+
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
53367
|
+
function splitSnapshotFields(line, fieldCount) {
|
|
53368
|
+
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
53369
|
+
if (parts.length <= fieldCount) {
|
|
53370
|
+
return parts;
|
|
53371
|
+
}
|
|
53372
|
+
if (fieldCount === 2) {
|
|
53373
|
+
return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
|
|
53374
|
+
}
|
|
53375
|
+
if (fieldCount === 4) {
|
|
53376
|
+
return [parts[0] ?? "", parts[1] ?? "", parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR), parts.at(-1) ?? ""];
|
|
53377
|
+
}
|
|
53378
|
+
if (fieldCount === 7) {
|
|
53379
|
+
return [
|
|
53380
|
+
parts[0] ?? "",
|
|
53381
|
+
parts[1] ?? "",
|
|
53382
|
+
parts[2] ?? "",
|
|
53383
|
+
parts.slice(3, -3).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
53384
|
+
parts.at(-3) ?? "",
|
|
53385
|
+
parts.at(-2) ?? "",
|
|
53386
|
+
parts.at(-1) ?? ""
|
|
53387
|
+
];
|
|
53388
|
+
}
|
|
53389
|
+
return parts;
|
|
53390
|
+
}
|
|
52813
53391
|
|
|
52814
53392
|
class SshExternalTmuxConnection {
|
|
52815
53393
|
deviceId;
|
|
@@ -52826,8 +53404,7 @@ class SshExternalTmuxConnection {
|
|
|
52826
53404
|
pendingPaneTitles = new Map;
|
|
52827
53405
|
snapshotSession = null;
|
|
52828
53406
|
snapshotWindows = new Map;
|
|
52829
|
-
|
|
52830
|
-
pipeReadAbort = null;
|
|
53407
|
+
paneReaders = new Map;
|
|
52831
53408
|
pipeTransition = Promise.resolve();
|
|
52832
53409
|
hookReadAbort = null;
|
|
52833
53410
|
hookBuffer = "";
|
|
@@ -52873,6 +53450,7 @@ class SshExternalTmuxConnection {
|
|
|
52873
53450
|
await this.openCommandChannel();
|
|
52874
53451
|
await this.ensureRemoteRuntimeDirs();
|
|
52875
53452
|
await this.ensureSession();
|
|
53453
|
+
await this.configureSessionOptions();
|
|
52876
53454
|
await this.startHooks();
|
|
52877
53455
|
this.connected = true;
|
|
52878
53456
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
@@ -52938,7 +53516,7 @@ class SshExternalTmuxConnection {
|
|
|
52938
53516
|
if (!this.connected) {
|
|
52939
53517
|
return;
|
|
52940
53518
|
}
|
|
52941
|
-
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
53519
|
+
const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
|
|
52942
53520
|
this.runAndRefresh(argv).catch((error) => {
|
|
52943
53521
|
this.callbacks.onError(error);
|
|
52944
53522
|
});
|
|
@@ -52971,80 +53549,7 @@ class SshExternalTmuxConnection {
|
|
|
52971
53549
|
if (!this.device) {
|
|
52972
53550
|
throw new Error("SSH device not loaded");
|
|
52973
53551
|
}
|
|
52974
|
-
const
|
|
52975
|
-
const port = this.device.port ?? 22;
|
|
52976
|
-
const username = resolveSshUsername(this.device.username, this.device.authMode);
|
|
52977
|
-
if (this.device.authMode === "configRef" || !host && this.device.sshConfigRef) {
|
|
52978
|
-
throw new Error("ssh_config_ref_not_supported: \u5F53\u524D\u7248\u672C\u6682\u4E0D\u652F\u6301 SSH Config \u5F15\u7528\uFF0C\u8BF7\u6539\u4E3A\u586B\u5199 host + username\uFF0C\u5E76\u9009\u62E9 Agent/\u79C1\u94A5/\u5BC6\u7801\u8BA4\u8BC1");
|
|
52979
|
-
}
|
|
52980
|
-
if (!host) {
|
|
52981
|
-
throw new Error("SSH device missing host");
|
|
52982
|
-
}
|
|
52983
|
-
const authConfig = {
|
|
52984
|
-
host,
|
|
52985
|
-
port,
|
|
52986
|
-
username
|
|
52987
|
-
};
|
|
52988
|
-
switch (this.device.authMode) {
|
|
52989
|
-
case "password": {
|
|
52990
|
-
if (!this.device.passwordEnc) {
|
|
52991
|
-
throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
|
|
52992
|
-
}
|
|
52993
|
-
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
52994
|
-
scope: "device",
|
|
52995
|
-
entityId: this.device.id,
|
|
52996
|
-
field: "password_enc"
|
|
52997
|
-
});
|
|
52998
|
-
break;
|
|
52999
|
-
}
|
|
53000
|
-
case "key": {
|
|
53001
|
-
if (!this.device.privateKeyEnc) {
|
|
53002
|
-
throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
|
|
53003
|
-
}
|
|
53004
|
-
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
53005
|
-
scope: "device",
|
|
53006
|
-
entityId: this.device.id,
|
|
53007
|
-
field: "private_key_enc"
|
|
53008
|
-
});
|
|
53009
|
-
if (this.device.privateKeyPassphraseEnc) {
|
|
53010
|
-
authConfig.passphrase = await this.deps.decrypt(this.device.privateKeyPassphraseEnc, {
|
|
53011
|
-
scope: "device",
|
|
53012
|
-
entityId: this.device.id,
|
|
53013
|
-
field: "private_key_passphrase_enc"
|
|
53014
|
-
});
|
|
53015
|
-
}
|
|
53016
|
-
break;
|
|
53017
|
-
}
|
|
53018
|
-
case "agent": {
|
|
53019
|
-
authConfig.agent = resolveSshAgentSocket("agent");
|
|
53020
|
-
break;
|
|
53021
|
-
}
|
|
53022
|
-
case "auto": {
|
|
53023
|
-
const agentSocket = resolveSshAgentSocket("auto");
|
|
53024
|
-
if (agentSocket) {
|
|
53025
|
-
authConfig.agent = agentSocket;
|
|
53026
|
-
}
|
|
53027
|
-
if (this.device.privateKeyEnc) {
|
|
53028
|
-
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
53029
|
-
scope: "device",
|
|
53030
|
-
entityId: this.device.id,
|
|
53031
|
-
field: "private_key_enc"
|
|
53032
|
-
});
|
|
53033
|
-
} else if (this.device.passwordEnc) {
|
|
53034
|
-
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
53035
|
-
scope: "device",
|
|
53036
|
-
entityId: this.device.id,
|
|
53037
|
-
field: "password_enc"
|
|
53038
|
-
});
|
|
53039
|
-
}
|
|
53040
|
-
break;
|
|
53041
|
-
}
|
|
53042
|
-
case "configRef":
|
|
53043
|
-
break;
|
|
53044
|
-
}
|
|
53045
|
-
if (this.device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
|
|
53046
|
-
throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
|
|
53047
|
-
}
|
|
53552
|
+
const authConfig = await resolveSshConnectConfig(this.device, this.deps.decrypt);
|
|
53048
53553
|
const client = this.deps.createClient();
|
|
53049
53554
|
this.sshClient = client;
|
|
53050
53555
|
await new Promise((resolve, reject) => {
|
|
@@ -53154,6 +53659,26 @@ class SshExternalTmuxConnection {
|
|
|
53154
53659
|
}
|
|
53155
53660
|
await this.runTmux(["new-session", "-d", "-c", this.remoteHomeDir, "-s", this.sessionName]);
|
|
53156
53661
|
}
|
|
53662
|
+
async configureSessionOptions() {
|
|
53663
|
+
await this.runTmuxAllowFailure([
|
|
53664
|
+
"set-option",
|
|
53665
|
+
"-t",
|
|
53666
|
+
this.sessionName,
|
|
53667
|
+
"-s",
|
|
53668
|
+
"allow-passthrough",
|
|
53669
|
+
config.tmuxAllowPassthrough ? "on" : "off"
|
|
53670
|
+
]);
|
|
53671
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
|
|
53672
|
+
await this.runTmuxAllowFailure([
|
|
53673
|
+
"set-option",
|
|
53674
|
+
"-t",
|
|
53675
|
+
this.sessionName,
|
|
53676
|
+
"-s",
|
|
53677
|
+
"extended-keys-format",
|
|
53678
|
+
"csi-u"
|
|
53679
|
+
]);
|
|
53680
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "on"]);
|
|
53681
|
+
}
|
|
53157
53682
|
async startHooks() {
|
|
53158
53683
|
await this.ensureRemoteRuntimeDirs();
|
|
53159
53684
|
const fifoPath = this.fsPaths.hookFifoPath;
|
|
@@ -53172,14 +53697,16 @@ class SshExternalTmuxConnection {
|
|
|
53172
53697
|
stopReader();
|
|
53173
53698
|
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53174
53699
|
};
|
|
53175
|
-
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
53176
53700
|
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
53177
53701
|
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
53702
|
+
await this.installHook("after-new-window", ["refresh"]);
|
|
53703
|
+
await this.installHook("after-split-window", ["refresh"]);
|
|
53178
53704
|
}
|
|
53179
53705
|
async stopHooks() {
|
|
53180
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
53181
53706
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
53182
53707
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
53708
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
53709
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
53183
53710
|
this.hookReadAbort?.();
|
|
53184
53711
|
this.hookReadAbort = null;
|
|
53185
53712
|
this.hookBuffer = "";
|
|
@@ -53210,22 +53737,9 @@ class SshExternalTmuxConnection {
|
|
|
53210
53737
|
}
|
|
53211
53738
|
const [type, windowId, paneId] = line.split("\t");
|
|
53212
53739
|
if (type === "bell") {
|
|
53213
|
-
const key = paneId || windowId || "-";
|
|
53214
|
-
const previous = this.bellDedup.get(key) ?? 0;
|
|
53215
|
-
const now = Date.now();
|
|
53216
|
-
if (now - previous >= BELL_DEDUP_WINDOW_MS2) {
|
|
53217
|
-
this.bellDedup.set(key, now);
|
|
53218
|
-
this.callbacks.onEvent({
|
|
53219
|
-
type: "bell",
|
|
53220
|
-
data: {
|
|
53221
|
-
windowId: windowId || undefined,
|
|
53222
|
-
paneId: paneId || this.activePaneId || undefined
|
|
53223
|
-
}
|
|
53224
|
-
});
|
|
53225
|
-
}
|
|
53226
53740
|
continue;
|
|
53227
53741
|
}
|
|
53228
|
-
if (type === "pane-exited" || type === "pane-died") {
|
|
53742
|
+
if (type === "pane-exited" || type === "pane-died" || type === "refresh") {
|
|
53229
53743
|
this.requestSnapshot();
|
|
53230
53744
|
}
|
|
53231
53745
|
}
|
|
@@ -53256,7 +53770,6 @@ class SshExternalTmuxConnection {
|
|
|
53256
53770
|
this.activePaneId = paneId;
|
|
53257
53771
|
await this.runTmux(["select-window", "-t", windowId], true);
|
|
53258
53772
|
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
53259
|
-
await this.startPipeForPane(paneId);
|
|
53260
53773
|
if (size) {
|
|
53261
53774
|
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
53262
53775
|
}
|
|
@@ -53270,8 +53783,8 @@ class SshExternalTmuxConnection {
|
|
|
53270
53783
|
async capturePaneHistory(paneId) {
|
|
53271
53784
|
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
53272
53785
|
const alternateScreen = mode === "1";
|
|
53273
|
-
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-p"], true, 30000)).stdout;
|
|
53274
|
-
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-p", "-q"], true, 30000)).stdout;
|
|
53786
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true, 30000)).stdout;
|
|
53787
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true, 30000)).stdout;
|
|
53275
53788
|
const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
|
|
53276
53789
|
if (history) {
|
|
53277
53790
|
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
@@ -53287,21 +53800,21 @@ class SshExternalTmuxConnection {
|
|
|
53287
53800
|
"-p",
|
|
53288
53801
|
"-t",
|
|
53289
53802
|
this.sessionName,
|
|
53290
|
-
"#{session_id}
|
|
53803
|
+
"#{session_id}|#{session_name}"
|
|
53291
53804
|
]),
|
|
53292
53805
|
this.runTmuxAllowFailure([
|
|
53293
53806
|
"list-windows",
|
|
53294
53807
|
"-t",
|
|
53295
53808
|
this.sessionName,
|
|
53296
53809
|
"-F",
|
|
53297
|
-
"#{window_id}
|
|
53810
|
+
"#{window_id}|#{window_index}|#{window_name}|#{window_active}"
|
|
53298
53811
|
]),
|
|
53299
53812
|
this.runTmuxAllowFailure([
|
|
53300
53813
|
"list-panes",
|
|
53301
53814
|
"-t",
|
|
53302
53815
|
this.sessionName,
|
|
53303
53816
|
"-F",
|
|
53304
|
-
"#{pane_id}
|
|
53817
|
+
"#{pane_id}|#{window_id}|#{pane_index}|#{pane_title}|#{pane_active}|#{pane_width}|#{pane_height}"
|
|
53305
53818
|
])
|
|
53306
53819
|
]);
|
|
53307
53820
|
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
@@ -53311,6 +53824,7 @@ class SshExternalTmuxConnection {
|
|
|
53311
53824
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
53312
53825
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
53313
53826
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
53827
|
+
await this.syncPipeReaders();
|
|
53314
53828
|
this.emitSnapshot();
|
|
53315
53829
|
}
|
|
53316
53830
|
parseSnapshotSession(lines) {
|
|
@@ -53319,7 +53833,7 @@ class SshExternalTmuxConnection {
|
|
|
53319
53833
|
if (!line.trim()) {
|
|
53320
53834
|
continue;
|
|
53321
53835
|
}
|
|
53322
|
-
const [id, name] = line
|
|
53836
|
+
const [id, name] = splitSnapshotFields(line, 2);
|
|
53323
53837
|
if (id) {
|
|
53324
53838
|
this.snapshotSession = { id, name: name ?? "" };
|
|
53325
53839
|
}
|
|
@@ -53332,7 +53846,7 @@ class SshExternalTmuxConnection {
|
|
|
53332
53846
|
if (!line.trim()) {
|
|
53333
53847
|
continue;
|
|
53334
53848
|
}
|
|
53335
|
-
const [id, indexRaw, name, activeRaw] = line
|
|
53849
|
+
const [id, indexRaw, name, activeRaw] = splitSnapshotFields(line, 4);
|
|
53336
53850
|
if (!id) {
|
|
53337
53851
|
continue;
|
|
53338
53852
|
}
|
|
@@ -53358,7 +53872,7 @@ class SshExternalTmuxConnection {
|
|
|
53358
53872
|
if (!line.trim()) {
|
|
53359
53873
|
continue;
|
|
53360
53874
|
}
|
|
53361
|
-
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line
|
|
53875
|
+
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = splitSnapshotFields(line, 7);
|
|
53362
53876
|
if (!paneId || !windowId) {
|
|
53363
53877
|
continue;
|
|
53364
53878
|
}
|
|
@@ -53408,56 +53922,114 @@ class SshExternalTmuxConnection {
|
|
|
53408
53922
|
}
|
|
53409
53923
|
return null;
|
|
53410
53924
|
}
|
|
53411
|
-
|
|
53412
|
-
|
|
53413
|
-
|
|
53414
|
-
|
|
53925
|
+
recordBell(paneId, windowId) {
|
|
53926
|
+
const key = paneId || windowId || "-";
|
|
53927
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
53928
|
+
const now = Date.now();
|
|
53929
|
+
if (now - previous < BELL_DEDUP_WINDOW_MS2) {
|
|
53930
|
+
return;
|
|
53931
|
+
}
|
|
53932
|
+
this.bellDedup.set(key, now);
|
|
53933
|
+
this.callbacks.onEvent({
|
|
53934
|
+
type: "bell",
|
|
53935
|
+
data: {
|
|
53936
|
+
windowId,
|
|
53937
|
+
paneId: paneId || this.activePaneId || undefined
|
|
53938
|
+
}
|
|
53939
|
+
});
|
|
53940
|
+
}
|
|
53941
|
+
emitNotification(paneId, notification) {
|
|
53942
|
+
this.callbacks.onEvent({
|
|
53943
|
+
type: "notification",
|
|
53944
|
+
data: {
|
|
53945
|
+
paneId,
|
|
53946
|
+
...notification
|
|
53947
|
+
}
|
|
53948
|
+
});
|
|
53949
|
+
}
|
|
53950
|
+
getExpectedPaneIds() {
|
|
53951
|
+
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
53952
|
+
}
|
|
53953
|
+
async startPipeForPaneNow(paneId) {
|
|
53954
|
+
if (this.paneReaders.has(paneId)) {
|
|
53955
|
+
return;
|
|
53956
|
+
}
|
|
53957
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
53958
|
+
await this.ensureRemoteRuntimeDirs();
|
|
53959
|
+
await this.runShell(`rm -f ${quoteShellArg(fifoPath)} && mkfifo ${quoteShellArg(fifoPath)} && chmod 600 ${quoteShellArg(fifoPath)}`);
|
|
53960
|
+
const parser = createPaneStreamParser({
|
|
53961
|
+
onTitle: (title) => {
|
|
53962
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
53963
|
+
this.requestSnapshot();
|
|
53964
|
+
},
|
|
53965
|
+
onBell: () => {
|
|
53966
|
+
this.recordBell(paneId);
|
|
53967
|
+
},
|
|
53968
|
+
onNotification: (notification) => {
|
|
53969
|
+
this.emitNotification(paneId, notification);
|
|
53415
53970
|
}
|
|
53416
|
-
|
|
53417
|
-
|
|
53418
|
-
|
|
53419
|
-
|
|
53420
|
-
|
|
53421
|
-
|
|
53422
|
-
this.pendingPaneTitles.set(paneId, title);
|
|
53423
|
-
this.requestSnapshot();
|
|
53971
|
+
});
|
|
53972
|
+
const stopReader = await this.openReaderChannel(`exec cat ${quoteShellArg(fifoPath)}`, {
|
|
53973
|
+
onData: (raw) => {
|
|
53974
|
+
const output = parser.push(raw);
|
|
53975
|
+
if (output.length > 0) {
|
|
53976
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
53424
53977
|
}
|
|
53425
|
-
}
|
|
53426
|
-
|
|
53427
|
-
|
|
53428
|
-
|
|
53429
|
-
if (Array.from(raw).includes(7)) {
|
|
53430
|
-
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
53431
|
-
}
|
|
53432
|
-
if (output.length > 0) {
|
|
53433
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
53434
|
-
}
|
|
53435
|
-
},
|
|
53436
|
-
onClose: () => {
|
|
53437
|
-
if (!this.manualDisconnect && this.currentPipePaneId === paneId) {
|
|
53438
|
-
this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
|
|
53439
|
-
}
|
|
53978
|
+
},
|
|
53979
|
+
onClose: () => {
|
|
53980
|
+
if (!this.manualDisconnect && this.paneReaders.has(paneId)) {
|
|
53981
|
+
this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
|
|
53440
53982
|
}
|
|
53441
|
-
}
|
|
53442
|
-
|
|
53983
|
+
}
|
|
53984
|
+
});
|
|
53985
|
+
const handle = {
|
|
53986
|
+
paneId,
|
|
53987
|
+
fifoPath,
|
|
53988
|
+
stopReader: () => {
|
|
53443
53989
|
stopReader();
|
|
53444
53990
|
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53445
|
-
}
|
|
53991
|
+
}
|
|
53992
|
+
};
|
|
53993
|
+
this.paneReaders.set(paneId, handle);
|
|
53994
|
+
try {
|
|
53446
53995
|
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
53447
|
-
|
|
53448
|
-
|
|
53449
|
-
|
|
53450
|
-
|
|
53451
|
-
|
|
53996
|
+
} catch (error) {
|
|
53997
|
+
this.paneReaders.delete(paneId);
|
|
53998
|
+
handle.stopReader();
|
|
53999
|
+
throw error;
|
|
54000
|
+
}
|
|
53452
54001
|
}
|
|
53453
|
-
async
|
|
53454
|
-
const
|
|
53455
|
-
|
|
53456
|
-
|
|
53457
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
54002
|
+
async stopPipeForPaneNow(paneId) {
|
|
54003
|
+
const handle = this.paneReaders.get(paneId);
|
|
54004
|
+
if (!handle) {
|
|
54005
|
+
return;
|
|
53458
54006
|
}
|
|
53459
|
-
this.
|
|
53460
|
-
this.
|
|
54007
|
+
this.paneReaders.delete(paneId);
|
|
54008
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
54009
|
+
handle.stopReader();
|
|
54010
|
+
}
|
|
54011
|
+
async syncPipeReaders() {
|
|
54012
|
+
const expectedPaneIds = this.getExpectedPaneIds();
|
|
54013
|
+
const expectedSet = new Set(expectedPaneIds);
|
|
54014
|
+
await this.queuePipeTransition(async () => {
|
|
54015
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54016
|
+
if (!expectedSet.has(paneId)) {
|
|
54017
|
+
await this.stopPipeForPaneNow(paneId);
|
|
54018
|
+
}
|
|
54019
|
+
}
|
|
54020
|
+
for (const paneId of expectedPaneIds) {
|
|
54021
|
+
if (!this.paneReaders.has(paneId)) {
|
|
54022
|
+
await this.startPipeForPaneNow(paneId);
|
|
54023
|
+
}
|
|
54024
|
+
}
|
|
54025
|
+
});
|
|
54026
|
+
}
|
|
54027
|
+
async stopAllPipeReaders() {
|
|
54028
|
+
await this.queuePipeTransition(async () => {
|
|
54029
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54030
|
+
await this.stopPipeForPaneNow(paneId);
|
|
54031
|
+
}
|
|
54032
|
+
});
|
|
53461
54033
|
}
|
|
53462
54034
|
queuePipeTransition(task) {
|
|
53463
54035
|
const next = this.pipeTransition.catch(() => {
|
|
@@ -53627,7 +54199,7 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
53627
54199
|
}
|
|
53628
54200
|
this.connected = false;
|
|
53629
54201
|
this.cleanupPromise = (async () => {
|
|
53630
|
-
await this.
|
|
54202
|
+
await this.stopAllPipeReaders().catch(() => {
|
|
53631
54203
|
return;
|
|
53632
54204
|
});
|
|
53633
54205
|
await this.stopHooks().catch(() => {
|
|
@@ -53702,6 +54274,7 @@ class DeviceSessionRuntime {
|
|
|
53702
54274
|
}
|
|
53703
54275
|
this.closeEmitted = true;
|
|
53704
54276
|
this.terminated = true;
|
|
54277
|
+
this.connectPromise = null;
|
|
53705
54278
|
this.broadcast((listener) => listener.onClose?.());
|
|
53706
54279
|
}
|
|
53707
54280
|
});
|
|
@@ -53713,7 +54286,7 @@ class DeviceSessionRuntime {
|
|
|
53713
54286
|
};
|
|
53714
54287
|
}
|
|
53715
54288
|
async connect() {
|
|
53716
|
-
if (this.terminated
|
|
54289
|
+
if (this.terminated) {
|
|
53717
54290
|
return Promise.reject(new Error(`Device session runtime already terminated: ${this.deviceId}`));
|
|
53718
54291
|
}
|
|
53719
54292
|
if (this.connectPromise) {
|
|
@@ -53721,6 +54294,7 @@ class DeviceSessionRuntime {
|
|
|
53721
54294
|
}
|
|
53722
54295
|
this.connectPromise = this.connection.connect().catch((error) => {
|
|
53723
54296
|
this.terminated = true;
|
|
54297
|
+
this.connectPromise = null;
|
|
53724
54298
|
throw error;
|
|
53725
54299
|
});
|
|
53726
54300
|
return this.connectPromise;
|
|
@@ -53731,6 +54305,7 @@ class DeviceSessionRuntime {
|
|
|
53731
54305
|
}
|
|
53732
54306
|
this.terminated = true;
|
|
53733
54307
|
this.manualDisconnect = true;
|
|
54308
|
+
this.connectPromise = null;
|
|
53734
54309
|
this.connection.disconnect();
|
|
53735
54310
|
}
|
|
53736
54311
|
async shutdown() {
|
|
@@ -53853,7 +54428,7 @@ function pickPaneById(windows, paneId) {
|
|
|
53853
54428
|
}
|
|
53854
54429
|
return null;
|
|
53855
54430
|
}
|
|
53856
|
-
function
|
|
54431
|
+
function resolvePaneContext(options) {
|
|
53857
54432
|
const { deviceId, snapshot, rawData } = options;
|
|
53858
54433
|
const raw = rawData ?? {};
|
|
53859
54434
|
const bellWindowId = typeof raw.windowId === "string" && raw.windowId ? raw.windowId : undefined;
|
|
@@ -53928,6 +54503,34 @@ var defaultDeps = {
|
|
|
53928
54503
|
}
|
|
53929
54504
|
});
|
|
53930
54505
|
},
|
|
54506
|
+
async notifyNotification(context) {
|
|
54507
|
+
const { device, settings, notification } = context;
|
|
54508
|
+
await eventNotifier.notify("terminal_notification", {
|
|
54509
|
+
site: {
|
|
54510
|
+
name: settings.siteName,
|
|
54511
|
+
url: settings.siteUrl
|
|
54512
|
+
},
|
|
54513
|
+
device: {
|
|
54514
|
+
id: device.id,
|
|
54515
|
+
name: device.name,
|
|
54516
|
+
type: device.type,
|
|
54517
|
+
host: device.host
|
|
54518
|
+
},
|
|
54519
|
+
tmux: {
|
|
54520
|
+
sessionName: device.session,
|
|
54521
|
+
windowId: notification.windowId,
|
|
54522
|
+
paneId: notification.paneId,
|
|
54523
|
+
windowIndex: notification.windowIndex,
|
|
54524
|
+
paneIndex: notification.paneIndex,
|
|
54525
|
+
paneUrl: notification.paneUrl
|
|
54526
|
+
},
|
|
54527
|
+
payload: {
|
|
54528
|
+
source: notification.source,
|
|
54529
|
+
title: notification.title,
|
|
54530
|
+
message: notification.body
|
|
54531
|
+
}
|
|
54532
|
+
});
|
|
54533
|
+
},
|
|
53931
54534
|
fallbackReconnectDelayMs: 60000
|
|
53932
54535
|
};
|
|
53933
54536
|
|
|
@@ -54122,29 +54725,186 @@ class PushSupervisor {
|
|
|
54122
54725
|
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
54123
54726
|
return;
|
|
54124
54727
|
}
|
|
54125
|
-
if (event.type !== "bell") {
|
|
54126
|
-
return;
|
|
54127
|
-
}
|
|
54128
54728
|
const device = this.deps.getDevice(deviceId);
|
|
54129
54729
|
if (!device) {
|
|
54130
54730
|
return;
|
|
54131
54731
|
}
|
|
54132
54732
|
const settings = this.deps.getSettings();
|
|
54133
|
-
const
|
|
54733
|
+
const paneContext = resolvePaneContext({
|
|
54134
54734
|
deviceId,
|
|
54135
54735
|
siteUrl: settings.siteUrl,
|
|
54136
54736
|
snapshot: entry.lastSnapshot,
|
|
54137
54737
|
rawData: event.data
|
|
54138
54738
|
});
|
|
54139
|
-
|
|
54140
|
-
|
|
54141
|
-
|
|
54142
|
-
|
|
54143
|
-
|
|
54739
|
+
if (event.type === "bell") {
|
|
54740
|
+
await this.deps.notifyBell({
|
|
54741
|
+
device,
|
|
54742
|
+
settings,
|
|
54743
|
+
bell: paneContext
|
|
54744
|
+
});
|
|
54745
|
+
return;
|
|
54746
|
+
}
|
|
54747
|
+
if (event.type === "notification") {
|
|
54748
|
+
const raw = event.data ?? {};
|
|
54749
|
+
const title = typeof raw.title === "string" && raw.title ? raw.title : undefined;
|
|
54750
|
+
const body = typeof raw.body === "string" ? raw.body : "";
|
|
54751
|
+
if (!title && !body) {
|
|
54752
|
+
return;
|
|
54753
|
+
}
|
|
54754
|
+
const source = raw.source === "osc9" || raw.source === "osc777" || raw.source === "osc1337" ? raw.source : "osc9";
|
|
54755
|
+
await this.deps.notifyNotification({
|
|
54756
|
+
device,
|
|
54757
|
+
settings,
|
|
54758
|
+
notification: {
|
|
54759
|
+
...paneContext,
|
|
54760
|
+
source,
|
|
54761
|
+
title,
|
|
54762
|
+
body
|
|
54763
|
+
}
|
|
54764
|
+
});
|
|
54765
|
+
}
|
|
54144
54766
|
}
|
|
54145
54767
|
}
|
|
54146
54768
|
var pushSupervisor = new PushSupervisor;
|
|
54147
54769
|
|
|
54770
|
+
// ../../apps/gateway/src/ws/error-classify.ts
|
|
54771
|
+
function classifySshError(error) {
|
|
54772
|
+
const msg = error.message.toLowerCase();
|
|
54773
|
+
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
54774
|
+
return {
|
|
54775
|
+
type: "ssh_config_ref_not_supported",
|
|
54776
|
+
messageKey: "sshError.configRefNotSupported"
|
|
54777
|
+
};
|
|
54778
|
+
}
|
|
54779
|
+
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
54780
|
+
return {
|
|
54781
|
+
type: "agent_unavailable",
|
|
54782
|
+
messageKey: "sshError.agentUnavailable"
|
|
54783
|
+
};
|
|
54784
|
+
}
|
|
54785
|
+
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
54786
|
+
return {
|
|
54787
|
+
type: "agent_no_identity",
|
|
54788
|
+
messageKey: "sshError.agentNoIdentities"
|
|
54789
|
+
};
|
|
54790
|
+
}
|
|
54791
|
+
if (msg.includes("permission denied")) {
|
|
54792
|
+
return {
|
|
54793
|
+
type: "auth_failed",
|
|
54794
|
+
messageKey: "sshError.authFailed"
|
|
54795
|
+
};
|
|
54796
|
+
}
|
|
54797
|
+
if (msg.includes("all configured authentication methods failed")) {
|
|
54798
|
+
return {
|
|
54799
|
+
type: "auth_failed",
|
|
54800
|
+
messageKey: "sshError.authFailedGeneric"
|
|
54801
|
+
};
|
|
54802
|
+
}
|
|
54803
|
+
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
54804
|
+
return {
|
|
54805
|
+
type: "network_unreachable",
|
|
54806
|
+
messageKey: "sshError.networkUnreachable"
|
|
54807
|
+
};
|
|
54808
|
+
}
|
|
54809
|
+
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
54810
|
+
return {
|
|
54811
|
+
type: "connection_refused",
|
|
54812
|
+
messageKey: "sshError.connectionRefused"
|
|
54813
|
+
};
|
|
54814
|
+
}
|
|
54815
|
+
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
54816
|
+
return {
|
|
54817
|
+
type: "timeout",
|
|
54818
|
+
messageKey: "sshError.connectionTimeout"
|
|
54819
|
+
};
|
|
54820
|
+
}
|
|
54821
|
+
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
54822
|
+
return {
|
|
54823
|
+
type: "host_not_found",
|
|
54824
|
+
messageKey: "sshError.hostNotFound"
|
|
54825
|
+
};
|
|
54826
|
+
}
|
|
54827
|
+
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
54828
|
+
return {
|
|
54829
|
+
type: "handshake_failed",
|
|
54830
|
+
messageKey: "sshError.handshakeFailed"
|
|
54831
|
+
};
|
|
54832
|
+
}
|
|
54833
|
+
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
54834
|
+
return {
|
|
54835
|
+
type: "tmux_unavailable",
|
|
54836
|
+
messageKey: "sshError.tmuxUnavailable"
|
|
54837
|
+
};
|
|
54838
|
+
}
|
|
54839
|
+
return {
|
|
54840
|
+
type: "unknown",
|
|
54841
|
+
messageKey: "sshError.unknown",
|
|
54842
|
+
messageParams: { message: error.message }
|
|
54843
|
+
};
|
|
54844
|
+
}
|
|
54845
|
+
|
|
54846
|
+
// ../../apps/gateway/src/api/test-connection.ts
|
|
54847
|
+
function inferFailurePhase(errorType) {
|
|
54848
|
+
if (errorType === "tmux_unavailable") {
|
|
54849
|
+
return "bootstrap";
|
|
54850
|
+
}
|
|
54851
|
+
return "connect";
|
|
54852
|
+
}
|
|
54853
|
+
function json(data, status = 200) {
|
|
54854
|
+
return new Response(JSON.stringify(data), {
|
|
54855
|
+
status,
|
|
54856
|
+
headers: {
|
|
54857
|
+
"Content-Type": "application/json"
|
|
54858
|
+
}
|
|
54859
|
+
});
|
|
54860
|
+
}
|
|
54861
|
+
async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
|
|
54862
|
+
const deps = {
|
|
54863
|
+
getDevice: inputDeps.getDevice ?? ((currentDeviceId) => getDeviceById(currentDeviceId)),
|
|
54864
|
+
acquireRuntime: inputDeps.acquireRuntime ?? ((currentDeviceId) => tmuxRuntimeRegistry.acquire(currentDeviceId)),
|
|
54865
|
+
releaseRuntime: inputDeps.releaseRuntime ?? (async (currentDeviceId) => {
|
|
54866
|
+
await tmuxRuntimeRegistry.release(currentDeviceId);
|
|
54867
|
+
}),
|
|
54868
|
+
translate: inputDeps.translate ?? t2
|
|
54869
|
+
};
|
|
54870
|
+
const device = deps.getDevice(deviceId);
|
|
54871
|
+
if (!device) {
|
|
54872
|
+
return json({ error: deps.translate("apiError.deviceNotFound") }, 404);
|
|
54873
|
+
}
|
|
54874
|
+
const classifyErrorResponse = (error) => {
|
|
54875
|
+
const rawMessage = error instanceof Error ? error.message : String(error);
|
|
54876
|
+
const classified = classifySshError(new Error(rawMessage));
|
|
54877
|
+
const payload = {
|
|
54878
|
+
success: false,
|
|
54879
|
+
tmuxAvailable: false,
|
|
54880
|
+
phase: inferFailurePhase(classified.type),
|
|
54881
|
+
errorType: classified.type,
|
|
54882
|
+
message: deps.translate(classified.messageKey, classified.messageParams),
|
|
54883
|
+
rawMessage
|
|
54884
|
+
};
|
|
54885
|
+
return json(payload);
|
|
54886
|
+
};
|
|
54887
|
+
let runtime = null;
|
|
54888
|
+
try {
|
|
54889
|
+
runtime = await deps.acquireRuntime(deviceId);
|
|
54890
|
+
await runtime.connect();
|
|
54891
|
+
runtime.requestSnapshot();
|
|
54892
|
+
const payload = {
|
|
54893
|
+
success: true,
|
|
54894
|
+
tmuxAvailable: true,
|
|
54895
|
+
phase: "ready",
|
|
54896
|
+
message: deps.translate("common.success")
|
|
54897
|
+
};
|
|
54898
|
+
return json(payload);
|
|
54899
|
+
} catch (error) {
|
|
54900
|
+
return classifyErrorResponse(error);
|
|
54901
|
+
} finally {
|
|
54902
|
+
if (runtime) {
|
|
54903
|
+
await deps.releaseRuntime(deviceId, runtime);
|
|
54904
|
+
}
|
|
54905
|
+
}
|
|
54906
|
+
}
|
|
54907
|
+
|
|
54148
54908
|
// ../../apps/gateway/src/api/index.ts
|
|
54149
54909
|
function shouldReconnectPushSupervisor(existing, updates) {
|
|
54150
54910
|
if (updates.type !== undefined && updates.type !== existing.type)
|
|
@@ -54191,18 +54951,37 @@ function normalizeSiteSettingsInput(body) {
|
|
|
54191
54951
|
}
|
|
54192
54952
|
updates.bellThrottleSeconds = value;
|
|
54193
54953
|
}
|
|
54954
|
+
if (body.notificationThrottleSeconds !== undefined) {
|
|
54955
|
+
const value = Math.floor(Number(body.notificationThrottleSeconds));
|
|
54956
|
+
if (Number.isNaN(value) || value < 0 || value > 300) {
|
|
54957
|
+
throw new Error(t2("apiError.bellThrottleInvalid"));
|
|
54958
|
+
}
|
|
54959
|
+
updates.notificationThrottleSeconds = value;
|
|
54960
|
+
}
|
|
54194
54961
|
if (body.enableBrowserBellToast !== undefined) {
|
|
54195
54962
|
if (typeof body.enableBrowserBellToast !== "boolean") {
|
|
54196
54963
|
throw new Error(t2("apiError.invalidRequest"));
|
|
54197
54964
|
}
|
|
54198
54965
|
updates.enableBrowserBellToast = body.enableBrowserBellToast;
|
|
54199
54966
|
}
|
|
54967
|
+
if (body.enableBrowserNotificationToast !== undefined) {
|
|
54968
|
+
if (typeof body.enableBrowserNotificationToast !== "boolean") {
|
|
54969
|
+
throw new Error(t2("apiError.invalidRequest"));
|
|
54970
|
+
}
|
|
54971
|
+
updates.enableBrowserNotificationToast = body.enableBrowserNotificationToast;
|
|
54972
|
+
}
|
|
54200
54973
|
if (body.enableTelegramBellPush !== undefined) {
|
|
54201
54974
|
if (typeof body.enableTelegramBellPush !== "boolean") {
|
|
54202
54975
|
throw new Error(t2("apiError.invalidRequest"));
|
|
54203
54976
|
}
|
|
54204
54977
|
updates.enableTelegramBellPush = body.enableTelegramBellPush;
|
|
54205
54978
|
}
|
|
54979
|
+
if (body.enableTelegramNotificationPush !== undefined) {
|
|
54980
|
+
if (typeof body.enableTelegramNotificationPush !== "boolean") {
|
|
54981
|
+
throw new Error(t2("apiError.invalidRequest"));
|
|
54982
|
+
}
|
|
54983
|
+
updates.enableTelegramNotificationPush = body.enableTelegramNotificationPush;
|
|
54984
|
+
}
|
|
54206
54985
|
if (body.sshReconnectMaxRetries !== undefined) {
|
|
54207
54986
|
const value = Math.floor(Number(body.sshReconnectMaxRetries));
|
|
54208
54987
|
if (Number.isNaN(value) || value < 0 || value > 20) {
|
|
@@ -54293,28 +55072,28 @@ function handleApiRequest(req, _server) {
|
|
|
54293
55072
|
return handleGetManifest(req.method);
|
|
54294
55073
|
}
|
|
54295
55074
|
if (path === "/healthz" && req.method === "GET") {
|
|
54296
|
-
return
|
|
55075
|
+
return json2({ status: "ok", restarting: runtimeController.isRestarting() });
|
|
54297
55076
|
}
|
|
54298
|
-
return
|
|
55077
|
+
return json2({ error: t2("apiError.notFound") }, 404);
|
|
54299
55078
|
}
|
|
54300
55079
|
async function handleGetDevices() {
|
|
54301
55080
|
const devices2 = getAllDevices();
|
|
54302
|
-
return
|
|
55081
|
+
return json2({ devices: devices2 });
|
|
54303
55082
|
}
|
|
54304
55083
|
async function handleGetDevice(id) {
|
|
54305
55084
|
const device = getDeviceById(id);
|
|
54306
55085
|
if (!device) {
|
|
54307
|
-
return
|
|
55086
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54308
55087
|
}
|
|
54309
|
-
return
|
|
55088
|
+
return json2({ device });
|
|
54310
55089
|
}
|
|
54311
55090
|
async function handleCreateDevice(req) {
|
|
54312
55091
|
const body = await req.json();
|
|
54313
55092
|
if (!body.name || !body.type || !body.authMode) {
|
|
54314
|
-
return
|
|
55093
|
+
return json2({ error: t2("apiError.missingFields") }, 400);
|
|
54315
55094
|
}
|
|
54316
55095
|
if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
|
|
54317
|
-
return
|
|
55096
|
+
return json2({ error: t2("apiError.sshRequiresHost") }, 400);
|
|
54318
55097
|
}
|
|
54319
55098
|
const now = new Date().toISOString();
|
|
54320
55099
|
const device = {
|
|
@@ -54335,12 +55114,12 @@ async function handleCreateDevice(req) {
|
|
|
54335
55114
|
};
|
|
54336
55115
|
createDevice(device);
|
|
54337
55116
|
await pushSupervisor.upsert(device.id);
|
|
54338
|
-
return
|
|
55117
|
+
return json2({ device }, 201);
|
|
54339
55118
|
}
|
|
54340
55119
|
async function handleUpdateDevice(req, id) {
|
|
54341
55120
|
const existing = getDeviceById(id);
|
|
54342
55121
|
if (!existing) {
|
|
54343
|
-
return
|
|
55122
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54344
55123
|
}
|
|
54345
55124
|
const body = await req.json();
|
|
54346
55125
|
const updates = {};
|
|
@@ -54370,61 +55149,53 @@ async function handleUpdateDevice(req, id) {
|
|
|
54370
55149
|
await pushSupervisor.reconnect(id);
|
|
54371
55150
|
}
|
|
54372
55151
|
const device = getDeviceById(id);
|
|
54373
|
-
return
|
|
55152
|
+
return json2({ device });
|
|
54374
55153
|
}
|
|
54375
55154
|
async function handleDeleteDevice(id) {
|
|
54376
55155
|
const existing = getDeviceById(id);
|
|
54377
55156
|
if (!existing) {
|
|
54378
|
-
return
|
|
55157
|
+
return json2({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54379
55158
|
}
|
|
54380
55159
|
deleteDevice(id);
|
|
54381
55160
|
pushSupervisor.remove(id);
|
|
54382
|
-
return
|
|
55161
|
+
return json2({ success: true });
|
|
54383
55162
|
}
|
|
54384
55163
|
async function handleTestConnection(id) {
|
|
54385
|
-
|
|
54386
|
-
if (!device) {
|
|
54387
|
-
return json({ error: t2("apiError.deviceNotFound") }, 404);
|
|
54388
|
-
}
|
|
54389
|
-
return json({
|
|
54390
|
-
success: true,
|
|
54391
|
-
tmuxAvailable: false,
|
|
54392
|
-
message: "Connection test not fully implemented yet"
|
|
54393
|
-
});
|
|
55164
|
+
return handleDeviceTestConnection(id);
|
|
54394
55165
|
}
|
|
54395
55166
|
async function handleGetSiteSettings() {
|
|
54396
|
-
return
|
|
55167
|
+
return json2({ settings: getSiteSettings() });
|
|
54397
55168
|
}
|
|
54398
55169
|
async function handleUpdateSiteSettings(req) {
|
|
54399
55170
|
try {
|
|
54400
55171
|
const body = await req.json();
|
|
54401
55172
|
const updates = normalizeSiteSettingsInput(body);
|
|
54402
55173
|
const settings = updateSiteSettings(updates);
|
|
54403
|
-
return
|
|
55174
|
+
return json2({ settings });
|
|
54404
55175
|
} catch (err) {
|
|
54405
|
-
return
|
|
55176
|
+
return json2({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
|
|
54406
55177
|
}
|
|
54407
55178
|
}
|
|
54408
55179
|
async function handleRestartGateway() {
|
|
54409
55180
|
setTimeout(() => {
|
|
54410
55181
|
runtimeController.requestRestart();
|
|
54411
55182
|
}, 50);
|
|
54412
|
-
return
|
|
55183
|
+
return json2({
|
|
54413
55184
|
success: true,
|
|
54414
55185
|
message: t2("settings.restartScheduled")
|
|
54415
55186
|
});
|
|
54416
55187
|
}
|
|
54417
55188
|
async function handleGetTelegramBots() {
|
|
54418
55189
|
const bots = getTelegramBotsWithStats();
|
|
54419
|
-
return
|
|
55190
|
+
return json2({ bots });
|
|
54420
55191
|
}
|
|
54421
55192
|
async function handleCreateTelegramBot(req) {
|
|
54422
55193
|
const body = await req.json();
|
|
54423
55194
|
if (!body.name?.trim()) {
|
|
54424
|
-
return
|
|
55195
|
+
return json2({ error: t2("apiError.botNameRequired") }, 400);
|
|
54425
55196
|
}
|
|
54426
55197
|
if (!body.token?.trim()) {
|
|
54427
|
-
return
|
|
55198
|
+
return json2({ error: t2("apiError.botTokenRequired") }, 400);
|
|
54428
55199
|
}
|
|
54429
55200
|
const now = new Date().toISOString();
|
|
54430
55201
|
createTelegramBot({
|
|
@@ -54438,26 +55209,26 @@ async function handleCreateTelegramBot(req) {
|
|
|
54438
55209
|
updatedAt: now
|
|
54439
55210
|
});
|
|
54440
55211
|
await telegramService.refresh();
|
|
54441
|
-
return
|
|
55212
|
+
return json2({ success: true }, 201);
|
|
54442
55213
|
}
|
|
54443
55214
|
async function handleUpdateTelegramBot(req, botId) {
|
|
54444
55215
|
const existing = getTelegramBotById(botId);
|
|
54445
55216
|
if (!existing) {
|
|
54446
|
-
return
|
|
55217
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54447
55218
|
}
|
|
54448
55219
|
const body = await req.json();
|
|
54449
55220
|
const updates = {};
|
|
54450
55221
|
if (body.name !== undefined) {
|
|
54451
55222
|
const value = body.name.trim();
|
|
54452
55223
|
if (!value) {
|
|
54453
|
-
return
|
|
55224
|
+
return json2({ error: t2("apiError.botNameRequired") }, 400);
|
|
54454
55225
|
}
|
|
54455
55226
|
updates.name = value;
|
|
54456
55227
|
}
|
|
54457
55228
|
if (body.token !== undefined) {
|
|
54458
55229
|
const token = body.token.trim();
|
|
54459
55230
|
if (!token) {
|
|
54460
|
-
return
|
|
55231
|
+
return json2({ error: t2("apiError.botTokenRequired") }, 400);
|
|
54461
55232
|
}
|
|
54462
55233
|
updates.tokenEnc = await encrypt(token);
|
|
54463
55234
|
}
|
|
@@ -54469,69 +55240,69 @@ async function handleUpdateTelegramBot(req, botId) {
|
|
|
54469
55240
|
}
|
|
54470
55241
|
updateTelegramBot(botId, updates);
|
|
54471
55242
|
await telegramService.refresh();
|
|
54472
|
-
return
|
|
55243
|
+
return json2({ success: true });
|
|
54473
55244
|
}
|
|
54474
55245
|
async function handleDeleteTelegramBot(botId) {
|
|
54475
55246
|
const existing = getTelegramBotById(botId);
|
|
54476
55247
|
if (!existing) {
|
|
54477
|
-
return
|
|
55248
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54478
55249
|
}
|
|
54479
55250
|
deleteTelegramBot(botId);
|
|
54480
55251
|
await telegramService.refresh();
|
|
54481
|
-
return
|
|
55252
|
+
return json2({ success: true });
|
|
54482
55253
|
}
|
|
54483
55254
|
async function handleListTelegramChats(botId) {
|
|
54484
55255
|
const existing = getTelegramBotById(botId);
|
|
54485
55256
|
if (!existing) {
|
|
54486
|
-
return
|
|
55257
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54487
55258
|
}
|
|
54488
55259
|
const chats = listTelegramChatsByBot(botId);
|
|
54489
|
-
return
|
|
55260
|
+
return json2({ chats });
|
|
54490
55261
|
}
|
|
54491
55262
|
async function handleApproveTelegramChat(botId, chatId) {
|
|
54492
55263
|
const existing = getTelegramBotById(botId);
|
|
54493
55264
|
if (!existing) {
|
|
54494
|
-
return
|
|
55265
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54495
55266
|
}
|
|
54496
55267
|
const chat = approveTelegramChat(botId, chatId);
|
|
54497
55268
|
if (!chat) {
|
|
54498
|
-
return
|
|
55269
|
+
return json2({ error: t2("apiError.chatNotFound") }, 404);
|
|
54499
55270
|
}
|
|
54500
55271
|
const settings = getSiteSettings();
|
|
54501
55272
|
await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
|
|
54502
55273
|
botName: existing.name,
|
|
54503
55274
|
time: new Date().toLocaleString(toBCP47(settings.language))
|
|
54504
55275
|
}));
|
|
54505
|
-
return
|
|
55276
|
+
return json2({ chat });
|
|
54506
55277
|
}
|
|
54507
55278
|
async function handleDeleteTelegramChat(botId, chatId) {
|
|
54508
55279
|
const existing = getTelegramBotById(botId);
|
|
54509
55280
|
if (!existing) {
|
|
54510
|
-
return
|
|
55281
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54511
55282
|
}
|
|
54512
55283
|
deleteTelegramChat(botId, chatId);
|
|
54513
|
-
return
|
|
55284
|
+
return json2({ success: true });
|
|
54514
55285
|
}
|
|
54515
55286
|
async function handleTestTelegramChat(botId, chatId) {
|
|
54516
55287
|
const bot = getTelegramBotById(botId);
|
|
54517
55288
|
if (!bot) {
|
|
54518
|
-
return
|
|
55289
|
+
return json2({ error: t2("apiError.botNotFound") }, 404);
|
|
54519
55290
|
}
|
|
54520
55291
|
const settings = getSiteSettings();
|
|
54521
55292
|
await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
|
|
54522
55293
|
siteName: settings.siteName,
|
|
54523
55294
|
time: new Date().toLocaleString(toBCP47(settings.language))
|
|
54524
55295
|
}));
|
|
54525
|
-
return
|
|
55296
|
+
return json2({ success: true });
|
|
54526
55297
|
}
|
|
54527
55298
|
async function handleGetWebhooks() {
|
|
54528
55299
|
const webhooks = getAllWebhookEndpoints();
|
|
54529
|
-
return
|
|
55300
|
+
return json2({ webhooks });
|
|
54530
55301
|
}
|
|
54531
55302
|
async function handleCreateWebhook(req) {
|
|
54532
55303
|
const body = await req.json();
|
|
54533
55304
|
if (!body.url || !body.secret) {
|
|
54534
|
-
return
|
|
55305
|
+
return json2({ error: t2("apiError.urlAndSecretRequired") }, 400);
|
|
54535
55306
|
}
|
|
54536
55307
|
const now = new Date().toISOString();
|
|
54537
55308
|
const endpoint = {
|
|
@@ -54544,11 +55315,11 @@ async function handleCreateWebhook(req) {
|
|
|
54544
55315
|
updatedAt: now
|
|
54545
55316
|
};
|
|
54546
55317
|
createWebhookEndpoint(endpoint);
|
|
54547
|
-
return
|
|
55318
|
+
return json2({ webhook: endpoint }, 201);
|
|
54548
55319
|
}
|
|
54549
55320
|
async function handleDeleteWebhook(id) {
|
|
54550
55321
|
deleteWebhookEndpoint(id);
|
|
54551
|
-
return
|
|
55322
|
+
return json2({ success: true });
|
|
54552
55323
|
}
|
|
54553
55324
|
async function handleGetManifest(method) {
|
|
54554
55325
|
const settings = getSiteSettings();
|
|
@@ -54581,7 +55352,7 @@ function manifestJson(data, method) {
|
|
|
54581
55352
|
}
|
|
54582
55353
|
});
|
|
54583
55354
|
}
|
|
54584
|
-
function
|
|
55355
|
+
function json2(data, status = 200, headers = {}) {
|
|
54585
55356
|
return new Response(JSON.stringify(data), {
|
|
54586
55357
|
status,
|
|
54587
55358
|
headers: {
|
|
@@ -54592,7 +55363,7 @@ function json(data, status = 200, headers = {}) {
|
|
|
54592
55363
|
}
|
|
54593
55364
|
|
|
54594
55365
|
// ../../apps/gateway/src/db/migrate.ts
|
|
54595
|
-
import { existsSync as
|
|
55366
|
+
import { existsSync as existsSync3 } from "fs";
|
|
54596
55367
|
import { resolve } from "path";
|
|
54597
55368
|
|
|
54598
55369
|
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/migrator.js
|
|
@@ -54639,7 +55410,7 @@ function resolveMigrationsFolder() {
|
|
|
54639
55410
|
if (fromEnv)
|
|
54640
55411
|
return fromEnv;
|
|
54641
55412
|
const byCwd = resolve(process.cwd(), "drizzle");
|
|
54642
|
-
if (
|
|
55413
|
+
if (existsSync3(byCwd))
|
|
54643
55414
|
return byCwd;
|
|
54644
55415
|
return resolve(import.meta.dir, "../../drizzle");
|
|
54645
55416
|
}
|
|
@@ -54716,7 +55487,8 @@ class SessionStateStore {
|
|
|
54716
55487
|
deviceConnections: new Map,
|
|
54717
55488
|
selectTransactions: new Map,
|
|
54718
55489
|
outputGates: new Map,
|
|
54719
|
-
bellThrottles: new Map
|
|
55490
|
+
bellThrottles: new Map,
|
|
55491
|
+
notificationThrottles: new Map
|
|
54720
55492
|
};
|
|
54721
55493
|
this.states.set(ws, state);
|
|
54722
55494
|
return state;
|
|
@@ -54946,6 +55718,28 @@ class SessionStateStore {
|
|
|
54946
55718
|
ctx.throttleSeconds = throttleSeconds;
|
|
54947
55719
|
return true;
|
|
54948
55720
|
}
|
|
55721
|
+
shouldAllowNotification(ws, deviceId, paneId, source, throttleSeconds) {
|
|
55722
|
+
const state = this.states.get(ws);
|
|
55723
|
+
if (!state)
|
|
55724
|
+
return false;
|
|
55725
|
+
const key = `${deviceId}:${paneId}:${source}`;
|
|
55726
|
+
const now = Date.now();
|
|
55727
|
+
let ctx = state.notificationThrottles.get(key);
|
|
55728
|
+
if (!ctx) {
|
|
55729
|
+
ctx = {
|
|
55730
|
+
lastBellAt: 0,
|
|
55731
|
+
throttleSeconds
|
|
55732
|
+
};
|
|
55733
|
+
state.notificationThrottles.set(key, ctx);
|
|
55734
|
+
}
|
|
55735
|
+
const throttleMs = throttleSeconds * 1000;
|
|
55736
|
+
if (now - ctx.lastBellAt < throttleMs) {
|
|
55737
|
+
return false;
|
|
55738
|
+
}
|
|
55739
|
+
ctx.lastBellAt = now;
|
|
55740
|
+
ctx.throttleSeconds = throttleSeconds;
|
|
55741
|
+
return true;
|
|
55742
|
+
}
|
|
54949
55743
|
cleanupDevice(ws, deviceId) {
|
|
54950
55744
|
const state = this.states.get(ws);
|
|
54951
55745
|
if (!state)
|
|
@@ -54958,6 +55752,11 @@ class SessionStateStore {
|
|
|
54958
55752
|
state.bellThrottles.delete(key);
|
|
54959
55753
|
}
|
|
54960
55754
|
}
|
|
55755
|
+
for (const key of state.notificationThrottles.keys()) {
|
|
55756
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
55757
|
+
state.notificationThrottles.delete(key);
|
|
55758
|
+
}
|
|
55759
|
+
}
|
|
54961
55760
|
}
|
|
54962
55761
|
cleanup(ws) {
|
|
54963
55762
|
this.states.delete(ws);
|
|
@@ -55208,82 +56007,6 @@ class SwitchBarrier {
|
|
|
55208
56007
|
}
|
|
55209
56008
|
var switchBarrier = new SwitchBarrier;
|
|
55210
56009
|
|
|
55211
|
-
// ../../apps/gateway/src/ws/error-classify.ts
|
|
55212
|
-
function classifySshError(error) {
|
|
55213
|
-
const msg = error.message.toLowerCase();
|
|
55214
|
-
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
55215
|
-
return {
|
|
55216
|
-
type: "ssh_config_ref_not_supported",
|
|
55217
|
-
messageKey: "sshError.configRefNotSupported"
|
|
55218
|
-
};
|
|
55219
|
-
}
|
|
55220
|
-
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
55221
|
-
return {
|
|
55222
|
-
type: "agent_unavailable",
|
|
55223
|
-
messageKey: "sshError.agentUnavailable"
|
|
55224
|
-
};
|
|
55225
|
-
}
|
|
55226
|
-
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
55227
|
-
return {
|
|
55228
|
-
type: "agent_no_identity",
|
|
55229
|
-
messageKey: "sshError.agentNoIdentities"
|
|
55230
|
-
};
|
|
55231
|
-
}
|
|
55232
|
-
if (msg.includes("permission denied")) {
|
|
55233
|
-
return {
|
|
55234
|
-
type: "auth_failed",
|
|
55235
|
-
messageKey: "sshError.authFailed"
|
|
55236
|
-
};
|
|
55237
|
-
}
|
|
55238
|
-
if (msg.includes("all configured authentication methods failed")) {
|
|
55239
|
-
return {
|
|
55240
|
-
type: "auth_failed",
|
|
55241
|
-
messageKey: "sshError.authFailedGeneric"
|
|
55242
|
-
};
|
|
55243
|
-
}
|
|
55244
|
-
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
55245
|
-
return {
|
|
55246
|
-
type: "network_unreachable",
|
|
55247
|
-
messageKey: "sshError.networkUnreachable"
|
|
55248
|
-
};
|
|
55249
|
-
}
|
|
55250
|
-
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
55251
|
-
return {
|
|
55252
|
-
type: "connection_refused",
|
|
55253
|
-
messageKey: "sshError.connectionRefused"
|
|
55254
|
-
};
|
|
55255
|
-
}
|
|
55256
|
-
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
55257
|
-
return {
|
|
55258
|
-
type: "timeout",
|
|
55259
|
-
messageKey: "sshError.connectionTimeout"
|
|
55260
|
-
};
|
|
55261
|
-
}
|
|
55262
|
-
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
55263
|
-
return {
|
|
55264
|
-
type: "host_not_found",
|
|
55265
|
-
messageKey: "sshError.hostNotFound"
|
|
55266
|
-
};
|
|
55267
|
-
}
|
|
55268
|
-
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
55269
|
-
return {
|
|
55270
|
-
type: "handshake_failed",
|
|
55271
|
-
messageKey: "sshError.handshakeFailed"
|
|
55272
|
-
};
|
|
55273
|
-
}
|
|
55274
|
-
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
55275
|
-
return {
|
|
55276
|
-
type: "tmux_unavailable",
|
|
55277
|
-
messageKey: "sshError.tmuxUnavailable"
|
|
55278
|
-
};
|
|
55279
|
-
}
|
|
55280
|
-
return {
|
|
55281
|
-
type: "unknown",
|
|
55282
|
-
messageKey: "sshError.unknown",
|
|
55283
|
-
messageParams: { message: error.message }
|
|
55284
|
-
};
|
|
55285
|
-
}
|
|
55286
|
-
|
|
55287
56010
|
// ../../apps/gateway/src/ws/index.ts
|
|
55288
56011
|
var defaultDeps2 = {
|
|
55289
56012
|
acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
|
|
@@ -55802,13 +56525,21 @@ class WebSocketServer {
|
|
|
55802
56525
|
return;
|
|
55803
56526
|
this.scheduleSnapshot(deviceId);
|
|
55804
56527
|
const extendedEvent = await this.extendTmuxEvent(deviceId, event);
|
|
56528
|
+
const settings = getSiteSettings();
|
|
56529
|
+
if (extendedEvent.type === "notification") {
|
|
56530
|
+
const data = extendedEvent.data ?? {};
|
|
56531
|
+
const title = typeof data.title === "string" && data.title ? data.title : "";
|
|
56532
|
+
const body = typeof data.body === "string" ? data.body : "";
|
|
56533
|
+
if (!title && !body) {
|
|
56534
|
+
return;
|
|
56535
|
+
}
|
|
56536
|
+
}
|
|
55805
56537
|
const payloadBytes = exports_ws_borsh.encodeTmuxEventPayload({
|
|
55806
56538
|
deviceId,
|
|
55807
56539
|
type: extendedEvent.type,
|
|
55808
56540
|
data: extendedEvent.data
|
|
55809
56541
|
});
|
|
55810
56542
|
if (extendedEvent.type === "bell") {
|
|
55811
|
-
const settings = getSiteSettings();
|
|
55812
56543
|
const data = extendedEvent.data ?? {};
|
|
55813
56544
|
const paneId = typeof data.paneId === "string" && data.paneId ? data.paneId : "-";
|
|
55814
56545
|
for (const client of entry.clients) {
|
|
@@ -55819,25 +56550,52 @@ class WebSocketServer {
|
|
|
55819
56550
|
}
|
|
55820
56551
|
return;
|
|
55821
56552
|
}
|
|
56553
|
+
if (extendedEvent.type === "notification") {
|
|
56554
|
+
const data = extendedEvent.data ?? {};
|
|
56555
|
+
const paneId = typeof data.paneId === "string" && data.paneId ? data.paneId : "-";
|
|
56556
|
+
const source = typeof data.source === "string" && data.source ? data.source : "osc9";
|
|
56557
|
+
for (const client of entry.clients) {
|
|
56558
|
+
if (!sessionStateStore.shouldAllowNotification(client, deviceId, paneId, source, settings.notificationThrottleSeconds)) {
|
|
56559
|
+
continue;
|
|
56560
|
+
}
|
|
56561
|
+
this.sendEnvelope(client, exports_ws_borsh.KIND_TMUX_EVENT, payloadBytes);
|
|
56562
|
+
}
|
|
56563
|
+
return;
|
|
56564
|
+
}
|
|
55822
56565
|
for (const client of entry.clients) {
|
|
55823
56566
|
this.sendEnvelope(client, exports_ws_borsh.KIND_TMUX_EVENT, payloadBytes);
|
|
55824
56567
|
}
|
|
55825
56568
|
}
|
|
55826
56569
|
async extendTmuxEvent(deviceId, event) {
|
|
55827
|
-
if (event.type !== "bell") {
|
|
56570
|
+
if (event.type !== "bell" && event.type !== "notification") {
|
|
55828
56571
|
return event;
|
|
55829
56572
|
}
|
|
55830
56573
|
const settings = getSiteSettings();
|
|
55831
56574
|
const snapshot = this.connections.get(deviceId)?.lastSnapshot ?? null;
|
|
55832
|
-
const
|
|
56575
|
+
const paneContext = resolvePaneContext({
|
|
55833
56576
|
deviceId,
|
|
55834
56577
|
siteUrl: settings.siteUrl,
|
|
55835
56578
|
snapshot,
|
|
55836
56579
|
rawData: event.data
|
|
55837
56580
|
});
|
|
56581
|
+
if (event.type === "bell") {
|
|
56582
|
+
return {
|
|
56583
|
+
type: "bell",
|
|
56584
|
+
data: paneContext
|
|
56585
|
+
};
|
|
56586
|
+
}
|
|
56587
|
+
const raw = event.data ?? {};
|
|
56588
|
+
const source = raw.source === "osc9" || raw.source === "osc777" || raw.source === "osc1337" ? raw.source : "osc9";
|
|
56589
|
+
const title = typeof raw.title === "string" && raw.title ? raw.title : undefined;
|
|
56590
|
+
const body = typeof raw.body === "string" ? raw.body : "";
|
|
55838
56591
|
return {
|
|
55839
|
-
type: "
|
|
55840
|
-
data
|
|
56592
|
+
type: "notification",
|
|
56593
|
+
data: {
|
|
56594
|
+
...paneContext,
|
|
56595
|
+
source,
|
|
56596
|
+
title,
|
|
56597
|
+
body
|
|
56598
|
+
}
|
|
55841
56599
|
};
|
|
55842
56600
|
}
|
|
55843
56601
|
broadcastStateSnapshot(deviceId, payload) {
|
|
@@ -56302,9 +57060,9 @@ async function serveFrontend(req, staticRoot) {
|
|
|
56302
57060
|
if (!requestedPath) {
|
|
56303
57061
|
return new Response(t3("runtime.forbidden"), { status: 403 });
|
|
56304
57062
|
}
|
|
56305
|
-
const indexPath =
|
|
56306
|
-
const targetPath =
|
|
56307
|
-
if (!
|
|
57063
|
+
const indexPath = join5(staticRoot, "index.html");
|
|
57064
|
+
const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
|
|
57065
|
+
if (!existsSync4(targetPath)) {
|
|
56308
57066
|
return new Response(t3("runtime.frontendMissing"), { status: 500 });
|
|
56309
57067
|
}
|
|
56310
57068
|
const headers = new Headers;
|