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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peer-term",
3
- "version": "1.2.0",
3
+ "version": "1.2.3",
4
4
  "license": "MIT",
5
5
  "description": "Share your terminal instantly using a 6-digit code. Encrypted. No config.",
6
6
  "keywords": [
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 = 2 * 60 * 1000; // 2 minutes
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: '1.2.0',
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
- // Restart heartbeat if client is connected
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
- // Restart heartbeat if client is connected
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
- if (this.isClientConnected) {
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) {