@chaosity/location-client 0.1.4 → 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.d.ts +9 -17
- package/dist/adapters/GeoPlaces.js +92 -33
- package/dist/auth/TokenProvider.d.ts +4 -4
- package/dist/auth/TokenProvider.js +54 -21
- package/dist/client/GeoPlacesClient.d.ts +3 -1
- package/dist/client/GeoPlacesClient.js +41 -29
- package/dist/index.d.ts +2 -4
- package/dist/index.js +4 -4
- package/dist/maps/Utils.d.ts +2 -0
- package/dist/maps/Utils.js +5 -0
- package/dist/maps/createTransformRequest.d.ts +10 -0
- package/dist/maps/createTransformRequest.js +40 -0
- package/dist/server/LocationServiceConnector.d.ts +46 -0
- package/dist/server/LocationServiceConnector.js +112 -0
- package/dist/server/getClientConfig.d.ts +27 -5
- package/dist/server/getClientConfig.js +62 -4
- package/dist/server/getConnector.d.ts +37 -0
- package/dist/server/getConnector.js +34 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.js +3 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +24 -6
|
@@ -1,27 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MaplibreGeocoderApi, MaplibreGeocoderApiConfig, MaplibreGeocoderFeatureResults, MaplibreGeocoderPlaceResults, MaplibreGeocoderSuggestionResults } from '@maplibre/maplibre-gl-geocoder';
|
|
2
2
|
import type { Map } from 'maplibre-gl';
|
|
3
3
|
import { GeoPlacesClient } from '../client/GeoPlacesClient';
|
|
4
4
|
/**
|
|
5
5
|
* GeoPlaces - MapLibre adapter for AWS Location Service
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
* Only auth is custom (Bearer token instead of SigV4).
|
|
7
|
+
* Implements MaplibreGeocoderApi interface for compatibility with @maplibre/maplibre-gl-geocoder
|
|
9
8
|
*/
|
|
10
|
-
export declare class GeoPlaces {
|
|
9
|
+
export declare class GeoPlaces implements MaplibreGeocoderApi {
|
|
11
10
|
private client;
|
|
12
11
|
private map;
|
|
13
12
|
constructor(client: GeoPlacesClient, map: Map);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
limit?: number;
|
|
21
|
-
click?: boolean;
|
|
22
|
-
}): Promise<FeatureCollection>;
|
|
23
|
-
getSuggestions(config: {
|
|
24
|
-
query: string;
|
|
25
|
-
}): Promise<FeatureCollection>;
|
|
26
|
-
searchByPlaceId(placeId: string): Promise<FeatureCollection>;
|
|
13
|
+
private normalizeLanguage;
|
|
14
|
+
forwardGeocode(config: MaplibreGeocoderApiConfig): Promise<MaplibreGeocoderFeatureResults>;
|
|
15
|
+
reverseGeocode(config: MaplibreGeocoderApiConfig): Promise<MaplibreGeocoderFeatureResults>;
|
|
16
|
+
getSuggestions(config: MaplibreGeocoderApiConfig): Promise<MaplibreGeocoderSuggestionResults>;
|
|
17
|
+
searchByPlaceId(config: MaplibreGeocoderApiConfig): Promise<MaplibreGeocoderPlaceResults>;
|
|
18
|
+
localGeocode(config: MaplibreGeocoderApiConfig): Promise<MaplibreGeocoderFeatureResults>;
|
|
27
19
|
}
|
|
@@ -1,58 +1,117 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
*
|
|
6
|
-
*
|
|
7
|
-
* Only auth is custom (Bearer token instead of SigV4).
|
|
8
|
+
* Implements MaplibreGeocoderApi interface for compatibility with @maplibre/maplibre-gl-geocoder
|
|
8
9
|
*/
|
|
9
10
|
export class GeoPlaces {
|
|
10
11
|
constructor(client, map) {
|
|
11
12
|
this.client = client;
|
|
12
13
|
this.map = map;
|
|
13
14
|
}
|
|
15
|
+
normalizeLanguage(language) {
|
|
16
|
+
if (Array.isArray(language))
|
|
17
|
+
return language[0] || 'en';
|
|
18
|
+
if (typeof language === 'string')
|
|
19
|
+
return language;
|
|
20
|
+
return 'en';
|
|
21
|
+
}
|
|
14
22
|
async forwardGeocode(config) {
|
|
23
|
+
log('forwardGeocode query=%s', config.query);
|
|
15
24
|
const center = this.map.getCenter();
|
|
16
|
-
const
|
|
25
|
+
const biasPosition = config.proximity && config.proximity.length >= 2
|
|
26
|
+
? [config.proximity[0], config.proximity[1]]
|
|
27
|
+
: [center.lng, center.lat];
|
|
28
|
+
const commandInput = {
|
|
17
29
|
QueryText: config.query,
|
|
18
|
-
BiasPosition:
|
|
30
|
+
BiasPosition: biasPosition,
|
|
19
31
|
MaxResults: config.limit || 5,
|
|
20
|
-
Language:
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
Language: this.normalizeLanguage(config.language),
|
|
33
|
+
};
|
|
34
|
+
if (config.countries) {
|
|
35
|
+
commandInput.Filter = {
|
|
36
|
+
IncludeCountries: Array.isArray(config.countries) ? config.countries : config.countries.split(','),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const response = await this.client.send(new GeocodeCommand(commandInput));
|
|
40
|
+
const result = geocodeResponseToFeatureCollection(response, { flattenProperties: true });
|
|
41
|
+
log('forwardGeocode returned %d results', result.features?.length ?? 0);
|
|
42
|
+
return result;
|
|
24
43
|
}
|
|
25
44
|
async reverseGeocode(config) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
45
|
+
log('reverseGeocode query=%o', config.query);
|
|
46
|
+
const queryPosition = Array.isArray(config.query) && config.query.length >= 2
|
|
47
|
+
? [config.query[0], config.query[1]]
|
|
48
|
+
: [0, 0];
|
|
49
|
+
const commandInput = {
|
|
50
|
+
QueryPosition: queryPosition,
|
|
51
|
+
MaxResults: config.limit || 1,
|
|
52
|
+
Language: this.normalizeLanguage(config.language),
|
|
53
|
+
};
|
|
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;
|
|
39
58
|
}
|
|
40
59
|
async getSuggestions(config) {
|
|
60
|
+
log('getSuggestions query=%s', config.query);
|
|
41
61
|
const center = this.map.getCenter();
|
|
42
|
-
const
|
|
62
|
+
const biasPosition = config.proximity && config.proximity.length >= 2
|
|
63
|
+
? [config.proximity[0], config.proximity[1]]
|
|
64
|
+
: [center.lng, center.lat];
|
|
65
|
+
const commandInput = {
|
|
43
66
|
QueryText: config.query,
|
|
44
|
-
BiasPosition:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
BiasPosition: biasPosition,
|
|
68
|
+
MaxResults: config.limit || 5,
|
|
69
|
+
Language: this.normalizeLanguage(config.language),
|
|
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
|
+
} : {}),
|
|
77
|
+
};
|
|
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 });
|
|
86
|
+
}
|
|
87
|
+
log('getSuggestions returned %d suggestions', suggestions.suggestions.length);
|
|
88
|
+
return suggestions;
|
|
49
89
|
}
|
|
50
|
-
async searchByPlaceId(
|
|
90
|
+
async searchByPlaceId(config) {
|
|
91
|
+
log('searchByPlaceId placeId=%s', config.query);
|
|
51
92
|
const command = new GetPlaceCommand({
|
|
52
|
-
PlaceId:
|
|
53
|
-
Language:
|
|
93
|
+
PlaceId: config.query,
|
|
94
|
+
Language: this.normalizeLanguage(config.language),
|
|
95
|
+
AdditionalFeatures: [
|
|
96
|
+
GetPlaceAdditionalFeature.ACCESS,
|
|
97
|
+
GetPlaceAdditionalFeature.SECONDARY_ADDRESSES,
|
|
98
|
+
GetPlaceAdditionalFeature.CONTACT,
|
|
99
|
+
GetPlaceAdditionalFeature.TIME_ZONE,
|
|
100
|
+
],
|
|
54
101
|
});
|
|
55
102
|
const response = await this.client.send(command);
|
|
56
|
-
|
|
103
|
+
const result = getPlaceResponseToFeatureCollection(response, { flattenProperties: true });
|
|
104
|
+
const carmenGeojsonFeatures = result.features.map(feature => ({
|
|
105
|
+
...feature,
|
|
106
|
+
text: response.Title || '',
|
|
107
|
+
place_name: response.Address?.Label || '',
|
|
108
|
+
place_type: response.PlaceType ? [response.PlaceType] : [],
|
|
109
|
+
bbox: response.MapView,
|
|
110
|
+
}));
|
|
111
|
+
log('searchByPlaceId returned %d features', carmenGeojsonFeatures.length);
|
|
112
|
+
return { place: carmenGeojsonFeatures };
|
|
113
|
+
}
|
|
114
|
+
async localGeocode(config) {
|
|
115
|
+
return this.forwardGeocode(config);
|
|
57
116
|
}
|
|
58
117
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export interface TokenResponse {
|
|
2
2
|
success: boolean;
|
|
3
3
|
token?: string;
|
|
4
|
-
expiresIn?: number;
|
|
5
4
|
expiresAt?: number;
|
|
6
5
|
error?: string;
|
|
7
6
|
}
|
|
@@ -26,7 +25,7 @@ export interface TokenProviderConfig {
|
|
|
26
25
|
*
|
|
27
26
|
* @example
|
|
28
27
|
* // ✓ Correct: Server-side usage
|
|
29
|
-
* import { TokenProvider } from '@chaosity/location-client'
|
|
28
|
+
* import { TokenProvider } from '@chaosity/location-client/server'
|
|
30
29
|
*
|
|
31
30
|
* const provider = new TokenProvider({
|
|
32
31
|
* apiUrl: process.env.API_URL!,
|
|
@@ -41,10 +40,11 @@ export interface TokenProviderConfig {
|
|
|
41
40
|
export declare class TokenProvider {
|
|
42
41
|
private config;
|
|
43
42
|
private cachedToken?;
|
|
44
|
-
private
|
|
45
|
-
private
|
|
43
|
+
private cachedExpiresAt?;
|
|
44
|
+
private tokenPromise?;
|
|
46
45
|
constructor(config: TokenProviderConfig);
|
|
47
46
|
getToken(forceRefresh?: boolean): Promise<TokenResponse>;
|
|
47
|
+
private fetchToken;
|
|
48
48
|
private isExpired;
|
|
49
49
|
clearCache(): void;
|
|
50
50
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import ClientOAuth2 from 'client-oauth2';
|
|
2
1
|
import debug from 'debug';
|
|
3
2
|
const log = debug('location-client:auth');
|
|
4
3
|
/**
|
|
@@ -17,7 +16,7 @@ const log = debug('location-client:auth');
|
|
|
17
16
|
*
|
|
18
17
|
* @example
|
|
19
18
|
* // ✓ Correct: Server-side usage
|
|
20
|
-
* import { TokenProvider } from '@chaosity/location-client'
|
|
19
|
+
* import { TokenProvider } from '@chaosity/location-client/server'
|
|
21
20
|
*
|
|
22
21
|
* const provider = new TokenProvider({
|
|
23
22
|
* apiUrl: process.env.API_URL!,
|
|
@@ -39,36 +38,70 @@ export class TokenProvider {
|
|
|
39
38
|
}
|
|
40
39
|
log('Initializing TokenProvider for %s', config.apiUrl);
|
|
41
40
|
this.config = config;
|
|
42
|
-
this.oauth2Client = new ClientOAuth2({
|
|
43
|
-
clientId: config.clientId,
|
|
44
|
-
clientSecret: config.clientSecret,
|
|
45
|
-
accessTokenUri: `${config.apiUrl}/auth/token`,
|
|
46
|
-
});
|
|
47
41
|
}
|
|
48
42
|
async getToken(forceRefresh = false) {
|
|
49
|
-
if (!forceRefresh && this.cachedToken &&
|
|
50
|
-
|
|
43
|
+
if (!forceRefresh && this.cachedToken && !this.isExpired()) {
|
|
44
|
+
const expiresIn = this.cachedExpiresAt ? Math.floor((this.cachedExpiresAt - Date.now()) / 1000) : 'unknown';
|
|
45
|
+
log('Using cached token (expires in %ds)', expiresIn);
|
|
51
46
|
return {
|
|
52
47
|
success: true,
|
|
53
48
|
token: this.cachedToken,
|
|
54
|
-
expiresAt: this.
|
|
49
|
+
expiresAt: this.cachedExpiresAt
|
|
55
50
|
};
|
|
56
51
|
}
|
|
52
|
+
// If token fetch is already in progress, wait for it
|
|
53
|
+
if (this.tokenPromise) {
|
|
54
|
+
log('Token fetch in progress, waiting for existing request...');
|
|
55
|
+
return this.tokenPromise;
|
|
56
|
+
}
|
|
57
|
+
// Start new token fetch
|
|
57
58
|
const reason = forceRefresh ? 'forced refresh' : (this.cachedToken ? 'token expired' : 'no cached token');
|
|
58
59
|
log('Refreshing token (%s) from %s', reason, this.config.apiUrl);
|
|
60
|
+
this.tokenPromise = this.fetchToken();
|
|
61
|
+
try {
|
|
62
|
+
const result = await this.tokenPromise;
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
// Clear promise after completion (success or failure)
|
|
67
|
+
this.tokenPromise = undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async fetchToken() {
|
|
59
71
|
try {
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
:
|
|
64
|
-
|
|
65
|
-
|
|
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);
|
|
66
100
|
log('Token acquired successfully (expires in %ds)', expiresIn);
|
|
67
101
|
return {
|
|
68
102
|
success: true,
|
|
69
103
|
token: this.cachedToken,
|
|
70
|
-
|
|
71
|
-
expiresAt: this.expiresAt,
|
|
104
|
+
expiresAt: this.cachedExpiresAt,
|
|
72
105
|
};
|
|
73
106
|
}
|
|
74
107
|
catch (error) {
|
|
@@ -80,12 +113,12 @@ export class TokenProvider {
|
|
|
80
113
|
}
|
|
81
114
|
}
|
|
82
115
|
isExpired(bufferSeconds = 60) {
|
|
83
|
-
if (!this.
|
|
116
|
+
if (!this.cachedExpiresAt)
|
|
84
117
|
return true;
|
|
85
|
-
return Date.now() >= (this.
|
|
118
|
+
return Date.now() >= (this.cachedExpiresAt - bufferSeconds * 1000);
|
|
86
119
|
}
|
|
87
120
|
clearCache() {
|
|
88
121
|
this.cachedToken = undefined;
|
|
89
|
-
this.
|
|
122
|
+
this.cachedExpiresAt = undefined;
|
|
90
123
|
}
|
|
91
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
|
-
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
1
|
import debug from 'debug';
|
|
2
|
+
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
3
3
|
const log = debug('location-client:api');
|
|
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) {
|
|
@@ -15,46 +28,45 @@ export class GeoPlacesClient {
|
|
|
15
28
|
async send(command) {
|
|
16
29
|
const endpoint = this.getEndpoint(command);
|
|
17
30
|
const url = `${this.clientConfig.apiUrl}${endpoint}`;
|
|
18
|
-
|
|
31
|
+
const commandName = command.constructor.name;
|
|
32
|
+
const input = command.input;
|
|
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);
|
|
19
36
|
const startTime = Date.now();
|
|
20
37
|
const response = await fetch(url, {
|
|
21
38
|
method: 'POST',
|
|
22
39
|
headers: {
|
|
23
40
|
'Content-Type': 'application/json',
|
|
24
|
-
'Authorization': `Bearer ${
|
|
41
|
+
'Authorization': `Bearer ${token}`,
|
|
25
42
|
},
|
|
26
|
-
body: JSON.stringify(
|
|
43
|
+
body: JSON.stringify(input),
|
|
27
44
|
});
|
|
28
45
|
const duration = Date.now() - startTime;
|
|
29
46
|
if (!response.ok) {
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
const errorText = await response.text();
|
|
48
|
+
logError('Request failed: %s %s (%dms)', response.status, response.statusText, duration);
|
|
49
|
+
let errorMessage = `API request failed: ${response.statusText}`;
|
|
50
|
+
try {
|
|
51
|
+
const errorData = JSON.parse(errorText);
|
|
52
|
+
if (errorData.message)
|
|
53
|
+
errorMessage = errorData.message;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
if (errorText)
|
|
57
|
+
errorMessage = errorText;
|
|
58
|
+
}
|
|
59
|
+
throw new Error(errorMessage);
|
|
32
60
|
}
|
|
61
|
+
const result = await response.json();
|
|
33
62
|
log('Request successful: %s (%dms)', response.status, duration);
|
|
34
|
-
return
|
|
63
|
+
return result;
|
|
35
64
|
}
|
|
36
65
|
getEndpoint(command) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
else if (command instanceof GeocodeCommand) {
|
|
41
|
-
return '/address/geocode';
|
|
42
|
-
}
|
|
43
|
-
else if (command instanceof GetPlaceCommand) {
|
|
44
|
-
return '/address/place';
|
|
45
|
-
}
|
|
46
|
-
else if (command instanceof ReverseGeocodeCommand) {
|
|
47
|
-
return '/address/search/reverse-geocode';
|
|
48
|
-
}
|
|
49
|
-
else if (command instanceof SearchNearbyCommand) {
|
|
50
|
-
return '/address/search/nearby';
|
|
51
|
-
}
|
|
52
|
-
else if (command instanceof SearchTextCommand) {
|
|
53
|
-
return '/address/search/text';
|
|
54
|
-
}
|
|
55
|
-
else if (command instanceof SuggestCommand) {
|
|
56
|
-
return '/address/suggestion';
|
|
66
|
+
for (const [CommandClass, endpoint] of ENDPOINT_MAP) {
|
|
67
|
+
if (command instanceof CommandClass)
|
|
68
|
+
return endpoint;
|
|
57
69
|
}
|
|
58
|
-
throw new Error(
|
|
70
|
+
throw new Error(`Unknown command type: ${command.constructor?.name ?? typeof command}`);
|
|
59
71
|
}
|
|
60
72
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
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';
|
|
6
4
|
export { GeoPlaces } from './adapters/GeoPlaces';
|
|
5
|
+
export { createTransformRequest } from './maps/createTransformRequest';
|
|
6
|
+
export { transformRequest } from './maps/Utils';
|
|
7
7
|
export type { ClientConfig } from './types';
|
|
8
|
-
export { getClientConfig } from './server/getClientConfig';
|
|
9
|
-
export type { ServerAuthConfig } from './server/getClientConfig';
|
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
|
|
@@ -8,5 +6,7 @@ export * from '@aws-sdk/client-geo-places';
|
|
|
8
6
|
export * from '@aws/amazon-location-utilities-datatypes';
|
|
9
7
|
// Adapters (Custom - for MapLibre integration)
|
|
10
8
|
export { GeoPlaces } from './adapters/GeoPlaces';
|
|
11
|
-
//
|
|
12
|
-
export {
|
|
9
|
+
// Maps utilities
|
|
10
|
+
export { createTransformRequest } from './maps/createTransformRequest';
|
|
11
|
+
export { transformRequest } from './maps/Utils';
|
|
12
|
+
// Server-only utilities are available via '@chaosity/location-client/server'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RequestTransformFunction } from 'maplibre-gl';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a transformRequest function for MapLibre that adds authentication
|
|
4
|
+
* and proper Accept headers for AWS Location Service API requests.
|
|
5
|
+
*
|
|
6
|
+
* @param apiUrl - Base URL of the Location Service API
|
|
7
|
+
* @param getToken - Callback function that returns the current auth token
|
|
8
|
+
* @returns MapLibre transformRequest function
|
|
9
|
+
*/
|
|
10
|
+
export declare function createTransformRequest(apiUrl: string, getToken: () => string | undefined): RequestTransformFunction;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a transformRequest function for MapLibre that adds authentication
|
|
3
|
+
* and proper Accept headers for AWS Location Service API requests.
|
|
4
|
+
*
|
|
5
|
+
* @param apiUrl - Base URL of the Location Service API
|
|
6
|
+
* @param getToken - Callback function that returns the current auth token
|
|
7
|
+
* @returns MapLibre transformRequest function
|
|
8
|
+
*/
|
|
9
|
+
export function createTransformRequest(apiUrl, getToken) {
|
|
10
|
+
return (url, resourceType) => {
|
|
11
|
+
if (url.startsWith(apiUrl)) {
|
|
12
|
+
const token = getToken();
|
|
13
|
+
if (!token) {
|
|
14
|
+
console.warn('[createTransformRequest] No token available');
|
|
15
|
+
return { url };
|
|
16
|
+
}
|
|
17
|
+
const headers = {
|
|
18
|
+
'Authorization': `Bearer ${token}`,
|
|
19
|
+
};
|
|
20
|
+
// Set appropriate Accept headers based on resource type
|
|
21
|
+
if (url.includes('/tiles/')) {
|
|
22
|
+
headers['Accept'] = 'application/x-protobuf';
|
|
23
|
+
}
|
|
24
|
+
else if (url.includes('/glyphs/')) {
|
|
25
|
+
headers['Accept'] = 'application/x-protobuf';
|
|
26
|
+
}
|
|
27
|
+
else if (url.includes('/sprites/') && url.endsWith('.png')) {
|
|
28
|
+
headers['Accept'] = 'image/png';
|
|
29
|
+
}
|
|
30
|
+
else if (url.includes('/sprites/') && url.endsWith('.json')) {
|
|
31
|
+
headers['Accept'] = 'application/json';
|
|
32
|
+
}
|
|
33
|
+
else if (url.includes('/descriptor')) {
|
|
34
|
+
headers['Accept'] = 'application/json';
|
|
35
|
+
}
|
|
36
|
+
return { url, headers };
|
|
37
|
+
}
|
|
38
|
+
return { url };
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface ConnectorConfig {
|
|
2
|
+
apiUrl?: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
getToken?: () => Promise<{
|
|
5
|
+
token: string;
|
|
6
|
+
}>;
|
|
7
|
+
}
|
|
8
|
+
export interface SendOptions {
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* LocationServiceConnector - Server-side connector for Location Service API
|
|
13
|
+
*
|
|
14
|
+
* Optimized for backend-to-backend communication with:
|
|
15
|
+
* - Automatic configuration from environment variables
|
|
16
|
+
* - Automatic Origin header handling
|
|
17
|
+
* - Server-side token management
|
|
18
|
+
* - Enhanced error handling
|
|
19
|
+
*
|
|
20
|
+
* Uses AWS SDK command classes with Bearer token authentication.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Auto-detect from environment
|
|
25
|
+
* const connector = new LocationServiceConnector()
|
|
26
|
+
*
|
|
27
|
+
* // Or provide explicit config
|
|
28
|
+
* const connector = new LocationServiceConnector({
|
|
29
|
+
* apiUrl: 'https://api.example.com',
|
|
30
|
+
* token: 'your-token'
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Send with custom headers (including Origin)
|
|
34
|
+
* const result = await connector.send(
|
|
35
|
+
* new SearchTextCommand({ QueryText: 'Space Needle' }),
|
|
36
|
+
* { headers: { 'Origin': req.headers.origin } }
|
|
37
|
+
* )
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class LocationServiceConnector {
|
|
41
|
+
private configPromise;
|
|
42
|
+
readonly serviceId: string;
|
|
43
|
+
constructor(config?: ConnectorConfig);
|
|
44
|
+
send<TInput, TOutput>(command: TInput, options?: SendOptions): Promise<TOutput>;
|
|
45
|
+
private getEndpoint;
|
|
46
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
|
|
2
|
+
import debug from 'debug';
|
|
3
|
+
import { getClientConfig } from './getClientConfig';
|
|
4
|
+
const log = debug('location-client:connector');
|
|
5
|
+
/**
|
|
6
|
+
* LocationServiceConnector - Server-side connector for Location Service API
|
|
7
|
+
*
|
|
8
|
+
* Optimized for backend-to-backend communication with:
|
|
9
|
+
* - Automatic configuration from environment variables
|
|
10
|
+
* - Automatic Origin header handling
|
|
11
|
+
* - Server-side token management
|
|
12
|
+
* - Enhanced error handling
|
|
13
|
+
*
|
|
14
|
+
* Uses AWS SDK command classes with Bearer token authentication.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Auto-detect from environment
|
|
19
|
+
* const connector = new LocationServiceConnector()
|
|
20
|
+
*
|
|
21
|
+
* // Or provide explicit config
|
|
22
|
+
* const connector = new LocationServiceConnector({
|
|
23
|
+
* apiUrl: 'https://api.example.com',
|
|
24
|
+
* token: 'your-token'
|
|
25
|
+
* })
|
|
26
|
+
*
|
|
27
|
+
* // Send with custom headers (including Origin)
|
|
28
|
+
* const result = await connector.send(
|
|
29
|
+
* new SearchTextCommand({ QueryText: 'Space Needle' }),
|
|
30
|
+
* { headers: { 'Origin': req.headers.origin } }
|
|
31
|
+
* )
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class LocationServiceConnector {
|
|
35
|
+
constructor(config) {
|
|
36
|
+
this.serviceId = 'Geo Places';
|
|
37
|
+
this.configPromise = config
|
|
38
|
+
? Promise.resolve(config)
|
|
39
|
+
: getClientConfig();
|
|
40
|
+
}
|
|
41
|
+
async send(command, options) {
|
|
42
|
+
const config = await this.configPromise;
|
|
43
|
+
// Get token - ConnectorConfig may have getToken, ServerClientConfig has static token
|
|
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
|
+
}
|
|
52
|
+
if (!token) {
|
|
53
|
+
throw new Error('No token available');
|
|
54
|
+
}
|
|
55
|
+
const endpoint = this.getEndpoint(command);
|
|
56
|
+
const url = `${config.apiUrl}${endpoint}`;
|
|
57
|
+
const commandName = command.constructor.name;
|
|
58
|
+
const input = command.input;
|
|
59
|
+
log('Sending %s request to %s', commandName, endpoint);
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
// Merge headers: user headers, then system headers override
|
|
62
|
+
const headers = {
|
|
63
|
+
...(options?.headers || {}),
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
'Authorization': `Bearer ${token}`
|
|
66
|
+
};
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers,
|
|
70
|
+
body: JSON.stringify(input)
|
|
71
|
+
});
|
|
72
|
+
const duration = Date.now() - startTime;
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const errorText = await response.text();
|
|
75
|
+
log('Request failed: %s %s (%dms)', response.status, response.statusText, duration);
|
|
76
|
+
// Try to parse error message from response
|
|
77
|
+
let errorMessage = `API request failed: ${response.statusText}`;
|
|
78
|
+
try {
|
|
79
|
+
const errorData = JSON.parse(errorText);
|
|
80
|
+
if (errorData.message) {
|
|
81
|
+
errorMessage = errorData.message;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// If not JSON, use raw text if available
|
|
86
|
+
if (errorText)
|
|
87
|
+
errorMessage = errorText;
|
|
88
|
+
}
|
|
89
|
+
throw new Error(errorMessage);
|
|
90
|
+
}
|
|
91
|
+
const result = await response.json();
|
|
92
|
+
log('Request successful: %s (%dms)', response.status, duration);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
getEndpoint(command) {
|
|
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}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { ClientConfig } from '../types';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
export interface ServerAuthConfig {
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
clientId?: string;
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ServerClientConfig extends ClientConfig {
|
|
8
|
+
expiresAt?: number;
|
|
9
|
+
}
|
|
4
10
|
/**
|
|
5
11
|
* Get client configuration with OAuth2 authentication.
|
|
6
12
|
*
|
|
13
|
+
* Automatically reads from environment variables:
|
|
14
|
+
* - LOCATION_API_URL or LOCATION_SERVICE_API_URL
|
|
15
|
+
* - LOCATION_CLIENT_ID or LOCATION_SERVICE_CLIENT_ID
|
|
16
|
+
* - LOCATION_CLIENT_SECRET or LOCATION_SERVICE_CLIENT_SECRET
|
|
17
|
+
*
|
|
18
|
+
* You can override any value by passing it explicitly.
|
|
19
|
+
*
|
|
7
20
|
* WARNING: This function uses client credentials (clientId/clientSecret).
|
|
8
21
|
* Only call this from:
|
|
9
22
|
* - Next.js Server Components/Actions
|
|
@@ -12,7 +25,16 @@ export type ServerAuthConfig = TokenProviderConfig;
|
|
|
12
25
|
*
|
|
13
26
|
* NEVER call from browser/client code as it exposes credentials.
|
|
14
27
|
* For SPA projects, create your own backend endpoint that calls this.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Auto-detect from environment
|
|
31
|
+
* const config = await getClientConfig()
|
|
32
|
+
*
|
|
33
|
+
* // Or override specific values
|
|
34
|
+
* const config = await getClientConfig({ apiUrl: 'https://custom.api.com' })
|
|
35
|
+
*
|
|
36
|
+
* // Use getToken() for automatic caching and refresh
|
|
37
|
+
* const { token } = await config.getToken()
|
|
38
|
+
* const connector = new LocationServiceConnector({ apiUrl: config.apiUrl, token })
|
|
15
39
|
*/
|
|
16
|
-
export declare function getClientConfig(config
|
|
17
|
-
expiresAt?: number;
|
|
18
|
-
}>;
|
|
40
|
+
export declare function getClientConfig(config?: ServerAuthConfig): Promise<ServerClientConfig>;
|
|
@@ -1,7 +1,32 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
1
2
|
import { TokenProvider } from '../auth/TokenProvider';
|
|
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
|
+
}
|
|
2
20
|
/**
|
|
3
21
|
* Get client configuration with OAuth2 authentication.
|
|
4
22
|
*
|
|
23
|
+
* Automatically reads from environment variables:
|
|
24
|
+
* - LOCATION_API_URL or LOCATION_SERVICE_API_URL
|
|
25
|
+
* - LOCATION_CLIENT_ID or LOCATION_SERVICE_CLIENT_ID
|
|
26
|
+
* - LOCATION_CLIENT_SECRET or LOCATION_SERVICE_CLIENT_SECRET
|
|
27
|
+
*
|
|
28
|
+
* You can override any value by passing it explicitly.
|
|
29
|
+
*
|
|
5
30
|
* WARNING: This function uses client credentials (clientId/clientSecret).
|
|
6
31
|
* Only call this from:
|
|
7
32
|
* - Next.js Server Components/Actions
|
|
@@ -10,16 +35,49 @@ import { TokenProvider } from '../auth/TokenProvider';
|
|
|
10
35
|
*
|
|
11
36
|
* NEVER call from browser/client code as it exposes credentials.
|
|
12
37
|
* For SPA projects, create your own backend endpoint that calls this.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Auto-detect from environment
|
|
41
|
+
* const config = await getClientConfig()
|
|
42
|
+
*
|
|
43
|
+
* // Or override specific values
|
|
44
|
+
* const config = await getClientConfig({ apiUrl: 'https://custom.api.com' })
|
|
45
|
+
*
|
|
46
|
+
* // Use getToken() for automatic caching and refresh
|
|
47
|
+
* const { token } = await config.getToken()
|
|
48
|
+
* const connector = new LocationServiceConnector({ apiUrl: config.apiUrl, token })
|
|
13
49
|
*/
|
|
14
|
-
export async function getClientConfig(config) {
|
|
15
|
-
|
|
50
|
+
export async function getClientConfig(config = {}) {
|
|
51
|
+
log('[getClientConfig] Starting with config:', { hasApiUrl: !!config.apiUrl, hasClientId: !!config.clientId });
|
|
52
|
+
// Auto-detect from environment with fallbacks
|
|
53
|
+
const apiUrl = config.apiUrl ||
|
|
54
|
+
process.env.LOCATION_API_URL ||
|
|
55
|
+
process.env.LOCATION_SERVICE_API_URL;
|
|
56
|
+
const clientId = config.clientId ||
|
|
57
|
+
process.env.LOCATION_CLIENT_ID ||
|
|
58
|
+
process.env.LOCATION_SERVICE_CLIENT_ID;
|
|
59
|
+
const clientSecret = config.clientSecret ||
|
|
60
|
+
process.env.LOCATION_CLIENT_SECRET ||
|
|
61
|
+
process.env.LOCATION_SERVICE_CLIENT_SECRET;
|
|
62
|
+
log('[getClientConfig] Resolved config:', { apiUrl, clientId: clientId?.substring(0, 10) + '...' });
|
|
63
|
+
// Validate required values
|
|
64
|
+
if (!apiUrl || !clientId || !clientSecret) {
|
|
65
|
+
console.error('[getClientConfig] Missing required configuration');
|
|
66
|
+
throw new Error('Missing required configuration. Set environment variables: ' +
|
|
67
|
+
'LOCATION_API_URL, LOCATION_CLIENT_ID, LOCATION_CLIENT_SECRET');
|
|
68
|
+
}
|
|
69
|
+
log('[getClientConfig] Getting TokenProvider instance');
|
|
70
|
+
const provider = getTokenProvider(apiUrl, clientId, clientSecret);
|
|
71
|
+
log('[getClientConfig] Fetching token');
|
|
16
72
|
const result = await provider.getToken();
|
|
17
73
|
if (!result.success || !result.token) {
|
|
74
|
+
console.error('[getClientConfig] Token fetch failed:', result.error);
|
|
18
75
|
throw new Error(result.error || 'Failed to get token');
|
|
19
76
|
}
|
|
77
|
+
log('[getClientConfig] Token fetched successfully, length:', result.token.length);
|
|
20
78
|
return {
|
|
21
|
-
apiUrl
|
|
79
|
+
apiUrl,
|
|
22
80
|
token: result.token,
|
|
23
|
-
expiresAt: result.expiresAt
|
|
81
|
+
expiresAt: result.expiresAt
|
|
24
82
|
};
|
|
25
83
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { LocationServiceConnector } from './LocationServiceConnector';
|
|
2
|
+
export interface ServerAuthConfig {
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
clientId?: string;
|
|
5
|
+
clientSecret?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Get LocationServiceConnector with automatic token management.
|
|
9
|
+
*
|
|
10
|
+
* Automatically reads from environment variables:
|
|
11
|
+
* - LOCATION_API_URL or LOCATION_SERVICE_API_URL
|
|
12
|
+
* - LOCATION_CLIENT_ID or LOCATION_SERVICE_CLIENT_ID
|
|
13
|
+
* - LOCATION_CLIENT_SECRET or LOCATION_SERVICE_CLIENT_SECRET
|
|
14
|
+
*
|
|
15
|
+
* You can override any value by passing it explicitly.
|
|
16
|
+
*
|
|
17
|
+
* WARNING: This function uses client credentials (clientId/clientSecret).
|
|
18
|
+
* Only call this from:
|
|
19
|
+
* - Next.js Server Components/Actions
|
|
20
|
+
* - Node.js backend servers
|
|
21
|
+
* - API routes
|
|
22
|
+
*
|
|
23
|
+
* NEVER call from browser/client code as it exposes credentials.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Server Action for React provider
|
|
27
|
+
* 'use server'
|
|
28
|
+
* export async function getLocationConnector() {
|
|
29
|
+
* return getConnector()
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* // Or with custom config
|
|
33
|
+
* export async function getLocationConnector() {
|
|
34
|
+
* return getConnector({ apiUrl: 'https://custom.api.com' })
|
|
35
|
+
* }
|
|
36
|
+
*/
|
|
37
|
+
export declare function getConnector(config?: ServerAuthConfig): LocationServiceConnector;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { LocationServiceConnector } from './LocationServiceConnector';
|
|
2
|
+
/**
|
|
3
|
+
* Get LocationServiceConnector with automatic token management.
|
|
4
|
+
*
|
|
5
|
+
* Automatically reads from environment variables:
|
|
6
|
+
* - LOCATION_API_URL or LOCATION_SERVICE_API_URL
|
|
7
|
+
* - LOCATION_CLIENT_ID or LOCATION_SERVICE_CLIENT_ID
|
|
8
|
+
* - LOCATION_CLIENT_SECRET or LOCATION_SERVICE_CLIENT_SECRET
|
|
9
|
+
*
|
|
10
|
+
* You can override any value by passing it explicitly.
|
|
11
|
+
*
|
|
12
|
+
* WARNING: This function uses client credentials (clientId/clientSecret).
|
|
13
|
+
* Only call this from:
|
|
14
|
+
* - Next.js Server Components/Actions
|
|
15
|
+
* - Node.js backend servers
|
|
16
|
+
* - API routes
|
|
17
|
+
*
|
|
18
|
+
* NEVER call from browser/client code as it exposes credentials.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* // Server Action for React provider
|
|
22
|
+
* 'use server'
|
|
23
|
+
* export async function getLocationConnector() {
|
|
24
|
+
* return getConnector()
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // Or with custom config
|
|
28
|
+
* export async function getLocationConnector() {
|
|
29
|
+
* return getConnector({ apiUrl: 'https://custom.api.com' })
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export function getConnector(config) {
|
|
33
|
+
return new LocationServiceConnector(config);
|
|
34
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { getClientConfig } from './getClientConfig';
|
|
2
|
+
export type { ServerAuthConfig, ServerClientConfig } from './getClientConfig';
|
|
3
|
+
export { LocationServiceConnector } from './LocationServiceConnector';
|
|
4
|
+
export type { ConnectorConfig, SendOptions } from './LocationServiceConnector';
|
|
5
|
+
export { TokenProvider } from '../auth/TokenProvider';
|
|
6
|
+
export type { TokenProviderConfig, TokenResponse } from '../auth/TokenProvider';
|
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,13 +1,25 @@
|
|
|
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
|
+
"type": "module",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./server": {
|
|
14
|
+
"types": "./dist/server/index.d.ts",
|
|
15
|
+
"default": "./dist/server/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
7
18
|
"scripts": {
|
|
8
19
|
"build": "tsc",
|
|
9
20
|
"dev": "tsc --watch",
|
|
10
|
-
"test": "
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
11
23
|
"prepublishOnly": "npm run build"
|
|
12
24
|
},
|
|
13
25
|
"keywords": [
|
|
@@ -31,11 +43,11 @@
|
|
|
31
43
|
"dependencies": {
|
|
32
44
|
"@aws-sdk/client-geo-places": "^3.0.0",
|
|
33
45
|
"@aws/amazon-location-utilities-datatypes": "^1.0.0",
|
|
34
|
-
"
|
|
35
|
-
|
|
46
|
+
"@maplibre/maplibre-gl-geocoder": "^1.9.4",
|
|
47
|
+
"debug": "^4.4.3"
|
|
36
48
|
},
|
|
37
49
|
"peerDependencies": {
|
|
38
|
-
"maplibre-gl": "^
|
|
50
|
+
"maplibre-gl": "^5.0.0"
|
|
39
51
|
},
|
|
40
52
|
"peerDependenciesMeta": {
|
|
41
53
|
"maplibre-gl": {
|
|
@@ -44,7 +56,13 @@
|
|
|
44
56
|
},
|
|
45
57
|
"devDependencies": {
|
|
46
58
|
"@types/debug": "^4.1.12",
|
|
47
|
-
"
|
|
59
|
+
"@types/node": "^25.0.3",
|
|
60
|
+
"maplibre-gl": "^5.0.0",
|
|
61
|
+
"typescript": "^5.0.0",
|
|
62
|
+
"vitest": "^3.0.0"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
48
66
|
},
|
|
49
67
|
"files": [
|
|
50
68
|
"dist",
|