UNPKG

pg-browser-proxy

Version:

A WebSocket-TCP proxy that enables PostgreSQL clients to connect to a Postgres database running in the browser

2 lines 4.93 kB
var U={tcpPort:5432,wsPort:443};function W(e){return{tcpPort:(e==null?void 0:e.tcpPort)??U.tcpPort,wsPort:(e==null?void 0:e.wsPort)??U.wsPort}}import T from"debug";T.formatters.e=e=>e();var u=T("pg-browser-proxy");import{createServer as X}from"node:net";import{BackendError as S}from"pg-gateway";import{fromNodeSocket as V}from"pg-gateway/node";var A=class{connection;websocket;connectionId;hasConnection(){return!!this.connection}getConnection(){return this.connection}getConnectionId(){return this.connectionId}setConnection(t,r){this.connectionId=t,this.connection=r}deleteConnection(){this.connection=void 0,this.connectionId=void 0}hasWebsocket(){return!!this.websocket}getWebsocket(){return this.websocket}setWebsocket(t){this.websocket=t}deleteWebsocket(){this.websocket=void 0,this.deleteConnection()}},i=new A;function D(e,t,r={}){let a=new TextEncoder,s=196608,o={user:e,database:t,...r},n=4;for(let[b,w]of Object.entries(o))n+=b.length+1+w.length+1;n+=1;let c=new Uint8Array(4+n),I=new DataView(c.buffer),d=0;I.setInt32(d,n+4,!1),d+=4,I.setInt32(d,s,!1),d+=4;for(let[b,w]of Object.entries(o))c.set(a.encode(b),d),d+=b.length,c.set([0],d++),c.set(a.encode(w),d),d+=w.length,c.set([0],d++);return c.set([0],d),c}function E(){let e=new Uint8Array(5),t=new DataView(e.buffer);return t.setUint8(0,88),t.setUint32(1,4,!1),e}import{customAlphabet as N}from"nanoid";var O=N("0123456789abcdefghijklmnopqrstuvwxyz",16);function B(){return O()}function F(e){let t=e.subarray(0,16),r=new TextDecoder().decode(t),a=e.subarray(16);return{connectionId:r,message:a}}function y(e,t){let a=new TextEncoder().encode(e),s=new Uint8Array(a.length+t.length);return s.set(a,0),s.set(t,a.length),s}var R=u.extend("telemetry"),p=class{event_message;metadata;timestamp;constructor(t,r){this.event_message=t,this.metadata=r,this.timestamp=new Date().toISOString()}},x=class extends p{constructor(t){super("database-shared",t)}},v=class extends p{constructor(t){super("database-unshared",t)}},P=class extends p{constructor(t){super("user-connected",t)}},C=class extends p{constructor(t){super("user-disconnected",t)}};async function m(e){R(JSON.stringify(e,null,2))}var M=u.extend("tcp-server"),f=X(async e=>{var t;try{M("New TCP connection");let r={ip:e.localAddress,port:((t=e.localPort)==null?void 0:t.toString())||""},a=await V(e,{serverVersion(){return"16.3"},auth:{method:"trust"},onStartup(s){let o=i.getWebsocket();if(!o)throw S.create({code:"XX000",message:"the browser is not sharing the database",severity:"FATAL"});if(i.hasConnection())throw S.create({code:"53300",message:"sorry, too many clients already",severity:"FATAL"});let n=B();i.setConnection(n,a),m(new P({connectionId:n,userMetadata:{...r,...s.clientParams}}));let c=D("postgres","postgres",{client_ip:e.localAddress??"unknown"});o.send(y(n,c))},onMessage(s,o){if(!o.isAuthenticated)return;let n=i.getWebsocket();if(!n)throw S.create({code:"XX000",message:"the browser is not sharing the database",severity:"FATAL"});let c=i.getConnectionId();if(!c)throw S.create({code:"08003",message:"connection does not exist",severity:"FATAL"});return M("tcp message: %e",()=>Buffer.from(s).toString("hex")),n.send(y(c,s)),new Uint8Array}});e.on("close",()=>{let s=i.getConnectionId();if(s){m(new C({connectionId:s,userMetadata:r}));let o=i.getWebsocket();o==null||o.send(y(s,E())),i.deleteConnection()}})}catch(r){M("Error in TCP connection:",r),e.end()}});import*as L from"node:http";import{WebSocketServer as _}from"ws";var g=u.extend("websocket-server"),l=L.createServer(),k=new _({server:l});k.on("error",e=>{g("websocket server error",e)});function j(e){let t=e.headers;return{origin:t.origin,userAgent:t["user-agent"],host:t.host}}k.on("connection",async(e,t)=>{try{if(g("websocket connection"),!t.headers.host){g("No host header present"),e.close();return}if(i.hasWebsocket()){g("Database already shared"),e.close();return}i.setWebsocket(e);let r=j(t);g("User connected with metadata:",r),m(new x({userMetadata:r})),e.on("message",a=>{var n;let{message:s}=F(a),o=i.getConnection();o&&(g("websocket message: %e",()=>s.toString("hex")),(n=o.streamWriter)==null||n.write(s))}),e.on("close",()=>{i.deleteWebsocket(),m(new v({userMetadata:r}))})}catch(r){g("Error in WebSocket connection:",r),e.close()}});var h=u.extend("cli");function we(e){let t=W(e),r=!1;return{httpServer:l,tcpServer:f,start:async()=>{await Promise.all([new Promise((o,n)=>{l.listen(t.wsPort,()=>{o()}),l.on("error",n)}),new Promise((o,n)=>{f.listen(t.tcpPort,()=>{o()}),f.on("error",n)})])},shutdown:async o=>{if(!r){r=!0,h("Shutting down servers%s",o?` (signal: ${o})`:"");for(let n of k.clients)try{n.terminate()}catch(c){h("Error terminating WebSocket client:",c)}await Promise.allSettled([new Promise(n=>{l.close(()=>{h("WebSocket server closed"),n()})}),new Promise(n=>{f.close(()=>{h("TCP server closed"),n()})})]),h("Shutdown complete")}}}}export{we as createProxy}; //# sourceMappingURL=proxy.js.map