@communecter/cocolight-api-client 1.0.51 → 1.0.55
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/401.cocolight-api-client.browser.js +1 -0
- package/dist/401.cocolight-api-client.cjs +1 -0
- package/dist/401.cocolight-api-client.mjs.js +1 -0
- package/dist/588.cocolight-api-client.browser.js +1 -0
- package/dist/588.cocolight-api-client.cjs +1 -0
- package/dist/588.cocolight-api-client.mjs.js +1 -0
- package/dist/593.cocolight-api-client.browser.js +1 -0
- package/dist/593.cocolight-api-client.cjs +1 -0
- package/dist/593.cocolight-api-client.mjs.js +1 -0
- package/dist/839.cocolight-api-client.browser.js +1 -0
- package/dist/839.cocolight-api-client.cjs +1 -0
- package/dist/839.cocolight-api-client.mjs.js +1 -0
- package/dist/cocolight-api-client.browser.js +3 -3
- package/dist/cocolight-api-client.cjs +1 -1
- package/dist/cocolight-api-client.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js +1 -1
- package/dist/cocolight-api-client.vite.mjs.js.map +1 -1
- package/package.json +28 -10
- package/src/{Api.js → Api.ts} +99 -91
- package/src/{ApiClient.js → ApiClient.ts} +444 -240
- package/src/EJSONType.ts +103 -0
- package/src/api/{Badge.js → Badge.ts} +56 -45
- package/src/api/BaseEntity.ts +3897 -0
- package/src/api/Comment.ts +200 -0
- package/src/api/{EndpointApi.js → EndpointApi.ts} +365 -299
- package/src/api/{EndpointApi.types.d.ts → EndpointApi.types.ts} +166 -9
- package/src/api/EntityRegistry.ts +208 -0
- package/src/api/Event.ts +332 -0
- package/src/api/News.ts +331 -0
- package/src/api/{Organization.js → Organization.ts} +155 -119
- package/src/api/{Poi.js → Poi.ts} +68 -60
- package/src/api/{Project.js → Project.ts} +150 -127
- package/src/api/{User.js → User.ts} +321 -256
- package/src/api/UserApi.ts +148 -0
- package/src/api/serverDataType/Comment.ts +88 -0
- package/src/api/serverDataType/Event.ts +80 -0
- package/src/api/serverDataType/News.ts +138 -0
- package/src/api/serverDataType/Organization.ts +80 -0
- package/src/api/serverDataType/Project.ts +71 -0
- package/src/api/serverDataType/User.ts +103 -0
- package/src/api/serverDataType/common.ts +80 -0
- package/src/endpoints.module.ts +2621 -0
- package/src/error.ts +86 -0
- package/src/index.ts +86 -0
- package/src/mixin/UserMixin.ts +4 -0
- package/src/types/api-responses.ts +217 -0
- package/src/types/entities.ts +22 -0
- package/src/types/error-guards.ts +230 -0
- package/src/types/index.ts +39 -0
- package/src/types/payloads.ts +21 -0
- package/src/types/transforms.ts +110 -0
- package/src/utils/{FileOfflineStorageStrategy.node.js → FileOfflineStorageStrategy.node.ts} +15 -12
- package/src/utils/{FileStorageStrategy.node.js → FileStorageStrategy.node.ts} +17 -14
- package/src/utils/MultiServerFileStorageStrategy.node.ts +67 -0
- package/src/utils/MultiServerTokenStorageStrategy.ts +139 -0
- package/src/utils/{OfflineClientManager.js → OfflineClientManager.ts} +82 -86
- package/src/utils/OfflineQueueStorageStrategy.ts +47 -0
- package/src/utils/TokenStorage.ts +77 -0
- package/src/utils/compat.ts +12 -0
- package/src/utils/createDefaultMultiServerTokenStorageStrategy.ts +35 -0
- package/src/utils/{createDefaultOfflineStrategy.js → createDefaultOfflineStrategy.ts} +8 -3
- package/src/utils/createDefaultTokenStorageStrategy.ts +33 -0
- package/src/utils/{reactive.js → reactive.ts} +49 -40
- package/src/utils/stream-utils.node.ts +12 -0
- package/types/Api.d.ts +87 -0
- package/types/Api.d.ts.map +1 -0
- package/types/ApiClient.d.ts +437 -0
- package/types/ApiClient.d.ts.map +1 -0
- package/types/EJSONType.d.ts +53 -0
- package/types/EJSONType.d.ts.map +1 -0
- package/types/api/Badge.d.ts +24 -0
- package/types/api/Badge.d.ts.map +1 -0
- package/types/api/BaseEntity.d.ts +1322 -0
- package/types/api/BaseEntity.d.ts.map +1 -0
- package/types/api/Comment.d.ts +36 -0
- package/types/api/EndpointApi.d.ts +985 -0
- package/types/api/EndpointApi.d.ts.map +1 -0
- package/types/api/EndpointApi.types.d.ts +4233 -0
- package/types/api/EntityRegistry.d.ts +24 -0
- package/types/api/EntityRegistry.d.ts.map +1 -0
- package/types/api/Event.d.ts +122 -0
- package/types/api/Event.d.ts.map +1 -0
- package/types/api/News.d.ts +77 -0
- package/types/api/News.d.ts.map +1 -0
- package/types/api/Organization.d.ts +203 -0
- package/types/api/Organization.d.ts.map +1 -0
- package/types/api/Poi.d.ts +54 -0
- package/types/api/Poi.d.ts.map +1 -0
- package/types/api/Project.d.ts +180 -0
- package/types/api/Project.d.ts.map +1 -0
- package/types/api/User.d.ts +332 -0
- package/types/api/User.d.ts.map +1 -0
- package/types/api/UserApi.d.ts +64 -0
- package/types/api/UserApi.d.ts.map +1 -0
- package/types/api/serverDataType/Comment.d.ts +83 -0
- package/types/api/serverDataType/Event.d.ts +67 -0
- package/types/api/serverDataType/News.d.ts +130 -0
- package/types/api/serverDataType/Organization.d.ts +65 -0
- package/types/api/serverDataType/Organization.d.ts.map +1 -0
- package/types/api/serverDataType/Project.d.ts +58 -0
- package/types/api/serverDataType/Project.d.ts.map +1 -0
- package/types/api/serverDataType/User.d.ts +86 -0
- package/types/api/serverDataType/User.d.ts.map +1 -0
- package/types/api/serverDataType/common.d.ts +71 -0
- package/types/api/serverDataType/common.d.ts.map +1 -0
- package/types/endpoints.module.d.ts +20559 -0
- package/types/endpoints.module.d.ts.map +1 -0
- package/types/error.d.ts +54 -0
- package/types/error.d.ts.map +1 -0
- package/types/index.d.ts +59 -0
- package/types/index.d.ts.map +1 -0
- package/types/mixin/UserMixin.d.ts +1 -0
- package/types/mixin/UserMixin.d.ts.map +1 -0
- package/types/types/api-responses.d.ts +190 -0
- package/types/types/api-responses.d.ts.map +1 -0
- package/types/types/entities.d.ts +17 -0
- package/types/types/entities.d.ts.map +1 -0
- package/types/types/error-guards.d.ts +99 -0
- package/types/types/error-guards.d.ts.map +1 -0
- package/types/types/index.d.ts +7 -0
- package/types/types/payloads.d.ts +17 -0
- package/types/types/payloads.d.ts.map +1 -0
- package/types/types/transforms.d.ts +79 -0
- package/types/types/transforms.d.ts.map +1 -0
- package/types/utils/FileOfflineStorageStrategy.node.d.ts +11 -0
- package/types/utils/FileOfflineStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/FileStorageStrategy.node.d.ts +14 -0
- package/types/utils/FileStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/MultiServerFileStorageStrategy.node.d.ts +17 -0
- package/types/utils/MultiServerFileStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/MultiServerTokenStorageStrategy.d.ts +44 -0
- package/types/utils/MultiServerTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/OfflineClientManager.d.ts +58 -0
- package/types/utils/OfflineClientManager.d.ts.map +1 -0
- package/types/utils/OfflineQueueStorageStrategy.d.ts +16 -0
- package/types/utils/OfflineQueueStorageStrategy.d.ts.map +1 -0
- package/types/utils/TokenStorage.d.ts +26 -0
- package/types/utils/TokenStorage.d.ts.map +1 -0
- package/types/utils/compat.d.ts +4 -0
- package/types/utils/compat.d.ts.map +1 -0
- package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts +2 -0
- package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/createDefaultOfflineStrategy.d.ts +2 -0
- package/types/utils/createDefaultOfflineStrategy.d.ts.map +1 -0
- package/types/utils/createDefaultTokenStorageStrategy.d.ts +2 -0
- package/types/utils/createDefaultTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/reactive.d.ts +54 -0
- package/types/utils/reactive.d.ts.map +1 -0
- package/types/utils/stream-utils.node.d.ts +3 -0
- package/types/utils/stream-utils.node.d.ts.map +1 -0
- package/dist/123.cocolight-api-client.browser.js +0 -1
- package/dist/123.cocolight-api-client.cjs +0 -1
- package/dist/22.cocolight-api-client.mjs.js +0 -1
- package/dist/339.cocolight-api-client.mjs.js +0 -1
- package/dist/394.cocolight-api-client.browser.js +0 -1
- package/dist/394.cocolight-api-client.cjs +0 -1
- package/dist/405.cocolight-api-client.browser.js +0 -1
- package/dist/405.cocolight-api-client.cjs +0 -1
- package/dist/774.cocolight-api-client.mjs.js +0 -1
- package/dist/790.cocolight-api-client.mjs.js +0 -1
- package/dist/931.cocolight-api-client.browser.js +0 -1
- package/dist/931.cocolight-api-client.cjs +0 -1
- package/src/EJSONType.js +0 -53
- package/src/api/BaseEntity.js +0 -2828
- package/src/api/EntityRegistry.js +0 -152
- package/src/api/Event.js +0 -226
- package/src/api/News.js +0 -244
- package/src/api/UserApi.js +0 -81
- package/src/endpoints.module.js +0 -5
- package/src/error.js +0 -68
- package/src/index.js +0 -34
- package/src/mixin/UserMixin.js +0 -8
- package/src/utils/MultiServerFileStorageStrategy.node.js +0 -65
- package/src/utils/MultiServerTokenStorageStrategy.js +0 -131
- package/src/utils/OfflineQueueStorageStrategy.js +0 -51
- package/src/utils/TokenStorage.js +0 -93
- package/src/utils/createDefaultMultiServerTokenStorageStrategy.js +0 -45
- package/src/utils/createDefaultTokenStorageStrategy.js +0 -43
- package/src/utils/stream-utils.node.js +0 -10
|
@@ -1,31 +1,99 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
|
|
3
3
|
import { AggregateAjvError } from "@segment/ajv-human-errors";
|
|
4
|
-
import Ajv from "ajv";
|
|
5
|
-
import addFormats from "ajv-formats";
|
|
6
4
|
import axios from "axios";
|
|
7
|
-
import
|
|
8
|
-
import EJSON from "ejson";
|
|
5
|
+
import axiosRetryImport from "axios-retry";
|
|
9
6
|
import { jwtDecode } from "jwt-decode";
|
|
10
|
-
|
|
7
|
+
|
|
11
8
|
|
|
12
9
|
import MongoID from "./EJSONType.js";
|
|
13
10
|
import endpointsJson from "./endpoints.module.js";
|
|
14
11
|
import { ApiClientError, ApiResponseError, ApiValidationError, CircuitBreakerError } from "./error.js";
|
|
12
|
+
import { getErrorMessage, getErrorStatusCode, hasResponse } from "./types/error-guards.js";
|
|
13
|
+
import { Ajv, addFormats, pino, EJSON } from "./utils/compat.js";
|
|
15
14
|
import { MultiServerTokenStorageStrategy } from "./utils/MultiServerTokenStorageStrategy.js";
|
|
16
|
-
import { MemoryStorageStrategy } from "./utils/TokenStorage.js";
|
|
15
|
+
import { MemoryStorageStrategy, type TokenStorageStrategy } from "./utils/TokenStorage.js";
|
|
16
|
+
|
|
17
|
+
import type { ApiErrorDetails } from "./types/api-responses.js";
|
|
18
|
+
import type OfflineClientManager from "./utils/OfflineClientManager.js";
|
|
19
|
+
import type { ValidateFunction } from "ajv";
|
|
20
|
+
import type { AxiosInstance } from "axios";
|
|
21
|
+
import type { JwtPayload } from "jwt-decode";
|
|
22
|
+
|
|
23
|
+
// Fix pour le typage de axios-retry avec les modules CommonJS
|
|
24
|
+
interface AxiosRetryType {
|
|
25
|
+
(axiosInstance: AxiosInstance, config?: any): void;
|
|
26
|
+
exponentialDelay: (retryNumber?: number, error?: Error, delayFactor?: number) => number;
|
|
27
|
+
isNetworkError: (error: Error) => boolean;
|
|
28
|
+
isRetryableError: (error: Error) => boolean;
|
|
29
|
+
}
|
|
30
|
+
const axiosRetry = axiosRetryImport as unknown as AxiosRetryType;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Résultat retourné quand une requête est **mise en file** (offline ou breaker).
|
|
34
|
+
* Permet de typer précisément les chemins « non-réseau » de `callEndpoint`.
|
|
35
|
+
*/
|
|
36
|
+
export interface ApiClientOfflineEnqueueResult {
|
|
37
|
+
data: null;
|
|
38
|
+
offline?: true; // Présent si l'enqueue est dû au mode offline.
|
|
39
|
+
breaker?: true; // Présent si l'enqueue est dû au circuit breaker.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Options de configuration pour ApiClient
|
|
44
|
+
*/
|
|
45
|
+
export interface ApiClientOptions {
|
|
46
|
+
baseURL: string;
|
|
47
|
+
accessToken?: string | null;
|
|
48
|
+
refreshToken?: string | null;
|
|
49
|
+
refreshUrl?: string;
|
|
50
|
+
endpoints?: any[];
|
|
51
|
+
timeout?: number;
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
maxRetries?: number;
|
|
54
|
+
circuitBreakerThreshold?: number;
|
|
55
|
+
circuitBreakerResetTime?: number;
|
|
56
|
+
fromJSONValue?: boolean;
|
|
57
|
+
tokenStorageStrategy?: TokenStorageStrategy | null;
|
|
58
|
+
}
|
|
17
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Structure des données pour les appels d'endpoint
|
|
62
|
+
*/
|
|
63
|
+
export interface CallEndpointData {
|
|
64
|
+
pathParams?: Record<string, any>;
|
|
65
|
+
[key: string]: any;
|
|
66
|
+
}
|
|
18
67
|
|
|
19
|
-
|
|
20
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Résultat d'un appel d'endpoint réussi
|
|
70
|
+
*/
|
|
71
|
+
export interface CallEndpointResult<T = any> {
|
|
72
|
+
data: T;
|
|
73
|
+
offline?: never;
|
|
74
|
+
breaker?: never;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Union type pour tous les résultats possibles de callEndpoint
|
|
79
|
+
*/
|
|
80
|
+
export type CallEndpointResponse<T = any> = CallEndpointResult<T> | ApiClientOfflineEnqueueResult;
|
|
81
|
+
|
|
82
|
+
EJSON.addType("oid", (value: any) => {
|
|
83
|
+
if (typeof value === "string") {
|
|
84
|
+
return new MongoID.ObjectID(value);
|
|
85
|
+
}
|
|
86
|
+
if (typeof value === "undefined") {
|
|
87
|
+
return new MongoID.ObjectID();
|
|
88
|
+
}
|
|
89
|
+
throw new Error("Expected string value for ObjectID");
|
|
21
90
|
});
|
|
22
91
|
|
|
92
|
+
|
|
23
93
|
/**
|
|
24
94
|
* Client générique pour consommer une API REST avec validation AJV, gestion des tokens,
|
|
25
95
|
* circuit breaker, retry automatique, et support offline.
|
|
26
96
|
*
|
|
27
|
-
* @extends EventEmitter
|
|
28
|
-
*
|
|
29
97
|
* @fires ApiClient#retryAttempt
|
|
30
98
|
* @fires ApiClient#queuedOffline
|
|
31
99
|
* @fires ApiClient#circuitBreakerOpen
|
|
@@ -38,21 +106,36 @@ EJSON.addType("oid", value => {
|
|
|
38
106
|
* @fires ApiClient#userLoggedIn
|
|
39
107
|
*/
|
|
40
108
|
export default class ApiClient extends EventEmitter {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
109
|
+
// Public properties
|
|
110
|
+
public readonly __entityTag: string = "ApiClient";
|
|
111
|
+
public readonly userId: string | null = null;
|
|
112
|
+
|
|
113
|
+
// Private properties
|
|
114
|
+
private readonly _baseURL: string;
|
|
115
|
+
private readonly _refreshUrl: string;
|
|
116
|
+
private readonly _endpoints: any[];
|
|
117
|
+
private readonly _debug: boolean;
|
|
118
|
+
private readonly _fromJSONValue: boolean;
|
|
119
|
+
private readonly _tokenStorage: TokenStorageStrategy;
|
|
120
|
+
|
|
121
|
+
private _offlineClientManager: OfflineClientManager | null = null;
|
|
122
|
+
private _ajv: any;
|
|
123
|
+
public _logger: any;
|
|
124
|
+
private _client: AxiosInstance;
|
|
125
|
+
|
|
126
|
+
// Circuit breaker properties
|
|
127
|
+
private readonly _breakerThreshold: number;
|
|
128
|
+
private readonly _breakerResetTime: number;
|
|
129
|
+
private _breakerErrorCount: number = 0;
|
|
130
|
+
private _breakerOpen: boolean = false;
|
|
131
|
+
private _lastBreakerOpenTime: number | null = null;
|
|
132
|
+
|
|
133
|
+
// Token properties
|
|
134
|
+
private _accessToken: string | null = null;
|
|
135
|
+
private _refreshToken: string | null = null;
|
|
136
|
+
|
|
137
|
+
// Internal userId setter
|
|
138
|
+
private _setUserId: (id: string | null) => void;
|
|
56
139
|
constructor({
|
|
57
140
|
baseURL,
|
|
58
141
|
accessToken,
|
|
@@ -66,24 +149,23 @@ export default class ApiClient extends EventEmitter {
|
|
|
66
149
|
circuitBreakerResetTime = 60000,
|
|
67
150
|
fromJSONValue = true,
|
|
68
151
|
tokenStorageStrategy = null
|
|
69
|
-
}
|
|
152
|
+
}: ApiClientOptions) {
|
|
70
153
|
super(); // EventEmitter
|
|
71
154
|
|
|
72
155
|
if (!baseURL) {
|
|
73
156
|
throw new ApiClientError("Le paramètre \"baseURL\" est obligatoire.", 500);
|
|
74
157
|
}
|
|
75
158
|
|
|
76
|
-
this.__entityTag = "ApiClient";
|
|
77
|
-
|
|
78
159
|
this._baseURL = baseURL;
|
|
79
160
|
this._refreshUrl = refreshUrl;
|
|
80
|
-
this._endpoints = endpoints;
|
|
161
|
+
this._endpoints = endpoints || endpointsJson.endpoints;
|
|
81
162
|
this._debug = debug;
|
|
82
|
-
let _userId = null;
|
|
83
|
-
this._offlineClientManager = null;
|
|
84
|
-
|
|
85
|
-
// Active la transformation des données en EJSON globalement
|
|
86
163
|
this._fromJSONValue = fromJSONValue;
|
|
164
|
+
this._breakerThreshold = circuitBreakerThreshold;
|
|
165
|
+
this._breakerResetTime = circuitBreakerResetTime;
|
|
166
|
+
|
|
167
|
+
// Setup userId with getter/setter pattern
|
|
168
|
+
let _userId: string | null = null;
|
|
87
169
|
|
|
88
170
|
Object.defineProperty(this, "userId", {
|
|
89
171
|
get: () => _userId,
|
|
@@ -93,9 +175,9 @@ export default class ApiClient extends EventEmitter {
|
|
|
93
175
|
enumerable: true
|
|
94
176
|
});
|
|
95
177
|
|
|
96
|
-
this._setUserId = (id) => {
|
|
178
|
+
this._setUserId = (id: string | null) => {
|
|
97
179
|
_userId = id;
|
|
98
|
-
this._logger
|
|
180
|
+
this._logger?.debug(`[ApiClient] userId set: ${id}`);
|
|
99
181
|
};
|
|
100
182
|
|
|
101
183
|
// AJV
|
|
@@ -131,11 +213,11 @@ export default class ApiClient extends EventEmitter {
|
|
|
131
213
|
axiosRetry(this._client, {
|
|
132
214
|
retries: maxRetries,
|
|
133
215
|
retryDelay: axiosRetry.exponentialDelay,
|
|
134
|
-
retryCondition: (error) => {
|
|
216
|
+
retryCondition: (error: any) => {
|
|
135
217
|
// Retry sur erreurs 5xx ou erreurs réseau
|
|
136
218
|
return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error);
|
|
137
219
|
},
|
|
138
|
-
onRetry: (retryCount, error, requestConfig) => {
|
|
220
|
+
onRetry: (retryCount: number, error: any, requestConfig: any) => {
|
|
139
221
|
this._logger.warn(`[Retry] Tentative #${retryCount} pour ${requestConfig?.url}`);
|
|
140
222
|
this.emit("retryAttempt", { retryCount, url: requestConfig?.url });
|
|
141
223
|
}
|
|
@@ -143,20 +225,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
143
225
|
this._logger.info(`[ApiClient] Retry activé : ${maxRetries} max`);
|
|
144
226
|
}
|
|
145
227
|
|
|
146
|
-
// Circuit breaker (simplifié)
|
|
147
|
-
this._breakerThreshold = circuitBreakerThreshold;
|
|
148
|
-
this._breakerResetTime = circuitBreakerResetTime;
|
|
149
|
-
this._breakerErrorCount = 0;
|
|
150
|
-
this._breakerOpen = false;
|
|
151
|
-
this._lastBreakerOpenTime = null;
|
|
152
|
-
|
|
153
|
-
this._accessToken = null;
|
|
154
|
-
this._refreshToken = null;
|
|
155
|
-
|
|
156
228
|
if (tokenStorageStrategy instanceof MultiServerTokenStorageStrategy) {
|
|
157
229
|
tokenStorageStrategy.use(this._baseURL);
|
|
158
230
|
}
|
|
159
|
-
|
|
231
|
+
|
|
160
232
|
this._tokenStorage = tokenStorageStrategy || new MemoryStorageStrategy();
|
|
161
233
|
|
|
162
234
|
if (
|
|
@@ -170,7 +242,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
170
242
|
}
|
|
171
243
|
|
|
172
244
|
// Applique un token initial s'il est fourni
|
|
173
|
-
if(refreshToken){
|
|
245
|
+
if (refreshToken) {
|
|
174
246
|
this.setRefreshToken(refreshToken);
|
|
175
247
|
}
|
|
176
248
|
|
|
@@ -212,6 +284,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
212
284
|
this._logger.info("[ApiClient] Token rafraîchi avec succès.");
|
|
213
285
|
|
|
214
286
|
// 🔑 Mise à jour EXPLICITE du header Authorization dans la requête originale
|
|
287
|
+
originalRequest.headers = originalRequest.headers ?? {};
|
|
215
288
|
originalRequest.headers["Authorization"] = "Bearer " + this.getToken();
|
|
216
289
|
|
|
217
290
|
this._logger.info("[ApiClient] Retente la requête originale avec le nouveau token.");
|
|
@@ -222,7 +295,11 @@ export default class ApiClient extends EventEmitter {
|
|
|
222
295
|
}
|
|
223
296
|
} catch (err) {
|
|
224
297
|
this.resetSession();
|
|
225
|
-
|
|
298
|
+
const errorDetails: ApiErrorDetails = {
|
|
299
|
+
code: getErrorStatusCode(err) || 401,
|
|
300
|
+
message: getErrorMessage(err)
|
|
301
|
+
};
|
|
302
|
+
throw new ApiClientError("Erreur lors du rafraîchissement du token.", 401, errorDetails);
|
|
226
303
|
}
|
|
227
304
|
}
|
|
228
305
|
|
|
@@ -234,13 +311,15 @@ export default class ApiClient extends EventEmitter {
|
|
|
234
311
|
|
|
235
312
|
/**
|
|
236
313
|
* Sets the access token for the API client and updates the authorization header.
|
|
237
|
-
*
|
|
238
|
-
* @param {string} token - The access token to be set.
|
|
239
314
|
*/
|
|
240
|
-
setToken(token) {
|
|
315
|
+
setToken(token: string | null): void {
|
|
241
316
|
this._accessToken = token;
|
|
242
|
-
this._tokenStorage.setAccessToken(token);
|
|
243
|
-
|
|
317
|
+
this._tokenStorage.setAccessToken(token ?? "");
|
|
318
|
+
if (token) {
|
|
319
|
+
this._client.defaults.headers.common["Authorization"] = "Bearer " + token;
|
|
320
|
+
} else {
|
|
321
|
+
delete this._client.defaults.headers.common["Authorization"];
|
|
322
|
+
}
|
|
244
323
|
// Extrait l'id depuis le token et le stocke si disponible
|
|
245
324
|
const userId = this._getIdFromToken(token);
|
|
246
325
|
if (userId) {
|
|
@@ -252,21 +331,17 @@ export default class ApiClient extends EventEmitter {
|
|
|
252
331
|
|
|
253
332
|
/**
|
|
254
333
|
* Retrieves the current access token.
|
|
255
|
-
*
|
|
256
|
-
* @returns {string} The access token.
|
|
257
334
|
*/
|
|
258
|
-
getToken() {
|
|
335
|
+
getToken(): string | null {
|
|
259
336
|
return this._accessToken;
|
|
260
337
|
}
|
|
261
338
|
|
|
262
339
|
/**
|
|
263
340
|
* Sets the refresh token for the API client.
|
|
264
|
-
*
|
|
265
|
-
* @param {string} refreshToken - The refresh token to be set.
|
|
266
341
|
*/
|
|
267
|
-
setRefreshToken(token) {
|
|
342
|
+
setRefreshToken(token: string | null): void {
|
|
268
343
|
this._refreshToken = token;
|
|
269
|
-
this._tokenStorage.setRefreshToken(token);
|
|
344
|
+
this._tokenStorage.setRefreshToken(token ?? "");
|
|
270
345
|
if(this.userId === null){
|
|
271
346
|
// Extrait l'id depuis le token et le stocke si disponible
|
|
272
347
|
const userId = this._getIdFromToken(token);
|
|
@@ -280,34 +355,27 @@ export default class ApiClient extends EventEmitter {
|
|
|
280
355
|
|
|
281
356
|
/**
|
|
282
357
|
* Retrieves the current refresh token.
|
|
283
|
-
*
|
|
284
|
-
* @returns {string} The refresh token.
|
|
285
358
|
*/
|
|
286
|
-
getRefreshToken() {
|
|
359
|
+
getRefreshToken(): string | null {
|
|
287
360
|
return this._refreshToken;
|
|
288
361
|
}
|
|
289
362
|
|
|
290
363
|
/**
|
|
291
364
|
* Indique si le client est connecté.
|
|
292
365
|
* On considère que le client est connecté si un token d'accès (_accessToken) est défini.
|
|
293
|
-
*
|
|
294
|
-
* @returns {boolean} True si connecté, false sinon.
|
|
295
366
|
*/
|
|
296
|
-
get isConnected() {
|
|
367
|
+
get isConnected(): boolean {
|
|
297
368
|
return !!this._accessToken;
|
|
298
369
|
}
|
|
299
370
|
|
|
300
371
|
/**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
* @returns {string|null} L'identifiant extrait ou null si non trouvé.
|
|
305
|
-
*/
|
|
306
|
-
_getIdFromToken(token) {
|
|
372
|
+
* Extrait l'identifiant depuis un JWT.
|
|
373
|
+
*/
|
|
374
|
+
private _getIdFromToken(token: string | null): string | null {
|
|
307
375
|
if (!token) return null;
|
|
308
376
|
try {
|
|
309
377
|
// Décodage du token grâce à jwt-decode
|
|
310
|
-
const payload = jwtDecode(token);
|
|
378
|
+
const payload = jwtDecode<JwtPayload & { id?: string; userId?: string }>(token);
|
|
311
379
|
// L'identifiant peut être dans "id" ou "userId"
|
|
312
380
|
this._logger.debug("[ApiClient] Payload décodé :", payload);
|
|
313
381
|
return payload.id || payload.userId || null;
|
|
@@ -318,10 +386,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
318
386
|
}
|
|
319
387
|
|
|
320
388
|
/**
|
|
321
|
-
* Méthode simplifiée de refresh (en JSON).
|
|
389
|
+
* Méthode simplifiée de refresh (en JSON).
|
|
322
390
|
* Emet un event refreshSuccess si ça marche
|
|
323
391
|
*/
|
|
324
|
-
async _refreshAccessToken() {
|
|
392
|
+
private async _refreshAccessToken(): Promise<boolean> {
|
|
325
393
|
if (!this._refreshToken) return false;
|
|
326
394
|
|
|
327
395
|
const refreshClient = axios.create({
|
|
@@ -345,22 +413,21 @@ export default class ApiClient extends EventEmitter {
|
|
|
345
413
|
return false;
|
|
346
414
|
} catch (err) {
|
|
347
415
|
// Si on a une erreur, on reset la session
|
|
348
|
-
this.emit("refreshFailed", { error: err
|
|
416
|
+
this.emit("refreshFailed", { error: getErrorMessage(err) });
|
|
349
417
|
this.resetSession();
|
|
350
|
-
this._logger.error(`[ApiClient] Refresh Error : ${err
|
|
418
|
+
this._logger.error(`[ApiClient] Refresh Error : ${getErrorMessage(err)}`);
|
|
351
419
|
return false;
|
|
352
420
|
}
|
|
353
421
|
}
|
|
354
422
|
|
|
355
423
|
/**
|
|
356
424
|
* checkCircuitBreaker : vérifie si on peut appeler l'API ou non
|
|
357
|
-
* si le breaker est
|
|
425
|
+
* si le breaker est "open", on regarde si on peut "reset"
|
|
358
426
|
*/
|
|
359
|
-
_checkCircuitBreaker() {
|
|
427
|
+
private _checkCircuitBreaker(): boolean {
|
|
360
428
|
if (!this._breakerOpen) return true;
|
|
361
429
|
const now = Date.now();
|
|
362
|
-
if (now - this._lastBreakerOpenTime > this._breakerResetTime) {
|
|
363
|
-
// On reset
|
|
430
|
+
if (this._lastBreakerOpenTime != null && (now - this._lastBreakerOpenTime) > this._breakerResetTime) {
|
|
364
431
|
this._breakerOpen = false;
|
|
365
432
|
this._breakerErrorCount = 0;
|
|
366
433
|
this._logger.warn("[ApiClient] Circuit breaker réinitialisé");
|
|
@@ -371,10 +438,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
371
438
|
}
|
|
372
439
|
|
|
373
440
|
/**
|
|
374
|
-
* updateCircuitBreaker : incremente le compteur d'erreurs
|
|
375
|
-
* si on dépasse le threshold, on
|
|
441
|
+
* updateCircuitBreaker : incremente le compteur d'erreurs
|
|
442
|
+
* si on dépasse le threshold, on "open" le breaker.
|
|
376
443
|
*/
|
|
377
|
-
_updateCircuitBreakerError() {
|
|
444
|
+
private _updateCircuitBreakerError(): void {
|
|
378
445
|
this._breakerErrorCount += 1;
|
|
379
446
|
this._logger.warn(`[ApiClient] Erreur #${this._breakerErrorCount} sur ${this._breakerThreshold}`);
|
|
380
447
|
if (this._breakerErrorCount >= this._breakerThreshold) {
|
|
@@ -388,7 +455,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
388
455
|
/**
|
|
389
456
|
* resetCircuitBreaker : en cas de succès on reset le compteur
|
|
390
457
|
*/
|
|
391
|
-
_resetCircuitBreakerSuccess() {
|
|
458
|
+
private _resetCircuitBreakerSuccess(): void {
|
|
392
459
|
this._breakerErrorCount = 0;
|
|
393
460
|
if (this._breakerOpen) {
|
|
394
461
|
this._breakerOpen = false;
|
|
@@ -396,7 +463,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
396
463
|
}
|
|
397
464
|
}
|
|
398
465
|
|
|
399
|
-
static stripNullsInPlace(obj) {
|
|
466
|
+
static stripNullsInPlace(obj: any): any {
|
|
400
467
|
// Si l'objet semble être un "fichier uploadé", c'est-à-dire qu'il possède une propriété "value" qui est un stream, on le laisse intact.
|
|
401
468
|
if (obj && typeof obj === "object" && obj.value && typeof obj.value.pipe === "function") {
|
|
402
469
|
return obj;
|
|
@@ -424,13 +491,13 @@ export default class ApiClient extends EventEmitter {
|
|
|
424
491
|
return obj;
|
|
425
492
|
}
|
|
426
493
|
|
|
427
|
-
_resolveSpecialValuesInPlace(obj, pathParams = {}) {
|
|
428
|
-
const aliasMap = {
|
|
494
|
+
private _resolveSpecialValuesInPlace(obj: any, pathParams: any = {}) {
|
|
495
|
+
const aliasMap: any = {
|
|
429
496
|
userId: () => this.userId,
|
|
430
497
|
accessToken: () => this._accessToken,
|
|
431
498
|
refreshToken: () => this._refreshToken,
|
|
432
499
|
baseURL: () => this._baseURL,
|
|
433
|
-
pathParams: (subPath) => {
|
|
500
|
+
pathParams: (subPath: any) => {
|
|
434
501
|
// Si la valeur existe dans pathParams, on la retourne
|
|
435
502
|
const value = this._getValueByPath(pathParams, subPath);
|
|
436
503
|
return value !== undefined && value !== null ? value : undefined;
|
|
@@ -440,7 +507,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
440
507
|
// Expression régulière qui capture les alias sous forme délimitée @{...} ou simple @...
|
|
441
508
|
const regex = /@(?:\{([^}]+)\}|([\w.]+))/g;
|
|
442
509
|
|
|
443
|
-
const resolveString = (str) => {
|
|
510
|
+
const resolveString = (str: any) => {
|
|
444
511
|
if (typeof str !== "string") return str;
|
|
445
512
|
return str.replace(regex, (_, delimitedAlias, plainAlias) => {
|
|
446
513
|
// Si alias délimité, on traite toute la chaîne à l'intérieur des accolades.
|
|
@@ -489,17 +556,17 @@ export default class ApiClient extends EventEmitter {
|
|
|
489
556
|
|
|
490
557
|
|
|
491
558
|
// Vérifie si l'objet est un binaire (par exemple un flux ou un Buffer)
|
|
492
|
-
const isBinary = (input) => {
|
|
559
|
+
const isBinary = (input: any) => {
|
|
493
560
|
return input && typeof input === "object" &&
|
|
494
561
|
(typeof input.pipe === "function" || (typeof Buffer !== "undefined" && input instanceof Buffer));
|
|
495
562
|
};
|
|
496
|
-
|
|
563
|
+
|
|
497
564
|
// Vérifie si l'objet est un "plain object"
|
|
498
|
-
const isPlainObject = (input) => {
|
|
565
|
+
const isPlainObject = (input: any) => {
|
|
499
566
|
return Object.prototype.toString.call(input) === "[object Object]";
|
|
500
567
|
};
|
|
501
|
-
|
|
502
|
-
const internal = (input) => {
|
|
568
|
+
|
|
569
|
+
const internal = (input: any): any => {
|
|
503
570
|
// Si c'est un binaire, ne rien modifier
|
|
504
571
|
if (isBinary(input)) {
|
|
505
572
|
return input;
|
|
@@ -510,7 +577,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
510
577
|
}
|
|
511
578
|
// Si c'est un plain object, on crée un nouvel objet avec remplacement dans les clés et valeurs
|
|
512
579
|
if (isPlainObject(input)) {
|
|
513
|
-
const result = {};
|
|
580
|
+
const result: any = {};
|
|
514
581
|
for (const [key, value] of Object.entries(input)) {
|
|
515
582
|
const resolvedKey = resolveString(key);
|
|
516
583
|
result[resolvedKey] = internal(value);
|
|
@@ -529,22 +596,22 @@ export default class ApiClient extends EventEmitter {
|
|
|
529
596
|
}
|
|
530
597
|
|
|
531
598
|
|
|
532
|
-
_cleanSchemaLeftoverAlias(obj) {
|
|
599
|
+
private _cleanSchemaLeftoverAlias(obj: any): any {
|
|
533
600
|
// Expression régulière qui détecte n'importe quel alias du type "@quelqueChose"
|
|
534
601
|
const aliasRegex = /@\w+/;
|
|
535
602
|
|
|
536
603
|
if (Array.isArray(obj)) {
|
|
537
|
-
return obj.map(item => this._cleanSchemaLeftoverAlias(item));
|
|
604
|
+
return obj.map((item: any) => this._cleanSchemaLeftoverAlias(item));
|
|
538
605
|
} else if (obj && typeof obj === "object") {
|
|
539
|
-
const newObj = {};
|
|
606
|
+
const newObj: any = {};
|
|
540
607
|
for (const key of Object.keys(obj)) {
|
|
541
608
|
const val = obj[key];
|
|
542
|
-
|
|
609
|
+
|
|
543
610
|
// Si le nom de la clé contient un alias (exemple : "@userId", "@anything"), on ne la copie pas
|
|
544
611
|
if (aliasRegex.test(key)) {
|
|
545
612
|
continue;
|
|
546
613
|
}
|
|
547
|
-
|
|
614
|
+
|
|
548
615
|
// Si la propriété s'appelle "default"
|
|
549
616
|
if (key === "default") {
|
|
550
617
|
// Si la valeur est une chaîne et qu'elle contient un alias, on ne la copie pas
|
|
@@ -561,12 +628,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
561
628
|
continue;
|
|
562
629
|
}
|
|
563
630
|
}
|
|
564
|
-
|
|
631
|
+
|
|
565
632
|
// Si la valeur est une chaîne contenant un alias, on ne la copie pas
|
|
566
633
|
if (typeof val === "string" && aliasRegex.test(val)) {
|
|
567
634
|
continue;
|
|
568
635
|
}
|
|
569
|
-
|
|
636
|
+
|
|
570
637
|
// Sinon, on nettoie récursivement la valeur
|
|
571
638
|
newObj[key] = this._cleanSchemaLeftoverAlias(val);
|
|
572
639
|
}
|
|
@@ -579,32 +646,39 @@ export default class ApiClient extends EventEmitter {
|
|
|
579
646
|
* Safely calls an asynchronous function and handles any errors that occur.
|
|
580
647
|
* Logs the error message using the instance's logger before re-throwing the error.
|
|
581
648
|
*
|
|
582
|
-
* @param
|
|
583
|
-
* @param
|
|
584
|
-
* @returns
|
|
649
|
+
* @param fn - The asynchronous function to be called.
|
|
650
|
+
* @param args - The arguments to pass to the function.
|
|
651
|
+
* @returns The result of the asynchronous function.
|
|
585
652
|
* @throws {Error} Re-throws any error that occurs during the function execution.
|
|
586
653
|
*/
|
|
587
|
-
async safeCall(fn
|
|
654
|
+
async safeCall(fn: (...args: any[]) => Promise<any>, ...args: any[]) {
|
|
588
655
|
try {
|
|
589
656
|
return await fn(...args);
|
|
590
657
|
} catch (error) {
|
|
591
|
-
this._logger.error(`[ApiClient.safeCall] Erreur: ${error
|
|
658
|
+
this._logger.error(`[ApiClient.safeCall] Erreur: ${getErrorMessage(error)}`);
|
|
592
659
|
throw error;
|
|
593
660
|
}
|
|
594
661
|
}
|
|
595
662
|
|
|
596
663
|
/**
|
|
597
|
-
*
|
|
664
|
+
* Appelle un endpoint avec validations AJV, auth et circuit breaker.
|
|
665
|
+
* En cas d’indisponibilité (offline ou breaker), **met en file** l’action via
|
|
666
|
+
* `_offlineClientManager` si présent.
|
|
598
667
|
*
|
|
599
|
-
* @param
|
|
600
|
-
* @param
|
|
601
|
-
* @param
|
|
602
|
-
* @param
|
|
603
|
-
* @returns
|
|
668
|
+
* @param constant - The constant representing the endpoint to call.
|
|
669
|
+
* @param data - Données envoyées (incluant éventuellement `pathParams`).
|
|
670
|
+
* @param transformResponseData - `true` (transforme via `_transformData`), `false`, ou une fonction `(data) => any`.
|
|
671
|
+
* @param validateResponseSchema - Whether to validate the response schema.
|
|
672
|
+
* @returns The response from the endpoint call, or an enqueue result if offline or breaker is active.
|
|
604
673
|
* @throws {CircuitBreakerError} If the circuit breaker is activated.
|
|
605
674
|
* @throws {ApiClientError} If the endpoint is not found, token is required but not provided, or validation fails.
|
|
606
675
|
*/
|
|
607
|
-
async callEndpoint
|
|
676
|
+
async callEndpoint<T = any>(
|
|
677
|
+
constant: string,
|
|
678
|
+
data: CallEndpointData = {},
|
|
679
|
+
transformResponseData: boolean | ((data: any) => any) = true,
|
|
680
|
+
validateResponseSchema: boolean = true
|
|
681
|
+
): Promise<CallEndpointResponse<T>> {
|
|
608
682
|
|
|
609
683
|
const endpoint = this._endpoints.find((ep) => ep.constant === constant);
|
|
610
684
|
if (!endpoint) {
|
|
@@ -623,8 +697,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
623
697
|
|
|
624
698
|
const lowerMethod = (method || "GET").toLowerCase();
|
|
625
699
|
const realContentType = contentType || "application/json";
|
|
626
|
-
const headers = { "Content-Type": realContentType };
|
|
627
|
-
|
|
700
|
+
const headers: Record<string, string> = { "Content-Type": realContentType };
|
|
701
|
+
|
|
628
702
|
// Auth headers
|
|
629
703
|
if (this._accessToken) {
|
|
630
704
|
if (auth === "bearer") {
|
|
@@ -650,19 +724,19 @@ export default class ApiClient extends EventEmitter {
|
|
|
650
724
|
}
|
|
651
725
|
|
|
652
726
|
const pathParams = data.pathParams || {};
|
|
653
|
-
const validatePathParams = this._ajv.compile(schemaToCompile);
|
|
727
|
+
const validatePathParams: ValidateFunction = this._ajv.compile(schemaToCompile);
|
|
654
728
|
const valid = validatePathParams(pathParams);
|
|
655
729
|
|
|
656
730
|
if (!valid) {
|
|
657
|
-
const errorMessages = this._ajvErrorHuman(validatePathParams.errors);
|
|
731
|
+
const errorMessages = this._ajvErrorHuman(validatePathParams.errors ?? []);
|
|
658
732
|
this.emit("validationError", { stage: "pathParams", errors: validatePathParams.errors });
|
|
659
733
|
throw new ApiValidationError(`callEndpoint: ${constant} - Path parameter validation failed.`, 400, errorMessages, validatePathParams.errors);
|
|
660
734
|
}
|
|
661
735
|
|
|
662
736
|
resolvedParams = this._resolveSpecialValuesInPlace(pathParams);
|
|
663
|
-
|
|
664
|
-
resolvedPath = resolvedPath.replace(/\{(\w+)\}/g, (_, key) => {
|
|
665
|
-
const val = resolvedParams[key];
|
|
737
|
+
|
|
738
|
+
resolvedPath = resolvedPath.replace(/\{(\w+)\}/g, (_: any, key: string) => {
|
|
739
|
+
const val = (resolvedParams as any)[key];
|
|
666
740
|
if (val !== undefined) return encodeURIComponent(val);
|
|
667
741
|
throw new ApiClientError(`Path param manquant ou non résolu : {${key}}`, 400);
|
|
668
742
|
});
|
|
@@ -676,11 +750,19 @@ export default class ApiClient extends EventEmitter {
|
|
|
676
750
|
schemaToCompile = this._cleanSchemaLeftoverAlias(requestSchema);
|
|
677
751
|
}
|
|
678
752
|
|
|
679
|
-
|
|
753
|
+
let dataForValidation = { ...data };
|
|
680
754
|
delete dataForValidation.pathParams;
|
|
755
|
+
|
|
756
|
+
// Normalize Dates to ISO strings for x-www-form-urlencoded BEFORE validation
|
|
757
|
+
if (realContentType === "application/x-www-form-urlencoded") {
|
|
758
|
+
dataForValidation = ApiClient.normalizeDatesForValidation(dataForValidation);
|
|
759
|
+
// For fields with format:"date", extract only YYYY-MM-DD
|
|
760
|
+
dataForValidation = ApiClient.normalizeSchemaDateFields(dataForValidation, schemaToCompile);
|
|
761
|
+
}
|
|
762
|
+
|
|
681
763
|
const cleanedData = ApiClient.stripNullsInPlace(dataForValidation);
|
|
682
|
-
|
|
683
|
-
const validateRequest = this._ajv.compile(schemaToCompile);
|
|
764
|
+
|
|
765
|
+
const validateRequest: ValidateFunction = this._ajv.compile(schemaToCompile);
|
|
684
766
|
const valid = validateRequest(cleanedData);
|
|
685
767
|
if (!valid) {
|
|
686
768
|
const errorMessages = validateRequest.errors ? this._ajvErrorHuman(validateRequest.errors) : [];
|
|
@@ -709,7 +791,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
709
791
|
data
|
|
710
792
|
});
|
|
711
793
|
|
|
712
|
-
|
|
794
|
+
const enqueued = { data: null, offline: true as const };
|
|
795
|
+
return enqueued;
|
|
713
796
|
} else {
|
|
714
797
|
this._logger.warn("[ApiClient] Mode dégradé actif mais offlineManager non initialisé correctement");
|
|
715
798
|
throw new ApiClientError("Mode hors-ligne actif, mais gestionnaire offline non disponible.", 503);
|
|
@@ -737,7 +820,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
737
820
|
data
|
|
738
821
|
});
|
|
739
822
|
|
|
740
|
-
|
|
823
|
+
const enqueued = { data: null, breaker: true as const };
|
|
824
|
+
return enqueued;
|
|
741
825
|
}
|
|
742
826
|
|
|
743
827
|
throw new CircuitBreakerError("Le circuit breaker est activé, impossible d'appeler l'API");
|
|
@@ -762,15 +846,15 @@ export default class ApiClient extends EventEmitter {
|
|
|
762
846
|
});
|
|
763
847
|
|
|
764
848
|
if (validateResponseSchema) {
|
|
765
|
-
const
|
|
766
|
-
const schema = responses?.[
|
|
849
|
+
const statusStr = String(response.status);
|
|
850
|
+
const schema = responses?.[statusStr];
|
|
767
851
|
if (schema) {
|
|
768
|
-
const validateResponse = this._ajv.compile(schema);
|
|
852
|
+
const validateResponse: ValidateFunction = this._ajv.compile(schema);
|
|
769
853
|
const valid = validateResponse(response.data);
|
|
770
854
|
if (!valid) {
|
|
771
|
-
const errorMessages = this._ajvErrorHuman(validateResponse.errors);
|
|
855
|
+
const errorMessages = this._ajvErrorHuman(validateResponse.errors ?? []);
|
|
772
856
|
this.emit("validationError", { stage: "response", errors: validateResponse.errors });
|
|
773
|
-
throw new ApiValidationError("Response validation failed.", status, errorMessages, validateResponse.errors);
|
|
857
|
+
throw new ApiValidationError("Response validation failed.", response.status, errorMessages, validateResponse.errors);
|
|
774
858
|
}
|
|
775
859
|
}
|
|
776
860
|
}
|
|
@@ -785,7 +869,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
785
869
|
|
|
786
870
|
// postActions éventuelles
|
|
787
871
|
if (Array.isArray(endpoint.postActions)) {
|
|
788
|
-
endpoint.postActions.forEach(action => {
|
|
872
|
+
endpoint.postActions.forEach((action: any) => {
|
|
789
873
|
const value = action.path ? this._getValueByPath(response.data, action.path) : null;
|
|
790
874
|
|
|
791
875
|
switch (action.type) {
|
|
@@ -813,8 +897,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
813
897
|
break;
|
|
814
898
|
|
|
815
899
|
case "callMethod":
|
|
816
|
-
if (typeof this[action.method] === "function") {
|
|
817
|
-
this[action.method]();
|
|
900
|
+
if (typeof (this as any)[action.method] === "function") {
|
|
901
|
+
(this as any)[action.method]();
|
|
818
902
|
this._logger.debug(`[ApiClient] Méthode appelée : ${action.method}`);
|
|
819
903
|
} else {
|
|
820
904
|
this._logger.warn(`[ApiClient] Méthode inconnue : ${action.method}`);
|
|
@@ -831,14 +915,16 @@ export default class ApiClient extends EventEmitter {
|
|
|
831
915
|
return response;
|
|
832
916
|
} catch (error) {
|
|
833
917
|
this._updateCircuitBreakerError();
|
|
834
|
-
this._logger.error(`[ApiClient] Erreur lors de l'appel de ${constant}: ${error
|
|
918
|
+
this._logger.error(`[ApiClient] Erreur lors de l'appel de ${constant}: ${getErrorMessage(error)}`);
|
|
835
919
|
if(error instanceof ApiValidationError) {
|
|
836
920
|
throw error;
|
|
837
921
|
} else {
|
|
922
|
+
const statusCode = hasResponse(error) ? error.response.status : 500;
|
|
923
|
+
const responseData = hasResponse(error) ? error.response.data : null;
|
|
838
924
|
throw new ApiClientError(
|
|
839
|
-
`Erreur lors de l'appel de l'API : ${error
|
|
840
|
-
|
|
841
|
-
|
|
925
|
+
`Erreur lors de l'appel de l'API : ${getErrorMessage(error)}`,
|
|
926
|
+
statusCode,
|
|
927
|
+
responseData
|
|
842
928
|
);
|
|
843
929
|
}
|
|
844
930
|
|
|
@@ -848,17 +934,18 @@ export default class ApiClient extends EventEmitter {
|
|
|
848
934
|
/**
|
|
849
935
|
* Converts AJV (Another JSON Schema Validator) errors into human-readable messages.
|
|
850
936
|
*
|
|
851
|
-
* @param
|
|
852
|
-
* @returns
|
|
937
|
+
* @param errors - An array of AJV validation error objects.
|
|
938
|
+
* @returns An array of human-readable error messages extracted from the AJV errors.
|
|
853
939
|
*/
|
|
854
|
-
_ajvErrorHuman(errors){
|
|
940
|
+
private _ajvErrorHuman(errors: any[]){
|
|
855
941
|
try {
|
|
856
942
|
const errorsMessages = new AggregateAjvError(errors);
|
|
857
|
-
|
|
943
|
+
// @ts-expect-error - champ non typé public dans les defs
|
|
944
|
+
const messages = errorsMessages.errors.map(({ message }: any) => message);
|
|
858
945
|
return messages;
|
|
859
946
|
} catch (e) {
|
|
860
947
|
this._logger.error("[ApiClient] _ajvErrorHuman", e);
|
|
861
|
-
return errors.map(({ message }) => message);
|
|
948
|
+
return errors.map(({ message }: any) => message);
|
|
862
949
|
}
|
|
863
950
|
}
|
|
864
951
|
|
|
@@ -874,13 +961,15 @@ export default class ApiClient extends EventEmitter {
|
|
|
874
961
|
*
|
|
875
962
|
* If no errors are found, the original `response` is returned.
|
|
876
963
|
*
|
|
877
|
-
* @param {
|
|
878
|
-
* @
|
|
879
|
-
* @param {number} response.status - The HTTP status code of the response.
|
|
964
|
+
* @param {import("axios").AxiosResponse | { data: any } } response
|
|
965
|
+
* @returns {import("axios").AxiosResponse | { data: any }}
|
|
880
966
|
* @throws {ApiResponseError} If an error is detected in the response data.
|
|
881
|
-
* @returns {Object} The original `response` object if no errors are found.
|
|
882
967
|
*/
|
|
883
|
-
checkAndThrowApiResponseError(response) {
|
|
968
|
+
checkAndThrowApiResponseError(response: any) {
|
|
969
|
+
if (!("status" in response)) {
|
|
970
|
+
return response;
|
|
971
|
+
}
|
|
972
|
+
|
|
884
973
|
const data = response.data;
|
|
885
974
|
if (!data || typeof data !== "object") {
|
|
886
975
|
return response;
|
|
@@ -916,7 +1005,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
916
1005
|
* - userId
|
|
917
1006
|
* - En-têtes Axios
|
|
918
1007
|
*/
|
|
919
|
-
resetSession() {
|
|
1008
|
+
resetSession(): void {
|
|
920
1009
|
this.setToken(null);
|
|
921
1010
|
this.setRefreshToken(null);
|
|
922
1011
|
this._setUserId(null);
|
|
@@ -931,12 +1020,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
931
1020
|
|
|
932
1021
|
/**
|
|
933
1022
|
* Retrieves the value from an object based on a dot-separated path.
|
|
934
|
-
*
|
|
935
|
-
* @param {Object} obj - The object from which to retrieve the value.
|
|
936
|
-
* @param {string} path - The dot-separated path string indicating the value to retrieve.
|
|
937
|
-
* @returns {*} - The value found at the specified path, or undefined if the path is invalid.
|
|
938
1023
|
*/
|
|
939
|
-
_getValueByPath(obj, path) {
|
|
1024
|
+
private _getValueByPath(obj: any, path: string): any {
|
|
940
1025
|
if (!path) return undefined;
|
|
941
1026
|
const keys = path.split(".");
|
|
942
1027
|
return keys.reduce((acc, key) => acc && acc[key], obj);
|
|
@@ -944,54 +1029,139 @@ export default class ApiClient extends EventEmitter {
|
|
|
944
1029
|
|
|
945
1030
|
// Conversions
|
|
946
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Normalizes Date objects to ISO strings for AJV validation.
|
|
1034
|
+
* This preserves the object structure while converting only Dates.
|
|
1035
|
+
* Handles reactive Proxies by unwrapping them first.
|
|
1036
|
+
*
|
|
1037
|
+
* @param obj - The object to normalize
|
|
1038
|
+
* @returns The normalized object with Dates as ISO strings
|
|
1039
|
+
*/
|
|
1040
|
+
static normalizeDatesForValidation(obj: any): any {
|
|
1041
|
+
if (obj === null || obj === undefined) return obj;
|
|
1042
|
+
|
|
1043
|
+
// Unwrap reactive Proxies BEFORE checking for Date
|
|
1044
|
+
if (obj && typeof obj === "object" && obj.__isReactive && obj.__raw) {
|
|
1045
|
+
return ApiClient.normalizeDatesForValidation(obj.__raw);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (obj instanceof Date) {
|
|
1049
|
+
return obj.toISOString();
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (Array.isArray(obj)) {
|
|
1053
|
+
return obj.map((item: any) => ApiClient.normalizeDatesForValidation(item));
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
if (typeof obj === "object") {
|
|
1057
|
+
const normalized: any = {};
|
|
1058
|
+
for (const key of Object.keys(obj)) {
|
|
1059
|
+
normalized[key] = ApiClient.normalizeDatesForValidation(obj[key]);
|
|
1060
|
+
}
|
|
1061
|
+
return normalized;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return obj;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Normalizes fields with format:"date" in schema to YYYY-MM-DD format.
|
|
1069
|
+
* Extracts only the date part from ISO datetime strings.
|
|
1070
|
+
*
|
|
1071
|
+
* @param data - The data object to normalize
|
|
1072
|
+
* @param schema - The JSON schema containing field definitions
|
|
1073
|
+
* @returns The normalized data with date-only strings for format:"date" fields
|
|
1074
|
+
*/
|
|
1075
|
+
static normalizeSchemaDateFields(data: any, schema: any) {
|
|
1076
|
+
if (!data || !schema || typeof data !== "object") return data;
|
|
1077
|
+
|
|
1078
|
+
const normalized = { ...data };
|
|
1079
|
+
|
|
1080
|
+
// Récupérer les propriétés du schéma (gérer allOf, then, properties, etc.)
|
|
1081
|
+
let properties = schema.properties || {};
|
|
1082
|
+
|
|
1083
|
+
// Si le schéma utilise allOf, parcourir tous les éléments
|
|
1084
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
1085
|
+
for (const allOfItem of schema.allOf) {
|
|
1086
|
+
// Chercher dans properties directes
|
|
1087
|
+
if (allOfItem.properties) {
|
|
1088
|
+
properties = { ...properties, ...allOfItem.properties };
|
|
1089
|
+
}
|
|
1090
|
+
// Chercher dans if/then/properties
|
|
1091
|
+
if (allOfItem.then && allOfItem.then.properties) {
|
|
1092
|
+
properties = { ...properties, ...allOfItem.then.properties };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Aussi vérifier schema.then.properties si présent
|
|
1098
|
+
if (schema.then && schema.then.properties) {
|
|
1099
|
+
properties = { ...properties, ...schema.then.properties };
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Pour chaque propriété ayant format:"date"
|
|
1103
|
+
for (const [key, propDef] of Object.entries(properties)) {
|
|
1104
|
+
const typedPropDef = propDef as any;
|
|
1105
|
+
if (typedPropDef?.format === "date" && normalized[key]) {
|
|
1106
|
+
const value = normalized[key];
|
|
1107
|
+
|
|
1108
|
+
// Si c'est une string ISO datetime, extraire YYYY-MM-DD
|
|
1109
|
+
if (typeof value === "string" && value.includes("T")) {
|
|
1110
|
+
normalized[key] = value.split("T")[0];
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
return normalized;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
947
1118
|
/**
|
|
948
1119
|
* Converts an object to URL search parameters.
|
|
949
1120
|
*
|
|
950
|
-
* @param
|
|
951
|
-
* @param
|
|
952
|
-
* @returns
|
|
1121
|
+
* @param obj - The object to be converted to URL search parameters.
|
|
1122
|
+
* @param [options={}] - Optional settings for the conversion.
|
|
1123
|
+
* @returns The URL search parameters generated from the object.
|
|
953
1124
|
*/
|
|
954
|
-
static toURLSearchParams(obj, options = {}) {
|
|
955
|
-
return this._buildParams(obj, new URLSearchParams(), options);
|
|
1125
|
+
static toURLSearchParams(obj: any, options: any = {}) {
|
|
1126
|
+
return this._buildParams(obj, new URLSearchParams(), options) as URLSearchParams;
|
|
956
1127
|
}
|
|
957
1128
|
|
|
958
1129
|
/**
|
|
959
1130
|
* Builds parameters for an API request from a given object.
|
|
960
|
-
*
|
|
961
|
-
* @param {Object} obj - The object to be converted into parameters.
|
|
962
|
-
* @param {FormData|URLSearchParams} paramsInstance - The instance to which the parameters will be appended.
|
|
963
|
-
* @param {Object} [options={}] - Optional settings.
|
|
964
|
-
* @param {boolean} [options.dots=false] - Whether to use dots in the parameter keys.
|
|
965
|
-
* @param {boolean} [options.indexes=false] - Whether to include array indexes in the parameter keys.
|
|
966
|
-
* @param {boolean} [options.metaTokens=true] - Whether to include meta tokens in the parameter keys.
|
|
967
|
-
* @throws {TypeError} If the provided obj is not an object or is null.
|
|
968
|
-
* @returns {FormData|URLSearchParams} The instance with the appended parameters.
|
|
969
1131
|
*/
|
|
970
|
-
static _buildParams(
|
|
1132
|
+
static _buildParams(
|
|
1133
|
+
obj: any,
|
|
1134
|
+
paramsInstance: FormData | URLSearchParams,
|
|
1135
|
+
options: {
|
|
1136
|
+
dots?: boolean;
|
|
1137
|
+
indexes?: boolean;
|
|
1138
|
+
metaTokens?: boolean;
|
|
1139
|
+
} = {}
|
|
1140
|
+
): FormData | URLSearchParams {
|
|
971
1141
|
if (typeof obj !== "object" || obj === null) {
|
|
972
1142
|
throw new TypeError("La donnée doit être un objet non nul.");
|
|
973
1143
|
}
|
|
974
1144
|
|
|
975
1145
|
const { dots = false, indexes = false, metaTokens = true } = options;
|
|
976
|
-
const stack = [];
|
|
1146
|
+
const stack: any[] = [];
|
|
977
1147
|
|
|
978
|
-
function isVisitable(thing) {
|
|
1148
|
+
function isVisitable(thing: any) {
|
|
979
1149
|
return Object.prototype.toString.call(thing) === "[object Object]" || Array.isArray(thing);
|
|
980
1150
|
}
|
|
981
1151
|
|
|
982
|
-
function removeBrackets(key) {
|
|
1152
|
+
function removeBrackets(key: any) {
|
|
983
1153
|
return key.endsWith("[]") ? key.slice(0, -2) : key;
|
|
984
1154
|
}
|
|
985
1155
|
|
|
986
|
-
function renderKey(path, key, useDots = false) {
|
|
1156
|
+
function renderKey(path: any, key: any, useDots = false) {
|
|
987
1157
|
if (!path) return key;
|
|
988
|
-
return path.concat(key).map((token, i) => {
|
|
1158
|
+
return path.concat(key).map((token: any, i: any) => {
|
|
989
1159
|
token = removeBrackets(token);
|
|
990
1160
|
return !useDots && i ? `[${token}]` : token;
|
|
991
1161
|
}).join(useDots ? "." : "");
|
|
992
1162
|
}
|
|
993
1163
|
|
|
994
|
-
function convertValue(value) {
|
|
1164
|
+
function convertValue(value: any) {
|
|
995
1165
|
if (value === null || value === undefined) return "";
|
|
996
1166
|
if (value instanceof Date) return value.toISOString();
|
|
997
1167
|
if (
|
|
@@ -1005,7 +1175,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1005
1175
|
return value;
|
|
1006
1176
|
}
|
|
1007
1177
|
|
|
1008
|
-
function defaultVisitor(value, key, path) {
|
|
1178
|
+
function defaultVisitor(value: any, key: any, path: any) {
|
|
1009
1179
|
if (value && typeof value === "object") {
|
|
1010
1180
|
if (key.endsWith("{}")) {
|
|
1011
1181
|
key = metaTokens ? key : key.slice(0, -2);
|
|
@@ -1015,7 +1185,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1015
1185
|
|
|
1016
1186
|
if ((Array.isArray(value) && !value.some(isVisitable)) || key.endsWith("[]")) {
|
|
1017
1187
|
key = removeBrackets(key);
|
|
1018
|
-
value.forEach((el, index) => {
|
|
1188
|
+
value.forEach((el: any, index: any) => {
|
|
1019
1189
|
if (el !== undefined && el !== null) {
|
|
1020
1190
|
// const fieldKey = indexes ? renderKey([key], index, dots) : `${key}[]`;
|
|
1021
1191
|
const fieldKey = indexes
|
|
@@ -1033,9 +1203,9 @@ export default class ApiClient extends EventEmitter {
|
|
|
1033
1203
|
return false;
|
|
1034
1204
|
}
|
|
1035
1205
|
|
|
1036
|
-
function build(value, path = []) {
|
|
1037
|
-
if (value === undefined || value === null || stack.includes(value)) return;
|
|
1038
|
-
stack.push(value);
|
|
1206
|
+
function build(value: any, path: any = []) {
|
|
1207
|
+
if (value === undefined || value === null || (stack as any).includes(value)) return;
|
|
1208
|
+
(stack as any).push(value);
|
|
1039
1209
|
|
|
1040
1210
|
Object.entries(value).forEach(([k, el]) => {
|
|
1041
1211
|
if (el !== undefined && defaultVisitor(el, k.trim(), path)) {
|
|
@@ -1059,12 +1229,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
1059
1229
|
* nested structures like `resultGoods`, `resultErrors`, `results`, `news`,
|
|
1060
1230
|
* `notif`, `citoyens`, `organizations`, `cities`, `newComment`, `map`, and `object`.
|
|
1061
1231
|
*
|
|
1062
|
-
* @param
|
|
1063
|
-
* @returns
|
|
1232
|
+
* @param data - The data to be transformed.
|
|
1233
|
+
* @returns - The transformed data.
|
|
1064
1234
|
*
|
|
1065
1235
|
* @private
|
|
1066
1236
|
*/
|
|
1067
|
-
_transformData(data) {
|
|
1237
|
+
private _transformData(data: any): any {
|
|
1068
1238
|
if (data && typeof data === "object") {
|
|
1069
1239
|
if (data.resultGoods?.msg) {
|
|
1070
1240
|
data = this._normalizeJsonData({ msg: data.resultGoods.msg, ...data });
|
|
@@ -1075,7 +1245,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1075
1245
|
return this._normalizeJsonData({ id: key, ...data.results[key] });
|
|
1076
1246
|
});
|
|
1077
1247
|
} else if (Array.isArray(data.results) && data.results.length > 0) {
|
|
1078
|
-
data.results = data.results.map((item) => this._normalizeJsonData(item));
|
|
1248
|
+
data.results = data.results.map((item: any) => this._normalizeJsonData(item));
|
|
1079
1249
|
} else if (data.news && Array.isArray(data.news) && data.news.length === 0) {
|
|
1080
1250
|
data = data.news;
|
|
1081
1251
|
} else if (data.news && typeof data.news === "object" && !Array.isArray(data.news)) {
|
|
@@ -1130,8 +1300,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1130
1300
|
/**
|
|
1131
1301
|
* Normalizes JSON data by transforming specific fields and ensuring URLs are complete.
|
|
1132
1302
|
*
|
|
1133
|
-
* @param
|
|
1134
|
-
* @returns
|
|
1303
|
+
* @param item - The JSON object to be normalized.
|
|
1304
|
+
* @returns - The normalized JSON object.
|
|
1135
1305
|
*
|
|
1136
1306
|
* The function performs the following transformations:
|
|
1137
1307
|
* - Normalizes the ID field if it matches a specific pattern.
|
|
@@ -1141,7 +1311,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1141
1311
|
* - Removes the `timeAgo` field if it exists.
|
|
1142
1312
|
* - Converts the object to EJSON format if necessary.
|
|
1143
1313
|
*/
|
|
1144
|
-
_normalizeJsonData(item) {
|
|
1314
|
+
private _normalizeJsonData(item: any) {
|
|
1145
1315
|
if (!item || typeof item !== "object") {
|
|
1146
1316
|
return item;
|
|
1147
1317
|
}
|
|
@@ -1179,7 +1349,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1179
1349
|
|
|
1180
1350
|
if (item?.openingHours && Array.isArray(item.openingHours) && item.openingHours.length > 0) {
|
|
1181
1351
|
item.openingHours = item.openingHours.filter(
|
|
1182
|
-
(day) => day.dayOfWeek && day.hours && day.hours[0] && day.hours[0].opens
|
|
1352
|
+
(day: any) => day.dayOfWeek && day.hours && day.hours[0] && day.hours[0].opens
|
|
1183
1353
|
);
|
|
1184
1354
|
}
|
|
1185
1355
|
|
|
@@ -1208,6 +1378,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1208
1378
|
if (item?.timeAgo) {
|
|
1209
1379
|
delete item.timeAgo;
|
|
1210
1380
|
}
|
|
1381
|
+
|
|
1382
|
+
if(item?.author?.typeSig === "people"){
|
|
1383
|
+
item.author.type = "citoyens";
|
|
1384
|
+
}
|
|
1211
1385
|
|
|
1212
1386
|
// Convertit en EJSON si besoin
|
|
1213
1387
|
return this._fromJSONValue ? EJSON.fromJSONValue(item) : item;
|
|
@@ -1219,10 +1393,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1219
1393
|
* (i.e., it starts with "http://" or "https://"), it is returned as is. Otherwise, the image path
|
|
1220
1394
|
* is concatenated with the base URL of the ApiClient instance.
|
|
1221
1395
|
*
|
|
1222
|
-
* @param
|
|
1223
|
-
* @returns
|
|
1396
|
+
* @param imagePath - The image path to ensure as a full URL.
|
|
1397
|
+
* @returns - The full URL of the image path.
|
|
1224
1398
|
*/
|
|
1225
|
-
_ensureFullURL(imagePath) {
|
|
1399
|
+
private _ensureFullURL(imagePath: any) {
|
|
1226
1400
|
if (!imagePath) return imagePath;
|
|
1227
1401
|
imagePath = imagePath.trim();
|
|
1228
1402
|
if (!imagePath || /^https?:\/\//i.test(imagePath)) {
|
|
@@ -1246,8 +1420,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1246
1420
|
* Pour chaque clé présente dans dateFields, si la valeur est un nombre ou un objet
|
|
1247
1421
|
* contenant une propriété sec (nombre), la transforme en { $date: valeurEnMs }.
|
|
1248
1422
|
*
|
|
1249
|
-
* @param
|
|
1250
|
-
* @returns
|
|
1423
|
+
* @param obj - L’objet à normaliser.
|
|
1424
|
+
* @returns L’objet normalisé.
|
|
1251
1425
|
*/
|
|
1252
1426
|
// _normalizeDatesRecursively(obj) {
|
|
1253
1427
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1278,8 +1452,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1278
1452
|
* vérifie et convertit le chemin en URL complète à l’aide de _ensureFullURL.
|
|
1279
1453
|
* Les cas particuliers (comme mediaImg.images ou mediaFile.files) sont également gérés.
|
|
1280
1454
|
*
|
|
1281
|
-
* @param
|
|
1282
|
-
* @returns
|
|
1455
|
+
* @param obj - L’objet à normaliser.
|
|
1456
|
+
* @returns L’objet normalisé.
|
|
1283
1457
|
*/
|
|
1284
1458
|
// _normalizeImagesRecursively(obj) {
|
|
1285
1459
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1311,8 +1485,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1311
1485
|
* Si un objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
|
|
1312
1486
|
* l'ID est normalisé et la propriété _id est convertie au format EJSON attendu.
|
|
1313
1487
|
*
|
|
1314
|
-
* @param
|
|
1315
|
-
* @returns
|
|
1488
|
+
* @param obj - L'objet ou le tableau à normaliser.
|
|
1489
|
+
* @returns L'objet ou le tableau normalisé.
|
|
1316
1490
|
*/
|
|
1317
1491
|
// _normalizeIdRecursively(obj) {
|
|
1318
1492
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1346,8 +1520,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1346
1520
|
* Normalise récursivement les valeurs booléennes.
|
|
1347
1521
|
* Si une valeur est une chaîne "true" ou "false", elle est convertie en booléen.
|
|
1348
1522
|
*
|
|
1349
|
-
* @param
|
|
1350
|
-
* @returns
|
|
1523
|
+
* @param data - L'objet, le tableau ou la valeur à normaliser.
|
|
1524
|
+
* @returns La donnée normalisée.
|
|
1351
1525
|
*/
|
|
1352
1526
|
// _normalizeBooleansRecursively(data) {
|
|
1353
1527
|
// if (typeof data === "string") {
|
|
@@ -1369,10 +1543,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1369
1543
|
/**
|
|
1370
1544
|
* Transforme une chaîne "true" ou "false" en booléen.
|
|
1371
1545
|
*
|
|
1372
|
-
* @param
|
|
1373
|
-
* @returns
|
|
1546
|
+
* @param str - La chaîne à normaliser.
|
|
1547
|
+
* @returns La valeur booléenne ou la chaîne originale.
|
|
1374
1548
|
*/
|
|
1375
|
-
_normalizeString(str) {
|
|
1549
|
+
private _normalizeString(str: any) {
|
|
1376
1550
|
if (str === "true") return true;
|
|
1377
1551
|
if (str === "false") return false;
|
|
1378
1552
|
return str;
|
|
@@ -1383,10 +1557,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1383
1557
|
* Si l'objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
|
|
1384
1558
|
* il met à jour l'objet en assignant la valeur de l'ID et en formattant _id en EJSON.
|
|
1385
1559
|
*
|
|
1386
|
-
* @param
|
|
1387
|
-
* @returns
|
|
1560
|
+
* @param obj - L'objet à normaliser.
|
|
1561
|
+
* @returns L'objet avec l'ID normalisé.
|
|
1388
1562
|
*/
|
|
1389
|
-
_normalizeId(obj) {
|
|
1563
|
+
private _normalizeId(obj: any) {
|
|
1390
1564
|
if (obj._id && obj._id.$id && /^[0-9a-fA-F]{24}$/.test(obj._id.$id)) {
|
|
1391
1565
|
obj.id = obj._id.$id;
|
|
1392
1566
|
obj._id = { $type: "oid", $value: obj._id.$id };
|
|
@@ -1403,10 +1577,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1403
1577
|
* Si la valeur est un objet contenant "sec" (nombre) ou un nombre,
|
|
1404
1578
|
* elle est convertie en { $date: valeurEnMs }.
|
|
1405
1579
|
*
|
|
1406
|
-
* @param
|
|
1407
|
-
* @returns
|
|
1580
|
+
* @param value - La valeur à normaliser.
|
|
1581
|
+
* @returns La valeur normalisée.
|
|
1408
1582
|
*/
|
|
1409
|
-
_normalizeDate(value) {
|
|
1583
|
+
private _normalizeDate(value: any) {
|
|
1410
1584
|
if (value && typeof value === "object" && typeof value.sec === "number") {
|
|
1411
1585
|
return { $date: value.sec * 1000 };
|
|
1412
1586
|
} else if (typeof value === "number") {
|
|
@@ -1419,10 +1593,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1419
1593
|
* Normalise une URL d'image.
|
|
1420
1594
|
* Si la valeur est une chaîne non vide, retourne l'URL complète via _ensureFullURL.
|
|
1421
1595
|
*
|
|
1422
|
-
* @param
|
|
1423
|
-
* @returns
|
|
1596
|
+
* @param value - La valeur à normaliser.
|
|
1597
|
+
* @returns La valeur normalisée.
|
|
1424
1598
|
*/
|
|
1425
|
-
_normalizeImage(value) {
|
|
1599
|
+
private _normalizeImage(value: any) {
|
|
1426
1600
|
if (typeof value === "string" && value.trim() !== "") {
|
|
1427
1601
|
return this._ensureFullURL(value);
|
|
1428
1602
|
}
|
|
@@ -1432,7 +1606,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1432
1606
|
/**
|
|
1433
1607
|
* Liste des champs d'image à normaliser.
|
|
1434
1608
|
*/
|
|
1435
|
-
_imageFields = [
|
|
1609
|
+
private _imageFields = [
|
|
1436
1610
|
"profilImageUrl",
|
|
1437
1611
|
"profilThumbImageUrl",
|
|
1438
1612
|
"profilMediumImageUrl",
|
|
@@ -1449,7 +1623,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1449
1623
|
/**
|
|
1450
1624
|
* Liste des champs de date à normaliser.
|
|
1451
1625
|
*/
|
|
1452
|
-
_dateFields = ["modified", "created", "updated", "birthDate", "lastLoginDate", "startDate", "endDate", "date", "issuedOn"];
|
|
1626
|
+
private _dateFields = ["modified", "created", "updated", "birthDate", "lastLoginDate", "startDate", "endDate", "date", "issuedOn"];
|
|
1453
1627
|
|
|
1454
1628
|
/**
|
|
1455
1629
|
* Normalise récursivement un objet, un tableau ou une valeur simple.
|
|
@@ -1460,10 +1634,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1460
1634
|
* - Transformation récursive des objets et tableaux.
|
|
1461
1635
|
* - Normalisation des identifiants.
|
|
1462
1636
|
*
|
|
1463
|
-
* @param
|
|
1464
|
-
* @returns
|
|
1637
|
+
* @param data - La donnée à normaliser.
|
|
1638
|
+
* @returns La donnée normalisée.
|
|
1465
1639
|
*/
|
|
1466
|
-
_normalizeRecursively(data) {
|
|
1640
|
+
private _normalizeRecursively(data: any): any {
|
|
1467
1641
|
// Cas de base pour les chaînes
|
|
1468
1642
|
if (typeof data === "string") {
|
|
1469
1643
|
return this._normalizeString(data);
|
|
@@ -1471,13 +1645,13 @@ export default class ApiClient extends EventEmitter {
|
|
|
1471
1645
|
|
|
1472
1646
|
// Traitement des tableaux
|
|
1473
1647
|
if (Array.isArray(data)) {
|
|
1474
|
-
return data.map(item => this._normalizeRecursively(item));
|
|
1648
|
+
return data.map((item: any) => this._normalizeRecursively(item));
|
|
1475
1649
|
}
|
|
1476
1650
|
|
|
1477
1651
|
// Traitement des objets non nuls
|
|
1478
1652
|
if (data !== null && typeof data === "object") {
|
|
1479
1653
|
// On crée une copie de l'objet pour éviter les effets de bord.
|
|
1480
|
-
const normalizedData = {};
|
|
1654
|
+
const normalizedData: any = {};
|
|
1481
1655
|
|
|
1482
1656
|
Object.keys(data).forEach(key => {
|
|
1483
1657
|
// Appliquer récursivement la normalisation sur la valeur.
|
|
@@ -1509,11 +1683,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
1509
1683
|
return data;
|
|
1510
1684
|
}
|
|
1511
1685
|
|
|
1512
|
-
_startBeforeEndValidate = (schema, data) => {
|
|
1686
|
+
private _startBeforeEndValidate = (schema: any, data: any) => {
|
|
1513
1687
|
if (!data.startDate || !data.endDate) return true;
|
|
1514
1688
|
|
|
1515
1689
|
const isValid = new Date(data.startDate) < new Date(data.endDate);
|
|
1516
1690
|
if (!isValid) {
|
|
1691
|
+
// @ts-expect-error - on sait que ValidateFunction expose .errors au runtime
|
|
1517
1692
|
this._startBeforeEndValidate.errors = [
|
|
1518
1693
|
{
|
|
1519
1694
|
instancePath: "/startDate", // ou "." si tu veux le chemin actuel
|
|
@@ -1528,31 +1703,41 @@ export default class ApiClient extends EventEmitter {
|
|
|
1528
1703
|
};
|
|
1529
1704
|
|
|
1530
1705
|
|
|
1531
|
-
|
|
1706
|
+
/**
|
|
1707
|
+
* Récupère le schéma de requête pour un endpoint donné.
|
|
1708
|
+
* @param constant - Le nom de l'endpoint.
|
|
1709
|
+
* @returns Le schéma de requête ou null si non trouvé.
|
|
1710
|
+
*/
|
|
1711
|
+
getRequestSchema(constant: string) {
|
|
1532
1712
|
const endpoint = this._endpoints.find(e => e.constant === constant);
|
|
1533
1713
|
return endpoint?.request || null;
|
|
1534
1714
|
}
|
|
1535
1715
|
|
|
1536
|
-
|
|
1716
|
+
/**
|
|
1717
|
+
* Récupère le schéma de chemin pour un endpoint donné.
|
|
1718
|
+
* @param constant - Le nom de l'endpoint.
|
|
1719
|
+
* @returns Le schéma de chemin ou null si non trouvé.
|
|
1720
|
+
*/
|
|
1721
|
+
getPathSchema(constant: string) {
|
|
1537
1722
|
return this._endpoints.find(e => e.constant === constant)?.pathParams || null;
|
|
1538
1723
|
}
|
|
1539
1724
|
|
|
1540
1725
|
/**
|
|
1541
1726
|
* Permet d'écouter facilement un ensemble d'événements importants émis par l'ApiClient.
|
|
1542
1727
|
*
|
|
1543
|
-
* @param
|
|
1544
|
-
* @param
|
|
1545
|
-
* @param
|
|
1546
|
-
* @param
|
|
1547
|
-
* @param
|
|
1548
|
-
* @param
|
|
1549
|
-
* @param
|
|
1550
|
-
* @param
|
|
1551
|
-
* @param
|
|
1552
|
-
* @param
|
|
1553
|
-
* @param
|
|
1728
|
+
* @param handlers - Un objet avec des fonctions à appeler selon l'événement.
|
|
1729
|
+
* @param [handlers.retryAttempt] - Lors d'une tentative de retry axios.
|
|
1730
|
+
* @param [handlers.queuedOffline] - Lorsqu'une requête est mise en file.
|
|
1731
|
+
* @param [handlers.circuitBreakerOpen] - Quand le breaker s'ouvre.
|
|
1732
|
+
* @param [handlers.circuitBreakerReset] - Quand le breaker se referme.
|
|
1733
|
+
* @param [handlers.refreshSuccess] - Quand le token est rafraîchi.
|
|
1734
|
+
* @param [handlers.refreshFailed] - Quand le refresh échoue.
|
|
1735
|
+
* @param [handlers.sessionReset] - Quand la session est réinitialisée.
|
|
1736
|
+
* @param [handlers.validationError] - Quand une validation échoue.
|
|
1737
|
+
* @param [handlers.offlineModeChanged] - Quand le mode offline change.
|
|
1738
|
+
* @param [handlers.userLoggedIn] - Quand un utilisateur se connecte.
|
|
1554
1739
|
*/
|
|
1555
|
-
onEvent(handlers = {}) {
|
|
1740
|
+
onEvent(handlers: Record<string, (...args: any[]) => void> = {}) {
|
|
1556
1741
|
const availableEvents = [
|
|
1557
1742
|
"retryAttempt",
|
|
1558
1743
|
"queuedOffline",
|
|
@@ -1568,7 +1753,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1568
1753
|
|
|
1569
1754
|
for (const eventName of availableEvents) {
|
|
1570
1755
|
if (typeof handlers[eventName] === "function") {
|
|
1571
|
-
this.on(eventName, handlers[eventName]);
|
|
1756
|
+
this.on(eventName, handlers[eventName] as (...args: any[]) => void);
|
|
1572
1757
|
}
|
|
1573
1758
|
}
|
|
1574
1759
|
}
|
|
@@ -1577,7 +1762,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1577
1762
|
* Retourne la liste des noms d'événements personnalisés déclarés dans les endpoints.
|
|
1578
1763
|
* Utile pour introspection ou documentation.
|
|
1579
1764
|
*
|
|
1580
|
-
* @returns
|
|
1765
|
+
* @returns Liste des événements émis dynamiquement via postActions.
|
|
1581
1766
|
*/
|
|
1582
1767
|
getDeclaredEvents() {
|
|
1583
1768
|
const events = new Set();
|
|
@@ -1594,46 +1779,65 @@ export default class ApiClient extends EventEmitter {
|
|
|
1594
1779
|
}
|
|
1595
1780
|
|
|
1596
1781
|
}
|
|
1782
|
+
|
|
1597
1783
|
/**
|
|
1598
1784
|
* @event ApiClient#retryAttempt
|
|
1599
1785
|
* @type {Object}
|
|
1600
1786
|
* @property {number} retryCount - Le numéro de tentative.
|
|
1601
1787
|
* @property {string} url - L'URL de la requête ayant échoué.
|
|
1788
|
+
*/
|
|
1602
1789
|
|
|
1790
|
+
/**
|
|
1603
1791
|
* @event ApiClient#queuedOffline
|
|
1604
1792
|
* @type {Object}
|
|
1605
1793
|
* @property {string} constant - Le nom de l'endpoint.
|
|
1606
1794
|
* @property {Object} data - Les données envoyées.
|
|
1607
1795
|
* @property {"offlineMode"|"circuitBreaker"} reason - La raison de la mise en file.
|
|
1796
|
+
*/
|
|
1608
1797
|
|
|
1798
|
+
/**
|
|
1609
1799
|
* @event ApiClient#circuitBreakerOpen
|
|
1610
1800
|
* @type {Object}
|
|
1611
1801
|
* @property {number} timestamp - Date (ms) à laquelle le breaker s'est ouvert.
|
|
1802
|
+
*/
|
|
1612
1803
|
|
|
1804
|
+
/**
|
|
1613
1805
|
* @event ApiClient#circuitBreakerReset
|
|
1614
1806
|
* @type {void}
|
|
1807
|
+
*/
|
|
1615
1808
|
|
|
1809
|
+
/**
|
|
1616
1810
|
* @event ApiClient#refreshSuccess
|
|
1617
1811
|
* @type {Object}
|
|
1618
1812
|
* @property {string} token - Le nouveau token d'accès.
|
|
1619
1813
|
* @property {string} [refreshToken] - Le nouveau refreshToken (optionnel).
|
|
1814
|
+
*/
|
|
1620
1815
|
|
|
1816
|
+
/**
|
|
1621
1817
|
* @event ApiClient#refreshFailed
|
|
1622
1818
|
* @type {Object}
|
|
1623
1819
|
* @property {string} error - Le message d’erreur de refresh.
|
|
1820
|
+
*/
|
|
1624
1821
|
|
|
1822
|
+
/**
|
|
1625
1823
|
* @event ApiClient#sessionReset
|
|
1626
1824
|
* @type {void}
|
|
1825
|
+
*/
|
|
1627
1826
|
|
|
1827
|
+
/**
|
|
1628
1828
|
* @event ApiClient#validationError
|
|
1629
1829
|
* @type {Object}
|
|
1630
1830
|
* @property {"pathParams"|"request"|"response"} stage - Étape de validation échouée.
|
|
1631
1831
|
* @property {Array<Object>} errors - Erreurs AJV brutes.
|
|
1832
|
+
*/
|
|
1632
1833
|
|
|
1834
|
+
/**
|
|
1633
1835
|
* @event ApiClient#offlineModeChanged
|
|
1634
1836
|
* @type {boolean} - true si le client est offline, false sinon.
|
|
1635
|
-
|
|
1837
|
+
*/
|
|
1838
|
+
|
|
1839
|
+
/**
|
|
1636
1840
|
* @event ApiClient#userLoggedIn
|
|
1637
|
-
* @param
|
|
1841
|
+
* @param user - Les données utilisateur extraites de la réponse.
|
|
1638
1842
|
*/
|
|
1639
1843
|
|