@chaosity/location-client 0.11.0 → 0.13.0

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.
Files changed (72) hide show
  1. package/README.md +207 -40
  2. package/dist/adapters/GeoPlaces.d.ts +7 -1
  3. package/dist/adapters/GeoPlaces.js +64 -22
  4. package/dist/auth/TokenProvider.d.ts +24 -1
  5. package/dist/auth/TokenProvider.js +81 -12
  6. package/dist/auth/tokenHold.d.ts +81 -0
  7. package/dist/auth/tokenHold.js +174 -0
  8. package/dist/aws.d.ts +4 -0
  9. package/dist/aws.js +2 -0
  10. package/dist/cjs/adapters/GeoPlaces.d.ts +7 -1
  11. package/dist/cjs/adapters/GeoPlaces.js +63 -21
  12. package/dist/cjs/auth/TokenProvider.d.ts +24 -1
  13. package/dist/cjs/auth/TokenProvider.js +81 -12
  14. package/dist/cjs/auth/tokenHold.d.ts +81 -0
  15. package/dist/cjs/auth/tokenHold.js +181 -0
  16. package/dist/cjs/aws.d.ts +4 -0
  17. package/dist/cjs/aws.js +155 -0
  18. package/dist/cjs/client/GeoPlacesClient.d.ts +23 -3
  19. package/dist/cjs/client/GeoPlacesClient.js +50 -32
  20. package/dist/cjs/client/commands.d.ts +2 -3
  21. package/dist/cjs/errors/LocationServiceException.d.ts +30 -2
  22. package/dist/cjs/errors/LocationServiceException.js +53 -1
  23. package/dist/cjs/index.d.ts +5 -4
  24. package/dist/cjs/index.js +13 -8
  25. package/dist/cjs/maps/createTransformRequest.d.ts +25 -0
  26. package/dist/cjs/maps/createTransformRequest.js +1 -0
  27. package/dist/cjs/maps/mapPoi.d.ts +10 -5
  28. package/dist/cjs/maps/mapPoi.js +14 -5
  29. package/dist/cjs/maps/mapStyle.d.ts +9 -2
  30. package/dist/cjs/maps/mapStyle.js +16 -12
  31. package/dist/cjs/maps/mapToken.d.ts +84 -0
  32. package/dist/cjs/maps/mapToken.js +126 -0
  33. package/dist/cjs/maps/staticMap.d.ts +7 -3
  34. package/dist/cjs/maps/staticMap.js +13 -12
  35. package/dist/cjs/server/LocationServiceConnector.d.ts +20 -3
  36. package/dist/cjs/server/LocationServiceConnector.js +20 -22
  37. package/dist/cjs/server/getClientConfig.d.ts +10 -5
  38. package/dist/cjs/server/getClientConfig.js +43 -19
  39. package/dist/cjs/server/index.d.ts +1 -1
  40. package/dist/cjs/transport/errors.d.ts +14 -8
  41. package/dist/cjs/transport/errors.js +26 -18
  42. package/dist/cjs/transport/http.d.ts +18 -0
  43. package/dist/cjs/transport/http.js +39 -1
  44. package/dist/cjs/types/index.d.ts +26 -0
  45. package/dist/client/GeoPlacesClient.d.ts +23 -3
  46. package/dist/client/GeoPlacesClient.js +52 -34
  47. package/dist/client/commands.d.ts +2 -3
  48. package/dist/errors/LocationServiceException.d.ts +30 -2
  49. package/dist/errors/LocationServiceException.js +52 -0
  50. package/dist/index.d.ts +5 -4
  51. package/dist/index.js +11 -7
  52. package/dist/maps/createTransformRequest.d.ts +25 -0
  53. package/dist/maps/createTransformRequest.js +1 -1
  54. package/dist/maps/mapPoi.d.ts +10 -5
  55. package/dist/maps/mapPoi.js +14 -5
  56. package/dist/maps/mapStyle.d.ts +9 -2
  57. package/dist/maps/mapStyle.js +17 -13
  58. package/dist/maps/mapToken.d.ts +84 -0
  59. package/dist/maps/mapToken.js +122 -0
  60. package/dist/maps/staticMap.d.ts +7 -3
  61. package/dist/maps/staticMap.js +14 -13
  62. package/dist/server/LocationServiceConnector.d.ts +20 -3
  63. package/dist/server/LocationServiceConnector.js +22 -24
  64. package/dist/server/getClientConfig.d.ts +10 -5
  65. package/dist/server/getClientConfig.js +43 -19
  66. package/dist/server/index.d.ts +1 -1
  67. package/dist/transport/errors.d.ts +14 -8
  68. package/dist/transport/errors.js +27 -19
  69. package/dist/transport/http.d.ts +18 -0
  70. package/dist/transport/http.js +37 -1
  71. package/dist/types/index.d.ts +26 -0
  72. package/package.json +3 -3
@@ -1,18 +1,22 @@
1
1
  import type { RequestOptions } from '../transport/http.js';
2
- import type { ClientConfig } from '../types/index.js';
2
+ import type { ClientConfig, CommandOutput, CommandWithOutput } from '../types/index.js';
3
3
  import type { AppConfigClaims } from '../utils/tokenClaims.js';
4
4
  import type { VerifyAddressResponse } from './commands.js';
5
5
  export type SendOptions = RequestOptions;
6
6
  /**
7
7
  * GeoPlacesClient — AWS Location Service compatible client with custom auth.
8
8
  *
9
- * Uses AWS SDK command classes but replaces SigV4 with a Bearer token. All
10
- * request and response types are identical to AWS Location Service.
9
+ * Uses AWS SDK command classes but replaces SigV4 with a Bearer token. The
10
+ * request and response types are the AWS SDK's, except that the Places
11
+ * commands take neither `IntendedUse` nor `Key` (#40), and `VerifyAddressCommand`
12
+ * is this package's own (#54).
11
13
  *
12
14
  * Pass `getToken` in config for live refresh without recreating the client.
13
15
  */
