UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

248 lines (217 loc) 7.86 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ class PrefixEntry { public urlPrefix: string; public headers: Map<string, string>; public options: Partial<RequestInit> | null = null; /** * @param urlPrefix - The URL prefix for this host entry. */ public constructor(urlPrefix: string) { this.urlPrefix = urlPrefix; this.headers = new Map(); } /** * Sets a custom header applicable for URLs that match the prefix. * * @param name - The header name. * @param value - The header value. */ public setHeader(name: string, value: string): void { this.headers.set(name, value); } } const perHostProperties: Map<string, Array<PrefixEntry>> = new Map(); function getEntry(urlPrefix: string): PrefixEntry { const url = new URL(urlPrefix); const hostname = url.hostname; let hostEntry = perHostProperties.get(hostname); if (!hostEntry) { hostEntry = []; perHostProperties.set(hostname, hostEntry); } let prefixEntry = hostEntry.find(entry => entry.urlPrefix === urlPrefix); if (!prefixEntry) { prefixEntry = new PrefixEntry(urlPrefix); hostEntry.push(prefixEntry); // We want prefixes to be ordered from longer (more specific) to shorter (more global) hostEntry.sort((a, b) => b.urlPrefix.length - a.urlPrefix.length); } return prefixEntry; } /** * Update the request options with stored configuration applicable to this URL. * * @param input - The URL. * @param options - The request options. * @returns The updated options, if any. If no options object is passed, and no * configuration applies to this URL, then returns `undefined`. * @example * HttpConfiguration.setHeader('http://example.com', 'Foo', 'bar'); * * const fetchOptions = \{ * method: 'POST', * body: 'whatever', * headers: \{ * Width: 200, * \} * \}; * * // Let's update the options with headers applicable to 'http://example.com' * HttpConfiguration.applyConfiguration('http://example.com', fetchOptions); * * // now fetchOptions should be * // \{ * // method: 'POST', * // body: 'whatever', * // headers: \{ * // Width: 200, * // Foo: 'bar', * // \} * // \} * * // We can now send our HTTP request with correct headers * fetch('http://example.com/index.html', fetchOptions); */ function applyConfiguration( input: RequestInfo | URL, options?: RequestInit, ): RequestInit | undefined { if (perHostProperties.size === 0) { return options; } let urlObj: URL; if (typeof input === 'string') { urlObj = new URL(input); } else if (input instanceof URL) { urlObj = input; } else { urlObj = new URL(input.url); } const properties = perHostProperties.get(urlObj.hostname); if (!properties) { // Nothing to do return options; } if (!options) { options = {}; } const headers = (options.headers ?? {}) as Record<string, string>; const urlString = urlObj.toString(); let customOptions: Partial<RequestInit> = {}; for (const entry of properties) { if (urlString.startsWith(entry.urlPrefix)) { // Apply custom options with priority to the earlier entries. customOptions = { ...entry.options, ...customOptions }; for (const [k, v] of entry.headers.entries()) { if (headers[k]) { // The request already has a header with the same name. // We may not override it as it was already set either by the user manually, // or by a previous entry with higher precedence. continue; } headers[k] = v; } } } const keys = Object.keys(customOptions) as (keyof RequestInit)[]; for (const key of keys) { // @ts-expect-error the responsibility of checking the type falls on the caller options[key] = customOptions[key]; } options.headers = headers; return options; } function setOptions(urlPrefix: string, options: Partial<RequestInit>): void { const entry = getEntry(urlPrefix); entry.options = options; } /** * Sets the header for all HTTP requests that match the provided URL prefix. * * Note: The URL prefix must be a valid URL (e.g must contain a scheme and and host). * * @param urlPrefix - The URL prefix. * @param name - The header name. * @param value - The header value. */ function setHeader(urlPrefix: string, name: string, value: string): void { const prefixEntry = getEntry(urlPrefix); prefixEntry.setHeader(name, value); } /** * Sets the 'Authorization' header for the specified URL prefix. * * Note: this is a convenience function that calls {@link setHeader} internally: * * ```js * setHeader(urlPrefix, 'Authorization', value) * ``` * * @param urlPrefix - The URL prefix. * @param value - The header value * @example * // We wish to set the Authorization header for the 'example.com' * // domain to 'Bearer TOPLEVEL', except for the resources under * // 'example.com/sub/resource', where we use 'Bearer SUBRESOURCE'. * // * // Since 'example.com/sub/resource' is a longer prefix than 'example.com', * // its headers will have precedence and will be applied to HTTP requests * // that match this URL prefix. * HttpConfiguration.setAuth('https://example.com', 'Bearer TOPLEVEL'); * HttpConfiguration.setAuth('https://example.com/sub/resource', 'Bearer SUBRESOURCE'); * * HttpConfiguration.applyConfiguration('https://example.com/index.html') * // \{ 'Authorization', 'Bearer TOPLEVEL' \} * HttpConfiguration.applyConfiguration('https://example.com/sub/resource/index.html') * // \{ 'Authorization', 'Bearer SUBRESOURCE' \} */ function setAuth(urlPrefix: string, value: string): void { setHeader(urlPrefix, 'Authorization', value); } /** * Removes all configurations. */ function clear(): void { perHostProperties.clear(); } /** * Contains configuration for HTTP requests. * * Configuration is based on _URL prefixes_: each configuration entry applies to an URL prefix and * will apply to any URL that matches this prefix. Longer prefixes have more precedence over shorter * ones, so that you can cascade configurations. For example, you can have a general configuration * for the `example.com` domain, then more specific configuration entries for sub-paths in the same * domain. * * Note: URL prefixes must be valid absolute URLs (including scheme): `http://example.com/foo` is a * valid prefix, but `example.com/foo` is not. * * Note: If you plan to use the same configuration for different schemes (e.g `http` and `https`, * you must register the configuration twice, one for each scheme). * * Important: this module do _not_ automatically process outgoing HTTP requests. It is not a service * worker or a middleware. The `Fetcher` module automatically processes the requests by querying * configuration from this module, but if you can't or don't want to use `Fetcher`, then you have to * patch the request yourself (see the example below). * * @example * // Set the `Accept-Language` header to `fr-CH` for all requests under `http://example.com`. * HttpConfiguration.setHeader('http://example.com', 'Accept-Language', 'fr-CH'); * * // Later, query the configuration for a resource under `http://example.com` * const fetchOptions = HttpConfiguration.applyConfiguration('http://example.com/myPage.html'); * * // And put the options in the fetch request. * fetch('http://example.com/myPage.html', fetchOptions); */ export default { setAuth, setHeader, setOptions, applyConfiguration, clear, };