/** * @module server * * Native Node.js HTTP server for space-gib — using serve-gib from @ibgib/node-gib. */ import { createServer } from 'node:http'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs'; import { ServeGib_V1, ErrorHandler, SyncUpgradeHandler, canHandleWsUpgrade, handleWsEchoUpgrade, getStandardServeGibHandlers, } from '@ibgib/node-gib/dist/serve-gib/index.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); // --------------------------------------------------------------------------- // Config // --------------------------------------------------------------------------- const PORT = parseInt(process.env.PORT ?? '3000', 10); const DATA_DIR = process.env.DATA_DIR ?? join(__dirname, '../../.data/ibgib-space'); const CLIENT_DIR = join(__dirname, '../client'); // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- async function main(): Promise { const lc = '[space-gib server]'; try { console.log(`${lc} starting...`); console.log(`${lc} dataDir: ${DATA_DIR}`); const custodianPrimaryProfile = (process.env.KEYSTONE_PROFILE_CUSTODIAN_PRIMARY || '').trim(); if (!custodianPrimaryProfile) { throw new Error(`${lc} Fatal Error: KEYSTONE_PROFILE_CUSTODIAN_PRIMARY environment variable is missing or blank. (E: 8a9b0c1d2e3f4567890123456789abcd)`); } const isProdEnv = process.env.NODE_ENV === 'production'; if (isProdEnv && !custodianPrimaryProfile.endsWith('.prod')) { throw new Error(`${lc} Security Violation: Non-production keystone profile '${custodianPrimaryProfile}' is prohibited when NODE_ENV=production. (E: 9b0c1d2e3f4567890123456789abcdef)`); } console.log(`${lc} KEYSTONE_PROFILE_CUSTODIAN_PRIMARY: ${custodianPrimaryProfile} (I: 961014e2dd082293b83b917835c97e26)`); if (custodianPrimaryProfile.endsWith('.dev')) { console.warn(`\n==========================================================\n⚠️ WARNING: CUSTODIAN KEYSTONE PROFILE IS SET TO DEVELOPMENT ('${custodianPrimaryProfile}')!\nCUSTODIAL KEYSTONES WILL USE LOW-SECURITY DEV PARAMETERS.\nFOR LOCAL TESTING ONLY — DO NOT USE FOR REAL SECRETS OR PRODUCTION DATA!\n==========================================================\n (W: 890123456789abcdef0123456789a123)`); } else if (custodianPrimaryProfile.endsWith('.staging')) { console.info(`${lc} ℹ️ STAGING CUSTODIAN KEYSTONE PROFILE ACTIVE ('${custodianPrimaryProfile}')`); } const syncUpgradeHandler = new SyncUpgradeHandler(); // Initialize serve-gib using standard handler preset from @ibgib/node-gib const serveGib = new ServeGib_V1({ port: PORT, dataDir: DATA_DIR, handlers: getStandardServeGibHandlers({ clientDir: CLIENT_DIR }), errorHandler: new ErrorHandler() }); const server = createServer(async (req, res) => { await serveGib.handleRequest(req, res); }); // WebSocket upgrade handling — separate from the HTTP request pipeline // since Node.js fires `upgrade` events independently of `request` events. server.on('upgrade', async (req, socket, head) => { const reqCtx = await serveGib.prepareContext(req); // Check sync upgrade handler first const isSync = await syncUpgradeHandler.handleUpgrade(reqCtx, socket as any, head); // Fallback to debug echo if not handled by sync if (!isSync && canHandleWsUpgrade(req)) { handleWsEchoUpgrade(req, socket as any, head); } else if (!isSync) { // Reject unrecognized upgrade requests socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); socket.destroy(); } }); server.listen(PORT, '0.0.0.0', () => { console.log(`${lc} listening on http://0.0.0.0:${PORT}`); }); } catch (error) { console.error(`${lc} fatal: ${extractErrorMsg(error)}`); process.exit(1); } } main();