@attlaz/client 1.106.1 → 1.107.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/dist/Client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OAuthClientToken } from './Http/OAuthClientToken.js';
2
2
  import { ITransport, ParseErrorHandler } from './Http/Transport/ITransport.js';
3
- import { OAuthClient } from './Http/Transport/OAuthClient.js';
3
+ import { OAuthClient, TokenChangeHandler } from './Http/Transport/OAuthClient.js';
4
4
  import { AccessTokenEndpoint } from './Service/AccessTokenEndpoint.js';
5
5
  import { AdapterConnectionEndpoint } from './Service/AdapterConnectionEndpoint.js';
6
6
  import { AdapterEndpoint } from './Service/AdapterEndpoint.js';
@@ -91,6 +91,13 @@ export declare class Client {
91
91
  getToken(): OAuthClientToken | null;
92
92
  setToken(token: OAuthClientToken): void;
93
93
  unsetAccessToken(): void;
94
+ /**
95
+ * Observe tokens the client acquires or discards on its own — see {@link TokenChangeHandler}.
96
+ * A consumer that persists the token must use this rather than reading getToken() after a call:
97
+ * refresh tokens rotate, so a refreshed token that is not written back is replayed later and
98
+ * rejected. Pass null to stop observing. OAuth-only; a custom transport has no token.
99
+ */
100
+ setTokenChangeHandler(handler: TokenChangeHandler | null): void;
94
101
  getApiInformation(): Promise<{
95
102
  version: string;
96
103
  }>;
package/dist/Client.js CHANGED
@@ -184,6 +184,15 @@ export class Client {
184
184
  unsetAccessToken() {
185
185
  this.httpClient.unsetToken();
186
186
  }
187
+ /**
188
+ * Observe tokens the client acquires or discards on its own — see {@link TokenChangeHandler}.
189
+ * A consumer that persists the token must use this rather than reading getToken() after a call:
190
+ * refresh tokens rotate, so a refreshed token that is not written back is replayed later and
191
+ * rejected. Pass null to stop observing. OAuth-only; a custom transport has no token.
192
+ */
193
+ setTokenChangeHandler(handler) {
194
+ this.httpClient.setTokenChangeHandler(handler);
195
+ }
187
196
  async getApiInformation() {
188
197
  const result = await this.httpClient.request('/system/health', null, 'GET', false);
189
198
  if (result.version === undefined) {
@@ -3,10 +3,20 @@ import { Parameters } from '../Data/Parameters.js';
3
3
  import { OAuthClientOptions } from '../OAuthClientOptions.js';
4
4
  import { OAuthClientToken } from '../OAuthClientToken.js';
5
5
  import { ITransport, ParseErrorHandler } from './ITransport.js';
6
+ /**
7
+ * Invoked whenever the client changes the token by itself: after a successful authenticate or
8
+ * refresh (the server rotates refresh tokens, so the previous one is revoked at that moment), and
9
+ * with null when a refresh is rejected and the session is gone. A consumer that persists the token
10
+ * must listen here — reading getToken() after a call is too late, because a rotated token that is
11
+ * never written back is replayed later and rejected. Not invoked for setToken()/unsetToken(), which
12
+ * are the caller's own writes.
13
+ */
14
+ export type TokenChangeHandler = (token: OAuthClientToken | null) => void;
6
15
  export declare class OAuthClient implements ITransport {
7
16
  private readonly options;
8
17
  private debug;
9
18
  private oauthClientToken;
19
+ private tokenChangeHandler;
10
20
  private tokenAcquisitionPromise;
11
21
  private version;
12
22
  private parseErrorHandler;
@@ -43,6 +53,16 @@ export declare class OAuthClient implements ITransport {
43
53
  getToken(): OAuthClientToken | null;
44
54
  setToken(token: OAuthClientToken): void;
45
55
  unsetToken(): void;
56
+ /**
57
+ * Observe tokens the client acquires or discards on its own. See {@link TokenChangeHandler}.
58
+ * Pass null to stop observing.
59
+ */
60
+ setTokenChangeHandler(handler: TokenChangeHandler | null): void;
61
+ /**
62
+ * The single place the client updates its own token, so no acquisition path can change it
63
+ * without the handler hearing about it.
64
+ */
65
+ private applyToken;
46
66
  enableDebug(): void;
47
67
  disableDebug(): void;
48
68
  isDebugEnabled(): boolean;
@@ -9,6 +9,7 @@ export class OAuthClient {
9
9
  options;
10
10
  debug = false;
11
11
  oauthClientToken = null;
12
+ tokenChangeHandler = null;
12
13
  // Single-flight guard for token acquisition (initial client-credentials auth or refresh), so
13
14
  // concurrent requests share one in-flight acquisition instead of each hitting the token endpoint.
14
15
  tokenAcquisitionPromise = null;
@@ -34,7 +35,7 @@ export class OAuthClient {
34
35
  params.client_secret = this.options.clientSecret;
35
36
  }
36
37
  const rawAuthToken = await this.requestToken(params);
37
- this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
38
+ this.applyToken(this.tokenToOauthClientToken(rawAuthToken));
38
39
  return true;
39
40
  }
40
41
  if (this.options.clientId !== null && this.options.clientSecret !== null) {
@@ -45,7 +46,7 @@ export class OAuthClient {
45
46
  client_secret: this.options.clientSecret,
46
47
  scope: this.options.scopes.join(' '),
47
48
  });
48
- this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
49
+ this.applyToken(this.tokenToOauthClientToken(rawAuthToken));
49
50
  }
50
51
  return true;
51
52
  }
@@ -77,7 +78,7 @@ export class OAuthClient {
77
78
  params.client_secret = this.options.clientSecret;
78
79
  }
79
80
  const rawAuthToken = await this.requestToken(params);
80
- this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
81
+ this.applyToken(this.tokenToOauthClientToken(rawAuthToken));
81
82
  }
82
83
  catch (e) {
83
84
  if (e instanceof ClientError) {
@@ -88,6 +89,11 @@ export class OAuthClient {
88
89
  // error. Transient failures (network / 5xx) are rethrown unchanged so a
89
90
  // hiccup doesn't force a logout.
90
91
  if (e.httpStatus !== null && e.httpStatus >= 400 && e.httpStatus < 500) {
92
+ // Drop the rejected token: keeping it means every later request retries the
93
+ // same dead refresh token, and the server's firewall blocks the IP after a
94
+ // handful of those. A confidential client re-mints via client_credentials on
95
+ // the next request; a public one now fails fast with 401 instead of looping.
96
+ this.applyToken(null);
91
97
  throw new ClientError('Refresh token rejected, re-authentication required', HttpStatus.HTTP_UNAUTHORIZED);
92
98
  }
93
99
  throw e;
@@ -258,6 +264,32 @@ export class OAuthClient {
258
264
  unsetToken() {
259
265
  this.oauthClientToken = null;
260
266
  }
267
+ /**
268
+ * Observe tokens the client acquires or discards on its own. See {@link TokenChangeHandler}.
269
+ * Pass null to stop observing.
270
+ */
271
+ setTokenChangeHandler(handler) {
272
+ this.tokenChangeHandler = handler;
273
+ }
274
+ /**
275
+ * The single place the client updates its own token, so no acquisition path can change it
276
+ * without the handler hearing about it.
277
+ */
278
+ applyToken(token) {
279
+ this.oauthClientToken = token;
280
+ if (this.tokenChangeHandler === null) {
281
+ return;
282
+ }
283
+ try {
284
+ this.tokenChangeHandler(token);
285
+ }
286
+ catch (error) {
287
+ // A consumer's persistence failure must not fail the authentication that succeeded —
288
+ // the token in memory is valid either way. Report it: silently dropping it would hide
289
+ // exactly the stale-token bug this handler exists to prevent.
290
+ console.error('[OAuthClient] Token change handler threw', { error });
291
+ }
292
+ }
261
293
  enableDebug() {
262
294
  this.debug = true;
263
295
  }
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { ITransport } from './Http/Transport/ITransport.js';
11
11
  export { DirectTransport } from './Http/Transport/DirectTransport.js';
12
12
  export type { DirectTransportRoute } from './Http/Transport/DirectTransport.js';
13
13
  export { OAuthClient } from './Http/Transport/OAuthClient.js';
14
+ export type { TokenChangeHandler } from './Http/Transport/OAuthClient.js';
14
15
  export { OAuthClientOptions } from './Http/OAuthClientOptions.js';
15
16
  export { OAuthClientToken } from './Http/OAuthClientToken.js';
16
17
  export { ClientError } from './Http/ClientError.js';
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.106.0";
1
+ export declare const VERSION = "1.107.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.106.0";
1
+ export const VERSION = "1.107.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attlaz/client",
3
- "version": "1.106.1",
3
+ "version": "1.107.0",
4
4
  "description": "Javascript Client to access Attlaz API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",