33 *
44 * Check main license for more information
55 */
6- import { spawn , ChildProcess } from "node:child_process" ;
6+ import { spawn , type ChildProcess } from "node:child_process" ;
7+ import { once } from "node:events" ;
78import type { Connection } from "./types.ts" ;
89import { cloudflaredBinPath , connRegex , ipRegex , locationRegex , indexRegex } from "./constants.ts" ;
910
@@ -22,8 +23,8 @@ export function startCloudflaredTunnel(
2223 connections : Promise < Connection > [ ] ;
2324 /** Spwaned cloudflared process */
2425 child : ChildProcess ;
25- /** Stop the cloudflared process */
26- stop : ChildProcess [ "kill" ] ;
26+ /** Stop the cloudflared process and wait for it to exit */
27+ stop : ( ) => Promise < void > ;
2728} {
2829 const args : string [ ] = [ "tunnel" ] ;
2930 for ( const [ key , value ] of Object . entries ( options ) ) {
@@ -56,6 +57,8 @@ export function startCloudflaredTunnel(
5657 let urlResolver : ( value : string | PromiseLike < string > ) => void = ( ) => undefined ;
5758 let urlRejector : ( reason : unknown ) => void = ( ) => undefined ;
5859 const url = new Promise < string > ( ( ...pair ) => ( [ urlResolver , urlRejector ] = pair ) ) ;
60+ // Avoid unhandled-rejection warnings if no consumer awaits before child fails.
61+ url . catch ( ( ) => undefined ) ;
5962
6063 const connectionResolvers : ( ( value : Connection | PromiseLike < Connection > ) => void ) [ ] = [ ] ;
6164 const connectionRejectors : ( ( reason : unknown ) => void ) [ ] = [ ] ;
@@ -90,8 +93,28 @@ export function startCloudflaredTunnel(
9093 } ;
9194 child . stdout . on ( "data" , parser ) . on ( "error" , urlRejector ) ;
9295 child . stderr . on ( "data" , parser ) . on ( "error" , urlRejector ) ;
96+ child . on ( "error" , urlRejector ) ;
97+ child . on ( "exit" , ( code , signal ) => {
98+ const reason = new Error (
99+ `cloudflared exited (code=${ code } , signal=${ signal } ) before URL was ready` ,
100+ ) ;
101+ urlRejector ( reason ) ;
102+ for ( const reject of connectionRejectors ) reject ?.( reason ) ;
103+ } ) ;
93104
94- const stop = ( ) => child . kill ( "SIGINT" ) ;
105+ const stop = async ( ) : Promise < void > => {
106+ if ( child . exitCode !== null || child . signalCode !== null ) return ;
107+ const exited = once ( child , "exit" ) ;
108+ child . kill ( "SIGINT" ) ;
109+ const killTimer = setTimeout ( ( ) => {
110+ if ( child . exitCode === null && child . signalCode === null ) child . kill ( "SIGKILL" ) ;
111+ } , 5000 ) ;
112+ try {
113+ await exited ;
114+ } finally {
115+ clearTimeout ( killTimer ) ;
116+ }
117+ } ;
95118
96119 return { url, connections, child, stop } ;
97120}
0 commit comments