import { authorization } from "./authorization" ; export class FailedToRefreshOAuth2TokenError extends Error { constructor ( ) { super ( ) ; this . name = "FailedToRefreshError" ; this . message = "Could not generate a new OAuth Access Token. The Refresh Token has likely expired." ; } } type OAuth2Options = { accessToken ? : string ; tokenRefreshed ? : typeof tokenRefreshed ; authorizationHasExpired ? : typeof authorizationHasExpired ; } & RefreshTokenOptions ; type RefreshTokenOptions = { refreshToken : string ; tokenEndpoint : string ; clientID : string ; clientSecret : string ; redirectURI ? : string ; scope ? : string ; refreshFetch ? : typeof fetch ; } ; const getNewToken = async ( { refreshToken , tokenEndpoint , clientID , clientSecret , redirectURI , scope , refreshFetch = fetch , } ) => { const body = new URLSearchParams ( { grant_type : "refresh_token" , refresh_token : refreshToken , client_id : clientID , client_secret : clientSecret , ... ( scope === undefined ? { } : { scope } ) , ... ( redirectURI === undefined ? { } : { redirect_uri : redirectURI } ) , } ) ; try { const response = await refreshFetch ( tokenEndpoint , { method : "POST" , headers : { Accept : "application/json" , "Content-Type" : "application/x-www-form-urlencoded" , } , body : body . toString ( ) , } ) ; const data = await response . json ( ) ; if ( ! response . ok || ( ! ( "access_token" in data ) && ! ( "refresh_token" in data ) ) ) throw new Error ( "Invalid refresh response" ) ; return { accessToken : data . access_token , refreshToken : data . refresh_token , } ; } catch ( error ) { throw new FailedToRefreshOAuth2TokenError ( ) ; } } ; const authorizationHasExpired = async ( response ) => { return response . status === 401 ; } ; const tokenRefreshed = ( options ) => Promise . resolve ( ) ; export const oauth2 = ( prevFetch , options ) : typeof fetch => { let accessToken = options . accessToken ; const authedFetch = ( ... args ) => authorization ( prevFetch , { bearer : accessToken , } ) ( ... args ) ; const fetchWithRetry = async ( ... args ) => { let response = await authedFetch ( ... args ) ; if ( await ( options . authorizationHasExpired || authorizationHasExpired ) ( response ) ) { const refreshData = await getNewToken ( options ) ; accessToken = refreshData . accessToken ; options . refreshToken = refreshData . refreshToken ; await ( options . tokenRefreshed || tokenRefreshed ) ( refreshData ) ; response = await authedFetch ( ... args ) ; } return response ; } ; return fetchWithRetry ; } ;