attlaz-client 1.48.2 → 1.49.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
@@ -51,6 +51,11 @@ export declare class Client {
51
51
  apiEndpoint: string | undefined;
52
52
  }): Client;
53
53
  setClientCredentials(clientId: string, clientSecret?: string): void;
54
+ /**
55
+ * Configure this client as a public OAuth client (no client_secret).
56
+ * Used for SPAs and other public clients that cannot keep a secret.
57
+ */
58
+ setPublicClient(clientId: string): void;
54
59
  setVersion(version: string | null): void;
55
60
  getHttpClient(): OAuthClient;
56
61
  authenticate(): Promise<boolean>;
package/dist/Client.js CHANGED
@@ -104,6 +104,16 @@ export class Client {
104
104
  this.httpClient = new OAuthClient(options);
105
105
  this.transport = this.httpClient;
106
106
  }
107
+ /**
108
+ * Configure this client as a public OAuth client (no client_secret).
109
+ * Used for SPAs and other public clients that cannot keep a secret.
110
+ */
111
+ setPublicClient(clientId) {
112
+ const options = new OAuthClientOptions(this.apiEndpoint, clientId, null);
113
+ options.scopes = ['all'];
114
+ this.httpClient = new OAuthClient(options);
115
+ this.transport = this.httpClient;
116
+ }
107
117
  setVersion(version) {
108
118
  this.httpClient.setVersion(version);
109
119
  }
@@ -1,9 +1,8 @@
1
- import { AxiosError } from 'axios';
2
1
  export declare class ClientError extends Error {
3
2
  httpStatus: number | null;
4
3
  message: string;
5
4
  response: unknown;
6
5
  constructor(message: string, httpErrorCode?: number | null);
7
- static fromError(error: Error | AxiosError): ClientError;
6
+ static fromError(error: Error): ClientError;
8
7
  static byStatus(statusCode: number, statusText: string): ClientError | null;
9
8
  }
@@ -1,4 +1,3 @@
1
- import { AxiosError } from 'axios';
2
1
  import { HttpStatus } from './HttpStatus.js';
