@iosifnicolae22/remote-shell
Version:
Run any terminal application and control it remotely from your mobile device. Perfect for maxing out vibe coding sessions with Claude Code on the go!
2 lines (1 loc) • 6 kB
JavaScript
"use strict";var __importDefault=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(exports,"__esModule",{value:!0}),exports.WebSocketClient=void 0;const ws_1=__importDefault(require("ws"));const DEBUG="true"===process.env.DEBUG;function debugLog(e,t,s){if(!DEBUG)return;const n=(new Date).toISOString();const i=require("fs");const o=require("path");const c=require("os");const r=o.resolve(c.homedir(),".remote-shell.log");const a=`[${n}] [${e}] ${t}${s?" "+JSON.stringify(s):""}\n`;i.appendFileSync(r,a),console.log(`🐛 [${e}] ${t}`,s||"")}class WebSocketClient{ws=null;serverUrl;reconnectInterval=5e3;reconnectAttempts=0;isIntentionalDisconnect=!1;clientConnected=!1;pingInterval=null;lastPongReceived=Date.now();cliSessionId;constructor(e="ws://localhost:3001"){this.serverUrl=e,this.cliSessionId=this.generateCliSessionId(),debugLog("INIT","WebSocket client initialized",{serverUrl:e,cliSessionId:this.cliSessionId})}async connect(){return new Promise((e,t)=>{try{this.ws=new ws_1.default(this.serverUrl,{headers:{"User-Agent":"remote-shell"}}),this.ws.on("open",()=>{debugLog("CONNECTION","WebSocket connection opened",{url:this.serverUrl,cliSessionId:this.cliSessionId}),this.reconnectAttempts=0,this.lastPongReceived=Date.now(),this.startPingInterval(),e()}),this.ws.on("message",e=>{try{const t=JSON.parse(e.toString());debugLog("MESSAGE_RECEIVED","Received message from server",{type:t.type,hasData:!!t.data,timestamp:t.timestamp}),this.handleMessage(t)}catch(t){debugLog("MESSAGE_ERROR","Invalid message from server",{error:t instanceof Error?t.message:String(t),rawData:e.toString().substring(0,100)})}}),this.ws.on("close",(e,t)=>{debugLog("CONNECTION","WebSocket connection closed",{code:e,reason:t?.toString(),wasIntentional:this.isIntentionalDisconnect}),this.clientConnected=!1,this.stopPingInterval(),this.isIntentionalDisconnect||this.attemptReconnect()}),this.ws.on("error",e=>{debugLog("CONNECTION","WebSocket error occurred",{error:e.message,code:e.code,reconnectAttempts:this.reconnectAttempts}),0===this.reconnectAttempts&&t(e)})}catch(e){t(e)}})}attemptReconnect(){this.reconnectAttempts++,debugLog("RECONNECT","Attempting to reconnect",{attempt:this.reconnectAttempts,delay:this.reconnectInterval}),setTimeout(()=>{this.connect().catch(e=>{debugLog("RECONNECT","Reconnection failed",{attempt:this.reconnectAttempts,error:e.message})})},this.reconnectInterval)}handleMessage(e){switch(debugLog("MESSAGE_HANDLER","Processing message",{type:e.type,hasData:!!e.data}),e.type){case"input":debugLog("INPUT","Received input from mobile client",{dataLength:e.data?.length}),this.onInput?.(e.data);break;case"resize":debugLog("RESIZE","Received resize from mobile client",{cols:e.cols,rows:e.rows}),this.onResize?.(e.cols,e.rows);break;case"client_connected":debugLog("CLIENT","Mobile client connected to session"),this.clientConnected=!0,this.onClientConnected?.();break;case"client_disconnected":debugLog("CLIENT","Mobile client disconnected from session"),this.clientConnected=!1,this.onClientDisconnected?.();break;case"session_confirmed":debugLog("SESSION","Session confirmed by server",{token:e.token?.substring(0,8)+"..."}),this.onSessionConfirmed?.(e.token);break;case"ping":debugLog("PING","Received ping from server"),this.sendPong();break;case"pong":debugLog("PONG","Received pong from server"),this.lastPongReceived=Date.now();break;default:debugLog("MESSAGE_HANDLER","Unknown message type received",{type:e.type})}}sendOutput(e){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("OUTPUT","Sending output to mobile clients",{dataLength:e.length,cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"output",data:e,timestamp:Date.now(),cliSessionId:this.cliSessionId})))}sendBeep(){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("BEEP","Sending beep to mobile clients",{cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"beep",timestamp:Date.now(),cliSessionId:this.cliSessionId})))}sendBuffer(e){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("BUFFER","Sending buffer to mobile clients",{dataLength:e.length,cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"buffer",data:e,timestamp:Date.now(),cliSessionId:this.cliSessionId})))}notifySessionCreated(e){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("SESSION","Notifying server of session creation",{token:e.substring(0,8)+"...",cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"session_created",token:e,cliSessionId:this.cliSessionId})))}sendPing(){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("PING","Sending ping to server",{cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"ping",timestamp:Date.now(),cliSessionId:this.cliSessionId})))}sendPong(){this.ws&&this.ws.readyState===ws_1.default.OPEN&&(debugLog("PONG","Sending pong to server",{cliSessionId:this.cliSessionId}),this.ws.send(JSON.stringify({type:"pong",timestamp:Date.now(),cliSessionId:this.cliSessionId})))}startPingInterval(){this.stopPingInterval(),this.pingInterval=setInterval(()=>{if(Date.now()-this.lastPongReceived>6e4)return debugLog("PING","Connection seems dead, reconnecting",{lastPongReceived:new Date(this.lastPongReceived).toISOString(),timeSinceLastPong:Date.now()-this.lastPongReceived}),void this.ws?.close();this.sendPing()},3e4)}stopPingInterval(){this.pingInterval&&(clearInterval(this.pingInterval),this.pingInterval=null)}isConnected(){return null!==this.ws&&this.ws.readyState===ws_1.default.OPEN}hasClients(){return this.clientConnected}disconnect(){this.ws&&(this.isIntentionalDisconnect=!0,this.clientConnected=!1,this.stopPingInterval(),this.ws.close(),this.ws=null)}generateCliSessionId(){return`cli_${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`}getCliSessionId(){return this.cliSessionId}onInput;onResize;onClientConnected;onClientDisconnected;onSessionConfirmed}exports.WebSocketClient=WebSocketClient;