@@ -4,18 +4,26 @@ import { ip } from 'address';
44
55const debug = debuglog ( 'detect-port' ) ;
66
7- type DetectPortCallback = ( err : Error | null , port ?: number ) => void ;
7+ export type DetectPortCallback = ( err : Error | null , port ?: number ) => void ;
88
9- interface PortConfig {
9+ export interface PortConfig {
1010 port ?: number | string ;
1111 hostname ?: string | undefined ;
1212 callback ?: DetectPortCallback ;
1313}
1414
15- export default function detectPort ( port ?: number | PortConfig | string ) : Promise < number > ;
16- export default function detectPort ( callback : DetectPortCallback ) : void ;
17- export default function detectPort ( port : number | PortConfig | string | undefined , callback : DetectPortCallback ) : void ;
18- export default function detectPort ( port ?: number | string | PortConfig | DetectPortCallback , callback ?: DetectPortCallback ) {
15+ export class IPAddressNotAvailableError extends Error {
16+ constructor ( options ?: ErrorOptions ) {
17+ super ( 'The IP address is not available on this machine' , options ) ;
18+ this . name = this . constructor . name ;
19+ Error . captureStackTrace ( this , this . constructor ) ;
20+ }
21+ }
22+
23+ export function detectPort ( port ?: number | PortConfig | string ) : Promise < number > ;
24+ export function detectPort ( callback : DetectPortCallback ) : void ;
25+ export function detectPort ( port : number | PortConfig | string | undefined , callback : DetectPortCallback ) : void ;
26+ export function detectPort ( port ?: number | string | PortConfig | DetectPortCallback , callback ?: DetectPortCallback ) {
1927 let hostname : string | undefined = '' ;
2028
2129 if ( port && typeof port === 'object' ) {
@@ -36,99 +44,102 @@ export default function detectPort(port?: number | string | PortConfig | DetectP
3644 }
3745 debug ( 'detect free port between [%s, %s)' , port , maxPort ) ;
3846 if ( typeof callback === 'function' ) {
39- return tryListen ( port , maxPort , hostname , callback ) ;
47+ return tryListen ( port , maxPort , hostname )
48+ . then ( port => callback ( null , port ) )
49+ . catch ( callback ) ;
4050 }
4151 // promise
42- return new Promise ( resolve => {
43- tryListen ( port as number , maxPort , hostname , ( _ , realPort ) => {
44- resolve ( realPort ) ;
45- } ) ;
46- } ) ;
52+ return tryListen ( port as number , maxPort , hostname ) ;
4753}
4854
49- function tryListen ( port : number , maxPort : number , hostname : string | undefined , callback : DetectPortCallback ) {
50- function handleError ( ) {
51- port ++ ;
52- if ( port >= maxPort ) {
53- debug ( 'port: %s >= maxPort: %s, give up and use random port' , port , maxPort ) ;
54- port = 0 ;
55- maxPort = 0 ;
56- }
57- tryListen ( port , maxPort , hostname , callback ) ;
55+ async function handleError ( port : number , maxPort : number , hostname ?: string ) {
56+ if ( port >= maxPort ) {
57+ debug ( 'port: %s >= maxPort: %s, give up and use random port' , port , maxPort ) ;
58+ port = 0 ;
59+ maxPort = 0 ;
5860 }
61+ return await tryListen ( port , maxPort , hostname ) ;
62+ }
5963
64+ async function tryListen ( port : number , maxPort : number , hostname ?: string ) : Promise < number > {
6065 // use user hostname
6166 if ( hostname ) {
62- listen ( port , hostname , ( err , realPort ) => {
63- if ( err ) {
64- if ( ( err as any ) . code === 'EADDRNOTAVAIL' ) {
65- return callback ( new Error ( 'The IP address is not available on this machine' ) ) ;
66- }
67- return handleError ( ) ;
67+ try {
68+ return await listen ( port , hostname ) ;
69+ } catch ( err : any ) {
70+ if ( err . code === 'EADDRNOTAVAIL' ) {
71+ throw new IPAddressNotAvailableError ( { cause : err } ) ;
6872 }
73+ return await handleError ( ++ port , maxPort , hostname ) ;
74+ }
75+ }
6976
70- callback ( null , realPort ) ;
71- } ) ;
72- } else {
73- // 1. check null
74- listen ( port , void 0 , ( err , realPort ) => {
75- // ignore random listening
76- if ( port === 0 ) {
77- return callback ( err , realPort ) ;
78- }
77+ // 1. check null / undefined
78+ try {
79+ await listen ( port ) ;
80+ } catch ( err ) {
81+ // ignore random listening
82+ if ( port === 0 ) {
83+ throw err ;
84+ }
85+ return await handleError ( ++ port , maxPort , hostname ) ;
86+ }
7987
80- if ( err ) {
81- return handleError ( ) ;
82- }
88+ // 2. check 0.0.0.0
89+ try {
90+ await listen ( port , '0.0.0.0' ) ;
91+ } catch ( err ) {
92+ return await handleError ( ++ port , maxPort , hostname ) ;
93+ }
8394
84- // 2. check 0.0.0.0
85- listen ( port , '0.0.0.0' , err => {
86- if ( err ) {
87- return handleError ( ) ;
88- }
89-
90- // 3. check localhost
91- listen ( port , 'localhost' , err => {
92- // if localhost refer to the ip that is not unkonwn on the machine, you will see the error EADDRNOTAVAIL
93- // https://stackoverflow.com/questions/10809740/listen-eaddrnotavail-error-in-node-js
94- if ( err && ( err as any ) . code !== 'EADDRNOTAVAIL' ) {
95- return handleError ( ) ;
96- }
97-
98- // 4. check current ip
99- listen ( port , ip ( ) , ( err , realPort ) => {
100- if ( err ) {
101- return handleError ( ) ;
102- }
103-
104- callback ( null , realPort ) ;
105- } ) ;
106- } ) ;
107- } ) ;
108- } ) ;
95+ // 3. check 127.0.0.1
96+ try {
97+ await listen ( port , '127.0.0.1' ) ;
98+ } catch ( err ) {
99+ return await handleError ( ++ port , maxPort , hostname ) ;
100+ }
101+
102+ // 4. check localhost
103+ try {
104+ await listen ( port , 'localhost' ) ;
105+ } catch ( err : any ) {
106+ // if localhost refer to the ip that is not unknown on the machine, you will see the error EADDRNOTAVAIL
107+ // https://stackoverflow.com/questions/10809740/listen-eaddrnotavail-error-in-node-js
108+ if ( err . code !== 'EADDRNOTAVAIL' ) {
109+ return await handleError ( ++ port , maxPort , hostname ) ;
110+ }
111+ }
112+
113+ // 5. check current ip
114+ try {
115+ return await listen ( port , ip ( ) ) ;
116+ } catch ( err ) {
117+ return await handleError ( ++ port , maxPort , hostname ) ;
109118 }
110119}
111120
112- function listen ( port : number , hostname : string | undefined , callback : DetectPortCallback ) {
121+ function listen ( port : number , hostname ? : string ) {
113122 const server = createServer ( ) ;
114123
115- server . once ( 'error' , err => {
116- debug ( 'listen %s:%s error: %s' , hostname , port , err ) ;
117- server . close ( ) ;
124+ return new Promise < number > ( ( resolve , reject ) => {
125+ server . once ( 'error' , err => {
126+ debug ( 'listen %s:%s error: %s' , hostname , port , err ) ;
127+ server . close ( ) ;
118128
119- if ( ( err as any ) . code === 'ENOTFOUND' ) {
120- debug ( 'ignore dns ENOTFOUND error, get free %s:%s' , hostname , port ) ;
121- return callback ( null , port ) ;
122- }
129+ if ( ( err as any ) . code === 'ENOTFOUND' ) {
130+ debug ( 'ignore dns ENOTFOUND error, get free %s:%s' , hostname , port ) ;
131+ return resolve ( port ) ;
132+ }
123133
124- return callback ( err ) ;
125- } ) ;
134+ return reject ( err ) ;
135+ } ) ;
126136
127- debug ( 'try listen %d on %s' , port , hostname ) ;
128- server . listen ( port , hostname , ( ) => {
129- port = ( server . address ( ) as AddressInfo ) . port ;
130- debug ( 'get free %s:%s' , hostname , port ) ;
131- server . close ( ) ;
132- return callback ( null , port ) ;
137+ debug ( 'try listen %d on %s' , port , hostname ) ;
138+ server . listen ( port , hostname , ( ) => {
139+ port = ( server . address ( ) as AddressInfo ) . port ;
140+ debug ( 'get free %s:%s' , hostname , port ) ;
141+ server . close ( ) ;
142+ return resolve ( port ) ;
143+ } ) ;
133144 } ) ;
134145}
0 commit comments