mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-03-21 22:39:39 +00:00
* js: Allow passing credentials option to fetch [#397] * fix passing custom headers in case where remote archive is < 16 kB * clean up `any` usage
This commit is contained in:
@@ -345,14 +345,20 @@ export class FetchSource implements Source {
|
|||||||
* This should be used instead of maplibre's [transformRequest](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#example) for PMTiles archives.
|
* This should be used instead of maplibre's [transformRequest](https://maplibre.org/maplibre-gl-js/docs/API/classes/Map/#example) for PMTiles archives.
|
||||||
*/
|
*/
|
||||||
customHeaders: Headers;
|
customHeaders: Headers;
|
||||||
|
credentials: "same-origin" | "include" | undefined;
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
mustReload: boolean;
|
mustReload: boolean;
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
chromeWindowsNoCache: boolean;
|
chromeWindowsNoCache: boolean;
|
||||||
|
|
||||||
constructor(url: string, customHeaders: Headers = new Headers()) {
|
constructor(
|
||||||
|
url: string,
|
||||||
|
customHeaders: Headers = new Headers(),
|
||||||
|
credentials: "same-origin" | "include" | undefined = undefined
|
||||||
|
) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.customHeaders = customHeaders;
|
this.customHeaders = customHeaders;
|
||||||
|
this.credentials = credentials;
|
||||||
this.mustReload = false;
|
this.mustReload = false;
|
||||||
let userAgent = "";
|
let userAgent = "";
|
||||||
if ("navigator" in globalThis) {
|
if ("navigator" in globalThis) {
|
||||||
@@ -402,7 +408,7 @@ export class FetchSource implements Source {
|
|||||||
// * it requires CORS configuration becasue If-Match is not a CORs-safelisted header
|
// * it requires CORS configuration becasue If-Match is not a CORs-safelisted header
|
||||||
// CORs configuration should expose ETag.
|
// CORs configuration should expose ETag.
|
||||||
// if any etag mismatch is detected, we need to ignore the browser cache
|
// if any etag mismatch is detected, we need to ignore the browser cache
|
||||||
let cache: string | undefined;
|
let cache: "no-store" | "reload" | undefined;
|
||||||
if (this.mustReload) {
|
if (this.mustReload) {
|
||||||
cache = "reload";
|
cache = "reload";
|
||||||
} else if (this.chromeWindowsNoCache) {
|
} else if (this.chromeWindowsNoCache) {
|
||||||
@@ -413,8 +419,8 @@ export class FetchSource implements Source {
|
|||||||
signal: signal,
|
signal: signal,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
headers: requestHeaders,
|
headers: requestHeaders,
|
||||||
//biome-ignore lint: "cache" is incompatible between cloudflare workers and browser
|
credentials: this.credentials,
|
||||||
} as any);
|
});
|
||||||
|
|
||||||
// handle edge case where the archive is < 16384 kb total.
|
// handle edge case where the archive is < 16384 kb total.
|
||||||
if (offset === 0 && resp.status === 416) {
|
if (offset === 0 && resp.status === 416) {
|
||||||
@@ -423,12 +429,13 @@ export class FetchSource implements Source {
|
|||||||
throw new Error("Missing content-length on 416 response");
|
throw new Error("Missing content-length on 416 response");
|
||||||
}
|
}
|
||||||
const actualLength = +contentRange.substr(8);
|
const actualLength = +contentRange.substr(8);
|
||||||
|
requestHeaders.set("range", `bytes=0-${actualLength - 1}`);
|
||||||
resp = await fetch(this.url, {
|
resp = await fetch(this.url, {
|
||||||
signal: signal,
|
signal: signal,
|
||||||
cache: "reload",
|
cache: "reload",
|
||||||
headers: { range: `bytes=0-${actualLength - 1}` },
|
headers: requestHeaders,
|
||||||
//biome-ignore lint: "cache" is incompatible between cloudflare workers and browser
|
credentials: this.credentials,
|
||||||
} as any);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's a weak etag, it's not useful for us, so ignore it.
|
// if it's a weak etag, it's not useful for us, so ignore it.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { describe, mock, test } from "node:test";
|
import { describe, test } from "node:test";
|
||||||
import { PMTiles, Protocol } from "../src";
|
import { PMTiles, Protocol } from "../src";
|
||||||
import { mockServer } from "./utils";
|
import { mockServer } from "./utils";
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,27 @@ class MockServer {
|
|||||||
etag?: string;
|
etag?: string;
|
||||||
numRequests: number;
|
numRequests: number;
|
||||||
lastCache?: string;
|
lastCache?: string;
|
||||||
|
lastRequestHeaders: Headers | null;
|
||||||
|
lastCredentials?: string;
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.numRequests = 0;
|
this.numRequests = 0;
|
||||||
this.etag = undefined;
|
this.etag = undefined;
|
||||||
|
this.lastRequestHeaders = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.numRequests = 0;
|
this.numRequests = 0;
|
||||||
this.etag = undefined;
|
this.etag = undefined;
|
||||||
|
this.lastRequestHeaders = null;
|
||||||
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
||||||
const server = setupServer(
|
const server = setupServer(
|
||||||
http.get(
|
http.get(
|
||||||
"http://localhost:1337/example.pmtiles",
|
"http://localhost:1337/example.pmtiles",
|
||||||
({ request, params }) => {
|
({ request, params }) => {
|
||||||
this.lastCache = request.cache;
|
this.lastCache = request.cache;
|
||||||
|
this.lastRequestHeaders = request.headers;
|
||||||
|
this.lastCredentials = request.credentials;
|
||||||
this.numRequests++;
|
this.numRequests++;
|
||||||
const range = request.headers.get("range")?.substr(6).split("-");
|
const range = request.headers.get("range")?.substr(6).split("-");
|
||||||
if (!range) {
|
if (!range) {
|
||||||
@@ -35,7 +41,31 @@ class MockServer {
|
|||||||
headers: { etag: this.etag } as HeadersInit,
|
headers: { etag: this.etag } as HeadersInit,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
|
http.get("http://localhost:1337/small.pmtiles", ({ request }) => {
|
||||||
|
this.lastRequestHeaders = request.headers;
|
||||||
|
this.lastCredentials = request.credentials;
|
||||||
|
this.numRequests++;
|
||||||
|
const range = request.headers.get("range")?.substr(6).split("-");
|
||||||
|
if (!range) {
|
||||||
|
throw new Error("invalid range");
|
||||||
|
}
|
||||||
|
const rangeEnd = +range[1];
|
||||||
|
if (rangeEnd >= serverBuffer.length) {
|
||||||
|
return new HttpResponse(null, {
|
||||||
|
status: 416,
|
||||||
|
headers: {
|
||||||
|
"Content-Range": `bytes */${serverBuffer.length}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const rangeStart = +range[0];
|
||||||
|
const body = serverBuffer.slice(rangeStart, rangeEnd + 1);
|
||||||
|
return new HttpResponse(body, {
|
||||||
|
status: 206,
|
||||||
|
headers: { etag: this.etag } as HeadersInit,
|
||||||
|
});
|
||||||
|
})
|
||||||
);
|
);
|
||||||
server.listen({ onUnhandledRequest: "error" });
|
server.listen({ onUnhandledRequest: "error" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { mockServer } from "./utils";
|
|||||||
import {
|
import {
|
||||||
BufferPosition,
|
BufferPosition,
|
||||||
Entry,
|
Entry,
|
||||||
|
FetchSource,
|
||||||
PMTiles,
|
PMTiles,
|
||||||
RangeResponse,
|
RangeResponse,
|
||||||
SharedPromiseCache,
|
SharedPromiseCache,
|
||||||
@@ -407,3 +408,36 @@ describe("pmtiles v3", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("FetchSource", () => {
|
||||||
|
test("customHeaders are sent with requests", async () => {
|
||||||
|
mockServer.reset();
|
||||||
|
const source = new FetchSource(
|
||||||
|
"http://localhost:1337/example.pmtiles",
|
||||||
|
new Headers({ "X-Custom-Header": "test-value" }),
|
||||||
|
"include"
|
||||||
|
);
|
||||||
|
await source.getBytes(0, 100);
|
||||||
|
assert.strictEqual(
|
||||||
|
mockServer.lastRequestHeaders?.get("x-custom-header"),
|
||||||
|
"test-value"
|
||||||
|
);
|
||||||
|
assert.strictEqual(mockServer.lastCredentials, "include");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("customHeaders are preserved on 416 retry", async () => {
|
||||||
|
mockServer.reset();
|
||||||
|
const source = new FetchSource(
|
||||||
|
"http://localhost:1337/small.pmtiles",
|
||||||
|
new Headers({ "X-Custom-Header": "retry-value" }),
|
||||||
|
"include"
|
||||||
|
);
|
||||||
|
await source.getBytes(0, 16384);
|
||||||
|
assert.strictEqual(mockServer.numRequests, 2);
|
||||||
|
assert.strictEqual(
|
||||||
|
mockServer.lastRequestHeaders?.get("x-custom-header"),
|
||||||
|
"retry-value",
|
||||||
|
"include"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user