@chaosity/location-client 0.8.0 → 0.9.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.
- package/README.md +24 -11
- package/dist/cjs/client/GeoPlacesClient.d.ts +1 -1
- package/dist/cjs/client/GeoPlacesClient.js +7 -7
- package/dist/cjs/server/LocationServiceConnector.d.ts +1 -1
- package/dist/cjs/server/LocationServiceConnector.js +8 -10
- package/dist/cjs/utils/tokenClaims.d.ts +11 -13
- package/dist/cjs/utils/tokenClaims.js +11 -14
- package/dist/client/GeoPlacesClient.d.ts +1 -1
- package/dist/client/GeoPlacesClient.js +7 -7
- package/dist/server/LocationServiceConnector.d.ts +1 -1
- package/dist/server/LocationServiceConnector.js +8 -10
- package/dist/utils/tokenClaims.d.ts +11 -13
- package/dist/utils/tokenClaims.js +11 -14
- package/package.json +1 -1
- package/dist/cjs/utils/roundPosition.d.ts +0 -66
- package/dist/cjs/utils/roundPosition.js +0 -109
- package/dist/utils/roundPosition.d.ts +0 -66
- package/dist/utils/roundPosition.js +0 -104
package/README.md
CHANGED
|
@@ -485,17 +485,30 @@ const connector = new LocationServiceConnector({
|
|
|
485
485
|
})
|
|
486
486
|
```
|
|
487
487
|
|
|
488
|
-
##
|
|
489
|
-
|
|
490
|
-
`
|
|
491
|
-
|
|
492
|
-
the
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
488
|
+
## Coordinates Are Sent As You Supply Them
|
|
489
|
+
|
|
490
|
+
Both `GeoPlacesClient` and `LocationServiceConnector` put your command input on
|
|
491
|
+
the wire unchanged. `BiasPosition` and `QueryPosition` arrive at the service at
|
|
492
|
+
the precision you passed.
|
|
493
|
+
|
|
494
|
+
Earlier versions rounded `BiasPosition` to a grid — 3 decimal places by
|
|
495
|
+
default — so that nearby callers could share a cached upstream answer. Requests
|
|
496
|
+
are no longer cached, so the rounding had nothing left to share and only
|
|
497
|
+
lowered the precision the geocoder worked from.
|
|
498
|
+
|
|
499
|
+
That is not a coarser result, it is a different one. A 3 dp grid moves a
|
|
500
|
+
coordinate by up to ~70 m, depending where in its cell the coordinate falls,
|
|
501
|
+
and the places a search returns change well inside that distance: measured
|
|
502
|
+
against this service, a bias moved ~70 m returned a different set of nearby
|
|
503
|
+
places, not the same set in a different order. If you were relying on the
|
|
504
|
+
rounding to group nearby requests, round before you call.
|
|
505
|
+
|
|
506
|
+
One coordinate is normalised: a static map's `center`, `bounding-box` and
|
|
507
|
+
`bounded-positions` are rounded to six decimals — ~10 cm, below one pixel of a
|
|
508
|
+
raster render. That is this library's choice, well inside what the service
|
|
509
|
+
accepts: at most fourteen decimals per number, and at most 36 characters for
|
|
510
|
+
the pair. A value straight from `map.getCenter()` carries fifteen or sixteen
|
|
511
|
+
decimals and fails the first of those. A format rule, not a precision policy.
|
|
499
512
|
|
|
500
513
|
## Logging
|
|
501
514
|
|
|
@@ -18,7 +18,7 @@ export declare class GeoPlacesClient {
|
|
|
18
18
|
constructor(config: ClientConfig);
|
|
19
19
|
/**
|
|
20
20
|
* This application's own configuration, as carried on the access token
|
|
21
|
-
* (api#65) —
|
|
21
|
+
* (api#65) — today, the countries it is scoped to.
|
|
22
22
|
*
|
|
23
23
|
* Provided so an application can SHOW its own settings: populate a country
|
|
24
24
|
* selector with the markets it actually serves, label a settings screen,
|
|
@@ -8,7 +8,6 @@ const debug_1 = __importDefault(require("debug"));
|
|
|
8
8
|
const endpoints_js_1 = require("../transport/endpoints.js");
|
|
9
9
|
const errors_js_1 = require("../transport/errors.js");
|
|
10
10
|
const http_js_1 = require("../transport/http.js");
|
|
11
|
-
const roundPosition_js_1 = require("../utils/roundPosition.js");
|
|
12
11
|
const tokenClaims_js_1 = require("../utils/tokenClaims.js");
|
|
13
12
|
const log = (0, debug_1.default)('location-client:api');
|
|
14
13
|
/**
|
|
@@ -26,7 +25,7 @@ class GeoPlacesClient {
|
|
|
26
25
|
}
|
|
27
26
|
/**
|
|
28
27
|
* This application's own configuration, as carried on the access token
|
|
29
|
-
* (api#65) —
|
|
28
|
+
* (api#65) — today, the countries it is scoped to.
|
|
30
29
|
*
|
|
31
30
|
* Provided so an application can SHOW its own settings: populate a country
|
|
32
31
|
* selector with the markets it actually serves, label a settings screen,
|
|
@@ -100,10 +99,11 @@ class GeoPlacesClient {
|
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
dispatch(url, token, cmd, options) {
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
// The caller's input goes out as the caller wrote it. `BiasPosition` used
|
|
103
|
+
// to be rounded here to a grid sized by a token claim, so nearby callers
|
|
104
|
+
// shared a server cache entry; with no cache the rounding only lowered the
|
|
105
|
+
// precision the upstream geocoder had to work with, which moves the
|
|
106
|
+
// results rather than coarsening them (#51).
|
|
107
107
|
log('Sending %s to %s', cmd.constructor?.name, url);
|
|
108
108
|
return (0, http_js_1.requestJson)(url, {
|
|
109
109
|
method: 'POST',
|
|
@@ -111,7 +111,7 @@ class GeoPlacesClient {
|
|
|
111
111
|
'Content-Type': 'application/json',
|
|
112
112
|
Authorization: `Bearer ${token}`,
|
|
113
113
|
},
|
|
114
|
-
body: JSON.stringify(input),
|
|
114
|
+
body: JSON.stringify(cmd.input),
|
|
115
115
|
}, options);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -77,7 +77,7 @@ export declare class LocationServiceConnector {
|
|
|
77
77
|
private buildSource;
|
|
78
78
|
/**
|
|
79
79
|
* This application's own configuration, as carried on the access token
|
|
80
|
-
* (api#65) —
|
|
80
|
+
* (api#65) — today, the countries it is scoped to.
|
|
81
81
|
*
|
|
82
82
|
* Provided so an application can SHOW its own settings: populate a country
|
|
83
83
|
* selector with the markets it actually serves, label a settings screen, and
|
|
@@ -9,7 +9,6 @@ const LocationServiceException_js_1 = require("../errors/LocationServiceExceptio
|
|
|
9
9
|
const endpoints_js_1 = require("../transport/endpoints.js");
|
|
10
10
|
const errors_js_1 = require("../transport/errors.js");
|
|
11
11
|
const http_js_1 = require("../transport/http.js");
|
|
12
|
-
const roundPosition_js_1 = require("../utils/roundPosition.js");
|
|
13
12
|
const tokenClaims_js_1 = require("../utils/tokenClaims.js");
|
|
14
13
|
const getClientConfig_js_1 = require("./getClientConfig.js");
|
|
15
14
|
const log = (0, debug_1.default)('location-client:connector');
|
|
@@ -158,7 +157,7 @@ class LocationServiceConnector {
|
|
|
158
157
|
}
|
|
159
158
|
/**
|
|
160
159
|
* This application's own configuration, as carried on the access token
|
|
161
|
-
* (api#65) —
|
|
160
|
+
* (api#65) — today, the countries it is scoped to.
|
|
162
161
|
*
|
|
163
162
|
* Provided so an application can SHOW its own settings: populate a country
|
|
164
163
|
* selector with the markets it actually serves, label a settings screen, and
|
|
@@ -220,13 +219,12 @@ class LocationServiceConnector {
|
|
|
220
219
|
}
|
|
221
220
|
}
|
|
222
221
|
dispatch(url, token, cmd, options) {
|
|
223
|
-
// The
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
const input = (0, roundPosition_js_1.roundPositionFields)(cmd.input, biasDecimals);
|
|
222
|
+
// The caller's input goes out as the caller wrote it — nothing in the body
|
|
223
|
+
// is derived from the token any more. `BiasPosition` used to be rounded
|
|
224
|
+
// here to a grid sized by a token claim, so nearby callers shared a server
|
|
225
|
+
// cache entry; with no cache the rounding only lowered the precision the
|
|
226
|
+
// upstream geocoder had to work with, which moves the results rather than
|
|
227
|
+
// coarsening them (#51).
|
|
230
228
|
// Every system header is set exactly ONCE, and the caller's own spelling of
|
|
231
229
|
// each is dropped first.
|
|
232
230
|
//
|
|
@@ -251,7 +249,7 @@ class LocationServiceConnector {
|
|
|
251
249
|
Authorization: `Bearer ${token}`,
|
|
252
250
|
};
|
|
253
251
|
log('Sending %s request to %s', cmd.constructor?.name, url);
|
|
254
|
-
return (0, http_js_1.requestJson)(url, { method: 'POST', headers, body: JSON.stringify(input) }, options);
|
|
252
|
+
return (0, http_js_1.requestJson)(url, { method: 'POST', headers, body: JSON.stringify(cmd.input) }, options);
|
|
255
253
|
}
|
|
256
254
|
}
|
|
257
255
|
exports.LocationServiceConnector = LocationServiceConnector;
|
|
@@ -1,29 +1,27 @@
|
|
|
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 —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* `biasDecimals` used to be here too. It sized the grid this library rounded
|
|
9
|
+
* `BiasPosition` onto, so that nearby callers shared a server cache entry; the
|
|
10
|
+
* cache is gone, the API issues no such claim, and rounding a coordinate with
|
|
11
|
+
* nothing to share it with only lowered the precision the upstream geocoder
|
|
12
|
+
* received (#51).
|
|
7
13
|
*
|
|
8
14
|
* DELIBERATELY UNVERIFIED, and that is safe. This library has no signing key
|
|
9
15
|
* and does not need one: every claim here is re-read from the application row
|
|
10
16
|
* by the API on each request, and the API's answer is the one that counts. A
|
|
11
17
|
* forged token would fail at the authorizer long before any of this mattered.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* Nothing read here reaches a request at all now — it is displayed, never
|
|
19
|
+
* acted on, so a forged value misinforms only the caller who forged it.
|
|
14
20
|
*
|
|
15
21
|
* A JWT is signed, not encrypted, so the payload is plain base64url. Nothing
|
|
16
22
|
* secret is in it; these are the caller's own settings.
|
|
17
23
|
*/
|
|
18
24
|
export interface AppConfigClaims {
|
|
19
|
-
/**
|
|
20
|
-
* Bias precision this application is entitled to.
|
|
21
|
-
*
|
|
22
|
-
* Safe to act on: it only changes how a coordinate is rounded before
|
|
23
|
-
* sending, and the server re-rounds to its own configured value anyway. A
|
|
24
|
-
* stale value here costs precision, never correctness.
|
|
25
|
-
*/
|
|
26
|
-
biasDecimals?: number;
|
|
27
25
|
/**
|
|
28
26
|
* Countries this application may search, ISO 3166-1 alpha-2.
|
|
29
27
|
*
|
|
@@ -2,16 +2,22 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Read advisory application config out of the access token (api#65).
|
|
4
4
|
*
|
|
5
|
-
* The API puts an application's own settings —
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The API puts an application's own settings — today just `countries` — into
|
|
6
|
+
* the JWT alongside `allowedDomain` and `allowedResources`, so this library can
|
|
7
|
+
* stop hard-coding values it has no other way of knowing.
|
|
8
|
+
*
|
|
9
|
+
* `biasDecimals` used to be here too. It sized the grid this library rounded
|
|
10
|
+
* `BiasPosition` onto, so that nearby callers shared a server cache entry; the
|
|
11
|
+
* cache is gone, the API issues no such claim, and rounding a coordinate with
|
|
12
|
+
* nothing to share it with only lowered the precision the upstream geocoder
|
|
13
|
+
* received (#51).
|
|
8
14
|
*
|
|
9
15
|
* DELIBERATELY UNVERIFIED, and that is safe. This library has no signing key
|
|
10
16
|
* and does not need one: every claim here is re-read from the application row
|
|
11
17
|
* by the API on each request, and the API's answer is the one that counts. A
|
|
12
18
|
* forged token would fail at the authorizer long before any of this mattered.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
19
|
+
* Nothing read here reaches a request at all now — it is displayed, never
|
|
20
|
+
* acted on, so a forged value misinforms only the caller who forged it.
|
|
15
21
|
*
|
|
16
22
|
* A JWT is signed, not encrypted, so the payload is plain base64url. Nothing
|
|
17
23
|
* secret is in it; these are the caller's own settings.
|
|
@@ -39,15 +45,6 @@ function readAppConfigClaims(token) {
|
|
|
39
45
|
const json = decodeURIComponent(Array.from(atob(padded), (c) => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`).join(''));
|
|
40
46
|
const payload = JSON.parse(json);
|
|
41
47
|
const claims = {};
|
|
42
|
-
if (typeof payload.biasDecimals === 'number') {
|
|
43
|
-
claims.biasDecimals = payload.biasDecimals;
|
|
44
|
-
}
|
|
45
|
-
else if (typeof payload.biasDecimals === 'string' &&
|
|
46
|
-
payload.biasDecimals.trim() !== '') {
|
|
47
|
-
const n = Number(payload.biasDecimals);
|
|
48
|
-
if (Number.isFinite(n))
|
|
49
|
-
claims.biasDecimals = n;
|
|
50
|
-
}
|
|
51
48
|
if (Array.isArray(payload.countries)) {
|
|
52
49
|
const list = payload.countries.filter((c) => typeof c === 'string');
|
|
53
50
|
if (list.length)
|
|
@@ -18,7 +18,7 @@ export declare class GeoPlacesClient {
|
|
|
18
18
|
constructor(config: ClientConfig);
|
|
19
19
|
/**
|
|
20
20
|
* This application's own configuration, as carried on the access token
|
|
21
|
-
* (api#65) —
|
|
21
|
+
* (api#65) — today, the countries it is scoped to.
|
|
22
22
|
*
|
|
23
23
|
* Provided so an application can SHOW its own settings: populate a country
|
|
24
24
|
* selector with the markets it actually serves, label a settings screen,
|
|
@@ -2,7 +2,6 @@ import debug from 'debug';
|
|
|
2
2
|
import { resolveEndpoint } from '../transport/endpoints.js';
|
|
3
3
|
import { isTokenRejected, noTokenAvailable } from '../transport/errors.js';
|
|
4
4
|
import { requestJson } from '../transport/http.js';
|
|
5
|
-
import { roundPositionFields } from '../utils/roundPosition.js';
|
|
6
5
|
import { readAppConfigClaims } from '../utils/tokenClaims.js';
|
|
7
6
|
const log = debug('location-client:api');
|
|
8
7
|
/**
|
|
@@ -20,7 +19,7 @@ export class GeoPlacesClient {
|
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
21
|
* This application's own configuration, as carried on the access token
|
|
23
|
-
* (api#65) —
|
|
22
|
+
* (api#65) — today, the countries it is scoped to.
|
|
24
23
|
*
|
|
25
24
|
* Provided so an application can SHOW its own settings: populate a country
|
|
26
25
|
* selector with the markets it actually serves, label a settings screen,
|
|
@@ -94,10 +93,11 @@ export class GeoPlacesClient {
|
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
dispatch(url, token, cmd, options) {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
// The caller's input goes out as the caller wrote it. `BiasPosition` used
|
|
97
|
+
// to be rounded here to a grid sized by a token claim, so nearby callers
|
|
98
|
+
// shared a server cache entry; with no cache the rounding only lowered the
|
|
99
|
+
// precision the upstream geocoder had to work with, which moves the
|
|
100
|
+
// results rather than coarsening them (#51).
|
|
101
101
|
log('Sending %s to %s', cmd.constructor?.name, url);
|
|
102
102
|
return requestJson(url, {
|
|
103
103
|
method: 'POST',
|
|
@@ -105,7 +105,7 @@ export class GeoPlacesClient {
|
|
|
105
105
|
'Content-Type': 'application/json',
|
|
106
106
|
Authorization: `Bearer ${token}`,
|
|
107
107
|
},
|
|
108
|
-
body: JSON.stringify(input),
|
|
108
|
+
body: JSON.stringify(cmd.input),
|
|
109
109
|
}, options);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -77,7 +77,7 @@ export declare class LocationServiceConnector {
|
|
|
77
77
|
private buildSource;
|
|
78
78
|
/**
|
|
79
79
|
* This application's own configuration, as carried on the access token
|
|
80
|
-
* (api#65) —
|
|
80
|
+
* (api#65) — today, the countries it is scoped to.
|
|
81
81
|
*
|
|
82
82
|
* Provided so an application can SHOW its own settings: populate a country
|
|
83
83
|
* selector with the markets it actually serves, label a settings screen, and
|
|
@@ -3,7 +3,6 @@ import { LocationServiceException } from '../errors/LocationServiceException.js'
|
|
|
3
3
|
import { resolveEndpoint } from '../transport/endpoints.js';
|
|
4
4
|
import { isTokenRejected, noTokenAvailable } from '../transport/errors.js';
|
|
5
5
|
import { requestJson } from '../transport/http.js';
|
|
6
|
-
import { roundPositionFields } from '../utils/roundPosition.js';
|
|
7
6
|
import { readAppConfigClaims } from '../utils/tokenClaims.js';
|
|
8
7
|
import { resolveApiUrl, serverTokenSource } from './getClientConfig.js';
|
|
9
8
|
const log = debug('location-client:connector');
|
|
@@ -152,7 +151,7 @@ export class LocationServiceConnector {
|
|
|
152
151
|
}
|
|
153
152
|
/**
|
|
154
153
|
* This application's own configuration, as carried on the access token
|
|
155
|
-
* (api#65) —
|
|
154
|
+
* (api#65) — today, the countries it is scoped to.
|
|
156
155
|
*
|
|
157
156
|
* Provided so an application can SHOW its own settings: populate a country
|
|
158
157
|
* selector with the markets it actually serves, label a settings screen, and
|
|
@@ -214,13 +213,12 @@ export class LocationServiceConnector {
|
|
|
214
213
|
}
|
|
215
214
|
}
|
|
216
215
|
dispatch(url, token, cmd, options) {
|
|
217
|
-
// The
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
const input = roundPositionFields(cmd.input, biasDecimals);
|
|
216
|
+
// The caller's input goes out as the caller wrote it — nothing in the body
|
|
217
|
+
// is derived from the token any more. `BiasPosition` used to be rounded
|
|
218
|
+
// here to a grid sized by a token claim, so nearby callers shared a server
|
|
219
|
+
// cache entry; with no cache the rounding only lowered the precision the
|
|
220
|
+
// upstream geocoder had to work with, which moves the results rather than
|
|
221
|
+
// coarsening them (#51).
|
|
224
222
|
// Every system header is set exactly ONCE, and the caller's own spelling of
|
|
225
223
|
// each is dropped first.
|
|
226
224
|
//
|
|
@@ -245,7 +243,7 @@ export class LocationServiceConnector {
|
|
|
245
243
|
Authorization: `Bearer ${token}`,
|
|
246
244
|
};
|
|
247
245
|
log('Sending %s request to %s', cmd.constructor?.name, url);
|
|
248
|
-
return requestJson(url, { method: 'POST', headers, body: JSON.stringify(input) }, options);
|
|
246
|
+
return requestJson(url, { method: 'POST', headers, body: JSON.stringify(cmd.input) }, options);
|
|
249
247
|
}
|
|
250
248
|
}
|
|
251
249
|
function requireApiUrl(explicit) {
|
|
@@ -1,29 +1,27 @@
|
|
|
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 —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* `biasDecimals` used to be here too. It sized the grid this library rounded
|
|
9
|
+
* `BiasPosition` onto, so that nearby callers shared a server cache entry; the
|
|
10
|
+
* cache is gone, the API issues no such claim, and rounding a coordinate with
|
|
11
|
+
* nothing to share it with only lowered the precision the upstream geocoder
|
|
12
|
+
* received (#51).
|
|
7
13
|
*
|
|
8
14
|
* DELIBERATELY UNVERIFIED, and that is safe. This library has no signing key
|
|
9
15
|
* and does not need one: every claim here is re-read from the application row
|
|
10
16
|
* by the API on each request, and the API's answer is the one that counts. A
|
|
11
17
|
* forged token would fail at the authorizer long before any of this mattered.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* Nothing read here reaches a request at all now — it is displayed, never
|
|
19
|
+
* acted on, so a forged value misinforms only the caller who forged it.
|
|
14
20
|
*
|
|
15
21
|
* A JWT is signed, not encrypted, so the payload is plain base64url. Nothing
|
|
16
22
|
* secret is in it; these are the caller's own settings.
|
|
17
23
|
*/
|
|
18
24
|
export interface AppConfigClaims {
|
|
19
|
-
/**
|
|
20
|
-
* Bias precision this application is entitled to.
|
|
21
|
-
*
|
|
22
|
-
* Safe to act on: it only changes how a coordinate is rounded before
|
|
23
|
-
* sending, and the server re-rounds to its own configured value anyway. A
|
|
24
|
-
* stale value here costs precision, never correctness.
|
|
25
|
-
*/
|
|
26
|
-
biasDecimals?: number;
|
|
27
25
|
/**
|
|
28
26
|
* Countries this application may search, ISO 3166-1 alpha-2.
|
|
29
27
|
*
|
|
@@ -1,16 +1,22 @@
|
|
|
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 —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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.
|
|
7
|
+
*
|
|
8
|
+
* `biasDecimals` used to be here too. It sized the grid this library rounded
|
|
9
|
+
* `BiasPosition` onto, so that nearby callers shared a server cache entry; the
|
|
10
|
+
* cache is gone, the API issues no such claim, and rounding a coordinate with
|
|
11
|
+
* nothing to share it with only lowered the precision the upstream geocoder
|
|
12
|
+
* received (#51).
|
|
7
13
|
*
|
|
8
14
|
* DELIBERATELY UNVERIFIED, and that is safe. This library has no signing key
|
|
9
15
|
* and does not need one: every claim here is re-read from the application row
|
|
10
16
|
* by the API on each request, and the API's answer is the one that counts. A
|
|
11
17
|
* forged token would fail at the authorizer long before any of this mattered.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
18
|
+
* Nothing read here reaches a request at all now — it is displayed, never
|
|
19
|
+
* acted on, so a forged value misinforms only the caller who forged it.
|
|
14
20
|
*
|
|
15
21
|
* A JWT is signed, not encrypted, so the payload is plain base64url. Nothing
|
|
16
22
|
* secret is in it; these are the caller's own settings.
|
|
@@ -36,15 +42,6 @@ export function readAppConfigClaims(token) {
|
|
|
36
42
|
const json = decodeURIComponent(Array.from(atob(padded), (c) => `%${c.charCodeAt(0).toString(16).padStart(2, '0')}`).join(''));
|
|
37
43
|
const payload = JSON.parse(json);
|
|
38
44
|
const claims = {};
|
|
39
|
-
if (typeof payload.biasDecimals === 'number') {
|
|
40
|
-
claims.biasDecimals = payload.biasDecimals;
|
|
41
|
-
}
|
|
42
|
-
else if (typeof payload.biasDecimals === 'string' &&
|
|
43
|
-
payload.biasDecimals.trim() !== '') {
|
|
44
|
-
const n = Number(payload.biasDecimals);
|
|
45
|
-
if (Number.isFinite(n))
|
|
46
|
-
claims.biasDecimals = n;
|
|
47
|
-
}
|
|
48
45
|
if (Array.isArray(payload.countries)) {
|
|
49
46
|
const list = payload.countries.filter((c) => typeof c === 'string');
|
|
50
47
|
if (list.length)
|
package/package.json
CHANGED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Round coordinate arrays in command inputs so nearby callers share a server
|
|
3
|
-
* cache entry.
|
|
4
|
-
*
|
|
5
|
-
* `BiasPosition` is a hint about where to look, so collapsing it onto a grid
|
|
6
|
-
* lets two users in the same area reuse one upstream answer. `QueryPosition`
|
|
7
|
-
* is not a hint — reverse-geocode resolves an exact point a user clicked or a
|
|
8
|
-
* device reported, and rounding it returns a neighbour's address. It is left
|
|
9
|
-
* alone (RFC-0001 §2).
|
|
10
|
-
*
|
|
11
|
-
* WHY THE DEFAULT MOVED FROM 2 dp TO 3 dp
|
|
12
|
-
*
|
|
13
|
-
* 2 dp is a ~1.1 km grid, and that is not a coarser answer — it is a wrong
|
|
14
|
-
* one. Measured against Amazon Location directly, `QueryText: "cafe"` from
|
|
15
|
-
* Sydney CBD returns a completely different set of places when the bias moves
|
|
16
|
-
* 111 m:
|
|
17
|
-
*
|
|
18
|
-
* base -> Crepe de Paris | Deli Ziosa | CBD Patisserie
|
|
19
|
-
* +111 m -> Cafe Chocolat | Paradiso Cafe | Incanto Coffee
|
|
20
|
-
*
|
|
21
|
-
* So two users a kilometre apart both received whichever places were nearest
|
|
22
|
-
* the FIRST of them, with nothing in the response saying so. The server moved
|
|
23
|
-
* to 3 dp in api#21 — but this library rounds BEFORE sending, on both the
|
|
24
|
-
* browser and server paths, so the precision was already gone by the time the
|
|
25
|
-
* request arrived and that fix never reached anyone using the SDK. This is
|
|
26
|
-
* what makes it reach them.
|
|
27
|
-
*
|
|
28
|
-
* The cost is hit rate: cells shrink 100x in area. RFC-0001 §5 expected only
|
|
29
|
-
* "single-digit to low-double-digit percent" in mixed traffic, so there was
|
|
30
|
-
* little to protect, and a miss costs one upstream call while a wrong answer
|
|
31
|
-
* costs trust.
|
|
32
|
-
*/
|
|
33
|
-
/** The server's floor (api#65). A request for less is clamped up to it. */
|
|
34
|
-
export declare const DEFAULT_BIAS_DECIMALS = 3;
|
|
35
|
-
/**
|
|
36
|
-
* The band the server enforces (api#65). Mirrored here so the value this
|
|
37
|
-
* library rounds to is the value the server will actually key its cache by —
|
|
38
|
-
* rounding to something outside the band just means being wrong about what
|
|
39
|
-
* was sent.
|
|
40
|
-
*
|
|
41
|
-
* The floor is correctness: below 3 dp the nearest places are not the ones
|
|
42
|
-
* returned. The ceiling is cost: cells shrink 100x in area per decimal, so
|
|
43
|
-
* finer precision collapses the cache hit rate and every miss is a billable
|
|
44
|
-
* upstream call.
|
|
45
|
-
*/
|
|
46
|
-
export declare const MIN_BIAS_DECIMALS = 3;
|
|
47
|
-
export declare const MAX_BIAS_DECIMALS = 5;
|
|
48
|
-
/**
|
|
49
|
-
* Clamp a requested precision into the allowed band.
|
|
50
|
-
*
|
|
51
|
-
* Anything absent or malformed becomes the default rather than throwing: this
|
|
52
|
-
* runs on every request, and a surprising token claim must not break geocoding
|
|
53
|
-
* for an application that is otherwise working.
|
|
54
|
-
*/
|
|
55
|
-
export declare function clampBiasDecimals(requested?: unknown): number;
|
|
56
|
-
/**
|
|
57
|
-
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
58
|
-
* Returns the original object if no position fields are present.
|
|
59
|
-
*
|
|
60
|
-
* @param input the command input
|
|
61
|
-
* @param decimals precision for this application; defaults to the floor.
|
|
62
|
-
* Comes from the `biasDecimals` JWT claim where the token
|
|
63
|
-
* carries one (api#65) — an application entitled to finer
|
|
64
|
-
* bias gets it without the caller configuring anything.
|
|
65
|
-
*/
|
|
66
|
-
export declare function roundPositionFields<T extends object>(input: T, decimals?: number): T;
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Round coordinate arrays in command inputs so nearby callers share a server
|
|
4
|
-
* cache entry.
|
|
5
|
-
*
|
|
6
|
-
* `BiasPosition` is a hint about where to look, so collapsing it onto a grid
|
|
7
|
-
* lets two users in the same area reuse one upstream answer. `QueryPosition`
|
|
8
|
-
* is not a hint — reverse-geocode resolves an exact point a user clicked or a
|
|
9
|
-
* device reported, and rounding it returns a neighbour's address. It is left
|
|
10
|
-
* alone (RFC-0001 §2).
|
|
11
|
-
*
|
|
12
|
-
* WHY THE DEFAULT MOVED FROM 2 dp TO 3 dp
|
|
13
|
-
*
|
|
14
|
-
* 2 dp is a ~1.1 km grid, and that is not a coarser answer — it is a wrong
|
|
15
|
-
* one. Measured against Amazon Location directly, `QueryText: "cafe"` from
|
|
16
|
-
* Sydney CBD returns a completely different set of places when the bias moves
|
|
17
|
-
* 111 m:
|
|
18
|
-
*
|
|
19
|
-
* base -> Crepe de Paris | Deli Ziosa | CBD Patisserie
|
|
20
|
-
* +111 m -> Cafe Chocolat | Paradiso Cafe | Incanto Coffee
|
|
21
|
-
*
|
|
22
|
-
* So two users a kilometre apart both received whichever places were nearest
|
|
23
|
-
* the FIRST of them, with nothing in the response saying so. The server moved
|
|
24
|
-
* to 3 dp in api#21 — but this library rounds BEFORE sending, on both the
|
|
25
|
-
* browser and server paths, so the precision was already gone by the time the
|
|
26
|
-
* request arrived and that fix never reached anyone using the SDK. This is
|
|
27
|
-
* what makes it reach them.
|
|
28
|
-
*
|
|
29
|
-
* The cost is hit rate: cells shrink 100x in area. RFC-0001 §5 expected only
|
|
30
|
-
* "single-digit to low-double-digit percent" in mixed traffic, so there was
|
|
31
|
-
* little to protect, and a miss costs one upstream call while a wrong answer
|
|
32
|
-
* costs trust.
|
|
33
|
-
*/
|
|
34
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.MAX_BIAS_DECIMALS = exports.MIN_BIAS_DECIMALS = exports.DEFAULT_BIAS_DECIMALS = void 0;
|
|
36
|
-
exports.clampBiasDecimals = clampBiasDecimals;
|
|
37
|
-
exports.roundPositionFields = roundPositionFields;
|
|
38
|
-
/** The server's floor (api#65). A request for less is clamped up to it. */
|
|
39
|
-
exports.DEFAULT_BIAS_DECIMALS = 3;
|
|
40
|
-
/**
|
|
41
|
-
* The band the server enforces (api#65). Mirrored here so the value this
|
|
42
|
-
* library rounds to is the value the server will actually key its cache by —
|
|
43
|
-
* rounding to something outside the band just means being wrong about what
|
|
44
|
-
* was sent.
|
|
45
|
-
*
|
|
46
|
-
* The floor is correctness: below 3 dp the nearest places are not the ones
|
|
47
|
-
* returned. The ceiling is cost: cells shrink 100x in area per decimal, so
|
|
48
|
-
* finer precision collapses the cache hit rate and every miss is a billable
|
|
49
|
-
* upstream call.
|
|
50
|
-
*/
|
|
51
|
-
exports.MIN_BIAS_DECIMALS = 3;
|
|
52
|
-
exports.MAX_BIAS_DECIMALS = 5;
|
|
53
|
-
/**
|
|
54
|
-
* Clamp a requested precision into the allowed band.
|
|
55
|
-
*
|
|
56
|
-
* Anything absent or malformed becomes the default rather than throwing: this
|
|
57
|
-
* runs on every request, and a surprising token claim must not break geocoding
|
|
58
|
-
* for an application that is otherwise working.
|
|
59
|
-
*/
|
|
60
|
-
function clampBiasDecimals(requested) {
|
|
61
|
-
const n = Number(requested);
|
|
62
|
-
if ((typeof requested !== 'number' &&
|
|
63
|
-
!(typeof requested === 'string' && requested.trim() !== '')) ||
|
|
64
|
-
!Number.isFinite(n)) {
|
|
65
|
-
return exports.DEFAULT_BIAS_DECIMALS;
|
|
66
|
-
}
|
|
67
|
-
const floored = Math.floor(n);
|
|
68
|
-
if (floored < exports.MIN_BIAS_DECIMALS)
|
|
69
|
-
return exports.MIN_BIAS_DECIMALS;
|
|
70
|
-
if (floored > exports.MAX_BIAS_DECIMALS)
|
|
71
|
-
return exports.MAX_BIAS_DECIMALS;
|
|
72
|
-
return floored;
|
|
73
|
-
}
|
|
74
|
-
/** Known position field names and whether they should be rounded */
|
|
75
|
-
const POSITION_FIELDS = {
|
|
76
|
-
BiasPosition: true, // geocode, autocomplete, search — round for cache
|
|
77
|
-
QueryPosition: false, // reverse geocode — keep full precision
|
|
78
|
-
};
|
|
79
|
-
function roundCoord(value, decimals) {
|
|
80
|
-
const factor = 10 ** decimals;
|
|
81
|
-
return Math.round(value * factor) / factor;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
85
|
-
* Returns the original object if no position fields are present.
|
|
86
|
-
*
|
|
87
|
-
* @param input the command input
|
|
88
|
-
* @param decimals precision for this application; defaults to the floor.
|
|
89
|
-
* Comes from the `biasDecimals` JWT claim where the token
|
|
90
|
-
* carries one (api#65) — an application entitled to finer
|
|
91
|
-
* bias gets it without the caller configuring anything.
|
|
92
|
-
*/
|
|
93
|
-
function roundPositionFields(input, decimals = exports.DEFAULT_BIAS_DECIMALS) {
|
|
94
|
-
if (!input || typeof input !== 'object')
|
|
95
|
-
return input;
|
|
96
|
-
const dp = clampBiasDecimals(decimals);
|
|
97
|
-
let cloned = null;
|
|
98
|
-
for (const [field, shouldRound] of Object.entries(POSITION_FIELDS)) {
|
|
99
|
-
if (!shouldRound)
|
|
100
|
-
continue;
|
|
101
|
-
const value = input[field];
|
|
102
|
-
if (!Array.isArray(value))
|
|
103
|
-
continue;
|
|
104
|
-
if (!cloned)
|
|
105
|
-
cloned = { ...input };
|
|
106
|
-
cloned[field] = value.map((v) => typeof v === 'number' && Number.isFinite(v) ? roundCoord(v, dp) : v);
|
|
107
|
-
}
|
|
108
|
-
return cloned ?? input;
|
|
109
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Round coordinate arrays in command inputs so nearby callers share a server
|
|
3
|
-
* cache entry.
|
|
4
|
-
*
|
|
5
|
-
* `BiasPosition` is a hint about where to look, so collapsing it onto a grid
|
|
6
|
-
* lets two users in the same area reuse one upstream answer. `QueryPosition`
|
|
7
|
-
* is not a hint — reverse-geocode resolves an exact point a user clicked or a
|
|
8
|
-
* device reported, and rounding it returns a neighbour's address. It is left
|
|
9
|
-
* alone (RFC-0001 §2).
|
|
10
|
-
*
|
|
11
|
-
* WHY THE DEFAULT MOVED FROM 2 dp TO 3 dp
|
|
12
|
-
*
|
|
13
|
-
* 2 dp is a ~1.1 km grid, and that is not a coarser answer — it is a wrong
|
|
14
|
-
* one. Measured against Amazon Location directly, `QueryText: "cafe"` from
|
|
15
|
-
* Sydney CBD returns a completely different set of places when the bias moves
|
|
16
|
-
* 111 m:
|
|
17
|
-
*
|
|
18
|
-
* base -> Crepe de Paris | Deli Ziosa | CBD Patisserie
|
|
19
|
-
* +111 m -> Cafe Chocolat | Paradiso Cafe | Incanto Coffee
|
|
20
|
-
*
|
|
21
|
-
* So two users a kilometre apart both received whichever places were nearest
|
|
22
|
-
* the FIRST of them, with nothing in the response saying so. The server moved
|
|
23
|
-
* to 3 dp in api#21 — but this library rounds BEFORE sending, on both the
|
|
24
|
-
* browser and server paths, so the precision was already gone by the time the
|
|
25
|
-
* request arrived and that fix never reached anyone using the SDK. This is
|
|
26
|
-
* what makes it reach them.
|
|
27
|
-
*
|
|
28
|
-
* The cost is hit rate: cells shrink 100x in area. RFC-0001 §5 expected only
|
|
29
|
-
* "single-digit to low-double-digit percent" in mixed traffic, so there was
|
|
30
|
-
* little to protect, and a miss costs one upstream call while a wrong answer
|
|
31
|
-
* costs trust.
|
|
32
|
-
*/
|
|
33
|
-
/** The server's floor (api#65). A request for less is clamped up to it. */
|
|
34
|
-
export declare const DEFAULT_BIAS_DECIMALS = 3;
|
|
35
|
-
/**
|
|
36
|
-
* The band the server enforces (api#65). Mirrored here so the value this
|
|
37
|
-
* library rounds to is the value the server will actually key its cache by —
|
|
38
|
-
* rounding to something outside the band just means being wrong about what
|
|
39
|
-
* was sent.
|
|
40
|
-
*
|
|
41
|
-
* The floor is correctness: below 3 dp the nearest places are not the ones
|
|
42
|
-
* returned. The ceiling is cost: cells shrink 100x in area per decimal, so
|
|
43
|
-
* finer precision collapses the cache hit rate and every miss is a billable
|
|
44
|
-
* upstream call.
|
|
45
|
-
*/
|
|
46
|
-
export declare const MIN_BIAS_DECIMALS = 3;
|
|
47
|
-
export declare const MAX_BIAS_DECIMALS = 5;
|
|
48
|
-
/**
|
|
49
|
-
* Clamp a requested precision into the allowed band.
|
|
50
|
-
*
|
|
51
|
-
* Anything absent or malformed becomes the default rather than throwing: this
|
|
52
|
-
* runs on every request, and a surprising token claim must not break geocoding
|
|
53
|
-
* for an application that is otherwise working.
|
|
54
|
-
*/
|
|
55
|
-
export declare function clampBiasDecimals(requested?: unknown): number;
|
|
56
|
-
/**
|
|
57
|
-
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
58
|
-
* Returns the original object if no position fields are present.
|
|
59
|
-
*
|
|
60
|
-
* @param input the command input
|
|
61
|
-
* @param decimals precision for this application; defaults to the floor.
|
|
62
|
-
* Comes from the `biasDecimals` JWT claim where the token
|
|
63
|
-
* carries one (api#65) — an application entitled to finer
|
|
64
|
-
* bias gets it without the caller configuring anything.
|
|
65
|
-
*/
|
|
66
|
-
export declare function roundPositionFields<T extends object>(input: T, decimals?: number): T;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Round coordinate arrays in command inputs so nearby callers share a server
|
|
3
|
-
* cache entry.
|
|
4
|
-
*
|
|
5
|
-
* `BiasPosition` is a hint about where to look, so collapsing it onto a grid
|
|
6
|
-
* lets two users in the same area reuse one upstream answer. `QueryPosition`
|
|
7
|
-
* is not a hint — reverse-geocode resolves an exact point a user clicked or a
|
|
8
|
-
* device reported, and rounding it returns a neighbour's address. It is left
|
|
9
|
-
* alone (RFC-0001 §2).
|
|
10
|
-
*
|
|
11
|
-
* WHY THE DEFAULT MOVED FROM 2 dp TO 3 dp
|
|
12
|
-
*
|
|
13
|
-
* 2 dp is a ~1.1 km grid, and that is not a coarser answer — it is a wrong
|
|
14
|
-
* one. Measured against Amazon Location directly, `QueryText: "cafe"` from
|
|
15
|
-
* Sydney CBD returns a completely different set of places when the bias moves
|
|
16
|
-
* 111 m:
|
|
17
|
-
*
|
|
18
|
-
* base -> Crepe de Paris | Deli Ziosa | CBD Patisserie
|
|
19
|
-
* +111 m -> Cafe Chocolat | Paradiso Cafe | Incanto Coffee
|
|
20
|
-
*
|
|
21
|
-
* So two users a kilometre apart both received whichever places were nearest
|
|
22
|
-
* the FIRST of them, with nothing in the response saying so. The server moved
|
|
23
|
-
* to 3 dp in api#21 — but this library rounds BEFORE sending, on both the
|
|
24
|
-
* browser and server paths, so the precision was already gone by the time the
|
|
25
|
-
* request arrived and that fix never reached anyone using the SDK. This is
|
|
26
|
-
* what makes it reach them.
|
|
27
|
-
*
|
|
28
|
-
* The cost is hit rate: cells shrink 100x in area. RFC-0001 §5 expected only
|
|
29
|
-
* "single-digit to low-double-digit percent" in mixed traffic, so there was
|
|
30
|
-
* little to protect, and a miss costs one upstream call while a wrong answer
|
|
31
|
-
* costs trust.
|
|
32
|
-
*/
|
|
33
|
-
/** The server's floor (api#65). A request for less is clamped up to it. */
|
|
34
|
-
export const DEFAULT_BIAS_DECIMALS = 3;
|
|
35
|
-
/**
|
|
36
|
-
* The band the server enforces (api#65). Mirrored here so the value this
|
|
37
|
-
* library rounds to is the value the server will actually key its cache by —
|
|
38
|
-
* rounding to something outside the band just means being wrong about what
|
|
39
|
-
* was sent.
|
|
40
|
-
*
|
|
41
|
-
* The floor is correctness: below 3 dp the nearest places are not the ones
|
|
42
|
-
* returned. The ceiling is cost: cells shrink 100x in area per decimal, so
|
|
43
|
-
* finer precision collapses the cache hit rate and every miss is a billable
|
|
44
|
-
* upstream call.
|
|
45
|
-
*/
|
|
46
|
-
export const MIN_BIAS_DECIMALS = 3;
|
|
47
|
-
export const MAX_BIAS_DECIMALS = 5;
|
|
48
|
-
/**
|
|
49
|
-
* Clamp a requested precision into the allowed band.
|
|
50
|
-
*
|
|
51
|
-
* Anything absent or malformed becomes the default rather than throwing: this
|
|
52
|
-
* runs on every request, and a surprising token claim must not break geocoding
|
|
53
|
-
* for an application that is otherwise working.
|
|
54
|
-
*/
|
|
55
|
-
export function clampBiasDecimals(requested) {
|
|
56
|
-
const n = Number(requested);
|
|
57
|
-
if ((typeof requested !== 'number' &&
|
|
58
|
-
!(typeof requested === 'string' && requested.trim() !== '')) ||
|
|
59
|
-
!Number.isFinite(n)) {
|
|
60
|
-
return DEFAULT_BIAS_DECIMALS;
|
|
61
|
-
}
|
|
62
|
-
const floored = Math.floor(n);
|
|
63
|
-
if (floored < MIN_BIAS_DECIMALS)
|
|
64
|
-
return MIN_BIAS_DECIMALS;
|
|
65
|
-
if (floored > MAX_BIAS_DECIMALS)
|
|
66
|
-
return MAX_BIAS_DECIMALS;
|
|
67
|
-
return floored;
|
|
68
|
-
}
|
|
69
|
-
/** Known position field names and whether they should be rounded */
|
|
70
|
-
const POSITION_FIELDS = {
|
|
71
|
-
BiasPosition: true, // geocode, autocomplete, search — round for cache
|
|
72
|
-
QueryPosition: false, // reverse geocode — keep full precision
|
|
73
|
-
};
|
|
74
|
-
function roundCoord(value, decimals) {
|
|
75
|
-
const factor = 10 ** decimals;
|
|
76
|
-
return Math.round(value * factor) / factor;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Shallow-clone the input and round position arrays that benefit from caching.
|
|
80
|
-
* Returns the original object if no position fields are present.
|
|
81
|
-
*
|
|
82
|
-
* @param input the command input
|
|
83
|
-
* @param decimals precision for this application; defaults to the floor.
|
|
84
|
-
* Comes from the `biasDecimals` JWT claim where the token
|
|
85
|
-
* carries one (api#65) — an application entitled to finer
|
|
86
|
-
* bias gets it without the caller configuring anything.
|
|
87
|
-
*/
|
|
88
|
-
export function roundPositionFields(input, decimals = DEFAULT_BIAS_DECIMALS) {
|
|
89
|
-
if (!input || typeof input !== 'object')
|
|
90
|
-
return input;
|
|
91
|
-
const dp = clampBiasDecimals(decimals);
|
|
92
|
-
let cloned = null;
|
|
93
|
-
for (const [field, shouldRound] of Object.entries(POSITION_FIELDS)) {
|
|
94
|
-
if (!shouldRound)
|
|
95
|
-
continue;
|
|
96
|
-
const value = input[field];
|
|
97
|
-
if (!Array.isArray(value))
|
|
98
|
-
continue;
|
|
99
|
-
if (!cloned)
|
|
100
|
-
cloned = { ...input };
|
|
101
|
-
cloned[field] = value.map((v) => typeof v === 'number' && Number.isFinite(v) ? roundCoord(v, dp) : v);
|
|
102
|
-
}
|
|
103
|
-
return cloned ?? input;
|
|
104
|
-
}
|