diff --git a/electron/constants.js b/electron/constants.js index 833d55495..51a64260a 100644 --- a/electron/constants.js +++ b/electron/constants.js @@ -1,3 +1,4 @@ +const PORT_RANGE = { startPort: 39152, endPort: 65535 }; const ERROR_ADDRESS_IN_USE = 'EADDRINUSE'; module.exports = { ERROR_ADDRESS_IN_USE, diff --git a/electron/main.js b/electron/main.js index 1a8420e4a..a0f090770 100644 --- a/electron/main.js +++ b/electron/main.js @@ -25,6 +25,8 @@ const { OperateDirectory, } = require('./install'); const { killProcesses } = require('./processes'); +const { isPortAvailable, findAvailablePort } = require('./ports'); +const { PORT_RANGE } = require('./constants'); // Attempt to acquire the single instance lock const singleInstanceLock = app.requestSingleInstanceLock(); @@ -32,7 +34,7 @@ if (!singleInstanceLock) app.quit(); const platform = os.platform(); const isDev = process.env.NODE_ENV === 'development'; -const appConfig = { +let appConfig = { width: 600, height: 800, ports: { @@ -170,7 +172,7 @@ async function launchDaemon() { const check = new Promise(function (resolve, reject) { operateDaemon = spawn(OperateCmd, [ 'daemon', - '--port=8000', + `--port=${appConfig.ports.prod.operate}`, `--home=${OperateDirectory}`, ]); operateDaemonPid = operateDaemon.pid; @@ -194,7 +196,7 @@ async function launchDaemonDev() { 'run', 'operate', 'daemon', - '--port=8000', + `--port=${appConfig.ports.dev.operate}`, '--home=.operate', ]); operateDaemonPid = operateDaemon.pid; @@ -238,7 +240,12 @@ async function launchNextApp() { async function launchNextAppDev() { await new Promise(function (resolve, reject) { - nextAppProcess = spawn('yarn', ['dev:frontend']); + process.env.NEXT_PUBLIC_BACKEND_PORT = appConfig.ports.dev.operate; // must set next env var to connect to backend + nextAppProcess = spawn('yarn', [ + 'dev:frontend', + '--port', + appConfig.ports.dev.next, + ]); nextAppProcessPid = nextAppProcess.pid; nextAppProcess.stdout.on('data', (data) => { console.log(data.toString().trim()); @@ -268,16 +275,54 @@ ipcMain.on('check', async function (event, argument) { 'response', 'Starting Operate Daemon In Development Mode', ); + + const daemonDevPortAvailable = await isPortAvailable( + appConfig.ports.dev.operate, + ); + + if (!daemonDevPortAvailable) { + appConfig.ports.dev.operate = await findAvailablePort({ + ...PORT_RANGE, + }); + } await launchDaemonDev(); event.sender.send( 'response', 'Starting Frontend Server In Development Mode', ); + + const frontendDevPortAvailable = await isPortAvailable( + appConfig.ports.dev.next, + ); + + if (!frontendDevPortAvailable) { + appConfig.ports.dev.next = await findAvailablePort({ + ...PORT_RANGE, + excludePorts: [appConfig.ports.dev.operate], + }); + } await launchNextAppDev(); } else { event.sender.send('response', 'Starting Operate Daemon'); + const daemonPortAvailable = await isPortAvailable( + appConfig.ports.prod.operate, + ); + if (!daemonPortAvailable) { + appConfig.ports.prod.operate = await findAvailablePort({ + ...PORT_RANGE, + }); + } await launchDaemon(); event.sender.send('response', 'Starting Frontend Server'); + const frontendPortAvailable = await isPortAvailable( + appConfig.ports.prod.next, + ); + if (!frontendPortAvailable) { + appConfig.ports.prod.next = await findAvailablePort({ + ...PORT_RANGE, + excludePorts: [appConfig.ports.prod.operate], + }); + } await launchNextApp(); } diff --git a/electron/ports.js b/electron/ports.js index e88f566df..d01697ca0 100644 --- a/electron/ports.js +++ b/electron/ports.js @@ -1,56 +1,79 @@ const net = require('net'); const { ERROR_ADDRESS_IN_USE } = require('./constants'); -const portRange = { startPort: 39152, endPort: 65535 }; //only source dynamic and private ports https://www.arubanetworks.com/techdocs/AOS-S/16.10/MRG/YC/content/common%20files/tcp-por-num-ran.htm - -const isPortAvailable = async (port) => { +/** + * Finds an available port within the specified range, excluding specified ports. + * @param {number} startPort - The start of the port range. + * @param {number} endPort - The end of the port range. + * @param {Array} excludePorts - An array of ports to be skipped. + * @returns {Promise} The first available port found within the range that's not excluded. + */ +function findAvailablePort({ startPort, endPort, excludePorts = [] }) { return new Promise((resolve, reject) => { - const server = net.createServer(); + let currentPort = startPort; - server.once('error', (err) => { - if (err.code === ERROR_ADDRESS_IN_USE) { - resolve(false); - } else { - reject(err); + const tryPort = (port) => { + if (excludePorts.includes(port)) { + if (currentPort < endPort) { + tryPort(++currentPort); + } else { + reject( + new Error( + `Unable to find an available port between ${startPort} and ${endPort} excluding specified ports.`, + ), + ); + } + return; } - }); - server.once('listening', () => { - server.close(); - resolve(true); - }); + const server = net.createServer(); - server.listen(port, 'localhost'); - }); -}; - -const findAvailablePort = async (startPort, endPort) => { - return new Promise((resolve, reject) => { - const server = net.createServer(); + server.listen(port, () => { + server.close(() => { + resolve(port); + }); + }); - server.on('error', (err) => { - if (err.code === ERROR_ADDRESS_IN_USE) { - if (startPort < endPort) { - findAvailablePort(startPort + 1, endPort) - .then(resolve) - .catch(reject); + server.on('error', (err) => { + if (err.code === ERROR_ADDRESS_IN_USE && currentPort < endPort) { + // Try the next port if the current one is in use or excluded + tryPort(++currentPort); } else { - reject(new Error('No available ports in the specified range')); + reject( + new Error( + `Unable to find an available port between ${startPort} and ${endPort} excluding specified ports.`, + ), + ); } - } else { - reject(err); - } - }); + }); + }; + + tryPort(currentPort); + }); +} - server.on('listening', () => { - const port = server.address().port; +/** + * Checks if a port is available. + * @param {number} port - The port to check. + * @returns {Promise} Whether the port is available. + */ +function isPortAvailable(port) { + return new Promise((resolve) => { + const server = net.createServer(); + + server.listen(port, () => { server.close(() => { - resolve(port); + resolve(true); }); }); - server.listen(startPort, 'localhost'); + server.on('error', () => { + resolve(false); + }); }); -}; +} -module.exports = { isPortAvailable, portRange, findAvailablePort }; +module.exports = { + findAvailablePort, + isPortAvailable, +}; diff --git a/package.json b/package.json index 710714d3a..2c4e001d4 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "private": true, "build": { "appId": "com.olas.operate", - "productName": "Operate App", + "productName": "Olas Operate", "directories": { "output": "dist" },