diff --git a/CHANGELOG.md b/CHANGELOG.md index 7551571d4f..33d75582a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features and improvements - *...Add new stuff here...* +- Added custom protocol support to allow overriding ajax calls ((#29)[https://github.com/maplibre/maplibre-gl-js/issues/29]) - Added setTransformRequest to map (#159) - Publish @maplibre/maplibre-gl-style-spec v14.0.0 on NPM (#149) - Replace link to mapbox on LogoControl by link to maplibre (#151) diff --git a/package.json b/package.json index 490e9ad90c..ab7bb30342 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "maplibre-gl", "description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library", - "version": "1.14.1-rc.1", + "version": "1.14.1-rc.2", "main": "dist/maplibre-gl.js", "style": "dist/maplibre-gl.css", "license": "BSD-3-Clause", diff --git a/src/index.d.ts b/src/index.d.ts index 8a0b0c315c..a097cb0407 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -42,7 +42,46 @@ declare namespace maplibregl { * Tiles may still be cached by the browser in some cases. */ export function clearStorage(callback?: (err?: Error) => void): void; - + /** + * Sets a custom load tile function that will be called when using a source that starts with a custom url schema. + * The example below will be triggered for custom:// urls defined in the sources list in the style definitions. + * The function passed will receive the request parameters and should call the callback with the resulting request, + * for example a pbf vector tile, non-compressed, represented as ArrayBuffer. + * @param {string} customProtocol - the protocol to hook, for example 'custom' + * @param {Function} loadFn - the function to use when trying to fetch a tile specified by the customProtocol + * @example + * // this will fetch a file using the fetch API (this is obviously a non iteresting example...) + * maplibre.addProtocol('custom', (params, callback) => { + fetch(`https://${params.url.split("://")[1]}`) + .then(t => { + if (t.status == 200) { + t.arrayBuffer().then(arr => { + callback(null, arr, null, null); + }); + } else { + callback(new Error(`Tile fetch error: ${t.statusText}`)); + } + }) + .catch(e => { + callback(new Error(e)); + }); + return { cancel: () => { } }; + }); + * // the following is an example of a way to return an error when trying to load a tile + * maplibre.addProtocol('custom2', (params, callback) => { + * callback(new Error('someErrorMessage')); + * return { cancel: () => { } }; + * }); + */ + export function addProtocol(customProtocol: string, loadFn: (requestParameters: RequestParameters, callback: ResponseCallback) => Cancelable); + /** + * Removes a previusly added protocol + * @param {string} customProtocol - the custom protocol to remove registration for + * @example + * maplibregl.removeProtocol('custom'); + */ + export function removeProtocol(customProtocol: string); + export function setRTLTextPlugin(pluginURL: string, callback: (error: Error) => void, deferred?: boolean): void; export function getRTLTextPluginStatus(): PluginStatus; diff --git a/src/index.js b/src/index.js index eda359e538..e559b7defb 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,8 @@ import WorkerPool from './util/worker_pool'; import {prewarm, clearPrewarmedResources} from './util/global_worker_pool'; import {clearTileCache} from './util/tile_request_cache'; import {PerformanceUtils} from './util/performance'; +import type {RequestParameters, ResponseCallback} from './util/ajax'; +import type {Cancelable} from './types/cancelable'; const exported = { version, @@ -170,7 +172,52 @@ const exported = { clearTileCache(callback); }, - workerUrl: '' + workerUrl: '', + + /** + * Sets a custom load tile function that will be called when using a source that starts with a custom url schema. + * The example below will be triggered for custom:// urls defined in the sources list in the style definitions. + * The function passed will receive the request parameters and should call the callback with the resulting request, + * for example a pbf vector tile, non-compressed, represented as ArrayBuffer. + * @param {string} customProtocol - the protocol to hook, for example 'custom' + * @param {Function} loadFn - the function to use when trying to fetch a tile specified by the customProtocol + * @example + * // this will fetch a file using the fetch API (this is obviously a non iteresting example...) + * maplibre.addProtocol('custom', (params, callback) => { + fetch(`https://${params.url.split("://")[1]}`) + .then(t => { + if (t.status == 200) { + t.arrayBuffer().then(arr => { + callback(null, arr, null, null); + }); + } else { + callback(new Error(`Tile fetch error: ${t.statusText}`)); + } + }) + .catch(e => { + callback(new Error(e)); + }); + return { cancel: () => { } }; + }); + * // the following is an example of a way to return an error when trying to load a tile + * maplibre.addProtocol('custom2', (params, callback) => { + * callback(new Error('someErrorMessage')); + * return { cancel: () => { } }; + * }); + */ + addProtocol(customProtocol: string, loadFn: (requestParameters: RequestParameters, callback: ResponseCallback) => Cancelable) { + config.REGISTERED_PROTOCOLS[customProtocol] = loadFn; + }, + + /** + * Removes a previusly added protocol + * @param {string} customProtocol - the custom protocol to remove registration for + * @example + * maplibregl.removeProtocol('custom'); + */ + removeProtocol(customProtocol: string) { + delete config.REGISTERED_PROTOCOLS[customProtocol]; + } }; //This gets automatically stripped out in production builds. diff --git a/src/util/ajax.js b/src/util/ajax.js index e3dfeeef0e..83f0431211 100644 --- a/src/util/ajax.js +++ b/src/util/ajax.js @@ -242,6 +242,16 @@ export const makeRequest = function(requestParameters: RequestParameters, callba // some versions (see https://bugs.webkit.org/show_bug.cgi?id=174980#c2) // - Requests for resources with the file:// URI scheme don't work with the Fetch API either. In // this case we unconditionally use XHR on the current thread since referrers don't matter. + if (/:\/\//.test(requestParameters.url) && !(/^https?:|^file:/.test(requestParameters.url))) { + if (isWorker() && self.worker && self.worker.actor) { + return self.worker.actor.send('getResource', requestParameters, callback); + } + if (!isWorker()) { + const protocol = requestParameters.url.substring(0, requestParameters.url.indexOf('://')); + const action = config.REGISTERED_PROTOCOLS[protocol] || makeFetchRequest; + return action(requestParameters, callback); + } + } if (!isFileURL(requestParameters.url)) { if (window.fetch && window.Request && window.AbortController && window.Request.prototype.hasOwnProperty('signal')) { return makeFetchRequest(requestParameters, callback); diff --git a/src/util/config.js b/src/util/config.js index 8c9872d1d2..95d559d313 100644 --- a/src/util/config.js +++ b/src/util/config.js @@ -1,4 +1,4 @@ -// @flow strict +// @flow type Config = {| API_URL: string, @@ -6,7 +6,8 @@ type Config = {| FEEDBACK_URL: string, REQUIRE_ACCESS_TOKEN: boolean, ACCESS_TOKEN: ?string, - MAX_PARALLEL_IMAGE_REQUESTS: number + MAX_PARALLEL_IMAGE_REQUESTS: number, + REGISTERED_PROTOCOLS: { [string]: any }, |}; const config: Config = { @@ -24,7 +25,8 @@ const config: Config = { FEEDBACK_URL: 'https://apps.mapbox.com/feedback', REQUIRE_ACCESS_TOKEN: true, ACCESS_TOKEN: null, - MAX_PARALLEL_IMAGE_REQUESTS: 16 + MAX_PARALLEL_IMAGE_REQUESTS: 16, + REGISTERED_PROTOCOLS: {}, }; export default config;