peer-term 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -8
- package/package.json +1 -1
- package/src/index.js +16 -1
package/README.md
CHANGED
|
@@ -36,17 +36,24 @@ peer-term --path ~/projects # starts at ~/projects
|
|
|
36
36
|
peer-term --path . # starts in current directory
|
|
37
37
|
peer-term --readonly # view-only session
|
|
38
38
|
peer-term --expiry 10m # custom expiry time
|
|
39
|
+
peer-term --relay wss://custom # use a custom relay server
|
|
39
40
|
peer-term --verbose # enable debug logging
|
|
40
41
|
```
|
|
41
42
|
|
|
42
43
|
## Features
|
|
43
44
|
|
|
44
45
|
- **No Config**: Works instantly. No port forwarding or firewall configuration needed.
|
|
45
|
-
- **End-to-End Encrypted**: Terminal data is encrypted locally using AES-GCM
|
|
46
|
-
- **WebRTC P2P**: Creates a direct Peer-to-Peer connection
|
|
47
|
-
- **Read-Only Mode**: Guests can view your terminal but cannot type commands
|
|
48
|
-
- **Custom Start Path**: Set the starting directory with `--path
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
- **End-to-End Encrypted**: Terminal data is encrypted locally using AES-256-GCM with ECDH P-256 key exchange. The relay never sees plaintext.
|
|
47
|
+
- **WebRTC P2P**: Creates a direct Peer-to-Peer connection on the same LAN for zero-latency, relay-free sessions.
|
|
48
|
+
- **Read-Only Mode**: Guests can view your terminal but cannot type commands.
|
|
49
|
+
- **Custom Start Path**: Set the starting directory with `--path`.
|
|
50
|
+
- **Resilient Connections**: Both host and client get a 2-minute rejoin window if their IP changes or connection drops. PTY state is fully preserved — no session restart needed.
|
|
51
|
+
- **Multi-Session**: Manage multiple simultaneous sessions with the interactive CLI menu.
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
1. **Host** registers a session on the relay and gets a 6-digit code
|
|
56
|
+
2. **Client** opens the web UI and enters the code
|
|
57
|
+
3. Both sides perform an **ECDH key exchange** to derive a shared AES-256-GCM secret
|
|
58
|
+
4. All terminal I/O is encrypted end-to-end — the relay only forwards opaque blobs
|
|
59
|
+
5. If both peers are on the same LAN, a **WebRTC DataChannel** is established to bypass the relay entirely
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -602,8 +602,15 @@ class Session {
|
|
|
602
602
|
|
|
603
603
|
this.log(`⏳ Reconnect attempt ${attempts}/${maxAttempts}...`);
|
|
604
604
|
|
|
605
|
+
// Close any in-flight reconnect socket from the previous tick
|
|
606
|
+
if (this._pendingReconnectWs) {
|
|
607
|
+
try { this._pendingReconnectWs.removeAllListeners(); this._pendingReconnectWs.close(); } catch {}
|
|
608
|
+
this._pendingReconnectWs = null;
|
|
609
|
+
}
|
|
610
|
+
|
|
605
611
|
try {
|
|
606
612
|
const newWs = new WebSocket(this.relayUrl);
|
|
613
|
+
this._pendingReconnectWs = newWs;
|
|
607
614
|
|
|
608
615
|
newWs.on('open', () => {
|
|
609
616
|
newWs.send(JSON.stringify({ type: 'host-rejoin', code: this.code }));
|
|
@@ -619,6 +626,7 @@ class Session {
|
|
|
619
626
|
|
|
620
627
|
if (msg.type === 'rejoined') {
|
|
621
628
|
// Success — replace the old ws with this new one
|
|
629
|
+
this._pendingReconnectWs = null;
|
|
622
630
|
this.ws = newWs;
|
|
623
631
|
this._stopReconnecting();
|
|
624
632
|
this.log(`✅ Reconnected. Session restored.`);
|
|
@@ -632,6 +640,7 @@ class Session {
|
|
|
632
640
|
}
|
|
633
641
|
} else if (msg.type === 'error') {
|
|
634
642
|
this.log(`Rejoin failed: ${msg.msg}`);
|
|
643
|
+
this._pendingReconnectWs = null;
|
|
635
644
|
this._stopReconnecting();
|
|
636
645
|
this.destroy();
|
|
637
646
|
try { newWs.close(); } catch {}
|
|
@@ -640,11 +649,13 @@ class Session {
|
|
|
640
649
|
|
|
641
650
|
newWs.on('error', () => {
|
|
642
651
|
// Connection failed, next retry in 5s
|
|
652
|
+
if (this._pendingReconnectWs === newWs) this._pendingReconnectWs = null;
|
|
643
653
|
try { newWs.close(); } catch {}
|
|
644
654
|
});
|
|
645
655
|
|
|
646
656
|
newWs.on('close', () => {
|
|
647
|
-
//
|
|
657
|
+
// Clean up reference if this was the pending socket
|
|
658
|
+
if (this._pendingReconnectWs === newWs) this._pendingReconnectWs = null;
|
|
648
659
|
});
|
|
649
660
|
} catch (e) {
|
|
650
661
|
// Connection failed, next retry in 5s
|
|
@@ -657,6 +668,10 @@ class Session {
|
|
|
657
668
|
clearInterval(this.reconnectTimer);
|
|
658
669
|
this.reconnectTimer = null;
|
|
659
670
|
}
|
|
671
|
+
if (this._pendingReconnectWs) {
|
|
672
|
+
try { this._pendingReconnectWs.removeAllListeners(); this._pendingReconnectWs.close(); } catch {}
|
|
673
|
+
this._pendingReconnectWs = null;
|
|
674
|
+
}
|
|
660
675
|
}
|
|
661
676
|
|
|
662
677
|
/**
|