Files
AdPlatform-Client/wwwroot/server.js
Grae Jones fdb3e117a9
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s
First build
2026-03-21 17:54:42 -07:00

200 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const http = require('http');
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const { URL } = require('url');
// Azure sets PORT
const PORT = process.env.PORT || 8080;
// Default site if host doesn't map
const DEFAULT_SITE = 'default';
// Explicit host ? folder mapping
const HOST_MAP = {
'adpadmin.usimdev.com': 'admin',
'adpclient.usimdev.com': 'client',
'adpregist.usimdev.com': 'regist',
'adptestapi.usimdev.com': 'testapi',
'localhost': 'default'
};
const server = http.createServer(async (req, res) => {
try {
const hostHeader = (req.headers.host || '').toLowerCase();
console.log(`[SRV ${new Date().toISOString()}] ${req.method} ${req.url} (Host: ${hostHeader})`);
// Parse URL with WHATWG API (avoids url.parse deprecation)
const parsedUrl = new URL(req.url || '/', `http://${hostHeader || 'localhost'}`);
const pathname = decodeURIComponent(parsedUrl.pathname || '/');
const parts = pathname.replace(/^\/+/, '').split('/');
const firstSegment = parts[0] || '';
// Decide which site folder to serve from
const siteFolder = getSiteFolder(hostHeader);
const BASEDIR = path.join(__dirname, 'websites', siteFolder);
console.log(`Site folder resolved: /websites/${siteFolder} (host=${hostHeader})`);
// --- favicon special case ---
if (firstSegment === 'favicon.ico') {
const favPath = safeJoin(BASEDIR, 'favicon.ico');
if (await exists(favPath)) {
res.writeHead(200, { 'Content-Type': 'image/x-icon' });
return fs.createReadStream(favPath).pipe(res);
} else {
res.writeHead(204);
return res.end();
}
}
// --- robots.txt (optional per-site) ---
if (firstSegment === 'robots.txt') {
const robotsPath = safeJoin(BASEDIR, 'robots.txt');
if (await exists(robotsPath)) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
return fs.createReadStream(robotsPath).pipe(res);
}
// no robots for this site ? 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('Not found');
}
// Static file resolution
let requested = firstSegment || 'index.html';
if (parts.length > 1) {
requested = parts.join('/');
}
let filePath = safeJoin(BASEDIR, requested);
if (await isDir(filePath)) {
filePath = safeJoin(filePath, 'index.html');
}
// Handle Index.html vs index.html (case difference on dev machines)
if (!(await exists(filePath)) && /Index\.html$/.test(filePath)) {
const alt = filePath.replace(/Index\.html$/, 'index.html');
if (await exists(alt)) filePath = alt;
}
// If the requested file doesn't exist:
// 1) try this site's index.html (SPA routing)
// 2) try shared 404.html in root
if (!(await exists(filePath))) {
const siteIndex = safeJoin(BASEDIR, 'index.html');
if (await exists(siteIndex)) {
res.writeHead(200, { 'Content-Type': 'text/html' });
return fs.createReadStream(siteIndex).pipe(res);
}
const shared404 = path.join(__dirname, '404.html');
if (await exists(shared404)) {
res.writeHead(404, { 'Content-Type': 'text/html' });
return fs.createReadStream(shared404).pipe(res);
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('404 Not Found');
}
// Serve the static file
res.writeHead(200, { 'Content-Type': getType(filePath) });
return fs.createReadStream(filePath).pipe(res);
} catch (err) {
console.error('[SERVER ERROR]', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ status: 'error', error: err?.message || String(err) }));
}
});
server.listen(PORT, () => {
console.log('===========================================');
console.log(`Static HTTP server listening on ${PORT}`);
console.log(`Default site: /websites/${DEFAULT_SITE}/`);
console.log('Host mappings:');
Object.entries(HOST_MAP).forEach(([host, folder]) => {
console.log(` ${host} ? /websites/${folder}/`);
});
console.log('Fallback (subdomain): sub.domain.tld ? /websites/<sub>/');
console.log('Shared 404 (optional): /404.html');
console.log('===========================================');
});
module.exports = server;
/* ------------ helpers ------------ */
function getSiteFolder(hostnameRaw) {
if (!hostnameRaw) return DEFAULT_SITE;
const hostname = hostnameRaw.toLowerCase();
// 1) Explicit map wins
if (HOST_MAP[hostname]) {
return HOST_MAP[hostname];
}
// 2) localhost or IP ? default
if (hostname === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
return DEFAULT_SITE;
}
// 3) For multi-tenant subdomains: foo.example.com ? "foo"
const parts = hostname.split('.');
if (parts.length >= 3) {
return parts[0];
}
// 4) Anything else ? default
return DEFAULT_SITE;
}
function safeJoin(base, target) {
const resolved = path.resolve(base, target);
if (!resolved.startsWith(base)) {
// Prevent directory traversal fall back to base
return base;
}
return resolved;
}
async function exists(p) {
try {
await fsp.access(p, fs.constants.R_OK);
return true;
} catch {
return false;
}
}
async function isDir(p) {
try {
return (await fsp.stat(p)).isDirectory();
} catch {
return false;
}
}
function getType(filename) {
const ext = path.extname(filename).toLowerCase();
switch (ext) {
case '.html': return 'text/html';
case '.css': return 'text/css';
case '.js': return 'application/javascript';
case '.png': return 'image/png';
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.svg': return 'image/svg+xml';
case '.ico': return 'image/x-icon';
case '.json': return 'application/json';
case '.map': return 'application/json';
case '.woff2': return 'font/woff2';
case '.woff': return 'font/woff';
case '.ttf': return 'font/ttf';
case '.eot': return 'application/vnd.ms-fontobject';
default: return 'text/plain';
}
}