@@ -21,7 +21,6 @@ import {
2121 type TokenServiceAccountSecretBlob ,
2222} from '@/lib/credentials/token-service-accounts/server'
2323import { refreshOAuthToken } from '@/lib/oauth'
24- import { OAuthRefreshError } from '@/lib/oauth/errors'
2524import { isInstagramProvider , shouldProactivelyRefreshInstagramToken } from '@/lib/oauth/instagram'
2625import {
2726 getMicrosoftRefreshTokenExpiry ,
@@ -671,12 +670,6 @@ interface CoalescedRefreshOptions {
671670 userId ?: string
672671}
673672
674- interface CoalescedRefreshOutcome {
675- accessToken : string | null
676- /** Present when the refresh failed with a known provider error; absent on follower timeout / transient failure. */
677- error ?: OAuthRefreshError
678- }
679-
680673/**
681674 * Slack lock budgets sized past `TOKEN_REFRESH_TIMEOUT_MS` (15s) in
682675 * lib/oauth/oauth.ts: installation-keyed locks make every sibling row's request
@@ -695,7 +688,7 @@ async function performCoalescedRefresh({
695688 providerAccountId,
696689 requestId,
697690 userId,
698- } : CoalescedRefreshOptions ) : Promise < CoalescedRefreshOutcome > {
691+ } : CoalescedRefreshOptions ) : Promise < string | null > {
699692 /**
700693 * Slack bot tokens are per-installation (team × app): every account row for
701694 * one team holds a copy of the same rotating chain, so refreshes are locked,
@@ -718,13 +711,13 @@ async function performCoalescedRefresh({
718711 ...logContext ,
719712 errorCode : deadCode ,
720713 } )
721- return { accessToken : null , error : new OAuthRefreshError ( providerId , deadCode ) }
714+ return null
722715 }
723716
724717 const lockKey = `oauth:refresh:${ scopeKey } `
725718
726719 const refreshPromise = coalesceLocally ( lockKey , ( ) =>
727- withLeaderLock < CoalescedRefreshOutcome > ( {
720+ withLeaderLock < string > ( {
728721 key : lockKey ,
729722 // Installation-keyed Slack locks gather followers from every sibling row,
730723 // so their wait and the lock TTL must outlast the 15s provider timeout —
@@ -752,7 +745,7 @@ async function performCoalescedRefresh({
752745 accessTokenExpiresAt : freshest . accessTokenExpiresAt ,
753746 } )
754747 logger . info ( 'Reused freshest Slack installation token' , logContext )
755- return { accessToken : freshest . accessToken }
748+ return freshest . accessToken
756749 }
757750 refreshTokenToUse = freshest . refreshToken
758751 }
@@ -767,20 +760,7 @@ async function performCoalescedRefresh({
767760 if ( result . errorCode && isTerminalRefreshError ( result . errorCode ) ) {
768761 await markCredentialDead ( scopeKey , result . errorCode )
769762 }
770- return {
771- accessToken : null ,
772- // No errorCode = transient (timeout/network), not a provider rejection —
773- // stay errorless so callers keep their null-fallback behavior.
774- ...( result . errorCode
775- ? {
776- error : new OAuthRefreshError (
777- providerId ,
778- result . errorCode ,
779- result . errorDescription
780- ) ,
781- }
782- : { } ) ,
783- }
763+ return null
784764 }
785765
786766 const accessTokenExpiresAt = new Date ( Date . now ( ) + result . expiresIn * 1000 )
@@ -808,13 +788,13 @@ async function performCoalescedRefresh({
808788 }
809789
810790 logger . info ( 'Successfully refreshed access token' , logContext )
811- return { accessToken : result . accessToken }
791+ return result . accessToken
812792 } catch ( error ) {
813793 logger . error ( 'Refresh failed inside leader path' , {
814794 ...logContext ,
815795 error : toError ( error ) . message ,
816796 } )
817- return { accessToken : null }
797+ return null
818798 }
819799 } ,
820800 onFollower : async ( ) => {
@@ -833,7 +813,7 @@ async function performCoalescedRefresh({
833813 row . accessTokenExpiresAt > new Date ( )
834814 ) {
835815 logger . info ( 'Got fresh access token from coalesced refresh' , logContext )
836- return { accessToken : row . accessToken }
816+ return row . accessToken
837817 }
838818 return null
839819 } catch ( error ) {
@@ -848,13 +828,13 @@ async function performCoalescedRefresh({
848828 )
849829
850830 try {
851- return ( await refreshPromise ) ?? { accessToken : null }
831+ return await refreshPromise
852832 } catch ( error ) {
853833 logger . error ( 'Coalesced refresh did not settle' , {
854834 ...logContext ,
855835 error : toError ( error ) . message ,
856836 } )
857- return { accessToken : null }
837+ return null
858838 }
859839}
860840
@@ -898,18 +878,17 @@ export async function getOAuthToken(userId: string, providerId: string): Promise
898878 } )
899879
900880 if ( accessTokenNeedsRefresh || instagramNeedsProactiveRefresh ) {
901- const outcome = await performCoalescedRefresh ( {
881+ const fresh = await performCoalescedRefresh ( {
902882 accountId : credential . id ,
903883 providerId,
904884 refreshToken : credential . refreshToken ! ,
905885 providerAccountId : credential . providerAccountId ,
906886 userId,
907887 } )
908- if ( outcome . accessToken ) return outcome . accessToken
888+ if ( fresh ) return fresh
909889 if ( ! accessTokenNeedsRefresh && credential . accessToken ) {
910890 return credential . accessToken
911891 }
912- if ( outcome . error ) throw outcome . error
913892 return null
914893 }
915894
@@ -1002,15 +981,15 @@ export async function resolveCredentialAccessToken(
1002981 const resolvedCredentialId =
1003982 ( credential as { resolvedCredentialId ?: string } ) . resolvedCredentialId ?? credentialId
1004983
1005- const outcome = await performCoalescedRefresh ( {
984+ const fresh = await performCoalescedRefresh ( {
1006985 accountId : resolvedCredentialId ,
1007986 providerId : credential . providerId ,
1008987 refreshToken : credential . refreshToken ! ,
1009988 providerAccountId : credential . accountId ,
1010989 requestId,
1011990 userId : credential . userId ,
1012991 } )
1013- if ( outcome . accessToken ) return { accessToken : outcome . accessToken }
992+ if ( fresh ) return { accessToken : fresh }
1014993
1015994 // If refresh was only triggered proactively (Microsoft refresh-token aging /
1016995 // Instagram long-lived nearing expiry), the still-valid access token is fine.
@@ -1107,19 +1086,19 @@ export async function refreshTokenIfNeeded(
11071086 return { accessToken : credential . accessToken , refreshed : false }
11081087 }
11091088
1110- const outcome = await performCoalescedRefresh ( {
1089+ const fresh = await performCoalescedRefresh ( {
11111090 accountId : resolvedCredentialId ,
11121091 providerId : credential . providerId ,
11131092 refreshToken : credential . refreshToken ! ,
11141093 providerAccountId : credential . accountId ,
11151094 requestId,
11161095 userId : credential . userId ,
11171096 } )
1118- if ( outcome . accessToken ) return { accessToken : outcome . accessToken , refreshed : true }
1097+ if ( fresh ) return { accessToken : fresh , refreshed : true }
11191098
11201099 if ( ! accessTokenNeedsRefresh && credential . accessToken ) {
11211100 logger . info ( `[${ requestId } ] Refresh unavailable; reusing still-valid access token` )
11221101 return { accessToken : credential . accessToken , refreshed : false }
11231102 }
1124- throw outcome . error ?? new Error ( 'Failed to refresh token' )
1103+ throw new Error ( 'Failed to refresh token' )
11251104}
0 commit comments