Skip to content

Commit 30fd6ef

Browse files
committed
fix(serve): check all network interfaces for an available port
1 parent 474788b commit 30fd6ef

File tree

4 files changed

+63
-9
lines changed

4 files changed

+63
-9
lines changed

packages/@ionic/cli-framework/src/utils/network.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ const debug = Debug('ionic:cli-framework:utils:network');
99

1010
export const ERROR_NETWORK_ADDRESS_NOT_AVAIL = 'NETWORK_ADDRESS_NOT_AVAIL';
1111

12+
export const DEFAULT_ADDRESSES: ReadonlyArray<string> = getDefaultAddresses();
13+
14+
function getDefaultAddresses(): string[] {
15+
const addresses: string[] = ['0.0.0.0'];
16+
17+
try {
18+
const networkInterfaces = os.networkInterfaces();
19+
20+
for (const device of Object.keys(networkInterfaces)) {
21+
const networkInterface = networkInterfaces[device];
22+
23+
addresses.push(...networkInterface.map(i => i.address));
24+
}
25+
} catch (e) {
26+
// swallow
27+
}
28+
29+
return addresses;
30+
}
31+
1232
export function getExternalIPv4Interfaces(): NetworkInterface[] {
1333
const networkInterfaces = os.networkInterfaces();
1434
const devices: NetworkInterface[] = [];
@@ -26,9 +46,17 @@ export function getExternalIPv4Interfaces(): NetworkInterface[] {
2646
return devices;
2747
}
2848

29-
export async function findClosestOpenPort(port: number, host?: string): Promise<number> {
49+
/**
50+
* Attempts to locate a port number starting from `port` and incrementing by 1.
51+
*
52+
* This function looks through all internal network interfaces, attempting
53+
* host/port combinations until it finds an available port on all interfaces.
54+
*
55+
* @param port The port at which to start checking.
56+
*/
57+
export async function findClosestOpenPort(port: number): Promise<number> {
3058
async function t(portToCheck: number): Promise<number> {
31-
if (await isPortAvailable(portToCheck, host)) {
59+
if (await isPortAvailable(portToCheck)) {
3260
return portToCheck;
3361
}
3462

@@ -38,7 +66,33 @@ export async function findClosestOpenPort(port: number, host?: string): Promise<
3866
return t(port);
3967
}
4068

41-
export async function isPortAvailable(port: number, host?: string): Promise<boolean> {
69+
/**
70+
* Checks whether a port is open or closed.
71+
*
72+
* This function looks through all internal network interfaces, checking
73+
* whether all host/port combinations are open. If one or more is not, the port
74+
* is not available.
75+
*/
76+
export async function isPortAvailable(port: number): Promise<boolean> {
77+
let available = true;
78+
79+
for (const address of DEFAULT_ADDRESSES) {
80+
try {
81+
debug('checking for open port on %s:%d', address, port);
82+
available = await isPortAvailableForHost(address, port);
83+
84+
if (!available) {
85+
return false;
86+
}
87+
} catch (e) {
88+
debug('error while checking %s:%d: %o', address, port, e);
89+
}
90+
}
91+
92+
return available;
93+
}
94+
95+
export function isPortAvailableForHost(host: string, port: number): Promise<boolean> {
4296
return new Promise<boolean>((resolve, reject) => {
4397
const tester = net.createServer()
4498
.once('error', (err: any) => {

packages/@ionic/cli-utils/src/lib/project/angular/serve.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ ${chalk.cyan('[1]')}: ${chalk.bold('https://github.com/angular/angular-cli/wiki/
9797
async serveProject(options: AngularServeOptions): Promise<ServeDetails> {
9898
const [ externalIP, availableInterfaces ] = await this.selectExternalIP(options);
9999

100-
const ngPort = options.port = await findClosestOpenPort(options.port, '0.0.0.0');
100+
const ngPort = options.port = await findClosestOpenPort(options.port);
101101
const { program } = await this.serveCommandWrapper(options);
102102

103103
const interval = setInterval(() => {

packages/@ionic/cli-utils/src/lib/project/common.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export interface Ports {
2121
export async function findOpenIonicPorts(address: string, ports: Ports): Promise<Ports> {
2222
try {
2323
const [ port, livereloadPort, notificationPort ] = await Promise.all([
24-
findClosestOpenPort(ports.port, '0.0.0.0'),
25-
findClosestOpenPort(ports.livereloadPort, '0.0.0.0'),
26-
findClosestOpenPort(ports.notificationPort, '0.0.0.0'),
24+
findClosestOpenPort(ports.port),
25+
findClosestOpenPort(ports.livereloadPort),
26+
findClosestOpenPort(ports.notificationPort),
2727
]);
2828

2929
if (ports.port !== port) {

packages/@ionic/cli-utils/src/lib/serve.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export abstract class ServeRunner<T extends ServeOptions> extends EventEmitter i
250250
const { port } = details;
251251

252252
// the comm server always binds to 0.0.0.0 to target every possible interface
253-
const commPort = await findClosestOpenPort(DEFAULT_DEVAPP_COMM_PORT, '0.0.0.0');
253+
const commPort = await findClosestOpenPort(DEFAULT_DEVAPP_COMM_PORT);
254254

255255
return { port, commPort, interfaces };
256256
}
@@ -317,7 +317,7 @@ export abstract class ServeRunner<T extends ServeOptions> extends EventEmitter i
317317
const labDetails: LabServeDetails = {
318318
protocol: options.ssl ? 'https' : 'http',
319319
address: options.labHost,
320-
port: await findClosestOpenPort(options.labPort, options.labHost),
320+
port: await findClosestOpenPort(options.labPort),
321321
};
322322

323323
if (options.ssl) {

0 commit comments

Comments
 (0)