diff --git a/resources/js/electron-plugin/dist/server/php.js b/resources/js/electron-plugin/dist/server/php.js index aa6420f5..b0f5585e 100644 --- a/resources/js/electron-plugin/dist/server/php.js +++ b/resources/js/electron-plugin/dist/server/php.js @@ -15,6 +15,7 @@ import { promisify } from 'util'; import { join } from 'path'; import { app } from 'electron'; import { execFile, spawn, spawnSync } from 'child_process'; +import { createServer } from 'net'; import state from "./state.js"; import getPort, { portNumbers } from 'get-port'; const storagePath = join(app.getPath('userData'), 'storage'); @@ -42,10 +43,31 @@ function shouldOptimize(store) { } function getPhpPort() { return __awaiter(this, void 0, void 0, function* () { - return yield getPort({ + const suggestedPort = yield getPort({ host: '127.0.0.1', port: portNumbers(8100, 9000) }); + if (yield canBindToPort(suggestedPort)) { + return suggestedPort; + } + console.warn(`Port ${suggestedPort} is not bindable, manually searching...`); + for (let port = suggestedPort + 1; port < 9000; port++) { + if (yield canBindToPort(port)) { + return port; + } + } + throw new Error('Could not find an available port in range 8100-9000'); + }); +} +function canBindToPort(port) { + return new Promise((resolve) => { + const server = createServer(); + server.listen(port, '127.0.0.1', () => { + server.close(() => resolve(true)); + }); + server.on('error', () => { + resolve(false); + }); }); } function retrievePhpIniSettings() { @@ -235,8 +257,6 @@ function serveApp(secret, apiPort, phpIniSettings) { console.log('Skipping Database migration while in development.'); console.log('You may migrate manually by running: php artisan native:migrate'); } - console.log('Starting PHP server...'); - const phpPort = yield getPhpPort(); let serverPath; let cwd; if (runningSecureBuild()) { @@ -247,6 +267,8 @@ function serveApp(secret, apiPort, phpIniSettings) { serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php'); cwd = join(appPath, 'public'); } + console.log('Starting PHP server...'); + const phpPort = yield getPhpPort(); const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], { cwd: cwd, env diff --git a/resources/js/electron-plugin/src/server/php.ts b/resources/js/electron-plugin/src/server/php.ts index 2d0f5ebe..562b258d 100644 --- a/resources/js/electron-plugin/src/server/php.ts +++ b/resources/js/electron-plugin/src/server/php.ts @@ -8,6 +8,7 @@ import {promisify} from 'util' import {join} from 'path' import {app} from 'electron' import {execFile, spawn, spawnSync} from 'child_process' +import {createServer} from 'net' import state from "./state.js"; import getPort, {portNumbers} from 'get-port'; import {ProcessResult} from "./ProcessResult.js"; @@ -50,10 +51,41 @@ function shouldOptimize(store) { } async function getPhpPort() { - return await getPort({ + // Try get-port first (fast path) + const suggestedPort = await getPort({ host: '127.0.0.1', port: portNumbers(8100, 9000) }); + + // Validate that we can actually bind to this port + if (await canBindToPort(suggestedPort)) { + return suggestedPort; + } + + // If get-port gave us a bad port, manually search starting from suggestedPort + 1 + console.warn(`Port ${suggestedPort} is not bindable, manually searching...`); + + for (let port = suggestedPort + 1; port < 9000; port++) { + if (await canBindToPort(port)) { + return port; + } + } + + throw new Error('Could not find an available port in range 8100-9000'); +} + +function canBindToPort(port: number): Promise { + return new Promise((resolve) => { + const server = createServer(); + + server.listen(port, '127.0.0.1', () => { + server.close(() => resolve(true)); + }); + + server.on('error', () => { + resolve(false); + }); + }); } async function retrievePhpIniSettings() { @@ -346,10 +378,6 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { console.log('You may migrate manually by running: php artisan native:migrate') } - console.log('Starting PHP server...'); - const phpPort = await getPhpPort(); - - let serverPath: string; let cwd: string; @@ -361,6 +389,8 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { cwd = join(appPath, 'public'); } + console.log('Starting PHP server...'); + const phpPort = await getPhpPort(); const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], { cwd: cwd, env @@ -371,7 +401,6 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { // Show urls called phpServer.stdout.on('data', (data) => { // [Tue Jan 14 19:51:00 2025] 127.0.0.1:52779 [POST] URI: /_native/api/events - if (parseInt(process.env.SHELL_VERBOSITY) > 0) { console.log(data.toString().trim()); } diff --git a/resources/js/electron-plugin/tests/api.test.ts b/resources/js/electron-plugin/tests/api.test.ts index 3abe8b85..2bcb3b8c 100644 --- a/resources/js/electron-plugin/tests/api.test.ts +++ b/resources/js/electron-plugin/tests/api.test.ts @@ -31,6 +31,9 @@ describe('API test', () => { }); it('starts API server on port 4000', async () => { + // NOTE: If this fails it may be you have a NativePHP app running locally + // and the port negotiation actually woks as expected (might be 4001). + // Quit any running NativePHP apps to verify. expect(apiServer.port).toBe(4000); });