@chaosity/location-client 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -36
- package/dist/adapters/GeoPlaces.d.ts +7 -1
- package/dist/adapters/GeoPlaces.js +11 -5
- package/dist/auth/TokenProvider.d.ts +22 -1
- package/dist/auth/TokenProvider.js +42 -2
- package/dist/auth/tokenHold.d.ts +14 -5
- package/dist/auth/tokenHold.js +70 -5
- package/dist/aws.d.ts +4 -0
- package/dist/aws.js +2 -0
- package/dist/cjs/adapters/GeoPlaces.d.ts +7 -1
- package/dist/cjs/adapters/GeoPlaces.js +10 -4
- package/dist/cjs/auth/TokenProvider.d.ts +22 -1
- package/dist/cjs/auth/TokenProvider.js +42 -2
- package/dist/cjs/auth/tokenHold.d.ts +14 -5
- package/dist/cjs/auth/tokenHold.js +71 -5
- package/dist/cjs/aws.d.ts +4 -0
- package/dist/cjs/aws.js +155 -0
- package/dist/cjs/client/GeoPlacesClient.d.ts +17 -1
- package/dist/cjs/client/GeoPlacesClient.js +18 -62
- package/dist/cjs/index.d.ts +4 -3
- package/dist/cjs/index.js +13 -8
- package/dist/cjs/maps/createTransformRequest.d.ts +25 -0
- package/dist/cjs/maps/createTransformRequest.js +1 -0
- package/dist/cjs/maps/mapPoi.d.ts +10 -5
- package/dist/cjs/maps/mapPoi.js +14 -5
- package/dist/cjs/maps/mapStyle.d.ts +9 -2
- package/dist/cjs/maps/mapStyle.js +16 -12
- package/dist/cjs/maps/mapToken.d.ts +84 -0
- package/dist/cjs/maps/mapToken.js +126 -0
- package/dist/cjs/maps/staticMap.d.ts +7 -3
- package/dist/cjs/maps/staticMap.js +13 -12
- package/dist/cjs/server/LocationServiceConnector.d.ts +18 -3
- package/dist/cjs/server/LocationServiceConnector.js +17 -58
- package/dist/cjs/server/getClientConfig.d.ts +7 -3
- package/dist/cjs/server/getClientConfig.js +9 -12
- package/dist/cjs/server/index.d.ts +1 -1
- package/dist/cjs/transport/http.d.ts +18 -0
- package/dist/cjs/transport/http.js +39 -1
- package/dist/cjs/types/index.d.ts +26 -0
- package/dist/client/GeoPlacesClient.d.ts +17 -1
- package/dist/client/GeoPlacesClient.js +21 -65
- package/dist/index.d.ts +4 -3
- package/dist/index.js +11 -7
- package/dist/maps/createTransformRequest.d.ts +25 -0
- package/dist/maps/createTransformRequest.js +1 -1
- package/dist/maps/mapPoi.d.ts +10 -5
- package/dist/maps/mapPoi.js +14 -5
- package/dist/maps/mapStyle.d.ts +9 -2
- package/dist/maps/mapStyle.js +17 -13
- package/dist/maps/mapToken.d.ts +84 -0
- package/dist/maps/mapToken.js +122 -0
- package/dist/maps/staticMap.d.ts +7 -3
- package/dist/maps/staticMap.js +14 -13
- package/dist/server/LocationServiceConnector.d.ts +18 -3
- package/dist/server/LocationServiceConnector.js +20 -61
- package/dist/server/getClientConfig.d.ts +7 -3
- package/dist/server/getClientConfig.js +9 -12
- package/dist/server/index.d.ts +1 -1
- package/dist/transport/http.d.ts +18 -0
- package/dist/transport/http.js +37 -1
- package/dist/types/index.d.ts +26 -0
- package/package.json +3 -3
|
@@ -57,13 +57,21 @@ class GeoPlacesClient {
|
|
|
57
57
|
currentToken() {
|
|
58
58
|
return this.clientConfig.getToken?.() ?? this.clientConfig.token;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* `refreshToken`, held to the caller's signal and deadline (#62). A slow or
|
|
62
|
+
* stuck callback used to hold `send` past both.
|
|
63
|
+
*/
|
|
64
|
+
askRefreshToken(call) {
|
|
65
|
+
const asked = this.clientConfig.refreshToken?.();
|
|
66
|
+
return asked ? (0, http_js_1.withinCall)(asked, call) : Promise.resolve(undefined);
|
|
67
|
+
}
|
|
60
68
|
/**
|
|
61
69
|
* A token to send, or a refusal — never `undefined`.
|
|
62
70
|
*
|
|
63
71
|
* `refreshToken` is asked only when there is nothing at all in hand, so a
|
|
64
72
|
* client configured the ordinary way pays nothing for this.
|
|
65
73
|
*/
|
|
66
|
-
async ensureToken() {
|
|
74
|
+
async ensureToken(call) {
|
|
67
75
|
// Truthiness, not `??`: an empty string is a token source with nothing to
|
|
68
76
|
// give, not a decision to send an empty one. With `??` it survived the
|
|
69
77
|
// coalesce, skipped `refreshToken`, and then failed the check below — so
|
|
@@ -80,7 +88,7 @@ class GeoPlacesClient {
|
|
|
80
88
|
throw held;
|
|
81
89
|
let token;
|
|
82
90
|
try {
|
|
83
|
-
token = await this.
|
|
91
|
+
token = await this.askRefreshToken(call);
|
|
84
92
|
}
|
|
85
93
|
catch (refusal) {
|
|
86
94
|
this.refused.remember(refusal, NO_TOKEN);
|
|
@@ -91,11 +99,6 @@ class GeoPlacesClient {
|
|
|
91
99
|
}
|
|
92
100
|
return token;
|
|
93
101
|
}
|
|
94
|
-
/**
|
|
95
|
-
* @param options `signal` to cancel, `timeoutMs` per attempt,
|
|
96
|
-
* `overallTimeoutMs` for the whole call, `retry: false` to disable the
|
|
97
|
-
* retry loop. Every failure throws LocationServiceException.
|
|
98
|
-
*/
|
|
99
102
|
async send(command, options) {
|
|
100
103
|
const cmd = command;
|
|
101
104
|
const url = `${this.clientConfig.apiUrl}${(0, endpoints_js_1.resolveEndpoint)(cmd)}`;
|
|
@@ -106,62 +109,15 @@ class GeoPlacesClient {
|
|
|
106
109
|
// source has not produced one yet spent a whole round trip to learn
|
|
107
110
|
// something it already knew. Ask the refresh source instead, and refuse if
|
|
108
111
|
// there is still nothing.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
if (held && !held.askAgain)
|
|
117
|
-
throw held.error;
|
|
118
|
-
let rejected = held?.error;
|
|
119
|
-
if (!held) {
|
|
120
|
-
try {
|
|
121
|
-
return await this.dispatch(url, token, cmd, options);
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
if (!(0, errors_js_1.isTokenRejected)(err))
|
|
125
|
-
throw err;
|
|
126
|
-
rejected = err;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// One shot. `refreshToken` is the only way to actually obtain a new token
|
|
130
|
-
// here — `getToken` is synchronous and returns the one already in hand —
|
|
131
|
-
// but it is re-read as a fallback because a provider that refreshes in the
|
|
112
|
+
// The call's deadline starts here, before any wait for a token (#62).
|
|
113
|
+
const call = (0, http_js_1.startCall)(options);
|
|
114
|
+
const token = await this.ensureToken(call);
|
|
115
|
+
// One shot, through `sendRetryingOnce` like every 401 retry here (#38).
|
|
116
|
+
// `refreshToken` is the only way to actually obtain a new token here —
|
|
117
|
+
// `getToken` is synchronous and returns the one already in hand — but it
|
|
118
|
+
// is re-read as a fallback because a provider that refreshes in the
|
|
132
119
|
// background may have landed a new one while this request was in flight.
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
fresh = (await this.clientConfig.refreshToken?.()) ?? this.currentToken();
|
|
136
|
-
}
|
|
137
|
-
catch (refusal) {
|
|
138
|
-
// A suspended application's token route refuses it as its data routes
|
|
139
|
-
// refuse its token. Without this, every send asked again. A refusal that
|
|
140
|
-
// says nothing — a network fault, or a Server Action's error without its
|
|
141
|
-
// fields — leaves the source to be asked again, but not the token sent.
|
|
142
|
-
if ((0, tokenHold_js_1.holdFor)(refusal) > 0)
|
|
143
|
-
this.refused.remember(refusal, token);
|
|
144
|
-
// Only when no hold stands: one already standing keeps its own end, so
|
|
145
|
-
// the refused token is tried again once per hold rather than never.
|
|
146
|
-
else if (!held)
|
|
147
|
-
this.refused.remember(rejected, token, { askAgain: true });
|
|
148
|
-
throw refusal;
|
|
149
|
-
}
|
|
150
|
-
// Nothing new to send. Repeating the request would fail identically — a
|
|
151
|
-
// second round trip for the same 401.
|
|
152
|
-
if (!fresh || fresh === token) {
|
|
153
|
-
this.refused.remember(rejected, token);
|
|
154
|
-
throw rejected;
|
|
155
|
-
}
|
|
156
|
-
log('401 — retrying %s once with a refreshed token', cmd.constructor?.name);
|
|
157
|
-
try {
|
|
158
|
-
return await this.dispatch(url, fresh, cmd, options);
|
|
159
|
-
}
|
|
160
|
-
catch (again) {
|
|
161
|
-
if ((0, errors_js_1.isTokenRejected)(again))
|
|
162
|
-
this.refused.remember(again, fresh);
|
|
163
|
-
throw again;
|
|
164
|
-
}
|
|
120
|
+
return (0, tokenHold_js_1.sendRetryingOnce)(this.refused, token, (t) => this.dispatch(url, t, cmd, call), async () => (await this.askRefreshToken(call)) ?? this.currentToken(), () => log('401 — retrying %s once with a refreshed token', cmd.constructor?.name));
|
|
165
121
|
}
|
|
166
122
|
/**
|
|
167
123
|
* Verify a PlaceId: `send(new VerifyAddressCommand({ PlaceId }))`, typed
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -5,10 +5,9 @@ export type { RequestOptions } from './transport/http.js';
|
|
|
5
5
|
export { TOKEN_REFRESH_BUFFER_SECONDS, readTokenExpiry, } from './auth/tokenRefresh.js';
|
|
6
6
|
export { FEATURE_NOT_ENTITLED, LocationServiceException, } from './errors/LocationServiceException.js';
|
|
7
7
|
export type { LocationServiceErrorCode, LocationServiceExceptionOptions, } from './errors/LocationServiceException.js';
|
|
8
|
-
export * from '
|
|
8
|
+
export * from './aws.js';
|
|
9
9
|
export { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, VerifyAddressCommand, } from './client/commands.js';
|
|
10
10
|
export type { AutocompleteCommandInput, AutocompleteRequest, GeocodeCommandInput, GeocodeRequest, GetPlaceCommandInput, GetPlaceRequest, NeverForwarded, ReverseGeocodeCommandInput, ReverseGeocodeRequest, SearchNearbyCommandInput, SearchNearbyRequest, SearchTextCommandInput, SearchTextRequest, SuggestCommandInput, SuggestRequest, VerifyAddressCommandInput, VerifyAddressResponse, } from './client/commands.js';
|
|
11
|
-
export * from '@aws/amazon-location-utilities-datatypes';
|
|
12
11
|
export { GeoPlaces } from './adapters/GeoPlaces.js';
|
|
13
12
|
export type { GeoPlacesDetailOptions, GeoPlacesOptions, } from './adapters/GeoPlaces.js';
|
|
14
13
|
export { createTransformRequest } from './maps/createTransformRequest.js';
|
|
@@ -17,10 +16,12 @@ export { POI_CATEGORIES, setAllPoiVisibility, setPoiVisibility, } from './maps/m
|
|
|
17
16
|
export type { PoiCategory } from './maps/mapPoi.js';
|
|
18
17
|
export { buildMapStyleUrl, fetchMapStyle } from './maps/mapStyle.js';
|
|
19
18
|
export type { MapStyleOptions } from './maps/mapStyle.js';
|
|
19
|
+
export { refreshTokenOnUnauthorized } from './maps/mapToken.js';
|
|
20
|
+
export type { MapTokenSource, MapTokens, TokenRefreshMap, } from './maps/mapToken.js';
|
|
20
21
|
export { buildStaticMapUrl, fetchStaticMap, staticMapAccept, } from './maps/staticMap.js';
|
|
21
22
|
export type { StaticMapFileName, StaticMapOptions } from './maps/staticMap.js';
|
|
22
23
|
export { BUILDINGS, COLOR_SCHEMES, CONTOUR_DENSITIES, LABEL_SIZES, MAP_FEATURE_MODES, MAP_STYLES, POI_DENSITIES, SCALE_BAR_UNITS, SPRITE_VARIANTS, STATIC_MAP_STYLES, STYLE_POI_CATEGORIES, TERRAINS, TRAFFIC_MODES, TRAVEL_MODES, } from './maps/mapEnums.js';
|
|
23
24
|
export type { Buildings, ColorScheme, ContourDensity, LabelSize, MapFeatureMode, MapStyle, PoiDensity, ScaleBarUnit, SpriteVariant, StaticMapStyle, StylePoiCategory, Terrain, TrafficMode, TravelMode, } from './maps/mapEnums.js';
|
|
24
25
|
export { transformRequest } from './maps/Utils.js';
|
|
25
|
-
export type { ClientConfig, GeoPlacesCommand, MapLike } from './types/index.js';
|
|
26
|
+
export type { ClientConfig, CommandOutput, CommandWithOutput, GeoPlacesCommand, MapLike, } from './types/index.js';
|
|
26
27
|
export type { AppConfigClaims } from './utils/tokenClaims.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.transformRequest = exports.TRAVEL_MODES = exports.TRAFFIC_MODES = exports.TERRAINS = exports.STYLE_POI_CATEGORIES = exports.STATIC_MAP_STYLES = exports.SPRITE_VARIANTS = exports.SCALE_BAR_UNITS = exports.POI_DENSITIES = exports.MAP_STYLES = exports.MAP_FEATURE_MODES = exports.LABEL_SIZES = exports.CONTOUR_DENSITIES = exports.COLOR_SCHEMES = exports.BUILDINGS = exports.staticMapAccept = exports.fetchStaticMap = exports.buildStaticMapUrl = exports.fetchMapStyle = exports.buildMapStyleUrl = exports.setPoiVisibility = exports.setAllPoiVisibility = exports.POI_CATEGORIES = exports.applyMapLanguage = exports.createTransformRequest = exports.GeoPlaces = exports.VerifyAddressCommand = exports.SuggestCommand = exports.SearchTextCommand = exports.SearchNearbyCommand = exports.ReverseGeocodeCommand = exports.GetPlaceCommand = exports.GeocodeCommand = exports.AutocompleteCommand = exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = exports.readTokenExpiry = exports.TOKEN_REFRESH_BUFFER_SECONDS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_OVERALL_TIMEOUT_MS = exports.DEFAULT_MAX_ATTEMPTS = exports.GeoPlacesClient = void 0;
|
|
17
|
+
exports.transformRequest = exports.TRAVEL_MODES = exports.TRAFFIC_MODES = exports.TERRAINS = exports.STYLE_POI_CATEGORIES = exports.STATIC_MAP_STYLES = exports.SPRITE_VARIANTS = exports.SCALE_BAR_UNITS = exports.POI_DENSITIES = exports.MAP_STYLES = exports.MAP_FEATURE_MODES = exports.LABEL_SIZES = exports.CONTOUR_DENSITIES = exports.COLOR_SCHEMES = exports.BUILDINGS = exports.staticMapAccept = exports.fetchStaticMap = exports.buildStaticMapUrl = exports.refreshTokenOnUnauthorized = exports.fetchMapStyle = exports.buildMapStyleUrl = exports.setPoiVisibility = exports.setAllPoiVisibility = exports.POI_CATEGORIES = exports.applyMapLanguage = exports.createTransformRequest = exports.GeoPlaces = exports.VerifyAddressCommand = exports.SuggestCommand = exports.SearchTextCommand = exports.SearchNearbyCommand = exports.ReverseGeocodeCommand = exports.GetPlaceCommand = exports.GeocodeCommand = exports.AutocompleteCommand = exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = exports.readTokenExpiry = exports.TOKEN_REFRESH_BUFFER_SECONDS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_OVERALL_TIMEOUT_MS = exports.DEFAULT_MAX_ATTEMPTS = exports.GeoPlacesClient = void 0;
|
|
18
18
|
// Client (Custom - uses our auth instead of AWS SigV4)
|
|
19
19
|
var GeoPlacesClient_js_1 = require("./client/GeoPlacesClient.js");
|
|
20
20
|
Object.defineProperty(exports, "GeoPlacesClient", { enumerable: true, get: function () { return GeoPlacesClient_js_1.GeoPlacesClient; } });
|
|
@@ -31,11 +31,16 @@ Object.defineProperty(exports, "readTokenExpiry", { enumerable: true, get: funct
|
|
|
31
31
|
var LocationServiceException_js_1 = require("./errors/LocationServiceException.js");
|
|
32
32
|
Object.defineProperty(exports, "FEATURE_NOT_ENTITLED", { enumerable: true, get: function () { return LocationServiceException_js_1.FEATURE_NOT_ENTITLED; } });
|
|
33
33
|
Object.defineProperty(exports, "LocationServiceException", { enumerable: true, get: function () { return LocationServiceException_js_1.LocationServiceException; } });
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
34
|
+
// The AWS SDK's commands and types, and AWS Location Utilities' converters
|
|
35
|
+
// (#42): generated into ./aws.ts, values by name and types by `export type *`.
|
|
36
|
+
// Not `export *` of the packages themselves, which kept ~93 KB of SDK in the
|
|
37
|
+
// bundle of a consumer importing only a map helper — scripts/sdk-exports.mjs
|
|
38
|
+
// says why. A name this file exports itself is left out of ./aws.ts.
|
|
39
|
+
__exportStar(require("./aws.js"), exports);
|
|
40
|
+
// The seven Places commands and their inputs are this package's own: they take
|
|
41
|
+
// neither IntendedUse nor Key, which the service strips from every request
|
|
42
|
+
// (#40). A named export wins over `export *`, as GeoPlacesClient's does, and
|
|
43
|
+
// ./aws.ts leaves them out.
|
|
39
44
|
// VerifyAddressCommand is this package's own: its route has no SDK command (#54).
|
|
40
45
|
var commands_js_1 = require("./client/commands.js");
|
|
41
46
|
Object.defineProperty(exports, "AutocompleteCommand", { enumerable: true, get: function () { return commands_js_1.AutocompleteCommand; } });
|
|
@@ -46,8 +51,6 @@ Object.defineProperty(exports, "SearchNearbyCommand", { enumerable: true, get: f
|
|
|
46
51
|
Object.defineProperty(exports, "SearchTextCommand", { enumerable: true, get: function () { return commands_js_1.SearchTextCommand; } });
|
|
47
52
|
Object.defineProperty(exports, "SuggestCommand", { enumerable: true, get: function () { return commands_js_1.SuggestCommand; } });
|
|
48
53
|
Object.defineProperty(exports, "VerifyAddressCommand", { enumerable: true, get: function () { return commands_js_1.VerifyAddressCommand; } });
|
|
49
|
-
// Re-export AWS Location Utilities (data type conversions)
|
|
50
|
-
__exportStar(require("@aws/amazon-location-utilities-datatypes"), exports);
|
|
51
54
|
// Adapters (Custom - for MapLibre integration)
|
|
52
55
|
var GeoPlaces_js_1 = require("./adapters/GeoPlaces.js");
|
|
53
56
|
Object.defineProperty(exports, "GeoPlaces", { enumerable: true, get: function () { return GeoPlaces_js_1.GeoPlaces; } });
|
|
@@ -63,6 +66,8 @@ Object.defineProperty(exports, "setPoiVisibility", { enumerable: true, get: func
|
|
|
63
66
|
var mapStyle_js_1 = require("./maps/mapStyle.js");
|
|
64
67
|
Object.defineProperty(exports, "buildMapStyleUrl", { enumerable: true, get: function () { return mapStyle_js_1.buildMapStyleUrl; } });
|
|
65
68
|
Object.defineProperty(exports, "fetchMapStyle", { enumerable: true, get: function () { return mapStyle_js_1.fetchMapStyle; } });
|
|
69
|
+
var mapToken_js_1 = require("./maps/mapToken.js");
|
|
70
|
+
Object.defineProperty(exports, "refreshTokenOnUnauthorized", { enumerable: true, get: function () { return mapToken_js_1.refreshTokenOnUnauthorized; } });
|
|
66
71
|
var staticMap_js_1 = require("./maps/staticMap.js");
|
|
67
72
|
Object.defineProperty(exports, "buildStaticMapUrl", { enumerable: true, get: function () { return staticMap_js_1.buildStaticMapUrl; } });
|
|
68
73
|
Object.defineProperty(exports, "fetchStaticMap", { enumerable: true, get: function () { return staticMap_js_1.fetchStaticMap; } });
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
import type { RequestTransformFunction } from 'maplibre-gl';
|
|
2
|
+
/**
|
|
3
|
+
* Does this URL belong to our API?
|
|
4
|
+
*
|
|
5
|
+
* This decides who receives the customer's bearer token, and it used to be
|
|
6
|
+
* `url.startsWith(apiUrl)` — a string test standing in for a URL test. For
|
|
7
|
+
* `apiUrl = "https://api.example.com"`, the host `api.example.com.evil.test`
|
|
8
|
+
* is a prefix match, so a style referencing
|
|
9
|
+
* `https://api.example.com.evil.test/tiles/1/2/3` was handed
|
|
10
|
+
* `Authorization: Bearer <token>` and the token left the building (#34).
|
|
11
|
+
*
|
|
12
|
+
* That is reachable because a style descriptor is DATA: its `sprite`, `glyphs`
|
|
13
|
+
* and `sources` entries are URLs the style author chose, and MapLibre asks
|
|
14
|
+
* transformRequest about every one of them. Any style not wholly ours — a
|
|
15
|
+
* customer's own, or one edited through a tool — can name a host it likes.
|
|
16
|
+
*
|
|
17
|
+
* Compared as URLs instead, which also gets host case-folding, default ports
|
|
18
|
+
* (`https://api.test:443` === `https://api.test`) and userinfo right for free,
|
|
19
|
+
* and adds a path check so a shared host serving another tenant under a
|
|
20
|
+
* different base path is not "ours" either.
|
|
21
|
+
*
|
|
22
|
+
* Fails CLOSED: anything that will not parse gets no token. The only way to
|
|
23
|
+
* reach that is a relative `apiUrl` in a runtime with no `location` to resolve
|
|
24
|
+
* it against — i.e. not a browser, which is the only place MapLibre runs.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isOurApi(url: string, apiUrl: string): boolean;
|
|
2
27
|
/**
|
|
3
28
|
* Creates a transformRequest function for MapLibre that adds authentication
|
|
4
29
|
* and proper Accept headers for AWS Location Service API requests.
|
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
import type { Map } from 'maplibre-gl';
|
|
2
2
|
/**
|
|
3
3
|
* Mapping of POI category names to their MapLibre layer IDs in AWS GeoMaps tiles.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Standard and Hybrid carry the same sixteen `poi*` layers; Monochrome carries
|
|
6
|
+
* only the three park layers, and Satellite none. A category whose layers a
|
|
7
|
+
* style does not carry is skipped. AWS publishes no list of these ids, so
|
|
8
|
+
* `test/map-poi-layers.test.ts` holds this map to each style's layers as the
|
|
9
|
+
* service served them: every `poi*` layer in exactly one category (#33).
|
|
5
10
|
*/
|
|
6
11
|
export declare const POI_CATEGORIES: {
|
|
7
12
|
readonly food_drink: readonly ["poi_100_food_drink"];
|
|
8
13
|
readonly entertainment: readonly ["poi_200_going_out_entertainment"];
|
|
9
14
|
readonly sights: readonly ["poi_300_sights_museums"];
|
|
10
|
-
readonly transit: readonly ["poi_400_transit"];
|
|
15
|
+
readonly transit: readonly ["poi_400_transit", "poi_400_transit_small"];
|
|
11
16
|
readonly accommodations: readonly ["poi_500_accommodations"];
|
|
12
17
|
readonly leisure: readonly ["poi_550_leisure_outdoor"];
|
|
13
18
|
readonly shopping: readonly ["poi_600_shopping"];
|
|
14
|
-
readonly business: readonly ["poi_700_business_services"];
|
|
15
|
-
readonly facilities: readonly ["poi_800_facilities"];
|
|
19
|
+
readonly business: readonly ["poi_700_business_services", "poi_700_business_services_generic"];
|
|
20
|
+
readonly facilities: readonly ["poi_800_facilities", "poi_800_facilities_generic"];
|
|
16
21
|
readonly areas: readonly ["poi_900_areas_buildings"];
|
|
17
|
-
readonly parks: readonly ["poi_landuse_park", "poi_landuse_public_complex"];
|
|
22
|
+
readonly parks: readonly ["poi_landuse_park", "poi_landuse_park_lowzoom", "poi_landuse_public_complex"];
|
|
18
23
|
};
|
|
19
24
|
export type PoiCategory = keyof typeof POI_CATEGORIES;
|
|
20
25
|
/**
|
package/dist/cjs/maps/mapPoi.js
CHANGED
|
@@ -5,20 +5,29 @@ exports.setPoiVisibility = setPoiVisibility;
|
|
|
5
5
|
exports.setAllPoiVisibility = setAllPoiVisibility;
|
|
6
6
|
/**
|
|
7
7
|
* Mapping of POI category names to their MapLibre layer IDs in AWS GeoMaps tiles.
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* Standard and Hybrid carry the same sixteen `poi*` layers; Monochrome carries
|
|
10
|
+
* only the three park layers, and Satellite none. A category whose layers a
|
|
11
|
+
* style does not carry is skipped. AWS publishes no list of these ids, so
|
|
12
|
+
* `test/map-poi-layers.test.ts` holds this map to each style's layers as the
|
|
13
|
+
* service served them: every `poi*` layer in exactly one category (#33).
|
|
9
14
|
*/
|
|
10
15
|
exports.POI_CATEGORIES = {
|
|
11
16
|
food_drink: ['poi_100_food_drink'],
|
|
12
17
|
entertainment: ['poi_200_going_out_entertainment'],
|
|
13
18
|
sights: ['poi_300_sights_museums'],
|
|
14
|
-
transit: ['poi_400_transit'],
|
|
19
|
+
transit: ['poi_400_transit', 'poi_400_transit_small'],
|
|
15
20
|
accommodations: ['poi_500_accommodations'],
|
|
16
21
|
leisure: ['poi_550_leisure_outdoor'],
|
|
17
22
|
shopping: ['poi_600_shopping'],
|
|
18
|
-
business: ['poi_700_business_services'],
|
|
19
|
-
facilities: ['poi_800_facilities'],
|
|
23
|
+
business: ['poi_700_business_services', 'poi_700_business_services_generic'],
|
|
24
|
+
facilities: ['poi_800_facilities', 'poi_800_facilities_generic'],
|
|
20
25
|
areas: ['poi_900_areas_buildings'],
|
|
21
|
-
parks: [
|
|
26
|
+
parks: [
|
|
27
|
+
'poi_landuse_park',
|
|
28
|
+
'poi_landuse_park_lowzoom',
|
|
29
|
+
'poi_landuse_public_complex',
|
|
30
|
+
],
|
|
22
31
|
};
|
|
23
32
|
/**
|
|
24
33
|
* Set the visibility of one or more POI categories on the map.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StyleSpecification } from 'maplibre-gl';
|
|
2
2
|
import type { RequestOptions } from '../transport/http.js';
|
|
3
3
|
import type { Buildings, ColorScheme, ContourDensity, MapStyle, PoiDensity, StylePoiCategory, Terrain, TrafficMode, TravelMode } from './mapEnums.js';
|
|
4
|
+
import type { MapTokenSource } from './mapToken.js';
|
|
4
5
|
/**
|
|
5
6
|
* Options for building an AWS Location Service map style URL.
|
|
6
7
|
* All parameters map directly to query parameters supported by the style descriptor endpoint.
|
|
@@ -137,10 +138,16 @@ export declare function buildMapStyleUrl(apiUrl: string, mapStyle: MapStyle, opt
|
|
|
137
138
|
* `event.error.status` is 403, and `event.error.body` is a `Blob` holding the
|
|
138
139
|
* same `{ message, code }` JSON.
|
|
139
140
|
*
|
|
141
|
+
* A REFUSED TOKEN. Given `{ getToken, refreshToken }` in place of a bare
|
|
142
|
+
* `getToken`, a 401 asks `refreshToken` once and sends again when it brings a
|
|
143
|
+
* different token, within the same `signal` and `overallTimeoutMs` (#72). A
|
|
144
|
+
* 403 is never retried: a new token cannot change it.
|
|
145
|
+
*
|
|
140
146
|
* @param apiUrl - Base URL of the Location Service API
|
|
141
147
|
* @param mapStyle - Map style name: 'Standard' or 'Monochrome', or 'Satellite'
|
|
142
148
|
* or 'Hybrid', which need the `satellite` plan feature (see `MAP_STYLES`)
|
|
143
|
-
* @param
|
|
149
|
+
* @param tokens - Callback returning the current auth token, or
|
|
150
|
+
* `{ getToken, refreshToken }` to recover from a refused one
|
|
144
151
|
* @param options - Style options; `language` is applied to the descriptor, all
|
|
145
152
|
* others become URL params. Those tagged `@planFeature` need that feature of
|
|
146
153
|
* the application's plan
|
|
@@ -151,6 +158,6 @@ export declare function buildMapStyleUrl(apiUrl: string, mapStyle: MapStyle, opt
|
|
|
151
158
|
* const style = await fetchMapStyle(API_URL, 'Standard', getToken, { colorScheme: 'Dark', language: 'fr' })
|
|
152
159
|
* const map = new maplibregl.Map({ style, transformRequest: createTransformRequest(API_URL, getToken) })
|
|
153
160
|
*/
|
|
154
|
-
export declare function fetchMapStyle(apiUrl: string, mapStyle: MapStyle,
|
|
161
|
+
export declare function fetchMapStyle(apiUrl: string, mapStyle: MapStyle, tokens: MapTokenSource, options?: MapStyleOptions & {
|
|
155
162
|
language?: string;
|
|
156
163
|
}, request?: RequestOptions): Promise<StyleSpecification>;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildMapStyleUrl = buildMapStyleUrl;
|
|
4
4
|
exports.fetchMapStyle = fetchMapStyle;
|
|
5
|
-
const errors_js_1 = require("../transport/errors.js");
|
|
6
5
|
const http_js_1 = require("../transport/http.js");
|
|
7
6
|
const mapLanguage_js_1 = require("./mapLanguage.js");
|
|
7
|
+
const mapToken_js_1 = require("./mapToken.js");
|
|
8
8
|
/**
|
|
9
9
|
* Build a map style descriptor URL for the Location Service API.
|
|
10
10
|
*
|
|
@@ -67,10 +67,16 @@ function buildMapStyleUrl(apiUrl, mapStyle, options = {}) {
|
|
|
67
67
|
* `event.error.status` is 403, and `event.error.body` is a `Blob` holding the
|
|
68
68
|
* same `{ message, code }` JSON.
|
|
69
69
|
*
|
|
70
|
+
* A REFUSED TOKEN. Given `{ getToken, refreshToken }` in place of a bare
|
|
71
|
+
* `getToken`, a 401 asks `refreshToken` once and sends again when it brings a
|
|
72
|
+
* different token, within the same `signal` and `overallTimeoutMs` (#72). A
|
|
73
|
+
* 403 is never retried: a new token cannot change it.
|
|
74
|
+
*
|
|
70
75
|
* @param apiUrl - Base URL of the Location Service API
|
|
71
76
|
* @param mapStyle - Map style name: 'Standard' or 'Monochrome', or 'Satellite'
|
|
72
77
|
* or 'Hybrid', which need the `satellite` plan feature (see `MAP_STYLES`)
|
|
73
|
-
* @param
|
|
78
|
+
* @param tokens - Callback returning the current auth token, or
|
|
79
|
+
* `{ getToken, refreshToken }` to recover from a refused one
|
|
74
80
|
* @param options - Style options; `language` is applied to the descriptor, all
|
|
75
81
|
* others become URL params. Those tagged `@planFeature` need that feature of
|
|
76
82
|
* the application's plan
|
|
@@ -81,16 +87,10 @@ function buildMapStyleUrl(apiUrl, mapStyle, options = {}) {
|
|
|
81
87
|
* const style = await fetchMapStyle(API_URL, 'Standard', getToken, { colorScheme: 'Dark', language: 'fr' })
|
|
82
88
|
* const map = new maplibregl.Map({ style, transformRequest: createTransformRequest(API_URL, getToken) })
|
|
83
89
|
*/
|
|
84
|
-
async function fetchMapStyle(apiUrl, mapStyle,
|
|
90
|
+
async function fetchMapStyle(apiUrl, mapStyle, tokens, options = {}, request = {}) {
|
|
85
91
|
const { language, ...styleOptions } = options;
|
|
86
92
|
const url = buildMapStyleUrl(apiUrl, mapStyle, styleOptions);
|
|
87
|
-
const
|
|
88
|
-
// `Bearer undefined` used to go out here, and came back as a 401 the caller
|
|
89
|
-
// had to work backwards from — a whole round trip for a request that was
|
|
90
|
-
// never going to succeed (#37).
|
|
91
|
-
if (!token) {
|
|
92
|
-
throw (0, errors_js_1.noTokenAvailable)('getToken() returned nothing, so no style request was sent. Check the token provider has finished initialising.');
|
|
93
|
-
}
|
|
93
|
+
const call = (0, http_js_1.startCall)(request);
|
|
94
94
|
// Through the shared transport, not a bare fetch: this gets the same
|
|
95
95
|
// per-attempt timeout, overall budget, cancellation and retry as every other
|
|
96
96
|
// call in the package, and the same error type on the way out. It also keeps
|
|
@@ -104,12 +104,16 @@ async function fetchMapStyle(apiUrl, mapStyle, getToken, options = {}, request =
|
|
|
104
104
|
// This used to throw `Failed to fetch map style: 400`, discarding all of it
|
|
105
105
|
// two lines before anyone could read it — the same defect #89 fixed in the
|
|
106
106
|
// API, one layer up.
|
|
107
|
-
|
|
107
|
+
//
|
|
108
|
+
// `Bearer undefined` used to go out here, and came back as a 401 the caller
|
|
109
|
+
// had to work backwards from — a whole round trip for a request that was
|
|
110
|
+
// never going to succeed (#37). `sendWithTokenRefresh` refuses first.
|
|
111
|
+
const style = await (0, mapToken_js_1.sendWithTokenRefresh)(tokens, 'getToken() returned nothing, so no style request was sent. Check the token provider has finished initialising.', call, (token) => (0, http_js_1.requestJson)(url, {
|
|
108
112
|
headers: {
|
|
109
113
|
Authorization: `Bearer ${token}`,
|
|
110
114
|
Accept: 'application/json',
|
|
111
115
|
},
|
|
112
|
-
},
|
|
116
|
+
}, call));
|
|
113
117
|
if (language) {
|
|
114
118
|
applyLanguageToDescriptor(style, language);
|
|
115
119
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CallOptions } from '../transport/http.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where a map helper gets its token, and how it gets a new one (#72).
|
|
4
|
+
*
|
|
5
|
+
* `getToken` is synchronous by contract: MapLibre's `transformRequest` calls
|
|
6
|
+
* it for every tile and cannot wait. `refreshToken` is how a map recovers when
|
|
7
|
+
* the API refuses the token in hand before its `exp` — a rotated secret, for
|
|
8
|
+
* one — which nothing else in the map path can do. It is the escape hatch
|
|
9
|
+
* `GeoPlacesClient` takes, under the same name.
|
|
10
|
+
*/
|
|
11
|
+
export interface MapTokens {
|
|
12
|
+
/** The token in hand, now. */
|
|
13
|
+
getToken: () => string | undefined;
|
|
14
|
+
/** Obtain a new token after the API refused the one in hand. */
|
|
15
|
+
refreshToken?: () => Promise<string | undefined>;
|
|
16
|
+
}
|
|
17
|
+
/** A bare `getToken`, which behaves as it always has, or `MapTokens`. */
|
|
18
|
+
export type MapTokenSource = (() => string | undefined) | MapTokens;
|
|
19
|
+
/**
|
|
20
|
+
* Send with the token in hand, and after a 401 once more with a different one
|
|
21
|
+
* from `refreshToken`, all within the call's signal and deadline (#62). A 403
|
|
22
|
+
* is never retried, and neither is the token the API refused.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sendWithTokenRefresh<T>(source: MapTokenSource, noTokenAdvice: string, call: CallOptions, send: (token: string) => Promise<T>): Promise<T>;
|
|
25
|
+
/** A tile's coordinates, as `map.refreshTiles` takes them. */
|
|
26
|
+
interface TileCoordinates {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
z: number;
|
|
30
|
+
}
|
|
31
|
+
/** What MapLibre's `error` event carries for a refused request. */
|
|
32
|
+
interface MapErrorEvent {
|
|
33
|
+
error?: {
|
|
34
|
+
status?: number;
|
|
35
|
+
url?: string;
|
|
36
|
+
};
|
|
37
|
+
sourceId?: string;
|
|
38
|
+
tile?: {
|
|
39
|
+
tileID: {
|
|
40
|
+
canonical: TileCoordinates;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The part of a MapLibre `Map` that `refreshTokenOnUnauthorized` uses. */
|
|
45
|
+
export interface TokenRefreshMap {
|
|
46
|
+
on(type: 'error', listener: (event: MapErrorEvent) => void): unknown;
|
|
47
|
+
off(type: 'error', listener: (event: MapErrorEvent) => void): unknown;
|
|
48
|
+
refreshTiles(sourceId: string, tileIds?: TileCoordinates[]): void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Recover the tiles MapLibre fetches itself when the API refuses the token
|
|
52
|
+
* (#72).
|
|
53
|
+
*
|
|
54
|
+
* `createTransformRequest` builds each request synchronously and never sees
|
|
55
|
+
* the answer, so a refused tile used to leave a hole until the page reloaded.
|
|
56
|
+
* This listens for MapLibre's `error` events. On a 401 from a URL of our API it
|
|
57
|
+
* asks `refreshToken` once for the whole burst. When `getToken` then returns a
|
|
58
|
+
* different token, it reloads each refused tile with `map.refreshTiles`, whose
|
|
59
|
+
* requests carry that token. So `refreshToken` must make `getToken` return
|
|
60
|
+
* what it obtained: the tiles have no other way to receive it. A refused
|
|
61
|
+
* request that is not a tile (a glyph, a sprite) has the token replaced for
|
|
62
|
+
* the next one, and nothing reloaded.
|
|
63
|
+
*
|
|
64
|
+
* Tiles are reloaded by id, never a whole source: MapLibre 6 reloads a source's
|
|
65
|
+
* errored tiles as still loading, and they wait for a load that never comes.
|
|
66
|
+
*
|
|
67
|
+
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
68
|
+
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
69
|
+
* could not replace, and a failure that says when to ask again, are not asked
|
|
70
|
+
* about again until they lapse or the token in hand changes.
|
|
71
|
+
*
|
|
72
|
+
* Returns a function that stops listening.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* const tokens = { getToken, refreshToken }
|
|
76
|
+
* const map = new Map({
|
|
77
|
+
* container: 'map',
|
|
78
|
+
* style: await fetchMapStyle(API_URL, 'Standard', tokens),
|
|
79
|
+
* transformRequest: createTransformRequest(API_URL, getToken),
|
|
80
|
+
* })
|
|
81
|
+
* refreshTokenOnUnauthorized(map, API_URL, tokens)
|
|
82
|
+
*/
|
|
83
|
+
export declare function refreshTokenOnUnauthorized(map: TokenRefreshMap, apiUrl: string, tokens: Required<MapTokens>): () => void;
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendWithTokenRefresh = sendWithTokenRefresh;
|
|
4
|
+
exports.refreshTokenOnUnauthorized = refreshTokenOnUnauthorized;
|
|
5
|
+
const tokenHold_js_1 = require("../auth/tokenHold.js");
|
|
6
|
+
const LocationServiceException_js_1 = require("../errors/LocationServiceException.js");
|
|
7
|
+
const errors_js_1 = require("../transport/errors.js");
|
|
8
|
+
const http_js_1 = require("../transport/http.js");
|
|
9
|
+
const createTransformRequest_js_1 = require("./createTransformRequest.js");
|
|
10
|
+
/**
|
|
11
|
+
* One hold per `MapTokens` object, so the helpers handed the same one share
|
|
12
|
+
* what the API and `refreshToken` said (#38): a refusal one of them met is not
|
|
13
|
+
* asked again by the next. A bare `getToken` never asks again, and needs none.
|
|
14
|
+
*/
|
|
15
|
+
const holds = new WeakMap();
|
|
16
|
+
function holdOf(tokens) {
|
|
17
|
+
let hold = holds.get(tokens);
|
|
18
|
+
if (!hold)
|
|
19
|
+
holds.set(tokens, (hold = new tokenHold_js_1.TokenHold()));
|
|
20
|
+
return hold;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Send with the token in hand, and after a 401 once more with a different one
|
|
24
|
+
* from `refreshToken`, all within the call's signal and deadline (#62). A 403
|
|
25
|
+
* is never retried, and neither is the token the API refused.
|
|
26
|
+
*/
|
|
27
|
+
async function sendWithTokenRefresh(source, noTokenAdvice, call, send) {
|
|
28
|
+
const tokens = typeof source === 'function' ? { getToken: source } : source;
|
|
29
|
+
const token = tokens.getToken();
|
|
30
|
+
if (!token)
|
|
31
|
+
throw (0, errors_js_1.noTokenAvailable)(noTokenAdvice);
|
|
32
|
+
const { refreshToken } = tokens;
|
|
33
|
+
if (!refreshToken)
|
|
34
|
+
return send(token);
|
|
35
|
+
return (0, tokenHold_js_1.sendRetryingOnce)(holdOf(tokens), token, send, async () => (await (0, http_js_1.withinCall)(refreshToken(), call)) ?? tokens.getToken());
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Recover the tiles MapLibre fetches itself when the API refuses the token
|
|
39
|
+
* (#72).
|
|
40
|
+
*
|
|
41
|
+
* `createTransformRequest` builds each request synchronously and never sees
|
|
42
|
+
* the answer, so a refused tile used to leave a hole until the page reloaded.
|
|
43
|
+
* This listens for MapLibre's `error` events. On a 401 from a URL of our API it
|
|
44
|
+
* asks `refreshToken` once for the whole burst. When `getToken` then returns a
|
|
45
|
+
* different token, it reloads each refused tile with `map.refreshTiles`, whose
|
|
46
|
+
* requests carry that token. So `refreshToken` must make `getToken` return
|
|
47
|
+
* what it obtained: the tiles have no other way to receive it. A refused
|
|
48
|
+
* request that is not a tile (a glyph, a sprite) has the token replaced for
|
|
49
|
+
* the next one, and nothing reloaded.
|
|
50
|
+
*
|
|
51
|
+
* Tiles are reloaded by id, never a whole source: MapLibre 6 reloads a source's
|
|
52
|
+
* errored tiles as still loading, and they wait for a load that never comes.
|
|
53
|
+
*
|
|
54
|
+
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
55
|
+
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
56
|
+
* could not replace, and a failure that says when to ask again, are not asked
|
|
57
|
+
* about again until they lapse or the token in hand changes.
|
|
58
|
+
*
|
|
59
|
+
* Returns a function that stops listening.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const tokens = { getToken, refreshToken }
|
|
63
|
+
* const map = new Map({
|
|
64
|
+
* container: 'map',
|
|
65
|
+
* style: await fetchMapStyle(API_URL, 'Standard', tokens),
|
|
66
|
+
* transformRequest: createTransformRequest(API_URL, getToken),
|
|
67
|
+
* })
|
|
68
|
+
* refreshTokenOnUnauthorized(map, API_URL, tokens)
|
|
69
|
+
*/
|
|
70
|
+
function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
71
|
+
const hold = holdOf(tokens);
|
|
72
|
+
// Each source's refused tiles, keyed `z/x/y` so a tile refused twice is
|
|
73
|
+
// reloaded once.
|
|
74
|
+
const refused = new Map();
|
|
75
|
+
let asking = false;
|
|
76
|
+
const onError = ({ error, sourceId, tile }) => {
|
|
77
|
+
if (error?.status !== 401 || !error.url || !(0, createTransformRequest_js_1.isOurApi)(error.url, apiUrl))
|
|
78
|
+
return;
|
|
79
|
+
if (sourceId && tile) {
|
|
80
|
+
const { x, y, z } = tile.tileID.canonical;
|
|
81
|
+
const tiles = refused.get(sourceId) ?? new Map();
|
|
82
|
+
refused.set(sourceId, tiles.set(`${z}/${x}/${y}`, { x, y, z }));
|
|
83
|
+
}
|
|
84
|
+
if (asking)
|
|
85
|
+
return;
|
|
86
|
+
const inHand = tokens.getToken();
|
|
87
|
+
const held = hold.check(inHand);
|
|
88
|
+
if (held && !held.askAgain)
|
|
89
|
+
return;
|
|
90
|
+
asking = true;
|
|
91
|
+
// Asked inside an executor, so a `refreshToken` that throws instead of
|
|
92
|
+
// rejecting lands in the catch below rather than in MapLibre's emitter.
|
|
93
|
+
new Promise((resolve) => resolve(tokens.refreshToken()))
|
|
94
|
+
.then(() => {
|
|
95
|
+
// A reloaded tile takes its token from `getToken`, as every tile does,
|
|
96
|
+
// so that is the token that must have changed. Reloading on what
|
|
97
|
+
// `refreshToken` returned alone would send the refused one again, and
|
|
98
|
+
// its 401 would ask again: a loop of refreshes and reloads.
|
|
99
|
+
const now = tokens.getToken();
|
|
100
|
+
if (!now || now === inHand) {
|
|
101
|
+
// MapLibre's error carries none of the API's fields, so the refusal
|
|
102
|
+
// is remembered as the 401 it was.
|
|
103
|
+
hold.remember(new LocationServiceException_js_1.LocationServiceException({
|
|
104
|
+
code: 'UnauthorizedException',
|
|
105
|
+
message: 'The API refused the token, and getToken has no other since refreshToken settled.',
|
|
106
|
+
statusCode: 401,
|
|
107
|
+
}), inHand);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
for (const [id, tiles] of refused)
|
|
111
|
+
map.refreshTiles(id, [...tiles.values()]);
|
|
112
|
+
})
|
|
113
|
+
.catch((refusal) => {
|
|
114
|
+
// Only a failure that says when to ask again is remembered; any other
|
|
115
|
+
// leaves the next refused tile to ask, as the send paths do.
|
|
116
|
+
if ((0, tokenHold_js_1.holdFor)(refusal) > 0)
|
|
117
|
+
hold.remember(refusal, inHand);
|
|
118
|
+
})
|
|
119
|
+
.finally(() => {
|
|
120
|
+
refused.clear();
|
|
121
|
+
asking = false;
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
map.on('error', onError);
|
|
125
|
+
return () => map.off('error', onError);
|
|
126
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { RequestOptions } from '../transport/http.js';
|
|
2
2
|
import type { ColorScheme, LabelSize, MapFeatureMode, ScaleBarUnit, StaticMapStyle } from './mapEnums.js';
|
|
3
|
+
import type { MapTokenSource } from './mapToken.js';
|
|
3
4
|
/**
|
|
4
5
|
* Static maps: build the URL, send the right headers, get a Blob.
|
|
5
6
|
*
|
|
@@ -110,11 +111,14 @@ export declare function buildStaticMapUrl(apiUrl: string, options: StaticMapOpti
|
|
|
110
111
|
*
|
|
111
112
|
* A refused plan feature — a Satellite `style`, the default when none is
|
|
112
113
|
* given, or a `politicalView` — rejects with a `LocationServiceException`
|
|
113
|
-
* whose `isFeatureNotEntitled` is true, as `fetchMapStyle` does.
|
|
114
|
+
* whose `isFeatureNotEntitled` is true, as `fetchMapStyle` does. Given
|
|
115
|
+
* `{ getToken, refreshToken }`, a refused token is replaced once, as
|
|
116
|
+
* `fetchMapStyle` does it (#72).
|
|
114
117
|
*
|
|
115
118
|
* @param apiUrl Base URL of the Location Service API
|
|
116
119
|
* @param options Render options; exactly one of center / boundingBox / boundedPositions
|
|
117
|
-
* @param
|
|
120
|
+
* @param tokens Callback returning the current auth token, or
|
|
121
|
+
* `{ getToken, refreshToken }` to recover from a refused one
|
|
118
122
|
* @param request Transport options: `signal` to cancel, `timeoutMs`, `overallTimeoutMs`, `retry`
|
|
119
123
|
*
|
|
120
124
|
* @example
|
|
@@ -124,4 +128,4 @@ export declare function buildStaticMapUrl(apiUrl: string, options: StaticMapOpti
|
|
|
124
128
|
* }, getToken)
|
|
125
129
|
* const url = URL.createObjectURL(blob) // remember to revokeObjectURL
|
|
126
130
|
*/
|
|
127
|
-
export declare function fetchStaticMap(apiUrl: string, options: StaticMapOptions,
|
|
131
|
+
export declare function fetchStaticMap(apiUrl: string, options: StaticMapOptions, tokens: MapTokenSource, request?: RequestOptions): Promise<Blob>;
|