@communecter/cocolight-api-client 1.0.54 → 1.0.56
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 +29 -17
- package/src/{Api.js → Api.ts} +85 -95
- package/src/{ApiClient.js → ApiClient.ts} +436 -247
- package/src/EJSONType.ts +103 -0
- package/src/api/{Badge.js → Badge.ts} +56 -45
- package/src/api/BaseEntity.ts +3890 -0
- package/src/api/Comment.ts +200 -0
- package/src/api/{EndpointApi.js → EndpointApi.ts} +363 -297
- package/src/api/EndpointApi.types.ts +4609 -0
- package/src/api/EntityRegistry.ts +203 -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} +16 -39
- 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 +38 -82
- package/types/Api.d.ts.map +1 -0
- package/types/ApiClient.d.ts +244 -184
- package/types/ApiClient.d.ts.map +1 -0
- package/types/EJSONType.d.ts +48 -22
- package/types/EJSONType.d.ts.map +1 -0
- package/types/api/Badge.d.ts +20 -20
- package/types/api/Badge.d.ts.map +1 -0
- package/types/api/BaseEntity.d.ts +751 -446
- package/types/api/BaseEntity.d.ts.map +1 -0
- package/types/api/Comment.d.ts +36 -0
- package/types/api/EndpointApi.d.ts +347 -295
- package/types/api/EndpointApi.d.ts.map +1 -0
- package/types/api/EndpointApi.types.d.ts +3914 -4133
- package/types/api/EntityRegistry.d.ts +18 -16
- package/types/api/EntityRegistry.d.ts.map +1 -0
- package/types/api/Event.d.ts +119 -35
- package/types/api/Event.d.ts.map +1 -0
- package/types/api/News.d.ts +52 -20
- package/types/api/News.d.ts.map +1 -0
- package/types/api/Organization.d.ts +165 -49
- package/types/api/Organization.d.ts.map +1 -0
- package/types/api/Poi.d.ts +51 -22
- package/types/api/Poi.d.ts.map +1 -0
- package/types/api/Project.d.ts +151 -52
- package/types/api/Project.d.ts.map +1 -0
- package/types/api/User.d.ts +222 -93
- package/types/api/User.d.ts.map +1 -0
- package/types/api/UserApi.d.ts +60 -9
- 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 +6922 -1215
- package/types/endpoints.module.d.ts.map +1 -0
- package/types/error.d.ts +25 -51
- package/types/error.d.ts.map +1 -0
- package/types/index.d.ts +55 -48
- package/types/index.d.ts.map +1 -0
- package/types/mixin/UserMixin.d.ts +1 -1
- 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 +10 -9
- package/types/utils/FileOfflineStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/FileStorageStrategy.node.d.ts +9 -20
- package/types/utils/FileStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/MultiServerFileStorageStrategy.node.d.ts +13 -18
- package/types/utils/MultiServerFileStorageStrategy.node.d.ts.map +1 -0
- package/types/utils/MultiServerTokenStorageStrategy.d.ts +30 -51
- package/types/utils/MultiServerTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/OfflineClientManager.d.ts +52 -88
- package/types/utils/OfflineClientManager.d.ts.map +1 -0
- package/types/utils/OfflineQueueStorageStrategy.d.ts +12 -9
- package/types/utils/OfflineQueueStorageStrategy.d.ts.map +1 -0
- package/types/utils/TokenStorage.d.ts +20 -70
- 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 -11
- package/types/utils/createDefaultMultiServerTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/createDefaultOfflineStrategy.d.ts +2 -3
- package/types/utils/createDefaultOfflineStrategy.d.ts.map +1 -0
- package/types/utils/createDefaultTokenStorageStrategy.d.ts +2 -12
- package/types/utils/createDefaultTokenStorageStrategy.d.ts.map +1 -0
- package/types/utils/reactive.d.ts +10 -16
- package/types/utils/reactive.d.ts.map +1 -0
- package/types/utils/stream-utils.node.d.ts +3 -2
- 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 -121
- package/src/index.js +0 -97
- package/src/mixin/UserMixin.js +0 -8
- package/src/utils/MultiServerFileStorageStrategy.node.js +0 -87
- package/src/utils/MultiServerTokenStorageStrategy.js +0 -188
- package/src/utils/OfflineQueueStorageStrategy.js +0 -51
- package/src/utils/TokenStorage.js +0 -153
- package/src/utils/createDefaultMultiServerTokenStorageStrategy.js +0 -51
- package/src/utils/createDefaultTokenStorageStrategy.js +0 -49
- package/src/utils/stream-utils.node.js +0 -10
|
@@ -1,50 +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;
|
|
17
31
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
}
|
|
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
|
+
}
|
|
67
|
+
|
|
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
|
+
}
|
|
21
76
|
|
|
22
77
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @property {string} baseURL
|
|
25
|
-
* @property {string} [accessToken]
|
|
26
|
-
* @property {string} [refreshToken]
|
|
27
|
-
* @property {string} [refreshUrl="/api/cocolight/refreshtoken"]
|
|
28
|
-
* @property {any[]} [endpoints]
|
|
29
|
-
* @property {number} [timeout=30000]
|
|
30
|
-
* @property {boolean} [debug=false]
|
|
31
|
-
* @property {number} [maxRetries=0]
|
|
32
|
-
* @property {number} [circuitBreakerThreshold=5]
|
|
33
|
-
* @property {number} [circuitBreakerResetTime=60000]
|
|
34
|
-
* @property {boolean} [fromJSONValue=true]
|
|
35
|
-
* @property {TokenStorageStrategy|null} [tokenStorageStrategy=null]
|
|
78
|
+
* Union type pour tous les résultats possibles de callEndpoint
|
|
36
79
|
*/
|
|
80
|
+
export type CallEndpointResponse<T = any> = CallEndpointResult<T> | ApiClientOfflineEnqueueResult;
|
|
37
81
|
|
|
38
|
-
EJSON.addType("oid", value => {
|
|
39
|
-
|
|
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");
|
|
40
90
|
});
|
|
41
91
|
|
|
92
|
+
|
|
42
93
|
/**
|
|
43
94
|
* Client générique pour consommer une API REST avec validation AJV, gestion des tokens,
|
|
44
95
|
* circuit breaker, retry automatique, et support offline.
|
|
45
96
|
*
|
|
46
|
-
* @extends EventEmitter
|
|
47
|
-
*
|
|
48
97
|
* @fires ApiClient#retryAttempt
|
|
49
98
|
* @fires ApiClient#queuedOffline
|
|
50
99
|
* @fires ApiClient#circuitBreakerOpen
|
|
@@ -57,9 +106,36 @@ EJSON.addType("oid", value => {
|
|
|
57
106
|
* @fires ApiClient#userLoggedIn
|
|
58
107
|
*/
|
|
59
108
|
export default class ApiClient extends EventEmitter {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
63
139
|
constructor({
|
|
64
140
|
baseURL,
|
|
65
141
|
accessToken,
|
|
@@ -73,24 +149,23 @@ export default class ApiClient extends EventEmitter {
|
|
|
73
149
|
circuitBreakerResetTime = 60000,
|
|
74
150
|
fromJSONValue = true,
|
|
75
151
|
tokenStorageStrategy = null
|
|
76
|
-
}
|
|
152
|
+
}: ApiClientOptions) {
|
|
77
153
|
super(); // EventEmitter
|
|
78
154
|
|
|
79
155
|
if (!baseURL) {
|
|
80
156
|
throw new ApiClientError("Le paramètre \"baseURL\" est obligatoire.", 500);
|
|
81
157
|
}
|
|
82
158
|
|
|
83
|
-
this.__entityTag = "ApiClient";
|
|
84
|
-
|
|
85
159
|
this._baseURL = baseURL;
|
|
86
160
|
this._refreshUrl = refreshUrl;
|
|
87
|
-
this._endpoints = endpoints;
|
|
161
|
+
this._endpoints = endpoints || endpointsJson.endpoints;
|
|
88
162
|
this._debug = debug;
|
|
89
|
-
let _userId = null;
|
|
90
|
-
this._offlineClientManager = null;
|
|
91
|
-
|
|
92
|
-
// Active la transformation des données en EJSON globalement
|
|
93
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;
|
|
94
169
|
|
|
95
170
|
Object.defineProperty(this, "userId", {
|
|
96
171
|
get: () => _userId,
|
|
@@ -100,9 +175,9 @@ export default class ApiClient extends EventEmitter {
|
|
|
100
175
|
enumerable: true
|
|
101
176
|
});
|
|
102
177
|
|
|
103
|
-
this._setUserId = (id) => {
|
|
178
|
+
this._setUserId = (id: string | null) => {
|
|
104
179
|
_userId = id;
|
|
105
|
-
this._logger
|
|
180
|
+
this._logger?.debug(`[ApiClient] userId set: ${id}`);
|
|
106
181
|
};
|
|
107
182
|
|
|
108
183
|
// AJV
|
|
@@ -138,11 +213,11 @@ export default class ApiClient extends EventEmitter {
|
|
|
138
213
|
axiosRetry(this._client, {
|
|
139
214
|
retries: maxRetries,
|
|
140
215
|
retryDelay: axiosRetry.exponentialDelay,
|
|
141
|
-
retryCondition: (error) => {
|
|
216
|
+
retryCondition: (error: any) => {
|
|
142
217
|
// Retry sur erreurs 5xx ou erreurs réseau
|
|
143
218
|
return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error);
|
|
144
219
|
},
|
|
145
|
-
onRetry: (retryCount, error, requestConfig) => {
|
|
220
|
+
onRetry: (retryCount: number, error: any, requestConfig: any) => {
|
|
146
221
|
this._logger.warn(`[Retry] Tentative #${retryCount} pour ${requestConfig?.url}`);
|
|
147
222
|
this.emit("retryAttempt", { retryCount, url: requestConfig?.url });
|
|
148
223
|
}
|
|
@@ -150,24 +225,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
150
225
|
this._logger.info(`[ApiClient] Retry activé : ${maxRetries} max`);
|
|
151
226
|
}
|
|
152
227
|
|
|
153
|
-
// Circuit breaker (simplifié)
|
|
154
|
-
this._breakerThreshold = circuitBreakerThreshold;
|
|
155
|
-
this._breakerResetTime = circuitBreakerResetTime;
|
|
156
|
-
this._breakerErrorCount = 0;
|
|
157
|
-
this._breakerOpen = false;
|
|
158
|
-
/** @type {string|null} */
|
|
159
|
-
this._lastBreakerOpenTime = null;
|
|
160
|
-
|
|
161
|
-
/** @type {string|null} */
|
|
162
|
-
this._accessToken = null;
|
|
163
|
-
/** @type {string|null} */
|
|
164
|
-
this._refreshToken = null;
|
|
165
|
-
|
|
166
228
|
if (tokenStorageStrategy instanceof MultiServerTokenStorageStrategy) {
|
|
167
229
|
tokenStorageStrategy.use(this._baseURL);
|
|
168
230
|
}
|
|
169
|
-
|
|
170
|
-
/** @type {TokenStorageStrategy} */
|
|
231
|
+
|
|
171
232
|
this._tokenStorage = tokenStorageStrategy || new MemoryStorageStrategy();
|
|
172
233
|
|
|
173
234
|
if (
|
|
@@ -181,7 +242,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
181
242
|
}
|
|
182
243
|
|
|
183
244
|
// Applique un token initial s'il est fourni
|
|
184
|
-
if(refreshToken){
|
|
245
|
+
if (refreshToken) {
|
|
185
246
|
this.setRefreshToken(refreshToken);
|
|
186
247
|
}
|
|
187
248
|
|
|
@@ -223,6 +284,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
223
284
|
this._logger.info("[ApiClient] Token rafraîchi avec succès.");
|
|
224
285
|
|
|
225
286
|
// 🔑 Mise à jour EXPLICITE du header Authorization dans la requête originale
|
|
287
|
+
originalRequest.headers = originalRequest.headers ?? {};
|
|
226
288
|
originalRequest.headers["Authorization"] = "Bearer " + this.getToken();
|
|
227
289
|
|
|
228
290
|
this._logger.info("[ApiClient] Retente la requête originale avec le nouveau token.");
|
|
@@ -233,7 +295,11 @@ export default class ApiClient extends EventEmitter {
|
|
|
233
295
|
}
|
|
234
296
|
} catch (err) {
|
|
235
297
|
this.resetSession();
|
|
236
|
-
|
|
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);
|
|
237
303
|
}
|
|
238
304
|
}
|
|
239
305
|
|
|
@@ -245,12 +311,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
245
311
|
|
|
246
312
|
/**
|
|
247
313
|
* Sets the access token for the API client and updates the authorization header.
|
|
248
|
-
*
|
|
249
|
-
* @param {string|null} token - The access token to be set.
|
|
250
314
|
*/
|
|
251
|
-
setToken(token) {
|
|
315
|
+
setToken(token: string | null): void {
|
|
252
316
|
this._accessToken = token;
|
|
253
|
-
this._tokenStorage.setAccessToken(token);
|
|
317
|
+
this._tokenStorage.setAccessToken(token ?? "");
|
|
254
318
|
if (token) {
|
|
255
319
|
this._client.defaults.headers.common["Authorization"] = "Bearer " + token;
|
|
256
320
|
} else {
|
|
@@ -267,21 +331,17 @@ export default class ApiClient extends EventEmitter {
|
|
|
267
331
|
|
|
268
332
|
/**
|
|
269
333
|
* Retrieves the current access token.
|
|
270
|
-
*
|
|
271
|
-
* @returns {string|null} The access token.
|
|
272
334
|
*/
|
|
273
|
-
getToken() {
|
|
335
|
+
getToken(): string | null {
|
|
274
336
|
return this._accessToken;
|
|
275
337
|
}
|
|
276
338
|
|
|
277
339
|
/**
|
|
278
340
|
* Sets the refresh token for the API client.
|
|
279
|
-
*
|
|
280
|
-
* @param {string|null} token - The refresh token to be set.
|
|
281
341
|
*/
|
|
282
|
-
setRefreshToken(token) {
|
|
342
|
+
setRefreshToken(token: string | null): void {
|
|
283
343
|
this._refreshToken = token;
|
|
284
|
-
this._tokenStorage.setRefreshToken(token);
|
|
344
|
+
this._tokenStorage.setRefreshToken(token ?? "");
|
|
285
345
|
if(this.userId === null){
|
|
286
346
|
// Extrait l'id depuis le token et le stocke si disponible
|
|
287
347
|
const userId = this._getIdFromToken(token);
|
|
@@ -295,34 +355,27 @@ export default class ApiClient extends EventEmitter {
|
|
|
295
355
|
|
|
296
356
|
/**
|
|
297
357
|
* Retrieves the current refresh token.
|
|
298
|
-
*
|
|
299
|
-
* @returns {string|null} The refresh token.
|
|
300
358
|
*/
|
|
301
|
-
getRefreshToken() {
|
|
359
|
+
getRefreshToken(): string | null {
|
|
302
360
|
return this._refreshToken;
|
|
303
361
|
}
|
|
304
362
|
|
|
305
363
|
/**
|
|
306
364
|
* Indique si le client est connecté.
|
|
307
365
|
* On considère que le client est connecté si un token d'accès (_accessToken) est défini.
|
|
308
|
-
*
|
|
309
|
-
* @returns {boolean} True si connecté, false sinon.
|
|
310
366
|
*/
|
|
311
|
-
get isConnected() {
|
|
367
|
+
get isConnected(): boolean {
|
|
312
368
|
return !!this._accessToken;
|
|
313
369
|
}
|
|
314
370
|
|
|
315
371
|
/**
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
* @returns {string|null} L'identifiant extrait ou null si non trouvé.
|
|
320
|
-
*/
|
|
321
|
-
_getIdFromToken(token) {
|
|
372
|
+
* Extrait l'identifiant depuis un JWT.
|
|
373
|
+
*/
|
|
374
|
+
private _getIdFromToken(token: string | null): string | null {
|
|
322
375
|
if (!token) return null;
|
|
323
376
|
try {
|
|
324
377
|
// Décodage du token grâce à jwt-decode
|
|
325
|
-
const payload = jwtDecode(token);
|
|
378
|
+
const payload = jwtDecode<JwtPayload & { id?: string; userId?: string }>(token);
|
|
326
379
|
// L'identifiant peut être dans "id" ou "userId"
|
|
327
380
|
this._logger.debug("[ApiClient] Payload décodé :", payload);
|
|
328
381
|
return payload.id || payload.userId || null;
|
|
@@ -333,10 +386,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
333
386
|
}
|
|
334
387
|
|
|
335
388
|
/**
|
|
336
|
-
* Méthode simplifiée de refresh (en JSON).
|
|
389
|
+
* Méthode simplifiée de refresh (en JSON).
|
|
337
390
|
* Emet un event refreshSuccess si ça marche
|
|
338
391
|
*/
|
|
339
|
-
async _refreshAccessToken() {
|
|
392
|
+
private async _refreshAccessToken(): Promise<boolean> {
|
|
340
393
|
if (!this._refreshToken) return false;
|
|
341
394
|
|
|
342
395
|
const refreshClient = axios.create({
|
|
@@ -360,22 +413,21 @@ export default class ApiClient extends EventEmitter {
|
|
|
360
413
|
return false;
|
|
361
414
|
} catch (err) {
|
|
362
415
|
// Si on a une erreur, on reset la session
|
|
363
|
-
this.emit("refreshFailed", { error: err
|
|
416
|
+
this.emit("refreshFailed", { error: getErrorMessage(err) });
|
|
364
417
|
this.resetSession();
|
|
365
|
-
this._logger.error(`[ApiClient] Refresh Error : ${err
|
|
418
|
+
this._logger.error(`[ApiClient] Refresh Error : ${getErrorMessage(err)}`);
|
|
366
419
|
return false;
|
|
367
420
|
}
|
|
368
421
|
}
|
|
369
422
|
|
|
370
423
|
/**
|
|
371
424
|
* checkCircuitBreaker : vérifie si on peut appeler l'API ou non
|
|
372
|
-
* si le breaker est
|
|
425
|
+
* si le breaker est "open", on regarde si on peut "reset"
|
|
373
426
|
*/
|
|
374
|
-
_checkCircuitBreaker() {
|
|
427
|
+
private _checkCircuitBreaker(): boolean {
|
|
375
428
|
if (!this._breakerOpen) return true;
|
|
376
429
|
const now = Date.now();
|
|
377
|
-
if (now - this._lastBreakerOpenTime > this._breakerResetTime) {
|
|
378
|
-
// On reset
|
|
430
|
+
if (this._lastBreakerOpenTime != null && (now - this._lastBreakerOpenTime) > this._breakerResetTime) {
|
|
379
431
|
this._breakerOpen = false;
|
|
380
432
|
this._breakerErrorCount = 0;
|
|
381
433
|
this._logger.warn("[ApiClient] Circuit breaker réinitialisé");
|
|
@@ -386,10 +438,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
386
438
|
}
|
|
387
439
|
|
|
388
440
|
/**
|
|
389
|
-
* updateCircuitBreaker : incremente le compteur d'erreurs
|
|
390
|
-
* 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.
|
|
391
443
|
*/
|
|
392
|
-
_updateCircuitBreakerError() {
|
|
444
|
+
private _updateCircuitBreakerError(): void {
|
|
393
445
|
this._breakerErrorCount += 1;
|
|
394
446
|
this._logger.warn(`[ApiClient] Erreur #${this._breakerErrorCount} sur ${this._breakerThreshold}`);
|
|
395
447
|
if (this._breakerErrorCount >= this._breakerThreshold) {
|
|
@@ -403,7 +455,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
403
455
|
/**
|
|
404
456
|
* resetCircuitBreaker : en cas de succès on reset le compteur
|
|
405
457
|
*/
|
|
406
|
-
_resetCircuitBreakerSuccess() {
|
|
458
|
+
private _resetCircuitBreakerSuccess(): void {
|
|
407
459
|
this._breakerErrorCount = 0;
|
|
408
460
|
if (this._breakerOpen) {
|
|
409
461
|
this._breakerOpen = false;
|
|
@@ -411,7 +463,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
411
463
|
}
|
|
412
464
|
}
|
|
413
465
|
|
|
414
|
-
static stripNullsInPlace(obj) {
|
|
466
|
+
static stripNullsInPlace(obj: any): any {
|
|
415
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.
|
|
416
468
|
if (obj && typeof obj === "object" && obj.value && typeof obj.value.pipe === "function") {
|
|
417
469
|
return obj;
|
|
@@ -439,13 +491,13 @@ export default class ApiClient extends EventEmitter {
|
|
|
439
491
|
return obj;
|
|
440
492
|
}
|
|
441
493
|
|
|
442
|
-
_resolveSpecialValuesInPlace(obj, pathParams = {}) {
|
|
443
|
-
const aliasMap = {
|
|
494
|
+
private _resolveSpecialValuesInPlace(obj: any, pathParams: any = {}) {
|
|
495
|
+
const aliasMap: any = {
|
|
444
496
|
userId: () => this.userId,
|
|
445
497
|
accessToken: () => this._accessToken,
|
|
446
498
|
refreshToken: () => this._refreshToken,
|
|
447
499
|
baseURL: () => this._baseURL,
|
|
448
|
-
pathParams: (subPath) => {
|
|
500
|
+
pathParams: (subPath: any) => {
|
|
449
501
|
// Si la valeur existe dans pathParams, on la retourne
|
|
450
502
|
const value = this._getValueByPath(pathParams, subPath);
|
|
451
503
|
return value !== undefined && value !== null ? value : undefined;
|
|
@@ -455,7 +507,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
455
507
|
// Expression régulière qui capture les alias sous forme délimitée @{...} ou simple @...
|
|
456
508
|
const regex = /@(?:\{([^}]+)\}|([\w.]+))/g;
|
|
457
509
|
|
|
458
|
-
const resolveString = (str) => {
|
|
510
|
+
const resolveString = (str: any) => {
|
|
459
511
|
if (typeof str !== "string") return str;
|
|
460
512
|
return str.replace(regex, (_, delimitedAlias, plainAlias) => {
|
|
461
513
|
// Si alias délimité, on traite toute la chaîne à l'intérieur des accolades.
|
|
@@ -504,17 +556,17 @@ export default class ApiClient extends EventEmitter {
|
|
|
504
556
|
|
|
505
557
|
|
|
506
558
|
// Vérifie si l'objet est un binaire (par exemple un flux ou un Buffer)
|
|
507
|
-
const isBinary = (input) => {
|
|
559
|
+
const isBinary = (input: any) => {
|
|
508
560
|
return input && typeof input === "object" &&
|
|
509
561
|
(typeof input.pipe === "function" || (typeof Buffer !== "undefined" && input instanceof Buffer));
|
|
510
562
|
};
|
|
511
|
-
|
|
563
|
+
|
|
512
564
|
// Vérifie si l'objet est un "plain object"
|
|
513
|
-
const isPlainObject = (input) => {
|
|
565
|
+
const isPlainObject = (input: any) => {
|
|
514
566
|
return Object.prototype.toString.call(input) === "[object Object]";
|
|
515
567
|
};
|
|
516
|
-
|
|
517
|
-
const internal = (input) => {
|
|
568
|
+
|
|
569
|
+
const internal = (input: any): any => {
|
|
518
570
|
// Si c'est un binaire, ne rien modifier
|
|
519
571
|
if (isBinary(input)) {
|
|
520
572
|
return input;
|
|
@@ -525,7 +577,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
525
577
|
}
|
|
526
578
|
// Si c'est un plain object, on crée un nouvel objet avec remplacement dans les clés et valeurs
|
|
527
579
|
if (isPlainObject(input)) {
|
|
528
|
-
const result = {};
|
|
580
|
+
const result: any = {};
|
|
529
581
|
for (const [key, value] of Object.entries(input)) {
|
|
530
582
|
const resolvedKey = resolveString(key);
|
|
531
583
|
result[resolvedKey] = internal(value);
|
|
@@ -544,22 +596,22 @@ export default class ApiClient extends EventEmitter {
|
|
|
544
596
|
}
|
|
545
597
|
|
|
546
598
|
|
|
547
|
-
_cleanSchemaLeftoverAlias(obj) {
|
|
599
|
+
private _cleanSchemaLeftoverAlias(obj: any): any {
|
|
548
600
|
// Expression régulière qui détecte n'importe quel alias du type "@quelqueChose"
|
|
549
601
|
const aliasRegex = /@\w+/;
|
|
550
602
|
|
|
551
603
|
if (Array.isArray(obj)) {
|
|
552
|
-
return obj.map(item => this._cleanSchemaLeftoverAlias(item));
|
|
604
|
+
return obj.map((item: any) => this._cleanSchemaLeftoverAlias(item));
|
|
553
605
|
} else if (obj && typeof obj === "object") {
|
|
554
|
-
const newObj = {};
|
|
606
|
+
const newObj: any = {};
|
|
555
607
|
for (const key of Object.keys(obj)) {
|
|
556
608
|
const val = obj[key];
|
|
557
|
-
|
|
609
|
+
|
|
558
610
|
// Si le nom de la clé contient un alias (exemple : "@userId", "@anything"), on ne la copie pas
|
|
559
611
|
if (aliasRegex.test(key)) {
|
|
560
612
|
continue;
|
|
561
613
|
}
|
|
562
|
-
|
|
614
|
+
|
|
563
615
|
// Si la propriété s'appelle "default"
|
|
564
616
|
if (key === "default") {
|
|
565
617
|
// Si la valeur est une chaîne et qu'elle contient un alias, on ne la copie pas
|
|
@@ -576,12 +628,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
576
628
|
continue;
|
|
577
629
|
}
|
|
578
630
|
}
|
|
579
|
-
|
|
631
|
+
|
|
580
632
|
// Si la valeur est une chaîne contenant un alias, on ne la copie pas
|
|
581
633
|
if (typeof val === "string" && aliasRegex.test(val)) {
|
|
582
634
|
continue;
|
|
583
635
|
}
|
|
584
|
-
|
|
636
|
+
|
|
585
637
|
// Sinon, on nettoie récursivement la valeur
|
|
586
638
|
newObj[key] = this._cleanSchemaLeftoverAlias(val);
|
|
587
639
|
}
|
|
@@ -594,32 +646,39 @@ export default class ApiClient extends EventEmitter {
|
|
|
594
646
|
* Safely calls an asynchronous function and handles any errors that occur.
|
|
595
647
|
* Logs the error message using the instance's logger before re-throwing the error.
|
|
596
648
|
*
|
|
597
|
-
* @param
|
|
598
|
-
* @param
|
|
599
|
-
* @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.
|
|
600
652
|
* @throws {Error} Re-throws any error that occurs during the function execution.
|
|
601
653
|
*/
|
|
602
|
-
async safeCall(fn
|
|
654
|
+
async safeCall(fn: (...args: any[]) => Promise<any>, ...args: any[]) {
|
|
603
655
|
try {
|
|
604
656
|
return await fn(...args);
|
|
605
657
|
} catch (error) {
|
|
606
|
-
this._logger.error(`[ApiClient.safeCall] Erreur: ${error
|
|
658
|
+
this._logger.error(`[ApiClient.safeCall] Erreur: ${getErrorMessage(error)}`);
|
|
607
659
|
throw error;
|
|
608
660
|
}
|
|
609
661
|
}
|
|
610
662
|
|
|
611
663
|
/**
|
|
612
|
-
*
|
|
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.
|
|
613
667
|
*
|
|
614
|
-
* @param
|
|
615
|
-
* @param
|
|
616
|
-
* @param
|
|
617
|
-
* @param
|
|
618
|
-
* @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.
|
|
619
673
|
* @throws {CircuitBreakerError} If the circuit breaker is activated.
|
|
620
674
|
* @throws {ApiClientError} If the endpoint is not found, token is required but not provided, or validation fails.
|
|
621
675
|
*/
|
|
622
|
-
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>> {
|
|
623
682
|
|
|
624
683
|
const endpoint = this._endpoints.find((ep) => ep.constant === constant);
|
|
625
684
|
if (!endpoint) {
|
|
@@ -638,8 +697,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
638
697
|
|
|
639
698
|
const lowerMethod = (method || "GET").toLowerCase();
|
|
640
699
|
const realContentType = contentType || "application/json";
|
|
641
|
-
const headers = { "Content-Type": realContentType };
|
|
642
|
-
|
|
700
|
+
const headers: Record<string, string> = { "Content-Type": realContentType };
|
|
701
|
+
|
|
643
702
|
// Auth headers
|
|
644
703
|
if (this._accessToken) {
|
|
645
704
|
if (auth === "bearer") {
|
|
@@ -665,19 +724,19 @@ export default class ApiClient extends EventEmitter {
|
|
|
665
724
|
}
|
|
666
725
|
|
|
667
726
|
const pathParams = data.pathParams || {};
|
|
668
|
-
const validatePathParams = this._ajv.compile(schemaToCompile);
|
|
727
|
+
const validatePathParams: ValidateFunction = this._ajv.compile(schemaToCompile);
|
|
669
728
|
const valid = validatePathParams(pathParams);
|
|
670
729
|
|
|
671
730
|
if (!valid) {
|
|
672
|
-
const errorMessages = this._ajvErrorHuman(validatePathParams.errors);
|
|
731
|
+
const errorMessages = this._ajvErrorHuman(validatePathParams.errors ?? []);
|
|
673
732
|
this.emit("validationError", { stage: "pathParams", errors: validatePathParams.errors });
|
|
674
733
|
throw new ApiValidationError(`callEndpoint: ${constant} - Path parameter validation failed.`, 400, errorMessages, validatePathParams.errors);
|
|
675
734
|
}
|
|
676
735
|
|
|
677
736
|
resolvedParams = this._resolveSpecialValuesInPlace(pathParams);
|
|
678
|
-
|
|
679
|
-
resolvedPath = resolvedPath.replace(/\{(\w+)\}/g, (_, key) => {
|
|
680
|
-
const val = resolvedParams[key];
|
|
737
|
+
|
|
738
|
+
resolvedPath = resolvedPath.replace(/\{(\w+)\}/g, (_: any, key: string) => {
|
|
739
|
+
const val = (resolvedParams as any)[key];
|
|
681
740
|
if (val !== undefined) return encodeURIComponent(val);
|
|
682
741
|
throw new ApiClientError(`Path param manquant ou non résolu : {${key}}`, 400);
|
|
683
742
|
});
|
|
@@ -691,11 +750,19 @@ export default class ApiClient extends EventEmitter {
|
|
|
691
750
|
schemaToCompile = this._cleanSchemaLeftoverAlias(requestSchema);
|
|
692
751
|
}
|
|
693
752
|
|
|
694
|
-
|
|
753
|
+
let dataForValidation = { ...data };
|
|
695
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
|
+
|
|
696
763
|
const cleanedData = ApiClient.stripNullsInPlace(dataForValidation);
|
|
697
|
-
|
|
698
|
-
const validateRequest = this._ajv.compile(schemaToCompile);
|
|
764
|
+
|
|
765
|
+
const validateRequest: ValidateFunction = this._ajv.compile(schemaToCompile);
|
|
699
766
|
const valid = validateRequest(cleanedData);
|
|
700
767
|
if (!valid) {
|
|
701
768
|
const errorMessages = validateRequest.errors ? this._ajvErrorHuman(validateRequest.errors) : [];
|
|
@@ -724,7 +791,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
724
791
|
data
|
|
725
792
|
});
|
|
726
793
|
|
|
727
|
-
|
|
794
|
+
const enqueued = { data: null, offline: true as const };
|
|
795
|
+
return enqueued;
|
|
728
796
|
} else {
|
|
729
797
|
this._logger.warn("[ApiClient] Mode dégradé actif mais offlineManager non initialisé correctement");
|
|
730
798
|
throw new ApiClientError("Mode hors-ligne actif, mais gestionnaire offline non disponible.", 503);
|
|
@@ -752,7 +820,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
752
820
|
data
|
|
753
821
|
});
|
|
754
822
|
|
|
755
|
-
|
|
823
|
+
const enqueued = { data: null, breaker: true as const };
|
|
824
|
+
return enqueued;
|
|
756
825
|
}
|
|
757
826
|
|
|
758
827
|
throw new CircuitBreakerError("Le circuit breaker est activé, impossible d'appeler l'API");
|
|
@@ -777,15 +846,15 @@ export default class ApiClient extends EventEmitter {
|
|
|
777
846
|
});
|
|
778
847
|
|
|
779
848
|
if (validateResponseSchema) {
|
|
780
|
-
const
|
|
781
|
-
const schema = responses?.[
|
|
849
|
+
const statusStr = String(response.status);
|
|
850
|
+
const schema = responses?.[statusStr];
|
|
782
851
|
if (schema) {
|
|
783
|
-
const validateResponse = this._ajv.compile(schema);
|
|
852
|
+
const validateResponse: ValidateFunction = this._ajv.compile(schema);
|
|
784
853
|
const valid = validateResponse(response.data);
|
|
785
854
|
if (!valid) {
|
|
786
|
-
const errorMessages = this._ajvErrorHuman(validateResponse.errors);
|
|
855
|
+
const errorMessages = this._ajvErrorHuman(validateResponse.errors ?? []);
|
|
787
856
|
this.emit("validationError", { stage: "response", errors: validateResponse.errors });
|
|
788
|
-
throw new ApiValidationError("Response validation failed.", status, errorMessages, validateResponse.errors);
|
|
857
|
+
throw new ApiValidationError("Response validation failed.", response.status, errorMessages, validateResponse.errors);
|
|
789
858
|
}
|
|
790
859
|
}
|
|
791
860
|
}
|
|
@@ -800,7 +869,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
800
869
|
|
|
801
870
|
// postActions éventuelles
|
|
802
871
|
if (Array.isArray(endpoint.postActions)) {
|
|
803
|
-
endpoint.postActions.forEach(action => {
|
|
872
|
+
endpoint.postActions.forEach((action: any) => {
|
|
804
873
|
const value = action.path ? this._getValueByPath(response.data, action.path) : null;
|
|
805
874
|
|
|
806
875
|
switch (action.type) {
|
|
@@ -828,8 +897,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
828
897
|
break;
|
|
829
898
|
|
|
830
899
|
case "callMethod":
|
|
831
|
-
if (typeof this[action.method] === "function") {
|
|
832
|
-
this[action.method]();
|
|
900
|
+
if (typeof (this as any)[action.method] === "function") {
|
|
901
|
+
(this as any)[action.method]();
|
|
833
902
|
this._logger.debug(`[ApiClient] Méthode appelée : ${action.method}`);
|
|
834
903
|
} else {
|
|
835
904
|
this._logger.warn(`[ApiClient] Méthode inconnue : ${action.method}`);
|
|
@@ -846,14 +915,16 @@ export default class ApiClient extends EventEmitter {
|
|
|
846
915
|
return response;
|
|
847
916
|
} catch (error) {
|
|
848
917
|
this._updateCircuitBreakerError();
|
|
849
|
-
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)}`);
|
|
850
919
|
if(error instanceof ApiValidationError) {
|
|
851
920
|
throw error;
|
|
852
921
|
} else {
|
|
922
|
+
const statusCode = hasResponse(error) ? error.response.status : 500;
|
|
923
|
+
const responseData = hasResponse(error) ? error.response.data : null;
|
|
853
924
|
throw new ApiClientError(
|
|
854
|
-
`Erreur lors de l'appel de l'API : ${error
|
|
855
|
-
|
|
856
|
-
|
|
925
|
+
`Erreur lors de l'appel de l'API : ${getErrorMessage(error)}`,
|
|
926
|
+
statusCode,
|
|
927
|
+
responseData
|
|
857
928
|
);
|
|
858
929
|
}
|
|
859
930
|
|
|
@@ -863,17 +934,18 @@ export default class ApiClient extends EventEmitter {
|
|
|
863
934
|
/**
|
|
864
935
|
* Converts AJV (Another JSON Schema Validator) errors into human-readable messages.
|
|
865
936
|
*
|
|
866
|
-
* @param
|
|
867
|
-
* @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.
|
|
868
939
|
*/
|
|
869
|
-
_ajvErrorHuman(errors){
|
|
940
|
+
private _ajvErrorHuman(errors: any[]){
|
|
870
941
|
try {
|
|
871
942
|
const errorsMessages = new AggregateAjvError(errors);
|
|
872
|
-
|
|
943
|
+
// @ts-expect-error - champ non typé public dans les defs
|
|
944
|
+
const messages = errorsMessages.errors.map(({ message }: any) => message);
|
|
873
945
|
return messages;
|
|
874
946
|
} catch (e) {
|
|
875
947
|
this._logger.error("[ApiClient] _ajvErrorHuman", e);
|
|
876
|
-
return errors.map(({ message }) => message);
|
|
948
|
+
return errors.map(({ message }: any) => message);
|
|
877
949
|
}
|
|
878
950
|
}
|
|
879
951
|
|
|
@@ -889,13 +961,15 @@ export default class ApiClient extends EventEmitter {
|
|
|
889
961
|
*
|
|
890
962
|
* If no errors are found, the original `response` is returned.
|
|
891
963
|
*
|
|
892
|
-
* @param {
|
|
893
|
-
* @
|
|
894
|
-
* @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 }}
|
|
895
966
|
* @throws {ApiResponseError} If an error is detected in the response data.
|
|
896
|
-
* @returns {Object} The original `response` object if no errors are found.
|
|
897
967
|
*/
|
|
898
|
-
checkAndThrowApiResponseError(response) {
|
|
968
|
+
checkAndThrowApiResponseError(response: any) {
|
|
969
|
+
if (!("status" in response)) {
|
|
970
|
+
return response;
|
|
971
|
+
}
|
|
972
|
+
|
|
899
973
|
const data = response.data;
|
|
900
974
|
if (!data || typeof data !== "object") {
|
|
901
975
|
return response;
|
|
@@ -931,7 +1005,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
931
1005
|
* - userId
|
|
932
1006
|
* - En-têtes Axios
|
|
933
1007
|
*/
|
|
934
|
-
resetSession() {
|
|
1008
|
+
resetSession(): void {
|
|
935
1009
|
this.setToken(null);
|
|
936
1010
|
this.setRefreshToken(null);
|
|
937
1011
|
this._setUserId(null);
|
|
@@ -946,12 +1020,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
946
1020
|
|
|
947
1021
|
/**
|
|
948
1022
|
* Retrieves the value from an object based on a dot-separated path.
|
|
949
|
-
*
|
|
950
|
-
* @param {Object} obj - The object from which to retrieve the value.
|
|
951
|
-
* @param {string} path - The dot-separated path string indicating the value to retrieve.
|
|
952
|
-
* @returns {*} - The value found at the specified path, or undefined if the path is invalid.
|
|
953
1023
|
*/
|
|
954
|
-
_getValueByPath(obj, path) {
|
|
1024
|
+
private _getValueByPath(obj: any, path: string): any {
|
|
955
1025
|
if (!path) return undefined;
|
|
956
1026
|
const keys = path.split(".");
|
|
957
1027
|
return keys.reduce((acc, key) => acc && acc[key], obj);
|
|
@@ -959,54 +1029,139 @@ export default class ApiClient extends EventEmitter {
|
|
|
959
1029
|
|
|
960
1030
|
// Conversions
|
|
961
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
|
+
|
|
962
1118
|
/**
|
|
963
1119
|
* Converts an object to URL search parameters.
|
|
964
1120
|
*
|
|
965
|
-
* @param
|
|
966
|
-
* @param
|
|
967
|
-
* @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.
|
|
968
1124
|
*/
|
|
969
|
-
static toURLSearchParams(obj, options = {}) {
|
|
970
|
-
return this._buildParams(obj, new URLSearchParams(), options);
|
|
1125
|
+
static toURLSearchParams(obj: any, options: any = {}) {
|
|
1126
|
+
return this._buildParams(obj, new URLSearchParams(), options) as URLSearchParams;
|
|
971
1127
|
}
|
|
972
1128
|
|
|
973
1129
|
/**
|
|
974
1130
|
* Builds parameters for an API request from a given object.
|
|
975
|
-
*
|
|
976
|
-
* @param {Object} obj - The object to be converted into parameters.
|
|
977
|
-
* @param {FormData|URLSearchParams} paramsInstance - The instance to which the parameters will be appended.
|
|
978
|
-
* @param {Object} [options={}] - Optional settings.
|
|
979
|
-
* @param {boolean} [options.dots=false] - Whether to use dots in the parameter keys.
|
|
980
|
-
* @param {boolean} [options.indexes=false] - Whether to include array indexes in the parameter keys.
|
|
981
|
-
* @param {boolean} [options.metaTokens=true] - Whether to include meta tokens in the parameter keys.
|
|
982
|
-
* @throws {TypeError} If the provided obj is not an object or is null.
|
|
983
|
-
* @returns {FormData|URLSearchParams} The instance with the appended parameters.
|
|
984
1131
|
*/
|
|
985
|
-
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 {
|
|
986
1141
|
if (typeof obj !== "object" || obj === null) {
|
|
987
1142
|
throw new TypeError("La donnée doit être un objet non nul.");
|
|
988
1143
|
}
|
|
989
1144
|
|
|
990
1145
|
const { dots = false, indexes = false, metaTokens = true } = options;
|
|
991
|
-
const stack = [];
|
|
1146
|
+
const stack: any[] = [];
|
|
992
1147
|
|
|
993
|
-
function isVisitable(thing) {
|
|
1148
|
+
function isVisitable(thing: any) {
|
|
994
1149
|
return Object.prototype.toString.call(thing) === "[object Object]" || Array.isArray(thing);
|
|
995
1150
|
}
|
|
996
1151
|
|
|
997
|
-
function removeBrackets(key) {
|
|
1152
|
+
function removeBrackets(key: any) {
|
|
998
1153
|
return key.endsWith("[]") ? key.slice(0, -2) : key;
|
|
999
1154
|
}
|
|
1000
1155
|
|
|
1001
|
-
function renderKey(path, key, useDots = false) {
|
|
1156
|
+
function renderKey(path: any, key: any, useDots = false) {
|
|
1002
1157
|
if (!path) return key;
|
|
1003
|
-
return path.concat(key).map((token, i) => {
|
|
1158
|
+
return path.concat(key).map((token: any, i: any) => {
|
|
1004
1159
|
token = removeBrackets(token);
|
|
1005
1160
|
return !useDots && i ? `[${token}]` : token;
|
|
1006
1161
|
}).join(useDots ? "." : "");
|
|
1007
1162
|
}
|
|
1008
1163
|
|
|
1009
|
-
function convertValue(value) {
|
|
1164
|
+
function convertValue(value: any) {
|
|
1010
1165
|
if (value === null || value === undefined) return "";
|
|
1011
1166
|
if (value instanceof Date) return value.toISOString();
|
|
1012
1167
|
if (
|
|
@@ -1020,7 +1175,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1020
1175
|
return value;
|
|
1021
1176
|
}
|
|
1022
1177
|
|
|
1023
|
-
function defaultVisitor(value, key, path) {
|
|
1178
|
+
function defaultVisitor(value: any, key: any, path: any) {
|
|
1024
1179
|
if (value && typeof value === "object") {
|
|
1025
1180
|
if (key.endsWith("{}")) {
|
|
1026
1181
|
key = metaTokens ? key : key.slice(0, -2);
|
|
@@ -1030,7 +1185,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1030
1185
|
|
|
1031
1186
|
if ((Array.isArray(value) && !value.some(isVisitable)) || key.endsWith("[]")) {
|
|
1032
1187
|
key = removeBrackets(key);
|
|
1033
|
-
value.forEach((el, index) => {
|
|
1188
|
+
value.forEach((el: any, index: any) => {
|
|
1034
1189
|
if (el !== undefined && el !== null) {
|
|
1035
1190
|
// const fieldKey = indexes ? renderKey([key], index, dots) : `${key}[]`;
|
|
1036
1191
|
const fieldKey = indexes
|
|
@@ -1048,9 +1203,9 @@ export default class ApiClient extends EventEmitter {
|
|
|
1048
1203
|
return false;
|
|
1049
1204
|
}
|
|
1050
1205
|
|
|
1051
|
-
function build(value, path = []) {
|
|
1052
|
-
if (value === undefined || value === null || stack.includes(value)) return;
|
|
1053
|
-
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);
|
|
1054
1209
|
|
|
1055
1210
|
Object.entries(value).forEach(([k, el]) => {
|
|
1056
1211
|
if (el !== undefined && defaultVisitor(el, k.trim(), path)) {
|
|
@@ -1074,12 +1229,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
1074
1229
|
* nested structures like `resultGoods`, `resultErrors`, `results`, `news`,
|
|
1075
1230
|
* `notif`, `citoyens`, `organizations`, `cities`, `newComment`, `map`, and `object`.
|
|
1076
1231
|
*
|
|
1077
|
-
* @param
|
|
1078
|
-
* @returns
|
|
1232
|
+
* @param data - The data to be transformed.
|
|
1233
|
+
* @returns - The transformed data.
|
|
1079
1234
|
*
|
|
1080
1235
|
* @private
|
|
1081
1236
|
*/
|
|
1082
|
-
_transformData(data) {
|
|
1237
|
+
private _transformData(data: any): any {
|
|
1083
1238
|
if (data && typeof data === "object") {
|
|
1084
1239
|
if (data.resultGoods?.msg) {
|
|
1085
1240
|
data = this._normalizeJsonData({ msg: data.resultGoods.msg, ...data });
|
|
@@ -1090,7 +1245,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1090
1245
|
return this._normalizeJsonData({ id: key, ...data.results[key] });
|
|
1091
1246
|
});
|
|
1092
1247
|
} else if (Array.isArray(data.results) && data.results.length > 0) {
|
|
1093
|
-
data.results = data.results.map((item) => this._normalizeJsonData(item));
|
|
1248
|
+
data.results = data.results.map((item: any) => this._normalizeJsonData(item));
|
|
1094
1249
|
} else if (data.news && Array.isArray(data.news) && data.news.length === 0) {
|
|
1095
1250
|
data = data.news;
|
|
1096
1251
|
} else if (data.news && typeof data.news === "object" && !Array.isArray(data.news)) {
|
|
@@ -1145,8 +1300,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1145
1300
|
/**
|
|
1146
1301
|
* Normalizes JSON data by transforming specific fields and ensuring URLs are complete.
|
|
1147
1302
|
*
|
|
1148
|
-
* @param
|
|
1149
|
-
* @returns
|
|
1303
|
+
* @param item - The JSON object to be normalized.
|
|
1304
|
+
* @returns - The normalized JSON object.
|
|
1150
1305
|
*
|
|
1151
1306
|
* The function performs the following transformations:
|
|
1152
1307
|
* - Normalizes the ID field if it matches a specific pattern.
|
|
@@ -1156,7 +1311,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1156
1311
|
* - Removes the `timeAgo` field if it exists.
|
|
1157
1312
|
* - Converts the object to EJSON format if necessary.
|
|
1158
1313
|
*/
|
|
1159
|
-
_normalizeJsonData(item) {
|
|
1314
|
+
private _normalizeJsonData(item: any) {
|
|
1160
1315
|
if (!item || typeof item !== "object") {
|
|
1161
1316
|
return item;
|
|
1162
1317
|
}
|
|
@@ -1194,7 +1349,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1194
1349
|
|
|
1195
1350
|
if (item?.openingHours && Array.isArray(item.openingHours) && item.openingHours.length > 0) {
|
|
1196
1351
|
item.openingHours = item.openingHours.filter(
|
|
1197
|
-
(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
|
|
1198
1353
|
);
|
|
1199
1354
|
}
|
|
1200
1355
|
|
|
@@ -1223,6 +1378,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1223
1378
|
if (item?.timeAgo) {
|
|
1224
1379
|
delete item.timeAgo;
|
|
1225
1380
|
}
|
|
1381
|
+
|
|
1382
|
+
if(item?.author?.typeSig === "people"){
|
|
1383
|
+
item.author.type = "citoyens";
|
|
1384
|
+
}
|
|
1226
1385
|
|
|
1227
1386
|
// Convertit en EJSON si besoin
|
|
1228
1387
|
return this._fromJSONValue ? EJSON.fromJSONValue(item) : item;
|
|
@@ -1234,10 +1393,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1234
1393
|
* (i.e., it starts with "http://" or "https://"), it is returned as is. Otherwise, the image path
|
|
1235
1394
|
* is concatenated with the base URL of the ApiClient instance.
|
|
1236
1395
|
*
|
|
1237
|
-
* @param
|
|
1238
|
-
* @returns
|
|
1396
|
+
* @param imagePath - The image path to ensure as a full URL.
|
|
1397
|
+
* @returns - The full URL of the image path.
|
|
1239
1398
|
*/
|
|
1240
|
-
_ensureFullURL(imagePath) {
|
|
1399
|
+
private _ensureFullURL(imagePath: any) {
|
|
1241
1400
|
if (!imagePath) return imagePath;
|
|
1242
1401
|
imagePath = imagePath.trim();
|
|
1243
1402
|
if (!imagePath || /^https?:\/\//i.test(imagePath)) {
|
|
@@ -1261,8 +1420,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1261
1420
|
* Pour chaque clé présente dans dateFields, si la valeur est un nombre ou un objet
|
|
1262
1421
|
* contenant une propriété sec (nombre), la transforme en { $date: valeurEnMs }.
|
|
1263
1422
|
*
|
|
1264
|
-
* @param
|
|
1265
|
-
* @returns
|
|
1423
|
+
* @param obj - L’objet à normaliser.
|
|
1424
|
+
* @returns L’objet normalisé.
|
|
1266
1425
|
*/
|
|
1267
1426
|
// _normalizeDatesRecursively(obj) {
|
|
1268
1427
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1293,8 +1452,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1293
1452
|
* vérifie et convertit le chemin en URL complète à l’aide de _ensureFullURL.
|
|
1294
1453
|
* Les cas particuliers (comme mediaImg.images ou mediaFile.files) sont également gérés.
|
|
1295
1454
|
*
|
|
1296
|
-
* @param
|
|
1297
|
-
* @returns
|
|
1455
|
+
* @param obj - L’objet à normaliser.
|
|
1456
|
+
* @returns L’objet normalisé.
|
|
1298
1457
|
*/
|
|
1299
1458
|
// _normalizeImagesRecursively(obj) {
|
|
1300
1459
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1326,8 +1485,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1326
1485
|
* Si un objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
|
|
1327
1486
|
* l'ID est normalisé et la propriété _id est convertie au format EJSON attendu.
|
|
1328
1487
|
*
|
|
1329
|
-
* @param
|
|
1330
|
-
* @returns
|
|
1488
|
+
* @param obj - L'objet ou le tableau à normaliser.
|
|
1489
|
+
* @returns L'objet ou le tableau normalisé.
|
|
1331
1490
|
*/
|
|
1332
1491
|
// _normalizeIdRecursively(obj) {
|
|
1333
1492
|
// if (obj === null || typeof obj !== "object") return obj;
|
|
@@ -1361,8 +1520,8 @@ export default class ApiClient extends EventEmitter {
|
|
|
1361
1520
|
* Normalise récursivement les valeurs booléennes.
|
|
1362
1521
|
* Si une valeur est une chaîne "true" ou "false", elle est convertie en booléen.
|
|
1363
1522
|
*
|
|
1364
|
-
* @param
|
|
1365
|
-
* @returns
|
|
1523
|
+
* @param data - L'objet, le tableau ou la valeur à normaliser.
|
|
1524
|
+
* @returns La donnée normalisée.
|
|
1366
1525
|
*/
|
|
1367
1526
|
// _normalizeBooleansRecursively(data) {
|
|
1368
1527
|
// if (typeof data === "string") {
|
|
@@ -1384,10 +1543,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1384
1543
|
/**
|
|
1385
1544
|
* Transforme une chaîne "true" ou "false" en booléen.
|
|
1386
1545
|
*
|
|
1387
|
-
* @param
|
|
1388
|
-
* @returns
|
|
1546
|
+
* @param str - La chaîne à normaliser.
|
|
1547
|
+
* @returns La valeur booléenne ou la chaîne originale.
|
|
1389
1548
|
*/
|
|
1390
|
-
_normalizeString(str) {
|
|
1549
|
+
private _normalizeString(str: any) {
|
|
1391
1550
|
if (str === "true") return true;
|
|
1392
1551
|
if (str === "false") return false;
|
|
1393
1552
|
return str;
|
|
@@ -1398,10 +1557,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1398
1557
|
* Si l'objet possède une propriété "_id" ou "id" contenant un sous-objet "$id" valide,
|
|
1399
1558
|
* il met à jour l'objet en assignant la valeur de l'ID et en formattant _id en EJSON.
|
|
1400
1559
|
*
|
|
1401
|
-
* @param
|
|
1402
|
-
* @returns
|
|
1560
|
+
* @param obj - L'objet à normaliser.
|
|
1561
|
+
* @returns L'objet avec l'ID normalisé.
|
|
1403
1562
|
*/
|
|
1404
|
-
_normalizeId(obj) {
|
|
1563
|
+
private _normalizeId(obj: any) {
|
|
1405
1564
|
if (obj._id && obj._id.$id && /^[0-9a-fA-F]{24}$/.test(obj._id.$id)) {
|
|
1406
1565
|
obj.id = obj._id.$id;
|
|
1407
1566
|
obj._id = { $type: "oid", $value: obj._id.$id };
|
|
@@ -1418,10 +1577,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1418
1577
|
* Si la valeur est un objet contenant "sec" (nombre) ou un nombre,
|
|
1419
1578
|
* elle est convertie en { $date: valeurEnMs }.
|
|
1420
1579
|
*
|
|
1421
|
-
* @param
|
|
1422
|
-
* @returns
|
|
1580
|
+
* @param value - La valeur à normaliser.
|
|
1581
|
+
* @returns La valeur normalisée.
|
|
1423
1582
|
*/
|
|
1424
|
-
_normalizeDate(value) {
|
|
1583
|
+
private _normalizeDate(value: any) {
|
|
1425
1584
|
if (value && typeof value === "object" && typeof value.sec === "number") {
|
|
1426
1585
|
return { $date: value.sec * 1000 };
|
|
1427
1586
|
} else if (typeof value === "number") {
|
|
@@ -1434,10 +1593,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1434
1593
|
* Normalise une URL d'image.
|
|
1435
1594
|
* Si la valeur est une chaîne non vide, retourne l'URL complète via _ensureFullURL.
|
|
1436
1595
|
*
|
|
1437
|
-
* @param
|
|
1438
|
-
* @returns
|
|
1596
|
+
* @param value - La valeur à normaliser.
|
|
1597
|
+
* @returns La valeur normalisée.
|
|
1439
1598
|
*/
|
|
1440
|
-
_normalizeImage(value) {
|
|
1599
|
+
private _normalizeImage(value: any) {
|
|
1441
1600
|
if (typeof value === "string" && value.trim() !== "") {
|
|
1442
1601
|
return this._ensureFullURL(value);
|
|
1443
1602
|
}
|
|
@@ -1447,7 +1606,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1447
1606
|
/**
|
|
1448
1607
|
* Liste des champs d'image à normaliser.
|
|
1449
1608
|
*/
|
|
1450
|
-
_imageFields = [
|
|
1609
|
+
private _imageFields = [
|
|
1451
1610
|
"profilImageUrl",
|
|
1452
1611
|
"profilThumbImageUrl",
|
|
1453
1612
|
"profilMediumImageUrl",
|
|
@@ -1464,7 +1623,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1464
1623
|
/**
|
|
1465
1624
|
* Liste des champs de date à normaliser.
|
|
1466
1625
|
*/
|
|
1467
|
-
_dateFields = ["modified", "created", "updated", "birthDate", "lastLoginDate", "startDate", "endDate", "date", "issuedOn"];
|
|
1626
|
+
private _dateFields = ["modified", "created", "updated", "birthDate", "lastLoginDate", "startDate", "endDate", "date", "issuedOn"];
|
|
1468
1627
|
|
|
1469
1628
|
/**
|
|
1470
1629
|
* Normalise récursivement un objet, un tableau ou une valeur simple.
|
|
@@ -1475,10 +1634,10 @@ export default class ApiClient extends EventEmitter {
|
|
|
1475
1634
|
* - Transformation récursive des objets et tableaux.
|
|
1476
1635
|
* - Normalisation des identifiants.
|
|
1477
1636
|
*
|
|
1478
|
-
* @param
|
|
1479
|
-
* @returns
|
|
1637
|
+
* @param data - La donnée à normaliser.
|
|
1638
|
+
* @returns La donnée normalisée.
|
|
1480
1639
|
*/
|
|
1481
|
-
_normalizeRecursively(data) {
|
|
1640
|
+
private _normalizeRecursively(data: any): any {
|
|
1482
1641
|
// Cas de base pour les chaînes
|
|
1483
1642
|
if (typeof data === "string") {
|
|
1484
1643
|
return this._normalizeString(data);
|
|
@@ -1486,13 +1645,13 @@ export default class ApiClient extends EventEmitter {
|
|
|
1486
1645
|
|
|
1487
1646
|
// Traitement des tableaux
|
|
1488
1647
|
if (Array.isArray(data)) {
|
|
1489
|
-
return data.map(item => this._normalizeRecursively(item));
|
|
1648
|
+
return data.map((item: any) => this._normalizeRecursively(item));
|
|
1490
1649
|
}
|
|
1491
1650
|
|
|
1492
1651
|
// Traitement des objets non nuls
|
|
1493
1652
|
if (data !== null && typeof data === "object") {
|
|
1494
1653
|
// On crée une copie de l'objet pour éviter les effets de bord.
|
|
1495
|
-
const normalizedData = {};
|
|
1654
|
+
const normalizedData: any = {};
|
|
1496
1655
|
|
|
1497
1656
|
Object.keys(data).forEach(key => {
|
|
1498
1657
|
// Appliquer récursivement la normalisation sur la valeur.
|
|
@@ -1524,11 +1683,12 @@ export default class ApiClient extends EventEmitter {
|
|
|
1524
1683
|
return data;
|
|
1525
1684
|
}
|
|
1526
1685
|
|
|
1527
|
-
_startBeforeEndValidate = (schema, data) => {
|
|
1686
|
+
private _startBeforeEndValidate = (schema: any, data: any) => {
|
|
1528
1687
|
if (!data.startDate || !data.endDate) return true;
|
|
1529
1688
|
|
|
1530
1689
|
const isValid = new Date(data.startDate) < new Date(data.endDate);
|
|
1531
1690
|
if (!isValid) {
|
|
1691
|
+
// @ts-expect-error - on sait que ValidateFunction expose .errors au runtime
|
|
1532
1692
|
this._startBeforeEndValidate.errors = [
|
|
1533
1693
|
{
|
|
1534
1694
|
instancePath: "/startDate", // ou "." si tu veux le chemin actuel
|
|
@@ -1543,31 +1703,41 @@ export default class ApiClient extends EventEmitter {
|
|
|
1543
1703
|
};
|
|
1544
1704
|
|
|
1545
1705
|
|
|
1546
|
-
|
|
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) {
|
|
1547
1712
|
const endpoint = this._endpoints.find(e => e.constant === constant);
|
|
1548
1713
|
return endpoint?.request || null;
|
|
1549
1714
|
}
|
|
1550
1715
|
|
|
1551
|
-
|
|
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) {
|
|
1552
1722
|
return this._endpoints.find(e => e.constant === constant)?.pathParams || null;
|
|
1553
1723
|
}
|
|
1554
1724
|
|
|
1555
1725
|
/**
|
|
1556
1726
|
* Permet d'écouter facilement un ensemble d'événements importants émis par l'ApiClient.
|
|
1557
1727
|
*
|
|
1558
|
-
* @param
|
|
1559
|
-
* @param
|
|
1560
|
-
* @param
|
|
1561
|
-
* @param
|
|
1562
|
-
* @param
|
|
1563
|
-
* @param
|
|
1564
|
-
* @param
|
|
1565
|
-
* @param
|
|
1566
|
-
* @param
|
|
1567
|
-
* @param
|
|
1568
|
-
* @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.
|
|
1569
1739
|
*/
|
|
1570
|
-
onEvent(handlers = {}) {
|
|
1740
|
+
onEvent(handlers: Record<string, (...args: any[]) => void> = {}) {
|
|
1571
1741
|
const availableEvents = [
|
|
1572
1742
|
"retryAttempt",
|
|
1573
1743
|
"queuedOffline",
|
|
@@ -1583,7 +1753,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1583
1753
|
|
|
1584
1754
|
for (const eventName of availableEvents) {
|
|
1585
1755
|
if (typeof handlers[eventName] === "function") {
|
|
1586
|
-
this.on(eventName, handlers[eventName]);
|
|
1756
|
+
this.on(eventName, handlers[eventName] as (...args: any[]) => void);
|
|
1587
1757
|
}
|
|
1588
1758
|
}
|
|
1589
1759
|
}
|
|
@@ -1592,7 +1762,7 @@ export default class ApiClient extends EventEmitter {
|
|
|
1592
1762
|
* Retourne la liste des noms d'événements personnalisés déclarés dans les endpoints.
|
|
1593
1763
|
* Utile pour introspection ou documentation.
|
|
1594
1764
|
*
|
|
1595
|
-
* @returns
|
|
1765
|
+
* @returns Liste des événements émis dynamiquement via postActions.
|
|
1596
1766
|
*/
|
|
1597
1767
|
getDeclaredEvents() {
|
|
1598
1768
|
const events = new Set();
|
|
@@ -1609,46 +1779,65 @@ export default class ApiClient extends EventEmitter {
|
|
|
1609
1779
|
}
|
|
1610
1780
|
|
|
1611
1781
|
}
|
|
1782
|
+
|
|
1612
1783
|
/**
|
|
1613
1784
|
* @event ApiClient#retryAttempt
|
|
1614
1785
|
* @type {Object}
|
|
1615
1786
|
* @property {number} retryCount - Le numéro de tentative.
|
|
1616
1787
|
* @property {string} url - L'URL de la requête ayant échoué.
|
|
1788
|
+
*/
|
|
1617
1789
|
|
|
1790
|
+
/**
|
|
1618
1791
|
* @event ApiClient#queuedOffline
|
|
1619
1792
|
* @type {Object}
|
|
1620
1793
|
* @property {string} constant - Le nom de l'endpoint.
|
|
1621
1794
|
* @property {Object} data - Les données envoyées.
|
|
1622
1795
|
* @property {"offlineMode"|"circuitBreaker"} reason - La raison de la mise en file.
|
|
1796
|
+
*/
|
|
1623
1797
|
|
|
1798
|
+
/**
|
|
1624
1799
|
* @event ApiClient#circuitBreakerOpen
|
|
1625
1800
|
* @type {Object}
|
|
1626
1801
|
* @property {number} timestamp - Date (ms) à laquelle le breaker s'est ouvert.
|
|
1802
|
+
*/
|
|
1627
1803
|
|
|
1804
|
+
/**
|
|
1628
1805
|
* @event ApiClient#circuitBreakerReset
|
|
1629
1806
|
* @type {void}
|
|
1807
|
+
*/
|
|
1630
1808
|
|
|
1809
|
+
/**
|
|
1631
1810
|
* @event ApiClient#refreshSuccess
|
|
1632
1811
|
* @type {Object}
|
|
1633
1812
|
* @property {string} token - Le nouveau token d'accès.
|
|
1634
1813
|
* @property {string} [refreshToken] - Le nouveau refreshToken (optionnel).
|
|
1814
|
+
*/
|
|
1635
1815
|
|
|
1816
|
+
/**
|
|
1636
1817
|
* @event ApiClient#refreshFailed
|
|
1637
1818
|
* @type {Object}
|
|
1638
1819
|
* @property {string} error - Le message d’erreur de refresh.
|
|
1820
|
+
*/
|
|
1639
1821
|
|
|
1822
|
+
/**
|
|
1640
1823
|
* @event ApiClient#sessionReset
|
|
1641
1824
|
* @type {void}
|
|
1825
|
+
*/
|
|
1642
1826
|
|
|
1827
|
+
/**
|
|
1643
1828
|
* @event ApiClient#validationError
|
|
1644
1829
|
* @type {Object}
|
|
1645
1830
|
* @property {"pathParams"|"request"|"response"} stage - Étape de validation échouée.
|
|
1646
1831
|
* @property {Array<Object>} errors - Erreurs AJV brutes.
|
|
1832
|
+
*/
|
|
1647
1833
|
|
|
1834
|
+
/**
|
|
1648
1835
|
* @event ApiClient#offlineModeChanged
|
|
1649
1836
|
* @type {boolean} - true si le client est offline, false sinon.
|
|
1650
|
-
|
|
1837
|
+
*/
|
|
1838
|
+
|
|
1839
|
+
/**
|
|
1651
1840
|
* @event ApiClient#userLoggedIn
|
|
1652
|
-
* @param
|
|
1841
|
+
* @param user - Les données utilisateur extraites de la réponse.
|
|
1653
1842
|
*/
|
|
1654
1843
|
|