@chaosity/location-client 0.1.3 → 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
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
AWS Location Service compatible client with custom Bearer token authentication.
|
|
4
4
|
|
|
5
|
+
## ⚠️ Security Warning
|
|
6
|
+
|
|
7
|
+
**This package contains server-side authentication utilities that require client credentials.**
|
|
8
|
+
|
|
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
|
|
16
|
+
|
|
17
|
+
**For React applications**, use [`@chaosity/location-client-react`](https://www.npmjs.com/package/@chaosity/location-client-react) which handles authentication safely.
|
|
18
|
+
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
7
21
|
```bash
|
|
@@ -148,6 +162,33 @@ import {
|
|
|
148
162
|
} from '@chaosity/location-client'
|
|
149
163
|
```
|
|
150
164
|
|
|
165
|
+
## Logging
|
|
166
|
+
|
|
167
|
+
The library uses the `debug` package for optional verbose logging. Enable it via the `DEBUG` environment variable:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Enable all location-client logs
|
|
171
|
+
DEBUG=location-client:* npm run dev
|
|
172
|
+
|
|
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
|
|
178
|
+
|
|
179
|
+
# Enable multiple namespaces
|
|
180
|
+
DEBUG=location-client:*,express:* npm run dev
|
|
181
|
+
```
|
|
182
|
+
|
|
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
|
+
```
|
|
191
|
+
|
|
151
192
|
## Security Best Practices
|
|
152
193
|
|
|
153
194
|
⚠️ **NEVER expose client credentials in browser code!**
|
|
@@ -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": [
|