@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 response = await fetch(`${this.config.apiUrl}/auth/token`, {
15
- method: 'POST',
16
- headers: { 'Content-Type': 'application/json' },
17
- body: JSON.stringify({
18
- client_id: this.config.clientId,
19
- client_secret: this.config.clientSecret,
20
- grant_type: 'client_credentials',
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: data.expires_in,
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 response = await fetch(`${this.clientConfig.apiUrl}${endpoint}`, {
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
+ }>;
@@ -20,5 +20,6 @@ export async function getClientConfig(config) {
20
20
  return {
21
21
  apiUrl: config.apiUrl,
22
22
  token: result.token,
23
+ expiresAt: result.expiresAt,
23
24
  };
24
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaosity/location-client",
3
- "version": "0.1.3",
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": [