@@ -44,6 +44,17 @@ interface SendToQueueMessage {
4444 reject : ( err : Error ) => void ;
4545}
4646
47+ interface ConsumerOptions extends amqplib . Options . Consume {
48+ prefetch ?: number
49+ }
50+
51+ interface Consumer {
52+ consumerTag : string | null ;
53+ queue : string ;
54+ onMessage : ( msg : amqplib . ConsumeMessage ) => void ;
55+ options : ConsumerOptions ;
56+ }
57+
4758type Message = PublishMessage | SendToQueueMessage ;
4859
4960const IRRECOVERABLE_ERRORS = [
@@ -87,6 +98,8 @@ export default class ChannelWrapper extends EventEmitter {
8798 private _unconfirmedMessages : Message [ ] = [ ] ;
8899 /** Reason code during publish or sendtoqueue messages. */
89100 private _irrecoverableCode : number | undefined ;
101+ /** Consumers which will be reconnected on channel errors etc. */
102+ private _consumers : Consumer [ ] = [ ] ;
90103
91104 /**
92105 * The currently connected channel. Note that not all setup functions
@@ -324,6 +337,8 @@ export default class ChannelWrapper extends EventEmitter {
324337
325338 // Array of setup functions to call.
326339 this . _setups = [ ] ;
340+ this . _consumers = [ ] ;
341+
327342 if ( options . setup ) {
328343 this . _setups . push ( options . setup ) ;
329344 }
@@ -359,10 +374,13 @@ export default class ChannelWrapper extends EventEmitter {
359374 this . emit ( 'error' , err , { name : this . name } ) ;
360375 } )
361376 )
362- ) . then ( ( ) => {
363- this . _settingUp = undefined ;
364- } ) ;
365-
377+ )
378+ . then ( ( ) => {
379+ return Promise . all ( this . _consumers . map ( ( c ) => this . _reconnectConsumer ( c ) ) ) ;
380+ } )
381+ . then ( ( ) => {
382+ this . _settingUp = undefined ;
383+ } ) ;
366384 await this . _settingUp ;
367385
368386 if ( ! this . _channel ) {
@@ -581,6 +599,89 @@ export default class ChannelWrapper extends EventEmitter {
581599 }
582600 }
583601
602+ /**
603+ * Setup a consumer
604+ * This consumer will be reconnected on cancellation and channel errors.
605+ */
606+ async consume (
607+ queue : string ,
608+ onMessage : Consumer [ 'onMessage' ] ,
609+ options : ConsumerOptions = { }
610+ ) : Promise < void > {
611+ const consumer : Consumer = {
612+ consumerTag : null ,
613+ queue,
614+ onMessage,
615+ options,
616+ } ;
617+ this . _consumers . push ( consumer ) ;
618+ await this . _consume ( consumer ) ;
619+ }
620+
621+ private async _consume ( consumer : Consumer ) : Promise < void > {
622+ if ( ! this . _channel ) {
623+ return ;
624+ }
625+
626+ const { prefetch, ...options } = consumer . options ;
627+ if ( typeof prefetch === 'number' ) {
628+ this . _channel . prefetch ( prefetch , false ) ;
629+ }
630+
631+ const { consumerTag } = await this . _channel . consume (
632+ consumer . queue ,
633+ ( msg ) => {
634+ if ( ! msg ) {
635+ consumer . consumerTag = null ;
636+ this . _reconnectConsumer ( consumer ) . catch ( ( err ) => {
637+ if ( err . isOperational && err . message . includes ( 'BasicConsume; 404' ) ) {
638+ // Ignore errors caused by queue not declared. In
639+ // those cases the connection will reconnect and
640+ // then consumers reestablished. The full reconnect
641+ // might be avoided if we assert the queue again
642+ // before starting to consume.
643+ return ;
644+ }
645+ throw err ;
646+ } ) ;
647+ return ;
648+ }
649+ consumer . onMessage ( msg ) ;
650+ } ,
651+ options
652+ ) ;
653+ consumer . consumerTag = consumerTag ;
654+ }
655+
656+ private async _reconnectConsumer ( consumer : Consumer ) : Promise < void > {
657+ if ( ! this . _consumers . includes ( consumer ) ) {
658+ // Intentionally canceled
659+ return ;
660+ }
661+ await this . _consume ( consumer ) ;
662+ }
663+
664+ /**
665+ * Cancel all consumers
666+ */
667+ async cancelAll ( ) : Promise < void > {
668+ const consumers = this . _consumers ;
669+ this . _consumers = [ ] ;
670+ if ( ! this . _channel ) {
671+ return ;
672+ }
673+
674+ const channel = this . _channel ;
675+ await Promise . all (
676+ consumers . reduce < any [ ] > ( ( acc , consumer ) => {
677+ if ( consumer . consumerTag ) {
678+ acc . push ( channel . cancel ( consumer . consumerTag ) ) ;
679+ }
680+ return acc ;
681+ } , [ ] )
682+ ) ;
683+ }
684+
584685 /** Send an `ack` to the underlying channel. */
585686 ack ( message : amqplib . Message , allUpTo ?: boolean ) : void {
586687 this . _channel && this . _channel . ack ( message , allUpTo ) ;
0 commit comments