@chaosity/location-client 0.9.0 → 0.10.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 (50) hide show
  1. package/README.md +243 -34
  2. package/dist/adapters/GeoPlaces.d.ts +30 -3
  3. package/dist/adapters/GeoPlaces.js +5 -1
  4. package/dist/cjs/adapters/GeoPlaces.d.ts +30 -3
  5. package/dist/cjs/adapters/GeoPlaces.js +5 -4
  6. package/dist/cjs/client/GeoPlacesClient.d.ts +16 -4
  7. package/dist/cjs/client/GeoPlacesClient.js +18 -4
  8. package/dist/cjs/client/commands.d.ts +159 -0
  9. package/dist/cjs/client/commands.js +108 -0
  10. package/dist/cjs/errors/LocationServiceException.d.ts +28 -1
  11. package/dist/cjs/errors/LocationServiceException.js +31 -2
  12. package/dist/cjs/index.d.ts +5 -3
  13. package/dist/cjs/index.js +17 -1
  14. package/dist/cjs/maps/mapEnums.d.ts +82 -4
  15. package/dist/cjs/maps/mapEnums.js +98 -5
  16. package/dist/cjs/maps/mapStyle.d.ts +87 -10
  17. package/dist/cjs/maps/mapStyle.js +28 -5
  18. package/dist/cjs/maps/staticMap.d.ts +40 -0
  19. package/dist/cjs/maps/staticMap.js +4 -0
  20. package/dist/cjs/server/LocationServiceConnector.d.ts +23 -5
  21. package/dist/cjs/server/LocationServiceConnector.js +25 -5
  22. package/dist/cjs/transport/endpoints.js +3 -0
  23. package/dist/cjs/transport/http.d.ts +2 -2
  24. package/dist/cjs/transport/http.js +2 -2
  25. package/dist/cjs/types/index.d.ts +6 -5
  26. package/dist/cjs/utils/tokenClaims.d.ts +29 -3
  27. package/dist/cjs/utils/tokenClaims.js +28 -3
  28. package/dist/client/GeoPlacesClient.d.ts +16 -4
  29. package/dist/client/GeoPlacesClient.js +18 -4
  30. package/dist/client/commands.d.ts +159 -0
  31. package/dist/client/commands.js +97 -0
  32. package/dist/errors/LocationServiceException.d.ts +28 -1
  33. package/dist/errors/LocationServiceException.js +30 -1
  34. package/dist/index.d.ts +5 -3
  35. package/dist/index.js +7 -2
  36. package/dist/maps/mapEnums.d.ts +82 -4
  37. package/dist/maps/mapEnums.js +97 -4
  38. package/dist/maps/mapStyle.d.ts +87 -10
  39. package/dist/maps/mapStyle.js +28 -5
  40. package/dist/maps/staticMap.d.ts +40 -0
  41. package/dist/maps/staticMap.js +4 -0
  42. package/dist/server/LocationServiceConnector.d.ts +23 -5
  43. package/dist/server/LocationServiceConnector.js +25 -5
  44. package/dist/transport/endpoints.js +3 -0
  45. package/dist/transport/http.d.ts +2 -2
  46. package/dist/transport/http.js +2 -2
  47. package/dist/types/index.d.ts +6 -5
  48. package/dist/utils/tokenClaims.d.ts +29 -3
  49. package/dist/utils/tokenClaims.js +28 -3
  50. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import debug from 'debug';
2
+ import { VerifyAddressCommand } from '../client/commands.js';
2
3
  import { LocationServiceException } from '../errors/LocationServiceException.js';
3
4
  import { resolveEndpoint } from '../transport/endpoints.js';
4
5
  import { isTokenRejected, noTokenAvailable } from '../transport/errors.js';
