portless 0.3.0 → 0.4.1

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.
@@ -5,8 +5,10 @@ function isErrnoException(err) {
5
5
  function escapeHtml(str) {
6
6
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
7
7
  }
8
- function formatUrl(hostname, proxyPort) {
9
- return proxyPort === 80 ? `http://${hostname}` : `http://${hostname}:${proxyPort}`;
8
+ function formatUrl(hostname, proxyPort, tls = false) {
9
+ const proto = tls ? "https" : "http";
10
+ const defaultPort = tls ? 443 : 80;
11
+ return proxyPort === defaultPort ? `${proto}://${hostname}` : `${proto}://${hostname}:${proxyPort}`;
10
12
  }
11
13
  function parseHostname(input) {
12
14
  let hostname = input.trim().replace(/^https?:\/\//, "").split("/")[0].toLowerCase();
@@ -30,24 +32,40 @@ function parseHostname(input) {
30
32
 
31
33
  // src/proxy.ts
32
34
  import * as http from "http";
35
+ import * as http2 from "http2";
36
+ import * as net from "net";
33
37
  var PORTLESS_HEADER = "X-Portless";
34
- function buildForwardedHeaders(req) {
38
+ var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
39
+ "connection",
40
+ "keep-alive",
41
+ "proxy-connection",
42
+ "transfer-encoding",
43
+ "upgrade"
44
+ ]);
45
+ function getRequestHost(req) {
46
+ const authority = req.headers[":authority"];
47
+ if (typeof authority === "string" && authority) return authority;
48
+ return req.headers.host || "";
49
+ }
50
+ function buildForwardedHeaders(req, tls) {
35
51
  const headers = {};
36
52
  const remoteAddress = req.socket.remoteAddress || "127.0.0.1";
37
- const proto = "http";
38
- const hostHeader = req.headers.host || "";
53
+ const proto = tls ? "https" : "http";
54
+ const defaultPort = tls ? "443" : "80";
55
+ const hostHeader = getRequestHost(req);
39
56
  headers["x-forwarded-for"] = req.headers["x-forwarded-for"] ? `${req.headers["x-forwarded-for"]}, ${remoteAddress}` : remoteAddress;
40
57
  headers["x-forwarded-proto"] = req.headers["x-forwarded-proto"] || proto;
41
58
  headers["x-forwarded-host"] = req.headers["x-forwarded-host"] || hostHeader;
42
- headers["x-forwarded-port"] = req.headers["x-forwarded-port"] || hostHeader.split(":")[1] || "80";
59
+ headers["x-forwarded-port"] = req.headers["x-forwarded-port"] || hostHeader.split(":")[1] || defaultPort;
43
60
  return headers;
44
61
  }
45
62
  function createProxyServer(options) {
46
- const { getRoutes, proxyPort, onError = (msg) => console.error(msg) } = options;
63
+ const { getRoutes, proxyPort, onError = (msg) => console.error(msg), tls } = options;
64
+ const isTls = !!tls;
47
65
  const handleRequest = (req, res) => {
48
66
  res.setHeader(PORTLESS_HEADER, "1");
49
67
  const routes = getRoutes();
50
- const host = (req.headers.host || "").split(":")[0];
68
+ const host = getRequestHost(req).split(":")[0];
51
69
  if (!host) {
52
70
  res.writeHead(400, { "Content-Type": "text/plain" });
53
71
  res.end("Missing Host header");
@@ -66,7 +84,7 @@ function createProxyServer(options) {
66
84
  ${routes.length > 0 ? `
67
85
  <h2>Active apps:</h2>
68
86
  <ul>
69
- ${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort))}">${escapeHtml(r.hostname)}</a> - localhost:${escapeHtml(String(r.port))}</li>`).join("")}
87
+ ${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort, isTls))}">${escapeHtml(r.hostname)}</a> - localhost:${escapeHtml(String(r.port))}</li>`).join("")}
70
88
  </ul>
71
89
  ` : "<p><em>No apps running.</em></p>"}
72
90
  <p>Start an app with: <code>portless ${safeHost.replace(".localhost", "")} your-command</code></p>
@@ -75,11 +93,16 @@ function createProxyServer(options) {
75
93
  `);
76
94
  return;
77
95
  }
78
- const forwardedHeaders = buildForwardedHeaders(req);
96
+ const forwardedHeaders = buildForwardedHeaders(req, isTls);
79
97
  const proxyReqHeaders = { ...req.headers };
80
98
  for (const [key, value] of Object.entries(forwardedHeaders)) {
81
99
  proxyReqHeaders[key] = value;
82
100
  }
101
+ for (const key of Object.keys(proxyReqHeaders)) {
102
+ if (key.startsWith(":")) {
103
+ delete proxyReqHeaders[key];
104
+ }
105
+ }
83
106
  const proxyReq = http.request(
84
107
  {
85
108
  hostname: "127.0.0.1",
@@ -89,12 +112,18 @@ function createProxyServer(options) {
89
112
  headers: proxyReqHeaders
90
113
  },
91
114
  (proxyRes) => {
92
- res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
115
+ const responseHeaders = { ...proxyRes.headers };
116
+ if (isTls) {
117
+ for (const h of HOP_BY_HOP_HEADERS) {
118
+ delete responseHeaders[h];
119
+ }
120
+ }
121
+ res.writeHead(proxyRes.statusCode || 502, responseHeaders);
93
122
  proxyRes.pipe(res);
94
123
  }
95
124
  );
96
125
  proxyReq.on("error", (err) => {
97
- onError(`Proxy error for ${req.headers.host}: ${err.message}`);
126
+ onError(`Proxy error for ${getRequestHost(req)}: ${err.message}`);
98
127
  if (!res.headersSent) {
99
128
  const errWithCode = err;
100
129
  const message = errWithCode.code === "ECONNREFUSED" ? "Bad Gateway: the target app is not responding. It may have crashed." : "Bad Gateway: the target app may not be running.";
@@ -116,17 +145,22 @@ function createProxyServer(options) {
116
145
  };
117
146
  const handleUpgrade = (req, socket, head) => {
118
147
  const routes = getRoutes();
119
- const host = (req.headers.host || "").split(":")[0];
148
+ const host = getRequestHost(req).split(":")[0];
120
149
  const route = routes.find((r) => r.hostname === host);
121
150
  if (!route) {
122
151
  socket.destroy();
123
152
  return;
124
153
  }
125
- const forwardedHeaders = buildForwardedHeaders(req);
154
+ const forwardedHeaders = buildForwardedHeaders(req, isTls);
126
155
  const proxyReqHeaders = { ...req.headers };
127
156
  for (const [key, value] of Object.entries(forwardedHeaders)) {
128
157
  proxyReqHeaders[key] = value;
129
158
  }
159
+ for (const key of Object.keys(proxyReqHeaders)) {
160
+ if (key.startsWith(":")) {
161
+ delete proxyReqHeaders[key];
162
+ }
163
+ }
130
164
  const proxyReq = http.request({
131
165
  hostname: "127.0.0.1",
132
166
  port: route.port,
@@ -152,7 +186,7 @@ function createProxyServer(options) {
152
186
  socket.on("error", () => proxySocket.destroy());
153
187
  });
154
188
  proxyReq.on("error", (err) => {
155
- onError(`WebSocket proxy error for ${req.headers.host}: ${err.message}`);
189
+ onError(`WebSocket proxy error for ${getRequestHost(req)}: ${err.message}`);
156
190
  socket.destroy();
157
191
  });
158
192
  proxyReq.on("response", (res) => {
@@ -173,6 +207,44 @@ function createProxyServer(options) {
173
207
  }
174
208
  proxyReq.end();
175
209
  };
210
+ if (tls) {
211
+ const h2Server = http2.createSecureServer({
212
+ cert: tls.cert,
213
+ key: tls.key,
214
+ allowHTTP1: true,
215
+ ...tls.SNICallback ? { SNICallback: tls.SNICallback } : {}
216
+ });
217
+ h2Server.on("request", (req, res) => {
218
+ handleRequest(req, res);
219
+ });
220
+ h2Server.on("upgrade", (req, socket, head) => {
221
+ handleUpgrade(req, socket, head);
222
+ });
223
+ const plainServer = http.createServer(handleRequest);
224
+ plainServer.on("upgrade", handleUpgrade);
225
+ const wrapper = net.createServer((socket) => {
226
+ socket.once("readable", () => {
227
+ const buf = socket.read(1);
228
+ if (!buf) {
229
+ socket.destroy();
230
+ return;
231
+ }
232
+ socket.unshift(buf);
233
+ if (buf[0] === 22) {
234
+ h2Server.emit("connection", socket);
235
+ } else {
236
+ plainServer.emit("connection", socket);
237
+ }
238
+ });
239
+ });
240
+ const origClose = wrapper.close.bind(wrapper);
241
+ wrapper.close = function(cb) {
242
+ h2Server.close();
243
+ plainServer.close();
244
+ return origClose(cb);
245
+ };
246
+ return wrapper;
247
+ }
176
248
  const httpServer = http.createServer(handleRequest);
177
249
  httpServer.on("upgrade", handleUpgrade);
178
250
  return httpServer;
@@ -190,6 +262,7 @@ function isValidRoute(value) {
190
262
  return typeof value === "object" && value !== null && typeof value.hostname === "string" && typeof value.port === "number" && typeof value.pid === "number";
191
263
  }
192
264
  var RouteStore = class _RouteStore {
265
+ /** The state directory path. */
193
266
  dir;
194
267
  routesPath;
195
268
  lockPath;