Skip to content

Commit 5981751

Browse files
committed
reload reconstruction info if expired
1 parent 4dec1ff commit 5981751

File tree

1 file changed

+48
-12
lines changed

1 file changed

+48
-12
lines changed

packages/hub/src/utils/XetBlob.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)