peer-term 1.2.10 → 1.2.12

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +107 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peer-term",
3
- "version": "1.2.10",
3
+ "version": "1.2.12",
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
@@ -368,25 +368,54 @@ class Session {
368
368
 
369
369
  async _completeKeyExchange(clientPublicKeyBase64) {
370
370
  if (!this.keyPair || !this.hostPublicKeyBase64) return;
371
+ if (typeof clientPublicKeyBase64 !== 'string' || clientPublicKeyBase64.length === 0) {
372
+ this._rejectProtocolMessage('Invalid key-exchange public key');
373
+ return;
374
+ }
371
375
 
372
- this.logDebug('Deriving shared secret...');
373
- this.clientPublicKeyBase64 = clientPublicKeyBase64;
376
+ try {
377
+ this.logDebug('Deriving shared secret...');
378
+ this.clientPublicKeyBase64 = clientPublicKeyBase64;
379
+
380
+ const peerPublicKey = await importPublicKey(clientPublicKeyBase64);
381
+ this.sharedKey = await deriveSharedKey(this.keyPair.privateKey, peerPublicKey);
382
+
383
+ if (this.secureMode) {
384
+ this.securityFingerprint = await fingerprintPublicKeys(
385
+ this.code,
386
+ this.hostPublicKeyBase64,
387
+ this.clientPublicKeyBase64
388
+ );
389
+ this.fingerprintAuthorized = false;
390
+ this._printFingerprintAuthorization();
391
+ } else {
392
+ this.fingerprintAuthorized = true;
393
+ await this._activateSecureSession();
394
+ }
395
+ } catch (err) {
396
+ this._rejectProtocolMessage(`Invalid key exchange: ${err.message}`);
397
+ }
398
+ }
374
399
 
375
- const peerPublicKey = await importPublicKey(clientPublicKeyBase64);
376
- this.sharedKey = await deriveSharedKey(this.keyPair.privateKey, peerPublicKey);
400
+ _rejectProtocolMessage(reason) {
401
+ this.log(`Protocol error: ${reason}`);
402
+ this.isClientConnected = false;
403
+ this.awaitingRejoin = false;
404
+ this.sharedKey = null;
405
+ this.keyPair = null;
406
+ this.hostPublicKeyBase64 = null;
407
+ this.clientPublicKeyBase64 = null;
408
+ this.securityFingerprint = null;
409
+ this.fingerprintAuthorized = false;
410
+ this.stopHeartbeat();
411
+ this._cleanupWebRTC();
412
+ this.destroy();
413
+ }
377
414
 
378
- if (this.secureMode) {
379
- this.securityFingerprint = await fingerprintPublicKeys(
380
- this.code,
381
- this.hostPublicKeyBase64,
382
- this.clientPublicKeyBase64
383
- );
384
- this.fingerprintAuthorized = false;
385
- this._printFingerprintAuthorization();
386
- } else {
387
- this.fingerprintAuthorized = true;
388
- await this._activateSecureSession();
389
- }
415
+ _handleMessageError(err) {
416
+ const message = err instanceof Error ? err.message : String(err);
417
+ this.log(`Protocol handler error: ${message}`);
418
+ this.destroy();
390
419
  }
391
420
 
392
421
  _printFingerprintAuthorization() {
@@ -481,6 +510,7 @@ class Session {
481
510
  });
482
511
 
483
512
  this.ws.on('message', async (raw) => {
513
+ try {
484
514
  let msg;
485
515
  try {
486
516
  msg = JSON.parse(raw.toString());
@@ -488,6 +518,10 @@ class Session {
488
518
  this.log('Invalid message from relay');
489
519
  return;
490
520
  }
521
+ if (!msg || typeof msg.type !== 'string') {
522
+ this._rejectProtocolMessage('Malformed relay message');
523
+ return;
524
+ }
491
525
 
492
526
  switch (msg.type) {
493
527
  case 'code': {
@@ -614,6 +648,9 @@ class Session {
614
648
  break;
615
649
  }
616
650
  }
651
+ } catch (err) {
652
+ this._handleMessageError(err);
653
+ }
617
654
  });
618
655
 
619
656
  this.ws.on('close', () => {
@@ -648,13 +685,10 @@ class Session {
648
685
  try {
649
686
  const resizeJson = JSON.stringify({ type: 'resize', cols, rows });
650
687
  const payload = await encrypt(this.sharedKey, resizeJson);
651
- // Phase 4: Route through DataChannel if active, else relay
652
- if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
653
- // Send resize as a tagged message so client can distinguish
654
- this.webrtc.send(JSON.stringify({ _meta: 'resize', payload }));
655
- } else if (this.ws && this.ws.readyState === WebSocket.OPEN) {
656
- this.ws.send(JSON.stringify({ type: 'data', payload, meta: 'resize' }));
657
- }
688
+ this._sendEncryptedToClient(payload, {
689
+ directPayload: JSON.stringify({ _meta: 'resize', payload }),
690
+ meta: 'resize',
691
+ });
658
692
  } catch {}
659
693
  }
660
694
 
@@ -746,14 +780,41 @@ class Session {
746
780
  const payload = await encrypt(sharedKey, data);
747
781
  if (this.destroyed || sharedKey !== this.sharedKey) return;
748
782
 
749
- // Phase 4: Route through DataChannel if active, else relay
750
- if (this.useDataChannel && this.webrtc && this.webrtc.isActive()) {
751
- this.webrtc.send(payload);
752
- } else if (this.ws && this.ws.readyState === WebSocket.OPEN) {
753
- // Backpressure: drop chunk if relay can't keep up (1.25 MB buffer limit)
754
- if (this.ws.bufferedAmount < 1.25 * 1024 * 1024) {
755
- this.ws.send(JSON.stringify({ type: 'data', payload }));
783
+ this._sendEncryptedToClient(payload, { enforceBackpressure: true });
784
+ }
785
+
786
+ _sendEncryptedToClient(payload, options = {}) {
787
+ const directPayload = options.directPayload || payload;
788
+
789
+ if (this.useDataChannel) {
790
+ if (this.webrtc && this.webrtc.isActive()) {
791
+ let sentDirect = false;
792
+ try {
793
+ sentDirect = this.webrtc.send(directPayload);
794
+ } catch {}
795
+
796
+ if (sentDirect) return true;
756
797
  }
798
+
799
+ this.useDataChannel = false;
800
+ this.log('DataChannel send failed — falling back to relay');
801
+ }
802
+
803
+ return this._sendViaRelay(payload, options);
804
+ }
805
+
806
+ _sendViaRelay(payload, { meta, enforceBackpressure = false } = {}) {
807
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return false;
808
+ if (enforceBackpressure && this.ws.bufferedAmount >= 1.25 * 1024 * 1024) return false;
809
+
810
+ const msg = { type: 'data', payload };
811
+ if (meta) msg.meta = meta;
812
+
813
+ try {
814
+ this.ws.send(JSON.stringify(msg));
815
+ return true;
816
+ } catch {
817
+ return false;
757
818
  }
758
819
  }
759
820
 
@@ -886,12 +947,17 @@ class Session {
886
947
  });
887
948
 
888
949
  newWs.on('message', async (raw) => {
950
+ try {
889
951
  let msg;
890
952
  try {
891
953
  msg = JSON.parse(raw.toString());
892
954
  } catch {
893
955
  return;
894
956
  }
957
+ if (!msg || typeof msg.type !== 'string') {
958
+ this._rejectProtocolMessage('Malformed relay message');
959
+ return;
960
+ }
895
961
 
896
962
  if (msg.type === 'rejoined') {
897
963
  // Success — replace the old ws with this new one
@@ -911,6 +977,9 @@ class Session {
911
977
  this.destroy();
912
978
  try { newWs.close(); } catch {}
913
979
  }
980
+ } catch (err) {
981
+ this._handleMessageError(err);
982
+ }
914
983
  });
915
984
 
916
985
  newWs.on('error', () => {
@@ -946,6 +1015,7 @@ class Session {
946
1015
  */
947
1016
  _attachWsHandlers(newWs) {
948
1017
  newWs.on('message', async (raw) => {
1018
+ try {
949
1019
  let msg;
950
1020
  try {
951
1021
  msg = JSON.parse(raw.toString());
@@ -953,6 +1023,10 @@ class Session {
953
1023
  this.log('Invalid message from relay');
954
1024
  return;
955
1025
  }
1026
+ if (!msg || typeof msg.type !== 'string') {
1027
+ this._rejectProtocolMessage('Malformed relay message');
1028
+ return;
1029
+ }
956
1030
 
957
1031
  switch (msg.type) {
958
1032
  case 'client-connected': {
@@ -1060,6 +1134,9 @@ class Session {
1060
1134
  break;
1061
1135
  }
1062
1136
  }
1137
+ } catch (err) {
1138
+ this._handleMessageError(err);
1139
+ }
1063
1140
  });
1064
1141
 
1065
1142
  newWs.on('close', () => {
@@ -1390,7 +1467,6 @@ process.on('uncaughtException', (err) => {
1390
1467
  process.on('unhandledRejection', (reason) => {
1391
1468
  const err = reason instanceof Error ? reason : new Error(String(reason));
1392
1469
  logger.error(`Unhandled rejection: ${err.message}`, err);
1393
- process.exit(1);
1394
1470
  });
1395
1471
 
1396
1472
  // ─── Run ─────────────────────────────────────────────────────────────────────