| 385 | } |
| 386 | |
| 387 | async getBytes( |
| 388 | offset: number, |
| 389 | length: number, |
| 390 | passedSignal?: AbortSignal, |
| 391 | etag?: string |
| 392 | ): Promise<RangeResponse> { |
| 393 | let controller: AbortController | undefined; |
| 394 | let signal: AbortSignal | undefined; |
| 395 | if (passedSignal) { |
| 396 | signal = passedSignal; |
| 397 | } else { |
| 398 | controller = new AbortController(); |
| 399 | signal = controller.signal; |
| 400 | } |
| 401 | |
| 402 | const requestHeaders = new Headers(this.customHeaders); |
| 403 | requestHeaders.set("range", `bytes=${offset}-${offset + length - 1}`); |
| 404 | |
| 405 | // we don't send if match because: |
| 406 | // * it disables browser caching completely (Chromium) |
| 407 | // * it requires a preflight request for every tile request |
| 408 | // * it requires CORS configuration becasue If-Match is not a CORs-safelisted header |
| 409 | // CORs configuration should expose ETag. |
| 410 | // if any etag mismatch is detected, we need to ignore the browser cache |
| 411 | let cache: "no-store" | "reload" | undefined; |
| 412 | if (this.mustReload) { |
| 413 | cache = "reload"; |
| 414 | } else if (this.chromeWindowsNoCache) { |
| 415 | cache = "no-store"; |
| 416 | } |
| 417 | |
| 418 | let resp = await fetch(this.url, { |
| 419 | signal: signal, |
| 420 | cache: cache, |
| 421 | headers: requestHeaders, |
| 422 | credentials: this.credentials, |
| 423 | }); |
| 424 | |
| 425 | // handle edge case where the archive is < 16384 kb total. |
| 426 | if (offset === 0 && resp.status === 416) { |
| 427 | const contentRange = resp.headers.get("Content-Range"); |
| 428 | if (!contentRange || !contentRange.startsWith("bytes */")) { |
| 429 | throw new Error("Missing content-length on 416 response"); |
| 430 | } |
| 431 | const actualLength = +contentRange.substr(8); |
| 432 | requestHeaders.set("range", `bytes=0-${actualLength - 1}`); |
| 433 | resp = await fetch(this.url, { |
| 434 | signal: signal, |
| 435 | cache: "reload", |
| 436 | headers: requestHeaders, |
| 437 | credentials: this.credentials, |
| 438 | }); |
| 439 | } |
| 440 | |
| 441 | // if it's a weak etag, it's not useful for us, so ignore it. |
| 442 | let newEtag = resp.headers.get("Etag"); |
| 443 | if (newEtag?.startsWith("W/")) { |
| 444 | newEtag = null; |