@chaosity/location-client 0.1.5 → 0.1.6
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/dist/adapters/GeoPlaces.js +45 -75
- package/dist/auth/TokenProvider.d.ts +3 -3
- package/dist/auth/TokenProvider.js +39 -30
- package/dist/client/GeoPlacesClient.d.ts +3 -1
- package/dist/client/GeoPlacesClient.js +24 -30
- package/dist/index.d.ts +0 -6
- package/dist/index.js +1 -5
- package/dist/maps/Utils.d.ts +1 -7
- package/dist/maps/Utils.js +3 -22
- package/dist/server/LocationServiceConnector.js +24 -22
- package/dist/server/getClientConfig.d.ts +1 -0
- package/dist/server/getClientConfig.js +20 -3
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +1 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +9 -6
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { geocodeResponseToFeatureCollection, getPlaceResponseToFeatureCollection, reverseGeocodeResponseToFeatureCollection } from '@aws/amazon-location-utilities-datatypes';
|
|
1
|
+
import { GeocodeCommand, GetPlaceAdditionalFeature, GetPlaceCommand, ReverseGeocodeCommand, SuggestAdditionalFeature, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
|
+
import { geocodeResponseToFeatureCollection, getPlaceResponseToFeatureCollection, reverseGeocodeResponseToFeatureCollection, } from '@aws/amazon-location-utilities-datatypes';
|
|
3
|
+
import debug from 'debug';
|
|
4
|
+
const log = debug('location-client:geocoder');
|
|
3
5
|
/**
|
|
4
6
|
* GeoPlaces - MapLibre adapter for AWS Location Service
|
|
5
7
|
*
|
|
@@ -18,7 +20,7 @@ export class GeoPlaces {
|
|
|
18
20
|
return 'en';
|
|
19
21
|
}
|
|
20
22
|
async forwardGeocode(config) {
|
|
21
|
-
|
|
23
|
+
log('forwardGeocode query=%s', config.query);
|
|
22
24
|
const center = this.map.getCenter();
|
|
23
25
|
const biasPosition = config.proximity && config.proximity.length >= 2
|
|
24
26
|
? [config.proximity[0], config.proximity[1]]
|
|
@@ -29,61 +31,33 @@ export class GeoPlaces {
|
|
|
29
31
|
MaxResults: config.limit || 5,
|
|
30
32
|
Language: this.normalizeLanguage(config.language),
|
|
31
33
|
};
|
|
32
|
-
if (config.countries
|
|
33
|
-
commandInput.Filter = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (config.bbox)
|
|
37
|
-
commandInput.Filter.BoundingBox = config.bbox;
|
|
34
|
+
if (config.countries) {
|
|
35
|
+
commandInput.Filter = {
|
|
36
|
+
IncludeCountries: Array.isArray(config.countries) ? config.countries : config.countries.split(','),
|
|
37
|
+
};
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
const command = new GeocodeCommand(commandInput);
|
|
41
|
-
const response = await this.client.send(command);
|
|
42
|
-
console.log('[GeoPlaces] SearchTextCommand response:', response);
|
|
39
|
+
const response = await this.client.send(new GeocodeCommand(commandInput));
|
|
43
40
|
const result = geocodeResponseToFeatureCollection(response, { flattenProperties: true });
|
|
44
|
-
|
|
41
|
+
log('forwardGeocode returned %d results', result.features?.length ?? 0);
|
|
45
42
|
return result;
|
|
46
43
|
}
|
|
47
44
|
async reverseGeocode(config) {
|
|
48
|
-
|
|
45
|
+
log('reverseGeocode query=%o', config.query);
|
|
49
46
|
const queryPosition = Array.isArray(config.query) && config.query.length >= 2
|
|
50
47
|
? [config.query[0], config.query[1]]
|
|
51
48
|
: [0, 0];
|
|
52
49
|
const commandInput = {
|
|
53
50
|
QueryPosition: queryPosition,
|
|
54
51
|
MaxResults: config.limit || 1,
|
|
55
|
-
Language: this.normalizeLanguage(config.language)
|
|
52
|
+
Language: this.normalizeLanguage(config.language),
|
|
56
53
|
};
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const response = await this.client.send(command);
|
|
62
|
-
const maplibreGeocoderFeatureResults = reverseGeocodeResponseToFeatureCollection(response, { flattenProperties: true });
|
|
63
|
-
maplibreGeocoderFeatureResults.features.forEach(feature => {
|
|
64
|
-
});
|
|
65
|
-
console.log('[GeoPlaces] Converted to FeatureCollection:', maplibreGeocoderFeatureResults);
|
|
66
|
-
return maplibreGeocoderFeatureResults;
|
|
54
|
+
const response = await this.client.send(new ReverseGeocodeCommand(commandInput));
|
|
55
|
+
const result = reverseGeocodeResponseToFeatureCollection(response, { flattenProperties: true });
|
|
56
|
+
log('reverseGeocode returned %d results', result.features?.length ?? 0);
|
|
57
|
+
return result;
|
|
67
58
|
}
|
|
68
59
|
async getSuggestions(config) {
|
|
69
|
-
|
|
70
|
-
// const biasPosition = config.proximity && config.proximity.length >= 2
|
|
71
|
-
// ? [config.proximity[0], config.proximity[1]]
|
|
72
|
-
// : [center.lng, center.lat]
|
|
73
|
-
// const commandInput: any = {
|
|
74
|
-
// QueryText: config.query as string,
|
|
75
|
-
// BiasPosition: biasPosition,
|
|
76
|
-
// MaxResults: config.limit || 5,
|
|
77
|
-
// Language: this.normalizeLanguage(config.language),
|
|
78
|
-
// AdditionalFeatures: [SuggestAdditionalFeature.CORE],
|
|
79
|
-
// }
|
|
80
|
-
// if (config.countries || config.bbox) {
|
|
81
|
-
// commandInput.Filter = {}
|
|
82
|
-
// if (config.countries) commandInput.Filter.IncludeCountries = config.countries
|
|
83
|
-
// if (config.bbox) commandInput.Filter.BoundingBox = config.bbox
|
|
84
|
-
// }
|
|
85
|
-
// const command = new SuggestCommand(commandInput)
|
|
86
|
-
// const response = await this.client.send(command) as SuggestResponse
|
|
60
|
+
log('getSuggestions query=%s', config.query);
|
|
87
61
|
const center = this.map.getCenter();
|
|
88
62
|
const biasPosition = config.proximity && config.proximity.length >= 2
|
|
89
63
|
? [config.proximity[0], config.proximity[1]]
|
|
@@ -93,55 +67,51 @@ export class GeoPlaces {
|
|
|
93
67
|
BiasPosition: biasPosition,
|
|
94
68
|
MaxResults: config.limit || 5,
|
|
95
69
|
Language: this.normalizeLanguage(config.language),
|
|
96
|
-
AdditionalFeatures: [
|
|
70
|
+
AdditionalFeatures: [SuggestAdditionalFeature.CORE],
|
|
71
|
+
...(config.countries || config.bbox ? {
|
|
72
|
+
Filter: {
|
|
73
|
+
...(config.countries ? { IncludeCountries: Array.isArray(config.countries) ? config.countries : config.countries.split(',') } : {}),
|
|
74
|
+
...(config.bbox ? { BoundingBox: config.bbox } : {}),
|
|
75
|
+
},
|
|
76
|
+
} : {}),
|
|
97
77
|
};
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
const response = await this.client.send(new SuggestCommand(commandInput));
|
|
79
|
+
const suggestions = { suggestions: [] };
|
|
80
|
+
for (const item of response.ResultItems ?? []) {
|
|
81
|
+
const text = item.Title;
|
|
82
|
+
if (!text)
|
|
83
|
+
continue;
|
|
84
|
+
const placeId = item.Place?.PlaceId;
|
|
85
|
+
suggestions.suggestions.push({ text, placeId });
|
|
103
86
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const response = await this.client.send(command);
|
|
107
|
-
console.log('[GeoPlaces] SearchTextCommand response:', response);
|
|
108
|
-
const maplibreGeocoderSuggestionResults = { suggestions: [] };
|
|
109
|
-
response?.ResultItems?.forEach(item => {
|
|
110
|
-
const text = item?.Title || item?.Address?.Label;
|
|
111
|
-
const placeId = item?.PlaceId;
|
|
112
|
-
if (text) {
|
|
113
|
-
maplibreGeocoderSuggestionResults.suggestions.push({
|
|
114
|
-
text,
|
|
115
|
-
placeId,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
console.log('[GeoPlaces] Converted to MaplibreGeocoderSuggestionResults:', maplibreGeocoderSuggestionResults);
|
|
120
|
-
return maplibreGeocoderSuggestionResults;
|
|
87
|
+
log('getSuggestions returned %d suggestions', suggestions.suggestions.length);
|
|
88
|
+
return suggestions;
|
|
121
89
|
}
|
|
122
90
|
async searchByPlaceId(config) {
|
|
123
|
-
|
|
124
|
-
const placeId = config.query;
|
|
91
|
+
log('searchByPlaceId placeId=%s', config.query);
|
|
125
92
|
const command = new GetPlaceCommand({
|
|
126
|
-
PlaceId:
|
|
93
|
+
PlaceId: config.query,
|
|
127
94
|
Language: this.normalizeLanguage(config.language),
|
|
128
|
-
AdditionalFeatures: [
|
|
95
|
+
AdditionalFeatures: [
|
|
96
|
+
GetPlaceAdditionalFeature.ACCESS,
|
|
97
|
+
GetPlaceAdditionalFeature.SECONDARY_ADDRESSES,
|
|
98
|
+
GetPlaceAdditionalFeature.CONTACT,
|
|
99
|
+
GetPlaceAdditionalFeature.TIME_ZONE,
|
|
100
|
+
],
|
|
129
101
|
});
|
|
130
102
|
const response = await this.client.send(command);
|
|
131
103
|
const result = getPlaceResponseToFeatureCollection(response, { flattenProperties: true });
|
|
132
|
-
console.log('[GeoPlaces] GetPlaceCommand response converted to FeatureCollection:', result);
|
|
133
104
|
const carmenGeojsonFeatures = result.features.map(feature => ({
|
|
134
105
|
...feature,
|
|
135
106
|
text: response.Title || '',
|
|
136
107
|
place_name: response.Address?.Label || '',
|
|
137
108
|
place_type: response.PlaceType ? [response.PlaceType] : [],
|
|
138
|
-
bbox: response.MapView
|
|
109
|
+
bbox: response.MapView,
|
|
139
110
|
}));
|
|
140
|
-
|
|
111
|
+
log('searchByPlaceId returned %d features', carmenGeojsonFeatures.length);
|
|
141
112
|
return { place: carmenGeojsonFeatures };
|
|
142
113
|
}
|
|
143
114
|
async localGeocode(config) {
|
|
144
|
-
console.log('[GeoPlaces] localGeocode called with:', config);
|
|
145
115
|
return this.forwardGeocode(config);
|
|
146
116
|
}
|
|
147
117
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface TokenResponse {
|
|
2
2
|
success: boolean;
|
|
3
3
|
token?: string;
|
|
4
|
+
expiresAt?: number;
|
|
4
5
|
error?: string;
|
|
5
6
|
}
|
|
6
7
|
export interface TokenProviderConfig {
|
|
@@ -24,7 +25,7 @@ export interface TokenProviderConfig {
|
|
|
24
25
|
*
|
|
25
26
|
* @example
|
|
26
27
|
* // ✓ Correct: Server-side usage
|
|
27
|
-
* import { TokenProvider } from '@chaosity/location-client'
|
|
28
|
+
* import { TokenProvider } from '@chaosity/location-client/server'
|
|
28
29
|
*
|
|
29
30
|
* const provider = new TokenProvider({
|
|
30
31
|
* apiUrl: process.env.API_URL!,
|
|
@@ -39,12 +40,11 @@ export interface TokenProviderConfig {
|
|
|
39
40
|
export declare class TokenProvider {
|
|
40
41
|
private config;
|
|
41
42
|
private cachedToken?;
|
|
42
|
-
private
|
|
43
|
+
private cachedExpiresAt?;
|
|
43
44
|
private tokenPromise?;
|
|
44
45
|
constructor(config: TokenProviderConfig);
|
|
45
46
|
getToken(forceRefresh?: boolean): Promise<TokenResponse>;
|
|
46
47
|
private fetchToken;
|
|
47
|
-
private getTokenExpiry;
|
|
48
48
|
private isExpired;
|
|
49
49
|
clearCache(): void;
|
|
50
50
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import ClientOAuth2 from 'client-oauth2';
|
|
2
|
-
import { decodeJwt } from 'jose';
|
|
3
1
|
import debug from 'debug';
|
|
4
2
|
const log = debug('location-client:auth');
|
|
5
3
|
/**
|
|
@@ -18,7 +16,7 @@ const log = debug('location-client:auth');
|
|
|
18
16
|
*
|
|
19
17
|
* @example
|
|
20
18
|
* // ✓ Correct: Server-side usage
|
|
21
|
-
* import { TokenProvider } from '@chaosity/location-client'
|
|
19
|
+
* import { TokenProvider } from '@chaosity/location-client/server'
|
|
22
20
|
*
|
|
23
21
|
* const provider = new TokenProvider({
|
|
24
22
|
* apiUrl: process.env.API_URL!,
|
|
@@ -40,19 +38,15 @@ export class TokenProvider {
|
|
|
40
38
|
}
|
|
41
39
|
log('Initializing TokenProvider for %s', config.apiUrl);
|
|
42
40
|
this.config = config;
|
|
43
|
-
this.oauth2Client = new ClientOAuth2({
|
|
44
|
-
clientId: config.clientId,
|
|
45
|
-
clientSecret: config.clientSecret,
|
|
46
|
-
accessTokenUri: `${config.apiUrl}/auth/token`,
|
|
47
|
-
});
|
|
48
41
|
}
|
|
49
42
|
async getToken(forceRefresh = false) {
|
|
50
43
|
if (!forceRefresh && this.cachedToken && !this.isExpired()) {
|
|
51
|
-
const
|
|
52
|
-
log('Using cached token (expires in %ds)',
|
|
44
|
+
const expiresIn = this.cachedExpiresAt ? Math.floor((this.cachedExpiresAt - Date.now()) / 1000) : 'unknown';
|
|
45
|
+
log('Using cached token (expires in %ds)', expiresIn);
|
|
53
46
|
return {
|
|
54
47
|
success: true,
|
|
55
|
-
token: this.cachedToken
|
|
48
|
+
token: this.cachedToken,
|
|
49
|
+
expiresAt: this.cachedExpiresAt
|
|
56
50
|
};
|
|
57
51
|
}
|
|
58
52
|
// If token fetch is already in progress, wait for it
|
|
@@ -75,13 +69,39 @@ export class TokenProvider {
|
|
|
75
69
|
}
|
|
76
70
|
async fetchToken() {
|
|
77
71
|
try {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
72
|
+
const { clientId, clientSecret, apiUrl } = this.config;
|
|
73
|
+
const credentials = btoa(`${clientId}:${clientSecret}`);
|
|
74
|
+
const response = await fetch(`${apiUrl}/auth/token`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: {
|
|
77
|
+
'Authorization': `Basic ${credentials}`,
|
|
78
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
79
|
+
},
|
|
80
|
+
body: new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const errorText = await response.text();
|
|
84
|
+
let errorMessage = `Token request failed: ${response.statusText}`;
|
|
85
|
+
try {
|
|
86
|
+
const errorData = JSON.parse(errorText);
|
|
87
|
+
if (errorData.error_description)
|
|
88
|
+
errorMessage = errorData.error_description;
|
|
89
|
+
else if (errorData.error)
|
|
90
|
+
errorMessage = errorData.error;
|
|
91
|
+
}
|
|
92
|
+
catch { /* use statusText */ }
|
|
93
|
+
throw new Error(errorMessage);
|
|
94
|
+
}
|
|
95
|
+
const data = await response.json();
|
|
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);
|
|
82
101
|
return {
|
|
83
102
|
success: true,
|
|
84
|
-
token: this.cachedToken
|
|
103
|
+
token: this.cachedToken,
|
|
104
|
+
expiresAt: this.cachedExpiresAt,
|
|
85
105
|
};
|
|
86
106
|
}
|
|
87
107
|
catch (error) {
|
|
@@ -92,24 +112,13 @@ export class TokenProvider {
|
|
|
92
112
|
};
|
|
93
113
|
}
|
|
94
114
|
}
|
|
95
|
-
getTokenExpiry() {
|
|
96
|
-
if (!this.cachedToken)
|
|
97
|
-
return null;
|
|
98
|
-
try {
|
|
99
|
-
const decoded = decodeJwt(this.cachedToken);
|
|
100
|
-
return decoded.exp || null;
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
115
|
isExpired(bufferSeconds = 60) {
|
|
107
|
-
|
|
108
|
-
if (!exp)
|
|
116
|
+
if (!this.cachedExpiresAt)
|
|
109
117
|
return true;
|
|
110
|
-
return Date.now()
|
|
118
|
+
return Date.now() >= (this.cachedExpiresAt - bufferSeconds * 1000);
|
|
111
119
|
}
|
|
112
120
|
clearCache() {
|
|
113
121
|
this.cachedToken = undefined;
|
|
122
|
+
this.cachedExpiresAt = undefined;
|
|
114
123
|
}
|
|
115
124
|
}
|
|
@@ -2,8 +2,10 @@ import { ClientConfig } from '../types';
|
|
|
2
2
|
/**
|
|
3
3
|
* GeoPlacesClient - AWS Location Service compatible client with custom auth
|
|
4
4
|
*
|
|
5
|
-
* Uses AWS
|
|
5
|
+
* Uses AWS SDK command classes but replaces SigV4 auth with Bearer token.
|
|
6
6
|
* All request/response types are identical to AWS Location Service.
|
|
7
|
+
*
|
|
8
|
+
* Pass `getToken` in config to support live token refresh without recreating the client.
|
|
7
9
|
*/
|
|
8
10
|
export declare class GeoPlacesClient {
|
|
9
11
|
private clientConfig;
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
|
+
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
3
|
const log = debug('location-client:api');
|
|
3
4
|
const logError = debug('location-client:api:error');
|
|
5
|
+
// Minification-safe endpoint map — uses constructor identity, not class name strings
|
|
6
|
+
const ENDPOINT_MAP = new Map([
|
|
7
|
+
[AutocompleteCommand, '/address/autocomplete'],
|
|
8
|
+
[GeocodeCommand, '/address/geocode'],
|
|
9
|
+
[GetPlaceCommand, '/address/place'],
|
|
10
|
+
[ReverseGeocodeCommand, '/address/search/reverse-geocode'],
|
|
11
|
+
[SearchNearbyCommand, '/address/search/nearby'],
|
|
12
|
+
[SearchTextCommand, '/address/search/text'],
|
|
13
|
+
[SuggestCommand, '/address/suggestion'],
|
|
14
|
+
]);
|
|
4
15
|
/**
|
|
5
16
|
* GeoPlacesClient - AWS Location Service compatible client with custom auth
|
|
6
17
|
*
|
|
7
|
-
* Uses AWS
|
|
18
|
+
* Uses AWS SDK command classes but replaces SigV4 auth with Bearer token.
|
|
8
19
|
* All request/response types are identical to AWS Location Service.
|
|
20
|
+
*
|
|
21
|
+
* Pass `getToken` in config to support live token refresh without recreating the client.
|
|
9
22
|
*/
|
|
10
23
|
export class GeoPlacesClient {
|
|
11
24
|
constructor(config) {
|
|
@@ -17,62 +30,43 @@ export class GeoPlacesClient {
|
|
|
17
30
|
const url = `${this.clientConfig.apiUrl}${endpoint}`;
|
|
18
31
|
const commandName = command.constructor.name;
|
|
19
32
|
const input = command.input;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
log('Sending %s
|
|
33
|
+
// Prefer getToken callback (live ref) over static token string
|
|
34
|
+
const token = this.clientConfig.getToken?.() ?? this.clientConfig.token;
|
|
35
|
+
log('Sending %s to %s', commandName, endpoint);
|
|
23
36
|
const startTime = Date.now();
|
|
24
37
|
const response = await fetch(url, {
|
|
25
38
|
method: 'POST',
|
|
26
39
|
headers: {
|
|
27
40
|
'Content-Type': 'application/json',
|
|
28
|
-
'Authorization': `Bearer ${
|
|
41
|
+
'Authorization': `Bearer ${token}`,
|
|
29
42
|
},
|
|
30
|
-
body: JSON.stringify(input)
|
|
43
|
+
body: JSON.stringify(input),
|
|
31
44
|
});
|
|
32
45
|
const duration = Date.now() - startTime;
|
|
33
46
|
if (!response.ok) {
|
|
34
47
|
const errorText = await response.text();
|
|
35
|
-
log(`[GeoPlacesClient] Request failed: ${response.status} ${response.statusText}`, errorText);
|
|
36
48
|
logError('Request failed: %s %s (%dms)', response.status, response.statusText, duration);
|
|
37
|
-
// Try to parse error message from response
|
|
38
49
|
let errorMessage = `API request failed: ${response.statusText}`;
|
|
39
50
|
try {
|
|
40
51
|
const errorData = JSON.parse(errorText);
|
|
41
|
-
if (errorData.message)
|
|
52
|
+
if (errorData.message)
|
|
42
53
|
errorMessage = errorData.message;
|
|
43
|
-
}
|
|
44
54
|
}
|
|
45
55
|
catch {
|
|
46
|
-
// If not JSON, use raw text if available
|
|
47
56
|
if (errorText)
|
|
48
57
|
errorMessage = errorText;
|
|
49
58
|
}
|
|
50
59
|
throw new Error(errorMessage);
|
|
51
60
|
}
|
|
52
61
|
const result = await response.json();
|
|
53
|
-
log(`[GeoPlacesClient] Response (${duration}ms):`, JSON.stringify(result, null, 2));
|
|
54
62
|
log('Request successful: %s (%dms)', response.status, duration);
|
|
55
63
|
return result;
|
|
56
64
|
}
|
|
57
65
|
getEndpoint(command) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return '/address/autocomplete';
|
|
62
|
-
case 'GeocodeCommand':
|
|
63
|
-
return '/address/geocode';
|
|
64
|
-
case 'GetPlaceCommand':
|
|
65
|
-
return '/address/place';
|
|
66
|
-
case 'ReverseGeocodeCommand':
|
|
67
|
-
return '/address/search/reverse-geocode';
|
|
68
|
-
case 'SearchNearbyCommand':
|
|
69
|
-
return '/address/search/nearby';
|
|
70
|
-
case 'SearchTextCommand':
|
|
71
|
-
return '/address/search/text';
|
|
72
|
-
case 'SuggestCommand':
|
|
73
|
-
return '/address/suggestion';
|
|
74
|
-
default:
|
|
75
|
-
throw new Error(`Unknown command type: ${commandName}`);
|
|
66
|
+
for (const [CommandClass, endpoint] of ENDPOINT_MAP) {
|
|
67
|
+
if (command instanceof CommandClass)
|
|
68
|
+
return endpoint;
|
|
76
69
|
}
|
|
70
|
+
throw new Error(`Unknown command type: ${command.constructor?.name ?? typeof command}`);
|
|
77
71
|
}
|
|
78
72
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { TokenProvider } from './auth/TokenProvider';
|
|
2
|
-
export type { TokenResponse, TokenProviderConfig } from './auth/TokenProvider';
|
|
3
1
|
export { GeoPlacesClient } from './client/GeoPlacesClient';
|
|
4
2
|
export * from '@aws-sdk/client-geo-places';
|
|
5
3
|
export * from '@aws/amazon-location-utilities-datatypes';
|
|
@@ -7,7 +5,3 @@ export { GeoPlaces } from './adapters/GeoPlaces';
|
|
|
7
5
|
export { createTransformRequest } from './maps/createTransformRequest';
|
|
8
6
|
export { transformRequest } from './maps/Utils';
|
|
9
7
|
export type { ClientConfig } from './types';
|
|
10
|
-
export { getClientConfig } from './server/getClientConfig';
|
|
11
|
-
export type { ServerAuthConfig } from './server/getClientConfig';
|
|
12
|
-
export { LocationServiceConnector } from './server/LocationServiceConnector';
|
|
13
|
-
export type { ConnectorConfig } from './server/LocationServiceConnector';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// Auth (Custom - only difference from AWS SDK)
|
|
2
|
-
export { TokenProvider } from './auth/TokenProvider';
|
|
3
1
|
// Client (Custom - uses our auth instead of AWS SigV4)
|
|
4
2
|
export { GeoPlacesClient } from './client/GeoPlacesClient';
|
|
5
3
|
// Re-export AWS SDK commands and types
|
|
@@ -11,6 +9,4 @@ export { GeoPlaces } from './adapters/GeoPlaces';
|
|
|
11
9
|
// Maps utilities
|
|
12
10
|
export { createTransformRequest } from './maps/createTransformRequest';
|
|
13
11
|
export { transformRequest } from './maps/Utils';
|
|
14
|
-
// Server-only utilities
|
|
15
|
-
export { getClientConfig } from './server/getClientConfig';
|
|
16
|
-
export { LocationServiceConnector } from './server/LocationServiceConnector';
|
|
12
|
+
// Server-only utilities are available via '@chaosity/location-client/server'
|
package/dist/maps/Utils.d.ts
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import { ClientConfig } from "../types";
|
|
2
|
-
export declare function transformRequest(url: string, config: ClientConfig):
|
|
3
|
-
url: string;
|
|
4
|
-
headers: Record<string, string>;
|
|
5
|
-
} | {
|
|
6
|
-
url: string;
|
|
7
|
-
headers?: undefined;
|
|
8
|
-
};
|
|
2
|
+
export declare function transformRequest(url: string, config: ClientConfig): import("maplibre-gl").RequestParameters | undefined;
|
package/dist/maps/Utils.js
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
|
+
import { createTransformRequest } from "./createTransformRequest";
|
|
1
2
|
export function transformRequest(url, config) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
'Authorization': `Bearer ${config.token}`,
|
|
5
|
-
};
|
|
6
|
-
if (url.includes('/tiles/')) {
|
|
7
|
-
headers['Accept'] = 'application/x-protobuf';
|
|
8
|
-
}
|
|
9
|
-
else if (url.includes('/glyphs/')) {
|
|
10
|
-
headers['Accept'] = 'application/x-protobuf';
|
|
11
|
-
}
|
|
12
|
-
else if (url.includes('/sprites/') && url.endsWith('.png')) {
|
|
13
|
-
headers['Accept'] = 'image/png';
|
|
14
|
-
}
|
|
15
|
-
else if (url.includes('/sprites/') && url.endsWith('.json')) {
|
|
16
|
-
headers['Accept'] = 'application/json';
|
|
17
|
-
}
|
|
18
|
-
else if (url.includes('/descriptor')) {
|
|
19
|
-
headers['Accept'] = 'application/json';
|
|
20
|
-
}
|
|
21
|
-
return { url, headers };
|
|
22
|
-
}
|
|
23
|
-
return { url };
|
|
3
|
+
const token = config.getToken?.() ?? config.token;
|
|
4
|
+
return createTransformRequest(config.apiUrl, () => token)(url);
|
|
24
5
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
1
2
|
import debug from 'debug';
|
|
2
3
|
import { getClientConfig } from './getClientConfig';
|
|
3
4
|
const log = debug('location-client:connector');
|
|
@@ -40,9 +41,14 @@ export class LocationServiceConnector {
|
|
|
40
41
|
async send(command, options) {
|
|
41
42
|
const config = await this.configPromise;
|
|
42
43
|
// Get token - ConnectorConfig may have getToken, ServerClientConfig has static token
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
let token;
|
|
45
|
+
if ('getToken' in config && typeof config.getToken === 'function') {
|
|
46
|
+
const result = await config.getToken();
|
|
47
|
+
token = result ? (typeof result === 'string' ? result : result.token) : undefined;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
token = config.token;
|
|
51
|
+
}
|
|
46
52
|
if (!token) {
|
|
47
53
|
throw new Error('No token available');
|
|
48
54
|
}
|
|
@@ -87,24 +93,20 @@ export class LocationServiceConnector {
|
|
|
87
93
|
return result;
|
|
88
94
|
}
|
|
89
95
|
getEndpoint(command) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return '/address/suggestion';
|
|
106
|
-
default:
|
|
107
|
-
throw new Error(`Unknown command type: ${commandName}`);
|
|
108
|
-
}
|
|
96
|
+
if (command instanceof AutocompleteCommand)
|
|
97
|
+
return '/address/autocomplete';
|
|
98
|
+
if (command instanceof GeocodeCommand)
|
|
99
|
+
return '/address/geocode';
|
|
100
|
+
if (command instanceof GetPlaceCommand)
|
|
101
|
+
return '/address/place';
|
|
102
|
+
if (command instanceof ReverseGeocodeCommand)
|
|
103
|
+
return '/address/search/reverse-geocode';
|
|
104
|
+
if (command instanceof SearchNearbyCommand)
|
|
105
|
+
return '/address/search/nearby';
|
|
106
|
+
if (command instanceof SearchTextCommand)
|
|
107
|
+
return '/address/search/text';
|
|
108
|
+
if (command instanceof SuggestCommand)
|
|
109
|
+
return '/address/suggestion';
|
|
110
|
+
throw new Error(`Unknown command type: ${command.constructor?.name ?? typeof command}`);
|
|
109
111
|
}
|
|
110
112
|
}
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
import { TokenProvider } from '../auth/TokenProvider';
|
|
3
3
|
const log = debug('location-client:clientConfig');
|
|
4
|
+
// Singleton instance to prevent race conditions
|
|
5
|
+
let tokenProviderInstance = null;
|
|
6
|
+
let currentConfig = null;
|
|
7
|
+
function getTokenProvider(apiUrl, clientId, clientSecret) {
|
|
8
|
+
const configKey = `${apiUrl}:${clientId}`;
|
|
9
|
+
// Reuse existing instance if config matches
|
|
10
|
+
if (tokenProviderInstance && currentConfig === configKey) {
|
|
11
|
+
log('[getTokenProvider] Reusing existing TokenProvider instance');
|
|
12
|
+
return tokenProviderInstance;
|
|
13
|
+
}
|
|
14
|
+
// Create new instance if config changed
|
|
15
|
+
log('[getTokenProvider] Creating new TokenProvider instance');
|
|
16
|
+
tokenProviderInstance = new TokenProvider({ apiUrl, clientId, clientSecret });
|
|
17
|
+
currentConfig = configKey;
|
|
18
|
+
return tokenProviderInstance;
|
|
19
|
+
}
|
|
4
20
|
/**
|
|
5
21
|
* Get client configuration with OAuth2 authentication.
|
|
6
22
|
*
|
|
@@ -50,8 +66,8 @@ export async function getClientConfig(config = {}) {
|
|
|
50
66
|
throw new Error('Missing required configuration. Set environment variables: ' +
|
|
51
67
|
'LOCATION_API_URL, LOCATION_CLIENT_ID, LOCATION_CLIENT_SECRET');
|
|
52
68
|
}
|
|
53
|
-
log('[getClientConfig]
|
|
54
|
-
const provider =
|
|
69
|
+
log('[getClientConfig] Getting TokenProvider instance');
|
|
70
|
+
const provider = getTokenProvider(apiUrl, clientId, clientSecret);
|
|
55
71
|
log('[getClientConfig] Fetching token');
|
|
56
72
|
const result = await provider.getToken();
|
|
57
73
|
if (!result.success || !result.token) {
|
|
@@ -61,6 +77,7 @@ export async function getClientConfig(config = {}) {
|
|
|
61
77
|
log('[getClientConfig] Token fetched successfully, length:', result.token.length);
|
|
62
78
|
return {
|
|
63
79
|
apiUrl,
|
|
64
|
-
token: result.token
|
|
80
|
+
token: result.token,
|
|
81
|
+
expiresAt: result.expiresAt
|
|
65
82
|
};
|
|
66
83
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { getClientConfig } from './getClientConfig';
|
|
|
2
2
|
export type { ServerAuthConfig, ServerClientConfig } from './getClientConfig';
|
|
3
3
|
export { LocationServiceConnector } from './LocationServiceConnector';
|
|
4
4
|
export type { ConnectorConfig, SendOptions } from './LocationServiceConnector';
|
|
5
|
+
export { TokenProvider } from '../auth/TokenProvider';
|
|
6
|
+
export type { TokenProviderConfig, TokenResponse } from '../auth/TokenProvider';
|
package/dist/server/index.js
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export interface ClientConfig {
|
|
2
2
|
apiUrl: string;
|
|
3
3
|
token: string;
|
|
4
|
+
/** Optional callback to get the current token dynamically. When provided,
|
|
5
|
+
* called on every request so token updates are reflected without recreating the client. */
|
|
6
|
+
getToken?: () => string | undefined;
|
|
4
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaosity/location-client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Client library for Chaosity Location Service with AWS Location Service compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"dev": "tsc --watch",
|
|
21
|
-
"test": "
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
22
23
|
"prepublishOnly": "npm run build"
|
|
23
24
|
},
|
|
24
25
|
"keywords": [
|
|
@@ -43,9 +44,7 @@
|
|
|
43
44
|
"@aws-sdk/client-geo-places": "^3.0.0",
|
|
44
45
|
"@aws/amazon-location-utilities-datatypes": "^1.0.0",
|
|
45
46
|
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
|
46
|
-
|
|
47
|
-
"debug": "^4.4.3",
|
|
48
|
-
"jose": "^5.0.0"
|
|
47
|
+
"debug": "^4.4.3"
|
|
49
48
|
},
|
|
50
49
|
"peerDependencies": {
|
|
51
50
|
"maplibre-gl": "^5.0.0"
|
|
@@ -59,7 +58,11 @@
|
|
|
59
58
|
"@types/debug": "^4.1.12",
|
|
60
59
|
"@types/node": "^25.0.3",
|
|
61
60
|
"maplibre-gl": "^5.0.0",
|
|
62
|
-
"typescript": "^5.0.0"
|
|
61
|
+
"typescript": "^5.0.0",
|
|
62
|
+
"vitest": "^3.0.0"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
63
66
|
},
|
|
64
67
|
"files": [
|
|
65
68
|
"dist",
|