tasq/node_modules/pipenet/dist/server/server.js

168 lines
5.7 KiB
JavaScript

import debug from 'debug';
import http from 'http';
import { hri } from 'human-readable-ids';
import Koa from 'koa';
import Router from 'koa-router';
import tldjs from 'tldjs';
import { ClientManager } from './ClientManager.js';
import { TunnelServer } from './TunnelServer.js';
const log = debug('pipenet:server');
export function createServer(opt = {}) {
const validHosts = opt.domains && opt.domains.length > 0 ? opt.domains : undefined;
const myTldjs = tldjs.fromUserSettings({ validHosts });
const landingPage = opt.landing || 'https://pipenet.dev/';
function GetClientIdFromHostname(hostname) {
return myTldjs.getSubdomain(hostname);
}
// Create shared tunnel server if tunnelPort is specified
const tunnelServer = opt.tunnelPort ? new TunnelServer() : undefined;
const manager = new ClientManager({
...opt,
tunnelServer,
});
const schema = opt.secure ? 'https' : 'http';
const tunnelPort = opt.tunnelPort;
const app = new Koa();
const router = new Router();
// CORS middleware
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
return;
}
await next();
});
router.get('/api/status', async (ctx) => {
const stats = manager.stats;
ctx.body = {
mem: process.memoryUsage(),
tunnels: stats.tunnels,
};
});
router.get('/api/tunnels/:id/status', async (ctx) => {
const clientId = ctx.params.id;
const client = manager.getClient(clientId);
if (!client) {
ctx.throw(404);
return;
}
const stats = client.stats();
ctx.body = {
connectedSockets: stats.connectedSockets,
};
});
app.use(router.routes());
app.use(router.allowedMethods());
// Helper to extract domain from host (strips port if present)
const getDomain = (host) => {
// Remove port if present (e.g., "localhost:3000" -> "localhost")
return host.split(':')[0];
};
// Helper to build the tunnel URL
const buildUrl = (id, host) => {
return schema + '://' + id + '.' + host;
};
// Helper to build response with tunnel port if configured
const buildResponse = (info) => {
const response = { ...info };
// If using shared tunnel server, override port and add sharedTunnel flag
if (tunnelPort) {
response.port = tunnelPort;
response.sharedTunnel = true;
}
return response;
};
app.use(async (ctx, next) => {
const path = ctx.request.path;
if (path !== '/') {
await next();
return;
}
const isNewClientRequest = ctx.query['new'] !== undefined;
if (isNewClientRequest) {
const reqId = hri.random();
const domain = getDomain(ctx.request.host);
const url = buildUrl(reqId, ctx.request.host);
log('making new client with id %s', reqId);
const info = await manager.newClient(reqId, url, domain);
ctx.body = buildResponse(info);
return;
}
ctx.redirect(landingPage);
});
app.use(async (ctx, next) => {
const parts = ctx.request.path.split('/');
if (parts.length !== 2) {
await next();
return;
}
const reqId = parts[1];
if (!/^(?:[a-z0-9][a-z0-9-]{4,63}[a-z0-9]|[a-z0-9]{4,63})$/.test(reqId)) {
const msg = 'Invalid subdomain. Subdomains must be lowercase and between 4 and 63 alphanumeric characters.';
ctx.status = 403;
ctx.body = { message: msg };
return;
}
const domain = getDomain(ctx.request.host);
const url = buildUrl(reqId, ctx.request.host);
log('making new client with id %s', reqId);
const info = await manager.newClient(reqId, url, domain);
ctx.body = buildResponse(info);
});
const server = http.createServer();
server.tunnelServer = tunnelServer;
const appCallback = app.callback();
server.on('request', (req, res) => {
const hostname = req.headers.host;
if (!hostname) {
res.statusCode = 400;
res.end('Host header is required');
return;
}
const clientId = GetClientIdFromHostname(hostname);
if (!clientId) {
appCallback(req, res);
return;
}
const client = manager.getClient(clientId);
if (!client) {
res.statusCode = 404;
res.end('404');
return;
}
// Call the onRequest hook
if (opt.onRequest) {
opt.onRequest({
headers: req.headers,
method: req.method || 'GET',
path: req.url || '/',
remoteAddress: req.socket.remoteAddress,
tunnelId: clientId,
});
}
client.handleRequest(req, res);
});
server.on('upgrade', (req, socket) => {
const hostname = req.headers.host;
if (!hostname) {
socket.destroy();
return;
}
const clientId = GetClientIdFromHostname(hostname);
if (!clientId) {
socket.destroy();
return;
}
const client = manager.getClient(clientId);
if (!client) {
socket.destroy();
return;
}
client.handleUpgrade(req, socket);
});
return server;
}
//# sourceMappingURL=server.js.map