Skip to content

Commit

Permalink
use Fetch API if available and set correct referrer
Browse files Browse the repository at this point in the history
  • Loading branch information
kkaefer committed Oct 9, 2018
1 parent c87c5d8 commit eec8cd5
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 11 deletions.
7 changes: 6 additions & 1 deletion src/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default class Worker {
workerSourceTypes: { [string]: Class<WorkerSource> };
workerSources: { [string]: { [string]: { [string]: WorkerSource } } };
demWorkerSources: { [string]: { [string]: RasterDEMTileWorkerSource } };
referrer: ?string;

constructor(self: WorkerGlobalScopeInterface) {
this.self = self;
Expand Down Expand Up @@ -65,6 +66,10 @@ export default class Worker {
};
}

setReferrer(mapID: string, referrer: string) {
this.referrer = referrer;
}

setLayers(mapId: string, layers: Array<LayerSpecification>, callback: WorkerTileCallback) {
this.getLayerIndex(mapId).replace(layers);
callback();
Expand Down Expand Up @@ -196,5 +201,5 @@ export default class Worker {
if (typeof WorkerGlobalScope !== 'undefined' &&
typeof self !== 'undefined' &&
self instanceof WorkerGlobalScope) {
new Worker(self);
self.worker = new Worker(self);
}
4 changes: 3 additions & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import GlyphManager from '../render/glyph_manager';
import Light from './light';
import LineAtlas from '../render/line_atlas';
import { pick, clone, extend, deepEqual, filterObject, mapObject } from '../util/util';
import { getJSON, ResourceType } from '../util/ajax';
import { getJSON, getReferrer, ResourceType } from '../util/ajax';
import { isMapboxURL, normalizeStyleURL } from '../util/mapbox';
import browser from '../util/browser';
import Dispatcher from '../util/dispatcher';
Expand Down Expand Up @@ -144,6 +144,8 @@ class Style extends Evented {

this._resetUpdates();

this.dispatcher.broadcast('setReferrer', getReferrer());

const self = this;
this._rtlTextPluginCallback = Style.registerForPluginAvailability((args) => {
self.dispatcher.broadcast('loadRTLTextPlugin', args.pluginURL, args.completionCallback);
Expand Down
3 changes: 3 additions & 0 deletions src/types/window.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface Window extends EventTarget, IDBEnvironment {
+isSecureContext: boolean;
+length: number;
+location: Location;
+origin: string;
name: string;
+navigator: Navigator;
offscreenBuffering: string | boolean;
Expand Down Expand Up @@ -131,6 +132,8 @@ export interface Window extends EventTarget, IDBEnvironment {
WheelEvent: typeof WheelEvent;
Worker: typeof Worker;
XMLHttpRequest: typeof XMLHttpRequest;
Request: typeof Request;
AbortController: any;

alert(message?: any): void;
blur(): void;
Expand Down
66 changes: 57 additions & 9 deletions src/util/ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import window from './window';
import { extend } from './util';
import { isMapboxHTTPURL } from './mapbox';

import type { Callback } from '../types/callback';
import type { Cancelable } from '../types/cancelable';
Expand Down Expand Up @@ -40,7 +41,7 @@ export type RequestParameters = {
headers?: Object,
method?: 'GET' | 'POST' | 'PUT',
body?: string,
type?: 'string' | 'json' | 'arraybuffer',
type?: 'string' | 'json' | 'arrayBuffer',
credentials?: 'same-origin' | 'include',
collectResourceTiming?: boolean
};
Expand All @@ -51,6 +52,9 @@ class AJAXError extends Error {
status: number;
url: string;
constructor(message: string, status: number, url: string) {
if (status === 401 && isMapboxHTTPURL(url)) {
message += ': you may have provided an invalid Mapbox access token. See https://www.mapbox.com/api-documentation/#access-tokens';
}
super(message);
this.status = status;
this.url = url;
Expand All @@ -65,11 +69,57 @@ class AJAXError extends Error {
}
}

function makeRequest(requestParameters: RequestParameters, callback: ResponseCallback<any>): Cancelable {
// Ensure that we're sending the correct referrer from blob URL worker bundles.
// For files loaded from the local file system, `location.origin` will be set
// to the string(!) "null" (Firefox), or "file://" (Chrome, Safari, Edge, IE),
// and we will set an empty referrer. Otherwise, we're using the document's URL.
/* global self, WorkerGlobalScope */
export const getReferrer = typeof WorkerGlobalScope !== 'undefined' &&
typeof self !== 'undefined' &&
self instanceof WorkerGlobalScope ?
() => self.worker && self.worker.referrer :
() => {
const origin = window.location.origin;
if (origin && origin !== 'null' && origin !== 'file://') {
return origin + window.location.pathname;
}
};

function makeFetchRequest(requestParameters: RequestParameters, callback: ResponseCallback<any>): Cancelable {
const controller = new window.AbortController();
const request = new window.Request(requestParameters.url, {
method: requestParameters.method || 'GET',
body: requestParameters.body,
credentials: requestParameters.credentials,
headers: requestParameters.headers,
referrer: getReferrer(),
signal: controller.signal
});

if (requestParameters.type === 'json') {
request.headers.set('Accept', 'application/json');
}

window.fetch(request).then(response => {
if (response.ok) {
response[requestParameters.type || 'text']().then(result => {
callback(null, result, response.headers.get('Cache-Control'), response.headers.get('Expires'));
}).catch(callback);
} else {
callback(new AJAXError(response.statusText, response.status, requestParameters.url));
}
}).catch((error) => {
callback(new Error(error.message));
});

return { cancel: () => controller.abort() };
}

function makeXMLHttpRequest(requestParameters: RequestParameters, callback: ResponseCallback<any>): Cancelable {
const xhr: XMLHttpRequest = new window.XMLHttpRequest();

xhr.open(requestParameters.method || 'GET', requestParameters.url, true);
if (requestParameters.type === 'arraybuffer') {
if (requestParameters.type === 'arrayBuffer') {
xhr.responseType = 'arraybuffer';
}
for (const k in requestParameters.headers) {
Expand All @@ -95,23 +145,21 @@ function makeRequest(requestParameters: RequestParameters, callback: ResponseCal
}
callback(null, data, xhr.getResponseHeader('Cache-Control'), xhr.getResponseHeader('Expires'));
} else {
if (xhr.status === 401 && requestParameters.url.match(/mapbox.com/)) {
callback(new AJAXError(`${xhr.statusText}: you may have provided an invalid Mapbox access token. See https://www.mapbox.com/api-documentation/#access-tokens`, xhr.status, requestParameters.url));
} else {
callback(new AJAXError(xhr.statusText, xhr.status, requestParameters.url));
}
callback(new AJAXError(xhr.statusText, xhr.status, requestParameters.url));
}
};
xhr.send(requestParameters.body);
return { cancel: () => xhr.abort() };
}

const makeRequest = window.fetch && window.Request && window.AbortController ? makeFetchRequest : makeXMLHttpRequest;

export const getJSON = function(requestParameters: RequestParameters, callback: ResponseCallback<Object>): Cancelable {
return makeRequest(extend(requestParameters, { type: 'json' }), callback);
};

export const getArrayBuffer = function(requestParameters: RequestParameters, callback: ResponseCallback<ArrayBuffer>): Cancelable {
return makeRequest(extend(requestParameters, { type: 'arraybuffer' }), callback);
return makeRequest(extend(requestParameters, { type: 'arrayBuffer' }), callback);
};

export const postData = function(requestParameters: RequestParameters, callback: ResponseCallback<string>): Cancelable {
Expand Down
2 changes: 2 additions & 0 deletions test/ajax_stubs.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function cached(data, callback) {
});
}

export const getReferrer = () => undefined;

export const getJSON = function({ url }, callback) {
if (cache[url]) return cached(cache[url], callback);
return request(url, (error, response, body) => {
Expand Down

1 comment on commit eec8cd5

@gpiffault
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks cordova apps storing the style in a local file (accessed through file: scheme). Fetch doesn't support the file: scheme whereas xhr does (JakeChampion/fetch#92 (comment)). What do you think about also checking the request scheme and using makeXMLHttpRequest if it's file: ?

Please sign in to comment.