Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ import(pathToFileURL(path.join(root, entry)).href).catch((error) => {
env_vars = ["PHOENIX_DESKTOP_PATH", "PHOENIX_MCP_WS_PORT"]
# Live-preview connection and execution can take longer than the default 60s.
tool_timeout_sec = 120

[mcp_servers.phoenix-builder.tools.get_phoenix_status]
approval_mode = "approve"
22 changes: 20 additions & 2 deletions serve-proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,26 @@ const server = http.createServer((req, res) => {
}
}

// Serve static files
let filePath = path.join(config.root, parsedUrl.pathname);
// Serve static files. url.parse leaves the pathname percent encoded, so a
// directory or file whose name contains a space was looked up on disk as
// "sub%20dir" and answered 404 even though it was right there. Decode before
// touching the filesystem - the traversal check below still runs on the
// result, which is what stops an encoded "%2e%2e" from buying anything.
let decodedPathname;
try {
decodedPathname = decodeURIComponent(parsedUrl.pathname);
} catch (e) {
// A malformed escape is a bad request, not a missing file.
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Bad Request');
return;
}
if (decodedPathname.indexOf('\0') !== -1) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Bad Request');
return;
}
let filePath = path.join(config.root, decodedPathname);

// Security: prevent directory traversal
const normalizedPath = path.normalize(filePath);
Expand Down
57 changes: 51 additions & 6 deletions src-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,62 @@ rl.on('close', () => {
process.exit(1);
});

// Opt-in watchdog for test processes; disabled by default. node-loader.js arms
// it for Phoenix.isTestWindow, and SpecRunnerUtils refreshes the runner and its
// test window. Normal editor sessions never send setIdleExit.
//
// Tauri owns this process and holds stdin open after the spawning page goes away.
// A terminate command sent during beforeunload can be lost, leaving Node and its
// LSP servers running. Test runs accept timeout-based cleanup for these orphans.
// Keep this disabled in normal editor sessions: sleep, page suspension or long
// pauses can stop heartbeats even though the page still exists. Expiry can then
// trigger a Node crash dialog after wake. Earlier heartbeat/socket orphan checks
// were removed for sleep-related crashes in a1e660cb5 and ba5800d93.
let _idleExitMs = 0;
let _idleExitTimer = null;

function _shutdown(reason) {
lmdb.dumpDBToFileAndCloseDB()
.catch(console.error)
.finally(() => {
console.log(reason);
process.exit(0);
});
}

function _refreshIdleExit() {
if (!_idleExitMs) {
return;
}
if (_idleExitTimer) {
clearTimeout(_idleExitTimer);
}
_idleExitTimer = setTimeout(() => {
_shutdown(`No command for ${_idleExitMs}ms, the page that spawned us is gone.`);
}, _idleExitMs);
// never let this timer alone hold the process up
if (_idleExitTimer.unref) {
_idleExitTimer.unref();
}
}

function processCommand(line) {
try{
let jsonCmd = JSON.parse(line);
// any command at all is proof the page is still there
_refreshIdleExit();
switch (jsonCmd.commandCode) {
case "setIdleExit":
_idleExitMs = Number(jsonCmd.commandData) || 0;
if (!_idleExitMs && _idleExitTimer) {
clearTimeout(_idleExitTimer);
_idleExitTimer = null;
}
_refreshIdleExit();
_sendResponse(_idleExitMs, jsonCmd.commandID);
return;
case "terminate":
lmdb.dumpDBToFileAndCloseDB()
.catch(console.error)
.finally(()=>{
console.log("Node terminated by phcode.");
process.exit(0);
});
_shutdown("Node terminated by phcode.");
return;
case "ping": _sendResponse("pong", jsonCmd.commandID); return;
case "setDebugMode":
Expand Down
Loading
Loading