@wangyaoshen/remux 0.3.9-dev.390cb29 → 0.3.10-dev.574c4d2
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/apps/macos/Package.swift +5 -0
- package/apps/macos/Sources/Remux/AppCommand.swift +114 -0
- package/apps/macos/Sources/Remux/AppDelegate.swift +26 -0
- package/apps/macos/Sources/Remux/MainContentView.swift +56 -0
- package/apps/macos/Sources/Remux/MenuBarManager.swift +18 -26
- package/apps/macos/Sources/Remux/NotificationManager.swift +52 -7
- package/apps/macos/Sources/Remux/Views/SplitTree/SplitView.swift +1 -1
- package/apps/macos/Sources/Remux/Views/Terminal/GhosttyNativeView.swift +10 -4
- package/apps/macos/Sources/Remux/Views/Terminal/TerminalContainerView.swift +35 -5
- package/apps/macos/Sources/Remux/WindowObserver.swift +38 -0
- package/apps/macos/Tests/RemuxTests/AppCommandTests.swift +30 -0
- package/apps/macos/Tests/RemuxTests/NotificationManagerTests.swift +28 -0
- package/package.json +1 -1
- package/packages/RemuxKit/Sources/RemuxKit/Storage/KeychainStore.swift +3 -3
- package/packages/RemuxKit/Tests/RemuxKitTests/KeychainStoreTests.swift +3 -3
- package/pty-daemon.js +17 -11
- package/server.js +250 -1467
- package/services/discovery/local-server.js +98 -0
- package/services/discovery/worker.js +125 -0
- package/services/discovery/wrangler.toml +7 -0
- package/src/pty-daemon.ts +17 -11
- package/src/server.ts +205 -1458
- package/src/session.ts +42 -4
- package/tests/auth.test.js +1 -1
- package/tests/e2e/app.spec.js +113 -288
- package/tests/pty-daemon.test.js +20 -1
- package/tests/server.test.js +49 -11
- package/vitest.config.js +1 -0
package/tests/pty-daemon.test.js
CHANGED
|
@@ -7,6 +7,7 @@ import net from "net";
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
|
+
import pty from "node-pty";
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const DAEMON_SCRIPT = path.join(__dirname, "..", "pty-daemon.js");
|
|
@@ -21,6 +22,24 @@ const TAG_SNAPSHOT_REQ = 0x06;
|
|
|
21
22
|
const TAG_SNAPSHOT_RES = 0x07;
|
|
22
23
|
const TAG_SHUTDOWN = 0xff;
|
|
23
24
|
|
|
25
|
+
function supportsNodePty() {
|
|
26
|
+
try {
|
|
27
|
+
const proc = pty.spawn("/bin/sh", ["-lc", "exit 0"], {
|
|
28
|
+
name: "xterm-256color",
|
|
29
|
+
cols: 80,
|
|
30
|
+
rows: 24,
|
|
31
|
+
cwd: "/tmp",
|
|
32
|
+
env: process.env,
|
|
33
|
+
});
|
|
34
|
+
proc.kill();
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const describePtyLifecycle = supportsNodePty() ? describe : describe.skip;
|
|
42
|
+
|
|
24
43
|
function encodeFrame(tag, payload) {
|
|
25
44
|
const data =
|
|
26
45
|
typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
@@ -137,7 +156,7 @@ describe("TLV Frame Codec", () => {
|
|
|
137
156
|
});
|
|
138
157
|
});
|
|
139
158
|
|
|
140
|
-
|
|
159
|
+
describePtyLifecycle("PTY Daemon Lifecycle", () => {
|
|
141
160
|
let daemon;
|
|
142
161
|
let socketPath;
|
|
143
162
|
|
package/tests/server.test.js
CHANGED
|
@@ -11,6 +11,7 @@ import WebSocket from "ws";
|
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { homedir } from "os";
|
|
14
|
+
import pty from "node-pty";
|
|
14
15
|
|
|
15
16
|
const PORT = 19876 + Math.floor(Math.random() * 1000); // randomized test port
|
|
16
17
|
const TOKEN = "test-token-" + Date.now();
|
|
@@ -20,6 +21,24 @@ const PERSIST_FILE = path.join(PERSIST_DIR, `sessions-${INSTANCE_ID}.json`);
|
|
|
20
21
|
const DB_FILE = path.join(PERSIST_DIR, `remux-${INSTANCE_ID}.db`);
|
|
21
22
|
let serverProc;
|
|
22
23
|
|
|
24
|
+
function supportsNodePty() {
|
|
25
|
+
try {
|
|
26
|
+
const proc = pty.spawn("/bin/sh", ["-lc", "exit 0"], {
|
|
27
|
+
name: "xterm-256color",
|
|
28
|
+
cols: 80,
|
|
29
|
+
rows: 24,
|
|
30
|
+
cwd: "/tmp",
|
|
31
|
+
env: process.env,
|
|
32
|
+
});
|
|
33
|
+
proc.kill();
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const PTY_SUPPORTED = supportsNodePty();
|
|
41
|
+
|
|
23
42
|
function httpGet(urlPath) {
|
|
24
43
|
return new Promise((resolve, reject) => {
|
|
25
44
|
http.get(`http://localhost:${PORT}${urlPath}`, (res) => {
|
|
@@ -113,7 +132,7 @@ beforeAll(async () => {
|
|
|
113
132
|
// Explicitly remove REMUX_PASSWORD to avoid env leaking from parent
|
|
114
133
|
const cleanEnv = { ...process.env };
|
|
115
134
|
delete cleanEnv.REMUX_PASSWORD;
|
|
116
|
-
serverProc = spawn(
|
|
135
|
+
serverProc = spawn(process.execPath, ["server.js"], {
|
|
117
136
|
env: {
|
|
118
137
|
...cleanEnv,
|
|
119
138
|
PORT: String(PORT),
|
|
@@ -185,6 +204,15 @@ describe("HTTP", () => {
|
|
|
185
204
|
expect(res.body).toContain("<title>Remux</title>");
|
|
186
205
|
});
|
|
187
206
|
|
|
207
|
+
it("serves emergency mode shell without workspace or device chrome", async () => {
|
|
208
|
+
const res = await httpGet(`/?token=${TOKEN}`);
|
|
209
|
+
expect(res.status).toBe(200);
|
|
210
|
+
expect(res.body).toContain("cmd-input");
|
|
211
|
+
expect(res.body).not.toContain("Workspace");
|
|
212
|
+
expect(res.body).not.toContain("Devices");
|
|
213
|
+
expect(res.body).not.toContain("Enable Notifications");
|
|
214
|
+
});
|
|
215
|
+
|
|
188
216
|
it("serves ghostty-web JS", async () => {
|
|
189
217
|
const res = await httpGet("/dist/ghostty-web.js");
|
|
190
218
|
expect(res.status).toBe(200);
|
|
@@ -513,16 +541,18 @@ describe("client connection state", () => {
|
|
|
513
541
|
const ws2 = await connectAuthed();
|
|
514
542
|
|
|
515
543
|
// ws1 attaches first (active)
|
|
516
|
-
await sendAndCollect(
|
|
544
|
+
const msgs1 = await sendAndCollect(
|
|
517
545
|
ws1,
|
|
518
546
|
{ type: "attach_first", session: "main", cols: 80, rows: 24 },
|
|
519
547
|
{ timeout: 3000 },
|
|
520
548
|
);
|
|
549
|
+
const att1 = msgs1.find((m) => m.type === "attached");
|
|
550
|
+
expect(att1).toBeDefined();
|
|
521
551
|
|
|
522
552
|
// ws2 attaches to same tab (observer)
|
|
523
553
|
const msgs2 = await sendAndCollect(
|
|
524
554
|
ws2,
|
|
525
|
-
{ type: "
|
|
555
|
+
{ type: "attach_tab", tabId: att1.tabId, cols: 80, rows: 24 },
|
|
526
556
|
{ timeout: 3000 },
|
|
527
557
|
);
|
|
528
558
|
const att2 = msgs2.find((m) => m.type === "attached");
|
|
@@ -552,11 +582,12 @@ describe("client connection state", () => {
|
|
|
552
582
|
const att1 = msgs1.find((m) => m.type === "attached");
|
|
553
583
|
expect(att1.role).toBe("active");
|
|
554
584
|
const clientId1 = att1.clientId;
|
|
585
|
+
const tabId = att1.tabId;
|
|
555
586
|
|
|
556
587
|
// ws2 attaches (observer)
|
|
557
588
|
const msgs2 = await sendAndCollect(
|
|
558
589
|
ws2,
|
|
559
|
-
{ type: "
|
|
590
|
+
{ type: "attach_tab", tabId, cols: 80, rows: 24 },
|
|
560
591
|
{ timeout: 3000 },
|
|
561
592
|
);
|
|
562
593
|
const att2 = msgs2.find((m) => m.type === "attached");
|
|
@@ -617,11 +648,12 @@ describe("client connection state", () => {
|
|
|
617
648
|
const att1 = msgs1.find((m) => m.type === "attached");
|
|
618
649
|
expect(att1.role).toBe("active");
|
|
619
650
|
const clientId1 = att1.clientId;
|
|
651
|
+
const tabId = att1.tabId;
|
|
620
652
|
|
|
621
653
|
// ws2 attaches (observer)
|
|
622
654
|
const msgs2 = await sendAndCollect(
|
|
623
655
|
ws2,
|
|
624
|
-
{ type: "
|
|
656
|
+
{ type: "attach_tab", tabId, cols: 80, rows: 24 },
|
|
625
657
|
{ timeout: 3000 },
|
|
626
658
|
);
|
|
627
659
|
const att2 = msgs2.find((m) => m.type === "attached");
|
|
@@ -669,16 +701,18 @@ describe("client connection state", () => {
|
|
|
669
701
|
const ws2 = await connectAuthed();
|
|
670
702
|
|
|
671
703
|
// ws1 attaches (active)
|
|
672
|
-
await sendAndCollect(
|
|
704
|
+
const msgs1 = await sendAndCollect(
|
|
673
705
|
ws1,
|
|
674
706
|
{ type: "attach_first", session: "main", cols: 80, rows: 24 },
|
|
675
707
|
{ timeout: 3000 },
|
|
676
708
|
);
|
|
709
|
+
const att1 = msgs1.find((m) => m.type === "attached");
|
|
710
|
+
expect(att1).toBeDefined();
|
|
677
711
|
|
|
678
712
|
// ws2 attaches (observer)
|
|
679
713
|
const msgs2 = await sendAndCollect(
|
|
680
714
|
ws2,
|
|
681
|
-
{ type: "
|
|
715
|
+
{ type: "attach_tab", tabId: att1.tabId, cols: 80, rows: 24 },
|
|
682
716
|
{ timeout: 3000 },
|
|
683
717
|
);
|
|
684
718
|
const att2 = msgs2.find((m) => m.type === "attached");
|
|
@@ -720,15 +754,15 @@ describe("multi-client", () => {
|
|
|
720
754
|
{ type: "attach_first", session: "main", cols: 100, rows: 30 },
|
|
721
755
|
{ timeout: 3000 },
|
|
722
756
|
);
|
|
757
|
+
const att1 = msgs1.find((m) => m.type === "attached");
|
|
758
|
+
expect(att1).toBeDefined();
|
|
723
759
|
const msgs2 = await sendAndCollect(
|
|
724
760
|
ws2,
|
|
725
|
-
{ type: "
|
|
761
|
+
{ type: "attach_tab", tabId: att1.tabId, cols: 80, rows: 24 },
|
|
726
762
|
{ timeout: 3000 },
|
|
727
763
|
);
|
|
728
764
|
|
|
729
|
-
const att1 = msgs1.find((m) => m.type === "attached");
|
|
730
765
|
const att2 = msgs2.find((m) => m.type === "attached");
|
|
731
|
-
expect(att1).toBeDefined();
|
|
732
766
|
expect(att2).toBeDefined();
|
|
733
767
|
expect(att1.tabId).toBe(att2.tabId);
|
|
734
768
|
|
|
@@ -840,7 +874,11 @@ describe("inspect", () => {
|
|
|
840
874
|
const result = msgs.find((m) => m.type === "inspect_result");
|
|
841
875
|
expect(result).toBeDefined();
|
|
842
876
|
expect(typeof result.text).toBe("string");
|
|
843
|
-
|
|
877
|
+
if (PTY_SUPPORTED) {
|
|
878
|
+
expect(result.text).toContain("inspect-test-output");
|
|
879
|
+
} else {
|
|
880
|
+
expect(result.text).toContain("Shell unavailable on this machine");
|
|
881
|
+
}
|
|
844
882
|
expect(result.meta).toBeDefined();
|
|
845
883
|
expect(result.meta.session).toBe("main");
|
|
846
884
|
expect(typeof result.meta.cols).toBe("number");
|