14
16
  export declare class GeoPlacesClient {
15
17
  private clientConfig;
18
+ /** A token the API refused, and why, until the hold lapses (#38). */
19
+ private readonly refused;
16
20
  readonly config: {
17
21
  serviceId: string;
18
22
  };
@@ -36,6 +40,11 @@ export declare class GeoPlacesClient {
36
40
  getAppConfig(): AppConfigClaims;
37
41
  /** Prefer the getToken callback (live ref) over a static token string. */
38
42
  private currentToken;
43
+ /**
44
+ * `refreshToken`, held to the caller's signal and deadline (#62). A slow or
45
+ * stuck callback used to hold `send` past both.
46
+ */
47
+ private askRefreshToken;
39
48
  /**
40
49
  * A token to send, or a refusal — never `undefined`.
41
50
  *
@@ -44,10 +53,21 @@ export declare class GeoPlacesClient {
44
53
  */
45
54
  private ensureToken;
46
55
  /**
56
+ * Send a command, and resolve with its output:
57
+ * `await client.send(new AutocompleteCommand(…))` is an
58
+ * `AutocompleteCommandOutput`, with nothing to annotate (#68).
59
+ *
47
60
  * @param options `signal` to cancel, `timeoutMs` per attempt,
48
61
  * `overallTimeoutMs` for the whole call, `retry: false` to disable the
49
62
  * retry loop. Every failure throws LocationServiceException.
50
63
  */
64
+ send<C extends CommandWithOutput>(command: C, options?: SendOptions): Promise<CommandOutput<C>>;
65
+ /**
66
+ * The signature `send` had before it inferred its output (#68). It stays
67
+ * so that a call naming both type arguments, and a client typed by a
68
+ * structural `send<TInput, TOutput>` — as `@chaosity/address-form` types
69
+ * its client — still compile.
70
+ */
51
71
  send<TInput, TOutput>(command: TInput, options?: SendOptions): Promise<TOutput>;
52
72
  /**
53
73
  * Verify a PlaceId: `send(new VerifyAddressCommand({ PlaceId }))`, typed
@@ -5,22 +5,32 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GeoPlacesClient = void 0;
7
7
  const debug_1 = __importDefault(require("debug"));
8
+ const tokenHold_js_1 = require("../auth/tokenHold.js");
8
9
  const endpoints_js_1 = require("../transport/endpoints.js");
9
10
  const errors_js_1 = require("../transport/errors.js");
10
11
  const http_js_1 = require("../transport/http.js");
11
12
  const tokenClaims_js_1 = require("../utils/tokenClaims.js");
12
13
  const commands_js_1 = require("./commands.js");
13
14
  const log = (0, debug_1.default)('location-client:api');
15
+ /**
16
+ * What a hold is kept against when there was no token in hand to key it to.
17
+ * `ensureToken` treats an empty token as none, so no real token is this.
18
+ */
19
+ const NO_TOKEN = '';
14
20
  /**
15
21
  * GeoPlacesClient — AWS Location Service compatible client with custom auth.
16
22
  *
17
- * Uses AWS SDK command classes but replaces SigV4 with a Bearer token. All
18
- * request and response types are identical to AWS Location Service.
23
+ * Uses AWS SDK command classes but replaces SigV4 with a Bearer token. The
24
+ * request and response types are the AWS SDK's, except that the Places
25
+ * commands take neither `IntendedUse` nor `Key` (#40), and `VerifyAddressCommand`
26
+ * is this package's own (#54).
19
27
  *
20
28
  * Pass `getToken` in config for live refresh without recreating the client.
21
29
  */
22
30
  class GeoPlacesClient {
23
31
  constructor(config) {
32
+ /** A token the API refused, and why, until the hold lapses (#38). */
33
+ this.refused = new tokenHold_js_1.TokenHold();
24
34
  this.clientConfig = config;
25
35
  this.config = { serviceId: 'Geo Places' };
26
36
  }
@@ -47,29 +57,48 @@ class GeoPlacesClient {
47
57
  currentToken() {
48
58
  return this.clientConfig.getToken?.() ?? this.clientConfig.token;
49
59
  }
60
+ /**
61
+ * `refreshToken`, held to the caller's signal and deadline (#62). A slow or
62
+ * stuck callback used to hold `send` past both.
63
+ */
64
+ askRefreshToken(call) {
65
+ const asked = this.clientConfig.refreshToken?.();
66
+ return asked ? (0, http_js_1.withinCall)(asked, call) : Promise.resolve(undefined);
67
+ }
50
68
  /**
51
69
  * A token to send, or a refusal — never `undefined`.
52
70
  *
53
71
  * `refreshToken` is asked only when there is nothing at all in hand, so a
54
72
  * client configured the ordinary way pays nothing for this.
55
73
  */
56
- async ensureToken() {
57
- // `||`, not `??`: an empty string is a token source with nothing to give,
58
- // not a decision to send an empty one. With `??` it survived the coalesce,
59
- // skipped `refreshToken`, and then failed the check two lines below — so
74
+ async ensureToken(call) {
75
+ // Truthiness, not `??`: an empty string is a token source with nothing to
76
+ // give, not a decision to send an empty one. With `??` it survived the
77
+ // coalesce, skipped `refreshToken`, and then failed the check below — so
60
78
  // `getToken: () => undefined` got the refresh ask and `getToken: () => ''`
61
79
  // did not, which is a distinction no caller means to draw.
62
- const token = this.currentToken() || (await this.clientConfig.refreshToken?.());
80
+ const inHand = this.currentToken();
81
+ if (inHand)
82
+ return inHand;
83
+ // `refreshToken` refused a moment ago, and there is still nothing in hand:
84
+ // answer with that rather than ask it again on every send (#38). The hold
85
+ // is kept against "no token", so one arriving from `getToken` ends it.
86
+ const held = this.refused.check(NO_TOKEN)?.error;
87
+ if (held)
88
+ throw held;
89
+ let token;
90
+ try {
91
+ token = await this.askRefreshToken(call);
92
+ }
93
+ catch (refusal) {
94
+ this.refused.remember(refusal, NO_TOKEN);
95
+ throw refusal;
96
+ }
63
97
  if (!token) {
64
98
  throw (0, errors_js_1.noTokenAvailable)('the client has no token yet. Pass `token`, or a `getToken`/`refreshToken` that has one.');
65
99
  }
66
100
  return token;
67
101
  }
68
- /**
69
- * @param options `signal` to cancel, `timeoutMs` per attempt,
70
- * `overallTimeoutMs` for the whole call, `retry: false` to disable the
71
- * retry loop. Every failure throws LocationServiceException.
72
- */
73
102
  async send(command, options) {
74
103
  const cmd = command;
75
104
  const url = `${this.clientConfig.apiUrl}${(0, endpoints_js_1.resolveEndpoint)(cmd)}`;
@@ -80,26 +109,15 @@ class GeoPlacesClient {
80
109
  // source has not produced one yet spent a whole round trip to learn
81
110
  // something it already knew. Ask the refresh source instead, and refuse if
82
111
  // there is still nothing.
83
- const token = await this.ensureToken();
84
- try {
85
- return await this.dispatch(url, token, cmd, options);
86
- }
87
- catch (err) {
88
- if (!(0, errors_js_1.isTokenRejected)(err))
89
- throw err;
90
- // One shot. `refreshToken` is the only way to actually obtain a new
91
- // token here — `getToken` is synchronous and returns the one already in
92
- // hand — but it is re-read as a fallback because a provider that
93
- // refreshes in the background may have landed a new one while this
94
- // request was in flight.
95
- const fresh = (await this.clientConfig.refreshToken?.()) ?? this.currentToken();
96
- // Nothing new to send. Repeating the request would fail identically — a
97
- // second round trip for the same 401.
98
- if (!fresh || fresh === token)
99
- throw err;
100
- log('401 — retrying %s once with a refreshed token', cmd.constructor?.name);
101
- return await this.dispatch(url, fresh, cmd, options);
102
- }
112
+ // The call's deadline starts here, before any wait for a token (#62).
113
+ const call = (0, http_js_1.startCall)(options);
114
+ const token = await this.ensureToken(call);
115
+ // One shot, through `sendRetryingOnce` like every 401 retry here (#38).
116
+ // `refreshToken` is the only way to actually obtain a new token here —
117
+ // `getToken` is synchronous and returns the one already in hand — but it
118
+ // is re-read as a fallback because a provider that refreshes in the
119
+ // background may have landed a new one while this request was in flight.
120
+ return (0, tokenHold_js_1.sendRetryingOnce)(this.refused, token, (t) => this.dispatch(url, t, cmd, call), async () => (await this.askRefreshToken(call)) ?? this.currentToken(), () => log('401 — retrying %s once with a refreshed token', cmd.constructor?.name));
103
121
  }
104
122
  /**
105
123
  * Verify a PlaceId: `send(new VerifyAddressCommand({ PlaceId }))`, typed
@@ -125,9 +125,8 @@ export interface VerifyAddressCommandInput {
125
125
  * Japan, which may not be stored at all. Every other Places result is for
126
126
  * display.
127
127
  *
128
- * Keep the PlaceId you sent beside it. The answer's own `PlaceId` can differ,
129
- * and for a unit it does: the service fails that one on every Places route,
130
- * while the one you sent verifies again.
128
+ * Its `PlaceId` is the one sent, for a unit as for a building, so a stored
129
+ * verification can be verified, or looked up, again by its own `PlaceId`.
131
130
  */
132
131
  export type VerifyAddressResponse = Omit<GetPlaceResponse, 'PricingBucket'> & {
133
132
  /**
@@ -12,9 +12,36 @@
12
12
  * caller hears of it; there is no check to make beforehand.
13
13
  */
14
14
  export declare const FEATURE_NOT_ENTITLED = "FeatureNotEntitledException";
15
+ /**
16
+ * Every `code` the API sends (#38): its error contract, published at
17
+ * https://docs.chaosity.cloud/api/errors, plus the Amazon Location exception
18
+ * names it passes through from upstream.
19
+ *
20
+ * A few are worth knowing apart. `RateLimitExceededException` is the
21
+ * application's own throttle; `ThrottlingException` is Amazon Location
22
+ * throttling the service; `IpRateLimitExceededException` is the per-address
23
+ * limit in front of `/auth/token`. `ApplicationNotActiveException` (403) is an
24
+ * application that is new, suspended or off its plan.
25
+ * `TokenExpiredException` is in the contract but never sent: an expired token
26
+ * arrives as `UnauthorizedException`.
27
+ */
28
+ export declare const API_ERROR_CODES: readonly ["AccessDeniedException", "ApplicationNotActiveException", "ClientException", "FeatureNotEntitledException", "ForbiddenException", "InternalException", "InternalServerException", "InvalidCredentialsException", "IpRateLimitExceededException", "NotAcceptableException", "NotFoundException", "OriginNotAllowedException", "RateLimitExceededException", "ResourceNotFoundException", "ServiceUnavailableException", "ThrottlingException", "TimeoutException", "TokenExpiredException", "UnauthorizedException", "UpstreamException", "ValidationException"];
29
+ /**
30
+ * The codes only this package raises, for failures that never reached the
31
+ * API. It raises some of the API's codes too — `TimeoutException` for its own
32
+ * timeout, `InvalidCredentialsException` for a missing token — and those are
33
+ * in the list above.
34
+ */
35
+ export declare const CLIENT_ERROR_CODES: readonly ["AbortedException", "NetworkException", "ServiceException", "UnknownCommandException"];
36
+ export declare const LOCATION_SERVICE_ERROR_CODES: readonly ["AccessDeniedException", "ApplicationNotActiveException", "ClientException", "FeatureNotEntitledException", "ForbiddenException", "InternalException", "InternalServerException", "InvalidCredentialsException", "IpRateLimitExceededException", "NotAcceptableException", "NotFoundException", "OriginNotAllowedException", "RateLimitExceededException", "ResourceNotFoundException", "ServiceUnavailableException", "ThrottlingException", "TimeoutException", "TokenExpiredException", "UnauthorizedException", "UpstreamException", "ValidationException", "AbortedException", "NetworkException", "ServiceException", "UnknownCommandException"];
37
+ /**
38
+ * A `LocationServiceException`'s `code`: one of these, or a code the API adds
39
+ * after this release, which arrives all the same.
40
+ */
41
+ export type LocationServiceErrorCode = (typeof LOCATION_SERVICE_ERROR_CODES)[number];
15
42
  export interface LocationServiceExceptionOptions {
16
43
  message: string;
17
- code: string;
44
+ code: LocationServiceErrorCode | (string & {});
18
45
  /** Absent for failures that never reached the server: network, timeout, abort. */
19
46
  statusCode?: number;
20
47
  requestId?: string;
@@ -32,7 +59,8 @@ export interface LocationServiceExceptionOptions {
32
59
  * rather than sniffing at `TypeError` vs `DOMException` vs a bare `Error`.
33
60
  */
34
61
  export declare class LocationServiceException extends Error {
35
- readonly code: string;
62
+ /** See `LocationServiceErrorCode`: typed, and open to a code added later. */
63
+ readonly code: LocationServiceErrorCode | (string & {});
36
64
  readonly statusCode?: number;
37
65
  readonly requestId?: string;
38
66
  readonly details?: Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = void 0;
3
+ exports.LocationServiceException = exports.LOCATION_SERVICE_ERROR_CODES = exports.CLIENT_ERROR_CODES = exports.API_ERROR_CODES = exports.FEATURE_NOT_ENTITLED = void 0;
4
4
  /**
5
5
  * The code of a 403 for an option the application's plan does not include
6
6
  * (#55): a map feature such as `satellite` or `terrain`, or rich place data.
@@ -15,6 +15,58 @@ exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = void 0;
15
15
  * caller hears of it; there is no check to make beforehand.
16
16
  */
17
17
  exports.FEATURE_NOT_ENTITLED = 'FeatureNotEntitledException';
18
+ /**
19
+ * Every `code` the API sends (#38): its error contract, published at
20
+ * https://docs.chaosity.cloud/api/errors, plus the Amazon Location exception
21
+ * names it passes through from upstream.
22
+ *
23
+ * A few are worth knowing apart. `RateLimitExceededException` is the
24
+ * application's own throttle; `ThrottlingException` is Amazon Location
25
+ * throttling the service; `IpRateLimitExceededException` is the per-address
26
+ * limit in front of `/auth/token`. `ApplicationNotActiveException` (403) is an
27
+ * application that is new, suspended or off its plan.
28
+ * `TokenExpiredException` is in the contract but never sent: an expired token
29
+ * arrives as `UnauthorizedException`.
30
+ */
31
+ exports.API_ERROR_CODES = [
32
+ 'AccessDeniedException',
33
+ 'ApplicationNotActiveException',
34
+ 'ClientException',
35
+ 'FeatureNotEntitledException',
36
+ 'ForbiddenException',
37
+ 'InternalException',
38
+ 'InternalServerException',
39
+ 'InvalidCredentialsException',
40
+ 'IpRateLimitExceededException',
41
+ 'NotAcceptableException',
42
+ 'NotFoundException',
43
+ 'OriginNotAllowedException',
44
+ 'RateLimitExceededException',
45
+ 'ResourceNotFoundException',
46
+ 'ServiceUnavailableException',
47
+ 'ThrottlingException',
48
+ 'TimeoutException',
49
+ 'TokenExpiredException',
50
+ 'UnauthorizedException',
51
+ 'UpstreamException',
52
+ 'ValidationException',
53
+ ];
54
+ /**
55
+ * The codes only this package raises, for failures that never reached the
56
+ * API. It raises some of the API's codes too — `TimeoutException` for its own
57
+ * timeout, `InvalidCredentialsException` for a missing token — and those are
58
+ * in the list above.
59
+ */
60
+ exports.CLIENT_ERROR_CODES = [
61
+ 'AbortedException',
62
+ 'NetworkException',
63
+ 'ServiceException',
64
+ 'UnknownCommandException',
65
+ ];
66
+ exports.LOCATION_SERVICE_ERROR_CODES = [
67
+ ...exports.API_ERROR_CODES,
68
+ ...exports.CLIENT_ERROR_CODES,
69
+ ];
18
70
  /**
19
71
  * The single error type this package throws.
20
72
  *
@@ -4,11 +4,10 @@ export { DEFAULT_MAX_ATTEMPTS, DEFAULT_OVERALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, }
4
4
  export type { RequestOptions } from './transport/http.js';
5
5
  export { TOKEN_REFRESH_BUFFER_SECONDS, readTokenExpiry, } from './auth/tokenRefresh.js';
6
6
  export { FEATURE_NOT_ENTITLED, LocationServiceException, } from './errors/LocationServiceException.js';
7
- export type { LocationServiceExceptionOptions } from './errors/LocationServiceException.js';
8
- export * from '@aws-sdk/client-geo-places';
7
+ export type { LocationServiceErrorCode, LocationServiceExceptionOptions, } from './errors/LocationServiceException.js';
8
+ export * from './aws.js';
9
9
  export { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, VerifyAddressCommand, } from './client/commands.js';
10
10
  export type { AutocompleteCommandInput, AutocompleteRequest, GeocodeCommandInput, GeocodeRequest, GetPlaceCommandInput, GetPlaceRequest, NeverForwarded, ReverseGeocodeCommandInput, ReverseGeocodeRequest, SearchNearbyCommandInput, SearchNearbyRequest, SearchTextCommandInput, SearchTextRequest, SuggestCommandInput, SuggestRequest, VerifyAddressCommandInput, VerifyAddressResponse, } from './client/commands.js';
11
- export * from '@aws/amazon-location-utilities-datatypes';
12
11
  export { GeoPlaces } from './adapters/GeoPlaces.js';
13
12
  export type { GeoPlacesDetailOptions, GeoPlacesOptions, } from './adapters/GeoPlaces.js';
14
13
  export { createTransformRequest } from './maps/createTransformRequest.js';
@@ -17,10 +16,12 @@ export { POI_CATEGORIES, setAllPoiVisibility, setPoiVisibility, } from './maps/m
17
16
  export type { PoiCategory } from './maps/mapPoi.js';
18
17
  export { buildMapStyleUrl, fetchMapStyle } from './maps/mapStyle.js';
19
18
  export type { MapStyleOptions } from './maps/mapStyle.js';
19
+ export { refreshTokenOnUnauthorized } from './maps/mapToken.js';
20
+ export type { MapTokenSource, MapTokens, TokenRefreshMap, } from './maps/mapToken.js';
20
21
  export { buildStaticMapUrl, fetchStaticMap, staticMapAccept, } from './maps/staticMap.js';
21
22
  export type { StaticMapFileName, StaticMapOptions } from './maps/staticMap.js';
22
23
  export { BUILDINGS, COLOR_SCHEMES, CONTOUR_DENSITIES, LABEL_SIZES, MAP_FEATURE_MODES, MAP_STYLES, POI_DENSITIES, SCALE_BAR_UNITS, SPRITE_VARIANTS, STATIC_MAP_STYLES, STYLE_POI_CATEGORIES, TERRAINS, TRAFFIC_MODES, TRAVEL_MODES, } from './maps/mapEnums.js';
23
24
  export type { Buildings, ColorScheme, ContourDensity, LabelSize, MapFeatureMode, MapStyle, PoiDensity, ScaleBarUnit, SpriteVariant, StaticMapStyle, StylePoiCategory, Terrain, TrafficMode, TravelMode, } from './maps/mapEnums.js';
24
25
  export { transformRequest } from './maps/Utils.js';
25
- export type { ClientConfig, GeoPlacesCommand, MapLike } from './types/index.js';
26
+ export type { ClientConfig, CommandOutput, CommandWithOutput, GeoPlacesCommand, MapLike, } from './types/index.js';
26
27
  export type { AppConfigClaims } from './utils/tokenClaims.js';
package/dist/cjs/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.transformRequest = exports.TRAVEL_MODES = exports.TRAFFIC_MODES = exports.TERRAINS = exports.STYLE_POI_CATEGORIES = exports.STATIC_MAP_STYLES = exports.SPRITE_VARIANTS = exports.SCALE_BAR_UNITS = exports.POI_DENSITIES = exports.MAP_STYLES = exports.MAP_FEATURE_MODES = exports.LABEL_SIZES = exports.CONTOUR_DENSITIES = exports.COLOR_SCHEMES = exports.BUILDINGS = exports.staticMapAccept = exports.fetchStaticMap = exports.buildStaticMapUrl = exports.fetchMapStyle = exports.buildMapStyleUrl = exports.setPoiVisibility = exports.setAllPoiVisibility = exports.POI_CATEGORIES = exports.applyMapLanguage = exports.createTransformRequest = exports.GeoPlaces = exports.VerifyAddressCommand = exports.SuggestCommand = exports.SearchTextCommand = exports.SearchNearbyCommand = exports.ReverseGeocodeCommand = exports.GetPlaceCommand = exports.GeocodeCommand = exports.AutocompleteCommand = exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = exports.readTokenExpiry = exports.TOKEN_REFRESH_BUFFER_SECONDS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_OVERALL_TIMEOUT_MS = exports.DEFAULT_MAX_ATTEMPTS = exports.GeoPlacesClient = void 0;
17
+ exports.transformRequest = exports.TRAVEL_MODES = exports.TRAFFIC_MODES = exports.TERRAINS = exports.STYLE_POI_CATEGORIES = exports.STATIC_MAP_STYLES = exports.SPRITE_VARIANTS = exports.SCALE_BAR_UNITS = exports.POI_DENSITIES = exports.MAP_STYLES = exports.MAP_FEATURE_MODES = exports.LABEL_SIZES = exports.CONTOUR_DENSITIES = exports.COLOR_SCHEMES = exports.BUILDINGS = exports.staticMapAccept = exports.fetchStaticMap = exports.buildStaticMapUrl = exports.refreshTokenOnUnauthorized = exports.fetchMapStyle = exports.buildMapStyleUrl = exports.setPoiVisibility = exports.setAllPoiVisibility = exports.POI_CATEGORIES = exports.applyMapLanguage = exports.createTransformRequest = exports.GeoPlaces = exports.VerifyAddressCommand = exports.SuggestCommand = exports.SearchTextCommand = exports.SearchNearbyCommand = exports.ReverseGeocodeCommand = exports.GetPlaceCommand = exports.GeocodeCommand = exports.AutocompleteCommand = exports.LocationServiceException = exports.FEATURE_NOT_ENTITLED = exports.readTokenExpiry = exports.TOKEN_REFRESH_BUFFER_SECONDS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_OVERALL_TIMEOUT_MS = exports.DEFAULT_MAX_ATTEMPTS = exports.GeoPlacesClient = void 0;
18
18
  // Client (Custom - uses our auth instead of AWS SigV4)
19
19
  var GeoPlacesClient_js_1 = require("./client/GeoPlacesClient.js");
20
20
  Object.defineProperty(exports, "GeoPlacesClient", { enumerable: true, get: function () { return GeoPlacesClient_js_1.GeoPlacesClient; } });
@@ -31,11 +31,16 @@ Object.defineProperty(exports, "readTokenExpiry", { enumerable: true, get: funct
31
31
  var LocationServiceException_js_1 = require("./errors/LocationServiceException.js");
32
32
  Object.defineProperty(exports, "FEATURE_NOT_ENTITLED", { enumerable: true, get: function () { return LocationServiceException_js_1.FEATURE_NOT_ENTITLED; } });
33
33
  Object.defineProperty(exports, "LocationServiceException", { enumerable: true, get: function () { return LocationServiceException_js_1.LocationServiceException; } });
34
- // Re-export AWS SDK commands and types
35
- __exportStar(require("@aws-sdk/client-geo-places"), exports);
36
- // …except the seven Places commands and their inputs, which take neither
37
- // IntendedUse nor Key: the service strips both from every request (#40). A
38
- // named export wins over the `export *` above, as GeoPlacesClient's does.
34
+ // The AWS SDK's commands and types, and AWS Location Utilities' converters
35
+ // (#42): generated into ./aws.ts, values by name and types by `export type *`.
36
+ // Not `export *` of the packages themselves, which kept ~93 KB of SDK in the
37
+ // bundle of a consumer importing only a map helper — scripts/sdk-exports.mjs
38
+ // says why. A name this file exports itself is left out of ./aws.ts.
39
+ __exportStar(require("./aws.js"), exports);
40
+ // The seven Places commands and their inputs are this package's own: they take
41
+ // neither IntendedUse nor Key, which the service strips from every request
42
+ // (#40). A named export wins over `export *`, as GeoPlacesClient's does, and
43
+ // ./aws.ts leaves them out.
39
44
  // VerifyAddressCommand is this package's own: its route has no SDK command (#54).
40
45
  var commands_js_1 = require("./client/commands.js");
41
46
  Object.defineProperty(exports, "AutocompleteCommand", { enumerable: true, get: function () { return commands_js_1.AutocompleteCommand; } });
@@ -46,8 +51,6 @@ Object.defineProperty(exports, "SearchNearbyCommand", { enumerable: true, get: f
46
51
  Object.defineProperty(exports, "SearchTextCommand", { enumerable: true, get: function () { return commands_js_1.SearchTextCommand; } });
47
52
  Object.defineProperty(exports, "SuggestCommand", { enumerable: true, get: function () { return commands_js_1.SuggestCommand; } });
48
53
  Object.defineProperty(exports, "VerifyAddressCommand", { enumerable: true, get: function () { return commands_js_1.VerifyAddressCommand; } });
49
- // Re-export AWS Location Utilities (data type conversions)
50
- __exportStar(require("@aws/amazon-location-utilities-datatypes"), exports);
51
54
  // Adapters (Custom - for MapLibre integration)
52
55
  var GeoPlaces_js_1 = require("./adapters/GeoPlaces.js");
53
56
  Object.defineProperty(exports, "GeoPlaces", { enumerable: true, get: function () { return GeoPlaces_js_1.GeoPlaces; } });
@@ -63,6 +66,8 @@ Object.defineProperty(exports, "setPoiVisibility", { enumerable: true, get: func
63
66
  var mapStyle_js_1 = require("./maps/mapStyle.js");
64
67
  Object.defineProperty(exports, "buildMapStyleUrl", { enumerable: true, get: function () { return mapStyle_js_1.buildMapStyleUrl; } });
65
68
  Object.defineProperty(exports, "fetchMapStyle", { enumerable: true, get: function () { return mapStyle_js_1.fetchMapStyle; } });
69
+ var mapToken_js_1 = require("./maps/mapToken.js");
70
+ Object.defineProperty(exports, "refreshTokenOnUnauthorized", { enumerable: true, get: function () { return mapToken_js_1.refreshTokenOnUnauthorized; } });
66
71
  var staticMap_js_1 = require("./maps/staticMap.js");
67
72
  Object.defineProperty(exports, "buildStaticMapUrl", { enumerable: true, get: function () { return staticMap_js_1.buildStaticMapUrl; } });
68
73
  Object.defineProperty(exports, "fetchStaticMap", { enumerable: true, get: function () { return staticMap_js_1.fetchStaticMap; } });
@@ -1,4 +1,29 @@
1
1
  import type { RequestTransformFunction } from 'maplibre-gl';
2
+ /**
3
+ * Does this URL belong to our API?
4
+ *
5
+ * This decides who receives the customer's bearer token, and it used to be
6
+ * `url.startsWith(apiUrl)` — a string test standing in for a URL test. For
7
+ * `apiUrl = "https://api.example.com"`, the host `api.example.com.evil.test`
8
+ * is a prefix match, so a style referencing
9
+ * `https://api.example.com.evil.test/tiles/1/2/3` was handed
10
+ * `Authorization: Bearer <token>` and the token left the building (#34).
11
+ *
12
+ * That is reachable because a style descriptor is DATA: its `sprite`, `glyphs`
13
+ * and `sources` entries are URLs the style author chose, and MapLibre asks
14
+ * transformRequest about every one of them. Any style not wholly ours — a
15
+ * customer's own, or one edited through a tool — can name a host it likes.
16
+ *
17
+ * Compared as URLs instead, which also gets host case-folding, default ports
18
+ * (`https://api.test:443` === `https://api.test`) and userinfo right for free,
19
+ * and adds a path check so a shared host serving another tenant under a
20
+ * different base path is not "ours" either.
21
+ *
22
+ * Fails CLOSED: anything that will not parse gets no token. The only way to
23
+ * reach that is a relative `apiUrl` in a runtime with no `location` to resolve
24
+ * it against — i.e. not a browser, which is the only place MapLibre runs.
25
+ */
26
+ export declare function isOurApi(url: string, apiUrl: string): boolean;
2
27
  /**
3
28
  * Creates a transformRequest function for MapLibre that adds authentication
4
29
  * and proper Accept headers for AWS Location Service API requests.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isOurApi = isOurApi;
3
4
  exports.createTransformRequest = createTransformRequest;
4
5
  /**
5
6
  * Does this URL belong to our API?
@@ -1,20 +1,25 @@
1
1
  import type { Map } from 'maplibre-gl';
2
2
  /**
3
3
  * Mapping of POI category names to their MapLibre layer IDs in AWS GeoMaps tiles.
4
- * Layer IDs are stable across Standard, Monochrome, and Hybrid map styles.
4
+ *
5
+ * Standard and Hybrid carry the same sixteen `poi*` layers; Monochrome carries
6
+ * only the three park layers, and Satellite none. A category whose layers a
7
+ * style does not carry is skipped. AWS publishes no list of these ids, so
8
+ * `test/map-poi-layers.test.ts` holds this map to each style's layers as the
9
+ * service served them: every `poi*` layer in exactly one category (#33).
5
10
  */
6
11
  export declare const POI_CATEGORIES: {
7
12
  readonly food_drink: readonly ["poi_100_food_drink"];
8
13
  readonly entertainment: readonly ["poi_200_going_out_entertainment"];
9
14
  readonly sights: readonly ["poi_300_sights_museums"];
10
- readonly transit: readonly ["poi_400_transit"];
15
+ readonly transit: readonly ["poi_400_transit", "poi_400_transit_small"];
11
16
  readonly accommodations: readonly ["poi_500_accommodations"];
12
17
  readonly leisure: readonly ["poi_550_leisure_outdoor"];
13
18
  readonly shopping: readonly ["poi_600_shopping"];
14
- readonly business: readonly ["poi_700_business_services"];
15
- readonly facilities: readonly ["poi_800_facilities"];
19
+ readonly business: readonly ["poi_700_business_services", "poi_700_business_services_generic"];
20
+ readonly facilities: readonly ["poi_800_facilities", "poi_800_facilities_generic"];
16
21
  readonly areas: readonly ["poi_900_areas_buildings"];
17
- readonly parks: readonly ["poi_landuse_park", "poi_landuse_public_complex"];
22
+ readonly parks: readonly ["poi_landuse_park", "poi_landuse_park_lowzoom", "poi_landuse_public_complex"];
18
23
  };
19
24
  export type PoiCategory = keyof typeof POI_CATEGORIES;
20
25
  /**
@@ -5,20 +5,29 @@ exports.setPoiVisibility = setPoiVisibility;
5
5
  exports.setAllPoiVisibility = setAllPoiVisibility;
6
6
  /**
7
7
  * Mapping of POI category names to their MapLibre layer IDs in AWS GeoMaps tiles.
8
- * Layer IDs are stable across Standard, Monochrome, and Hybrid map styles.
8
+ *
9
+ * Standard and Hybrid carry the same sixteen `poi*` layers; Monochrome carries
10
+ * only the three park layers, and Satellite none. A category whose layers a
11
+ * style does not carry is skipped. AWS publishes no list of these ids, so
12
+ * `test/map-poi-layers.test.ts` holds this map to each style's layers as the
13
+ * service served them: every `poi*` layer in exactly one category (#33).
9
14
  */
10
15
  exports.POI_CATEGORIES = {
11
16
  food_drink: ['poi_100_food_drink'],
12
17
  entertainment: ['poi_200_going_out_entertainment'],
13
18
  sights: ['poi_300_sights_museums'],
14
- transit: ['poi_400_transit'],
19
+ transit: ['poi_400_transit', 'poi_400_transit_small'],
15
20
  accommodations: ['poi_500_accommodations'],
16
21
  leisure: ['poi_550_leisure_outdoor'],
17
22
  shopping: ['poi_600_shopping'],
18
- business: ['poi_700_business_services'],
19
- facilities: ['poi_800_facilities'],
23
+ business: ['poi_700_business_services', 'poi_700_business_services_generic'],
24
+ facilities: ['poi_800_facilities', 'poi_800_facilities_generic'],
20
25
  areas: ['poi_900_areas_buildings'],
21
- parks: ['poi_landuse_park', 'poi_landuse_public_complex'],
26
+ parks: [
27
+ 'poi_landuse_park',
28
+ 'poi_landuse_park_lowzoom',
29
+ 'poi_landuse_public_complex',
30
+ ],
22
31
  };
23
32
  /**
24
33
  * Set the visibility of one or more POI categories on the map.
@@ -1,6 +1,7 @@
1
1
  import type { StyleSpecification } from 'maplibre-gl';
2
2
  import type { RequestOptions } from '../transport/http.js';
3
3
  import type { Buildings, ColorScheme, ContourDensity, MapStyle, PoiDensity, StylePoiCategory, Terrain, TrafficMode, TravelMode } from './mapEnums.js';
4
+ import type { MapTokenSource } from './mapToken.js';
4
5
  /**
5
6
  * Options for building an AWS Location Service map style URL.
6
7
  * All parameters map directly to query parameters supported by the style descriptor endpoint.
@@ -137,10 +138,16 @@ export declare function buildMapStyleUrl(apiUrl: string, mapStyle: MapStyle, opt
137
138
  * `event.error.status` is 403, and `event.error.body` is a `Blob` holding the
138
139
  * same `{ message, code }` JSON.
139
140
  *
141
+ * A REFUSED TOKEN. Given `{ getToken, refreshToken }` in place of a bare
142
+ * `getToken`, a 401 asks `refreshToken` once and sends again when it brings a
143
+ * different token, within the same `signal` and `overallTimeoutMs` (#72). A
144
+ * 403 is never retried: a new token cannot change it.
145
+ *
140
146
  * @param apiUrl - Base URL of the Location Service API
141
147
  * @param mapStyle - Map style name: 'Standard' or 'Monochrome', or 'Satellite'
142
148
  * or 'Hybrid', which need the `satellite` plan feature (see `MAP_STYLES`)
143
- * @param getToken - Callback returning the current auth token
149
+ * @param tokens - Callback returning the current auth token, or
150
+ * `{ getToken, refreshToken }` to recover from a refused one
144
151
  * @param options - Style options; `language` is applied to the descriptor, all
145
152
  * others become URL params. Those tagged `@planFeature` need that feature of
146
153
  * the application's plan
@@ -151,6 +158,6 @@ export declare function buildMapStyleUrl(apiUrl: string, mapStyle: MapStyle, opt
151
158
  * const style = await fetchMapStyle(API_URL, 'Standard', getToken, { colorScheme: 'Dark', language: 'fr' })
152
159
  * const map = new maplibregl.Map({ style, transformRequest: createTransformRequest(API_URL, getToken) })
153
160
  */
154
- export declare function fetchMapStyle(apiUrl: string, mapStyle: MapStyle, getToken: () => string | undefined, options?: MapStyleOptions & {
161
+ export declare function fetchMapStyle(apiUrl: string, mapStyle: MapStyle, tokens: MapTokenSource, options?: MapStyleOptions & {
155
162
  language?: string;
156
163
  }, request?: RequestOptions): Promise<StyleSpecification>;
@@ -2,9 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildMapStyleUrl = buildMapStyleUrl;
4
4
  exports.fetchMapStyle = fetchMapStyle;
5
- const errors_js_1 = require("../transport/errors.js");
6
5
  const http_js_1 = require("../transport/http.js");
7
6
  const mapLanguage_js_1 = require("./mapLanguage.js");
7
+ const mapToken_js_1 = require("./mapToken.js");
8
8
  /**
9
9
  * Build a map style descriptor URL for the Location Service API.
10
10
  *
@@ -67,10 +67,16 @@ function buildMapStyleUrl(apiUrl, mapStyle, options = {}) {
67
67
  * `event.error.status` is 403, and `event.error.body` is a `Blob` holding the
68
68
  * same `{ message, code }` JSON.
69
69
  *
70
+ * A REFUSED TOKEN. Given `{ getToken, refreshToken }` in place of a bare
71
+ * `getToken`, a 401 asks `refreshToken` once and sends again when it brings a
72
+ * different token, within the same `signal` and `overallTimeoutMs` (#72). A
73
+ * 403 is never retried: a new token cannot change it.
74
+ *
70
75
  * @param apiUrl - Base URL of the Location Service API
71
76
  * @param mapStyle - Map style name: 'Standard' or 'Monochrome', or 'Satellite'
72
77
  * or 'Hybrid', which need the `satellite` plan feature (see `MAP_STYLES`)
73
- * @param getToken - Callback returning the current auth token
78
+ * @param tokens - Callback returning the current auth token, or
79
+ * `{ getToken, refreshToken }` to recover from a refused one
74
80
  * @param options - Style options; `language` is applied to the descriptor, all
75
81
  * others become URL params. Those tagged `@planFeature` need that feature of
76
82
  * the application's plan
@@ -81,16 +87,10 @@ function buildMapStyleUrl(apiUrl, mapStyle, options = {}) {
81
87
  * const style = await fetchMapStyle(API_URL, 'Standard', getToken, { colorScheme: 'Dark', language: 'fr' })
82
88
  * const map = new maplibregl.Map({ style, transformRequest: createTransformRequest(API_URL, getToken) })
83
89
  */
84
- async function fetchMapStyle(apiUrl, mapStyle, getToken, options = {}, request = {}) {
90
+ async function fetchMapStyle(apiUrl, mapStyle, tokens, options = {}, request = {}) {
85
91
  const { language, ...styleOptions } = options;
86
92
  const url = buildMapStyleUrl(apiUrl, mapStyle, styleOptions);
87
- const token = getToken();
88
- // `Bearer undefined` used to go out here, and came back as a 401 the caller
89
- // had to work backwards from — a whole round trip for a request that was
90
- // never going to succeed (#37).
91
- if (!token) {
92
- throw (0, errors_js_1.noTokenAvailable)('getToken() returned nothing, so no style request was sent. Check the token provider has finished initialising.');
93
- }
93
+ const call = (0, http_js_1.startCall)(request);
94
94
  // Through the shared transport, not a bare fetch: this gets the same
95
95
  // per-attempt timeout, overall budget, cancellation and retry as every other
96
96
  // call in the package, and the same error type on the way out. It also keeps
@@ -104,12 +104,16 @@ async function fetchMapStyle(apiUrl, mapStyle, getToken, options = {}, request =
104
104
  // This used to throw `Failed to fetch map style: 400`, discarding all of it
105
105
  // two lines before anyone could read it — the same defect #89 fixed in the
106
106
  // API, one layer up.
107
- const style = await (0, http_js_1.requestJson)(url, {
107
+ //
108
+ // `Bearer undefined` used to go out here, and came back as a 401 the caller
109
+ // had to work backwards from — a whole round trip for a request that was
110
+ // never going to succeed (#37). `sendWithTokenRefresh` refuses first.
111
+ const style = await (0, mapToken_js_1.sendWithTokenRefresh)(tokens, 'getToken() returned nothing, so no style request was sent. Check the token provider has finished initialising.', call, (token) => (0, http_js_1.requestJson)(url, {
108
112
  headers: {
109
113
  Authorization: `Bearer ${token}`,
110
114
  Accept: 'application/json',
111
115
  },
112
- }, request);
116
+ }, call));
113
117
  if (language) {
114
118
  applyLanguageToDescriptor(style, language);
115
119
  }