attlaz-client 1.48.3 → 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,35 +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.code = 'Service not available';
18
- clientError.message = 'Service not available';
19
- return clientError;
20
- }
21
- if (error.status !== undefined && error.status !== null) {
22
- clientError.httpStatus = error.status;
23
- }
24
- // console.log('Status', clientError.httpStatus);
25
- // if(clientError.httpStatus === HttpStatusCode.Ref)
26
- // clientError.message = error.message;
27
- if (error.response === undefined) {
28
- console.error('Unknown AxiosError error', {
29
- error, status: error.status, code: error.code,
30
- });
31
- }
32
- else {
33
- const response = {
34
- status: error.response.status,
35
- statusText: error.response.statusText,
36
- data: error.response.data,
37
- };
38
- clientError.message = response.statusText;
39
- clientError.response = response;
40
- }
41
- return clientError;
42
- }
43
13
  const xError = error;
44
14
  if (xError.status !== null && xError.status !== undefined) {
45
15
  const statusCode = xError.status;
@@ -73,6 +43,12 @@ export class ClientError extends Error {
73
43
  clientError.httpStatus = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;
74
44
  }
75
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
+ }
76
52
  clientError.name = error.name;
77
53
  // TODO: only in debug mode
78
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) {
@@ -134,7 +134,7 @@ export class Endpoint {
134
134
  const apiError = new ApiError(error.message, error.httpStatus);
135
135
  if (error.response !== null && error.response !== undefined) {
136
136
  const response = error.response;
137
- if (response.data.error !== undefined) {
137
+ if (response.data !== null && response.data !== undefined && response.data.error !== undefined) {
138
138
  if (response.data.error.type !== undefined) {
139
139
  apiError.type = response.data.error.type;
140
140
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.48.3";
1
+ export declare const VERSION = "1.49.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = "1.48.3";
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.3",
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,18 +48,13 @@
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
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
56
  "eslint": "^9.39.4",
62
- "eslint-config-attlaz-base": "^1.5.2",
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",
@@ -67,9 +62,6 @@
67
62
  "eslint-plugin-promise": "^7.2.1",
68
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
  },