diff --git a/src/ops/AuthenticateOps.ts b/src/ops/AuthenticateOps.ts index dae27a014..004e1df3e 100644 --- a/src/ops/AuthenticateOps.ts +++ b/src/ops/AuthenticateOps.ts @@ -27,31 +27,25 @@ export type Authenticate = { /** * Get tokens * @param {boolean} forceLoginAsUser true to force login as user even if a service account is available (default: false) + * @param {boolean} autoRefresh true to automatically refresh tokens before they expire (default: true) * @returns {Promise} true if tokens were successfully obtained, false otherwise */ - getTokens(forceLoginAsUser?: boolean): Promise; + getTokens( + forceLoginAsUser?: boolean, + autoRefresh?: boolean + ): Promise; }; export default (state: State): Authenticate => { return { - /** - * Get access token for service account - * @returns {string | null} Access token or null - */ async getAccessTokenForServiceAccount( saId: string = undefined, saJwk: JwkRsa = undefined ): Promise { return getAccessTokenForServiceAccount({ saId, saJwk, state }); }, - - /** - * Get tokens - * @param {boolean} forceLoginAsUser true to force login as user even if a service account is available (default: false) - * @returns {Promise} true if tokens were successfully obtained, false otherwise - */ - getTokens(forceLoginAsUser = false) { - return getTokens({ forceLoginAsUser, state }); + getTokens(forceLoginAsUser = false, autoRefresh = true) { + return getTokens({ forceLoginAsUser, autoRefresh, state }); }, }; }; @@ -571,6 +565,10 @@ export async function getAccessTokenForServiceAccount({ state, }); debugMessage({ message: response.data.access_token, state }); + debugMessage({ + message: `AuthenticateOps.getAccessTokenForServiceAccount: expires: ${response.data.expires_in}`, + state, + }); debugMessage({ message: `AuthenticateOps.getAccessTokenForServiceAccount: end`, state, @@ -644,17 +642,65 @@ async function getLoggedInSubject(state: State): Promise { return subjectString; } +/** + * Helper method to set, reset, or cancel timer to auto refresh tokens + * @param {boolean} forceLoginAsUser true to force login as user even if a service account is available (default: false) + * @param {boolean} autoRefresh true to automatically refresh tokens before they expire (default: true) + * @param {State} state library state + */ +function scheduleAutoRefresh( + forceLoginAsUser: boolean, + autoRefresh: boolean, + state: State +) { + let timer = state.getAutoRefreshTimer(); + // reset existing timer + if (timer) { + if (autoRefresh) { + debugMessage({ + message: `AuthenticateOps.scheduleAutoRefresh: reset existing timer`, + state, + }); + timer.refresh(); + } else { + debugMessage({ + message: `AuthenticateOps.scheduleAutoRefresh: cancel existing timer`, + state, + }); + clearInterval(timer); + } + } + // new timer + else if (autoRefresh) { + debugMessage({ + message: `AuthenticateOps.scheduleAutoRefresh: set new timer`, + state, + }); + timer = setInterval(getTokens, 1000 * 10, { + forceLoginAsUser, + autoRefresh, + state, + // Volker's Visual Studio Code doesn't want to have it any other way. + }) as unknown as NodeJS.Timeout; + timer.unref(); + state.setAutoRefreshTimer(timer); + } +} + /** * Get tokens * @param {boolean} forceLoginAsUser true to force login as user even if a service account is available (default: false) + * @param {boolean} autoRefresh true to automatically refresh tokens before they expire (default: true) * @param {State} state library state * @returns {Promise} true if tokens were successfully obtained, false otherwise */ export async function getTokens({ forceLoginAsUser = false, + autoRefresh = true, state, }: { forceLoginAsUser?: boolean; + autoRefresh?: boolean; state: State; }): Promise { debugMessage({ message: `AuthenticateOps.getTokens: start`, state }); @@ -770,6 +816,7 @@ export async function getTokens({ type: 'info', state, }); + scheduleAutoRefresh(forceLoginAsUser, autoRefresh, state); debugMessage({ message: `AuthenticateOps.getTokens: end with tokens`, state, diff --git a/src/shared/State.ts b/src/shared/State.ts index cb87b4a9b..7c7508616 100644 --- a/src/shared/State.ts +++ b/src/shared/State.ts @@ -58,6 +58,8 @@ export type State = { getOutputFile(): string; setDirectory(directory: string): void; getDirectory(): string; + setAutoRefreshTimer(timer: NodeJS.Timeout): void; + getAutoRefreshTimer(): NodeJS.Timeout; setCurlirizeHandler(handler: (message: string) => void): void; getCurlirizeHandler(): (message: string) => void; setCurlirize(curlirize: boolean): void; @@ -275,6 +277,13 @@ export default (initialState: StateInterface): State => { return state.directory; }, + setAutoRefreshTimer(timer: NodeJS.Timeout): void { + state.autoRefreshTimer = timer; + }, + getAutoRefreshTimer(): NodeJS.Timeout { + return state.autoRefreshTimer; + }, + setCurlirizeHandler(handler: (message: string) => void) { state.curlirizeHandler = handler; }, @@ -396,6 +405,7 @@ export interface StateInterface { masterKeyPath?: string; outputFile?: string; directory?: string; + autoRefreshTimer?: NodeJS.Timeout; // output handler settings printHandler?: ( message: string | object, @@ -858,6 +868,7 @@ export const getDebug = (): boolean => * @deprecated since version v0.17.0. Import state: * * ```import { state } from '@rockcarver/frodo-lib';``` +import { setInterval } from '@types/node'; * * then call functions: *