Skip to content

Commit

Permalink
Update port handling and configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
truemiller committed Feb 27, 2024
1 parent 94199ab commit 26067b7
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 43 deletions.
1 change: 1 addition & 0 deletions electron/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const PORT_RANGE = { startPort: 39152, endPort: 65535 };
const ERROR_ADDRESS_IN_USE = 'EADDRINUSE';
module.exports = {
ERROR_ADDRESS_IN_USE,
Expand Down
53 changes: 49 additions & 4 deletions electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ 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();
if (!singleInstanceLock) app.quit();

const platform = os.platform();
const isDev = process.env.NODE_ENV === 'development';
const appConfig = {
let appConfig = {
width: 600,
height: 800,
ports: {
Expand Down Expand Up @@ -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;
Expand All @@ -194,7 +196,7 @@ async function launchDaemonDev() {
'run',
'operate',
'daemon',
'--port=8000',
`--port=${appConfig.ports.dev.operate}`,
'--home=.operate',
]);
operateDaemonPid = operateDaemon.pid;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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();
}

Expand Down
99 changes: 61 additions & 38 deletions electron/ports.js
Original file line number Diff line number Diff line change
@@ -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<number>} excludePorts - An array of ports to be skipped.
* @returns {Promise<number>} 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<boolean>} 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,
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"private": true,
"build": {
"appId": "com.olas.operate",
"productName": "Operate App",
"productName": "Olas Operate",
"directories": {
"output": "dist"
},
Expand Down

0 comments on commit 26067b7

Please sign in to comment.