@@ -88,7 +89,13 @@ function explainMissingOrigin(err, sentOrigin) {
88
89
  * ```typescript
89
90
  * // Credentials and apiUrl from the environment, Origin supplied here
90
91
  * const connector = new LocationServiceConnector({ origin: 'https://app.example.com' })
91
- * const result = await connector.send(new SearchTextCommand({ QueryText: 'Space Needle' }))
92
+ * const result = await connector.send(
93
+ * new SearchTextCommand({
94
+ * QueryText: 'Space Needle',
95
+ * // SearchText takes exactly one of BiasPosition, Filter.BoundingBox or Filter.Circle.
96
+ * BiasPosition: [-122.3493, 47.6205],
97
+ * }),
98
+ * )
92
99
  * ```
93
100
  */
94
101
  export class LocationServiceConnector {
@@ -151,17 +158,19 @@ export class LocationServiceConnector {
151
158
  }
152
159
  /**
153
160
  * This application's own configuration, as carried on the access token
154
- * (api#65) — today, the countries it is scoped to.
161
+ * (api#65): the routes it may call, the domain its requests must come from,
162
+ * and the countries it is scoped to (#40).
155
163
  *
156
164
  * Provided so an application can SHOW its own settings: populate a country
157
165
  * selector with the markets it actually serves, label a settings screen, and
158
166
  * so on. Being a few minutes stale is cosmetic for that.
159
167
  *
160
168
  * It is not an entitlement check. See AppConfigClaims for why acting on
161
- * `countries` client-side makes requests fail that would otherwise succeed.
169
+ * any of it client-side makes requests fail that would otherwise succeed.
162
170
  *
163
- * Returns `{}` when the token carries no application config, which is the
164
- * case until one is configured in the portal.
171
+ * Every token the API issues carries `allowedResources` and `allowedDomain`;
172
+ * `countries` only once a scope is configured in the portal. Returns `{}`
173
+ * for a token carrying none of them.
165
174
  */
166
175
  async getAppConfig() {
167
176
  return readAppConfigClaims(await this.source().get());
@@ -189,6 +198,17 @@ export class LocationServiceConnector {
189
198
  throw explainMissingOrigin(err, this.effectiveOrigin(options));
190
199
  }
191
200
  }
201
+ /**
202
+ * Verify a PlaceId: `send(new VerifyAddressCommand({ PlaceId }))`, typed
203
+ * (#54). Resolves the full place record plus `verified`, and resolves a
204
+ * `verified: false` too — see VerifyAddressResponse for what may be stored.
205
+ *
206
+ * Billed per call, whether or not the address verifies: call it once per
207
+ * chosen PlaceId, never per keystroke.
208
+ */
209
+ verifyAddress(placeId, options) {
210
+ return this.send(new VerifyAddressCommand({ PlaceId: placeId }), options);
211
+ }
192
212
  async dispatchWithRetry(source, url, cmd, options) {
193
213
  const token = await source.get();
194
214
  if (!token)
@@ -1,4 +1,5 @@
1
1
  import { AutocompleteCommand, GeocodeCommand, GetPlaceCommand, ReverseGeocodeCommand, SearchNearbyCommand, SearchTextCommand, SuggestCommand, } from '@aws-sdk/client-geo-places';
2
+ import { VerifyAddressCommand } from '../client/commands.js';
2
3
  import { LocationServiceException } from '../errors/LocationServiceException.js';
3
4
  /**
4
5
  * Command class -> API path.
@@ -17,6 +18,8 @@ const ENDPOINTS = new Map([
17
18
  [SearchNearbyCommand, '/address/search/nearby'],
18
19
  [SearchTextCommand, '/address/search/text'],
19
20
  [SuggestCommand, '/address/suggestion'],
21
+ // This package's own: the route has no AWS command (#54).
22
+ [VerifyAddressCommand, '/address/verify'],
20
23
  ]);
21
24
  export function resolveEndpoint(command) {
22
25
  for (const [CommandClass, endpoint] of ENDPOINTS) {
@@ -4,8 +4,8 @@ export declare const DEFAULT_TIMEOUT_MS = 10000;
4
4
  * Ceiling for the WHOLE call — every attempt plus every wait between them.
5
5
  *
6
6
  * `timeoutMs` bounds an attempt, not a call, and the gap between those two is
7
- * where the caller's own deadline disappears. The API answers a spent quota
8
- * with `Retry-After: 60`, which the retry loop honoured literally: two waits of
7
+ * where the caller's own deadline disappears. Nothing bounds the `Retry-After`
8
+ * a 429 carries, and the retry loop honoured one of 60 s literally: two waits of
9
9
  * a minute each, so one call could sit for ~120 s — past any Lambda budget,
10
10
  * past any HTTP gateway, and until now uncancellable (#37).
11
11
  *
@@ -8,8 +8,8 @@ export const DEFAULT_TIMEOUT_MS = 10000;
8
8
  * Ceiling for the WHOLE call — every attempt plus every wait between them.
9
9
  *
10
10
  * `timeoutMs` bounds an attempt, not a call, and the gap between those two is
11
- * where the caller's own deadline disappears. The API answers a spent quota
12
- * with `Retry-After: 60`, which the retry loop honoured literally: two waits of
11
+ * where the caller's own deadline disappears. Nothing bounds the `Retry-After`
12
+ * a 429 carries, and the retry loop honoured one of 60 s literally: two waits of
13
13
  * a minute each, so one call could sit for ~120 s — past any Lambda budget,
14
14
  * past any HTTP gateway, and until now uncancellable (#37).
15
15
  *
@@ -42,11 +42,12 @@ export interface ClientConfig {
42
42
  refreshToken?: () => Promise<string | undefined>;
43
43
  }
44
44
  /**
45
- * Minimal interface for AWS SDK command objects.
46
- * All AWS SDK commands (AutocompleteCommand, SearchTextCommand, etc.) extend
47
- * Smithy's Command base class which has an `input` property containing the
48
- * request parameters. This interface captures what we actually need from
49
- * commands without coupling to Smithy internals.
45
+ * Minimal interface for a command object: the AWS SDK's, and this package's
46
+ * own `VerifyAddressCommand` (#54). The SDK's commands (AutocompleteCommand,
47
+ * SearchTextCommand, etc.) extend Smithy's Command base class, which has an
48
+ * `input` property containing the request parameters; `VerifyAddressCommand`
49
+ * has the same `input` and nothing else. This interface captures what we
50
+ * actually need from commands without coupling to Smithy internals.
50
51
  */
51
52
  export interface GeoPlacesCommand {
52
53
  readonly input: object;
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Read advisory application config out of the access token (api#65).
3
3
  *
4
- * The API puts an application's own settings — today just `countries` — into
5
- * the JWT alongside `allowedDomain` and `allowedResources`, so this library can
6
- * stop hard-coding values it has no other way of knowing.
4
+ * The API puts an application's own settings into the JWT — `countries`,
5
+ * `allowedResources` and `allowedDomain` — so this library can stop
6
+ * hard-coding values it has no other way of knowing. The last two were in the
7
+ * token all along and were not surfaced until #40, so an application could
8
+ * only learn it lacked a route from the 403.
7
9
  *
8
10
  * `biasDecimals` used to be here too. It sized the grid this library rounded
9
11
  * `BiasPosition` onto, so that nearby callers shared a server cache entry; the
@@ -42,6 +44,30 @@ export interface AppConfigClaims {
42
44
  * Sending nothing and letting the API scope the request is always correct.
43
45
  */
44
46
  countries?: string[];
47
+ /**
48
+ * The routes this application may call, as the API names them — method and
49
+ * route template, such as `POST /address/autocomplete` or
50
+ * `GET /maps/static/{fileName}` (#40).
51
+ *
52
+ * So an application can ask "may I call the static-map route?" before it
53
+ * offers one, rather than only learning from the 403. It answers for the
54
+ * route, not for the options sent on it: a plan feature (`@planFeature`) is
55
+ * answered only by its own 403, and a static map's default Satellite render
56
+ * is one (#55). The same rule as `countries`
57
+ * applies, for the same reason: show it, never refuse with it. A route
58
+ * granted since the token was minted is answered by the API, which reads the
59
+ * entitlement fresh on every request.
60
+ *
61
+ * The API issues this claim JSON-encoded — a string holding the list —
62
+ * because it copies the application's stored setting. Both forms are read.
63
+ */
64
+ allowedResources?: string[];
65
+ /**
66
+ * The domain this application's requests must come from (#40). A request
67
+ * whose `Origin` is neither this host nor one of its subdomains is refused
68
+ * 403, so this is what to show next to "Origin not allowed".
69
+ */
70
+ allowedDomain?: string;
45
71
  }
46
72
  /**
47
73
  * Decode a JWT payload without verifying it.
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Read advisory application config out of the access token (api#65).
3
3
  *
4
- * The API puts an application's own settings — today just `countries` — into
5
- * the JWT alongside `allowedDomain` and `allowedResources`, so this library can
6
- * stop hard-coding values it has no other way of knowing.
4
+ * The API puts an application's own settings into the JWT — `countries`,
5
+ * `allowedResources` and `allowedDomain` — so this library can stop
6
+ * hard-coding values it has no other way of knowing. The last two were in the
7
+ * token all along and were not surfaced until #40, so an application could
8
+ * only learn it lacked a route from the 403.
7
9
  *
8
10
  * `biasDecimals` used to be here too. It sized the grid this library rounded
9
11
  * `BiasPosition` onto, so that nearby callers shared a server cache entry; the
@@ -21,6 +23,21 @@
21
23
  * A JWT is signed, not encrypted, so the payload is plain base64url. Nothing
22
24
  * secret is in it; these are the caller's own settings.
23
25
  */
26
+ /** A list of strings, whether the claim carries it as a list or JSON-encoded. */
27
+ function readStringList(value) {
28
+ let list = value;
29
+ if (typeof list === 'string') {
30
+ try {
31
+ list = JSON.parse(list);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ }
37
+ if (!Array.isArray(list))
38
+ return undefined;
39
+ return list.filter((v) => typeof v === 'string');
40
+ }
24
41
  /**
25
42
  * Decode a JWT payload without verifying it.
26
43
  *
@@ -47,6 +64,14 @@ export function readAppConfigClaims(token) {
47
64
  if (list.length)
48
65
  claims.countries = list;
49
66
  }
67
+ // Kept when empty, unlike countries: no countries means "search the
68
+ // world", but no resources means an application entitled to nothing.
69
+ const resources = readStringList(payload.allowedResources);
70
+ if (resources)
71
+ claims.allowedResources = resources;
72
+ if (typeof payload.allowedDomain === 'string' && payload.allowedDomain) {
73
+ claims.allowedDomain = payload.allowedDomain;
74
+ }
50
75
  return claims;
51
76
  }
52
77
  catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaosity/location-client",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Client library for Chaosity Location Service with AWS Location Service compatibility",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.js",