UNPKG

node-ssh-tunnel

Version:
2 lines (1 loc) 6.17 kB
import{createServer as o}from"net";import{Client as n}from"ssh2";const e=async n=>{let e=Object.assign({},n);return e.port||e.host||(e=null),new Promise(((n,s)=>{let t=o(),r=o=>{s(o)};t.on("error",r),process.on("uncaughtException",r),t.listen(e),t.on("listening",(()=>{process.removeListener("uncaughtException",r),n(t)}))}))},s=async o=>new Promise(((e,s)=>{let t=new n;t.on("ready",(()=>{t.removeListener("error",s),e(t)})),t.on("error",(o=>{s(o)})),t.connect(o)}));let t=!1;const r=async o=>(t=!0,new Promise((async(n,e)=>{try{console.log("ReCreateSSHConnection");const e=await s(o);t=!1,n(e)}catch(e){setTimeout((()=>{n(r(o))}),1e3)}})));class c extends Error{code;constructor(o,n){super(o),this.code=n}}class l{connection;sshOptions;servers;reconnecting=!1;constructor(){this.connection=null,this.sshOptions=null,this.servers=null}getConnection(){const{connection:o}=this;if(null===o)throw new Error("Not connected to server");return o}isConnected(){return null!=this.connection}async connect(o){const e=Object.assign({port:22,username:"root"},o);this.sshOptions=e;const s=new n;return await new Promise(((o,n)=>{s.on("error",n),s.on("ready",(()=>{this.connection=s,s.removeListener("error",n),o(!0)})),s.on("end",(()=>{this.connection===s&&(this.connection=null)})),s.on("close",(()=>{this.connection===s&&(this.connection=null),n(new c("No response from server","ETIMEDOUT"))})),s.connect(e)})),this}async reConnect(){return this.reconnecting=!0,new Promise((async(o,n)=>{try{console.log("ReCreateSSHConnection");const n=await s(this.sshOptions);this.reconnecting=!1,o(n)}catch(n){setTimeout((()=>{o(r(this.sshOptions))}),1e3)}}))}async disconnect(){const{connection:o}=this;if(null!==o){try{o.removeAllListeners(),o.end(),o.destroy()}catch(o){console.log(o)}this.connection=null}}async close(){this.disconnect(),this.closeTunnel()}async closeTunnel(){null!==this.servers&&(this.servers.forEach((o=>{if(o)try{o.close()}catch(o){console.log(o)}})),this.servers=null)}async createTunnel(o,n){if(null===this.connection)throw new Error("Not connected to server");if(null!==this.servers)throw new Error("Tunnel already created");const s=()=>{this.connection?.on("error",(async o=>{this.connection?.removeAllListeners(),this.connection=null,console.log("sshConnection","error"),this.reconnecting||(r.reconnectOnError?(console.log("ReconnectOnError","start reconnect"),this.connection=await this.reConnect(),s(),console.log("sshConnection","reconnected")):console.log("Error",o))})),this.connection?.on("close",(async()=>{this.connection?.removeAllListeners(),this.connection=null,console.log("sshConnection","close"),this.reconnecting||(r.reconnectOnError?(console.log("ReconnectOnClose","start reconnect"),this.connection=await this.reConnect(),s(),console.log("sshConnection","reconnected")):console.log("close"))}))};s();const t=(Array.isArray(o)?o:[o]).map((o=>Object.assign({dstAddr:"127.0.0.1",srcAddr:"0.0.0.0"},o))),r=Object.assign({autoClose:!1,reconnectOnError:!0},n||{});this.servers=await Promise.all(t.map((async o=>{const n={host:o.srcAddr,port:o.srcPort};let s;const t=(n,e=0)=>{if(null!==this.getConnection()){const e=this.getConnection();try{e.forwardOut(o.srcAddr,o.srcPort,o.dstAddr,o.dstPort,((o,e)=>{if(o){console.log(o.message),n.on("close",(()=>{})),n.on("error",(()=>{}));try{n.end(),n.destroy()}catch(o){console.log(o)}}else n.on("close",(()=>{e.end()})),n.on("error",(()=>{e.end()})),n.pipe(e).pipe(n)}))}catch(o){n.on("close",(()=>{})),n.on("error",(()=>{}));try{n.end(),n.destroy()}catch(o){console.log(o)}}}else if(e<20)setTimeout((()=>{t(n,e+1)}),500);else try{n.end(),n.destroy()}catch(o){console.log(o)}};try{return s=await e(n),console.log("create tunel success: ",`${o.srcAddr}:${o.srcPort} => ${this.sshOptions?.host}:${o.dstPort}`),s.on("connection",t),s}catch(o){return void console.log(o)}})))}}const i=async(o,n,c)=>{const l=Object.assign({port:22,username:"root"},o),i=(Array.isArray(n)?n:[n]).map((o=>Object.assign({dstAddr:"127.0.0.1",srcAddr:"0.0.0.0"},o))),a=Object.assign({autoClose:!1,reconnectOnError:!0},c||{});let h;const d=()=>{h?.on("error",(async o=>{h?.removeAllListeners(),h=void 0,console.log("sshConnection","error"),t||(a.reconnectOnError?(console.log("ReconnectOnError","start reconnect"),h=await r(l),d(),console.log("sshConnection","reconnected")):console.log("Error",o))})),h?.on("close",(async()=>{h?.removeAllListeners(),h=void 0,console.log("sshConnection","close"),t||(a.reconnectOnError?(console.log("ReconnectOnClose","start reconnect"),h=await r(l),d(),console.log("sshConnection","reconnected")):console.log("close"))}))};try{h=await s(l),d()}catch(o){return Promise.reject("用户名或密码错误, 请检查你的配置信息")}const u=await Promise.all(i.map((async n=>{const s={host:n.srcAddr,port:n.srcPort};let t;const r=l=>{a.reconnectOnError&&l.on("error",(async()=>{t=await e(s),r(t)})),l.on("connection",c),l.on("close",(()=>{console.log("close tunel: ",`${n.srcAddr}:${n.srcPort} => ${o.host}:${n.dstPort}`)}))},c=(o,e=0)=>{if(a.autoClose&&((o,n)=>{n.on("close",(()=>{o.getConnections(((n,e)=>{0===e&&o.close()}))}))})(t,o),h)try{h.forwardOut(o.remoteAddress??n.srcAddr,o.remotePort??n.srcPort,n.dstAddr,n.dstPort,((n,e)=>{if(n){console.log(n.message),o.on("close",(()=>{})),o.on("error",(()=>{}));try{o.end(),o.destroy()}catch(o){console.log(o)}}else o.on("close",(()=>{e.end()})),o.on("error",(()=>{e.end()})),o.pipe(e).pipe(o)}))}catch(n){o.on("close",(()=>{})),o.on("error",(()=>{}));try{o.end(),o.destroy()}catch(o){console.log(o)}}else if(e<20)setTimeout((()=>{c(o,e+1)}),500);else{o.on("close",(()=>{})),o.on("error",(()=>{}));try{o.end(),o.destroy()}catch(o){console.log(o)}}};try{return t=await e(s),r(t),console.log("create tunel success: ",`${n.srcAddr}:${n.srcPort} => ${o.host}:${n.dstPort}`),t}catch(o){return void console.log(o)}})));return{servers:u,sshConnection:h,close:()=>{if(u.forEach((o=>{if(o)try{o.close()}catch(o){console.log(o)}})),h)try{h.end(),h.destroy()}catch(o){console.log(o)}}}},a=async(o,n,e)=>{const s=new l;return await s.connect(o),await s.createTunnel(n,e),s};export{l as NodeSSHTunnel,c as SSHError,i as createTunnel,a as createTunnelEx,i as default};