@chaosity/location-client-react 0.6.0 → 0.8.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
CHANGED
|
@@ -61,7 +61,12 @@ function SearchComponent() {
|
|
|
61
61
|
const searchPlaces = async (query: string) => {
|
|
62
62
|
if (!client) return
|
|
63
63
|
const response: SuggestCommandOutput = await client.send(
|
|
64
|
-
new SuggestCommand({
|
|
64
|
+
new SuggestCommand({
|
|
65
|
+
QueryText: query,
|
|
66
|
+
MaxResults: 5,
|
|
67
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
68
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
69
|
+
}),
|
|
65
70
|
)
|
|
66
71
|
return response.ResultItems
|
|
67
72
|
}
|
|
@@ -77,7 +82,7 @@ function SearchComponent() {
|
|
|
77
82
|
|
|
78
83
|
### useMapLanguage
|
|
79
84
|
|
|
80
|
-
React hook that keeps map label language in sync. Automatically reapplies after `map.setStyle()` calls (e.g. when switching color schemes
|
|
85
|
+
React hook that keeps map label language in sync. Automatically reapplies after `map.setStyle()` calls (e.g. when switching color schemes).
|
|
81
86
|
|
|
82
87
|
```tsx
|
|
83
88
|
import { useMapLanguage } from '@chaosity/location-client-react'
|
|
@@ -123,7 +128,8 @@ Provides the location client and automatic token refresh to all child components
|
|
|
123
128
|
|
|
124
129
|
**Props:**
|
|
125
130
|
|
|
126
|
-
- `getConfig` — Async function that returns `{ apiUrl: string, token: string, expiresAt?: number }`. Called on
|
|
131
|
+
- `getConfig` — Async function that returns `{ apiUrl: string, token: string, expiresAt?: number }`. Called on mount, whenever the token needs refreshing, and to retry a call that failed (see [Token Refresh](#token-refresh)).
|
|
132
|
+
- `configKey` — Optional. What `getConfig` answers for, such as an organisation or application id. When it changes, the provider drops the old client, token and `apiUrl`, calls `getConfig` again and hands out a new client, without remounting its children.
|
|
127
133
|
- `children` — Child components.
|
|
128
134
|
|
|
129
135
|
There is no `refreshBuffer` prop. It was removed in `0.3.0` — a value shorter
|
|
@@ -132,20 +138,47 @@ the server would not yet replace, and the two spun against each other. Both
|
|
|
132
138
|
sides now apply the same buffer to the token's own `exp`. Passing it does
|
|
133
139
|
nothing.
|
|
134
140
|
|
|
141
|
+
#### Switching organisation or application
|
|
142
|
+
|
|
143
|
+
Either of these is correct:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
// Keeps the children mounted: only the client is replaced.
|
|
147
|
+
<LocationClientProvider configKey={orgId} getConfig={getLocationConfig}>
|
|
148
|
+
|
|
149
|
+
// Remounts everything below the provider, and its state with it.
|
|
150
|
+
<LocationClientProvider key={orgId} getConfig={getLocationConfig}>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Without either, the provider goes on using the old configuration until its
|
|
154
|
+
next token refresh, up to 14 minutes later. A new `getConfig` function is not
|
|
155
|
+
a signal on its own, because it is usually a new function on every render.
|
|
156
|
+
|
|
157
|
+
After a switch, a client or `getToken` kept from before it refuses: `send()`
|
|
158
|
+
and `verifyAddress()` reject, `getToken()` returns `undefined`, and
|
|
159
|
+
`getAppConfig()` answers `{}`. So one configuration's token never reaches
|
|
160
|
+
another's URL. Build anything that holds
|
|
161
|
+
on to them, such as a MapLibre map or a geocoder, from the context's current
|
|
162
|
+
values, and rebuild it when `client`, `getToken` or `apiUrl` changes, as the
|
|
163
|
+
[complete example](#complete-example-with-maplibre) does. The same happens
|
|
164
|
+
without a `configKey` when `getConfig` starts answering with a different
|
|
165
|
+
`apiUrl`.
|
|
166
|
+
|
|
135
167
|
### useLocationClient
|
|
136
168
|
|
|
137
169
|
Hook to access the location client in any component.
|
|
138
170
|
|
|
139
171
|
```tsx
|
|
140
|
-
const { client, getToken, loading, error } = useLocationClient()
|
|
172
|
+
const { client, getToken, apiUrl, loading, error } = useLocationClient()
|
|
141
173
|
```
|
|
142
174
|
|
|
143
175
|
**Returns:**
|
|
144
176
|
|
|
145
|
-
- `client` (`LocationClient | null`) — The location client. Not a bare `GeoPlacesClient`: the provider wraps it so `send()`
|
|
146
|
-
- `getToken` (`() => string | undefined`) — Returns the current token
|
|
147
|
-
- `
|
|
148
|
-
- `
|
|
177
|
+
- `client` (`LocationClient | null`) — The location client. Not a bare `GeoPlacesClient`: the provider wraps it so `send()` and `verifyAddress()` refresh the token first when they need to, and retry once if the API rejects it.
|
|
178
|
+
- `getToken` (`() => string | undefined`) — Returns the current token, for requests the client does not make itself, such as a map's style, tiles and glyphs. It is available before the first token arrives and stays the same function afterwards, so a map built early reads the token once it lands. It returns `undefined` once `configKey` changes.
|
|
179
|
+
- `apiUrl` (`string | null`) — The API `client` talks to, from the same `getConfig` answer as the token. Build map URLs from it rather than restating the URL. `null` until a configuration has loaded.
|
|
180
|
+
- `loading` (`boolean`) — Whether the client is initializing. `true` again while a new `configKey` loads.
|
|
181
|
+
- `error` (`string | null`) — Error message if initialization or a token refresh failed. The provider keeps retrying, and `error` returns to `null` on the first success (see [Token Refresh](#token-refresh)).
|
|
149
182
|
|
|
150
183
|
**Throws:** Error if used outside `LocationClientProvider`.
|
|
151
184
|
|
|
@@ -158,7 +191,8 @@ The provider owns the token lifecycle. There is nothing to manage manually.
|
|
|
158
191
|
`exp`. This is what keeps a map alive: MapLibre requests tiles, glyphs and
|
|
159
192
|
sprites directly, never through `send()`, so a refresh that happened only
|
|
160
193
|
inside `send()` would never fire for them.
|
|
161
|
-
3. `send()`
|
|
194
|
+
3. `send()` and `verifyAddress()` check too, and refresh first if the token is
|
|
195
|
+
inside that window.
|
|
162
196
|
4. Returning to a backgrounded tab refreshes immediately — a throttled tab's
|
|
163
197
|
timer can be arbitrarily late.
|
|
164
198
|
5. If the API rejects a token **before** its `exp` — revoked from the portal, or
|
|
@@ -166,15 +200,50 @@ The provider owns the token lifecycle. There is nothing to manage manually.
|
|
|
166
200
|
the request is retried once with the new token. Nothing on this side has any
|
|
167
201
|
other reason to replace that token, so without this the failures continue
|
|
168
202
|
until the timer next comes around: for a token with 14 minutes left, 14
|
|
169
|
-
minutes of a broken page.
|
|
170
|
-
|
|
203
|
+
minutes of a broken page. The core added this in 0.7.0, so every core this
|
|
204
|
+
package's peer range admits has it.
|
|
171
205
|
6. Concurrent refreshes are deduplicated — everything waiting shares one call to
|
|
172
206
|
`getConfig`.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
7. A failed `getConfig` is retried on the provider's own timer, backing off
|
|
208
|
+
exponentially from 1 to 30 seconds, with jitter. When `getConfig` rejects
|
|
209
|
+
with an error that has `retryAfterMs` (milliseconds), it waits that long
|
|
210
|
+
instead. Until then nothing else asks, neither a map reading its token for
|
|
211
|
+
every tile nor a `send()`. The tab coming back into view, or the browser
|
|
212
|
+
coming back online, retries at once, unless the wait was a `retryAfterMs`.
|
|
213
|
+
|
|
214
|
+
A Server Action, as in the Quick Start, cannot pass `retryAfterMs` on:
|
|
215
|
+
React sends a thrown error to the browser without its own fields. To honour
|
|
216
|
+
a token route's `Retry-After`, call the route from the browser and throw it
|
|
217
|
+
there:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
async function getConfig() {
|
|
221
|
+
const res = await fetch('/api/location-token')
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
const retryAfter = Number(res.headers.get('retry-after'))
|
|
224
|
+
throw Object.assign(new Error(`token route answered ${res.status}`), {
|
|
225
|
+
retryAfterMs: retryAfter > 0 ? retryAfter * 1000 : undefined,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
return res.json()
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
8. That includes the first call. A page whose first `getConfig` fails gets its
|
|
233
|
+
client when a retry succeeds, without a reload.
|
|
234
|
+
9. A token that arrives already stale by the browser's clock, because that
|
|
235
|
+
clock runs ahead of the server's, is not asked for again at once: the
|
|
236
|
+
server would hand back the same one. The provider waits, longer each time,
|
|
237
|
+
and sends with the token it has. A 401 still replaces it straight away.
|
|
238
|
+
|
|
239
|
+
A refresh that fails is reported as `error` from `useLocationClient()` until a
|
|
240
|
+
retry succeeds. Meanwhile every request goes out with the token in hand for as
|
|
241
|
+
long as that token is before its own expiry: `send()`, `verifyAddress()` and a
|
|
242
|
+
map's tiles alike, because the API still accepts it. Once it has expired,
|
|
243
|
+
`send()` and `verifyAddress()` reject with the refresh error rather than a 401,
|
|
244
|
+
so the cause reads as the token endpoint being unreachable, not as the API
|
|
245
|
+
refusing you. A token the API refuses (a 401) is never sent again: that
|
|
246
|
+
request rejects with the refresh error too.
|
|
178
247
|
|
|
179
248
|
## Complete Example with MapLibre
|
|
180
249
|
|
|
@@ -194,26 +263,25 @@ import {
|
|
|
194
263
|
import maplibregl from 'maplibre-gl'
|
|
195
264
|
import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
|
|
196
265
|
|
|
197
|
-
const API_URL = process.env.NEXT_PUBLIC_LOCATION_API_URL!
|
|
198
|
-
|
|
199
266
|
export default function MapComponent() {
|
|
200
267
|
const mapContainer = useRef<HTMLDivElement>(null)
|
|
201
268
|
const map = useRef<maplibregl.Map | null>(null)
|
|
202
269
|
const [mapInstance, setMapInstance] = useState<maplibregl.Map | null>(null)
|
|
270
|
+
const [mapError, setMapError] = useState<string | null>(null)
|
|
203
271
|
const [language, setLanguage] = useState('en')
|
|
204
|
-
const { client, getToken, loading, error } = useLocationClient()
|
|
272
|
+
const { client, getToken, apiUrl, loading, error } = useLocationClient()
|
|
205
273
|
|
|
206
274
|
// Keeps map labels in sync with language — reapplies after every setStyle() call
|
|
207
275
|
useMapLanguage(mapInstance, language)
|
|
208
276
|
|
|
209
277
|
useEffect(() => {
|
|
210
|
-
if (!mapContainer.current || map.current || loading || !client)
|
|
278
|
+
if (!mapContainer.current || map.current || loading || !client || !apiUrl)
|
|
279
|
+
return
|
|
211
280
|
;(async () => {
|
|
212
|
-
// Fetch style with
|
|
213
|
-
|
|
281
|
+
// Fetch the style with the language baked into the descriptor. The URL
|
|
282
|
+
// comes from the context with the token, so the two always belong together.
|
|
283
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
214
284
|
colorScheme: 'Light',
|
|
215
|
-
terrain: 'Terrain3D',
|
|
216
|
-
buildings: 'Buildings3D',
|
|
217
285
|
language,
|
|
218
286
|
})
|
|
219
287
|
|
|
@@ -222,18 +290,15 @@ export default function MapComponent() {
|
|
|
222
290
|
style,
|
|
223
291
|
center: [-123.12, 49.28],
|
|
224
292
|
zoom: 10,
|
|
225
|
-
|
|
226
|
-
transformRequest: createTransformRequest(API_URL, getToken),
|
|
293
|
+
transformRequest: createTransformRequest(apiUrl, getToken),
|
|
227
294
|
})
|
|
295
|
+
// Held at once, so the catch and the cleanup below can remove it
|
|
296
|
+
map.current = instance
|
|
228
297
|
|
|
229
298
|
instance.addControl(
|
|
230
299
|
new maplibregl.NavigationControl({ visualizePitch: true }),
|
|
231
300
|
'top-right',
|
|
232
301
|
)
|
|
233
|
-
instance.addControl(
|
|
234
|
-
new maplibregl.TerrainControl({ source: 'amazon' }),
|
|
235
|
-
'top-right',
|
|
236
|
-
)
|
|
237
302
|
|
|
238
303
|
const geoPlaces = new GeoPlaces(client, instance)
|
|
239
304
|
const geocoder = new MaplibreGeocoder(geoPlaces, {
|
|
@@ -243,9 +308,15 @@ export default function MapComponent() {
|
|
|
243
308
|
})
|
|
244
309
|
instance.addControl(geocoder, 'top-left')
|
|
245
310
|
|
|
246
|
-
map.current = instance
|
|
247
311
|
setMapInstance(instance)
|
|
248
|
-
})()
|
|
312
|
+
})().catch((err: unknown) => {
|
|
313
|
+
// A refused style request lands here, and its message says why — for
|
|
314
|
+
// an option outside the application's plan, it names the feature.
|
|
315
|
+
// Anything that failed after the map was built removes it too.
|
|
316
|
+
map.current?.remove()
|
|
317
|
+
map.current = null
|
|
318
|
+
setMapError(err instanceof Error ? err.message : String(err))
|
|
319
|
+
})
|
|
249
320
|
|
|
250
321
|
return () => {
|
|
251
322
|
if (map.current) {
|
|
@@ -254,15 +325,39 @@ export default function MapComponent() {
|
|
|
254
325
|
setMapInstance(null)
|
|
255
326
|
}
|
|
256
327
|
}
|
|
257
|
-
}, [client, getToken, loading])
|
|
328
|
+
}, [client, getToken, apiUrl, loading])
|
|
258
329
|
|
|
259
330
|
if (error) return <div>Error: {error}</div>
|
|
331
|
+
if (mapError) return <div>Map unavailable: {mapError}</div>
|
|
260
332
|
if (loading) return <div>Loading map...</div>
|
|
261
333
|
|
|
262
334
|
return <div ref={mapContainer} style={{ width: '100%', height: '600px' }} />
|
|
263
335
|
}
|
|
264
336
|
```
|
|
265
337
|
|
|
338
|
+
Some map options are features of the application's plan, and a plan without
|
|
339
|
+
one refuses the style request with 403 `FeatureNotEntitledException`, which the
|
|
340
|
+
`catch` above puts on screen. 3D terrain and buildings need the `terrain` and `buildings` plan features:
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
const style = await fetchMapStyle(apiUrl, 'Standard', getToken, {
|
|
344
|
+
colorScheme: 'Light',
|
|
345
|
+
terrain: 'Terrain3D',
|
|
346
|
+
buildings: 'Buildings3D',
|
|
347
|
+
language,
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// …then `maxPitch: 85` on the map, and a control to toggle the terrain. The
|
|
351
|
+
// descriptor names its own elevation source, so read it rather than typing it:
|
|
352
|
+
instance.addControl(
|
|
353
|
+
new maplibregl.TerrainControl({ source: style.terrain!.source }),
|
|
354
|
+
'top-right',
|
|
355
|
+
)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
`@chaosity/location-client`'s README lists every plan feature and what asks
|
|
359
|
+
for it.
|
|
360
|
+
|
|
266
361
|
### useMapLanguage
|
|
267
362
|
|
|
268
363
|
Hook that keeps map labels in the specified language. Registers a persistent `style.load` listener so language is automatically reapplied after `map.setStyle()` calls.
|
|
@@ -296,13 +391,62 @@ function MyComponent() {
|
|
|
296
391
|
|
|
297
392
|
const search = async () => {
|
|
298
393
|
const response: SuggestCommandOutput = await client!.send(
|
|
299
|
-
new SuggestCommand({
|
|
394
|
+
new SuggestCommand({
|
|
395
|
+
QueryText: 'Vancouver',
|
|
396
|
+
MaxResults: 5,
|
|
397
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
398
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
399
|
+
}),
|
|
300
400
|
)
|
|
301
401
|
return response.ResultItems
|
|
302
402
|
}
|
|
303
403
|
}
|
|
304
404
|
```
|
|
305
405
|
|
|
406
|
+
## Verifying an address
|
|
407
|
+
|
|
408
|
+
`POST /address/verify` resolves the PlaceId a person chose — a building, or a
|
|
409
|
+
unit from its `SecondaryAddresses` — to the full place record plus `verified`.
|
|
410
|
+
It is the one Places result you may store; the core package's README has the
|
|
411
|
+
whole flow. **Needs `@chaosity/location-client` 0.10.0 or later**, which is this
|
|
412
|
+
package's peer range from the release that adds `verifyAddress`.
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
import { useLocationClient } from '@chaosity/location-client-react'
|
|
416
|
+
|
|
417
|
+
function useVerifyOnSubmit() {
|
|
418
|
+
const { client } = useLocationClient()
|
|
419
|
+
|
|
420
|
+
// Call it from the submit handler, once per chosen PlaceId.
|
|
421
|
+
return async (placeId: string) => {
|
|
422
|
+
const answer = await client!.verifyAddress(placeId)
|
|
423
|
+
return answer.verified ? answer : undefined // `answer` is what you may keep
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The same request as a command, through `send`:
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
import {
|
|
432
|
+
VerifyAddressCommand,
|
|
433
|
+
type VerifyAddressResponse,
|
|
434
|
+
} from '@chaosity/location-client'
|
|
435
|
+
|
|
436
|
+
const answer: VerifyAddressResponse = await client!.send(
|
|
437
|
+
new VerifyAddressCommand({ PlaceId: placeId }),
|
|
438
|
+
)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Either form goes through the provider's pre-send refresh and its 401 retry,
|
|
442
|
+
exactly as `send` does. A `verified: false` resolves; it is not an error.
|
|
443
|
+
|
|
444
|
+
**Every verify is billed, whether or not the address verifies.** That is why
|
|
445
|
+
there is no `useVerifyAddress` hook. A hook keyed on a PlaceId would call on
|
|
446
|
+
every pick, including picks nobody submits, so the call belongs in your submit
|
|
447
|
+
handler instead. Keeping one answer per PlaceId is up to you: a verify result
|
|
448
|
+
may be stored, so keep it for as long as your form lives.
|
|
449
|
+
|
|
306
450
|
## Logging
|
|
307
451
|
|
|
308
452
|
Enable debug logging with the `DEBUG` environment variable:
|
|
@@ -323,7 +467,11 @@ import {
|
|
|
323
467
|
|
|
324
468
|
const { client } = useLocationClient()
|
|
325
469
|
const response: SuggestCommandOutput = await client!.send(
|
|
326
|
-
new SuggestCommand({
|
|
470
|
+
new SuggestCommand({
|
|
471
|
+
QueryText: 'Vancouver',
|
|
472
|
+
// Suggest takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
|
|
473
|
+
BiasPosition: [-123.1207, 49.2827],
|
|
474
|
+
}),
|
|
327
475
|
)
|
|
328
476
|
```
|
|
329
477
|
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type { ClientConfig } from '@chaosity/location-client';
|
|
1
|
+
import type { ClientConfig, VerifyAddressResponse } from '@chaosity/location-client';
|
|
2
2
|
import { type AppConfigClaims } from '@chaosity/location-client';
|
|
3
3
|
import type { ReactNode } from 'react';
|
|
4
4
|
/**
|
|
5
5
|
* Per-request transport options.
|
|
6
6
|
*
|
|
7
|
-
* Declared structurally rather than imported
|
|
8
|
-
*
|
|
7
|
+
* Declared structurally rather than imported, like `LocationClient` below. A
|
|
8
|
+
* hand copy does not move when the core does — `overallTimeoutMs` reached the
|
|
9
|
+
* core in 0.8.0 and not this copy (#26) — so `test/core-surface.test.ts` fails
|
|
10
|
+
* while the core's `SendOptions` has a key this one lacks.
|
|
9
11
|
*/
|
|
10
12
|
export interface SendOptions {
|
|
13
|
+
/** Caller cancellation. Aborting rejects with code `AbortedException`. */
|
|
11
14
|
signal?: AbortSignal;
|
|
15
|
+
/** Per ATTEMPT, not for the whole call. */
|
|
12
16
|
timeoutMs?: number;
|
|
17
|
+
/** The whole call: attempts and the waits between them. Core 0.8.0+. */
|
|
18
|
+
overallTimeoutMs?: number;
|
|
19
|
+
/** `false` disables retries entirely. */
|
|
13
20
|
retry?: false | {
|
|
14
21
|
maxAttempts?: number;
|
|
15
22
|
};
|
|
@@ -21,6 +28,9 @@ export interface SendOptions {
|
|
|
21
28
|
* real client to refresh tokens first, and a class with private fields is not
|
|
22
29
|
* structurally assignable — which is why this used to be an `Object.create`
|
|
23
30
|
* prototype hack.
|
|
31
|
+
*
|
|
32
|
+
* It must carry every public member of the core client this package builds
|
|
33
|
+
* with: `test/core-surface.test.ts` fails when the core gains one (#26).
|
|
24
34
|
*/
|
|
25
35
|
export interface LocationClient {
|
|
26
36
|
readonly config: {
|
|
@@ -28,27 +38,49 @@ export interface LocationClient {
|
|
|
28
38
|
};
|
|
29
39
|
send<TInput, TOutput>(command: TInput, options?: SendOptions): Promise<TOutput>;
|
|
30
40
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
41
|
+
* Verify a PlaceId through `POST /address/verify`: the full place record plus
|
|
42
|
+
* `verified`, the one Places result an integrator may store (#26). A
|
|
43
|
+
* `verified: false` resolves; it is not an error. Refreshes a stale token
|
|
44
|
+
* first, as `send` does.
|
|
45
|
+
*
|
|
46
|
+
* Needs @chaosity/location-client 0.10.0 or later, the peer range's floor.
|
|
47
|
+
* `send(new VerifyAddressCommand({ PlaceId }))` is the same request.
|
|
48
|
+
*
|
|
49
|
+
* Billed per call, whether or not the address verifies: call it once per
|
|
50
|
+
* chosen PlaceId, at submit.
|
|
51
|
+
*/
|
|
52
|
+
verifyAddress(placeId: string, options?: SendOptions): Promise<VerifyAddressResponse>;
|
|
53
|
+
/**
|
|
54
|
+
* This application's own configuration, read from the access token
|
|
55
|
+
* (api#65). The fields are whatever the installed @chaosity/location-client
|
|
56
|
+
* reads — its `AppConfigClaims` is the list, and this passes that client's
|
|
57
|
+
* answer through unchanged. So a field the core adds appears here with no
|
|
58
|
+
* change to this package, and is absent under a core too old to read it;
|
|
59
|
+
* the peer range cannot say which.
|
|
33
60
|
*
|
|
34
61
|
* Here so a React app can SHOW its own settings: populate a country
|
|
35
62
|
* selector with the markets it serves, label a settings screen. Being a
|
|
36
63
|
* few minutes stale is cosmetic for that.
|
|
37
64
|
*
|
|
38
|
-
* It is not an entitlement check, and
|
|
39
|
-
*
|
|
40
|
-
* fresh from the application
|
|
41
|
-
* turns a request that would have succeeded into a 400. See
|
|
65
|
+
* It is not an entitlement check, and none of it may be used to shape or
|
|
66
|
+
* refuse requests. The token is a snapshot; the API reads every setting
|
|
67
|
+
* fresh from the application on every call. Injecting a stale country
|
|
68
|
+
* scope turns a request that would have succeeded into a 400. See
|
|
42
69
|
* `AppConfigClaims` in @chaosity/location-client for the measurement.
|
|
43
|
-
*
|
|
44
|
-
* Returns `{}` when the token carries no application config, which is the
|
|
45
|
-
* case until one is set in the portal.
|
|
46
70
|
*/
|
|
47
71
|
getAppConfig(): AppConfigClaims;
|
|
48
72
|
}
|
|
49
73
|
interface LocationClientContextValue {
|
|
50
74
|
client: LocationClient | null;
|
|
51
75
|
getToken: () => string | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The API `client` talks to, from the same `getConfig` answer as the token
|
|
78
|
+
* `getToken` returns (#14). Build a map's style and tile URLs from this
|
|
79
|
+
* rather than restating the URL, so a map and its token cannot come from two
|
|
80
|
+
* different configurations. `null` until a configuration has loaded, and
|
|
81
|
+
* again while a new `configKey` loads.
|
|
82
|
+
*/
|
|
83
|
+
apiUrl: string | null;
|
|
52
84
|
loading: boolean;
|
|
53
85
|
error: string | null;
|
|
54
86
|
}
|
|
@@ -57,7 +89,20 @@ export interface LocationClientProviderProps {
|
|
|
57
89
|
getConfig: () => Promise<ClientConfig & {
|
|
58
90
|
expiresAt?: number;
|
|
59
91
|
}>;
|
|
92
|
+
/**
|
|
93
|
+
* What `getConfig` answers for: an organisation or application id.
|
|
94
|
+
*
|
|
95
|
+
* Changing it drops the old configuration's token, client and `apiUrl` in
|
|
96
|
+
* the same render, and asks `getConfig` again, without remounting the
|
|
97
|
+
* children (#14). A client or a `getToken` kept from before the change
|
|
98
|
+
* refuses from then on, rather than pairing one configuration's token with
|
|
99
|
+
* the other's URL.
|
|
100
|
+
*
|
|
101
|
+
* `getConfig`'s own identity is deliberately not such a signal: it is
|
|
102
|
+
* usually an inline function, new on every render.
|
|
103
|
+
*/
|
|
104
|
+
configKey?: string | number;
|
|
60
105
|
}
|
|
61
|
-
export declare function LocationClientProvider({ children, getConfig, }: LocationClientProviderProps): import("react").JSX.Element;
|
|
106
|
+
export declare function LocationClientProvider({ children, getConfig, configKey, }: LocationClientProviderProps): import("react").JSX.Element;
|
|
62
107
|
export declare function useLocationClient(): LocationClientContextValue;
|
|
63
108
|
export {};
|