@chaosity/location-client 0.1.2 → 0.1.4
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,23 +1,20 @@
|
|
|
1
1
|
# @chaosity/location-client
|
|
2
2
|
|
|
3
|
-
AWS Location Service compatible client with custom authentication.
|
|
3
|
+
AWS Location Service compatible client with custom Bearer token authentication.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## ⚠️ Security Warning
|
|
6
6
|
|
|
7
|
-
This client
|
|
8
|
-
- [`@aws/amazon-location-client`](https://github.com/aws-geospatial/amazon-location-client-js) - AWS Location Client with all commands
|
|
9
|
-
- [`@aws/amazon-location-utilities-datatypes`](https://github.com/aws-geospatial/amazon-location-utilities-datatypes-js) - Data type conversions (GeoJSON, etc.)
|
|
7
|
+
**This package contains server-side authentication utilities that require client credentials.**
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
- `TokenProvider` and `getClientConfig()` are **SERVER-SIDE ONLY**
|
|
10
|
+
- They require `clientId` and `clientSecret` which must **NEVER** be exposed to browsers
|
|
11
|
+
- Only use these in:
|
|
12
|
+
- Node.js servers
|
|
13
|
+
- Next.js Server Actions (`'use server'`)
|
|
14
|
+
- Next.js API routes
|
|
15
|
+
- Backend services
|
|
12
16
|
|
|
13
|
-
**
|
|
14
|
-
- AWS SDK: Uses AWS SigV4 (IAM credentials)
|
|
15
|
-
- This client: Uses Bearer token (OAuth2)
|
|
16
|
-
|
|
17
|
-
**Everything else is identical**:
|
|
18
|
-
- Same command classes from AWS Location Client
|
|
19
|
-
- Same request/response types
|
|
20
|
-
- Same data type conversions
|
|
17
|
+
**For React applications**, use [`@chaosity/location-client-react`](https://www.npmjs.com/package/@chaosity/location-client-react) which handles authentication safely.
|
|
21
18
|
|
|
22
19
|
## Installation
|
|
23
20
|
|
|
@@ -25,20 +22,26 @@ This client uses:
|
|
|
25
22
|
npm install @chaosity/location-client
|
|
26
23
|
```
|
|
27
24
|
|
|
28
|
-
##
|
|
25
|
+
## Key Features
|
|
26
|
+
|
|
27
|
+
- **Custom Authentication**: Uses Bearer tokens instead of AWS SigV4
|
|
28
|
+
- **AWS SDK Commands**: Full access to all AWS Location Service commands
|
|
29
|
+
- **Data Type Utilities**: Built-in GeoJSON conversion utilities
|
|
30
|
+
- **MapLibre Integration**: Adapter for MapLibre GL Geocoder
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### Basic Client Usage
|
|
31
35
|
|
|
32
36
|
```typescript
|
|
33
|
-
import { GeoPlacesClient,
|
|
37
|
+
import { GeoPlacesClient, SuggestCommand } from '@chaosity/location-client'
|
|
34
38
|
|
|
35
39
|
const client = new GeoPlacesClient({
|
|
36
40
|
apiUrl: 'https://api.example.com',
|
|
37
41
|
token: 'your-bearer-token'
|
|
38
42
|
})
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
const command = new places.SuggestCommand({
|
|
44
|
+
const command = new SuggestCommand({
|
|
42
45
|
QueryText: 'Vancouver',
|
|
43
46
|
MaxResults: 5
|
|
44
47
|
})
|
|
@@ -46,173 +49,212 @@ const command = new places.SuggestCommand({
|
|
|
46
49
|
const response = await client.send(command)
|
|
47
50
|
```
|
|
48
51
|
|
|
49
|
-
###
|
|
52
|
+
### Server-Side Authentication (Next.js Server Action)
|
|
50
53
|
|
|
51
54
|
```typescript
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const map = new maplibregl.Map({ /* ... */ })
|
|
56
|
-
const client = new GeoPlacesClient({ apiUrl, token })
|
|
57
|
-
const geoPlaces = new GeoPlaces(client, map)
|
|
58
|
-
|
|
59
|
-
// Use with MapLibre Geocoder
|
|
60
|
-
const results = await geoPlaces.forwardGeocode({ query: 'Vancouver' })
|
|
55
|
+
// app/actions/location.ts
|
|
56
|
+
'use server'
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
const
|
|
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
|
+
}
|
|
64
75
|
```
|
|
65
76
|
|
|
66
|
-
|
|
77
|
+
### MapLibre Integration
|
|
67
78
|
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
- `places.ReverseGeocodeCommand`
|
|
73
|
-
- `places.SearchNearbyCommand`
|
|
74
|
-
- `places.SearchTextCommand`
|
|
75
|
-
- `places.SuggestCommand`
|
|
76
|
-
- `maps.*` - All Maps commands
|
|
77
|
-
- `routes.*` - All Routes commands
|
|
79
|
+
```typescript
|
|
80
|
+
import { GeoPlaces } from '@chaosity/location-client'
|
|
81
|
+
import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'
|
|
82
|
+
import maplibregl from 'maplibre-gl'
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
const map = new maplibregl.Map({ /* ... */ })
|
|
85
|
+
const geoPlaces = new GeoPlaces(apiUrl, token, map)
|
|
80
86
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
// Add geocoder control
|
|
88
|
+
const geocoder = new MaplibreGeocoder(geoPlaces, {
|
|
89
|
+
maplibregl,
|
|
90
|
+
showResultsWhileTyping: true,
|
|
91
|
+
limit: 30
|
|
92
|
+
})
|
|
87
93
|
|
|
88
|
-
|
|
94
|
+
map.addControl(geocoder, 'top-left')
|
|
89
95
|
|
|
90
|
-
|
|
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
|
+
}
|
|
103
|
+
})
|
|
104
|
+
```
|
|
91
105
|
|
|
92
|
-
|
|
106
|
+
## API Reference
|
|
93
107
|
|
|
94
|
-
|
|
95
|
-
- Client credentials must be stored in environment variables on the server
|
|
96
|
-
- Browser code should only receive the JWT token from your backend
|
|
108
|
+
### GeoPlacesClient
|
|
97
109
|
|
|
98
|
-
|
|
110
|
+
Main client for executing commands.
|
|
99
111
|
|
|
100
|
-
```
|
|
101
|
-
|
|
112
|
+
```typescript
|
|
113
|
+
const client = new GeoPlacesClient({
|
|
114
|
+
apiUrl: string,
|
|
115
|
+
token: string
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
await client.send(command)
|
|
102
119
|
```
|
|
103
120
|
|
|
104
|
-
|
|
121
|
+
### GeoPlaces Adapter
|
|
105
122
|
|
|
106
|
-
|
|
123
|
+
MapLibre Geocoder adapter for search and geocoding.
|
|
107
124
|
|
|
108
125
|
```typescript
|
|
109
|
-
|
|
110
|
-
'use server'
|
|
111
|
-
|
|
112
|
-
export async function getLocationToken() {
|
|
113
|
-
const response = await fetch('https://api.locationservice.com/auth/token', {
|
|
114
|
-
method: 'POST',
|
|
115
|
-
headers: { 'Content-Type': 'application/json' },
|
|
116
|
-
body: JSON.stringify({
|
|
117
|
-
grant_type: 'client_credentials',
|
|
118
|
-
client_id: process.env.LOCATION_CLIENT_ID!,
|
|
119
|
-
client_secret: process.env.LOCATION_CLIENT_SECRET!
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const data = await response.json()
|
|
124
|
-
return { token: data.access_token, apiUrl: 'https://api.locationservice.com' }
|
|
125
|
-
}
|
|
126
|
+
const geoPlaces = new GeoPlaces(apiUrl, token, map)
|
|
126
127
|
|
|
127
|
-
//
|
|
128
|
-
'
|
|
129
|
-
import { GeoPlaces } from '@chaosity/location-client'
|
|
130
|
-
import { getLocationToken } from '@/lib/actions/location-auth'
|
|
128
|
+
// Forward geocoding (search)
|
|
129
|
+
await geoPlaces.forwardGeocode({ query: 'Vancouver' })
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
// Reverse geocoding (coordinates to address)
|
|
132
|
+
await geoPlaces.reverseGeocode({ query: [-123.12, 49.28] })
|
|
134
133
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
map.addControl(geocoder)
|
|
134
|
+
// Get place details by ID
|
|
135
|
+
await geoPlaces.searchByPlaceId('place-id')
|
|
138
136
|
```
|
|
139
137
|
|
|
140
|
-
###
|
|
138
|
+
### Available Commands
|
|
141
139
|
|
|
142
|
-
|
|
143
|
-
// lib/actions/location-auth.ts (Server-side only)
|
|
144
|
-
'use server'
|
|
145
|
-
import { AuthClient } from '@chaosity/location-client'
|
|
140
|
+
All AWS Location Service commands from `@aws-sdk/client-geo-places`:
|
|
146
141
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
142
|
+
```typescript
|
|
143
|
+
import {
|
|
144
|
+
SuggestCommand,
|
|
145
|
+
GeocodeCommand,
|
|
146
|
+
ReverseGeocodeCommand,
|
|
147
|
+
GetPlaceCommand,
|
|
148
|
+
SearchTextCommand,
|
|
149
|
+
SearchNearbyCommand
|
|
150
|
+
} from '@chaosity/location-client'
|
|
156
151
|
```
|
|
157
152
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
### AuthHelper
|
|
153
|
+
### Data Type Utilities
|
|
161
154
|
|
|
162
|
-
|
|
155
|
+
GeoJSON conversion utilities from `@aws/amazon-location-utilities-datatypes`:
|
|
163
156
|
|
|
164
157
|
```typescript
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
158
|
+
import {
|
|
159
|
+
placeToFeatureCollection,
|
|
160
|
+
routeToFeatureCollection,
|
|
161
|
+
devicePositionsToFeatureCollection
|
|
162
|
+
} from '@chaosity/location-client'
|
|
169
163
|
```
|
|
170
164
|
|
|
171
|
-
|
|
165
|
+
## Logging
|
|
172
166
|
|
|
173
|
-
|
|
167
|
+
The library uses the `debug` package for optional verbose logging. Enable it via the `DEBUG` environment variable:
|
|
174
168
|
|
|
175
|
-
|
|
169
|
+
```bash
|
|
170
|
+
# Enable all location-client logs
|
|
171
|
+
DEBUG=location-client:* npm run dev
|
|
176
172
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const tokenResponse = await authClient.fetchToken({
|
|
180
|
-
client_id: string,
|
|
181
|
-
client_secret: string
|
|
182
|
-
})
|
|
183
|
-
// Returns: { access_token, token_type, expires_in }
|
|
184
|
-
```
|
|
173
|
+
# Enable only authentication logs
|
|
174
|
+
DEBUG=location-client:auth npm run dev
|
|
185
175
|
|
|
186
|
-
|
|
176
|
+
# Enable only API request logs
|
|
177
|
+
DEBUG=location-client:api npm run dev
|
|
187
178
|
|
|
188
|
-
|
|
179
|
+
# Enable multiple namespaces
|
|
180
|
+
DEBUG=location-client:*,express:* npm run dev
|
|
181
|
+
```
|
|
189
182
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
193
190
|
```
|
|
194
191
|
|
|
195
|
-
|
|
192
|
+
## Security Best Practices
|
|
193
|
+
|
|
194
|
+
⚠️ **NEVER expose client credentials in browser code!**
|
|
195
|
+
|
|
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
|
|
196
200
|
|
|
197
|
-
|
|
201
|
+
## Example: Complete MapLibre Setup
|
|
198
202
|
|
|
199
203
|
```typescript
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
'use client'
|
|
205
|
+
|
|
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'
|
|
210
|
+
|
|
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
|
+
}
|
|
205
245
|
```
|
|
206
246
|
|
|
207
|
-
##
|
|
247
|
+
## TypeScript Support
|
|
248
|
+
|
|
249
|
+
Full TypeScript support with types from AWS SDK:
|
|
208
250
|
|
|
209
|
-
|
|
251
|
+
```typescript
|
|
252
|
+
import type { SuggestCommandOutput } from '@aws-sdk/client-geo-places'
|
|
210
253
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
- `GetPlaceCommand` - Get place details by ID
|
|
254
|
+
const response: SuggestCommandOutput = await client.send(
|
|
255
|
+
new SuggestCommand({ QueryText: 'Vancouver' })
|
|
256
|
+
)
|
|
257
|
+
```
|
|
216
258
|
|
|
217
259
|
## License
|
|
218
260
|
|
|
@@ -10,10 +10,39 @@ export interface TokenProviderConfig {
|
|
|
10
10
|
clientId: string;
|
|
11
11
|
clientSecret: string;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* TokenProvider - SERVER-SIDE ONLY
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ WARNING: This class requires client credentials (clientId and clientSecret)
|
|
17
|
+
* and must NEVER be used in browser/client-side code.
|
|
18
|
+
*
|
|
19
|
+
* Use this only in:
|
|
20
|
+
* - Node.js server environments
|
|
21
|
+
* - Next.js Server Actions (marked with 'use server')
|
|
22
|
+
* - Next.js API routes
|
|
23
|
+
* - Backend services
|
|
24
|
+
*
|
|
25
|
+
* For browser usage, use the React provider which receives tokens from server-side code.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // ✓ Correct: Server-side usage
|
|
29
|
+
* import { TokenProvider } from '@chaosity/location-client'
|
|
30
|
+
*
|
|
31
|
+
* const provider = new TokenProvider({
|
|
32
|
+
* apiUrl: process.env.API_URL!,
|
|
33
|
+
* clientId: process.env.CLIENT_ID!,
|
|
34
|
+
* clientSecret: process.env.CLIENT_SECRET!,
|
|
35
|
+
* })
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // ✗ Wrong: Never use in browser code
|
|
39
|
+
* // This would expose your credentials!
|
|
40
|
+
*/
|
|
13
41
|
export declare class TokenProvider {
|
|
14
42
|
private config;
|
|
15
43
|
private cachedToken?;
|
|
16
44
|
private expiresAt?;
|
|
45
|
+
private oauth2Client;
|
|
17
46
|
constructor(config: TokenProviderConfig);
|
|
18
47
|
getToken(forceRefresh?: boolean): Promise<TokenResponse>;
|
|
19
48
|
private isExpired;
|
|
@@ -1,39 +1,78 @@
|
|
|
1
|
+
import ClientOAuth2 from 'client-oauth2';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
const log = debug('location-client:auth');
|
|
4
|
+
/**
|
|
5
|
+
* TokenProvider - SERVER-SIDE ONLY
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ WARNING: This class requires client credentials (clientId and clientSecret)
|
|
8
|
+
* and must NEVER be used in browser/client-side code.
|
|
9
|
+
*
|
|
10
|
+
* Use this only in:
|
|
11
|
+
* - Node.js server environments
|
|
12
|
+
* - Next.js Server Actions (marked with 'use server')
|
|
13
|
+
* - Next.js API routes
|
|
14
|
+
* - Backend services
|
|
15
|
+
*
|
|
16
|
+
* For browser usage, use the React provider which receives tokens from server-side code.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // ✓ Correct: Server-side usage
|
|
20
|
+
* import { TokenProvider } from '@chaosity/location-client'
|
|
21
|
+
*
|
|
22
|
+
* const provider = new TokenProvider({
|
|
23
|
+
* apiUrl: process.env.API_URL!,
|
|
24
|
+
* clientId: process.env.CLIENT_ID!,
|
|
25
|
+
* clientSecret: process.env.CLIENT_SECRET!,
|
|
26
|
+
* })
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* // ✗ Wrong: Never use in browser code
|
|
30
|
+
* // This would expose your credentials!
|
|
31
|
+
*/
|
|
1
32
|
export class TokenProvider {
|
|
2
33
|
constructor(config) {
|
|
34
|
+
// Runtime check: prevent usage in browser
|
|
35
|
+
if (typeof window !== 'undefined') {
|
|
36
|
+
throw new Error('TokenProvider cannot be used in browser environments. ' +
|
|
37
|
+
'It requires client credentials that must never be exposed to browsers. ' +
|
|
38
|
+
'Use @chaosity/location-client-react for browser usage.');
|
|
39
|
+
}
|
|
40
|
+
log('Initializing TokenProvider for %s', config.apiUrl);
|
|
3
41
|
this.config = config;
|
|
42
|
+
this.oauth2Client = new ClientOAuth2({
|
|
43
|
+
clientId: config.clientId,
|
|
44
|
+
clientSecret: config.clientSecret,
|
|
45
|
+
accessTokenUri: `${config.apiUrl}/auth/token`,
|
|
46
|
+
});
|
|
4
47
|
}
|
|
5
48
|
async getToken(forceRefresh = false) {
|
|
6
49
|
if (!forceRefresh && this.cachedToken && this.expiresAt && !this.isExpired()) {
|
|
50
|
+
log('Using cached token (expires in %ds)', Math.floor((this.expiresAt - Date.now()) / 1000));
|
|
7
51
|
return {
|
|
8
52
|
success: true,
|
|
9
53
|
token: this.cachedToken,
|
|
10
54
|
expiresAt: this.expiresAt
|
|
11
55
|
};
|
|
12
56
|
}
|
|
57
|
+
const reason = forceRefresh ? 'forced refresh' : (this.cachedToken ? 'token expired' : 'no cached token');
|
|
58
|
+
log('Refreshing token (%s) from %s', reason, this.config.apiUrl);
|
|
13
59
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}),
|
|
22
|
-
});
|
|
23
|
-
if (!response.ok) {
|
|
24
|
-
throw new Error(`Failed to get token: ${response.statusText}`);
|
|
25
|
-
}
|
|
26
|
-
const data = await response.json();
|
|
27
|
-
this.cachedToken = data.access_token;
|
|
28
|
-
this.expiresAt = Date.now() + (data.expires_in * 1000);
|
|
60
|
+
const token = await this.oauth2Client.credentials.getToken();
|
|
61
|
+
const expiresIn = typeof token.data?.expires_in === 'number'
|
|
62
|
+
? token.data.expires_in
|
|
63
|
+
: parseInt(token.data?.expires_in || '3600');
|
|
64
|
+
this.cachedToken = token.accessToken;
|
|
65
|
+
this.expiresAt = Date.now() + (expiresIn * 1000);
|
|
66
|
+
log('Token acquired successfully (expires in %ds)', expiresIn);
|
|
29
67
|
return {
|
|
30
68
|
success: true,
|
|
31
69
|
token: this.cachedToken,
|
|
32
|
-
expiresIn
|
|
70
|
+
expiresIn,
|
|
33
71
|
expiresAt: this.expiresAt,
|
|
34
72
|
};
|
|
35
73
|
}
|
|
36
74
|
catch (error) {
|
|
75
|
+
log('Token acquisition failed: %s', error instanceof Error ? error.message : 'Unknown error');
|
|
37
76
|
return {
|
|
38
77
|
success: false,
|
|
39
78
|
error: error instanceof Error ? error.message : 'Failed to get token',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
const log = debug('location-client:api');
|
|
2
4
|
/**
|
|
3
5
|
* GeoPlacesClient - AWS Location Service compatible client with custom auth
|
|
4
6
|
*
|
|
@@ -12,7 +14,10 @@ export class GeoPlacesClient {
|
|
|
12
14
|
}
|
|
13
15
|
async send(command) {
|
|
14
16
|
const endpoint = this.getEndpoint(command);
|
|
15
|
-
const
|
|
17
|
+
const url = `${this.clientConfig.apiUrl}${endpoint}`;
|
|
18
|
+
log('Sending %s request to %s', command.constructor.name, endpoint);
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
const response = await fetch(url, {
|
|
16
21
|
method: 'POST',
|
|
17
22
|
headers: {
|
|
18
23
|
'Content-Type': 'application/json',
|
|
@@ -20,9 +25,12 @@ export class GeoPlacesClient {
|
|
|
20
25
|
},
|
|
21
26
|
body: JSON.stringify(command)
|
|
22
27
|
});
|
|
28
|
+
const duration = Date.now() - startTime;
|
|
23
29
|
if (!response.ok) {
|
|
30
|
+
log('Request failed: %s %s (%dms)', response.status, response.statusText, duration);
|
|
24
31
|
throw new Error(`API request failed: ${response.statusText}`);
|
|
25
32
|
}
|
|
33
|
+
log('Request successful: %s (%dms)', response.status, duration);
|
|
26
34
|
return response.json();
|
|
27
35
|
}
|
|
28
36
|
getEndpoint(command) {
|
|
@@ -13,4 +13,6 @@ export type ServerAuthConfig = TokenProviderConfig;
|
|
|
13
13
|
* NEVER call from browser/client code as it exposes credentials.
|
|
14
14
|
* For SPA projects, create your own backend endpoint that calls this.
|
|
15
15
|
*/
|
|
16
|
-
export declare function getClientConfig(config: ServerAuthConfig): Promise<ClientConfig
|
|
16
|
+
export declare function getClientConfig(config: ServerAuthConfig): Promise<ClientConfig & {
|
|
17
|
+
expiresAt?: number;
|
|
18
|
+
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaosity/location-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Client library for Chaosity Location Service with AWS Location Service compatibility",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@aws-sdk/client-geo-places": "^3.0.0",
|
|
33
|
-
"@aws/amazon-location-utilities-datatypes": "^1.0.0"
|
|
33
|
+
"@aws/amazon-location-utilities-datatypes": "^1.0.0",
|
|
34
|
+
"client-oauth2": "^4.3.3",
|
|
35
|
+
"debug": "^4.4.3"
|
|
34
36
|
},
|
|
35
37
|
"peerDependencies": {
|
|
36
38
|
"maplibre-gl": "^4.0.0"
|
|
@@ -41,6 +43,7 @@
|
|
|
41
43
|
}
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
46
|
+
"@types/debug": "^4.1.12",
|
|
44
47
|
"typescript": "^5.0.0"
|
|
45
48
|
},
|
|
46
49
|
"files": [
|