@@ -122,7 +122,11 @@ interface RetryRequestInput {
122122 bucket : string
123123 url : string
124124 method : string
125- headers : Record < string , string >
125+ /**
126+ * Built per attempt so the OAuth access token is refreshed if it expired
127+ * between retries (google-auth-library caches and refreshes on demand).
128+ */
129+ buildHeaders : ( ) => Promise < Record < string , string > >
126130 body ?: BodyInit | Buffer
127131 signal : AbortSignal
128132 /** HTTP statuses to treat as success in addition to 2xx. */
@@ -136,10 +140,11 @@ async function fetchWithRetry(input: RetryRequestInput): Promise<void> {
136140 const perAttempt = AbortSignal . any ( [ input . signal , AbortSignal . timeout ( PER_ATTEMPT_TIMEOUT_MS ) ] )
137141 let response : Response
138142 try {
143+ const headers = await input . buildHeaders ( )
139144 response = await fetch ( input . url , {
140145 method : input . method ,
141146 body : input . body as BodyInit | undefined ,
142- headers : input . headers ,
147+ headers,
143148 signal : perAttempt ,
144149 } )
145150 } catch ( error ) {
@@ -200,22 +205,24 @@ async function uploadObject(action: string, input: UploadInput): Promise<void> {
200205 `GCS custom metadata is ${ metadataBytes } bytes, exceeds the ${ MAX_CUSTOM_METADATA_BYTES } -byte per-object limit`
201206 )
202207 }
203- const token = await getAccessToken ( input . jwt )
204208 const url = `${ GCS_HOST } /upload/storage/v1/b/${ encodeURIComponent ( input . bucket ) } /o?uploadType=media&name=${ encodeURIComponent ( input . objectName ) } `
205- const headers : Record < string , string > = {
206- Authorization : `Bearer ${ token } ` ,
207- 'Content-Type' : input . contentType ,
208- 'User-Agent' : USER_AGENT ,
209- }
210- for ( const [ key , value ] of Object . entries ( input . metadata ) ) {
211- headers [ `x-goog-meta-${ key } ` ] = value
212- }
213209 await fetchWithRetry ( {
214210 action,
215211 bucket : input . bucket ,
216212 url,
217213 method : 'POST' ,
218- headers,
214+ buildHeaders : async ( ) => {
215+ const token = await getAccessToken ( input . jwt )
216+ const headers : Record < string , string > = {
217+ Authorization : `Bearer ${ token } ` ,
218+ 'Content-Type' : input . contentType ,
219+ 'User-Agent' : USER_AGENT ,
220+ }
221+ for ( const [ key , value ] of Object . entries ( input . metadata ) ) {
222+ headers [ `x-goog-meta-${ key } ` ] = value
223+ }
224+ return headers
225+ } ,
219226 body : input . body ,
220227 signal : input . signal ,
221228 } )
@@ -227,16 +234,18 @@ async function deleteObject(input: {
227234 jwt : JWT
228235 signal : AbortSignal
229236} ) : Promise < void > {
230- const token = await getAccessToken ( input . jwt )
231237 const url = `${ GCS_HOST } /storage/v1/b/${ encodeURIComponent ( input . bucket ) } /o/${ encodeURIComponent ( input . objectName ) } `
232238 await fetchWithRetry ( {
233239 action : 'delete-object' ,
234240 bucket : input . bucket ,
235241 url,
236242 method : 'DELETE' ,
237- headers : {
238- Authorization : `Bearer ${ token } ` ,
239- 'User-Agent' : USER_AGENT ,
243+ buildHeaders : async ( ) => {
244+ const token = await getAccessToken ( input . jwt )
245+ return {
246+ Authorization : `Bearer ${ token } ` ,
247+ 'User-Agent' : USER_AGENT ,
248+ }
240249 } ,
241250 signal : input . signal ,
242251 successStatuses : [ 404 ] ,
0 commit comments