3
2
  export class ClientError extends Error {
4
3
  httpStatus = null;
@@ -11,34 +10,6 @@ export class ClientError extends Error {
11
10
  }
12
11
  static fromError(error) {
13
12
  let clientError = new ClientError('Unknown error', HttpStatus.HTTP_INTERNAL_SERVER_ERROR);
14
- if (error instanceof AxiosError) {
15
- if (error.code === 'ECONNREFUSED' || error.code === 'ERR_NETWORK') {
16
- clientError.httpStatus = HttpStatus.HTTP_UNAVAILABLE;
17
- clientError.message = 'Service not available';
18
- return clientError;
19
- }
20
- if (error.status !== undefined && error.status !== null) {
21
- clientError.httpStatus = error.status;
22
- }
23
- // console.log('Status', clientError.httpStatus);
24
- // if(clientError.httpStatus === HttpStatusCode.Ref)
25
- // clientError.message = error.message;
26
- if (error.response === undefined) {
27
- console.error('Unknown AxiosError error', {
28
- error, status: error.status, code: error.code,
29
- });
30
- }
31
- else {
32
- const response = {
33
- status: error.response.status,
34
- statusText: error.response.statusText,
35
- data: error.response.data,
36
- };
37
- clientError.message = response.statusText;
38
- clientError.response = response;
39
- }
40
- return clientError;
41
- }
42
13
  const xError = error;
43
14
  if (xError.status !== null && xError.status !== undefined) {
44
15
  const statusCode = xError.status;
@@ -72,6 +43,12 @@ export class ClientError extends Error {
72
43
  clientError.httpStatus = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;
73
44
  }
74
45
  }
46
+ else if (error instanceof TypeError) {
47
+ // fetch throws TypeError on network failures (ECONNREFUSED, DNS, etc.)
48
+ clientError.httpStatus = HttpStatus.HTTP_UNAVAILABLE;
49
+ clientError.message = 'Service not available';
50
+ return clientError;
51
+ }
75
52
  clientError.name = error.name;
76
53
  // TODO: only in debug mode
77
54
  clientError.stack = error.stack;
@@ -1,7 +1,5 @@
1
1
  import { HttpClientRequest } from './HttpClientRequest.js';
2
2
  import { HttpClientResponse } from './HttpClientResponse.js';
3
3
  export declare class HttpClient {
4
- private static readonly axiosInstance;
5
4
  static request(request: HttpClientRequest): Promise<HttpClientResponse>;
6
- private static axiosRequest;
7
5
  }
@@ -1,32 +1,51 @@
1
- import axios from 'axios';
2
1
  import { HttpClientResponse } from './HttpClientResponse.js';
3
2
  import { ClientError } from './ClientError.js';
4
3
  export class HttpClient {
5
- static axiosInstance = axios.create();
6
4
  static async request(request) {
7
- return HttpClient.axiosRequest(request);
8
- }
9
- static async axiosRequest(request) {
10
- const requestConfig = {
11
- url: request.getFullUrl(),
5
+ const init = {
12
6
  method: request.method,
13
7
  headers: request.headers,
14
8
  };
15
9
  if (request.body !== null && request.body !== undefined) {
16
- requestConfig.data = request.body;
10
+ init.body = request.body;
17
11
  }
18
12
  try {
19
- const rawResponse = await HttpClient.axiosInstance.request(requestConfig);
13
+ const rawResponse = await fetch(request.getFullUrl(), init);
14
+ if (!rawResponse.ok) {
15
+ const statusText = rawResponse.statusText || 'Unknown error';
16
+ const clientError = ClientError.byStatus(rawResponse.status, statusText)
17
+ ?? new ClientError(statusText, rawResponse.status);
18
+ // Try to attach the response body for downstream error handling
19
+ try {
20
+ clientError.response = {
21
+ status: rawResponse.status,
22
+ statusText,
23
+ data: await rawResponse.json(),
24
+ };
25
+ }
26
+ catch {
27
+ // Response body is not JSON — leave response as-is
28
+ }
29
+ throw clientError;
30
+ }
20
31
  const httpResponse = new HttpClientResponse(rawResponse.status, rawResponse.statusText);
21
- Object.keys(rawResponse.headers).forEach((header) => {
22
- const headerValue = rawResponse.headers[header];
23
- httpResponse.headers[header] = headerValue === null ? '' : headerValue;
32
+ rawResponse.headers.forEach((value, header) => {
33
+ httpResponse.headers[header] = value;
24
34
  });
25
- httpResponse.body = rawResponse.data;
35
+ const contentType = rawResponse.headers.get('content-type');
36
+ if (contentType !== null && contentType.includes('application/json')) {
37
+ httpResponse.body = await rawResponse.json();
38
+ }
39
+ else {
40
+ httpResponse.body = await rawResponse.text();
41
+ }
26
42
  return httpResponse;
27
43
  }
28
- catch (requestError) {
29
- throw ClientError.fromError(requestError);
44
+ catch (error) {
45
+ if (error instanceof ClientError) {
46
+ throw error;
47
+ }
48
+ throw ClientError.fromError(error);
30
49
  }
31
50
  }
32
51
  }
@@ -1,12 +1,11 @@
1
1
  import { Headers } from '../Data/Headers.js';
2
2
  import { Parameters } from '../Data/Parameters.js';
3
- import { ITransport } from './ITransport.js';
4
3
  import { OAuthClientOptions } from '../OAuthClientOptions.js';
5
4
  import { OAuthClientToken } from '../OAuthClientToken.js';
5
+ import { ITransport } from './ITransport.js';
6
6
  export declare class OAuthClient implements ITransport {
7
7
  private readonly options;
8
8
  private debug;
9
- private axiosInstance;
10
9
  private oauthClientToken;
11
10
  private refreshTokenPromise;
12
11
  private version;
@@ -14,7 +13,13 @@ export declare class OAuthClient implements ITransport {
14
13
  authenticate(username: string, password: string): Promise<boolean>;
15
14
  authenticate(): Promise<boolean>;
16
15
  refreshToken(): Promise<void>;
17
- private getAxiosInstance;
16
+ private isPublicClient;
17
+ /**
18
+ * Direct token request for public clients that must not send client_secret.
19
+ * The axios-oauth-client library always includes client_secret in the body,
20
+ * so public clients use this method instead.
21
+ */
22
+ private requestToken;
18
23
  isTokenExpires(): boolean;
19
24
  request<T>(action: string, parameters?: Parameters, method?: string, signWithOauthToken?: boolean): Promise<T>;
20
25
  isAuthenticated(): boolean;
@@ -1,5 +1,3 @@
1
- import axios from 'axios';
2
- import { clientCredentials, ownerCredentials, refreshToken } from 'axios-oauth-client';
3
1
  import { JsonSerializable } from '../../Model/JsonSerializable.js';
4
2
  import { VERSION } from '../../version.js';
5
3
  import { ClientError } from '../ClientError.js';
@@ -9,7 +7,6 @@ import { OAuthClientToken } from '../OAuthClientToken.js';
9
7
  export class OAuthClient {
10
8
  options;
11
9
  debug = false;
12
- axiosInstance = null;
13
10
  oauthClientToken = null;
14
11
  refreshTokenPromise = null;
15
12
  version = null;
@@ -22,15 +19,28 @@ export class OAuthClient {
22
19
  try {
23
20
  if (username !== null && username !== undefined && password !== null && password !== undefined) {
24
21
  // Password flow / Owner Credentials grant
25
- const getOwnerCredentials = ownerCredentials(this.getAxiosInstance(), this.getApiEndpointUrl(this.options.accessTokenUri), this.options.clientId === null ? undefined : this.options.clientId, this.options.clientSecret === null ? undefined : this.options.clientSecret);
26
- const rawAuthToken = await getOwnerCredentials(username, password, this.options.scopes.join(' '));
22
+ const params = {
23
+ grant_type: 'password',
24
+ client_id: this.options.clientId,
25
+ username,
26
+ password,
27
+ scope: this.options.scopes.join(' '),
28
+ };
29
+ if (!this.isPublicClient()) {
30
+ params.client_secret = this.options.clientSecret;
31
+ }
32
+ const rawAuthToken = await this.requestToken(params);
27
33
  this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
28
34
  return true;
29
35
  }
30
36
  if (this.options.clientId !== null && this.options.clientSecret !== null) {
31
- // Client credentials flow
32
- const getClientCredentials = clientCredentials(this.getAxiosInstance(), this.getApiEndpointUrl(this.options.accessTokenUri), this.options.clientId === null ? undefined : this.options.clientId, this.options.clientSecret === null ? undefined : this.options.clientSecret);
33
- const rawAuthToken = await getClientCredentials(this.options.scopes.join(' '));
37
+ // Client credentials flow (only for confidential clients)
38
+ const rawAuthToken = await this.requestToken({
39
+ grant_type: 'client_credentials',
40
+ client_id: this.options.clientId,
41
+ client_secret: this.options.clientSecret,
42
+ scope: this.options.scopes.join(' '),
43
+ });
34
44
  this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
35
45
  }
36
46
  return true;
@@ -53,24 +63,40 @@ export class OAuthClient {
53
63
  throw new Error('unable to refresh token, refresh token not set');
54
64
  }
55
65
  try {
56
- const getRefreshToken = refreshToken(this.getAxiosInstance(), this.getApiEndpointUrl(this.options.accessTokenUri), this.options.clientId === null ? undefined : this.options.clientId, this.options.clientSecret === null ? undefined : this.options.clientSecret);
57
- const auth = await getRefreshToken(this.oauthClientToken.refresh_token, this.options.scopes.join(' '));
58
- this.oauthClientToken = this.tokenToOauthClientToken(auth);
66
+ const params = {
67
+ grant_type: 'refresh_token',
68
+ client_id: this.options.clientId,
69
+ refresh_token: this.oauthClientToken.refresh_token,
70
+ scope: this.options.scopes.join(' '),
71
+ };
72
+ if (!this.isPublicClient()) {
73
+ params.client_secret = this.options.clientSecret;
74
+ }
75
+ const rawAuthToken = await this.requestToken(params);
76
+ this.oauthClientToken = this.tokenToOauthClientToken(rawAuthToken);
59
77
  }
60
78
  catch (e) {
61
79
  throw ClientError.fromError(e);
62
80
  }
63
81
  }
64
- getAxiosInstance() {
65
- if (this.axiosInstance === null) {
66
- const config = {
67
- // httpsAgent: new https.Agent({
68
- // rejectUnauthorized: false,
69
- // }),
70
- };
71
- this.axiosInstance = axios.create(config);
82
+ isPublicClient() {
83
+ return this.options.clientSecret === null || this.options.clientSecret === '';
84
+ }
85
+ /**
86
+ * Direct token request for public clients that must not send client_secret.
87
+ * The axios-oauth-client library always includes client_secret in the body,
88
+ * so public clients use this method instead.
89
+ */
90
+ async requestToken(params) {
91
+ const response = await fetch(this.getApiEndpointUrl(this.options.accessTokenUri), {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
94
+ body: new URLSearchParams(params).toString(),
95
+ });
96
+ if (!response.ok) {
97
+ throw new ClientError(response.statusText || 'Token request failed', response.status);
72
98
  }
73
- return this.axiosInstance;
99
+ return response.json();
74
100
  }
75
101
  isTokenExpires() {
76
102
  if (this.oauthClientToken === null) {
@@ -167,7 +193,7 @@ export class OAuthClient {
167
193
  this.version = version;
168
194
  }
169
195
  isNodeEnvironment() {
170
- return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
196
+ return typeof process !== 'undefined' && process.versions !== null && process.versions.node !== null;
171
197
  }
172
198
  tokenToOauthClientToken(oauthToken) {
173
199
  let scopes = '';
@@ -185,7 +211,7 @@ export class OAuthClient {
185
211
  return token;
186
212
  }
187
213
  getApiEndpointUrl(uri) {
188
- if (uri.indexOf('http://') === 0 || uri.indexOf('https://') === 0) {
214
+ if (uri.startsWith('http://') || uri.startsWith('https://')) {
189
215
  return uri;
190
216
  }
191
217
  return this.options.apiEndpoint.replace(/\/+$/, '') + '/' + uri.replace(/^\/+/g, '');
@@ -210,10 +236,9 @@ export class OAuthClient {
210
236
  requestData.setJsonHeader();
211
237
  }
212
238
  if (method === 'GET' && parameters !== null && parameters !== undefined) {
213
- Object.keys(parameters).forEach((key, index) => {
214
- // @ts-ignore
215
- const value = parameters[key];
216
- requestData.addQueryParam(key, value);
239
+ const params = parameters;
240
+ Object.keys(params).forEach((key) => {
241
+ requestData.addQueryParam(key, params[key]);
217
242
  });
218
243
  }
219
244
  if (signWithOauthToken) {
@@ -114,6 +114,11 @@ export class Endpoint {
114
114
  }
115
115
  throw this.toApiError(error);
116
116
  }
117
+ // TODO: add errors
118
+ /**
119
+ * Parse errors
120
+ */
121
+ // TODO: temporary check until we know the API is fully upgraded
117
122
  if (requestResponse === null || requestResponse === undefined) {
118
123
  throw new ApiError('Unable to parse object: response is empty for action `[' + method + '] ' + action + '`', HttpStatus.HTTP_INTERNAL_SERVER_ERROR);
119
124
  }
@@ -129,7 +134,7 @@ export class Endpoint {
129
134
  const apiError = new ApiError(error.message, error.httpStatus);
130
135
  if (error.response !== null && error.response !== undefined) {
131
136
  const response = error.response;
132
- if (response.data.error !== undefined) {
137
+ if (response.data !== null && response.data !== undefined && response.data.error !== undefined) {
133
138
  if (response.data.error.type !== undefined) {
134
139
  apiError.type = response.data.error.type;
135
140
  }
@@ -165,6 +170,7 @@ export class Endpoint {
165
170
  return data;
166
171
  }
167
172
  parseObject(rawObject, parser) {
173
+ // TODO: is it interesting to keep this wrapper, or only in develop mode?
168
174
  const wrappedData = ObjectWrapper.wrap(rawObject);
169
175
  try {
170
176
  return parser(wrappedData);
@@ -1,7 +1,7 @@
1
- import { Endpoint } from './Endpoint.js';
1
+ import { EntityType } from '../Model/EntityType.js';
2
2
  import { HealthAlert } from '../Model/HealthAlert/HealthAlert.js';
3
3
  import { CollectionResult } from '../Model/Result/CollectionResult.js';
4
- import { EntityType } from '../Model/EntityType.js';
4
+ import { Endpoint } from './Endpoint.js';
5
5
  export declare class HealthAlertEndpoint extends Endpoint {
6
- getLatestByScope(scopeType: EntityType, scopeId: string, parentScopeId: string | null): Promise<CollectionResult<HealthAlert>>;
6
+ getCurrentAlertsByScope(scopeType: EntityType, scopeId: string, parentScopeId: string | null): Promise<CollectionResult<HealthAlert>>;
7
7
  }
@@ -1,10 +1,10 @@
1
- import { Endpoint } from './Endpoint.js';
2
- import { HealthAlert } from '../Model/HealthAlert/HealthAlert.js';
3
1
  import { QueryString } from '../Http/Data/QueryString.js';
2
+ import { HealthAlert } from '../Model/HealthAlert/HealthAlert.js';
3
+ import { Endpoint } from './Endpoint.js';
4
4
  export class HealthAlertEndpoint extends Endpoint {
5
- async getLatestByScope(scopeType, scopeId, parentScopeId) {
5
+ async getCurrentAlertsByScope(scopeType, scopeId, parentScopeId) {
6
6
  try {
7
- const q = new QueryString('/health/' + scopeType + '/' + scopeId + '');
7
+ const q = new QueryString('/health/' + scopeType + '/' + scopeId);
8
8
  if (parentScopeId !== null) {
9
9
  q.set('parentScopeId', parentScopeId);
10
10
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.48.1";
1
+ export declare const VERSION = "1.49.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.48.1";
1
+ export const VERSION = "1.49.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "attlaz-client",
3
- "version": "1.48.2",
3
+ "version": "1.49.0",
4
4
  "description": "Javascript Client to access Attlaz API",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",
@@ -48,28 +48,20 @@
48
48
  "cache": "~/.npm",
49
49
  "registry": "https://registry.npmjs.org"
50
50
  },
51
- "dependencies": {
52
- "axios": "^1.13.6",
53
- "axios-oauth-client": "^2.2.0"
54
- },
55
51
  "devDependencies": {
56
52
  "@types/jest": "^30.0.0",
57
- "@types/node": "^24.10.15",
53
+ "@types/node": "^24.12.0",
58
54
  "@typescript-eslint/eslint-plugin": "^8.1.0",
59
55
  "@typescript-eslint/parser": "^8.1.0",
60
- "dotenv": "^17.3.1",
61
- "eslint": "^9.39.3",
62
- "eslint-config-attlaz-base": "^1.5.2",
56
+ "eslint": "^9.39.4",
57
+ "eslint-config-attlaz-base": "^1.6.0",
63
58
  "eslint-import-resolver-typescript": "^4.4.4",
64
59
  "eslint-plugin-import": "^2.32.0",
65
60
  "eslint-plugin-jsdoc": "^51.4.1",
66
61
  "eslint-plugin-prefer-arrow": "^1.2.3",
67
62
  "eslint-plugin-promise": "^7.2.1",
68
- "jest": "^30.2.0",
63
+ "jest": "^30.3.0",
69
64
  "rimraf": "^6.1.3",
70
- "rollup-plugin-commonjs": "^10.1.0",
71
- "rollup-plugin-node-resolve": "^5.2.0",
72
- "rollup-plugin-typescript": "^1.0.1",
73
65
  "ts-jest": "^29.4.6",
74
66
  "typescript": "^5.9.3"
75
67
  },