Skip to content

Commit 67c4a33

Browse files
committed
chore: electron node connector scaffolding
1 parent 82eae86 commit 67c4a33

1 file changed

Lines changed: 202 additions & 96 deletions

File tree

src/node-loader.js

Lines changed: 202 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -635,110 +635,216 @@ function nodeLoader() {
635635
nodeErrorLogCount = 0;
636636
}, NODE_ERROR_LOGS_RESET_INTERVAL);
637637

638-
window.__TAURI__.path.resolveResource("src-node/index.js")
639-
.then(async nodeSrcPath=>{
640-
// Strip Windows UNC prefix (\\?\) that Tauri adds on Windows
641-
// Node 24 doesn't handle UNC paths correctly in module resolution
642-
if (Phoenix.platform === "win" && nodeSrcPath.startsWith('\\\\?\\')) {
643-
nodeSrcPath = nodeSrcPath.slice(4);
638+
async function _tauriNodeSetup() {
639+
let nodeSrcPath = await window.__TAURI__.path.resolveResource("src-node/index.js");
640+
// Strip Windows UNC prefix (\\?\) that Tauri adds on Windows
641+
// Node 24 doesn't handle UNC paths correctly in module resolution
642+
if (Phoenix.platform === "win" && nodeSrcPath.startsWith('\\\\?\\')) {
643+
nodeSrcPath = nodeSrcPath.slice(4);
644+
}
645+
if(Phoenix.platform === "linux") {
646+
// in linux installed distributions, src-node is present in the same dir as the executable.
647+
const cliArgs = await window.__TAURI__.invoke('_get_commandline_args');
648+
nodeSrcPath = `${window.path.dirname(cliArgs[0])}/src-node/index.js`;
649+
}
650+
// node is designed such that it is not required at boot time to lower startup time.
651+
// Keep this so to increase boot speed.
652+
const inspectPort = Phoenix.isTestWindow ? getRandomNumber(5000, 50000) : 9229;
653+
const argsArray = isInspectEnabled() ? [`--inspect=${inspectPort}`, nodeSrcPath] : [nodeSrcPath, ''];
654+
command = window.__TAURI__.shell.Command.sidecar('phnode', argsArray);
655+
command.on('close', data => {
656+
window.isNodeTerminated = true;
657+
window.isNodeReady = false;
658+
nodeTerminationResolve();
659+
console.log(`PhNode: command finished with code ${data.code} and signal ${data.signal}`);
660+
if(!resolved) {
661+
reject("PhNode: closed - Terminated.");
644662
}
645-
if(Phoenix.platform === "linux" && window.__TAURI__) {
646-
// in linux installed distributions, src-node is present in the same dir as the executable.
647-
const cliArgs = await window.__TAURI__.invoke('_get_commandline_args');
648-
nodeSrcPath = `${window.path.dirname(cliArgs[0])}/src-node/index.js`;
663+
});
664+
command.on('error', error => {
665+
window.isNodeTerminated = true;
666+
window.isNodeReady = false;
667+
nodeTerminationResolve();
668+
console.error(`PhNode: command error: "${error}"`);
669+
if(!resolved) {
670+
logger.reportError(error, `PhNode failed to start!`);
671+
reject("PhNode: closed - Terminated.");
649672
}
650-
// node is designed such that it is not required at boot time to lower startup time.
651-
// Keep this so to increase boot speed.
652-
const inspectPort = Phoenix.isTestWindow ? getRandomNumber(5000, 50000) : 9229;
653-
const argsArray = isInspectEnabled() ? [`--inspect=${inspectPort}`, nodeSrcPath] : [nodeSrcPath, ''];
654-
command = window.__TAURI__.shell.Command.sidecar('phnode', argsArray);
655-
command.on('close', data => {
656-
window.isNodeTerminated = true;
657-
window.isNodeReady = false;
658-
nodeTerminationResolve();
659-
console.log(`PhNode: command finished with code ${data.code} and signal ${data.signal}`);
660-
if(!resolved) {
661-
reject("PhNode: closed - Terminated.");
662-
}
663-
});
664-
command.on('error', error => {
665-
window.isNodeTerminated = true;
666-
window.isNodeReady = false;
667-
nodeTerminationResolve();
668-
console.error(`PhNode: command error: "${error}"`);
669-
if(!resolved) {
670-
logger.reportError(error, `PhNode failed to start!`);
671-
reject("PhNode: closed - Terminated.");
672-
}
673-
});
674-
command.stdout.on('data', line => {
675-
if(line){
676-
if(line.startsWith(COMMAND_RESPONSE_PREFIX)){
677-
// its a js response object
678-
line = line.replace(COMMAND_RESPONSE_PREFIX, "");
679-
const jsonMsg = JSON.parse(line);
680-
pendingCommands[jsonMsg.commandID].resolve(jsonMsg.message);
681-
delete pendingCommands[jsonMsg.commandID];
682-
} else if(line.startsWith(COMMAND_ERROR_PREFIX)){
683-
// its a js response object
684-
line = line.replace(COMMAND_ERROR_PREFIX, "");
685-
const err = JSON.parse(line);
686-
logger.reportError(err, `PhNode ${err.type}:${err.code?err.code:''}`);
687-
} else {
688-
console.log(`PhNode: ${line}`);
689-
}
673+
});
674+
command.stdout.on('data', line => {
675+
if(line){
676+
if(line.startsWith(COMMAND_RESPONSE_PREFIX)){
677+
// its a js response object
678+
line = line.replace(COMMAND_RESPONSE_PREFIX, "");
679+
const jsonMsg = JSON.parse(line);
680+
pendingCommands[jsonMsg.commandID].resolve(jsonMsg.message);
681+
delete pendingCommands[jsonMsg.commandID];
682+
} else if(line.startsWith(COMMAND_ERROR_PREFIX)){
683+
// its a js response object
684+
line = line.replace(COMMAND_ERROR_PREFIX, "");
685+
const err = JSON.parse(line);
686+
logger.reportError(err, `PhNode ${err.type}:${err.code?err.code:''}`);
687+
} else {
688+
console.log(`PhNode: ${line}`);
690689
}
690+
}
691+
});
692+
command.stderr.on('data', line => {
693+
if(window.debugMode || nodeErrorLogCount < MAX_NODE_ERROR_LOGS_ALLOWED){
694+
// in release builds, too many node errors from file system/other sources can
695+
// happen, Eg. user opens a very large project and fs watchers goes bust.
696+
// if that happens, the app may get stuck logging large number of errors to console, so
697+
// we show atmost 10 error lines every 10 seconds in non-debug builds.
698+
console.error(`PhNode: ${line}`);
699+
}
700+
nodeErrorLogCount ++;
701+
});
702+
child = await command.spawn();
703+
704+
const execNode = function (commandCode, commandData) {
705+
if(window.isNodeTerminated){
706+
return Promise.reject("Node is terminated! Cannot execute: " + commandCode);
707+
}
708+
const newCommandID = commandID ++;
709+
child.write(JSON.stringify({
710+
commandCode: commandCode, commandID: newCommandID, commandData
711+
}) + "\n");
712+
let resolveP, rejectP;
713+
const promise = new Promise((resolve, reject) => { resolveP = resolve; rejectP=reject; });
714+
pendingCommands[newCommandID]= {resolve: resolveP, reject: rejectP};
715+
return promise;
716+
};
717+
718+
window.PhNodeEngine.terminateNode = function () {
719+
if(!window.isNodeTerminated) {
720+
execNode(NODE_COMMANDS.TERMINATE);
721+
}
722+
return nodeTerminationPromise;
723+
};
724+
window.PhNodeEngine.getInspectPort = function () {
725+
return inspectPort;
726+
};
727+
728+
execNode(NODE_COMMANDS.GET_ENDPOINTS)
729+
.then(message=>{
730+
fs.setNodeWSEndpoint(message.phoenixFSURL);
731+
fs.forceUseNodeWSEndpoint(true);
732+
setNodeWSEndpoint(message.phoenixNodeURL);
733+
KernalModeTrust.localAutoAuthURL = message.autoAuthURL;
734+
window.isNodeReady = true;
735+
resolve(message);
736+
// node is designed such that it is not required at boot time to lower startup time.
737+
// Keep this so to increase boot speed.
738+
window.PhNodeEngine._nodeLoadTime = Date.now() - nodeLoadstartTime;
691739
});
692-
command.stderr.on('data', line => {
693-
if(window.debugMode || nodeErrorLogCount < MAX_NODE_ERROR_LOGS_ALLOWED){
694-
// in release builds, too many node errors from file system/other sources can
695-
// happen, Eg. user opens a very large project and fs watchers goes bust.
696-
// if that happens, the app may get stuck logging large number of errors to console, so
697-
// we show atmost 10 error lines every 10 seconds in non-debug builds.
698-
console.error(`PhNode: ${line}`);
740+
execNode(NODE_COMMANDS.SET_DEBUG_MODE, window.debugMode);
741+
}
742+
743+
async function _electronNodeSetup() {
744+
// Get phnode path and src-node path, similar to Tauri's sidecar approach
745+
const phNodePath = await window.electronAPI.getPhNodePath();
746+
const nodeSrcPath = `${await window.electronAPI.getSrcNodePath()}/index.js`;
747+
748+
const inspectPort = Phoenix.isTestWindow ? getRandomNumber(5000, 50000) : 9229;
749+
const argsArray = isInspectEnabled()
750+
? [`--inspect=${inspectPort}`, nodeSrcPath]
751+
: [nodeSrcPath, ''];
752+
753+
// Spawn the node process
754+
const instanceId = await window.electronAppAPI.spawnProcess(phNodePath, argsArray);
755+
756+
// Register event handlers - filter by our instanceId since callbacks are global
757+
window.electronAppAPI.onProcessClose((eventInstanceId, data) => {
758+
if (eventInstanceId !== instanceId) {
759+
return;
760+
}
761+
window.isNodeTerminated = true;
762+
window.isNodeReady = false;
763+
nodeTerminationResolve();
764+
console.log(`PhNode: command finished with code ${data.code} and signal ${data.signal}`);
765+
if (!resolved) {
766+
reject("PhNode: closed - Terminated.");
767+
}
768+
});
769+
770+
window.electronAppAPI.onProcessStdout((eventInstanceId, line) => {
771+
if (eventInstanceId !== instanceId) {
772+
return;
773+
}
774+
if (line) {
775+
if (line.startsWith(COMMAND_RESPONSE_PREFIX)) {
776+
// its a js response object
777+
line = line.replace(COMMAND_RESPONSE_PREFIX, "");
778+
const jsonMsg = JSON.parse(line);
779+
pendingCommands[jsonMsg.commandID].resolve(jsonMsg.message);
780+
delete pendingCommands[jsonMsg.commandID];
781+
} else if (line.startsWith(COMMAND_ERROR_PREFIX)) {
782+
// its a js response object
783+
line = line.replace(COMMAND_ERROR_PREFIX, "");
784+
const err = JSON.parse(line);
785+
logger.reportError(err, `PhNode ${err.type}:${err.code ? err.code : ''}`);
786+
} else {
787+
console.log(`PhNode: ${line}`);
699788
}
700-
nodeErrorLogCount ++;
789+
}
790+
});
791+
792+
window.electronAppAPI.onProcessStderr((eventInstanceId, line) => {
793+
if (eventInstanceId !== instanceId) {
794+
return;
795+
}
796+
if (window.debugMode || nodeErrorLogCount < MAX_NODE_ERROR_LOGS_ALLOWED) {
797+
console.error(`PhNode: ${line}`);
798+
}
799+
nodeErrorLogCount++;
800+
});
801+
802+
const execNode = function (commandCode, commandData) {
803+
if (window.isNodeTerminated) {
804+
return Promise.reject("Node is terminated! Cannot execute: " + commandCode);
805+
}
806+
const newCommandID = commandID++;
807+
window.electronAppAPI.writeToProcess(instanceId, JSON.stringify({
808+
commandCode: commandCode, commandID: newCommandID, commandData
809+
}) + "\n");
810+
let resolveP, rejectP;
811+
const promise = new Promise((res, rej) => { resolveP = res; rejectP = rej; });
812+
pendingCommands[newCommandID] = { resolve: resolveP, reject: rejectP };
813+
return promise;
814+
};
815+
816+
window.PhNodeEngine.terminateNode = function () {
817+
if (!window.isNodeTerminated) {
818+
execNode(NODE_COMMANDS.TERMINATE);
819+
}
820+
return nodeTerminationPromise;
821+
};
822+
window.PhNodeEngine.getInspectPort = function () {
823+
return inspectPort;
824+
};
825+
826+
execNode(NODE_COMMANDS.GET_ENDPOINTS)
827+
.then(message => {
828+
fs.setNodeWSEndpoint(message.phoenixFSURL);
829+
fs.forceUseNodeWSEndpoint(true);
830+
setNodeWSEndpoint(message.phoenixNodeURL);
831+
KernalModeTrust.localAutoAuthURL = message.autoAuthURL;
832+
window.isNodeReady = true;
833+
resolve(message);
834+
window.PhNodeEngine._nodeLoadTime = Date.now() - nodeLoadstartTime;
701835
});
702-
child = await command.spawn();
836+
execNode(NODE_COMMANDS.SET_DEBUG_MODE, window.debugMode);
837+
}
703838

704-
const execNode = function (commandCode, commandData) {
705-
if(window.isNodeTerminated){
706-
return Promise.reject("Node is terminated! Cannot execute: " + commandCode);
707-
}
708-
const newCommandID = commandID ++;
709-
child.write(JSON.stringify({
710-
commandCode: commandCode, commandID: newCommandID, commandData
711-
}) + "\n");
712-
let resolveP, rejectP;
713-
const promise = new Promise((resolve, reject) => { resolveP = resolve; rejectP=reject; });
714-
pendingCommands[newCommandID]= {resolve: resolveP, reject: rejectP};
715-
return promise;
716-
};
717-
718-
window.PhNodeEngine.terminateNode = function () {
719-
if(!window.isNodeTerminated) {
720-
execNode(NODE_COMMANDS.TERMINATE);
721-
}
722-
return nodeTerminationPromise;
723-
};
724-
window.PhNodeEngine.getInspectPort = function () {
725-
return inspectPort;
726-
};
727-
728-
execNode(NODE_COMMANDS.GET_ENDPOINTS)
729-
.then(message=>{
730-
fs.setNodeWSEndpoint(message.phoenixFSURL);
731-
fs.forceUseNodeWSEndpoint(true);
732-
setNodeWSEndpoint(message.phoenixNodeURL);
733-
KernalModeTrust.localAutoAuthURL = message.autoAuthURL;
734-
window.isNodeReady = true;
735-
resolve(message);
736-
// node is designed such that it is not required at boot time to lower startup time.
737-
// Keep this so to increase boot speed.
738-
window.PhNodeEngine._nodeLoadTime = Date.now() - nodeLoadstartTime;
739-
});
740-
execNode(NODE_COMMANDS.SET_DEBUG_MODE, window.debugMode);
839+
if(window.__TAURI__) {
840+
_tauriNodeSetup().catch(err => {
841+
logger.reportError(err, "PhNode Tauri setup failed");
741842
});
843+
} else if(window.__ELECTRON__) {
844+
_electronNodeSetup().catch(err => {
845+
logger.reportError(err, "PhNode Electron setup failed");
846+
});
847+
}
742848
});
743849
}
744850

0 commit comments

Comments
 (0)