diff --git a/package-lock.json b/package-lock.json index 778cf7d205..06055874e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix", - "version": "4.1.1-0", + "version": "4.1.2-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "4.1.1-0", + "version": "4.1.2-0", "dependencies": { "@bugsnag/js": "^7.18.0", "@floating-ui/dom": "^0.5.4", @@ -58,6 +58,7 @@ "gulp-useref": "^5.0.0", "gulp-webserver": "^0.9.1", "gulp-zip": "^5.1.0", + "http-proxy": "^1.18.1", "http-server": "14.1.0", "husky": "^7.0.4", "jasmine-core": "^4.2.0", diff --git a/package.json b/package.json index 1c70018d64..431f449548 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "gulp-useref": "^5.0.0", "gulp-webserver": "^0.9.1", "gulp-zip": "^5.1.0", + "http-proxy": "^1.18.1", "http-server": "14.1.0", "husky": "^7.0.4", "jasmine-core": "^4.2.0", @@ -71,10 +72,10 @@ "_patchVersionBump": "gulp patchVersionBump", "_minorVersionBump": "gulp minorVersionBump", "_majorVersionBump": "gulp majorVersionBump", - "serve": "http-server . -p 8000 -c-1", + "serve": "node serve-proxy.js . -p 8000 -c-1", "_serveWithWebCacheHelp": "echo !!!Make sure to npm run release:dev/stageing/prod before testing the cache!!!", "serveWithWebCache": "npm run _releaseWebCache && npm run _serveWithWebCacheHelp && http-server ./dist -p 8000 -c-1", - "serveExternal": "http-server . -p 8000 -a 0.0.0.0 --log-ip true -c-1", + "serveExternal": "node serve-proxy.js . -p 8000 -a 0.0.0.0 --log-ip -c-1", "createJSDocs": "node build/api-docs-generator.js && git add docs", "_translateStrings": "gulp translateStrings", "_minify": "r.js -o require.min.config.js && echo this is untested see https://stackoverflow.com/questions/14337970/minifying-requirejs-javascript-codebase-to-a-single-file" diff --git a/serve-proxy.js b/serve-proxy.js new file mode 100644 index 0000000000..93ca243ced --- /dev/null +++ b/serve-proxy.js @@ -0,0 +1,321 @@ +#!/usr/bin/env node + +const http = require('http'); +const https = require('https'); +const url = require('url'); +const path = require('path'); +const fs = require('fs'); +const httpProxy = require('http-proxy'); + +// Default configuration +let config = { + port: 8000, + host: '0.0.0.0', + root: process.cwd(), + cache: false, + cors: true, + silent: false +}; + +// Parse command line arguments +function parseArgs() { + const args = process.argv.slice(2); + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (arg === '-p' && args[i + 1]) { + config.port = parseInt(args[i + 1]); + i++; + } else if (arg === '-a' && args[i + 1]) { + config.host = args[i + 1]; + i++; + } else if (arg === '-c-1') { + config.cache = false; + } else if (arg === '-c' && args[i + 1]) { + config.cache = parseInt(args[i + 1]) > 0; + i++; + } else if (arg === '--cors') { + config.cors = true; + } else if (arg === '-S' || arg === '--silent') { + config.silent = true; + } else if (arg === '--log-ip') { + config.logIp = true; + } else if (!arg.startsWith('-')) { + config.root = path.resolve(arg); + } + } +} + +// Create proxy server +const proxy = httpProxy.createProxyServer({ + changeOrigin: true, + secure: true, + followRedirects: true +}); + +// Handle proxy errors +proxy.on('error', (err, req, res) => { + console.error('Proxy Error:', err.message); + if (!res.headersSent) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: 'Proxy Error', message: err.message })); + } +}); + +// Modify proxy request headers +proxy.on('proxyReq', (proxyReq, req, res) => { + // Transform localhost:8000 to appear as phcode.dev domain + const originalHost = req.headers.host; + const originalReferer = req.headers.referer; + const originalOrigin = req.headers.origin; + + // Set target host + proxyReq.setHeader('Host', 'account.phcode.dev'); + + // Transform referer from localhost:8000 to phcode.dev + if (originalReferer && originalReferer.includes('localhost:8000')) { + const newReferer = originalReferer.replace(/localhost:8000/g, 'phcode.dev'); + proxyReq.setHeader('Referer', newReferer); + } else if (!originalReferer) { + proxyReq.setHeader('Referer', 'https://phcode.dev/'); + } + + // Transform origin from localhost:8000 to phcode.dev + if (originalOrigin && originalOrigin.includes('localhost:8000')) { + const newOrigin = originalOrigin.replace(/localhost:8000/g, 'phcode.dev'); + proxyReq.setHeader('Origin', newOrigin); + } else if (!originalOrigin) { + proxyReq.setHeader('Origin', 'https://phcode.dev'); + } + + // Ensure HTTPS scheme + proxyReq.setHeader('X-Forwarded-Proto', 'https'); + proxyReq.setHeader('X-Forwarded-For', req.connection.remoteAddress); + +}); + +// Modify proxy response headers +proxy.on('proxyRes', (proxyRes, req, res) => { + // Pass through cache control and other security headers + // But translate any domain references back to localhost for the browser + + const setCookieHeader = proxyRes.headers['set-cookie']; + if (setCookieHeader) { + // Transform any phcode.dev domain cookies back to localhost + const modifiedCookies = setCookieHeader.map(cookie => { + return cookie.replace(/domain=\.?phcode\.dev/gi, 'domain=localhost'); + }); + proxyRes.headers['set-cookie'] = modifiedCookies; + } + + // Ensure CORS headers if needed + if (config.cors) { + proxyRes.headers['Access-Control-Allow-Origin'] = '*'; + proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'; + proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control'; + } +}); + +// Get MIME type based on file extension +function getMimeType(filePath) { + const ext = path.extname(filePath).toLowerCase(); + const mimeTypes = { + '.html': 'text/html', + '.htm': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.eot': 'application/vnd.ms-fontobject' + }; + return mimeTypes[ext] || 'application/octet-stream'; +} + +// Serve static files +function serveStaticFile(req, res, filePath) { + fs.stat(filePath, (err, stats) => { + if (err) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('File not found'); + return; + } + + if (stats.isDirectory()) { + // Try to serve index.html from directory + const indexPath = path.join(filePath, 'index.html'); + fs.stat(indexPath, (err, indexStats) => { + if (!err && indexStats.isFile()) { + serveStaticFile(req, res, indexPath); + } else { + // List directory contents + fs.readdir(filePath, (err, files) => { + if (err) { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('Error reading directory'); + return; + } + + const html = ` + + +