@@ -135,14 +135,20 @@ export class XetBlob extends Blob {
135135 return slice ;
136136 }
137137
138- async #fetch( ) : Promise < ReadableStream < Uint8Array > > {
139- let connParams = await getAccessToken ( this . repoId , this . accessToken , this . fetch , this . hubUrl ) ;
138+ #reconstructionInfoPromise?: Promise < ReconstructionInfo > ;
139+
140+ #loadReconstructionInfo( ) {
141+ if ( this . #reconstructionInfoPromise) {
142+ return this . #reconstructionInfoPromise;
143+ }
144+
145+ this . #reconstructionInfoPromise = ( async ( ) => {
146+ const connParams = await getAccessToken ( this . repoId , this . accessToken , this . fetch , this . hubUrl ) ;
140147
141- let reconstructionInfo = this . reconstructionInfo ;
142- if ( ! reconstructionInfo ) {
143148 // console.log(
144149 // `curl '${connParams.casUrl}/reconstruction/${this.hash}' -H 'Authorization: Bearer ${connParams.accessToken}'`
145150 // );
151+
146152 const resp = await this . fetch ( `${ connParams . casUrl } /reconstruction/${ this . hash } ` , {
147153 headers : {
148154 Authorization : `Bearer ${ connParams . accessToken } ` ,
@@ -154,14 +160,25 @@ export class XetBlob extends Blob {
154160 throw await createApiError ( resp ) ;
155161 }
156162
157- this . reconstructionInfo = reconstructionInfo = ( await resp . json ( ) ) as ReconstructionInfo ;
158- }
159- // todo: also refresh reconstruction info if it's expired, (and avoid concurrent requests when doing so)
163+ this . reconstructionInfo = ( await resp . json ( ) ) as ReconstructionInfo ;
164+
165+ return this . reconstructionInfo ;
166+ } ) ( ) . finally ( ( ) => ( this . #reconstructionInfoPromise = undefined ) ) ;
167+
168+ return this . #reconstructionInfoPromise;
169+ }
160170
161- // Refetch the token if it's expired
162- connParams = await getAccessToken ( this . repoId , this . accessToken , this . fetch , this . hubUrl ) ;
171+ async #fetch( ) : Promise < ReadableStream < Uint8Array > > {
172+ if ( ! this . reconstructionInfo ) {
173+ await this . #loadReconstructionInfo( ) ;
174+ }
163175
164- async function * readData ( reconstructionInfo : ReconstructionInfo , customFetch : typeof fetch , maxBytes : number ) {
176+ async function * readData (
177+ reconstructionInfo : ReconstructionInfo ,
178+ customFetch : typeof fetch ,
179+ maxBytes : number ,
180+ reloadReconstructionInfo : ( ) => Promise < ReconstructionInfo >
181+ ) {
165182 let totalBytesRead = 0 ;
166183 let readBytesToSkip = reconstructionInfo . offset_into_first_range ;
167184
@@ -179,12 +196,22 @@ export class XetBlob extends Blob {
179196 ) ;
180197 }
181198
182- const resp = await customFetch ( fetchInfo . url , {
199+ let resp = await customFetch ( fetchInfo . url , {
183200 headers : {
184201 Range : `bytes=${ fetchInfo . url_range . start } -${ fetchInfo . url_range . end } ` ,
185202 } ,
186203 } ) ;
187204
205+ if ( resp . status === 403 ) {
206+ // In case it's expired
207+ reconstructionInfo = await reloadReconstructionInfo ( ) ;
208+ resp = await customFetch ( fetchInfo . url , {
209+ headers : {
210+ Range : `bytes=${ fetchInfo . url_range . start } -${ fetchInfo . url_range . end } ` ,
211+ } ,
212+ } ) ;
213+ }
214+
188215 if ( ! resp . ok ) {
189216 throw await createApiError ( resp ) ;
190217 }
@@ -293,7 +320,16 @@ export class XetBlob extends Blob {
293320 }
294321 }
295322
296- const iterator = readData ( reconstructionInfo , this . fetch , this . end - this . start ) ;
323+ if ( ! this . reconstructionInfo ) {
324+ throw new Error ( "Failed to load reconstruction info" ) ;
325+ }
326+
327+ const iterator = readData (
328+ this . reconstructionInfo ,
329+ this . fetch ,
330+ this . end - this . start ,
331+ this . #loadReconstructionInfo. bind ( this )
332+ ) ;
297333
298334 // todo: when Chrome/Safari support it, use ReadableStream.from(readData)
299335 return new ReadableStream < Uint8Array > (
0 commit comments