@chaosity/location-client-react 0.1.17 → 0.1.18
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 +27 -13
- package/dist/hooks/useMapLanguage.d.ts +1 -10
- package/dist/hooks/useMapLanguage.js +1 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/provider/LocationClientProvider.d.ts +3 -2
- package/dist/provider/LocationClientProvider.js +12 -7
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -33,7 +33,11 @@ export async function getLocationConfig() {
|
|
|
33
33
|
import { LocationClientProvider } from '@chaosity/location-client-react'
|
|
34
34
|
import { getLocationConfig } from './actions/location'
|
|
35
35
|
|
|
36
|
-
export default function RootLayout({
|
|
36
|
+
export default function RootLayout({
|
|
37
|
+
children,
|
|
38
|
+
}: {
|
|
39
|
+
children: React.ReactNode
|
|
40
|
+
}) {
|
|
37
41
|
return (
|
|
38
42
|
<LocationClientProvider getConfig={getLocationConfig}>
|
|
39
43
|
{children}
|
|
@@ -54,7 +58,7 @@ function SearchComponent() {
|
|
|
54
58
|
const searchPlaces = async (query: string) => {
|
|
55
59
|
if (!client) return
|
|
56
60
|
const response = await client.send(
|
|
57
|
-
new SuggestCommand({ QueryText: query, MaxResults: 5 })
|
|
61
|
+
new SuggestCommand({ QueryText: query, MaxResults: 5 }),
|
|
58
62
|
)
|
|
59
63
|
return response.ResultItems
|
|
60
64
|
}
|
|
@@ -83,7 +87,9 @@ function MapComponent() {
|
|
|
83
87
|
useMapLanguage(mapInstance, language)
|
|
84
88
|
|
|
85
89
|
useEffect(() => {
|
|
86
|
-
const map = new maplibregl.Map({
|
|
90
|
+
const map = new maplibregl.Map({
|
|
91
|
+
/* ... */
|
|
92
|
+
})
|
|
87
93
|
map.once('load', () => setMapInstance(map))
|
|
88
94
|
return () => map.remove()
|
|
89
95
|
}, [])
|
|
@@ -109,15 +115,13 @@ function MapComponent() {
|
|
|
109
115
|
Provides the location client and automatic token refresh to all child components.
|
|
110
116
|
|
|
111
117
|
```tsx
|
|
112
|
-
<LocationClientProvider
|
|
113
|
-
getConfig={getLocationConfig}
|
|
114
|
-
refreshBuffer={60}
|
|
115
|
-
>
|
|
118
|
+
<LocationClientProvider getConfig={getLocationConfig} refreshBuffer={60}>
|
|
116
119
|
{children}
|
|
117
120
|
</LocationClientProvider>
|
|
118
121
|
```
|
|
119
122
|
|
|
120
123
|
**Props:**
|
|
124
|
+
|
|
121
125
|
- `getConfig` — Async function that returns `{ apiUrl: string, token: string, expiresAt?: number }`. Called on init and whenever the token needs refreshing.
|
|
122
126
|
- `refreshBuffer` (optional, default: `60`) — Seconds before token expiry to proactively refresh. Prevents mid-request expiration.
|
|
123
127
|
- `children` — Child components.
|
|
@@ -131,6 +135,7 @@ const { client, getToken, loading, error } = useLocationClient()
|
|
|
131
135
|
```
|
|
132
136
|
|
|
133
137
|
**Returns:**
|
|
138
|
+
|
|
134
139
|
- `client` (`GeoPlacesClient | null`) — The location client instance. Automatically refreshes the token before each `send()` call if needed.
|
|
135
140
|
- `getToken` (`() => string | undefined`) — Returns the current token. Useful for direct API calls (e.g., map style fetch).
|
|
136
141
|
- `loading` (`boolean`) — Whether the client is initializing.
|
|
@@ -155,7 +160,10 @@ No manual token management needed. The `client` always uses a valid token.
|
|
|
155
160
|
'use client'
|
|
156
161
|
|
|
157
162
|
import { useEffect, useRef, useState } from 'react'
|
|
158
|
-
import {
|
|
163
|
+
import {
|
|
164
|
+
useLocationClient,
|
|
165
|
+
useMapLanguage,
|
|
166
|
+
} from '@chaosity/location-client-react'
|
|
159
167
|
import {
|
|
160
168
|
GeoPlaces,
|
|
161
169
|
fetchMapStyle,
|
|
@@ -178,7 +186,6 @@ export default function MapComponent() {
|
|
|
178
186
|
|
|
179
187
|
useEffect(() => {
|
|
180
188
|
if (!mapContainer.current || map.current || loading || !client) return
|
|
181
|
-
|
|
182
189
|
;(async () => {
|
|
183
190
|
// Fetch style with terrain, 3D buildings, and language baked into the descriptor
|
|
184
191
|
const style = await fetchMapStyle(API_URL, 'Standard', getToken, {
|
|
@@ -197,8 +204,14 @@ export default function MapComponent() {
|
|
|
197
204
|
transformRequest: createTransformRequest(API_URL, getToken),
|
|
198
205
|
})
|
|
199
206
|
|
|
200
|
-
instance.addControl(
|
|
201
|
-
|
|
207
|
+
instance.addControl(
|
|
208
|
+
new maplibregl.NavigationControl({ visualizePitch: true }),
|
|
209
|
+
'top-right',
|
|
210
|
+
)
|
|
211
|
+
instance.addControl(
|
|
212
|
+
new maplibregl.TerrainControl({ source: 'amazon' }),
|
|
213
|
+
'top-right',
|
|
214
|
+
)
|
|
202
215
|
|
|
203
216
|
const geoPlaces = new GeoPlaces(client, instance)
|
|
204
217
|
const geocoder = new MaplibreGeocoder(geoPlaces, {
|
|
@@ -237,6 +250,7 @@ useMapLanguage(map: MapLike | null, language: string): void
|
|
|
237
250
|
```
|
|
238
251
|
|
|
239
252
|
**Parameters:**
|
|
253
|
+
|
|
240
254
|
- `map` — MapLibre Map instance, or `null` while the map is initializing.
|
|
241
255
|
- `language` — ISO 639-1 language code (e.g. `'en'`, `'fr'`, `'de'`, `'ja'`, `'zh'`, `'ar'`).
|
|
242
256
|
|
|
@@ -259,7 +273,7 @@ function MyComponent() {
|
|
|
259
273
|
|
|
260
274
|
const search = async () => {
|
|
261
275
|
const response = await client!.send(
|
|
262
|
-
new SuggestCommand({ QueryText: 'Vancouver', MaxResults: 5 })
|
|
276
|
+
new SuggestCommand({ QueryText: 'Vancouver', MaxResults: 5 }),
|
|
263
277
|
)
|
|
264
278
|
return response.ResultItems
|
|
265
279
|
}
|
|
@@ -283,7 +297,7 @@ import type { SuggestCommandOutput } from '@aws-sdk/client-geo-places'
|
|
|
283
297
|
|
|
284
298
|
const { client } = useLocationClient()
|
|
285
299
|
const response: SuggestCommandOutput = await client!.send(
|
|
286
|
-
new SuggestCommand({ QueryText: 'Vancouver' })
|
|
300
|
+
new SuggestCommand({ QueryText: 'Vancouver' }),
|
|
287
301
|
)
|
|
288
302
|
```
|
|
289
303
|
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Minimal interface describing the MapLibre Map methods used by this hook.
|
|
3
|
-
* Using a structural type avoids maplibre-gl version conflicts between packages.
|
|
4
|
-
*/
|
|
5
|
-
interface MapLike {
|
|
6
|
-
isStyleLoaded(): boolean | void;
|
|
7
|
-
on(event: string, listener: (...args: unknown[]) => void): void;
|
|
8
|
-
off(event: string, listener: (...args: unknown[]) => void): void;
|
|
9
|
-
}
|
|
1
|
+
import type { MapLike } from '@chaosity/location-client';
|
|
10
2
|
/**
|
|
11
3
|
* React hook that keeps map label language in sync with the `language` prop.
|
|
12
4
|
*
|
|
@@ -22,4 +14,3 @@ interface MapLike {
|
|
|
22
14
|
* useMapLanguage(mapInstance, language)
|
|
23
15
|
*/
|
|
24
16
|
export declare function useMapLanguage(map: MapLike | null, language: string): void;
|
|
25
|
-
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useEffect } from 'react';
|
|
3
2
|
import { applyMapLanguage } from '@chaosity/location-client';
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
4
|
/**
|
|
5
5
|
* React hook that keeps map label language in sync with the `language` prop.
|
|
6
6
|
*
|
|
@@ -19,7 +19,6 @@ export function useMapLanguage(map, language) {
|
|
|
19
19
|
useEffect(() => {
|
|
20
20
|
if (!map)
|
|
21
21
|
return;
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
22
|
const applyFn = () => applyMapLanguage(map, language);
|
|
24
23
|
if (map.isStyleLoaded()) {
|
|
25
24
|
applyFn();
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { LocationClientProvider, useLocationClient } from './provider/LocationClientProvider';
|
|
1
|
+
export { LocationClientProvider, useLocationClient, } from './provider/LocationClientProvider';
|
|
2
2
|
export type { LocationClientProviderProps } from './provider/LocationClientProvider';
|
|
3
3
|
export { useMapLanguage } from './hooks/useMapLanguage';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { LocationClientProvider, useLocationClient } from './provider/LocationClientProvider';
|
|
1
|
+
export { LocationClientProvider, useLocationClient, } from './provider/LocationClientProvider';
|
|
2
2
|
export { useMapLanguage } from './hooks/useMapLanguage';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ClientConfig } from '@chaosity/location-client';
|
|
2
|
+
import { GeoPlacesClient } from '@chaosity/location-client';
|
|
1
3
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { GeoPlacesClient, ClientConfig } from '@chaosity/location-client';
|
|
3
4
|
interface LocationClientContextValue {
|
|
4
5
|
client: GeoPlacesClient | null;
|
|
5
6
|
getToken: () => string | undefined;
|
|
@@ -14,6 +15,6 @@ export interface LocationClientProviderProps {
|
|
|
14
15
|
/** Seconds before expiry to proactively refresh (default: 60) */
|
|
15
16
|
refreshBuffer?: number;
|
|
16
17
|
}
|
|
17
|
-
export declare function LocationClientProvider({ children, getConfig, refreshBuffer }: LocationClientProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function LocationClientProvider({ children, getConfig, refreshBuffer, }: LocationClientProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
19
|
export declare function useLocationClient(): LocationClientContextValue;
|
|
19
20
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useEffect, useState, useRef, useCallback } from 'react';
|
|
4
3
|
import { GeoPlacesClient } from '@chaosity/location-client';
|
|
5
4
|
import debug from 'debug';
|
|
5
|
+
import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react';
|
|
6
6
|
const log = debug('location-client-react:provider');
|
|
7
7
|
const LocationClientContext = createContext(undefined);
|
|
8
|
-
export function LocationClientProvider({ children, getConfig, refreshBuffer = 60 }) {
|
|
8
|
+
export function LocationClientProvider({ children, getConfig, refreshBuffer = 60, }) {
|
|
9
9
|
const [client, setClient] = useState(null);
|
|
10
10
|
const [loading, setLoading] = useState(true);
|
|
11
11
|
const [error, setError] = useState(null);
|
|
@@ -26,7 +26,7 @@ export function LocationClientProvider({ children, getConfig, refreshBuffer = 60
|
|
|
26
26
|
const isTokenExpired = useCallback(() => {
|
|
27
27
|
if (!expiresAtRef.current)
|
|
28
28
|
return true;
|
|
29
|
-
return Date.now() >=
|
|
29
|
+
return Date.now() >= expiresAtRef.current - refreshBuffer * 1000;
|
|
30
30
|
}, [refreshBuffer]);
|
|
31
31
|
// Refreshes the token exactly once even when called concurrently.
|
|
32
32
|
// All concurrent callers await the same in-flight promise — same pattern as
|
|
@@ -46,7 +46,7 @@ export function LocationClientProvider({ children, getConfig, refreshBuffer = 60
|
|
|
46
46
|
refreshPromiseRef.current = (async () => {
|
|
47
47
|
const cfg = await getConfigRef.current();
|
|
48
48
|
tokenRef.current = cfg.token;
|
|
49
|
-
expiresAtRef.current = cfg.expiresAt ??
|
|
49
|
+
expiresAtRef.current = cfg.expiresAt ?? Date.now() + 900000;
|
|
50
50
|
const newExpiry = Math.floor((expiresAtRef.current - Date.now()) / 1000);
|
|
51
51
|
log('Token refreshed (expires in %ds)', newExpiry);
|
|
52
52
|
})();
|
|
@@ -71,16 +71,21 @@ export function LocationClientProvider({ children, getConfig, refreshBuffer = 60
|
|
|
71
71
|
// transparently without recreating the client or causing re-renders.
|
|
72
72
|
useEffect(() => {
|
|
73
73
|
log('Initializing LocationClientProvider');
|
|
74
|
-
getConfigRef
|
|
74
|
+
getConfigRef
|
|
75
|
+
.current()
|
|
75
76
|
.then((cfg) => {
|
|
76
77
|
tokenRef.current = cfg.token;
|
|
77
|
-
expiresAtRef.current = cfg.expiresAt ??
|
|
78
|
+
expiresAtRef.current = cfg.expiresAt ?? Date.now() + 900000;
|
|
78
79
|
// GeoPlacesClient.send() calls getToken() synchronously for the token value,
|
|
79
80
|
// but token refresh is async. We create a thin send wrapper that:
|
|
80
81
|
// 1. awaits ensureValidToken (via ref — always latest, race-safe)
|
|
81
82
|
// 2. delegates to baseClient which reads the now-fresh token from getToken()
|
|
82
83
|
// The client instance is created once and never recreated on token refresh.
|
|
83
|
-
const baseClient = new GeoPlacesClient({
|
|
84
|
+
const baseClient = new GeoPlacesClient({
|
|
85
|
+
apiUrl: cfg.apiUrl,
|
|
86
|
+
token: cfg.token,
|
|
87
|
+
getToken,
|
|
88
|
+
});
|
|
84
89
|
const wrappingClient = Object.create(baseClient);
|
|
85
90
|
wrappingClient.send = async (command) => {
|
|
86
91
|
await ensureValidTokenRef.current();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaosity/location-client-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "React bindings for Chaosity Location Service client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "tsc",
|
|
10
10
|
"dev": "tsc --watch",
|
|
11
|
+
"lint": "eslint . && prettier --check .",
|
|
12
|
+
"lint:fix": "eslint --fix . && prettier --write .",
|
|
13
|
+
"format": "prettier --write .",
|
|
11
14
|
"test": "vitest run --passWithNoTests",
|
|
12
15
|
"test:watch": "vitest",
|
|
13
16
|
"prepublishOnly": "npm run build",
|
|
@@ -44,16 +47,21 @@
|
|
|
44
47
|
}
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
50
|
+
"@eslint/js": "^10.0.1",
|
|
47
51
|
"@testing-library/react": "^16.0.0",
|
|
48
52
|
"@testing-library/user-event": "^14.0.0",
|
|
49
53
|
"@types/debug": "^4.1.12",
|
|
50
54
|
"@types/react": "^19.0.0",
|
|
51
55
|
"@vitest/coverage-v8": "^3.0.0",
|
|
56
|
+
"eslint": "^10.1.0",
|
|
52
57
|
"happy-dom": "^17.0.0",
|
|
53
58
|
"husky": "^9.0.0",
|
|
54
59
|
"maplibre-gl": "^5.0.0",
|
|
60
|
+
"prettier": "^3.8.1",
|
|
61
|
+
"prettier-plugin-organize-imports": "^4.3.0",
|
|
55
62
|
"react": "^19.0.0",
|
|
56
63
|
"typescript": "^5.0.0",
|
|
64
|
+
"typescript-eslint": "^8.57.1",
|
|
57
65
|
"vitest": "^3.0.0"
|
|
58
66
|
},
|
|
59
67
|
"publishConfig": {
|