@@ -67,10 +67,10 @@ export interface ServerOptions {
67
67
pushManifestPath ?: string ;
68
68
}
69
69
70
- function applyDefaultOptions ( options : ServerOptions ) : ServerOptions {
70
+ async function applyDefaultOptions ( options : ServerOptions ) : Promise < ServerOptions > {
71
71
const withDefaults = Object . assign ( { } , options ) ;
72
72
Object . assign ( withDefaults , {
73
- port : options . port || 8080 ,
73
+ port : await nextOpenPort ( options . port ) ,
74
74
hostname : options . hostname || 'localhost' ,
75
75
root : path . resolve ( options . root || '.' ) ,
76
76
certPath : options . certPath || 'cert.pem' ,
@@ -80,28 +80,32 @@ function applyDefaultOptions(options: ServerOptions): ServerOptions {
80
80
}
81
81
82
82
/**
83
- * @return {Promise } A Promise that completes when the server has started.
83
+ * If port unspecified/negative, finds an open port on localhost
84
+ * @param {number } port
85
+ * @returns {Promise<number> } Promise of open port
84
86
*/
85
- export function startServer ( options : ServerOptions ) : Promise < http . Server > {
86
- return new Promise ( ( resolve ) => {
87
- options = options || { } ;
88
-
89
- assertNodeVersion ( options ) ;
90
-
91
- if ( options . port ) {
92
- resolve ( options ) ;
93
- } else {
94
- findPort ( 8080 , 8180 , ( ports ) => {
95
- options . port = ports [ 0 ] ;
96
- resolve ( options ) ;
87
+ async function nextOpenPort ( port : number ) : Promise < number > {
88
+ if ( ! port || port < 0 ) {
89
+ port = await new Promise < number > ( resolve => {
90
+ findPort ( 8080 , 8180 , ( ports : number [ ] ) => {
91
+ resolve ( ports [ 0 ] ) ;
97
92
} ) ;
98
- }
99
- } )
100
- . then < http . Server > ( ( opts ) => startWithPort ( opts ) )
101
- . catch ( ( e ) => {
102
- console . error ( 'ERROR: Server failed to start:' , e ) ;
103
- return Promise . reject ( e ) ;
104
- } ) ;
93
+ } ) ;
94
+ }
95
+ return port ;
96
+ }
97
+
98
+ /**
99
+ * @return {Promise } A Promise that completes when the server has started.
100
+ */
101
+ export async function startServer ( options : ServerOptions ) : Promise < http . Server > {
102
+ options = options || { } ;
103
+ assertNodeVersion ( options ) ;
104
+ return startWithPort ( options )
105
+ . catch ( ( e ) => {
106
+ console . error ( 'ERROR: Server failed to start:' , e ) ;
107
+ return Promise . reject ( e ) ;
108
+ } ) ;
105
109
}
106
110
107
111
const portInUseMessage = ( port : number ) => `
@@ -232,59 +236,74 @@ function handleServerReady(options: ServerOptions) {
232
236
233
237
/**
234
238
* Generates a TLS certificate for HTTPS
235
- * @param {string } keyPath path to TLS service key
236
- * @param {string } certPath path to TLS certificate
237
- * @returns {Promise<{}> } Promise of {serviceKey: string, certificate: string}
239
+ * @returns {Promise<{}> } Promise of {key: string, cert: string}
238
240
*/
239
- function createTLSCertificate ( keyPath : string , certPath : string ) {
240
- return new Promise < { } > ( ( resolve , reject ) => {
241
+ async function createTLSCertificate ( ) : Promise < { key : string , cert : string } > {
242
+ const keys : any = await new Promise ( ( resolve , reject ) => {
241
243
console . log ( 'Generating TLS certificate...' ) ;
242
- pem . createCertificate ( { days : 1 , selfSigned : true } , ( err : any , keys : any ) => {
244
+ pem . createCertificate ( {
245
+ days : 365 ,
246
+ selfSigned : true
247
+ } , ( err : any , keys : any ) => {
243
248
if ( err ) {
244
249
reject ( err ) ;
245
250
} else {
246
- Promise . all ( [
247
- fs . writeFile ( certPath , keys . certificate ) ,
248
- fs . writeFile ( keyPath , keys . serviceKey )
249
- ] )
250
- . then ( ( ) => resolve ( keys ) ) ;
251
+ resolve ( keys ) ;
251
252
}
252
253
} ) ;
253
254
} ) ;
255
+
256
+ return {
257
+ cert : keys . certificate ,
258
+ key : keys . serviceKey ,
259
+ } ;
254
260
}
255
261
256
262
/**
257
263
* Gets the current TLS certificate (from current directory)
258
264
* or generates one if needed
259
265
* @param {string } keyPath path to TLS service key
260
266
* @param {string } certPath path to TLS certificate
261
- * @returns {Promise<{}> } Promise of {serviceKey : string, certificate : string}
267
+ * @returns {Promise<{}> } Promise of {key : string, cert : string}
262
268
*/
263
- function getTLSCertificate ( keyPath : string , certPath : string ) {
264
- let certificate : string ;
265
- let serviceKey : string ;
269
+ async function getTLSCertificate ( keyPath : string , certPath : string ) : Promise < { key : string , cert : string } > {
270
+ let certObj : { cert : string , key : string } ;
271
+
272
+ if ( keyPath && certPath ) {
273
+ // TODO: Simplify code with ES6 destructuring when TypeScript 2.1 arrives.
274
+ //
275
+ // While TypeScript 2.0 already supports it, tsc does not transpile async/await
276
+ // to ES5, which is scheduled for TypeScript 2.1. The advantages of async/await
277
+ // outweigh that of array destructuring, so go the verbose way for now...
278
+
279
+ const certData = await Promise . all ( [
280
+ fs . readFile ( certPath )
281
+ . then ( ( value : Buffer ) => value . toString ( ) . trim ( ) ) ,
282
+ fs . readFile ( keyPath )
283
+ . then ( ( value : Buffer ) => value . toString ( ) . trim ( ) )
284
+ ] ) ;
285
+ const cert : string = certData [ 0 ] ;
286
+ const key : string = certData [ 1 ] ;
287
+ if ( key && cert ) {
288
+ certObj = {
289
+ cert : cert ,
290
+ key : key ,
291
+ } ;
292
+ }
293
+ }
266
294
267
- const validate = ( data : string ) => {
268
- if ( ! data ) {
269
- throw new Error ( 'invalid data' ) ;
270
- } else {
271
- return data ;
295
+ if ( ! certObj ) {
296
+ certObj = await createTLSCertificate ( ) ;
297
+
298
+ if ( keyPath && certPath ) {
299
+ const _ = await Promise . all ( [
300
+ fs . writeFile ( certPath , certObj . cert ) ,
301
+ fs . writeFile ( keyPath , certObj . key )
302
+ ] ) ;
272
303
}
273
- } ;
304
+ }
274
305
275
- return Promise . all ( [
276
- fs . readFile ( certPath )
277
- . then ( ( value : Buffer ) => value . toString ( ) . trim ( ) )
278
- . then ( ( data : string ) => { certificate = validate ( data ) ; } ) ,
279
- fs . readFile ( keyPath )
280
- . then ( ( value : Buffer ) => value . toString ( ) . trim ( ) )
281
- . then ( ( data : string ) => { serviceKey = validate ( data ) ; } )
282
- ] )
283
- . then ( ( ) => ( {
284
- certificate : certificate ,
285
- serviceKey : serviceKey
286
- } ) )
287
- . catch ( ( ) => createTLSCertificate ( keyPath , certPath ) ) ;
306
+ return certObj ;
288
307
}
289
308
290
309
/**
@@ -307,52 +326,46 @@ function assertNodeVersion(options: ServerOptions) {
307
326
* @param {ServerOptions } options
308
327
* @returns {Promise<http.Server> } Promise of server
309
328
*/
310
- function createServer ( app : any , options : ServerOptions ) : Promise < http . Server > {
311
- let p : Promise < http . Server > ;
329
+ async function createServer ( app : any , options : ServerOptions ) : Promise < http . Server > {
330
+ const opt : any = {
331
+ spdy : { protocols : [ options . protocol ] }
332
+ } ;
333
+
312
334
if ( isHttps ( options . protocol ) ) {
313
- p = getTLSCertificate ( options . keyPath , options . certPath )
314
- . then ( ( keys : any ) => {
315
- let opt = {
316
- spdy : { protocols : [ options . protocol ] } ,
317
- key : keys . serviceKey ,
318
- cert : keys . certificate
319
- } ;
320
- let server = http . createServer ( opt , app ) ;
321
- return Promise . resolve ( server ) ;
322
- } ) ;
335
+ const keys = await getTLSCertificate ( options . keyPath , options . certPath ) ;
336
+ opt . key = keys . key ;
337
+ opt . cert = keys . cert ;
323
338
} else {
324
- const spdyOptions = { protocols : [ options . protocol ] , plain : true , ssl : false } ;
325
- const server = http . createServer ( { spdy : spdyOptions } , app ) ;
326
- p = Promise . resolve ( server ) ;
339
+ opt . spdy . plain = true ;
340
+ opt . spdy . ssl = false ;
327
341
}
328
- return p ;
342
+
343
+ return http . createServer ( opt , app ) ;
329
344
}
330
345
331
346
/**
332
347
* Starts an HTTP(S) server on a specific port
333
348
* @param {ServerOptions } userOptions
334
349
* @returns {Promise<http.Server> } Promise of server
335
350
*/
336
- function startWithPort ( userOptions : ServerOptions ) : Promise < http . Server > {
337
- const options = applyDefaultOptions ( userOptions ) ;
351
+ async function startWithPort ( userOptions : ServerOptions ) : Promise < http . Server > {
352
+ const options = await applyDefaultOptions ( userOptions ) ;
338
353
const app = getApp ( options ) ;
339
354
340
- return createServer ( app , options )
341
- . then ( ( server ) => new Promise < http . Server > ( ( resolve , reject ) => {
342
- server . listen ( options . port , options . hostname , ( ) => {
343
- resolve ( server ) ;
344
- handleServerReady ( options ) ;
345
- } ) ;
355
+ const server = await createServer ( app , options ) ;
356
+ server . listen ( options . port , options . hostname , ( ) => {
357
+ handleServerReady ( options ) ;
358
+ } ) ;
359
+
360
+ server . on ( 'error' , ( err : any ) => {
361
+ if ( err . code === 'EADDRINUSE' ) {
362
+ console . error ( portInUseMessage ( options . port ) ) ;
363
+ }
364
+ console . warn ( 'rejecting with err' , err ) ;
365
+ throw new Error ( err ) ;
366
+ } ) ;
346
367
347
- server . on ( 'error' , ( err : any ) => {
348
- if ( err . code === 'EADDRINUSE' ) {
349
- console . error ( portInUseMessage ( options . port ) ) ;
350
- }
351
- console . warn ( 'rejecting with err' , err ) ;
352
- reject ( err ) ;
353
- } ) ;
354
- } )
355
- ) ;
368
+ return server ;
356
369
}
357
370
358
371
/**
0 commit comments