@chaosity/location-client-react 0.1.6 → 0.1.8

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
@@ -1,6 +1,6 @@
1
1
  # @chaosity/location-client-react
2
2
 
3
- React bindings for [@chaosity/location-client](https://www.npmjs.com/package/@chaosity/location-client) - AWS Location Service compatible client.
3
+ React bindings for [@chaosity/location-client](https://www.npmjs.com/package/@chaosity/location-client) with automatic token refresh.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,34 +10,58 @@ npm install @chaosity/location-client-react @chaosity/location-client
10
10
 
11
11
  ## Quick Start
12
12
 
13
+ ### 1. Create a Server Action to fetch config
14
+
15
+ ```typescript
16
+ // app/actions/location.ts
17
+ 'use server'
18
+
19
+ import { getClientConfig } from '@chaosity/location-client/server'
20
+
21
+ export async function getLocationConfig() {
22
+ // Auto-reads LOCATION_API_URL, LOCATION_CLIENT_ID, LOCATION_CLIENT_SECRET
23
+ return await getClientConfig()
24
+ }
25
+ ```
26
+
27
+ ### 2. Wrap your app with the provider
28
+
13
29
  ```tsx
14
- import { LocationClientProvider, useLocationClient } from '@chaosity/location-client-react'
15
- import { SuggestCommand } from '@chaosity/location-client'
30
+ // app/layout.tsx
31
+ 'use client'
32
+
33
+ import { LocationClientProvider } from '@chaosity/location-client-react'
34
+ import { getLocationConfig } from './actions/location'
16
35
 
17
- // 1. Wrap your app with the provider
18
- function App() {
36
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
19
37
  return (
20
38
  <LocationClientProvider getConfig={getLocationConfig}>
21
- <MapComponent />
39
+ {children}
22
40
  </LocationClientProvider>
23
41
  )
24
42
  }
43
+ ```
44
+
45
+ ### 3. Use the client in any component
46
+
47
+ ```tsx
48
+ import { useLocationClient } from '@chaosity/location-client-react'
49
+ import { SuggestCommand } from '@chaosity/location-client'
50
+
51
+ function SearchComponent() {
52
+ const { client, loading, error } = useLocationClient()
25
53
 
26
- // 2. Use the client in any component
27
- function MapComponent() {
28
- const { client, config, loading, error } = useLocationClient()
29
-
30
54
  const searchPlaces = async (query: string) => {
31
55
  if (!client) return
32
-
33
- const command = new SuggestCommand({
34
- QueryText: query,
35
- MaxResults: 5
36
- })
37
- const response = await client.send(command)
56
+ const response = await client.send(
57
+ new SuggestCommand({ QueryText: query, MaxResults: 5 })
58
+ )
38
59
  return response.ResultItems
39
60
  }
40
-
61
+
62
+ if (loading) return <div>Loading...</div>
63
+ if (error) return <div>Error: {error}</div>
64
+
41
65
  return <div>...</div>
42
66
  }
43
67
  ```
@@ -46,128 +70,96 @@ function MapComponent() {
46
70
 
47
71
  ### LocationClientProvider
48
72
 
49
- Provides the location client to all child components.
73
+ Provides the location client and automatic token refresh to all child components.
50
74
 
51
75
  ```tsx
52
- <LocationClientProvider getConfig={getLocationConfig}>
76
+ <LocationClientProvider
77
+ getConfig={getLocationConfig}
78
+ refreshBuffer={60}
79
+ >
53
80
  {children}
54
81
  </LocationClientProvider>
55
82
  ```
56
83
 
57
84
  **Props:**
58
- - `getConfig` (function, required) - Async function that returns `{ apiUrl: string, token: string }`
59
- - `children` (ReactNode, required) - Child components
85
+ - `getConfig` — Async function that returns `{ apiUrl: string, token: string, expiresAt?: number }`. Called on init and whenever the token needs refreshing.
86
+ - `refreshBuffer` (optional, default: `60`) — Seconds before token expiry to proactively refresh. Prevents mid-request expiration.
87
+ - `children` — Child components.
60
88
 
61
89
  ### useLocationClient
62
90
 
63
91
  Hook to access the location client in any component.
64
92
 
65
93
  ```tsx
66
- const { client, config, loading, error } = useLocationClient()
94
+ const { client, getToken, loading, error } = useLocationClient()
67
95
  ```
68
96
 
69
97
  **Returns:**
70
- - `client` (GeoPlacesClient | null) - The location client instance
71
- - `config` (ClientConfig | null) - The client configuration (apiUrl, token)
72
- - `loading` (boolean) - Whether the client is initializing
73
- - `error` (string | null) - Error message if initialization failed
74
-
75
- **Throws:** Error if used outside `LocationClientProvider`
98
+ - `client` (`GeoPlacesClient | null`) — The location client instance. Automatically refreshes the token before each `send()` call if needed.
99
+ - `getToken` (`() => string | undefined`) — Returns the current token. Useful for direct API calls (e.g., map style fetch).
100
+ - `loading` (`boolean`) — Whether the client is initializing.
101
+ - `error` (`string | null`) — Error message if initialization or token refresh failed.
76
102
 
77
- ## Usage with Next.js Server Actions
103
+ **Throws:** Error if used outside `LocationClientProvider`.
78
104
 
79
- ```tsx
80
- // app/actions/location.ts (Server-side)
81
- 'use server'
82
-
83
- export async function getLocationConfig() {
84
- const response = await fetch('https://api.example.com/auth/token', {
85
- method: 'POST',
86
- headers: { 'Content-Type': 'application/json' },
87
- body: JSON.stringify({
88
- grant_type: 'client_credentials',
89
- client_id: process.env.LOCATION_CLIENT_ID!,
90
- client_secret: process.env.LOCATION_CLIENT_SECRET!
91
- })
92
- })
93
-
94
- const data = await response.json()
95
- return {
96
- apiUrl: 'https://api.example.com',
97
- token: data.access_token
98
- }
99
- }
105
+ ## Token Refresh
100
106
 
101
- // app/layout.tsx (Client-side)
102
- 'use client'
107
+ The provider automatically handles token lifecycle:
103
108
 
104
- import { LocationClientProvider } from '@chaosity/location-client-react'
105
- import { getLocationConfig } from './actions/location'
109
+ 1. Fetches an initial token via `getConfig` on mount
110
+ 2. Before each `client.send()` call, checks if the token is expired or within the `refreshBuffer` window
111
+ 3. If expired, calls `getConfig` again to get a fresh token
112
+ 4. Concurrent refresh requests are deduplicated — multiple `send()` calls wait for the same refresh
106
113
 
107
- export default function RootLayout({ children }) {
108
- return (
109
- <LocationClientProvider getConfig={getLocationConfig}>
110
- {children}
111
- </LocationClientProvider>
112
- )
113
- }
114
- ```
114
+ No manual token management needed. The `client` always uses a valid token.
115
115
 
116
116
  ## Complete Example with MapLibre
117
117
 
118
118
  ```tsx
119
119
  'use client'
120
120
 
121
+ import { useEffect, useRef } from 'react'
121
122
  import { useLocationClient } from '@chaosity/location-client-react'
122
- import { GeoPlaces } from '@chaosity/location-client'
123
+ import { GeoPlaces, createTransformRequest } from '@chaosity/location-client'
123
124
  import maplibregl from 'maplibre-gl'
124
125
  import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
125
- import { useEffect, useRef } from 'react'
126
+
127
+ const API_URL = process.env.NEXT_PUBLIC_LOCATION_API_URL!
126
128
 
127
129
  export default function MapComponent() {
128
130
  const mapContainer = useRef<HTMLDivElement>(null)
129
131
  const map = useRef<maplibregl.Map | null>(null)
130
- const { config, client, loading, error } = useLocationClient()
132
+ const { client, getToken, loading, error } = useLocationClient()
131
133
 
132
134
  useEffect(() => {
133
- if (!mapContainer.current || map.current || loading || !config || !client) return
135
+ if (!mapContainer.current || map.current || loading || !client) return
136
+
137
+ const token = getToken()
138
+ if (!token) return
134
139
 
135
- // Initialize map
136
140
  const mapInstance = new maplibregl.Map({
137
141
  container: mapContainer.current,
138
- style: `${config.apiUrl}/maps/Standard/descriptor`,
142
+ style: `${API_URL}/maps/Standard/descriptor`,
139
143
  center: [-123.12, 49.28],
140
144
  zoom: 10,
141
- transformRequest: (url) => {
142
- if (url.startsWith(config.apiUrl)) {
143
- return {
144
- url,
145
- headers: { 'Authorization': `Bearer ${config.token}` }
146
- }
147
- }
148
- return { url }
149
- }
145
+ transformRequest: createTransformRequest(API_URL, getToken),
150
146
  })
151
147
 
152
- // Add navigation controls
153
148
  mapInstance.addControl(new maplibregl.NavigationControl(), 'top-right')
154
149
 
155
- // Add geocoder
156
- const geoPlaces = new GeoPlaces(config.apiUrl, config.token, mapInstance)
150
+ // GeoPlaces adapter takes the client instance (not raw apiUrl/token)
151
+ const geoPlaces = new GeoPlaces(client, mapInstance)
157
152
  const geocoder = new MaplibreGeocoder(geoPlaces, {
158
153
  maplibregl,
159
154
  showResultsWhileTyping: true,
160
- limit: 30
155
+ limit: 30,
161
156
  })
162
157
  mapInstance.addControl(geocoder, 'top-left')
163
158
 
164
- // Handle result selection
165
- geocoder.on('result', async (event) => {
166
- const { id, result_type } = event.result
167
- if (result_type === 'Place') {
168
- const details = await geoPlaces.searchByPlaceId(id)
169
- console.log('Place details:', details)
170
- }
159
+ // The geocoder resolves places internally via searchByPlaceId.
160
+ // The 'result' event fires with the resolved place feature.
161
+ geocoder.on('result', (event: any) => {
162
+ console.log('Selected place:', event.result)
171
163
  })
172
164
 
173
165
  map.current = mapInstance
@@ -178,15 +170,10 @@ export default function MapComponent() {
178
170
  map.current = null
179
171
  }
180
172
  }
181
- }, [config, client, loading])
182
-
183
- if (error) {
184
- return <div>Error: {error}</div>
185
- }
173
+ }, [client, getToken, loading])
186
174
 
187
- if (loading) {
188
- return <div>Loading map...</div>
189
- }
175
+ if (error) return <div>Error: {error}</div>
176
+ if (loading) return <div>Loading map...</div>
190
177
 
191
178
  return <div ref={mapContainer} style={{ width: '100%', height: '600px' }} />
192
179
  }
@@ -203,14 +190,14 @@ import {
203
190
  ReverseGeocodeCommand,
204
191
  GetPlaceCommand,
205
192
  SearchTextCommand,
206
- SearchNearbyCommand
193
+ SearchNearbyCommand,
207
194
  } from '@chaosity/location-client'
208
195
 
209
196
  function MyComponent() {
210
197
  const { client } = useLocationClient()
211
198
 
212
- const searchPlaces = async () => {
213
- const response = await client.send(
199
+ const search = async () => {
200
+ const response = await client!.send(
214
201
  new SuggestCommand({ QueryText: 'Vancouver', MaxResults: 5 })
215
202
  )
216
203
  return response.ResultItems
@@ -218,6 +205,14 @@ function MyComponent() {
218
205
  }
219
206
  ```
220
207
 
208
+ ## Logging
209
+
210
+ Enable debug logging with the `DEBUG` environment variable:
211
+
212
+ ```bash
213
+ DEBUG=location-client-react:* npm run dev
214
+ ```
215
+
221
216
  ## TypeScript Support
222
217
 
223
218
  Full TypeScript support with types from AWS SDK:
@@ -226,21 +221,11 @@ Full TypeScript support with types from AWS SDK:
226
221
  import type { SuggestCommandOutput } from '@aws-sdk/client-geo-places'
227
222
 
228
223
  const { client } = useLocationClient()
229
-
230
- const response: SuggestCommandOutput = await client.send(
224
+ const response: SuggestCommandOutput = await client!.send(
231
225
  new SuggestCommand({ QueryText: 'Vancouver' })
232
226
  )
233
227
  ```
234
228
 
235
- ## Security Best Practices
236
-
237
- ⚠️ **NEVER expose client credentials in browser code!**
238
-
239
- - The `getConfig` function should call a server-side API or Server Action
240
- - Store `client_id` and `client_secret` in server environment variables only
241
- - Only the JWT token should be sent to the browser
242
- - Tokens should be short-lived and refreshed as needed
243
-
244
229
  ## License
245
230
 
246
231
  MIT
@@ -0,0 +1,25 @@
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;
7
+ on(event: string, listener: (...args: unknown[]) => void): void;
8
+ off(event: string, listener: (...args: unknown[]) => void): void;
9
+ }
10
+ /**
11
+ * React hook that keeps map label language in sync with the `language` prop.
12
+ *
13
+ * Registers a persistent `style.load` listener so language is automatically
14
+ * reapplied whenever `map.setStyle()` is called (e.g. style or color scheme change).
15
+ * Also applies immediately if the style is already loaded.
16
+ *
17
+ * @param map - MapLibre Map instance, or null while the map is initializing
18
+ * @param language - ISO 639-1 language code (e.g. 'en', 'fr', 'de', 'ja')
19
+ *
20
+ * @example
21
+ * const [mapInstance, setMapInstance] = useState<maplibregl.Map | null>(null)
22
+ * useMapLanguage(mapInstance, language)
23
+ */
24
+ export declare function useMapLanguage(map: MapLike | null, language: string): void;
25
+ export {};
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { useEffect } from 'react';
3
+ import { applyMapLanguage } from '@chaosity/location-client';
4
+ /**
5
+ * React hook that keeps map label language in sync with the `language` prop.
6
+ *
7
+ * Registers a persistent `style.load` listener so language is automatically
8
+ * reapplied whenever `map.setStyle()` is called (e.g. style or color scheme change).
9
+ * Also applies immediately if the style is already loaded.
10
+ *
11
+ * @param map - MapLibre Map instance, or null while the map is initializing
12
+ * @param language - ISO 639-1 language code (e.g. 'en', 'fr', 'de', 'ja')
13
+ *
14
+ * @example
15
+ * const [mapInstance, setMapInstance] = useState<maplibregl.Map | null>(null)
16
+ * useMapLanguage(mapInstance, language)
17
+ */
18
+ export function useMapLanguage(map, language) {
19
+ useEffect(() => {
20
+ if (!map)
21
+ return;
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
+ const applyFn = () => applyMapLanguage(map, language);
24
+ if (map.isStyleLoaded()) {
25
+ applyFn();
26
+ }
27
+ map.on('style.load', applyFn);
28
+ return () => {
29
+ map.off('style.load', applyFn);
30
+ };
31
+ }, [map, language]);
32
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { LocationClientProvider, useLocationClient } from './provider/LocationClientProvider';
2
2
  export type { LocationClientProviderProps } from './provider/LocationClientProvider';
3
+ export { useMapLanguage } from './hooks/useMapLanguage';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { LocationClientProvider, useLocationClient } from './provider/LocationClientProvider';
2
+ export { useMapLanguage } from './hooks/useMapLanguage';
@@ -1,10 +1,8 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import { GeoPlacesClient, ClientConfig } from '@chaosity/location-client';
3
3
  interface LocationClientContextValue {
4
- config: ClientConfig | null;
5
4
  client: GeoPlacesClient | null;
6
5
  getToken: () => string | undefined;
7
- ensureValidToken: () => Promise<void>;
8
6
  loading: boolean;
9
7
  error: string | null;
10
8
  }
@@ -13,6 +11,7 @@ export interface LocationClientProviderProps {
13
11
  getConfig: () => Promise<ClientConfig & {
14
12
  expiresAt?: number;
15
13
  }>;
14
+ /** Seconds before expiry to proactively refresh (default: 60) */
16
15
  refreshBuffer?: number;
17
16
  }
18
17
  export declare function LocationClientProvider({ children, getConfig, refreshBuffer }: LocationClientProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -3,95 +3,92 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext, useEffect, useState, useRef, useCallback } from 'react';
4
4
  import { GeoPlacesClient } from '@chaosity/location-client';
5
5
  import debug from 'debug';
6
- import { jwtDecode } from 'jwt-decode';
7
6
  const log = debug('location-client-react:provider');
8
7
  const LocationClientContext = createContext(undefined);
9
8
  export function LocationClientProvider({ children, getConfig, refreshBuffer = 60 }) {
10
- const [config, setConfig] = useState(null);
11
9
  const [client, setClient] = useState(null);
12
10
  const [loading, setLoading] = useState(true);
13
11
  const [error, setError] = useState(null);
12
+ // Refs hold live values without triggering re-renders
14
13
  const tokenRef = useRef(undefined);
15
14
  const expiresAtRef = useRef(null);
16
15
  const getConfigRef = useRef(getConfig);
17
- const tokenGenerationCount = useRef(0);
18
- // Update ref when getConfig changes
16
+ const refreshPromiseRef = useRef(null);
17
+ const ensureValidTokenRef = useRef(async () => { });
18
+ // Keep getConfig ref current without recreating callbacks
19
19
  useEffect(() => {
20
20
  getConfigRef.current = getConfig;
21
21
  }, [getConfig]);
22
- // Token getter function (stable reference)
22
+ // Stable token getter — passed into GeoPlacesClient so it always reads the live ref
23
23
  const getToken = useCallback(() => tokenRef.current, []);
24
- // Check if token is expired or about to expire
24
+ // Returns true when the token is expired or within the refresh buffer window.
25
+ // Treats unknown expiry as expired so we always refresh on first use.
25
26
  const isTokenExpired = useCallback(() => {
26
27
  if (!expiresAtRef.current)
27
- return false;
28
+ return true;
28
29
  return Date.now() >= (expiresAtRef.current - refreshBuffer * 1000);
29
30
  }, [refreshBuffer]);
30
- // Refresh token if needed
31
+ // Refreshes the token exactly once even when called concurrently.
32
+ // All concurrent callers await the same in-flight promise — same pattern as
33
+ // server-side TokenProvider.tokenPromise deduplication.
31
34
  const ensureValidToken = useCallback(async () => {
32
35
  if (!isTokenExpired())
33
36
  return;
34
- const timeUntilExpiry = expiresAtRef.current ? Math.floor((expiresAtRef.current - Date.now()) / 1000) : 0;
35
- log('Token expired or expiring soon (expires in %ds), refreshing...', timeUntilExpiry);
36
- tokenGenerationCount.current++;
37
- try {
37
+ // Deduplicate: if a refresh is already in flight, wait for it instead of firing another
38
+ if (refreshPromiseRef.current) {
39
+ log('Token refresh already in progress, waiting...');
40
+ return refreshPromiseRef.current;
41
+ }
42
+ const timeUntilExpiry = expiresAtRef.current
43
+ ? Math.floor((expiresAtRef.current - Date.now()) / 1000)
44
+ : 0;
45
+ log('Token expired or expiring soon (in %ds), refreshing...', timeUntilExpiry);
46
+ refreshPromiseRef.current = (async () => {
38
47
  const cfg = await getConfigRef.current();
39
- setConfig(cfg);
40
48
  tokenRef.current = cfg.token;
41
- expiresAtRef.current = null;
42
- const baseClient = new GeoPlacesClient(cfg);
43
- const wrappedClient = new Proxy(baseClient, {
44
- get(target, prop) {
45
- if (prop === 'send') {
46
- return async (command) => {
47
- await ensureValidToken();
48
- return target.send(command);
49
- };
50
- }
51
- return target[prop];
52
- }
53
- });
54
- setClient(wrappedClient);
55
- const decoded = jwtDecode(cfg.token);
56
- expiresAtRef.current = decoded.exp * 1000;
57
- const newExpiry = Math.floor((decoded.exp * 1000 - Date.now()) / 1000);
58
- log('Token refreshed successfully (new token expires in %ds)', newExpiry);
49
+ expiresAtRef.current = cfg.expiresAt ?? (Date.now() + 900000);
50
+ const newExpiry = Math.floor((expiresAtRef.current - Date.now()) / 1000);
51
+ log('Token refreshed (expires in %ds)', newExpiry);
52
+ })();
53
+ try {
54
+ await refreshPromiseRef.current;
59
55
  }
60
56
  catch (err) {
61
57
  log('Token refresh failed: %s', err instanceof Error ? err.message : 'Unknown error');
62
58
  setError(err instanceof Error ? err.message : 'Failed to refresh token');
63
59
  }
60
+ finally {
61
+ refreshPromiseRef.current = null;
62
+ }
64
63
  }, [isTokenExpired]);
65
- // Initial load
64
+ // Keep ensureValidToken ref current so the send wrapper always uses the latest version
65
+ useEffect(() => {
66
+ ensureValidTokenRef.current = ensureValidToken;
67
+ }, [ensureValidToken]);
68
+ // Initial load — create the client once.
69
+ // getToken is passed as a callback so the client always reads the live token ref.
70
+ // ensureValidToken is called before each send so expiring tokens are refreshed
71
+ // transparently without recreating the client or causing re-renders.
66
72
  useEffect(() => {
67
73
  log('Initializing LocationClientProvider');
68
- tokenGenerationCount.current++;
69
- getConfig()
74
+ getConfigRef.current()
70
75
  .then((cfg) => {
71
- setConfig(cfg);
72
76
  tokenRef.current = cfg.token;
73
- const baseClient = new GeoPlacesClient(cfg);
74
- const wrappedClient = new Proxy(baseClient, {
75
- get(target, prop) {
76
- if (prop === 'send') {
77
- return async (command) => {
78
- await ensureValidToken();
79
- return target.send(command);
80
- };
81
- }
82
- return target[prop];
83
- }
84
- });
85
- setClient(wrappedClient);
86
- try {
87
- const decoded = jwtDecode(cfg.token);
88
- expiresAtRef.current = decoded.exp * 1000;
89
- const expiry = Math.floor((decoded.exp * 1000 - Date.now()) / 1000);
90
- log('Client initialized (token expires in %ds)', expiry);
91
- }
92
- catch (err) {
93
- log('Failed to decode token, no expiry info');
94
- }
77
+ expiresAtRef.current = cfg.expiresAt ?? (Date.now() + 900000);
78
+ // GeoPlacesClient.send() calls getToken() synchronously for the token value,
79
+ // but token refresh is async. We create a thin send wrapper that:
80
+ // 1. awaits ensureValidToken (via ref — always latest, race-safe)
81
+ // 2. delegates to baseClient which reads the now-fresh token from getToken()
82
+ // The client instance is created once and never recreated on token refresh.
83
+ const baseClient = new GeoPlacesClient({ apiUrl: cfg.apiUrl, token: cfg.token, getToken });
84
+ const wrappingClient = Object.create(baseClient);
85
+ wrappingClient.send = async (command) => {
86
+ await ensureValidTokenRef.current();
87
+ return baseClient.send(command);
88
+ };
89
+ setClient(wrappingClient);
90
+ const expiry = Math.floor((expiresAtRef.current - Date.now()) / 1000);
91
+ log('Client initialized (token expires in %ds)', expiry);
95
92
  setLoading(false);
96
93
  })
97
94
  .catch((err) => {
@@ -99,14 +96,8 @@ export function LocationClientProvider({ children, getConfig, refreshBuffer = 60
99
96
  setError(err instanceof Error ? err.message : 'Failed to initialize client');
100
97
  setLoading(false);
101
98
  });
102
- }, [ensureValidToken]);
103
- // Check token validity on every state change
104
- useEffect(() => {
105
- if (!loading && config) {
106
- ensureValidToken();
107
- }
108
- }, [config, loading, ensureValidToken]);
109
- return (_jsx(LocationClientContext.Provider, { value: { config, client, getToken, ensureValidToken, loading, error }, children: children }));
99
+ }, [getToken]);
100
+ return (_jsx(LocationClientContext.Provider, { value: { client, getToken, loading, error }, children: children }));
110
101
  }
111
102
  export function useLocationClient() {
112
103
  const context = useContext(LocationClientContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaosity/location-client-react",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "React bindings for Chaosity Location Service client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,8 @@
8
8
  "scripts": {
9
9
  "build": "tsc",
10
10
  "dev": "tsc --watch",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
11
13
  "prepublishOnly": "npm run build"
12
14
  },
13
15
  "keywords": [
@@ -28,18 +30,31 @@
28
30
  "url": "https://github.com/chaosity-io/location-service-client-react/issues"
29
31
  },
30
32
  "dependencies": {
31
- "@chaosity/location-client": "^0.1.5",
32
- "debug": "^4.4.3",
33
- "jwt-decode": "^4.0.0"
33
+ "@chaosity/location-client": "file:../location-service-client",
34
+ "debug": "^4.4.3"
34
35
  },
35
36
  "peerDependencies": {
37
+ "maplibre-gl": "^5.0.0",
36
38
  "react": "^18.0.0 || ^19.0.0"
37
39
  },
40
+ "peerDependenciesMeta": {
41
+ "maplibre-gl": {
42
+ "optional": true
43
+ }
44
+ },
38
45
  "devDependencies": {
46
+ "@testing-library/react": "^16.0.0",
47
+ "@testing-library/user-event": "^14.0.0",
39
48
  "@types/debug": "^4.1.12",
40
49
  "@types/react": "^19.0.0",
50
+ "happy-dom": "^17.0.0",
51
+ "maplibre-gl": "^5.0.0",
41
52
  "react": "^19.0.0",
42
- "typescript": "^5.0.0"
53
+ "typescript": "^5.0.0",
54
+ "vitest": "^3.0.0"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
43
58
  },
44
59
  "files": [
45
60
  "dist",