diff --git a/debug/token.html b/debug/token.html
new file mode 100644
index 00000000000..ff900c6ee1e
--- /dev/null
+++ b/debug/token.html
@@ -0,0 +1,47 @@
+
+
+
+ Mapbox GL JS debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/source/vector_tile_source.js b/src/source/vector_tile_source.js
index 98e471318c7..994c1208798 100644
--- a/src/source/vector_tile_source.js
+++ b/src/source/vector_tile_source.js
@@ -73,9 +73,8 @@ class VectorTileSource extends Evented implements Source {
} else if (tileJSON) {
extend(this, tileJSON);
if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom);
-
- postTurnstileEvent(tileJSON.tiles);
- postMapLoadEvent(tileJSON.tiles, this.map._getMapId(), this.map._requestManager._skuToken);
+ postTurnstileEvent(tileJSON.tiles, this.map._requestManager._customAccessToken);
+ postMapLoadEvent(tileJSON.tiles, this.map._getMapId(), this.map._requestManager._skuToken, this.map._requestManager._customAccessToken);
// `content` is included here to prevent a race condition where `Style#_updateSources` is called
// before the TileJSON arrives. this makes sure the tiles needed are loaded once TileJSON arrives
diff --git a/src/style/style.js b/src/style/style.js
index 7254ff29bc4..38cd20568a9 100644
--- a/src/style/style.js
+++ b/src/style/style.js
@@ -195,7 +195,6 @@ class Style extends Evented {
url = this.map._requestManager.normalizeStyleURL(url, options.accessToken);
const request = this.map._requestManager.transformRequest(url, ResourceType.Style);
-
this._request = getJSON(request, (error: ?Error, json: ?Object) => {
this._request = null;
if (error) {
diff --git a/src/ui/control/attribution_control.js b/src/ui/control/attribution_control.js
index 6b8977a5555..71122dd2df5 100644
--- a/src/ui/control/attribution_control.js
+++ b/src/ui/control/attribution_control.js
@@ -94,7 +94,7 @@ class AttributionControl {
const params = [
{key: "owner", value: this.styleOwner},
{key: "id", value: this.styleId},
- {key: "access_token", value: config.ACCESS_TOKEN}
+ {key: "access_token", value: this._map._requestManager._customAccessToken || config.ACCESS_TOKEN}
];
if (editLink) {
diff --git a/src/ui/map.js b/src/ui/map.js
index 827923607cf..387f9e5096f 100755
--- a/src/ui/map.js
+++ b/src/ui/map.js
@@ -92,7 +92,8 @@ type MapOptions = {
pitch?: number,
renderWorldCopies?: boolean,
maxTileCacheSize?: number,
- transformRequest?: RequestTransformFunction
+ transformRequest?: RequestTransformFunction,
+ accessToken: string
};
const defaultMinZoom = 0;
@@ -129,6 +130,7 @@ const defaultOptions = {
maxTileCacheSize: null,
localIdeographFontFamily: 'sans-serif',
transformRequest: null,
+ accessToken: null,
fadeDuration: 300,
crossSourceCollisions: true
};
@@ -212,6 +214,8 @@ const defaultOptions = {
* @param {boolean} [options.collectResourceTiming=false] If `true`, Resource Timing API information will be collected for requests made by GeoJSON and Vector Tile web workers (this information is normally inaccessible from the main Javascript thread). Information will be returned in a `resourceTiming` property of relevant `data` events.
* @param {number} [options.fadeDuration=300] Controls the duration of the fade-in/fade-out animation for label collisions, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading.
* @param {boolean} [options.crossSourceCollisions=true] If `true`, symbols from multiple sources can collide with each other during collision detection. If `false`, collision detection is run separately for the symbols in each source.
+ * @param {string} [options.accessToken=null] If specified, map will use this token instead of the one defined in mapboxgl.accessToken.
+
* @example
* var map = new mapboxgl.Map({
* container: 'map',
@@ -332,7 +336,8 @@ class Map extends Camera {
this._renderTaskQueue = new TaskQueue();
this._controls = [];
this._mapId = uniqueId();
- this._requestManager = new RequestManager(options.transformRequest);
+
+ this._requestManager = new RequestManager(options.transformRequest, options.accessToken);
if (typeof options.container === 'string') {
this._container = window.document.getElementById(options.container);
diff --git a/src/util/mapbox.js b/src/util/mapbox.js
index cb6f17190f9..94b35d2786a 100644
--- a/src/util/mapbox.js
+++ b/src/util/mapbox.js
@@ -41,9 +41,11 @@ export class RequestManager {
_skuToken: string;
_skuTokenExpiresAt: number;
_transformRequestFn: ?RequestTransformFunction;
+ _customAccessToken: ?string;
- constructor(transformRequestFn?: RequestTransformFunction) {
+ constructor(transformRequestFn?: RequestTransformFunction, customAccessToken?: string) {
this._transformRequestFn = transformRequestFn;
+ this._customAccessToken = customAccessToken;
this._createSkuToken();
}
@@ -66,19 +68,19 @@ export class RequestManager {
}
normalizeStyleURL(url: string, accessToken?: string): string {
- return normalizeStyleURL(url, accessToken);
+ return normalizeStyleURL(url, this._customAccessToken || accessToken);
}
normalizeGlyphsURL(url: string, accessToken?: string): string {
- return normalizeGlyphsURL(url, accessToken);
+ return normalizeGlyphsURL(url, this._customAccessToken || accessToken);
}
normalizeSourceURL(url: string, accessToken?: string): string {
- return normalizeSourceURL(url, accessToken);
+ return normalizeSourceURL(url, this._customAccessToken || accessToken);
}
normalizeSpriteURL(url: string, format: string, extension: string, accessToken?: string): string {
- return normalizeSpriteURL(url, format, extension, accessToken);
+ return normalizeSpriteURL(url, format, extension, this._customAccessToken || accessToken);
}
normalizeTileURL(tileURL: string, sourceURL?: ?string, tileSize?: ?number): string {
@@ -86,7 +88,7 @@ export class RequestManager {
this._createSkuToken();
}
- return normalizeTileURL(tileURL, sourceURL, tileSize, this._skuToken);
+ return normalizeTileURL(tileURL, sourceURL, tileSize, this._skuToken, this._customAccessToken);
}
canonicalizeTileURL(url: string) {
@@ -170,7 +172,7 @@ const normalizeSpriteURL = function(url: string, format: string, extension: stri
const imageExtensionRe = /(\.(png|jpg)\d*)(?=$)/;
-const normalizeTileURL = function(tileURL: string, sourceURL?: ?string, tileSize?: ?number, skuToken?: string): string {
+const normalizeTileURL = function(tileURL: string, sourceURL?: ?string, tileSize?: ?number, skuToken?: string, customAccessToken?: ?string): string {
if (!sourceURL || !isMapboxURL(sourceURL)) return tileURL;
const urlObject = parseUrl(tileURL);
@@ -183,11 +185,11 @@ const normalizeTileURL = function(tileURL: string, sourceURL?: ?string, tileSize
urlObject.path = urlObject.path.replace(imageExtensionRe, `${suffix}${extension}`);
urlObject.path = `/v4${urlObject.path}`;
- if (config.REQUIRE_ACCESS_TOKEN && config.ACCESS_TOKEN && skuToken) {
+ if (config.REQUIRE_ACCESS_TOKEN && (config.ACCESS_TOKEN || customAccessToken) && skuToken) {
urlObject.params.push(`sku=${skuToken}`);
}
- return makeAPIURL(urlObject);
+ return makeAPIURL(urlObject, customAccessToken);
};
// matches any file extension specified by a dot and one or more alphanumeric characters
@@ -273,6 +275,7 @@ class TelemetryEvent {
queue: Array;
type: TelemetryEventType;
pendingRequest: ?Cancelable;
+ _customAccessToken: ?string;
constructor(type: TelemetryEventType) {
this.type = type;
@@ -333,17 +336,18 @@ class TelemetryEvent {
}
- processRequests() {}
+ processRequests(_: ?string) {}
/*
* If any event data should be persisted after the POST request, the callback should modify eventData`
* to the values that should be saved. For this reason, the callback should be invoked prior to the call
* to TelemetryEvent#saveData
*/
- postEvent(timestamp: number, additionalPayload: {[string]: any}, callback: (err: ?Error) => void) {
+ postEvent(timestamp: number, additionalPayload: {[string]: any}, callback: (err: ?Error) => void, customAccessToken?: ?string) {
if (!config.EVENTS_URL) return;
const eventsUrlObject: UrlObject = parseUrl(config.EVENTS_URL);
- eventsUrlObject.params.push(`access_token=${config.ACCESS_TOKEN || ''}`);
+ eventsUrlObject.params.push(`access_token=${customAccessToken || config.ACCESS_TOKEN || ''}`);
+
const payload: Object = {
event: this.type,
created: new Date(timestamp).toISOString(),
@@ -366,13 +370,13 @@ class TelemetryEvent {
this.pendingRequest = null;
callback(error);
this.saveEventData();
- this.processRequests();
+ this.processRequests(customAccessToken);
});
}
- queueRequest(event: number | {id: number, timestamp: number}) {
+ queueRequest(event: number | {id: number, timestamp: number}, customAccessToken?: ?string) {
this.queue.push(event);
- this.processRequests();
+ this.processRequests(customAccessToken);
}
}
@@ -386,20 +390,20 @@ export class MapLoadEvent extends TelemetryEvent {
this.skuToken = '';
}
- postMapLoadEvent(tileUrls: Array, mapId: number, skuToken: string) {
+ postMapLoadEvent(tileUrls: Array, mapId: number, skuToken: string, customAccessToken: string) {
//Enabled only when Mapbox Access Token is set and a source uses
// mapbox tiles.
this.skuToken = skuToken;
if (config.EVENTS_URL &&
- config.ACCESS_TOKEN &&
+ customAccessToken || config.ACCESS_TOKEN &&
Array.isArray(tileUrls) &&
tileUrls.some(url => isMapboxURL(url) || isMapboxHTTPURL(url))) {
- this.queueRequest({id: mapId, timestamp: Date.now()});
+ this.queueRequest({id: mapId, timestamp: Date.now()}, customAccessToken);
}
}
- processRequests() {
+ processRequests(customAccessToken?: ?string) {
if (this.pendingRequest || this.queue.length === 0) return;
const {id, timestamp} = this.queue.shift();
@@ -418,28 +422,29 @@ export class MapLoadEvent extends TelemetryEvent {
if (!err) {
if (id) this.success[id] = true;
}
- });
+ }, customAccessToken);
}
}
export class TurnstileEvent extends TelemetryEvent {
- constructor() {
+ constructor(customAccessToken?: ?string) {
super('appUserTurnstile');
+ this._customAccessToken = customAccessToken;
}
- postTurnstileEvent(tileUrls: Array) {
+ postTurnstileEvent(tileUrls: Array, customAccessToken?: ?string) {
//Enabled only when Mapbox Access Token is set and a source uses
// mapbox tiles.
if (config.EVENTS_URL &&
config.ACCESS_TOKEN &&
Array.isArray(tileUrls) &&
tileUrls.some(url => isMapboxURL(url) || isMapboxHTTPURL(url))) {
- this.queueRequest(Date.now());
+ this.queueRequest(Date.now(), customAccessToken);
}
}
- processRequests() {
+ processRequests(customAccessToken?: ?string) {
if (this.pendingRequest || this.queue.length === 0) {
return;
}
@@ -479,7 +484,7 @@ export class TurnstileEvent extends TelemetryEvent {
this.eventData.lastSuccess = nextUpdate;
this.eventData.tokenU = tokenU;
}
- });
+ }, customAccessToken);
}
}
diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js
index c84fe3686ee..abd40d8f412 100755
--- a/test/unit/ui/map.test.js
+++ b/test/unit/ui/map.test.js
@@ -50,6 +50,15 @@ test('Map', (t) => {
t.end();
});
+ t.test('bad map-specific token breaks map', (t) => {
+ const container = window.document.createElement('div');
+ Object.defineProperty(container, 'offsetWidth', {value: 512});
+ Object.defineProperty(container, 'offsetHeight', {value: 512});
+ createMap(t, {accessToken:'notAToken'});
+ t.error();
+ t.end();
+ });
+
t.test('initial bounds in constructor options', (t) => {
const container = window.document.createElement('div');
Object.defineProperty(container, 'offsetWidth', {value: 512});
diff --git a/test/unit/util/mapbox.test.js b/test/unit/util/mapbox.test.js
index 7cf7ac58947..d068a24cfec 100644
--- a/test/unit/util/mapbox.test.js
+++ b/test/unit/util/mapbox.test.js
@@ -66,6 +66,12 @@ test("mapbox", (t) => {
t.end();
});
+ t.test('takes map-specific tokens correctly', (t) => {
+ const m = new mapbox.RequestManager(undefined, 'customAccessToken');
+ t.equal(m.normalizeStyleURL('mapbox://styles/user/style'), 'https://api.mapbox.com/styles/v1/user/style?access_token=customAccessToken');
+ t.end();
+ });
+
webpSupported.supported = false;
t.test('.normalizeStyleURL', (t) => {