Skip to content

Commit c1014a5

Browse files
authored
js: Allow passing credentials option to fetch [#397] (#644)
* js: Allow passing credentials option to fetch [#397] * fix passing custom headers in case where remote archive is < 16 kB * clean up `any` usage
1 parent 9ff3871 commit c1014a5

4 files changed

Lines changed: 80 additions & 9 deletions

File tree

js/src/index.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,20 @@ export class FetchSource implements Source {
345345
* This should be used instead of maplibre's [transformRequest](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#example) for PMTiles archives.
346346
*/
347347
customHeaders: Headers;
348+
credentials: "same-origin" | "include" | undefined;
348349
/** @hidden */
349350
mustReload: boolean;
350351
/** @hidden */
351352
chromeWindowsNoCache: boolean;
352353

353-
constructor(url: string, customHeaders: Headers = new Headers()) {
354+
constructor(
355+
url: string,
356+
customHeaders: Headers = new Headers(),
357+
credentials: "same-origin" | "include" | undefined = undefined
358+
) {
354359
this.url = url;
355360
this.customHeaders = customHeaders;
361+
this.credentials = credentials;
356362
this.mustReload = false;
357363
let userAgent = "";
358364
if ("navigator" in globalThis) {
@@ -402,7 +408,7 @@ export class FetchSource implements Source {
402408
// * it requires CORS configuration becasue If-Match is not a CORs-safelisted header
403409
// CORs configuration should expose ETag.
404410
// if any etag mismatch is detected, we need to ignore the browser cache
405-
let cache: string | undefined;
411+
let cache: "no-store" | "reload" | undefined;
406412
if (this.mustReload) {
407413
cache = "reload";
408414
} else if (this.chromeWindowsNoCache) {
@@ -413,8 +419,8 @@ export class FetchSource implements Source {
413419
signal: signal,
414420
cache: cache,
415421
headers: requestHeaders,
416-
//biome-ignore lint: "cache" is incompatible between cloudflare workers and browser
417-
} as any);
422+
credentials: this.credentials,
423+
});
418424

419425
// handle edge case where the archive is < 16384 kb total.
420426
if (offset === 0 && resp.status === 416) {
@@ -423,12 +429,13 @@ export class FetchSource implements Source {
423429
throw new Error("Missing content-length on 416 response");
424430
}
425431
const actualLength = +contentRange.substr(8);
432+
requestHeaders.set("range", `bytes=0-${actualLength - 1}`);
426433
resp = await fetch(this.url, {
427434
signal: signal,
428435
cache: "reload",
429-
headers: { range: `bytes=0-${actualLength - 1}` },
430-
//biome-ignore lint: "cache" is incompatible between cloudflare workers and browser
431-
} as any);
436+
headers: requestHeaders,
437+
credentials: this.credentials,
438+
});
432439
}
433440

434441
// if it's a weak etag, it's not useful for us, so ignore it.

js/test/adapter.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert";
2-
import { describe, mock, test } from "node:test";
2+
import { describe, test } from "node:test";
33
import { PMTiles, Protocol } from "../src";
44
import { mockServer } from "./utils";
55

js/test/utils.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@ class MockServer {
66
etag?: string;
77
numRequests: number;
88
lastCache?: string;
9+
lastRequestHeaders: Headers | null;
10+
lastCredentials?: string;
911

1012
reset() {
1113
this.numRequests = 0;
1214
this.etag = undefined;
15+
this.lastRequestHeaders = null;
1316
}
1417

1518
constructor() {
1619
this.numRequests = 0;
1720
this.etag = undefined;
21+
this.lastRequestHeaders = null;
1822
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
1923
const server = setupServer(
2024
http.get(
2125
"http://localhost:1337/example.pmtiles",
2226
({ request, params }) => {
2327
this.lastCache = request.cache;
28+
this.lastRequestHeaders = request.headers;
29+
this.lastCredentials = request.credentials;
2430
this.numRequests++;
2531
const range = request.headers.get("range")?.substr(6).split("-");
2632
if (!range) {
@@ -35,7 +41,31 @@ class MockServer {
3541
headers: { etag: this.etag } as HeadersInit,
3642
});
3743
}
38-
)
44+
),
45+
http.get("http://localhost:1337/small.pmtiles", ({ request }) => {
46+
this.lastRequestHeaders = request.headers;
47+
this.lastCredentials = request.credentials;
48+
this.numRequests++;
49+
const range = request.headers.get("range")?.substr(6).split("-");
50+
if (!range) {
51+
throw new Error("invalid range");
52+
}
53+
const rangeEnd = +range[1];
54+
if (rangeEnd >= serverBuffer.length) {
55+
return new HttpResponse(null, {
56+
status: 416,
57+
headers: {
58+
"Content-Range": `bytes */${serverBuffer.length}`,
59+
},
60+
});
61+
}
62+
const rangeStart = +range[0];
63+
const body = serverBuffer.slice(rangeStart, rangeEnd + 1);
64+
return new HttpResponse(body, {
65+
status: 206,
66+
headers: { etag: this.etag } as HeadersInit,
67+
});
68+
})
3969
);
4070
server.listen({ onUnhandledRequest: "error" });
4171
}

js/test/v3.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { mockServer } from "./utils";
66
import {
77
BufferPosition,
88
Entry,
9+
FetchSource,
910
PMTiles,
1011
RangeResponse,
1112
SharedPromiseCache,
@@ -407,3 +408,36 @@ describe("pmtiles v3", () => {
407408
});
408409
});
409410
});
411+
412+
describe("FetchSource", () => {
413+
test("customHeaders are sent with requests", async () => {
414+
mockServer.reset();
415+
const source = new FetchSource(
416+
"http://localhost:1337/example.pmtiles",
417+
new Headers({ "X-Custom-Header": "test-value" }),
418+
"include"
419+
);
420+
await source.getBytes(0, 100);
421+
assert.strictEqual(
422+
mockServer.lastRequestHeaders?.get("x-custom-header"),
423+
"test-value"
424+
);
425+
assert.strictEqual(mockServer.lastCredentials, "include");
426+
});
427+
428+
test("customHeaders are preserved on 416 retry", async () => {
429+
mockServer.reset();
430+
const source = new FetchSource(
431+
"http://localhost:1337/small.pmtiles",
432+
new Headers({ "X-Custom-Header": "retry-value" }),
433+
"include"
434+
);
435+
await source.getBytes(0, 16384);
436+
assert.strictEqual(mockServer.numRequests, 2);
437+
assert.strictEqual(
438+
mockServer.lastRequestHeaders?.get("x-custom-header"),
439+
"retry-value",
440+
"include"
441+
);
442+
});
443+
});

0 commit comments

Comments
 (0)