@chaosity/location-client-react 0.5.0 → 0.6.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
|
@@ -116,7 +116,7 @@ function MapComponent() {
|
|
|
116
116
|
Provides the location client and automatic token refresh to all child components.
|
|
117
117
|
|
|
118
118
|
```tsx
|
|
119
|
-
<LocationClientProvider getConfig={getLocationConfig}
|
|
119
|
+
<LocationClientProvider getConfig={getLocationConfig}>
|
|
120
120
|
{children}
|
|
121
121
|
</LocationClientProvider>
|
|
122
122
|
```
|
|
@@ -124,9 +124,14 @@ Provides the location client and automatic token refresh to all child components
|
|
|
124
124
|
**Props:**
|
|
125
125
|
|
|
126
126
|
- `getConfig` — Async function that returns `{ apiUrl: string, token: string, expiresAt?: number }`. Called on init and whenever the token needs refreshing.
|
|
127
|
-
- `refreshBuffer` (optional, default: `60`) — Seconds before token expiry to proactively refresh. Prevents mid-request expiration.
|
|
128
127
|
- `children` — Child components.
|
|
129
128
|
|
|
129
|
+
There is no `refreshBuffer` prop. It was removed in `0.3.0` — a value shorter
|
|
130
|
+
than the server's own re-mint window made the client judge a token stale that
|
|
131
|
+
the server would not yet replace, and the two spun against each other. Both
|
|
132
|
+
sides now apply the same buffer to the token's own `exp`. Passing it does
|
|
133
|
+
nothing.
|
|
134
|
+
|
|
130
135
|
### useLocationClient
|
|
131
136
|
|
|
132
137
|
Hook to access the location client in any component.
|
|
@@ -137,7 +142,7 @@ const { client, getToken, loading, error } = useLocationClient()
|
|
|
137
142
|
|
|
138
143
|
**Returns:**
|
|
139
144
|
|
|
140
|
-
- `client` (`
|
|
145
|
+
- `client` (`LocationClient | null`) — The location client. Not a bare `GeoPlacesClient`: the provider wraps it so `send()` refreshes the token first when it needs to, and retries once if the API rejects it.
|
|
141
146
|
- `getToken` (`() => string | undefined`) — Returns the current token. Useful for direct API calls (e.g., map style fetch).
|
|
142
147
|
- `loading` (`boolean`) — Whether the client is initializing.
|
|
143
148
|
- `error` (`string | null`) — Error message if initialization or token refresh failed.
|
|
@@ -146,14 +151,30 @@ const { client, getToken, loading, error } = useLocationClient()
|
|
|
146
151
|
|
|
147
152
|
## Token Refresh
|
|
148
153
|
|
|
149
|
-
The provider
|
|
150
|
-
|
|
151
|
-
1.
|
|
152
|
-
2.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
The provider owns the token lifecycle. There is nothing to manage manually.
|
|
155
|
+
|
|
156
|
+
1. `getConfig` is called on mount for the initial token.
|
|
157
|
+
2. A timer refreshes **ahead of expiry**, 60 seconds before the token's own
|
|
158
|
+
`exp`. This is what keeps a map alive: MapLibre requests tiles, glyphs and
|
|
159
|
+
sprites directly, never through `send()`, so a refresh that happened only
|
|
160
|
+
inside `send()` would never fire for them.
|
|
161
|
+
3. `send()` checks too, and refreshes first if the token is inside that window.
|
|
162
|
+
4. Returning to a backgrounded tab refreshes immediately — a throttled tab's
|
|
163
|
+
timer can be arbitrarily late.
|
|
164
|
+
5. If the API rejects a token **before** its `exp` — revoked from the portal, or
|
|
165
|
+
minted against a client secret since rotated — the 401 triggers a refresh and
|
|
166
|
+
the request is retried once with the new token. Nothing on this side has any
|
|
167
|
+
other reason to replace that token, so without this the failures continue
|
|
168
|
+
until the timer next comes around: for a token with 14 minutes left, 14
|
|
169
|
+
minutes of a broken page. Needs `@chaosity/location-client` 0.7.0 or later;
|
|
170
|
+
on older versions the other five steps still work.
|
|
171
|
+
6. Concurrent refreshes are deduplicated — everything waiting shares one call to
|
|
172
|
+
`getConfig`.
|
|
173
|
+
|
|
174
|
+
A refresh that fails is reported as `error` from `useLocationClient()`, and
|
|
175
|
+
rejects the `send()` that triggered it — with the refresh error rather than a
|
|
176
|
+
401, so the cause reads as the token endpoint being unreachable and not as the
|
|
177
|
+
API refusing you.
|
|
157
178
|
|
|
158
179
|
## Complete Example with MapLibre
|
|
159
180
|
|
|
@@ -166,6 +166,28 @@ function LocationClientProvider({ children, getConfig, }) {
|
|
|
166
166
|
apiUrl: cfg.apiUrl,
|
|
167
167
|
token: cfg.token,
|
|
168
168
|
getToken,
|
|
169
|
+
/**
|
|
170
|
+
* The 401 escape hatch (#19).
|
|
171
|
+
*
|
|
172
|
+
* Covers what the timer cannot: a token revoked from the portal, or
|
|
173
|
+
* minted against a client secret since rotated, is refused by the API
|
|
174
|
+
* while still minutes from its own `exp` — so nothing on this side has
|
|
175
|
+
* any reason to replace it, and every request fails until the buffer
|
|
176
|
+
* finally comes around. `getToken` cannot help, being synchronous.
|
|
177
|
+
*
|
|
178
|
+
* The client awaits this after a 401 and retries the request once with
|
|
179
|
+
* what it returns; the same token, or nothing, means no retry, so a
|
|
180
|
+
* doomed request is never sent — or billed — twice.
|
|
181
|
+
*
|
|
182
|
+
* It REJECTS when the refresh itself fails, and that is left to
|
|
183
|
+
* propagate out of `send` deliberately: the consumer learns the token
|
|
184
|
+
* endpoint is down rather than being told the API rejected them. Same
|
|
185
|
+
* answer the pre-flight `ensureValidToken` path already gives.
|
|
186
|
+
*/
|
|
187
|
+
refreshToken: async () => {
|
|
188
|
+
await refreshToken();
|
|
189
|
+
return tokenRef.current;
|
|
190
|
+
},
|
|
169
191
|
});
|
|
170
192
|
// A plain object, not Object.create(baseClient): the prototype hack was
|
|
171
193
|
// opaque, and its `send` dropped the second argument entirely — so once
|
|
@@ -203,7 +225,7 @@ function LocationClientProvider({ children, getConfig, }) {
|
|
|
203
225
|
if (timerRef.current)
|
|
204
226
|
clearTimeout(timerRef.current);
|
|
205
227
|
};
|
|
206
|
-
}, [getToken]);
|
|
228
|
+
}, [getToken, refreshToken]);
|
|
207
229
|
return ((0, jsx_runtime_1.jsx)(LocationClientContext.Provider, { value: { client, getToken, loading, error }, children: children }));
|
|
208
230
|
}
|
|
209
231
|
function useLocationClient() {
|
|
@@ -159,6 +159,28 @@ export function LocationClientProvider({ children, getConfig, }) {
|
|
|
159
159
|
apiUrl: cfg.apiUrl,
|
|
160
160
|
token: cfg.token,
|
|
161
161
|
getToken,
|
|
162
|
+
/**
|
|
163
|
+
* The 401 escape hatch (#19).
|
|
164
|
+
*
|
|
165
|
+
* Covers what the timer cannot: a token revoked from the portal, or
|
|
166
|
+
* minted against a client secret since rotated, is refused by the API
|
|
167
|
+
* while still minutes from its own `exp` — so nothing on this side has
|
|
168
|
+
* any reason to replace it, and every request fails until the buffer
|
|
169
|
+
* finally comes around. `getToken` cannot help, being synchronous.
|
|
170
|
+
*
|
|
171
|
+
* The client awaits this after a 401 and retries the request once with
|
|
172
|
+
* what it returns; the same token, or nothing, means no retry, so a
|
|
173
|
+
* doomed request is never sent — or billed — twice.
|
|
174
|
+
*
|
|
175
|
+
* It REJECTS when the refresh itself fails, and that is left to
|
|
176
|
+
* propagate out of `send` deliberately: the consumer learns the token
|
|
177
|
+
* endpoint is down rather than being told the API rejected them. Same
|
|
178
|
+
* answer the pre-flight `ensureValidToken` path already gives.
|
|
179
|
+
*/
|
|
180
|
+
refreshToken: async () => {
|
|
181
|
+
await refreshToken();
|
|
182
|
+
return tokenRef.current;
|
|
183
|
+
},
|
|
162
184
|
});
|
|
163
185
|
// A plain object, not Object.create(baseClient): the prototype hack was
|
|
164
186
|
// opaque, and its `send` dropped the second argument entirely — so once
|
|
@@ -196,7 +218,7 @@ export function LocationClientProvider({ children, getConfig, }) {
|
|
|
196
218
|
if (timerRef.current)
|
|
197
219
|
clearTimeout(timerRef.current);
|
|
198
220
|
};
|
|
199
|
-
}, [getToken]);
|
|
221
|
+
}, [getToken, refreshToken]);
|
|
200
222
|
return (_jsx(LocationClientContext.Provider, { value: { client, getToken, loading, error }, children: children }));
|
|
201
223
|
}
|
|
202
224
|
export function useLocationClient() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaosity/location-client-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React bindings for Chaosity Location Service client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@chaosity/location-client": "^0.
|
|
51
|
+
"@chaosity/location-client": "^0.7.0",
|
|
52
52
|
"@eslint/js": "^10.0.1",
|
|
53
53
|
"@testing-library/react": "^16.0.0",
|
|
54
54
|
"@testing-library/user-event": "^14.0.0",
|