@chaosity/location-client 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
@@ -2,11 +2,11 @@
2
2
 
3
3
  AWS Location Service compatible client with custom Bearer token authentication.
4
4
 
5
- ## ⚠️ Security Warning
5
+ ## Security Warning
6
6
 
7
7
  **This package contains server-side authentication utilities that require client credentials.**
8
8
 
9
- - `TokenProvider` and `getClientConfig()` are **SERVER-SIDE ONLY**
9
+ - `TokenProvider` and `getClientConfig()` are **SERVER-SIDE ONLY** (import from `@chaosity/location-client/server`)
10
10
  - They require `clientId` and `clientSecret` which must **NEVER** be exposed to browsers
11
11
  - Only use these in:
12
12
  - Node.js servers
@@ -14,7 +14,7 @@ AWS Location Service compatible client with custom Bearer token authentication.
14
14
  - Next.js API routes
15
15
  - Backend services
16
16
 
17
- **For React applications**, use [`@chaosity/location-client-react`](https://www.npmjs.com/package/@chaosity/location-client-react) which handles authentication safely.
17
+ **For React applications**, use [`@chaosity/location-client-react`](https://www.npmjs.com/package/@chaosity/location-client-react) which handles authentication and token refresh safely.
18
18
 
19
19
  ## Installation
20
20
 
@@ -27,115 +27,152 @@ npm install @chaosity/location-client
27
27
  - **Custom Authentication**: Uses Bearer tokens instead of AWS SigV4
28
28
  - **AWS SDK Commands**: Full access to all AWS Location Service commands
29
29
  - **Data Type Utilities**: Built-in GeoJSON conversion utilities
30
- - **MapLibre Integration**: Adapter for MapLibre GL Geocoder
30
+ - **MapLibre Integration**: Adapter for MapLibre GL Geocoder and `createTransformRequest` helper
31
+ - **Server Utilities**: `getClientConfig()` with auto-env detection and token caching
31
32
 
32
33
  ## Quick Start
33
34
 
34
- ### Basic Client Usage
35
+ ### Server-Side: Get Config with Auto-Authentication
36
+
37
+ The simplest way to authenticate server-side. Reads credentials from environment variables automatically.
35
38
 
36
39
  ```typescript
37
- import { GeoPlacesClient, SuggestCommand } from '@chaosity/location-client'
40
+ // app/actions/location.ts
41
+ 'use server'
38
42
 
39
- const client = new GeoPlacesClient({
40
- apiUrl: 'https://api.example.com',
41
- token: 'your-bearer-token'
43
+ import { getClientConfig } from '@chaosity/location-client/server'
44
+
45
+ export async function getLocationConfig() {
46
+ // Auto-reads LOCATION_API_URL, LOCATION_CLIENT_ID, LOCATION_CLIENT_SECRET
47
+ return await getClientConfig()
48
+ }
49
+ ```
50
+
51
+ Set these environment variables:
52
+
53
+ ```bash
54
+ LOCATION_API_URL=https://api.chaosity.cloud
55
+ LOCATION_CLIENT_ID=your-client-id
56
+ LOCATION_CLIENT_SECRET=your-client-secret
57
+ ```
58
+
59
+ Or pass credentials explicitly:
60
+
61
+ ```typescript
62
+ const config = await getClientConfig({
63
+ apiUrl: 'https://api.chaosity.cloud',
64
+ clientId: process.env.MY_CLIENT_ID!,
65
+ clientSecret: process.env.MY_SECRET!,
42
66
  })
67
+ // config = { apiUrl, token, expiresAt }
68
+ ```
69
+
70
+ ### Client-Side: Using the GeoPlacesClient
43
71
 
44
- const command = new SuggestCommand({
45
- QueryText: 'Vancouver',
46
- MaxResults: 5
72
+ ```typescript
73
+ import { GeoPlacesClient, SuggestCommand } from '@chaosity/location-client'
74
+
75
+ const client = new GeoPlacesClient({
76
+ apiUrl: 'https://api.chaosity.cloud',
77
+ token: 'your-bearer-token',
47
78
  })
48
79
 
49
- const response = await client.send(command)
80
+ const response = await client.send(
81
+ new SuggestCommand({ QueryText: 'Vancouver', MaxResults: 5 })
82
+ )
50
83
  ```
51
84
 
52
- ### Server-Side Authentication (Next.js Server Action)
85
+ ### MapLibre Map Integration
86
+
87
+ Use `createTransformRequest` to automatically attach Bearer tokens to map tile requests:
53
88
 
54
89
  ```typescript
55
- // app/actions/location.ts
56
- 'use server'
90
+ import { createTransformRequest } from '@chaosity/location-client'
91
+ import maplibregl from 'maplibre-gl'
57
92
 
58
- export async function getLocationConfig() {
59
- const response = await fetch('https://api.example.com/auth/token', {
60
- method: 'POST',
61
- headers: { 'Content-Type': 'application/json' },
62
- body: JSON.stringify({
63
- client_id: process.env.LOCATION_CLIENT_ID!,
64
- client_secret: process.env.LOCATION_CLIENT_SECRET!,
65
- grant_type: 'client_credentials'
66
- })
67
- })
68
-
69
- const data = await response.json()
70
- return {
71
- apiUrl: 'https://api.example.com',
72
- token: data.access_token
73
- }
74
- }
93
+ const map = new maplibregl.Map({
94
+ container: 'map',
95
+ style: `${apiUrl}/maps/Standard/descriptor`,
96
+ center: [-123.12, 49.28],
97
+ zoom: 10,
98
+ transformRequest: createTransformRequest(apiUrl, getToken),
99
+ })
75
100
  ```
76
101
 
77
- ### MapLibre Integration
102
+ `createTransformRequest` handles setting the correct `Accept` headers for tiles (protobuf), glyphs, sprites, and style descriptors.
103
+
104
+ ### MapLibre Geocoder Integration
78
105
 
79
106
  ```typescript
80
- import { GeoPlaces } from '@chaosity/location-client'
107
+ import { GeoPlacesClient, GeoPlaces } from '@chaosity/location-client'
81
108
  import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
82
109
  import maplibregl from 'maplibre-gl'
83
110
 
84
- const map = new maplibregl.Map({ /* ... */ })
85
- const geoPlaces = new GeoPlaces(apiUrl, token, map)
111
+ // GeoPlaces adapter takes a GeoPlacesClient instance and the map
112
+ const client = new GeoPlacesClient({ apiUrl, token })
113
+ const geoPlaces = new GeoPlaces(client, map)
86
114
 
87
- // Add geocoder control
88
115
  const geocoder = new MaplibreGeocoder(geoPlaces, {
89
116
  maplibregl,
90
117
  showResultsWhileTyping: true,
91
- limit: 30
118
+ limit: 30,
92
119
  })
93
120
 
94
121
  map.addControl(geocoder, 'top-left')
95
122
 
96
- // Handle result selection
97
- geocoder.on('result', async (event) => {
98
- const { id, result_type } = event.result
99
- if (result_type === 'Place') {
100
- const details = await geoPlaces.searchByPlaceId(id)
101
- console.log('Place details:', details)
102
- }
123
+ // The geocoder calls getSuggestions → searchByPlaceId internally.
124
+ // The 'result' event fires with the resolved place feature.
125
+ geocoder.on('result', (event) => {
126
+ console.log('Selected place:', event.result)
103
127
  })
104
128
  ```
105
129
 
106
130
  ## API Reference
107
131
 
108
- ### GeoPlacesClient
132
+ ### Main Exports (`@chaosity/location-client`)
133
+
134
+ #### GeoPlacesClient
109
135
 
110
- Main client for executing commands.
136
+ Client for executing AWS Location Service commands with Bearer token auth.
111
137
 
112
138
  ```typescript
113
139
  const client = new GeoPlacesClient({
114
140
  apiUrl: string,
115
- token: string
141
+ token: string,
142
+ getToken?: () => string | undefined, // Optional: dynamic token getter
116
143
  })
117
144
 
118
145
  await client.send(command)
119
146
  ```
120
147
 
121
- ### GeoPlaces Adapter
148
+ When `getToken` is provided, it is called on every request so token updates are reflected without recreating the client.
122
149
 
123
- MapLibre Geocoder adapter for search and geocoding.
150
+ #### GeoPlaces Adapter
151
+
152
+ Implements the `MaplibreGeocoderApi` interface for use with `@maplibre/maplibre-gl-geocoder`. Methods are called automatically by the geocoder control.
124
153
 
125
154
  ```typescript
126
- const geoPlaces = new GeoPlaces(apiUrl, token, map)
155
+ const client = new GeoPlacesClient({ apiUrl, token })
156
+ const geoPlaces = new GeoPlaces(client, map)
157
+
158
+ // Pass to MaplibreGeocoder — it calls these methods internally:
159
+ // geoPlaces.getSuggestions(config) — typeahead suggestions
160
+ // geoPlaces.forwardGeocode(config) — text to coordinates
161
+ // geoPlaces.reverseGeocode(config) — coordinates to address
162
+ // geoPlaces.searchByPlaceId(config) — place ID to details
163
+ ```
127
164
 
128
- // Forward geocoding (search)
129
- await geoPlaces.forwardGeocode({ query: 'Vancouver' })
165
+ #### createTransformRequest
130
166
 
131
- // Reverse geocoding (coordinates to address)
132
- await geoPlaces.reverseGeocode({ query: [-123.12, 49.28] })
167
+ Creates a MapLibre `transformRequest` function that adds Bearer auth and correct `Accept` headers.
168
+
169
+ ```typescript
170
+ import { createTransformRequest } from '@chaosity/location-client'
133
171
 
134
- // Get place details by ID
135
- await geoPlaces.searchByPlaceId('place-id')
172
+ const transformRequest = createTransformRequest(apiUrl, () => currentToken)
136
173
  ```
137
174
 
138
- ### Available Commands
175
+ #### Available Commands
139
176
 
140
177
  All AWS Location Service commands from `@aws-sdk/client-geo-places`:
141
178
 
@@ -146,11 +183,11 @@ import {
146
183
  ReverseGeocodeCommand,
147
184
  GetPlaceCommand,
148
185
  SearchTextCommand,
149
- SearchNearbyCommand
186
+ SearchNearbyCommand,
150
187
  } from '@chaosity/location-client'
151
188
  ```
152
189
 
153
- ### Data Type Utilities
190
+ #### Data Type Utilities
154
191
 
155
192
  GeoJSON conversion utilities from `@aws/amazon-location-utilities-datatypes`:
156
193
 
@@ -158,90 +195,67 @@ GeoJSON conversion utilities from `@aws/amazon-location-utilities-datatypes`:
158
195
  import {
159
196
  placeToFeatureCollection,
160
197
  routeToFeatureCollection,
161
- devicePositionsToFeatureCollection
198
+ devicePositionsToFeatureCollection,
162
199
  } from '@chaosity/location-client'
163
200
  ```
164
201
 
165
- ## Logging
202
+ ### Server Exports (`@chaosity/location-client/server`)
166
203
 
167
- The library uses the `debug` package for optional verbose logging. Enable it via the `DEBUG` environment variable:
204
+ #### getClientConfig
168
205
 
169
- ```bash
170
- # Enable all location-client logs
171
- DEBUG=location-client:* npm run dev
206
+ Gets a client config with a fresh token. Uses a singleton `TokenProvider` internally — safe to call repeatedly (tokens are cached and refreshed automatically).
172
207
 
173
- # Enable only authentication logs
174
- DEBUG=location-client:auth npm run dev
175
-
176
- # Enable only API request logs
177
- DEBUG=location-client:api npm run dev
208
+ ```typescript
209
+ import { getClientConfig } from '@chaosity/location-client/server'
178
210
 
179
- # Enable multiple namespaces
180
- DEBUG=location-client:*,express:* npm run dev
211
+ const config = await getClientConfig()
212
+ // { apiUrl: string, token: string, expiresAt?: number }
181
213
  ```
182
214
 
183
- Example output:
184
- ```
185
- location-client:auth Initializing TokenProvider for https://api.example.com +0ms
186
- location-client:auth Fetching new token from https://api.example.com +2ms
187
- location-client:auth Token acquired successfully (expires in 3600s) +145ms
188
- location-client:api Sending SuggestCommand request to /address/suggestion +0ms
189
- location-client:api Request successful: 200 (89ms) +89ms
190
- ```
215
+ #### TokenProvider
191
216
 
192
- ## Security Best Practices
217
+ Lower-level token management with caching and deduplication.
193
218
 
194
- ⚠️ **NEVER expose client credentials in browser code!**
219
+ ```typescript
220
+ import { TokenProvider } from '@chaosity/location-client/server'
195
221
 
196
- - Store `client_id` and `client_secret` in server environment variables
197
- - Use Server Actions or API routes to fetch tokens
198
- - Only send the JWT token to the browser
199
- - Tokens should be short-lived and refreshed as needed
222
+ const provider = new TokenProvider({
223
+ apiUrl: process.env.LOCATION_API_URL!,
224
+ clientId: process.env.LOCATION_CLIENT_ID!,
225
+ clientSecret: process.env.LOCATION_CLIENT_SECRET!,
226
+ })
200
227
 
201
- ## Example: Complete MapLibre Setup
228
+ const { success, token, expiresAt } = await provider.getToken()
229
+ ```
230
+
231
+ #### LocationServiceConnector
232
+
233
+ Server-side connector for backend-to-backend API calls. Can auto-configure from environment variables when no config is passed.
202
234
 
203
235
  ```typescript
204
- 'use client'
236
+ import { LocationServiceConnector } from '@chaosity/location-client/server'
205
237
 
206
- import { GeoPlaces } from '@chaosity/location-client'
207
- import { getLocationConfig } from '@/lib/actions/location'
208
- import maplibregl from 'maplibre-gl'
209
- import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
238
+ const connector = new LocationServiceConnector({
239
+ apiUrl: config.apiUrl,
240
+ token: config.token,
241
+ })
210
242
 
211
- export default function MapComponent() {
212
- useEffect(() => {
213
- async function initMap() {
214
- // Get config from server
215
- const { apiUrl, token } = await getLocationConfig()
216
-
217
- // Initialize map
218
- const map = new maplibregl.Map({
219
- container: 'map',
220
- style: `${apiUrl}/maps/Standard/descriptor`,
221
- center: [-123.12, 49.28],
222
- zoom: 10,
223
- transformRequest: (url) => {
224
- if (url.startsWith(apiUrl)) {
225
- return {
226
- url,
227
- headers: { 'Authorization': `Bearer ${token}` }
228
- }
229
- }
230
- return { url }
231
- }
232
- })
233
-
234
- // Add geocoder
235
- const geoPlaces = new GeoPlaces(apiUrl, token, map)
236
- const geocoder = new MaplibreGeocoder(geoPlaces, { maplibregl })
237
- map.addControl(geocoder, 'top-left')
238
- }
239
-
240
- initMap()
241
- }, [])
242
-
243
- return <div id="map" style={{ width: '100%', height: '600px' }} />
244
- }
243
+ const result = await connector.send(new SuggestCommand({ QueryText: 'Vancouver' }))
244
+ ```
245
+
246
+ ## Logging
247
+
248
+ The library uses the `debug` package for optional verbose logging:
249
+
250
+ ```bash
251
+ # Enable all location-client logs
252
+ DEBUG=location-client:* npm run dev
253
+
254
+ # Enable only authentication logs
255
+ DEBUG=location-client:auth npm run dev
256
+
257
+ # Enable only API request logs
258
+ DEBUG=location-client:api npm run dev
245
259
  ```
246
260
 
247
261
  ## TypeScript Support
@@ -94,10 +94,10 @@ export class TokenProvider {
94
94
  }
95
95
  const data = await response.json();
96
96
  this.cachedToken = data.access_token;
97
- // Use expires_in from OAuth2 response; null-coalesce so 0 is not replaced
98
- const expiresIn = data.expires_in ?? 900;
99
- this.cachedExpiresAt = Date.now() + (expiresIn * 1000);
100
- log('Token acquired successfully (expires in %ds)', expiresIn);
97
+ // Prefer absolute expires_at (ms) from response, fall back to expires_in (seconds)
98
+ this.cachedExpiresAt = data.expires_at ?? (Date.now() + ((data.expires_in ?? 900) * 1000));
99
+ const expiresInSec = Math.floor((this.cachedExpiresAt - Date.now()) / 1000);
100
+ log('Token acquired successfully (expires in %ds)', expiresInSec);
101
101
  return {
102
102
  success: true,
103
103
  token: this.cachedToken,
@@ -72,7 +72,12 @@ export async function getClientConfig(config = {}) {
72
72
  const result = await provider.getToken();
73
73
  if (!result.success || !result.token) {
74
74
  console.error('[getClientConfig] Token fetch failed:', result.error);
75
- throw new Error(result.error || 'Failed to get token');
75
+ const isAuthError = result.error === 'Invalid credentials' ||
76
+ result.error?.toLowerCase().includes('unauthorized');
77
+ throw new Error(isAuthError
78
+ ? `Authentication failed for client ID "${clientId}". ` +
79
+ `Verify LOCATION_CLIENT_ID and LOCATION_CLIENT_SECRET match your application in the developer portal.`
80
+ : (result.error || 'Failed to get token'));
76
81
  }
77
82
  log('[getClientConfig] Token fetched successfully, length:', result.token.length);
78
83
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaosity/location-client",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Client library for Chaosity Location Service with AWS Location Service compatibility",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,7 +44,7 @@
44
44
  "@aws-sdk/client-geo-places": "^3.0.0",
45
45
  "@aws/amazon-location-utilities-datatypes": "^1.0.0",
46
46
  "@maplibre/maplibre-gl-geocoder": "^1.9.4",
47
- "debug": "^4.4.3"
47
+ "debug": "^4.4.3"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "maplibre-gl": "^5.0.0"