peer-term 1.2.0 → 1.2.3
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 +5 -1
- package/package.json +1 -1
- package/src/index.js +60 -14
package/README.md
CHANGED
|
@@ -56,4 +56,8 @@ peer-term --verbose # enable debug logging
|
|
|
56
56
|
2. **Client** opens the web UI and enters the code
|
|
57
57
|
3. Both sides perform an **ECDH key exchange** to derive a shared AES-256-GCM secret
|
|
58
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
|
|
59
|
+
5. If both peers are on the same LAN, a **WebRTC DataChannel** is established to bypass the relay entirely
|
|
60
|
+
|
|
61
|
+
## Keywords
|
|
62
|
+
|
|
63
|
+
terminal, pty, mobile, web-terminal, remote-terminal, xterm, websocket, ssh-alternative, e2ee, end-to-end-encrypted, mobile-terminal, terminal-sharing, browser-terminal, remote-access, touch-terminal, terminal-emulator, devtools, cli, ai-coding, persistent-sessions, cross-platform-tool, remote-pair-programming, web-based-terminal-access, collaborative-coding, encryption, instant-terminal-sharing, remote-debugging, browser-terminal-emulator, multi-session-support
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -49,6 +49,14 @@ const __dirname = path.dirname(__filename);
|
|
|
49
49
|
// Load .env from host/ directory (parent of src/)
|
|
50
50
|
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
51
51
|
|
|
52
|
+
// Load package.json for version information
|
|
53
|
+
let version = 'unknown';
|
|
54
|
+
try {
|
|
55
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
57
|
+
version = pkg.version;
|
|
58
|
+
} catch {}
|
|
59
|
+
|
|
52
60
|
// ─── CLI Argument Parsing ────────────────────────────────────────────────────
|
|
53
61
|
|
|
54
62
|
const argv = minimist(process.argv.slice(2), {
|
|
@@ -241,7 +249,7 @@ async function getExpiryInteractive() {
|
|
|
241
249
|
* Get the reconnection window — from flag, interactive prompt, or silent default.
|
|
242
250
|
*/
|
|
243
251
|
async function getRejoinInteractive() {
|
|
244
|
-
const DEFAULT_REJOIN =
|
|
252
|
+
const DEFAULT_REJOIN = 5 * 60 * 1000; // 5 minutes
|
|
245
253
|
const MIN_REJOIN = 5 * 60 * 1000; // 5 minutes
|
|
246
254
|
const MAX_REJOIN = 6 * 60 * 60 * 1000; // 6 hours
|
|
247
255
|
|
|
@@ -341,7 +349,7 @@ class Session {
|
|
|
341
349
|
const config = JSON.stringify({
|
|
342
350
|
type: 'session-config',
|
|
343
351
|
readonly: this.readOnly,
|
|
344
|
-
version:
|
|
352
|
+
version: version,
|
|
345
353
|
shell: this.shell,
|
|
346
354
|
startPath: this.startPath,
|
|
347
355
|
});
|
|
@@ -493,6 +501,13 @@ class Session {
|
|
|
493
501
|
}
|
|
494
502
|
|
|
495
503
|
case 'peer-disconnected': {
|
|
504
|
+
if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
|
|
505
|
+
this.log('Client relay connection lost; direct DataChannel still active.');
|
|
506
|
+
this.missedPings = 0;
|
|
507
|
+
this.startHeartbeat();
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
|
|
496
511
|
this.log(`Client disconnected. Rejoin window: ${formatDuration(this.rejoinMs)}.`);
|
|
497
512
|
this.isClientConnected = false;
|
|
498
513
|
this.awaitingRejoin = true;
|
|
@@ -512,10 +527,7 @@ class Session {
|
|
|
512
527
|
|
|
513
528
|
case 'rejoined': {
|
|
514
529
|
this.log(`\u2705 Reconnected. Session restored. (code: ${msg.code})`);
|
|
515
|
-
|
|
516
|
-
if (this.isClientConnected) {
|
|
517
|
-
this.startHeartbeat();
|
|
518
|
-
}
|
|
530
|
+
await this._restartPeerSessionAfterHostRejoin();
|
|
519
531
|
break;
|
|
520
532
|
}
|
|
521
533
|
|
|
@@ -576,6 +588,12 @@ class Session {
|
|
|
576
588
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
577
589
|
this.ws.send(JSON.stringify({ type: 'heartbeat' }));
|
|
578
590
|
}
|
|
591
|
+
|
|
592
|
+
if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
|
|
593
|
+
this.missedPings = 0;
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
579
597
|
this.missedPings++;
|
|
580
598
|
if (this.missedPings >= MAX_MISSED_PINGS + 1) {
|
|
581
599
|
this.log('Client heartbeat lost. Waiting for reconnect...');
|
|
@@ -768,7 +786,7 @@ class Session {
|
|
|
768
786
|
newWs.send(JSON.stringify({ type: 'host-rejoin', code: this.code, hostToken: this.hostToken }));
|
|
769
787
|
});
|
|
770
788
|
|
|
771
|
-
newWs.on('message', (raw) => {
|
|
789
|
+
newWs.on('message', async (raw) => {
|
|
772
790
|
let msg;
|
|
773
791
|
try {
|
|
774
792
|
msg = JSON.parse(raw.toString());
|
|
@@ -786,10 +804,7 @@ class Session {
|
|
|
786
804
|
// Re-attach the full message handler by wiring up events
|
|
787
805
|
this._attachWsHandlers(newWs);
|
|
788
806
|
|
|
789
|
-
|
|
790
|
-
if (this.isClientConnected) {
|
|
791
|
-
this.startHeartbeat();
|
|
792
|
-
}
|
|
807
|
+
await this._restartPeerSessionAfterHostRejoin();
|
|
793
808
|
} else if (msg.type === 'error') {
|
|
794
809
|
this.log(`Rejoin failed: ${msg.msg}`);
|
|
795
810
|
this._pendingReconnectWs = null;
|
|
@@ -851,6 +866,12 @@ class Session {
|
|
|
851
866
|
this.isClientConnected = true;
|
|
852
867
|
this.missedPings = 0;
|
|
853
868
|
|
|
869
|
+
if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
|
|
870
|
+
// Keep existing keys if direct session is healthy
|
|
871
|
+
this.startHeartbeat();
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
|
|
854
875
|
this.keyPair = await generateKeyPair();
|
|
855
876
|
const pubKeyBase64 = await exportPublicKey(this.keyPair.publicKey);
|
|
856
877
|
this.ws.send(JSON.stringify({ type: 'key-exchange', publicKey: pubKeyBase64 }));
|
|
@@ -918,6 +939,13 @@ class Session {
|
|
|
918
939
|
}
|
|
919
940
|
|
|
920
941
|
case 'peer-disconnected': {
|
|
942
|
+
if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
|
|
943
|
+
this.log('Client relay connection lost; direct DataChannel still active.');
|
|
944
|
+
this.missedPings = 0;
|
|
945
|
+
this.startHeartbeat();
|
|
946
|
+
break;
|
|
947
|
+
}
|
|
948
|
+
|
|
921
949
|
this.log(`Client disconnected. Rejoin window: ${formatDuration(this.rejoinMs)}.`);
|
|
922
950
|
this.isClientConnected = false;
|
|
923
951
|
this.awaitingRejoin = true;
|
|
@@ -936,9 +964,7 @@ class Session {
|
|
|
936
964
|
|
|
937
965
|
case 'rejoined': {
|
|
938
966
|
this.log(`Reconnected. Session restored. (code: ${msg.code})`);
|
|
939
|
-
|
|
940
|
-
this.startHeartbeat();
|
|
941
|
-
}
|
|
967
|
+
await this._restartPeerSessionAfterHostRejoin();
|
|
942
968
|
break;
|
|
943
969
|
}
|
|
944
970
|
|
|
@@ -1061,6 +1087,26 @@ class Session {
|
|
|
1061
1087
|
});
|
|
1062
1088
|
}
|
|
1063
1089
|
|
|
1090
|
+
async _restartPeerSessionAfterHostRejoin() {
|
|
1091
|
+
this.missedPings = 0;
|
|
1092
|
+
|
|
1093
|
+
if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
|
|
1094
|
+
this.startHeartbeat();
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
this.sharedKey = null;
|
|
1099
|
+
this._cleanupWebRTC();
|
|
1100
|
+
|
|
1101
|
+
this.keyPair = await generateKeyPair();
|
|
1102
|
+
const pubKeyBase64 = await exportPublicKey(this.keyPair.publicKey);
|
|
1103
|
+
|
|
1104
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1105
|
+
this.ws.send(JSON.stringify({ type: 'key-exchange', publicKey: pubKeyBase64 }));
|
|
1106
|
+
this.startHeartbeat();
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1064
1110
|
_cleanupWebRTC() {
|
|
1065
1111
|
this.useDataChannel = false;
|
|
1066
1112
|
if (this.webrtc) {
|