@chaosity/location-client 0.13.0 → 0.13.1
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 +8 -1
- package/dist/cjs/maps/mapToken.d.ts +5 -2
- package/dist/cjs/maps/mapToken.js +64 -9
- package/dist/maps/mapToken.d.ts +5 -2
- package/dist/maps/mapToken.js +65 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -252,7 +252,14 @@ tiles receive the new token no other way — they read `getToken` through
|
|
|
252
252
|
`createTransformRequest` — so a `refreshToken` that leaves `getToken` unchanged
|
|
253
253
|
reloads nothing. A 403 is never retried: a new token cannot change it. A token
|
|
254
254
|
`refreshToken` could not replace is not asked about again for 30 seconds, by
|
|
255
|
-
any helper handed the same `tokens` object.
|
|
255
|
+
any helper handed the same `tokens` object. For 30 seconds after a refresh
|
|
256
|
+
brought a new token, a refused tile is reloaded once with it rather than asked
|
|
257
|
+
about: a tile requested with the old token can be refused after the new one
|
|
258
|
+
arrived. A tile the helper reloaded with the new token, refused again, means
|
|
259
|
+
the API refuses that token too, and it is held like the first. So an API that
|
|
260
|
+
refuses every token, such as a map pointed at an API its tokens are not for,
|
|
261
|
+
costs one new token per 30 seconds rather than one per reload, however many
|
|
262
|
+
maps share the `tokens` object. `stop()` removes the listener.
|
|
256
263
|
|
|
257
264
|
A bare `getToken` works as it always has: one request, and its 401 to you.
|
|
258
265
|
|
|
@@ -66,8 +66,11 @@ export interface TokenRefreshMap {
|
|
|
66
66
|
*
|
|
67
67
|
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
68
68
|
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
69
|
-
* could not replace,
|
|
70
|
-
*
|
|
69
|
+
* could not replace, a token it brought that the API refused on a tile
|
|
70
|
+
* reloaded with it, and a failure that says when to ask again, are not asked
|
|
71
|
+
* about again until they lapse or the token in hand changes. While the token
|
|
72
|
+
* a refresh brought is in hand, any other refused tile is reloaded once with
|
|
73
|
+
* it, without asking.
|
|
71
74
|
*
|
|
72
75
|
* Returns a function that stops listening.
|
|
73
76
|
*
|
|
@@ -19,6 +19,13 @@ function holdOf(tokens) {
|
|
|
19
19
|
holds.set(tokens, (hold = new tokenHold_js_1.TokenHold()));
|
|
20
20
|
return hold;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* The token each `MapTokens` object's refresh last brought, and until when a
|
|
24
|
+
* 401 while it is in hand is not news (#72). One per object, beside its hold:
|
|
25
|
+
* a note per helper had each of two helpers handed one object take the
|
|
26
|
+
* other's token as news, and ask again for every refusal.
|
|
27
|
+
*/
|
|
28
|
+
const broughtBy = new WeakMap();
|
|
22
29
|
/**
|
|
23
30
|
* Send with the token in hand, and after a 401 once more with a different one
|
|
24
31
|
* from `refreshToken`, all within the call's signal and deadline (#62). A 403
|
|
@@ -53,8 +60,11 @@ async function sendWithTokenRefresh(source, noTokenAdvice, call, send) {
|
|
|
53
60
|
*
|
|
54
61
|
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
55
62
|
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
56
|
-
* could not replace,
|
|
57
|
-
*
|
|
63
|
+
* could not replace, a token it brought that the API refused on a tile
|
|
64
|
+
* reloaded with it, and a failure that says when to ask again, are not asked
|
|
65
|
+
* about again until they lapse or the token in hand changes. While the token
|
|
66
|
+
* a refresh brought is in hand, any other refused tile is reloaded once with
|
|
67
|
+
* it, without asking.
|
|
58
68
|
*
|
|
59
69
|
* Returns a function that stops listening.
|
|
60
70
|
*
|
|
@@ -70,16 +80,26 @@ async function sendWithTokenRefresh(source, noTokenAdvice, call, send) {
|
|
|
70
80
|
function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
71
81
|
const hold = holdOf(tokens);
|
|
72
82
|
// Each source's refused tiles, keyed `z/x/y` so a tile refused twice is
|
|
73
|
-
// reloaded once.
|
|
83
|
+
// reloaded once. Tiles refused while a hold stands are kept, so the next ask
|
|
84
|
+
// reloads them; MapLibre ignores ids no longer in view.
|
|
74
85
|
const refused = new Map();
|
|
86
|
+
// The token each tile was reloaded with here, keyed `source:z/x/y`.
|
|
87
|
+
const reloadedWith = new Map();
|
|
75
88
|
let asking = false;
|
|
89
|
+
const reload = (sourceId, tiles, token) => {
|
|
90
|
+
for (const { x, y, z } of tiles)
|
|
91
|
+
reloadedWith.set(`${sourceId}:${z}/${x}/${y}`, token);
|
|
92
|
+
map.refreshTiles(sourceId, tiles);
|
|
93
|
+
};
|
|
76
94
|
const onError = ({ error, sourceId, tile }) => {
|
|
77
95
|
if (error?.status !== 401 || !error.url || !(0, createTransformRequest_js_1.isOurApi)(error.url, apiUrl))
|
|
78
96
|
return;
|
|
97
|
+
let refusedTile;
|
|
79
98
|
if (sourceId && tile) {
|
|
80
99
|
const { x, y, z } = tile.tileID.canonical;
|
|
81
100
|
const tiles = refused.get(sourceId) ?? new Map();
|
|
82
101
|
refused.set(sourceId, tiles.set(`${z}/${x}/${y}`, { x, y, z }));
|
|
102
|
+
refusedTile = [sourceId, { x, y, z }];
|
|
83
103
|
}
|
|
84
104
|
if (asking)
|
|
85
105
|
return;
|
|
@@ -87,6 +107,32 @@ function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
87
107
|
const held = hold.check(inHand);
|
|
88
108
|
if (held && !held.askAgain)
|
|
89
109
|
return;
|
|
110
|
+
// A 401 while the token a refresh brought is in hand. The event does not
|
|
111
|
+
// say which token the refused request carried, and a tile requested with
|
|
112
|
+
// the token before, answered late, looks like the new token refused. So
|
|
113
|
+
// only a tile reloaded here with the token in hand is that token refused:
|
|
114
|
+
// the API refuses every token, as it does a map pointed at an API its
|
|
115
|
+
// tokens are not for, and asking again minted a token and reloaded the
|
|
116
|
+
// tiles several times a second. That is remembered, as a send path
|
|
117
|
+
// remembers its retry refused, and the next ask is after the hold. Any
|
|
118
|
+
// other tile is reloaded once with the token in hand; anything else (a
|
|
119
|
+
// glyph, a sprite) asks nothing.
|
|
120
|
+
const brought = broughtBy.get(tokens);
|
|
121
|
+
if (inHand && inHand === brought?.token && Date.now() < brought.until) {
|
|
122
|
+
if (!refusedTile)
|
|
123
|
+
return;
|
|
124
|
+
const [id, coordinates] = refusedTile;
|
|
125
|
+
const key = `${coordinates.z}/${coordinates.x}/${coordinates.y}`;
|
|
126
|
+
if (reloadedWith.get(`${id}:${key}`) !== inHand) {
|
|
127
|
+
refused.get(id)?.delete(key);
|
|
128
|
+
reload(id, [coordinates], inHand);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// Kept in `refused`, as every tile refused during the hold is.
|
|
132
|
+
broughtBy.delete(tokens);
|
|
133
|
+
hold.remember(unauthorized('The API refused a tile reloaded with the token refreshToken brought.'), inHand);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
90
136
|
asking = true;
|
|
91
137
|
// Asked inside an executor, so a `refreshToken` that throws instead of
|
|
92
138
|
// rejecting lands in the catch below rather than in MapLibre's emitter.
|
|
@@ -100,15 +146,18 @@ function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
100
146
|
if (!now || now === inHand) {
|
|
101
147
|
// MapLibre's error carries none of the API's fields, so the refusal
|
|
102
148
|
// is remembered as the 401 it was.
|
|
103
|
-
hold.remember(
|
|
104
|
-
code: 'UnauthorizedException',
|
|
105
|
-
message: 'The API refused the token, and getToken has no other since refreshToken settled.',
|
|
106
|
-
statusCode: 401,
|
|
107
|
-
}), inHand);
|
|
149
|
+
hold.remember(unauthorized('The API refused the token, and getToken has no other since refreshToken settled.'), inHand);
|
|
108
150
|
return;
|
|
109
151
|
}
|
|
152
|
+
broughtBy.set(tokens, {
|
|
153
|
+
token: now,
|
|
154
|
+
until: Date.now() + tokenHold_js_1.TOKEN_REFUSAL_HOLD_MS,
|
|
155
|
+
});
|
|
156
|
+
// What was reloaded with an earlier token can no longer match.
|
|
157
|
+
reloadedWith.clear();
|
|
110
158
|
for (const [id, tiles] of refused)
|
|
111
|
-
|
|
159
|
+
if (tiles.size)
|
|
160
|
+
reload(id, [...tiles.values()], now);
|
|
112
161
|
})
|
|
113
162
|
.catch((refusal) => {
|
|
114
163
|
// Only a failure that says when to ask again is remembered; any other
|
|
@@ -124,3 +173,9 @@ function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
124
173
|
map.on('error', onError);
|
|
125
174
|
return () => map.off('error', onError);
|
|
126
175
|
}
|
|
176
|
+
/** A refusal MapLibre reported, remembered as the 401 it was. */
|
|
177
|
+
const unauthorized = (message) => new LocationServiceException_js_1.LocationServiceException({
|
|
178
|
+
code: 'UnauthorizedException',
|
|
179
|
+
message,
|
|
180
|
+
statusCode: 401,
|
|
181
|
+
});
|
package/dist/maps/mapToken.d.ts
CHANGED
|
@@ -66,8 +66,11 @@ export interface TokenRefreshMap {
|
|
|
66
66
|
*
|
|
67
67
|
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
68
68
|
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
69
|
-
* could not replace,
|
|
70
|
-
*
|
|
69
|
+
* could not replace, a token it brought that the API refused on a tile
|
|
70
|
+
* reloaded with it, and a failure that says when to ask again, are not asked
|
|
71
|
+
* about again until they lapse or the token in hand changes. While the token
|
|
72
|
+
* a refresh brought is in hand, any other refused tile is reloaded once with
|
|
73
|
+
* it, without asking.
|
|
71
74
|
*
|
|
72
75
|
* Returns a function that stops listening.
|
|
73
76
|
*
|
package/dist/maps/mapToken.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TokenHold, holdFor, sendRetryingOnce } from '../auth/tokenHold.js';
|
|
1
|
+
import { TOKEN_REFUSAL_HOLD_MS, TokenHold, holdFor, sendRetryingOnce, } from '../auth/tokenHold.js';
|
|
2
2
|
import { LocationServiceException } from '../errors/LocationServiceException.js';
|
|
3
3
|
import { noTokenAvailable } from '../transport/errors.js';
|
|
4
4
|
import { withinCall } from '../transport/http.js';
|
|
@@ -15,6 +15,13 @@ function holdOf(tokens) {
|
|
|
15
15
|
holds.set(tokens, (hold = new TokenHold()));
|
|
16
16
|
return hold;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The token each `MapTokens` object's refresh last brought, and until when a
|
|
20
|
+
* 401 while it is in hand is not news (#72). One per object, beside its hold:
|
|
21
|
+
* a note per helper had each of two helpers handed one object take the
|
|
22
|
+
* other's token as news, and ask again for every refusal.
|
|
23
|
+
*/
|
|
24
|
+
const broughtBy = new WeakMap();
|
|
18
25
|
/**
|
|
19
26
|
* Send with the token in hand, and after a 401 once more with a different one
|
|
20
27
|
* from `refreshToken`, all within the call's signal and deadline (#62). A 403
|
|
@@ -49,8 +56,11 @@ export async function sendWithTokenRefresh(source, noTokenAdvice, call, send) {
|
|
|
49
56
|
*
|
|
50
57
|
* What it learns is held as the fetch helpers hold it, and shared with them
|
|
51
58
|
* when they are handed the same `tokens` object (#38): a token `refreshToken`
|
|
52
|
-
* could not replace,
|
|
53
|
-
*
|
|
59
|
+
* could not replace, a token it brought that the API refused on a tile
|
|
60
|
+
* reloaded with it, and a failure that says when to ask again, are not asked
|
|
61
|
+
* about again until they lapse or the token in hand changes. While the token
|
|
62
|
+
* a refresh brought is in hand, any other refused tile is reloaded once with
|
|
63
|
+
* it, without asking.
|
|
54
64
|
*
|
|
55
65
|
* Returns a function that stops listening.
|
|
56
66
|
*
|
|
@@ -66,16 +76,26 @@ export async function sendWithTokenRefresh(source, noTokenAdvice, call, send) {
|
|
|
66
76
|
export function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
67
77
|
const hold = holdOf(tokens);
|
|
68
78
|
// Each source's refused tiles, keyed `z/x/y` so a tile refused twice is
|
|
69
|
-
// reloaded once.
|
|
79
|
+
// reloaded once. Tiles refused while a hold stands are kept, so the next ask
|
|
80
|
+
// reloads them; MapLibre ignores ids no longer in view.
|
|
70
81
|
const refused = new Map();
|
|
82
|
+
// The token each tile was reloaded with here, keyed `source:z/x/y`.
|
|
83
|
+
const reloadedWith = new Map();
|
|
71
84
|
let asking = false;
|
|
85
|
+
const reload = (sourceId, tiles, token) => {
|
|
86
|
+
for (const { x, y, z } of tiles)
|
|
87
|
+
reloadedWith.set(`${sourceId}:${z}/${x}/${y}`, token);
|
|
88
|
+
map.refreshTiles(sourceId, tiles);
|
|
89
|
+
};
|
|
72
90
|
const onError = ({ error, sourceId, tile }) => {
|
|
73
91
|
if (error?.status !== 401 || !error.url || !isOurApi(error.url, apiUrl))
|
|
74
92
|
return;
|
|
93
|
+
let refusedTile;
|
|
75
94
|
if (sourceId && tile) {
|
|
76
95
|
const { x, y, z } = tile.tileID.canonical;
|
|
77
96
|
const tiles = refused.get(sourceId) ?? new Map();
|
|
78
97
|
refused.set(sourceId, tiles.set(`${z}/${x}/${y}`, { x, y, z }));
|
|
98
|
+
refusedTile = [sourceId, { x, y, z }];
|
|
79
99
|
}
|
|
80
100
|
if (asking)
|
|
81
101
|
return;
|
|
@@ -83,6 +103,32 @@ export function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
83
103
|
const held = hold.check(inHand);
|
|
84
104
|
if (held && !held.askAgain)
|
|
85
105
|
return;
|
|
106
|
+
// A 401 while the token a refresh brought is in hand. The event does not
|
|
107
|
+
// say which token the refused request carried, and a tile requested with
|
|
108
|
+
// the token before, answered late, looks like the new token refused. So
|
|
109
|
+
// only a tile reloaded here with the token in hand is that token refused:
|
|
110
|
+
// the API refuses every token, as it does a map pointed at an API its
|
|
111
|
+
// tokens are not for, and asking again minted a token and reloaded the
|
|
112
|
+
// tiles several times a second. That is remembered, as a send path
|
|
113
|
+
// remembers its retry refused, and the next ask is after the hold. Any
|
|
114
|
+
// other tile is reloaded once with the token in hand; anything else (a
|
|
115
|
+
// glyph, a sprite) asks nothing.
|
|
116
|
+
const brought = broughtBy.get(tokens);
|
|
117
|
+
if (inHand && inHand === brought?.token && Date.now() < brought.until) {
|
|
118
|
+
if (!refusedTile)
|
|
119
|
+
return;
|
|
120
|
+
const [id, coordinates] = refusedTile;
|
|
121
|
+
const key = `${coordinates.z}/${coordinates.x}/${coordinates.y}`;
|
|
122
|
+
if (reloadedWith.get(`${id}:${key}`) !== inHand) {
|
|
123
|
+
refused.get(id)?.delete(key);
|
|
124
|
+
reload(id, [coordinates], inHand);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Kept in `refused`, as every tile refused during the hold is.
|
|
128
|
+
broughtBy.delete(tokens);
|
|
129
|
+
hold.remember(unauthorized('The API refused a tile reloaded with the token refreshToken brought.'), inHand);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
86
132
|
asking = true;
|
|
87
133
|
// Asked inside an executor, so a `refreshToken` that throws instead of
|
|
88
134
|
// rejecting lands in the catch below rather than in MapLibre's emitter.
|
|
@@ -96,15 +142,18 @@ export function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
96
142
|
if (!now || now === inHand) {
|
|
97
143
|
// MapLibre's error carries none of the API's fields, so the refusal
|
|
98
144
|
// is remembered as the 401 it was.
|
|
99
|
-
hold.remember(
|
|
100
|
-
code: 'UnauthorizedException',
|
|
101
|
-
message: 'The API refused the token, and getToken has no other since refreshToken settled.',
|
|
102
|
-
statusCode: 401,
|
|
103
|
-
}), inHand);
|
|
145
|
+
hold.remember(unauthorized('The API refused the token, and getToken has no other since refreshToken settled.'), inHand);
|
|
104
146
|
return;
|
|
105
147
|
}
|
|
148
|
+
broughtBy.set(tokens, {
|
|
149
|
+
token: now,
|
|
150
|
+
until: Date.now() + TOKEN_REFUSAL_HOLD_MS,
|
|
151
|
+
});
|
|
152
|
+
// What was reloaded with an earlier token can no longer match.
|
|
153
|
+
reloadedWith.clear();
|
|
106
154
|
for (const [id, tiles] of refused)
|
|
107
|
-
|
|
155
|
+
if (tiles.size)
|
|
156
|
+
reload(id, [...tiles.values()], now);
|
|
108
157
|
})
|
|
109
158
|
.catch((refusal) => {
|
|
110
159
|
// Only a failure that says when to ask again is remembered; any other
|
|
@@ -120,3 +169,9 @@ export function refreshTokenOnUnauthorized(map, apiUrl, tokens) {
|
|
|
120
169
|
map.on('error', onError);
|
|
121
170
|
return () => map.off('error', onError);
|
|
122
171
|
}
|
|
172
|
+
/** A refusal MapLibre reported, remembered as the 401 it was. */
|
|
173
|
+
const unauthorized = (message) => new LocationServiceException({
|
|
174
|
+
code: 'UnauthorizedException',
|
|
175
|
+
message,
|
|
176
|
+
statusCode: 401,
|
|
177
|
+
});
|
package/package.json
CHANGED