@@ -6,6 +6,7 @@ const originalCommands = require('./commands.js');
66const originalMustache = require ( 'mustache' ) ;
77const axios = require ( 'axios' ) ;
88const https = require ( 'https' ) ;
9+ const EventEmitter = require ( 'events' ) ;
910
1011class VaultError extends Error { }
1112
@@ -165,7 +166,7 @@ module.exports = (config = {}) => {
165166 } ) ;
166167 } ;
167168 } ) ( ) ;
168- const client = { } ;
169+ const client = new EventEmitter ( ) ;
169170
170171 function handleVaultResponse ( response ) {
171172 if ( ! response ) return Promise . reject ( new VaultError ( 'No response passed' ) ) ;
@@ -378,5 +379,154 @@ module.exports = (config = {}) => {
378379 const assignFunctions = ( commandName ) => generateFunction ( commandName , commands [ commandName ] ) ;
379380 Object . keys ( commands ) . forEach ( assignFunctions ) ;
380381
382+ // -- Token renewal management --
383+ let tokenRenewalTimer = null ;
384+
385+ /**
386+ * Start automatic token renewal.
387+ * @param {Object } [opts={}] - Options
388+ * @param {number } [opts.ttl] - Initial TTL in seconds. If omitted,
389+ * tokenLookupSelf is called to determine the current TTL.
390+ * @param {number|string } [opts.increment] - Increment to request when
391+ * renewing (forwarded to tokenRenewSelf).
392+ * @param {number } [opts.renewFraction=0.8] - Fraction of TTL at which
393+ * to renew (0 < renewFraction < 1).
394+ * @returns {Promise } Resolves once the first renewal is scheduled.
395+ *
396+ * Events emitted:
397+ * 'token:renewed' – successful renewal, receives the response.
398+ * 'token:error:renew' – renewal failed, receives the error.
399+ * 'token:expired' – token is no longer renewable.
400+ */
401+ client . startTokenRenewal = ( opts = { } ) => {
402+ const renewFraction = opts . renewFraction || 0.8 ;
403+ const increment = opts . increment ;
404+
405+ client . stopTokenRenewal ( ) ;
406+
407+ function scheduleRenewal ( ttl ) {
408+ const delay = Math . max ( 1 , Math . floor ( ttl * renewFraction ) ) * 1000 ;
409+ tokenRenewalTimer = setTimeout ( ( ) => {
410+ const renewArgs = increment != null ? { increment } : { } ;
411+ client . tokenRenewSelf ( renewArgs )
412+ . then ( ( result ) => {
413+ client . emit ( 'token:renewed' , result ) ;
414+ const newTtl = result . auth && result . auth . lease_duration ;
415+ const renewable = result . auth && result . auth . renewable ;
416+ if ( newTtl > 0 && renewable !== false ) {
417+ scheduleRenewal ( newTtl ) ;
418+ } else {
419+ tokenRenewalTimer = null ;
420+ client . emit ( 'token:expired' ) ;
421+ }
422+ } )
423+ . catch ( ( err ) => {
424+ tokenRenewalTimer = null ;
425+ client . emit ( 'token:error:renew' , err ) ;
426+ } ) ;
427+ } , delay ) ;
428+ if ( tokenRenewalTimer . unref ) tokenRenewalTimer . unref ( ) ;
429+ }
430+
431+ const ttl = opts . ttl ;
432+ if ( ttl != null && ttl > 0 ) {
433+ scheduleRenewal ( ttl ) ;
434+ return Promise . resolve ( ) ;
435+ }
436+
437+ return client . tokenLookupSelf ( ) . then ( ( result ) => {
438+ const currentTtl = result . data && result . data . ttl ;
439+ if ( currentTtl > 0 ) {
440+ scheduleRenewal ( currentTtl ) ;
441+ }
442+ return result ;
443+ } ) ;
444+ } ;
445+
446+ /**
447+ * Stop automatic token renewal.
448+ */
449+ client . stopTokenRenewal = ( ) => {
450+ if ( tokenRenewalTimer ) {
451+ clearTimeout ( tokenRenewalTimer ) ;
452+ tokenRenewalTimer = null ;
453+ }
454+ } ;
455+
456+ // -- Lease renewal management --
457+ const leaseTimers = { } ;
458+
459+ /**
460+ * Start automatic lease renewal.
461+ * @param {string } leaseId - The lease ID to renew.
462+ * @param {number } leaseDuration - Initial lease duration in seconds.
463+ * @param {Object } [opts={}] - Options
464+ * @param {number } [opts.increment] - Increment to request on renewal.
465+ * @param {number } [opts.renewFraction=0.8] - Fraction of TTL at which
466+ * to renew (0 < renewFraction < 1).
467+ *
468+ * Events emitted:
469+ * 'lease:renewed' – successful renewal, receives { leaseId, result }.
470+ * 'lease:error:renew' – renewal failed, receives { leaseId, error }.
471+ * 'lease:expired' – lease is no longer renewable, receives { leaseId }.
472+ */
473+ client . startLeaseRenewal = ( leaseId , leaseDuration , opts = { } ) => {
474+ if ( ! leaseId ) throw new VaultError ( 'leaseId is required' ) ;
475+ if ( ! leaseDuration || leaseDuration <= 0 ) throw new VaultError ( 'leaseDuration must be positive' ) ;
476+
477+ const renewFraction = opts . renewFraction || 0.8 ;
478+ const increment = opts . increment ;
479+
480+ client . stopLeaseRenewal ( leaseId ) ;
481+
482+ function scheduleRenewal ( duration ) {
483+ const delay = Math . max ( 1 , Math . floor ( duration * renewFraction ) ) * 1000 ;
484+ const timer = setTimeout ( ( ) => {
485+ const renewArgs = { lease_id : leaseId } ;
486+ if ( increment != null ) renewArgs . increment = increment ;
487+ client . renew ( renewArgs )
488+ . then ( ( result ) => {
489+ client . emit ( 'lease:renewed' , { leaseId, result } ) ;
490+ if ( result . lease_duration > 0 && result . renewable !== false ) {
491+ scheduleRenewal ( result . lease_duration ) ;
492+ } else {
493+ delete leaseTimers [ leaseId ] ;
494+ client . emit ( 'lease:expired' , { leaseId } ) ;
495+ }
496+ } )
497+ . catch ( ( err ) => {
498+ delete leaseTimers [ leaseId ] ;
499+ client . emit ( 'lease:error:renew' , { leaseId, error : err } ) ;
500+ } ) ;
501+ } , delay ) ;
502+ if ( timer . unref ) timer . unref ( ) ;
503+ leaseTimers [ leaseId ] = timer ;
504+ }
505+
506+ scheduleRenewal ( leaseDuration ) ;
507+ } ;
508+
509+ /**
510+ * Stop automatic renewal for a specific lease.
511+ * @param {string } leaseId - The lease ID to stop renewing.
512+ */
513+ client . stopLeaseRenewal = ( leaseId ) => {
514+ if ( leaseTimers [ leaseId ] ) {
515+ clearTimeout ( leaseTimers [ leaseId ] ) ;
516+ delete leaseTimers [ leaseId ] ;
517+ }
518+ } ;
519+
520+ /**
521+ * Stop all automatic renewals (token + all leases).
522+ */
523+ client . stopAllRenewals = ( ) => {
524+ client . stopTokenRenewal ( ) ;
525+ Object . keys ( leaseTimers ) . forEach ( ( id ) => {
526+ clearTimeout ( leaseTimers [ id ] ) ;
527+ delete leaseTimers [ id ] ;
528+ } ) ;
529+ } ;
530+
381531 return client ;
382532} ;
0 commit comments