'use strict';
// Zero-dependency local server. Binds loopback only; it holds real OAuth tokens.
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const { URL } = require('node:url');
const P = require('./lib/paths');
const store = require('./lib/store');
const oauth = require('./lib/oauth');
const usage = require('./lib/usage');
const swap = require('./lib/swap');
const credentials = require('./lib/credentials');
const targets = require('./lib/targets');
const terminal = require('./lib/terminal');
const auto = require('./lib/auto');
// Codex lives in its own package, with its own store: nothing below this line ever sees it.
const codexRoutes = require('./lib/codex/routes');
const codexSwap = require('./lib/codex/swap');
const codexPaseo = require('./lib/codex/paseo');
const codexDaemon = require('./lib/codex/daemon');
// The host the BROWSER uses, and the only one the Host-header allowlist accepts. Kept separate
// from the bind address on purpose: a container has to listen on all of its own interfaces to be
// reachable at all, but that must not widen what this server accepts as a legitimate origin.
const HOST = '127.0.0.1';
// Where the socket actually binds. Loopback everywhere except inside a container, where the
// Dockerfile sets 0.0.0.0. The loopback guarantee then moves outward, to how the port is
// published: -p 127.0.0.1:7373:7373 - see the README.
const BIND = process.env.SWAPPER_BIND || HOST;
const BASE_PORT = Number(process.env.PORT) || 7373;
const PUBLIC_DIR = path.join(__dirname, 'public');
const MAX_BODY = 1024 * 1024;
const deps = { store, oauth, usage, swap };
const MIME = {
'.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8',
'.js': 'text/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8',
'.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.png': 'image/png', '.woff2': 'font/woff2',
};
function send(res, status, body, headers = {}) {
const payload = typeof body === 'string' ? body : JSON.stringify(body);
res.writeHead(status, {
'Content-Type': typeof body === 'string' ? 'text/html; charset=utf-8' : 'application/json; charset=utf-8',
'Content-Length': Buffer.byteLength(payload),
'Cache-Control': 'no-store',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'no-referrer',
...headers,
});
res.end(payload);
}
const fail = (res, status, message) => send(res, status, { ok: false, error: oauth.scrub(message) });
function readBody(req) {
return new Promise((resolve, reject) => {
let size = 0;
const chunks = [];
req.on('data', (c) => {
size += c.length;
if (size > MAX_BODY) { reject(new Error('Cuerpo de la petición demasiado grande')); req.destroy(); return; }
chunks.push(c);
});
req.on('end', () => {
const text = Buffer.concat(chunks).toString('utf8').trim();
if (!text) return resolve({});
try { resolve(JSON.parse(text)); } catch { reject(new Error('JSON inválido en el cuerpo')); }
});
req.on('error', reject);
});
}
/**
* Two guards against a random website in the user's browser driving this API:
* a same-origin check, and a custom header that no cross-site form,
or