@chaosity/location-client 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/TokenProvider.js +8 -3
- package/dist/auth/tokenRefresh.d.ts +37 -0
- package/dist/auth/tokenRefresh.js +54 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import debug from 'debug';
|
|
2
2
|
import { LocationServiceException } from '../errors/LocationServiceException';
|
|
3
3
|
import { requestJson } from '../transport/http';
|
|
4
|
+
import { TOKEN_REFRESH_BUFFER_SECONDS, readTokenExpiry } from './tokenRefresh';
|
|
4
5
|
const log = debug('location-client:auth');
|
|
5
6
|
/**
|
|
6
7
|
* TokenProvider - SERVER-SIDE ONLY
|
|
@@ -110,9 +111,13 @@ export class TokenProvider {
|
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
this.cachedToken = data.access_token;
|
|
113
|
-
//
|
|
114
|
+
// The token's own `exp` claim first — it is the only value that cannot
|
|
115
|
+
// disagree with what the API will actually accept. `expires_at` and
|
|
116
|
+
// `expires_in` are what the response CLAIMS, and are kept as fallbacks.
|
|
114
117
|
this.cachedExpiresAt =
|
|
115
|
-
data.
|
|
118
|
+
readTokenExpiry(data.access_token) ??
|
|
119
|
+
data.expires_at ??
|
|
120
|
+
Date.now() + (data.expires_in ?? 900) * 1000;
|
|
116
121
|
log('Token acquired successfully (expires in %ds)', Math.floor((this.cachedExpiresAt - Date.now()) / 1000));
|
|
117
122
|
return {
|
|
118
123
|
success: true,
|
|
@@ -120,7 +125,7 @@ export class TokenProvider {
|
|
|
120
125
|
expiresAt: this.cachedExpiresAt,
|
|
121
126
|
};
|
|
122
127
|
}
|
|
123
|
-
isExpired(bufferSeconds =
|
|
128
|
+
isExpired(bufferSeconds = TOKEN_REFRESH_BUFFER_SECONDS) {
|
|
124
129
|
if (!this.cachedExpiresAt)
|
|
125
130
|
return true;
|
|
126
131
|
return Date.now() >= this.cachedExpiresAt - bufferSeconds * 1000;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long before expiry a token is treated as needing replacement.
|
|
3
|
+
*
|
|
4
|
+
* ONE number, used by both sides: the server-side `TokenProvider` deciding
|
|
5
|
+
* whether its cached token is still good, and the React provider deciding
|
|
6
|
+
* whether to ask for a new one. Both apply it to the same `exp` claim, so they
|
|
7
|
+
* cannot reach different answers about the same token.
|
|
8
|
+
*
|
|
9
|
+
* It used to be two separate `60`s — a private literal in `isExpired()` and a
|
|
10
|
+
* public `refreshBuffer` prop default — with nothing tying them together. On
|
|
11
|
+
* 2026-08-23 a consumer passed `refreshBuffer={800}` against a 900 s token: the
|
|
12
|
+
* client judged it stale after 100 s, the server still considered it fresh for
|
|
13
|
+
* another 840 s and returned the same one, and the client asked again
|
|
14
|
+
* immediately — roughly 110 requests per second from an idle page.
|
|
15
|
+
*
|
|
16
|
+
* Not configurable per consumer, deliberately. A settable client-side buffer is
|
|
17
|
+
* precisely what allowed that disagreement, and no amount of capping or backing
|
|
18
|
+
* off fixes it as cleanly as the two sides simply sharing the number.
|
|
19
|
+
*/
|
|
20
|
+
export declare const TOKEN_REFRESH_BUFFER_SECONDS = 60;
|
|
21
|
+
/**
|
|
22
|
+
* Read the `exp` claim out of a JWT, in milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* The expiry is IN THE TOKEN, so neither side needs to be told it. Both used to
|
|
25
|
+
* take it on trust from elsewhere — the server from the response body's
|
|
26
|
+
* `expires_at`, the React provider from whatever `getConfig` returned, falling
|
|
27
|
+
* back to inventing `Date.now() + 900_000` when that was absent. An invented
|
|
28
|
+
* expiry is how the two ended up disagreeing about the same token.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT verified. This is only used to decide *when to refresh*;
|
|
31
|
+
* nothing is authorised on the strength of it, and the API verifies the
|
|
32
|
+
* signature on every request regardless. Trusting `exp` for scheduling is safe
|
|
33
|
+
* in a way that trusting it for access would not be.
|
|
34
|
+
*
|
|
35
|
+
* Returns undefined for anything unparseable, so callers keep their fallback.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readTokenExpiry(token: string | undefined): number | undefined;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long before expiry a token is treated as needing replacement.
|
|
3
|
+
*
|
|
4
|
+
* ONE number, used by both sides: the server-side `TokenProvider` deciding
|
|
5
|
+
* whether its cached token is still good, and the React provider deciding
|
|
6
|
+
* whether to ask for a new one. Both apply it to the same `exp` claim, so they
|
|
7
|
+
* cannot reach different answers about the same token.
|
|
8
|
+
*
|
|
9
|
+
* It used to be two separate `60`s — a private literal in `isExpired()` and a
|
|
10
|
+
* public `refreshBuffer` prop default — with nothing tying them together. On
|
|
11
|
+
* 2026-08-23 a consumer passed `refreshBuffer={800}` against a 900 s token: the
|
|
12
|
+
* client judged it stale after 100 s, the server still considered it fresh for
|
|
13
|
+
* another 840 s and returned the same one, and the client asked again
|
|
14
|
+
* immediately — roughly 110 requests per second from an idle page.
|
|
15
|
+
*
|
|
16
|
+
* Not configurable per consumer, deliberately. A settable client-side buffer is
|
|
17
|
+
* precisely what allowed that disagreement, and no amount of capping or backing
|
|
18
|
+
* off fixes it as cleanly as the two sides simply sharing the number.
|
|
19
|
+
*/
|
|
20
|
+
export const TOKEN_REFRESH_BUFFER_SECONDS = 60;
|
|
21
|
+
/**
|
|
22
|
+
* Read the `exp` claim out of a JWT, in milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* The expiry is IN THE TOKEN, so neither side needs to be told it. Both used to
|
|
25
|
+
* take it on trust from elsewhere — the server from the response body's
|
|
26
|
+
* `expires_at`, the React provider from whatever `getConfig` returned, falling
|
|
27
|
+
* back to inventing `Date.now() + 900_000` when that was absent. An invented
|
|
28
|
+
* expiry is how the two ended up disagreeing about the same token.
|
|
29
|
+
*
|
|
30
|
+
* Deliberately NOT verified. This is only used to decide *when to refresh*;
|
|
31
|
+
* nothing is authorised on the strength of it, and the API verifies the
|
|
32
|
+
* signature on every request regardless. Trusting `exp` for scheduling is safe
|
|
33
|
+
* in a way that trusting it for access would not be.
|
|
34
|
+
*
|
|
35
|
+
* Returns undefined for anything unparseable, so callers keep their fallback.
|
|
36
|
+
*/
|
|
37
|
+
export function readTokenExpiry(token) {
|
|
38
|
+
if (!token)
|
|
39
|
+
return undefined;
|
|
40
|
+
const payload = token.split('.')[1];
|
|
41
|
+
if (!payload)
|
|
42
|
+
return undefined;
|
|
43
|
+
try {
|
|
44
|
+
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
45
|
+
const json = typeof atob === 'function'
|
|
46
|
+
? atob(base64)
|
|
47
|
+
: Buffer.from(base64, 'base64').toString('utf8');
|
|
48
|
+
const exp = JSON.parse(json)?.exp;
|
|
49
|
+
return typeof exp === 'number' ? exp * 1000 : undefined;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { GeoPlacesClient } from './client/GeoPlacesClient';
|
|
|
2
2
|
export type { SendOptions } from './client/GeoPlacesClient';
|
|
3
3
|
export { DEFAULT_MAX_ATTEMPTS, DEFAULT_TIMEOUT_MS } from './transport/http';
|
|
4
4
|
export type { RequestOptions } from './transport/http';
|
|
5
|
+
export { TOKEN_REFRESH_BUFFER_SECONDS, readTokenExpiry, } from './auth/tokenRefresh';
|
|
5
6
|
export { LocationServiceException } from './errors/LocationServiceException';
|
|
6
7
|
export type { LocationServiceExceptionOptions } from './errors/LocationServiceException';
|
|
7
8
|
export * from '@aws-sdk/client-geo-places';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export { GeoPlacesClient } from './client/GeoPlacesClient';
|
|
3
3
|
// Transport options — cancellation, per-attempt timeout, retry policy
|
|
4
4
|
export { DEFAULT_MAX_ATTEMPTS, DEFAULT_TIMEOUT_MS } from './transport/http';
|
|
5
|
+
// Token refresh policy — shared by the server provider and the React provider
|
|
6
|
+
export { TOKEN_REFRESH_BUFFER_SECONDS, readTokenExpiry, } from './auth/tokenRefresh';
|
|
5
7
|
// Errors
|
|
6
8
|
export { LocationServiceException } from './errors/LocationServiceException';
|
|
7
9
|
// Re-export AWS SDK commands and types
|