@esfaenza/httpservice 16.2.27 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,983 +8,983 @@ import * as i2 from '@esfaenza/localizations';
8
8
  import * as i4 from '@esfaenza/preferences';
9
9
  import * as i5 from '@esfaenza/extensions';
10
10
 
11
- /**
12
- * Dizionario di invalidazioni delle chiamate HTTP
13
- */
14
- const HTTP_INVALIDATION = new InjectionToken('HTTP_INVALIDATION');
15
- /**
16
- * Durata della cache lato libreria dopo il quale la cache verrà ripulita
17
- */
18
- const CLIENT_CACHE_DURATION_S = new InjectionToken('CLIENT_CACHE_DURATION_S');
19
- /**
20
- * Nome dell'APP utilizzato per creare le chiavi delle cache specifiche per l'applicazione corrente
21
- */
22
- const APP_NAME = new InjectionToken('APP_NAME');
23
- /**
24
- * Indica se utilizzare l'identificativo utente nella cache
25
- */
26
- const CACHE_BY_USER = new InjectionToken('CACHE_BY_USER');
27
- /**
28
- * Indica se fare log nella console ad ogni azione significativa
29
- */
11
+ /**
12
+ * Dizionario di invalidazioni delle chiamate HTTP
13
+ */
14
+ const HTTP_INVALIDATION = new InjectionToken('HTTP_INVALIDATION');
15
+ /**
16
+ * Durata della cache lato libreria dopo il quale la cache verrà ripulita
17
+ */
18
+ const CLIENT_CACHE_DURATION_S = new InjectionToken('CLIENT_CACHE_DURATION_S');
19
+ /**
20
+ * Nome dell'APP utilizzato per creare le chiavi delle cache specifiche per l'applicazione corrente
21
+ */
22
+ const APP_NAME = new InjectionToken('APP_NAME');
23
+ /**
24
+ * Indica se utilizzare l'identificativo utente nella cache
25
+ */
26
+ const CACHE_BY_USER = new InjectionToken('CACHE_BY_USER');
27
+ /**
28
+ * Indica se fare log nella console ad ogni azione significativa
29
+ */
30
30
  const HTTP_DEBUG_MODE = new InjectionToken('DEBUG_MODE');
31
31
 
32
- /**
33
- * Classe d'estensione che permette di modificare il comportamento della libreria negli hook definiti da interfaccia, in modo da poterla adattare
34
- * con un certo grado di customizzazione a qualsiasi struttura di frontend
35
- */
36
- class HttpServiceExtensions {
37
- /**
38
- * Hook per recuperare l'access token con cui effettuare le chiamate HTTP
39
- */
40
- retrieveAccessToken() {
41
- return "";
42
- }
43
- /**
44
- * Hook per recuperare l'Identity corrente in un oggetto generico con le informazioni di User e Tenant
45
- */
46
- retrieveIdentity() {
47
- return null;
48
- }
49
- /**
50
- * Hook per leggere dallo stato dell'applicazione se stiamo già venendo ridirezionati al login, in modo da non tentare di effettuare
51
- * N redirect contemporaneamente qualora molte API fallissero allo stesso tempo nell'autenticazione
52
- */
53
- readRedirectingToLogin() {
54
- return false;
55
- }
56
- /**
57
- * Hook per scrivere nello stato dell'applicazione se stiamo effettuando un redirect al login o meno
58
- */
59
- writeRedirectingToLogin(status) {
60
- }
61
- /**
62
- * Hook per effettuare il redirect al login
63
- */
64
- redirectToLogin() {
65
- }
66
- /**
67
- * Hook per leggere dallo stato dell'applicazione se stiamo già gestendo un errore, in modo da non tentare di gestire
68
- * N errori contemporaneamente qualora molte API fallissero allo stesso tempo
69
- */
70
- readHandlingErrorStatus() {
71
- return false;
72
- }
73
- /**
74
- * Hook per scrivere nello stato dell'applicazione se stiamo gestendo un errore o meno
75
- */
76
- writeHandlingErrorStatus(status) {
77
- }
78
- /**
79
- * Hook per leggere dallo stato dell'applicazione se stiamo già gestendo un redirect, in modo da non tentare di gestire
80
- * N redirect contemporaneamente qualora molte API lo richiedessero allo stesso tempo
81
- */
82
- readRedirectionStatus() {
83
- return false;
84
- }
85
- /**
86
- * Hook per scrivere nello stato dell'applicazione se stiamo gestendo un redirect o meno
87
- */
88
- writeRedirectionStatus(status) {
89
- }
90
- /**
91
- * Hook per indicare se l'applicazione può gestire eccezioni del tipo "NotConfigured" (444). Qualora l'applicazione che lo implementa dichiari di gestirle verrà chiamata
92
- * la funzione **handleBlockingException** per ognuno di questi errori
93
- */
94
- handleNotConfiguredException() {
95
- return false;
96
- }
97
- /**
98
- * Hook per la gestione di un qualsiasi errore bloccante per l'operazione attuale, ma non fatale (Generalmente gestibile da dialog di avviso)
99
- */
100
- handleBlockingException(status, text) {
101
- console.error("ESHttpError Blocking Exception " + status + " " + text);
102
- }
103
- /**
104
- * Hook per la gestione di un qualsiasi errore fatale (Generalmente gestibile tramite navigazione a una pagina di errore custom per l'applicativo)
105
- */
106
- handleException(status, text) {
107
- console.error("ESHttpError " + status + " " + text);
108
- }
109
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpServiceExtensions, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
110
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpServiceExtensions, providedIn: 'root' }); }
111
- }
112
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpServiceExtensions, decorators: [{
113
- type: Injectable,
114
- args: [{ providedIn: 'root' }]
32
+ /**
33
+ * Classe d'estensione che permette di modificare il comportamento della libreria negli hook definiti da interfaccia, in modo da poterla adattare
34
+ * con un certo grado di customizzazione a qualsiasi struttura di frontend
35
+ */
36
+ class HttpServiceExtensions {
37
+ /**
38
+ * Hook per recuperare l'access token con cui effettuare le chiamate HTTP
39
+ */
40
+ retrieveAccessToken() {
41
+ return "";
42
+ }
43
+ /**
44
+ * Hook per recuperare l'Identity corrente in un oggetto generico con le informazioni di User e Tenant
45
+ */
46
+ retrieveIdentity() {
47
+ return null;
48
+ }
49
+ /**
50
+ * Hook per leggere dallo stato dell'applicazione se stiamo già venendo ridirezionati al login, in modo da non tentare di effettuare
51
+ * N redirect contemporaneamente qualora molte API fallissero allo stesso tempo nell'autenticazione
52
+ */
53
+ readRedirectingToLogin() {
54
+ return false;
55
+ }
56
+ /**
57
+ * Hook per scrivere nello stato dell'applicazione se stiamo effettuando un redirect al login o meno
58
+ */
59
+ writeRedirectingToLogin(status) {
60
+ }
61
+ /**
62
+ * Hook per effettuare il redirect al login
63
+ */
64
+ redirectToLogin() {
65
+ }
66
+ /**
67
+ * Hook per leggere dallo stato dell'applicazione se stiamo già gestendo un errore, in modo da non tentare di gestire
68
+ * N errori contemporaneamente qualora molte API fallissero allo stesso tempo
69
+ */
70
+ readHandlingErrorStatus() {
71
+ return false;
72
+ }
73
+ /**
74
+ * Hook per scrivere nello stato dell'applicazione se stiamo gestendo un errore o meno
75
+ */
76
+ writeHandlingErrorStatus(status) {
77
+ }
78
+ /**
79
+ * Hook per leggere dallo stato dell'applicazione se stiamo già gestendo un redirect, in modo da non tentare di gestire
80
+ * N redirect contemporaneamente qualora molte API lo richiedessero allo stesso tempo
81
+ */
82
+ readRedirectionStatus() {
83
+ return false;
84
+ }
85
+ /**
86
+ * Hook per scrivere nello stato dell'applicazione se stiamo gestendo un redirect o meno
87
+ */
88
+ writeRedirectionStatus(status) {
89
+ }
90
+ /**
91
+ * Hook per indicare se l'applicazione può gestire eccezioni del tipo "NotConfigured" (444). Qualora l'applicazione che lo implementa dichiari di gestirle verrà chiamata
92
+ * la funzione **handleBlockingException** per ognuno di questi errori
93
+ */
94
+ handleNotConfiguredException() {
95
+ return false;
96
+ }
97
+ /**
98
+ * Hook per la gestione di un qualsiasi errore bloccante per l'operazione attuale, ma non fatale (Generalmente gestibile da dialog di avviso)
99
+ */
100
+ handleBlockingException(status, text) {
101
+ console.error("ESHttpError Blocking Exception " + status + " " + text);
102
+ }
103
+ /**
104
+ * Hook per la gestione di un qualsiasi errore fatale (Generalmente gestibile tramite navigazione a una pagina di errore custom per l'applicativo)
105
+ */
106
+ handleException(status, text) {
107
+ console.error("ESHttpError " + status + " " + text);
108
+ }
109
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpServiceExtensions, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
110
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpServiceExtensions, providedIn: 'root' }); }
111
+ }
112
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpServiceExtensions, decorators: [{
113
+ type: Injectable,
114
+ args: [{ providedIn: 'root' }]
115
115
  }] });
116
116
 
117
- class CacheInfos {
118
- constructor(finalUrl, cache, cacheKey, cacheTime, cacheItem) {
119
- this.finalUrl = finalUrl;
120
- this.cache = cache;
121
- this.cacheKey = cacheKey;
122
- this.cacheTime = cacheTime;
123
- this.cacheItem = cacheItem;
124
- }
117
+ class CacheInfos {
118
+ constructor(finalUrl, cache, cacheKey, cacheTime, cacheItem) {
119
+ this.finalUrl = finalUrl;
120
+ this.cache = cache;
121
+ this.cacheKey = cacheKey;
122
+ this.cacheTime = cacheTime;
123
+ this.cacheItem = cacheItem;
124
+ }
125
125
  }
126
126
 
127
- // Angular
128
- /**
129
- * Servizio utilizzato per effettuare qualsiasi tipo di chiamata HTTP dall'applicazione, gestisce errori e cache delle risposte nativamente e ne può essere
130
- * esteso/modificato il comportamento implementando la classe di "hook" **IHttpServiceExtensions** e registrandola nel provider della classe **HttpServiceExtensions**
131
- */
132
- class HTTPService {
133
- /**
134
- * @ignore
135
- */
136
- constructor(EXTENSION_CLASS, HTTP_INVALIDATION, CLIENT_CACHE_DURATION_S, CACHE_BY_USER, HTTP_DEBUG_MODE, bc, http, localStorage, hash, utisExts) {
137
- this.EXTENSION_CLASS = EXTENSION_CLASS;
138
- this.HTTP_INVALIDATION = HTTP_INVALIDATION;
139
- this.CLIENT_CACHE_DURATION_S = CLIENT_CACHE_DURATION_S;
140
- this.CACHE_BY_USER = CACHE_BY_USER;
141
- this.HTTP_DEBUG_MODE = HTTP_DEBUG_MODE;
142
- this.bc = bc;
143
- this.http = http;
144
- this.localStorage = localStorage;
145
- this.hash = hash;
146
- this.utisExts = utisExts;
147
- /** Cache dello stato delle chiamate HTTP attualmente effettuate. Ogni chiamata è rappresentata da un GUID e lo status assume valore **false** solo sugli errori */
148
- this.callStatus = {};
149
- /** Nome della cache in cui vengono storicizzate le informazioni sulle risposte delle chiamate sotto forma di dictionary */
150
- this.localStorageHttpCacheKey = null;
151
- /** Nome della cache in cui vengono storicizzate le informazioni sulle risposte delle chiamate sotto forma di dictionary, solo per le chiamate con un tempo definito di caching */
152
- this.localStorageHttpTimedCacheKey = null;
153
- /** Nome della cache in cui vengono storicizzate le informazioni sullo scadere della cache stessa */
154
- this.localStorageHttpCacheExpireKey = null;
155
- /** Identità collegata al token di autenticazione da usare, richiesta al programma che utilizza la libreria attraverso l'hook **retrieveIdentity** della **HttpServiceExtensions** */
156
- this.me = null;
157
- /** Indica che la cache a tempo dev'essere persistita il prima possibile */
158
- this.PersistTimedCacheASAP = false;
159
- /** Indica che la cache senza limiti di tempo dev'essere persistita il prima possibile */
160
- this.PersistUntimedCacheASAP = false;
161
- /** Subject emesso quando il servizio http si stabilizza, cioè quando non c'è nessuna chiamata pending o in corso da qualche istante */
162
- this.Stabilized = new Subject();
163
- /** Lista delle ultime 3 chiamate completate con successo in ordine di completamento (in posizione 0 c'è l'ultima completata eccetera...) */
164
- this.LastThreeCompletedCalls = [];
165
- this.CacheInitialized = false;
166
- /*****************************************************************************************************************************************
167
- ***************************************************** TRANSACTION HELPERS ***************************************************************
168
- *****************************************************************************************************************************************/
169
- this.GET_Trans_BeginTrans = "/api/Trans/BeginTrans";
170
- this.GET_Trans_CheckExecution = "/api/Trans/CheckExecution";
171
- this.GET_Trans_EndTrans = "/api/Trans/EndTrans";
172
- this.ExtensionsImpl = this.EXTENSION_CLASS || new HttpServiceExtensions();
173
- }
174
- setAppConfig(myconfig) {
175
- this.localStorageHttpCacheKey = (myconfig.AppData.ModuleDescription).replace(/\s/g, '_') + "_HttpCache";
176
- this.localStorageHttpTimedCacheKey = (myconfig.AppData.ModuleDescription).replace(/\s/g, '_') + "_HttpTimedCache";
177
- this.localStorageHttpCacheExpireKey = this.localStorageHttpCacheKey + "_Expire";
178
- this.adjustOldStorageSystem();
179
- this.CacheInitialized = true;
180
- }
181
- /**
182
- * Se vedo che ci sono elementi salvati nel localStorage provo a toglierli e a risalvarli via preference.
183
- * Nel caso migliore li sposto dal localStorage al DB di preference, nel caso peggiore li tolgo e li risalvo nel localStorage
184
- */
185
- async adjustOldStorageSystem() {
186
- var lsTimed = localStorage.getItem(this.localStorageHttpTimedCacheKey);
187
- var lsUntimed = localStorage.getItem(this.localStorageHttpCacheKey);
188
- if (!lsTimed && !lsUntimed)
189
- return;
190
- localStorage.removeItem(this.localStorageHttpTimedCacheKey);
191
- localStorage.removeItem(this.localStorageHttpCacheKey);
192
- lsTimed = lsTimed ? JSON.parse(lsTimed) : {};
193
- lsUntimed = lsUntimed ? JSON.parse(lsUntimed) : {};
194
- await forkJoin([
195
- this.localStorage.setItem(this.localStorageHttpTimedCacheKey, lsTimed),
196
- this.localStorage.setItem(this.localStorageHttpCacheKey, lsUntimed)
197
- ]).toPromise();
198
- this.log(this.loc("Converted old storage system to new one"));
199
- }
200
- /**
201
- * Esecuzione di una GET HTTP con eventualmente la gestione della cache qualora la chiamata termini con la convenzione "#cache"
202
- *
203
- * @param {string} url URL su cui effettuare la chiamata
204
- * @param {HttpParams} params Parametri della chiamata
205
- * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
206
- * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
207
- * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
208
- * @param {Function} errorCallback Callback chiamato in caso di errore
209
- *
210
- * @returns {Observable<T>} Observable che contiene il risultato della chiamata
211
- */
212
- get(url, params = null, transaction = "", succesfulResultMandatory = true, errorCallback = null) {
213
- return forkJoin({
214
- myGuid: from([this.defaultStartCall(url)]),
215
- locale: this.bc.Locale.pipe(first())
216
- }).pipe(concatMap((ret) => {
217
- let cachedInfos = this.tryGetCacheInformations(url, params);
218
- if (cachedInfos.cache && cachedInfos.cacheItem) {
219
- this.log(this.loc("Result for") + " " + url + " " + this.loc("retrieved from cache"));
220
- return of(cachedInfos.cacheItem).pipe(delay(0), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
221
- }
222
- var httpCall = this.http.get(cachedInfos.finalUrl, { headers: this.getHeaders(ret.locale, transaction), params: params });
223
- return httpCall.pipe(map((res) => {
224
- let responseItem = res;
225
- if (cachedInfos.cache) {
226
- this.cacheResponse(this.utisExts.deepClone(responseItem), cachedInfos);
227
- this.log(this.loc("Cached result for") + " " + url);
228
- }
229
- return responseItem;
230
- }), catchError((err, caught) => {
231
- if (errorCallback)
232
- errorCallback();
233
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
234
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid, cachedInfos); }));
235
- }));
236
- }
237
- /**
238
- * Esecuzione di una GET HTTP con eventualmente la gestione della cache qualora la chiamata termini con la convenzione "#cache"
239
- *
240
- * Caso specifico di chiamata che deve restituire un Blob o un ArrayBuffer nativo da elaborare
241
- *
242
- * @param {string} url URL su cui effettuare la chiamata
243
- * @param {HttpParams} params Parametri della chiamata
244
- * @param {"arraybuffer"| "blob"} type Tipo di ritorno richiesto
245
- * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
246
- * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
247
- * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
248
- * @param {Function} errorCallback Callback chiamato in caso di errore
249
- *
250
- * @returns {Observable<Blob | ArrayBuffer>} Observable che contiene il risultato della chiamata
251
- */
252
- getRaw(url, params = null, type = "arraybuffer", transaction = "", succesfulResultMandatory = true, errorCallback = null) {
253
- return forkJoin({
254
- myGuid: from([this.defaultStartCall(url)]),
255
- locale: this.bc.Locale.pipe(first())
256
- }).pipe(concatMap((ret) => {
257
- let cachedInfos = this.tryGetCacheInformations(url, params);
258
- if (cachedInfos.cache && cachedInfos.cacheItem) {
259
- this.log(this.loc("Result for") + " " + url + " " + this.loc("retrieved from cache"));
260
- return of(cachedInfos.cacheItem).pipe(delay(0), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
261
- }
262
- var httpCall = this.http.get(cachedInfos.finalUrl, { responseType: type, headers: this.getHeaders(ret.locale, transaction), params: params });
263
- return httpCall.pipe(map((blob) => {
264
- if (cachedInfos.cache) {
265
- this.cacheResponse(blob, cachedInfos);
266
- this.log(this.loc("Cached result for") + " " + url);
267
- }
268
- return blob;
269
- }), catchError((err, caught) => {
270
- if (errorCallback)
271
- errorCallback();
272
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
273
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid, cachedInfos); }));
274
- }));
275
- }
276
- /**
277
- * Esecuzione di una POST HTTP
278
- *
279
- * @param {string} url URL su cui effettuare la chiamata
280
- * @param {any} bodyData DTO per la chiamata
281
- * @param {HttpParams} params Parametri della chiamata
282
- * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
283
- * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
284
- * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
285
- *
286
- * @returns {Observable<T>} Observable che contiene il risultato della chiamata
287
- */
288
- post(url, bodyData, params = null, transaction = "", succesfulResultMandatory = true) {
289
- return forkJoin({
290
- myGuid: from([this.defaultStartCall(url)]),
291
- locale: this.bc.Locale.pipe(first())
292
- }).pipe(concatMap((ret) => {
293
- // Se è una ricerca, shallow copy e rimuovo gli "items" per evitare di farli andare su e giù al backend
294
- if (!!bodyData?.items && !!bodyData?.page) {
295
- bodyData = { ...bodyData };
296
- delete bodyData.items;
297
- }
298
- var httpCall = this.http.post(url, bodyData, { headers: this.getHeaders(ret.locale, transaction), params: params });
299
- return httpCall.pipe(catchError((err, _) => {
300
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
301
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
302
- }));
303
- }
304
- /**
305
- * Esecuzione di una DELETE HTTP
306
- *
307
- * @param {string} url URL su cui effettuare la chiamata
308
- * @param {HttpParams} params Parametri della chiamata
309
- * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
310
- * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
311
- * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
312
- *
313
- * @returns {Observable<T>} Observable che contiene il risultato della chiamata
314
- */
315
- delete(url, params = null, transaction = "", succesfulResultMandatory = true) {
316
- return forkJoin({
317
- myGuid: from([this.defaultStartCall(url)]),
318
- locale: this.bc.Locale.pipe(first())
319
- }).pipe(concatMap((ret) => {
320
- var httpCall = this.http.delete(url, { headers: this.getHeaders(ret.locale, transaction), params: params });
321
- return httpCall.pipe(catchError((err, caught) => {
322
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
323
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
324
- }));
325
- }
326
- /**
327
- * Esecuzione di una PUT HTTP
328
- *
329
- * @param {string} url URL su cui effettuare la chiamata
330
- * @param {any} bodyData DTO per la chiamata
331
- * @param {HttpParams} params Parametri della chiamata
332
- * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
333
- * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
334
- * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
335
- *
336
- * @returns {Observable<T>} Observable che contiene il risultato della chiamata
337
- */
338
- put(url, bodyData, params = null, transaction = "", succesfulResultMandatory = true) {
339
- return forkJoin({
340
- myGuid: from([this.defaultStartCall(url)]),
341
- locale: this.bc.Locale.pipe(first())
342
- }).pipe(concatMap((ret) => {
343
- var httpCall = this.http.put(url, bodyData, { headers: this.getHeaders(ret.locale, transaction), params: params });
344
- return httpCall.pipe(catchError((err, caught) => {
345
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
346
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
347
- }));
348
- }
349
- /**
350
- * Esecuzione di una POST HTTP
351
- *
352
- * Casistica particolare in cui bisogna anche inviare dei File
353
- *
354
- * @param {string} url URL su cui effettuare la chiamata
355
- * @param {any} bodyData DTO per la chiamata
356
- * @param {File[] | File} fileArrayOrFile Singolo file o lista di file da inviare. Il tipo **File** è quello nativo del Javascript
357
- *
358
- * @returns {Observable<T>} Observable che contiene il risultato della chiamata
359
- */
360
- postwithFiles(url, bodyData, fileArrayOrFile, params = null, succesfulResultMandatory = true, onError) {
361
- return forkJoin({
362
- myGuid: from([this.defaultStartCall(url)]),
363
- locale: this.bc.Locale.pipe(first())
364
- }).pipe(concatMap((ret) => {
365
- var formData = this.getFilesFormData(fileArrayOrFile);
366
- formData.append("body", JSON.stringify(bodyData));
367
- var httpCall = this.http.post(url, formData, { params: params, headers: this.getHeaders(ret.locale, null, true, null, null) });
368
- return httpCall.pipe(catchError((err, caught) => {
369
- onError?.();
370
- return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
371
- }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
372
- }));
373
- }
374
- /**
375
- * Esecuzione di una POST HTTP
376
- *
377
- * Casistica particolare in cui viene restituito un file gestito come ObjectURL il cui URL si può gestire dal parametro di callback **getFileCallback**.
378
- * La chiamata non ha un tipo restituito in quanto il risultato dev'essere gestito attraverso la callback **getFileCallback**
379
- *
380
- * @param {string} url URL su cui effettuare la chiamata
381
- * @param {K} bodyData DTO per la chiamata
382
- * @param {string} filename Nome del file che si sta richiedendo, rimane lato Frontend e serve per assegnare arbitrariamente un nome al file restituito, se vuoto viene utilizzata la Content-Disposition della risposta
383
- * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
384
- * @param {Function} afterGetFile Callback da chiamare dopo aver gestito la risposta del **getFileCallback**, in caso **handleError** fosse **false** verrà richiamato anche in caso di errore
385
- * @param {boolean} handleError Indica se di default gli errori devono essere gestiti dalla libreria o se devono essere ignorati
386
- */
387
- async postGetFile(url, bodyData, filename, getFileCallback, afterGetFile = null, handleError = true, pars) {
388
- let myGuid = this.defaultStartCall(url);
389
- let locale = await this.bc.Locale.pipe(first()).toPromise();
390
- this.http.post(url, bodyData, { params: pars, headers: this.getHeaders(locale), observe: 'response', responseType: "blob" }).pipe(catchError((err, caught) => {
391
- if (afterGetFile && !handleError)
392
- afterGetFile();
393
- return this.registerErrorAndReturnEmptyObservable(err, myGuid, handleError);
394
- }), finalize(() => { this.tryFinalizeByGuid(myGuid); })).subscribe((response) => {
395
- let retUrl = window.URL.createObjectURL(response.body);
396
- let fn = response.headers.get('content-disposition')?.split(';')[1]?.split('filename')[1]?.split('=')[1]?.trim().replace(/\"/g, '');
397
- getFileCallback(retUrl, filename || fn);
398
- if (afterGetFile)
399
- afterGetFile();
400
- });
401
- }
402
- /**
403
- * Esecuzione di una GET HTTP
404
- *
405
- * Casistica particolare in cui viene restituito un file gestito come ObjectURL il cui URL si può gestire dal parametro di callback **getFileCallback**.
406
- * La chiamata non ha un tipo restituito in quanto il risultato dev'essere gestito attraverso la callback **getFileCallback**
407
- *
408
- * @param {string} url URL su cui effettuare la chiamata
409
- * @param {HttpParams} params Parametri della chiamata
410
- * @param {string} filename Nome del file che si sta richiedendo, rimane lato Frontend e serve per assegnare arbitrariamente un nome al file restituito, se vuoto viene utilizzata la Content-Disposition della risposta
411
- * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
412
- * @param {Function} afterGetFile Callback da chiamare dopo aver gestito la risposta del **getFileCallback**, in caso **handleError** fosse **false** verrà richiamato anche in caso di errore
413
- * @param {boolean} handleError Indica se di default gli errori devono essere gestiti dalla libreria o se devono essere ignorati
414
- */
415
- async getFile(url, params = null, filename, getFileCallback, afterGetFile = null, handleError = true) {
416
- let myGuid = this.defaultStartCall(url);
417
- let locale = await this.bc.Locale.pipe(first()).toPromise();
418
- this.http.get(url, { params: params, headers: this.getHeaders(locale), observe: 'response', responseType: "blob" }).pipe(catchError((err, caught) => {
419
- if (afterGetFile && !handleError)
420
- afterGetFile();
421
- return this.registerErrorAndReturnEmptyObservable(err, myGuid, handleError);
422
- }), finalize(() => { this.tryFinalizeByGuid(myGuid); })).subscribe((response) => {
423
- let retUrl = window.URL.createObjectURL(response.body);
424
- let fn = response.headers.get('content-disposition')?.split(';')[1]?.split('filename')[1]?.split('=')[1]?.trim().replace(/\"/g, '');
425
- getFileCallback(retUrl, filename || fn);
426
- if (afterGetFile)
427
- afterGetFile();
428
- });
429
- }
430
- /**
431
- * Download nativo di un file. Internamente utilizza il pattern **window.location.href = "..."**
432
- *
433
- * @param {string} url URL su cui effettuare la chiamata
434
- * @param {HttpParams} params Parametri della chiamata
435
- */
436
- getFileNative(url, params = null) {
437
- // Encodo tutti i parametri per evitare casi particolari
438
- // var encodedParams = new HttpParams();
439
- // let parKeys = params.keys();
440
- // for (let i = 0; i < parKeys.length; i++)
441
- // encodedParams = encodedParams.set(parKeys[i], encodeURIComponent(params.get(parKeys[i])));
442
- let finalUrl = url + (params ? "?" + params.toString().replace(/\+/g, '%2b') : "");
443
- window.location.href = finalUrl;
444
- }
445
- /**
446
- * Esecuzione di una GET di un file che emette i vari eventi di progresso di download.
447
- * Utilizzarla per overridare la dialog nativa di progress del browser e sostituirla con qualcosa tipo quello che fa MEGA.
448
- *
449
- * In caso si voglia ottenere il risultato nativo del browser del download utilizzare **getFileNative(url, pars)**
450
- *
451
- * @param {string} url URL su cui effettuare la chiamata
452
- * @param {HttpParams} params Parametri della chiamata
453
- * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
454
- *
455
- * @returns {Observable<Download>} Observable dei vari step di progresso del download
456
- */
457
- getFileWithProgress(url, params = null, getFileCallback) {
458
- return forkJoin({
459
- myGuid: from([this.defaultStartCall(url)]),
460
- locale: this.bc.Locale.pipe(first())
461
- }).pipe(concatMap((ret) => {
462
- return this.http.get(url, { responseType: 'blob', headers: this.getHeaders(ret.locale), params: params, observe: 'events', reportProgress: true })
463
- .pipe(scan((previous, event) => {
464
- if (this.isHttpProgressEvent(event)) {
465
- let progress = event.total ? (event.loaded > event.total ? 100 : Math.round((100 * event.loaded) / event.total)) : previous.progress;
466
- return { progress: progress, state: 'IN_PROGRESS', content: null };
467
- }
468
- if (this.isHttpResponse(event)) {
469
- if (event.body) {
470
- let cdisp = event.headers.get('content-disposition');
471
- let cdispFilename = cdisp.split(';')[1].split('filename')[1].split('=')[1].trim().replace(/\"/g, '');
472
- let retUrl = window.URL.createObjectURL(event.body);
473
- getFileCallback(retUrl, cdispFilename);
474
- }
475
- return { progress: 100, state: 'DONE', content: event.body };
476
- }
477
- return previous;
478
- }, { state: 'PENDING', progress: 0, content: null }), catchError((err, _) => { return this.registerErrorAndReturnEmptyObservable(err, ret.myGuid); }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
479
- }));
480
- }
481
- /**
482
- * Riconosce se l'evento HTTP è di tipo **HttpResponse** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
483
- */
484
- isHttpResponse(event) {
485
- return event.type === HttpEventType.Response;
486
- }
487
- /**
488
- * Riconosce se l'evento HTTP è di tipo **HttpProgressEvent** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
489
- */
490
- isHttpProgressEvent(event) {
491
- return event.type === HttpEventType.DownloadProgress || event.type === HttpEventType.UploadProgress;
492
- }
493
- /**
494
- * Gestione dell'inizio di una chiamata ad un certo URL, viene generato un GUID da assegnarci, controllata la scadenza della cache,
495
- * e eventualmente loggato il fatto che sta per essere effettuata una chiamata HTTP
496
- *
497
- * @param {string} url Url a cui effettuare la chiamata
498
- * @returns {string} GUID assegnato alla chiamata
499
- */
500
- defaultStartCall(url) {
501
- let myGuid = this.getGUID();
502
- this.checkCacheInvalidation(url);
503
- this.callStatus[myGuid] = { url: url, success: true };
504
- this.log(this.loc("Starting call") + " " + myGuid + " " + this.loc("at URL") + ": " + url);
505
- return myGuid;
506
- }
507
- /**
508
- * Dato un url vengono controllate le informazioni su tutti gli url da invalidare collegati all'url chiamato e, se presenti in cache, invalidati
509
- *
510
- * @param {string} url URL per cui controllare se ci sono chiamate in cache da invalidare
511
- */
512
- checkCacheInvalidation(url) {
513
- if (!this.CacheInitialized)
514
- return;
515
- let cache = this.localStorage.getLocalItem(this.localStorageHttpCacheKey);
516
- let tmp = /#cache(:?-(\d+))/gi.exec(url);
517
- let cacheTime = tmp ? tmp[2] : null;
518
- let normalizedUrl_tmp = url.replace("#cache" + (cacheTime ? "-" + cacheTime : ""), "");
519
- let normalizedUrl = normalizedUrl_tmp.substring(normalizedUrl_tmp.indexOf('/api'));
520
- if (!cache || !this.HTTP_INVALIDATION || !this.HTTP_INVALIDATION[normalizedUrl])
521
- return;
522
- let apisToInvalidate = this.HTTP_INVALIDATION[normalizedUrl];
523
- if (!apisToInvalidate || apisToInvalidate.length == 0)
524
- return;
525
- let keysToRemove = [];
526
- apisToInvalidate.forEach(api => {
527
- let cacheKey = this.makeCacheKey(api, null);
528
- for (let item in cache)
529
- if (item.indexOf(cacheKey) != -1)
530
- keysToRemove.push(item);
531
- });
532
- for (let i = 0; i < keysToRemove.length; i++) {
533
- let k = keysToRemove[i];
534
- this.log(this.loc("Invlalidating cache element") + " " + k);
535
- delete cache[k];
536
- }
537
- this.localStorage.setItem(this.localStorageHttpCacheKey, cache);
538
- }
539
- /**
540
- * Finalizzazione di una chiamata
541
- *
542
- * @param {string} guid GUID collegato alla chiamata appena terminata
543
- * @param {boolean} persistCacheIfIdle Effettua il persist della cache HTTP solo se non ci sono altre chiamate in corso, per evitare
544
- * che mi cambi sotto l'oggetto di cache mentre sto salvando le informazioni
545
- */
546
- async tryFinalizeByGuid(guid, cacheInfos) {
547
- this.log(this.loc("Finalizing call") + " " + guid + " " + this.loc("with status") + ": " + (!this.callStatus[guid] ? 'Unknown' : this.callStatus[guid].success ? this.loc('Success') : this.loc('Failure')));
548
- // TODO: Perdere 3-4 ore per capire perché ogni tanto arriva null in casi pesanti di Observable spammati a random
549
- if (this.callStatus[guid]) {
550
- // Alla prima chiamata buona, significa che almeno il login ce l'ho. Posso mettermi in modalità redirectInProgress false
551
- // e riiniziare a valutare errori di javashit
552
- if (this.callStatus[guid].success && this.ExtensionsImpl.readRedirectionStatus())
553
- this.ExtensionsImpl.writeRedirectionStatus(false);
554
- // Aggiungo la chiamata alla lista delle ultime 3 completate
555
- this.LastThreeCompletedCalls.unshift(this.callStatus[guid].url);
556
- if (this.LastThreeCompletedCalls.length > 3)
557
- this.LastThreeCompletedCalls.pop();
558
- // In ogni caso questa posso rimuoverla
559
- delete this.callStatus[guid];
560
- }
561
- // Se non ho nessun'altra chiamata in corso considero la situazione stabilizzatasi
562
- if (Object.keys(this.callStatus).length == 0)
563
- this.Stabilized.next(true);
564
- // Se vengo da una chiamata che ha aggiornato la cache a timer o globale marco il fatto che quella cache dev'essere aggiornata
565
- if (cacheInfos && cacheInfos.cache) {
566
- this.PersistTimedCacheASAP = this.PersistTimedCacheASAP || !!cacheInfos.cacheTime;
567
- this.PersistUntimedCacheASAP = this.PersistTimedCacheASAP || !cacheInfos.cacheTime;
568
- this.log(this.loc("Marking cache to be persisted as soon as possible"));
569
- }
570
- // Se una delle due cache devono essere aggiornate e non ho nessuna chiamata in corso effettuo l'aggiornamento in base a quale delle due va aggiornata
571
- // Alla fine di tutto marco come fatto
572
- if ((this.PersistTimedCacheASAP || this.PersistUntimedCacheASAP) && Object.keys(this.callStatus).length == 0 && this.CacheInitialized) {
573
- var toCall = [];
574
- let tmpKey = "";
575
- if (this.PersistTimedCacheASAP) {
576
- tmpKey = this.localStorageHttpTimedCacheKey;
577
- toCall.push(this.localStorage.setItem(tmpKey, this.localStorage.getLocalItem(tmpKey)));
578
- }
579
- if (this.PersistUntimedCacheASAP) {
580
- tmpKey = this.localStorageHttpCacheKey;
581
- toCall.push(this.localStorage.setItem(tmpKey, this.localStorage.getLocalItem(tmpKey)));
582
- }
583
- this.log(this.loc("No more HTTP calls to be executed, entering idle state and persisting modified caches") + "...");
584
- var ret = await forkJoin(toCall).toPromise();
585
- if (ret)
586
- this.log(this.loc("Caches succesfully persisted"));
587
- else
588
- this.log(this.loc("Errors persisting modified caches"));
589
- this.PersistTimedCacheASAP = false;
590
- this.PersistUntimedCacheASAP = false;
591
- }
592
- }
593
- /**
594
- * Helper che dato un unico File o una lista di File crea un oggetto FormData che può essere inviato comodamente
595
- * ad un backend ASP.NET da cui si potrà accedere a Request.Form.Files
596
- *
597
- * @param {File | File[]} fileArrayOrFile Singolo File o lista di File. N.B: nella firma della funzione viene considerato **any** per fare test sulle proprietà
598
- * invece che fare test con l'**istanceof**
599
- *
600
- * @returns {FormData} FormData contenente tutti i file passati
601
- */
602
- getFilesFormData(fileArrayOrFile) {
603
- var formData = new FormData();
604
- if (fileArrayOrFile) {
605
- if (fileArrayOrFile.length)
606
- fileArrayOrFile.forEach(t => { formData.append("file", t, t.name); });
607
- else if (fileArrayOrFile.name)
608
- formData.append("files", fileArrayOrFile, fileArrayOrFile.name);
609
- }
610
- return formData;
611
- }
612
- /**
613
- * Helper che in base ai parametri disponibili genera gli Header HTTP per la chiamata da effettuare
614
- *
615
- * @param {string} contentType Indica se aggiungere agli header "Content-Type", valorizzato al valore del parametro
616
- * @param {string} accept Indica se aggiungere agli header "Accept", valorizzato al valore del parametro
617
- * @param {boolean} includeAuth Indica se aggiungere agli header "Authorization", valorizzato col token recuperato dall'hook **retrieveAccessToken** della classe di estensione
618
- * @param {string} transactionId Indica se aggiungere agli header "X-TX-ID", valorizzato al valore del parametro
619
- *
620
- * @returns {HttpHeaders} Header generati in base ai parametri
621
- */
622
- getHeaders(locale = 'it-IT', transactionId = "", includeAuth = true, contentType = "application/json", accept = "application/json") {
623
- var headers = new HttpHeaders();
624
- var authtoken = this.ExtensionsImpl.retrieveAccessToken();
625
- headers = headers.append("ngsw-bypass", "");
626
- if (contentType)
627
- headers = headers.append("Content-Type", contentType);
628
- if (transactionId)
629
- headers = headers.append("X-TX-ID", transactionId);
630
- if (authtoken && includeAuth)
631
- headers = headers.append("Authorization", "Bearer " + authtoken);
632
- if (accept)
633
- headers = headers.append("Accept", accept);
634
- headers = headers.append("Accept-Language", locale);
635
- return headers;
636
- }
637
- /**
638
- * Gestione di default degli errori
639
- *
640
- * @param {any} res Risultato della chiamata effettuata
641
- * @param {string} guid Identificativo GUID della chiamata effettuata
642
- */
643
- async onError(res, guid) {
644
- if (this.callStatus[guid])
645
- this.callStatus[guid].success = false;
646
- //Mi assicuro che tutti gli errori durante il redirect al login vengano ignorati
647
- if (this.ExtensionsImpl.readRedirectingToLogin()) {
648
- this.log(this.loc("Redirecting to Login, ignoring HTTP error on call") + " " + guid);
649
- return;
650
- }
651
- //Mi assicuro che venga gestito un solo errore per volta
652
- if (this.ExtensionsImpl.readHandlingErrorStatus()) {
653
- this.log(this.loc("Already Handling an error, ignoring HTTP error on call") + " " + guid);
654
- return;
655
- }
656
- else
657
- this.ExtensionsImpl.writeHandlingErrorStatus(true);
658
- let finalError = "";
659
- let finalStatus = "";
660
- if (this.isTypeError(res)) {
661
- finalError = res.message;
662
- finalStatus = "HTTP Service Internal Error";
663
- }
664
- else if (this.isHttpErrorResponse(res)) {
665
- finalError = await this.getHttpErrorText(res.error);
666
- finalStatus = res.status.toString();
667
- switch (res.status) {
668
- // Unauthorized
669
- case 401:
670
- if (this.ExtensionsImpl.readRedirectionStatus()) {
671
- this.log(this.loc("Error 401 on call") + " " + guid + " " + this.loc("with redirect already in progress. Ignoring this event"));
672
- return;
673
- }
674
- this.log(this.loc("Error 401 on call") + " " + guid + " " + this.loc("redirecting to login."));
675
- this.ExtensionsImpl.writeRedirectionStatus(true);
676
- this.ExtensionsImpl.writeRedirectingToLogin(true);
677
- this.ExtensionsImpl.redirectToLogin();
678
- return;
679
- // Handled exception
680
- case 400:
681
- // No Permissions
682
- case 403:
683
- this.ExtensionsImpl.handleBlockingException(res.status, finalError);
684
- return;
685
- // Not found
686
- case 404:
687
- this.ExtensionsImpl.handleBlockingException(res.status, this.loc("Resource not found"));
688
- return;
689
- // NotConfiguredException
690
- case 444:
691
- if (this.ExtensionsImpl.readRedirectionStatus())
692
- this.log(this.loc("Error 444 on call") + " " + guid + " " + this.loc("with redirect already in progress. Ignoring this event"));
693
- else if (!this.ExtensionsImpl.handleNotConfiguredException())
694
- this.ExtensionsImpl.handleBlockingException(res.status, finalError);
695
- return;
696
- case 0:
697
- finalError = `Generic connection error while calling '[${guid}] - ${this.callStatus[guid].url}' at ${dayjs().format()}. Response: ${JSON.stringify(res)}`;
698
- }
699
- }
700
- this.ExtensionsImpl.handleException(finalStatus, finalError);
701
- }
702
- /**
703
- * Dato un errore restituisce una promise con la stringa finale dell'errore in modo da poter fare un await
704
- *
705
- * @param {any} err Errore da controllare
706
- *
707
- * @returns {Promise<string>} Promise che risolve con la stringa finale dell'errore
708
- */
709
- getHttpErrorText(err) {
710
- // Se ho un Blob può essere o un CallResult o suppongo una semplice stringa,
711
- // altrimenti potrebbe essere direttamente un CallResult, oppure una semplice stringa
712
- return new Promise((resolve) => {
713
- if (err instanceof Blob) {
714
- let reader = new FileReader();
715
- reader.onload = (e) => {
716
- let errMsg = e.target.result;
717
- let jsonerrMsg;
718
- try {
719
- jsonerrMsg = JSON.parse(errMsg);
720
- }
721
- catch (e) { }
722
- return resolve(jsonerrMsg && this.isCallResult(jsonerrMsg) ? jsonerrMsg.error.message : errMsg);
723
- };
724
- reader.readAsText(err);
725
- }
726
- else if (this.isCallResult(err))
727
- return resolve(err.error.message);
728
- else
729
- return resolve(err?.toString());
730
- });
731
- }
732
- /**
733
- * Riconosce se l'oggetto è di tipo **CallResult** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
734
- *
735
- * @param {any} err Errore da controllare
736
- */
737
- isCallResult(err) {
738
- return !!err?.error?.message;
739
- }
740
- /**
741
- * Riconosce se l'errore è di tipo **HttpErrorResponse** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
742
- *
743
- * @param {any} err Errore da controllare
744
- */
745
- isHttpErrorResponse(err) {
746
- return err instanceof HttpErrorResponse;
747
- }
748
- /**
749
- * Riconosce se l'errore è di tipo **TypeError** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
750
- *
751
- * @param {any} err Errore da controllare
752
- */
753
- isTypeError(err) {
754
- return err.name == "TypeError";
755
- }
756
- /**
757
- * Come dice il nome, registra un errore e restituisce un Observable vuoto come risultato della chiamata
758
- *
759
- * @param {any} res Risultato della chiamata effettuata
760
- * @param {string} guid Identificativo GUID della chiamata effettuata
761
- * @param {boolean} handleError Indica se gestire l'errore via libreria o se limitarsi a restituire l'Observable vuoto
762
- *
763
- * @returns {Observable<any>} Observable vuoto
764
- */
765
- registerErrorAndReturnEmptyObservable(res, guid, handleError = true) {
766
- if (handleError)
767
- this.onError(res, guid);
768
- return new Observable(() => { });
769
- }
770
- /**
771
- * Dato un Url e dei parametri cerca di recuperare l'eventuale oggetto già salvato in cache con le informazioni base della cache stessa
772
- *
773
- * @param {string} url URL su cui effettuare la chiamata
774
- * @param {HttpParams} params Parametri della chiamata (nulli in caso di POST)
775
- *
776
- * @returns {CacheInfos} Oggetto contenente le informazioni sulla cache per l'oggetto specificato
777
- */
778
- tryGetCacheInformations(url, params) {
779
- let cachingKey = null, cacheTime = null, item = null;
780
- let useCaching = !!(/#cache/gi.exec(url));
781
- if (useCaching && this.CacheInitialized) {
782
- let tmp = /#cache(:?-(\d+))/gi.exec(url);
783
- cacheTime = tmp ? tmp[2] : null;
784
- url = url.replace("#cache" + (cacheTime ? "-" + cacheTime : ""), "");
785
- if (this.untimedCacheExpired() && !cacheTime)
786
- this.resetHttpUntimedCacheStorage();
787
- cachingKey = this.makeCacheKey(url, params);
788
- let cacheKey = cacheTime ? this.localStorageHttpTimedCacheKey : this.localStorageHttpCacheKey;
789
- let cacheLs = this.localStorage.getLocalItem(cacheKey);
790
- let cache = cacheLs ? cacheLs : {};
791
- if (cache && cache[cachingKey] && !this.timedCacheItemExpired(cache[cachingKey])) {
792
- // Se gestisco il tempo esatto della cache prima di restituire l'oggetto cachato lo ripulisco
793
- // dalle proprietà interne che gli ho aggiunto per controllarne la scadenza
794
- if (cacheTime)
795
- delete cache[cachingKey].__expire;
796
- item = cache[cachingKey];
797
- }
798
- }
799
- // Per assicurarmi che il rifeirmento degli oggetti sia diverso utilizzo il deepClone
800
- return new CacheInfos(url, useCaching && this.CacheInitialized, cachingKey, cacheTime, this.utisExts.deepClone(item));
801
- }
802
- /**
803
- * Inserisce un oggetto nella cache appropriata in base alle informazioni contenute nel parametro **cacheInfos**
804
- *
805
- * @param {T} item Oggetto da inserire nella cache
806
- * @param {CacheInfos} cacheInfos Informazioni sulla cache recuperate da una chiamata precedente a **tryGetCacheInformations**
807
- */
808
- cacheResponse(item, cacheInfos) {
809
- let cacheKey = cacheInfos.cacheTime ? this.localStorageHttpTimedCacheKey : this.localStorageHttpCacheKey;
810
- let cacheLs = this.localStorage.getLocalItem(cacheKey);
811
- let cache = cacheLs ? cacheLs : {};
812
- cache[cacheInfos.cacheKey] = item;
813
- if (cacheInfos.cacheTime)
814
- cache[cacheInfos.cacheKey].__expire = dayjs().add(cacheInfos.cacheTime, "seconds");
815
- this.localStorage.setLocalItem(cacheKey, cache);
816
- }
817
- /**
818
- * Controlla se la cache per le chiamate senza expire time è scaduta
819
- *
820
- * @returns {boolean} **true** se la cache è scaduta e dev'essere ripulita, **false** altrimenti
821
- */
822
- untimedCacheExpired() {
823
- let expire = JSON.parse(localStorage.getItem(this.localStorageHttpCacheExpireKey));
824
- return !expire || expire && dayjs(expire) < dayjs();
825
- }
826
- /**
827
- * Controlla se la cache per una chiamata che ha un tempo di vita ben definito è scaduta o meno
828
- *
829
- * @param {any} item Oggetto recuperato dalla cache a cui ho aggiunto la chiave interna __expire
830
- *
831
- * @returns {boolean} **true** se la cache è scaduta e dev'essere ripulita, **false** altrimenti
832
- */
833
- timedCacheItemExpired(item) {
834
- return dayjs(item.__expire) < dayjs();
835
- }
836
- /**
837
- * Ripulisce la cache e imposta la prossima scadenza ad adesso + **CLIENT_CACHE_DURATION_S** (token valorizzato in fase di configurazione dalla proprietà **clientCacheDurationS** della classe **HttpServiceModuleConfig**)
838
- */
839
- async resetHttpUntimedCacheStorage() {
840
- if (!this.CacheInitialized)
841
- return;
842
- await this.localStorage.setItem(this.localStorageHttpCacheKey, {}).toPromise();
843
- localStorage.setItem(this.localStorageHttpCacheExpireKey, JSON.stringify(dayjs().add(this.CLIENT_CACHE_DURATION_S, "second")));
844
- }
845
- /**
846
- * Ripulisce la cache delle chiamate con un tempo di expire definito
847
- */
848
- async resetHttpTimedCacheStorage() {
849
- if (!this.CacheInitialized)
850
- return;
851
- await this.localStorage.setItem(this.localStorageHttpTimedCacheKey, {}).toPromise();
852
- }
853
- /**
854
- * Genera una chiave per la cache delle chiamate in base ad URL e Parametri HTTP
855
- *
856
- * @param {string} url URL su cui effettuare la chiamata
857
- * @param {HttpParams} params Parametri della chiamata
858
- *
859
- * @returns {string} Chiave che rappresenta in maniera univoca la chiamata
860
- */
861
- makeCacheKey(url, params) {
862
- this.me = this.me || this.ExtensionsImpl.retrieveIdentity();
863
- let authKeyPart = (this.me ? this.me.Idtenant : "") + (this.me && this.CACHE_BY_USER ? this.me.IdUser : "");
864
- let paramsKeyPart = params && params.keys().length > 0 ? params.keys().reduce((prev, next) => prev + "," + next + '=' + params.get(next), "") : "";
865
- return authKeyPart + "_" + url.substring(url.indexOf("api") + 4) + "?" + paramsKeyPart;
866
- }
867
- /**
868
- * Generazione GUID
869
- *
870
- * @returns {string} Nuovo GUID generato in maniera casuale
871
- */
872
- getGUID() {
873
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
874
- var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
875
- return v.toString(16);
876
- });
877
- }
878
- /**
879
- * Metodo per scrivere log a console qualora **DEBUG_MODE** risultasse **true**
880
- *
881
- * @param {string} message Messaggio di log
882
- */
883
- log(message) {
884
- if (this.HTTP_DEBUG_MODE)
885
- console.log("@httpservice: " + message);
886
- }
887
- /**
888
- * Helper per la localizzazione dei messaggi
889
- *
890
- * @param {string} message Messaggio da localizzare
891
- *
892
- * @returns {string} Messaggio localizzato
893
- */
894
- loc(message) {
895
- // Togliamo velocemente ste localizzazioni inutili....
896
- return message;
897
- }
898
- /**
899
- * Helper per lanciare la selezione di tutto quello che è relativo a un api di ricerca con AppSearch (di cui non utilizzo il tipo vero per ridurre le dipendenze)
900
- */
901
- getAll(view, api, pars, transaction) {
902
- if (!view.selection.all)
903
- return of(view.selection.items);
904
- var jsonView = this.utisExts.deepClone(view);
905
- jsonView.page = 1;
906
- jsonView.itemsperpageoverride = 999999;
907
- return this.post(api, jsonView, pars, transaction).pipe(map(res => {
908
- let items = res.items;
909
- items.forEach(t => t.hash = this.hash.hashObject(t, null, false));
910
- if (res.selection.exclusions?.length > 0)
911
- items = items.filter((t) => !res.selection.exclusions.find((e) => this.hash.hashObject(e, null, false) == t.hash));
912
- return items;
913
- }));
914
- }
915
- beginTransaction(executionKey, secondsTimeout) {
916
- this.log("beginTransaction call received: " + executionKey);
917
- var pars = new HttpParams().set("executionKey", executionKey);
918
- if (secondsTimeout)
919
- pars = pars.set("secondsTimeout", secondsTimeout.toString());
920
- return this.get(this.GET_Trans_BeginTrans, pars);
921
- }
922
- checkTransaction(executionKey) {
923
- this.log("checkTransaction call received: " + executionKey);
924
- return this.get(this.GET_Trans_CheckExecution, new HttpParams().set("executionKey", executionKey));
925
- }
926
- endTransaction(transactionKey, transactionResult) {
927
- this.log("endTransaction call received: " + transactionKey + " Commit: " + transactionResult);
928
- let pars = new HttpParams().set("idtrans", transactionKey).set("success", transactionResult.toString());
929
- return this.get(this.GET_Trans_EndTrans, pars);
930
- }
931
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HTTPService, deps: [{ token: HttpServiceExtensions }, { token: HTTP_INVALIDATION, optional: true }, { token: CLIENT_CACHE_DURATION_S }, { token: CACHE_BY_USER }, { token: HTTP_DEBUG_MODE }, { token: i2.BaseLocalization }, { token: i3.HttpClient }, { token: i4.CacheService }, { token: i5.HashingService }, { token: i5.UtilityService }], target: i0.ɵɵFactoryTarget.Injectable }); }
932
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HTTPService, providedIn: 'root' }); }
933
- }
934
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HTTPService, decorators: [{
935
- type: Injectable,
936
- args: [{ providedIn: 'root' }]
937
- }], ctorParameters: function () { return [{ type: HttpServiceExtensions }, { type: undefined, decorators: [{
938
- type: Optional
939
- }, {
940
- type: Inject,
941
- args: [HTTP_INVALIDATION]
942
- }] }, { type: undefined, decorators: [{
943
- type: Inject,
944
- args: [CLIENT_CACHE_DURATION_S]
945
- }] }, { type: undefined, decorators: [{
946
- type: Inject,
947
- args: [CACHE_BY_USER]
948
- }] }, { type: undefined, decorators: [{
949
- type: Inject,
950
- args: [HTTP_DEBUG_MODE]
951
- }] }, { type: i2.BaseLocalization }, { type: i3.HttpClient }, { type: i4.CacheService }, { type: i5.HashingService }, { type: i5.UtilityService }]; } });
127
+ // Angular
128
+ /**
129
+ * Servizio utilizzato per effettuare qualsiasi tipo di chiamata HTTP dall'applicazione, gestisce errori e cache delle risposte nativamente e ne può essere
130
+ * esteso/modificato il comportamento implementando la classe di "hook" **IHttpServiceExtensions** e registrandola nel provider della classe **HttpServiceExtensions**
131
+ */
132
+ class HTTPService {
133
+ /**
134
+ * @ignore
135
+ */
136
+ constructor(EXTENSION_CLASS, HTTP_INVALIDATION, CLIENT_CACHE_DURATION_S, CACHE_BY_USER, HTTP_DEBUG_MODE, bc, http, localStorage, hash, utisExts) {
137
+ this.EXTENSION_CLASS = EXTENSION_CLASS;
138
+ this.HTTP_INVALIDATION = HTTP_INVALIDATION;
139
+ this.CLIENT_CACHE_DURATION_S = CLIENT_CACHE_DURATION_S;
140
+ this.CACHE_BY_USER = CACHE_BY_USER;
141
+ this.HTTP_DEBUG_MODE = HTTP_DEBUG_MODE;
142
+ this.bc = bc;
143
+ this.http = http;
144
+ this.localStorage = localStorage;
145
+ this.hash = hash;
146
+ this.utisExts = utisExts;
147
+ /** Cache dello stato delle chiamate HTTP attualmente effettuate. Ogni chiamata è rappresentata da un GUID e lo status assume valore **false** solo sugli errori */
148
+ this.callStatus = {};
149
+ /** Nome della cache in cui vengono storicizzate le informazioni sulle risposte delle chiamate sotto forma di dictionary */
150
+ this.localStorageHttpCacheKey = null;
151
+ /** Nome della cache in cui vengono storicizzate le informazioni sulle risposte delle chiamate sotto forma di dictionary, solo per le chiamate con un tempo definito di caching */
152
+ this.localStorageHttpTimedCacheKey = null;
153
+ /** Nome della cache in cui vengono storicizzate le informazioni sullo scadere della cache stessa */
154
+ this.localStorageHttpCacheExpireKey = null;
155
+ /** Identità collegata al token di autenticazione da usare, richiesta al programma che utilizza la libreria attraverso l'hook **retrieveIdentity** della **HttpServiceExtensions** */
156
+ this.me = null;
157
+ /** Indica che la cache a tempo dev'essere persistita il prima possibile */
158
+ this.PersistTimedCacheASAP = false;
159
+ /** Indica che la cache senza limiti di tempo dev'essere persistita il prima possibile */
160
+ this.PersistUntimedCacheASAP = false;
161
+ /** Subject emesso quando il servizio http si stabilizza, cioè quando non c'è nessuna chiamata pending o in corso da qualche istante */
162
+ this.Stabilized = new Subject();
163
+ /** Lista delle ultime 3 chiamate completate con successo in ordine di completamento (in posizione 0 c'è l'ultima completata eccetera...) */
164
+ this.LastThreeCompletedCalls = [];
165
+ this.CacheInitialized = false;
166
+ /*****************************************************************************************************************************************
167
+ ***************************************************** TRANSACTION HELPERS ***************************************************************
168
+ *****************************************************************************************************************************************/
169
+ this.GET_Trans_BeginTrans = "/api/Trans/BeginTrans";
170
+ this.GET_Trans_CheckExecution = "/api/Trans/CheckExecution";
171
+ this.GET_Trans_EndTrans = "/api/Trans/EndTrans";
172
+ this.ExtensionsImpl = this.EXTENSION_CLASS || new HttpServiceExtensions();
173
+ }
174
+ setAppConfig(myconfig) {
175
+ this.localStorageHttpCacheKey = (myconfig.AppData.ModuleDescription).replace(/\s/g, '_') + "_HttpCache";
176
+ this.localStorageHttpTimedCacheKey = (myconfig.AppData.ModuleDescription).replace(/\s/g, '_') + "_HttpTimedCache";
177
+ this.localStorageHttpCacheExpireKey = this.localStorageHttpCacheKey + "_Expire";
178
+ this.adjustOldStorageSystem();
179
+ this.CacheInitialized = true;
180
+ }
181
+ /**
182
+ * Se vedo che ci sono elementi salvati nel localStorage provo a toglierli e a risalvarli via preference.
183
+ * Nel caso migliore li sposto dal localStorage al DB di preference, nel caso peggiore li tolgo e li risalvo nel localStorage
184
+ */
185
+ async adjustOldStorageSystem() {
186
+ var lsTimed = localStorage.getItem(this.localStorageHttpTimedCacheKey);
187
+ var lsUntimed = localStorage.getItem(this.localStorageHttpCacheKey);
188
+ if (!lsTimed && !lsUntimed)
189
+ return;
190
+ localStorage.removeItem(this.localStorageHttpTimedCacheKey);
191
+ localStorage.removeItem(this.localStorageHttpCacheKey);
192
+ lsTimed = lsTimed ? JSON.parse(lsTimed) : {};
193
+ lsUntimed = lsUntimed ? JSON.parse(lsUntimed) : {};
194
+ await forkJoin([
195
+ this.localStorage.setItem(this.localStorageHttpTimedCacheKey, lsTimed),
196
+ this.localStorage.setItem(this.localStorageHttpCacheKey, lsUntimed)
197
+ ]).toPromise();
198
+ this.log(this.loc("Converted old storage system to new one"));
199
+ }
200
+ /**
201
+ * Esecuzione di una GET HTTP con eventualmente la gestione della cache qualora la chiamata termini con la convenzione "#cache"
202
+ *
203
+ * @param {string} url URL su cui effettuare la chiamata
204
+ * @param {HttpParams} params Parametri della chiamata
205
+ * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
206
+ * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
207
+ * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
208
+ * @param {Function} errorCallback Callback chiamato in caso di errore
209
+ *
210
+ * @returns {Observable<T>} Observable che contiene il risultato della chiamata
211
+ */
212
+ get(url, params = null, transaction = "", succesfulResultMandatory = true, errorCallback = null) {
213
+ return forkJoin({
214
+ myGuid: from([this.defaultStartCall(url)]),
215
+ locale: this.bc.Locale.pipe(first())
216
+ }).pipe(concatMap((ret) => {
217
+ let cachedInfos = this.tryGetCacheInformations(url, params);
218
+ if (cachedInfos.cache && cachedInfos.cacheItem) {
219
+ this.log(this.loc("Result for") + " " + url + " " + this.loc("retrieved from cache"));
220
+ return of(cachedInfos.cacheItem).pipe(delay(0), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
221
+ }
222
+ var httpCall = this.http.get(cachedInfos.finalUrl, { headers: this.getHeaders(ret.locale, transaction), params: params });
223
+ return httpCall.pipe(map((res) => {
224
+ let responseItem = res;
225
+ if (cachedInfos.cache) {
226
+ this.cacheResponse(this.utisExts.deepClone(responseItem), cachedInfos);
227
+ this.log(this.loc("Cached result for") + " " + url);
228
+ }
229
+ return responseItem;
230
+ }), catchError((err, caught) => {
231
+ if (errorCallback)
232
+ errorCallback();
233
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
234
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid, cachedInfos); }));
235
+ }));
236
+ }
237
+ /**
238
+ * Esecuzione di una GET HTTP con eventualmente la gestione della cache qualora la chiamata termini con la convenzione "#cache"
239
+ *
240
+ * Caso specifico di chiamata che deve restituire un Blob o un ArrayBuffer nativo da elaborare
241
+ *
242
+ * @param {string} url URL su cui effettuare la chiamata
243
+ * @param {HttpParams} params Parametri della chiamata
244
+ * @param {"arraybuffer"| "blob"} type Tipo di ritorno richiesto
245
+ * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
246
+ * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
247
+ * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
248
+ * @param {Function} errorCallback Callback chiamato in caso di errore
249
+ *
250
+ * @returns {Observable<Blob | ArrayBuffer>} Observable che contiene il risultato della chiamata
251
+ */
252
+ getRaw(url, params = null, type = "arraybuffer", transaction = "", succesfulResultMandatory = true, errorCallback = null) {
253
+ return forkJoin({
254
+ myGuid: from([this.defaultStartCall(url)]),
255
+ locale: this.bc.Locale.pipe(first())
256
+ }).pipe(concatMap((ret) => {
257
+ let cachedInfos = this.tryGetCacheInformations(url, params);
258
+ if (cachedInfos.cache && cachedInfos.cacheItem) {
259
+ this.log(this.loc("Result for") + " " + url + " " + this.loc("retrieved from cache"));
260
+ return of(cachedInfos.cacheItem).pipe(delay(0), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
261
+ }
262
+ var httpCall = this.http.get(cachedInfos.finalUrl, { responseType: type, headers: this.getHeaders(ret.locale, transaction), params: params });
263
+ return httpCall.pipe(map((blob) => {
264
+ if (cachedInfos.cache) {
265
+ this.cacheResponse(blob, cachedInfos);
266
+ this.log(this.loc("Cached result for") + " " + url);
267
+ }
268
+ return blob;
269
+ }), catchError((err, caught) => {
270
+ if (errorCallback)
271
+ errorCallback();
272
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
273
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid, cachedInfos); }));
274
+ }));
275
+ }
276
+ /**
277
+ * Esecuzione di una POST HTTP
278
+ *
279
+ * @param {string} url URL su cui effettuare la chiamata
280
+ * @param {any} bodyData DTO per la chiamata
281
+ * @param {HttpParams} params Parametri della chiamata
282
+ * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
283
+ * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
284
+ * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
285
+ *
286
+ * @returns {Observable<T>} Observable che contiene il risultato della chiamata
287
+ */
288
+ post(url, bodyData, params = null, transaction = "", succesfulResultMandatory = true) {
289
+ return forkJoin({
290
+ myGuid: from([this.defaultStartCall(url)]),
291
+ locale: this.bc.Locale.pipe(first())
292
+ }).pipe(concatMap((ret) => {
293
+ // Se è una ricerca, shallow copy e rimuovo gli "items" per evitare di farli andare su e giù al backend
294
+ if (!!bodyData?.items && !!bodyData?.page) {
295
+ bodyData = { ...bodyData };
296
+ delete bodyData.items;
297
+ }
298
+ var httpCall = this.http.post(url, bodyData, { headers: this.getHeaders(ret.locale, transaction), params: params });
299
+ return httpCall.pipe(catchError((err, _) => {
300
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
301
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
302
+ }));
303
+ }
304
+ /**
305
+ * Esecuzione di una DELETE HTTP
306
+ *
307
+ * @param {string} url URL su cui effettuare la chiamata
308
+ * @param {HttpParams} params Parametri della chiamata
309
+ * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
310
+ * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
311
+ * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
312
+ *
313
+ * @returns {Observable<T>} Observable che contiene il risultato della chiamata
314
+ */
315
+ delete(url, params = null, transaction = "", succesfulResultMandatory = true) {
316
+ return forkJoin({
317
+ myGuid: from([this.defaultStartCall(url)]),
318
+ locale: this.bc.Locale.pipe(first())
319
+ }).pipe(concatMap((ret) => {
320
+ var httpCall = this.http.delete(url, { headers: this.getHeaders(ret.locale, transaction), params: params });
321
+ return httpCall.pipe(catchError((err, caught) => {
322
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
323
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
324
+ }));
325
+ }
326
+ /**
327
+ * Esecuzione di una PUT HTTP
328
+ *
329
+ * @param {string} url URL su cui effettuare la chiamata
330
+ * @param {any} bodyData DTO per la chiamata
331
+ * @param {HttpParams} params Parametri della chiamata
332
+ * @param {string} transaction Codice transazione collegato a questa chiamata. Viene inserito nell'Header "X-TX-ID" e lato Backend dovrà essere gestito di conseguenza
333
+ * @param {boolean} succesfulResultMandatory Indica che questa chiamata deve assolutamente andare bene. In caso contrario viene fisicamente fatto il throw che può essere
334
+ * raccolto da chi usa la libreria nella pipe "onError" dell'Observable restituito ed essere gestito in maniera non standard
335
+ *
336
+ * @returns {Observable<T>} Observable che contiene il risultato della chiamata
337
+ */
338
+ put(url, bodyData, params = null, transaction = "", succesfulResultMandatory = true) {
339
+ return forkJoin({
340
+ myGuid: from([this.defaultStartCall(url)]),
341
+ locale: this.bc.Locale.pipe(first())
342
+ }).pipe(concatMap((ret) => {
343
+ var httpCall = this.http.put(url, bodyData, { headers: this.getHeaders(ret.locale, transaction), params: params });
344
+ return httpCall.pipe(catchError((err, caught) => {
345
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
346
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
347
+ }));
348
+ }
349
+ /**
350
+ * Esecuzione di una POST HTTP
351
+ *
352
+ * Casistica particolare in cui bisogna anche inviare dei File
353
+ *
354
+ * @param {string} url URL su cui effettuare la chiamata
355
+ * @param {any} bodyData DTO per la chiamata
356
+ * @param {File[] | File} fileArrayOrFile Singolo file o lista di file da inviare. Il tipo **File** è quello nativo del Javascript
357
+ *
358
+ * @returns {Observable<T>} Observable che contiene il risultato della chiamata
359
+ */
360
+ postwithFiles(url, bodyData, fileArrayOrFile, params = null, succesfulResultMandatory = true, onError) {
361
+ return forkJoin({
362
+ myGuid: from([this.defaultStartCall(url)]),
363
+ locale: this.bc.Locale.pipe(first())
364
+ }).pipe(concatMap((ret) => {
365
+ var formData = this.getFilesFormData(fileArrayOrFile);
366
+ formData.append("body", JSON.stringify(bodyData));
367
+ var httpCall = this.http.post(url, formData, { params: params, headers: this.getHeaders(ret.locale, null, true, null, null) });
368
+ return httpCall.pipe(catchError((err, caught) => {
369
+ onError?.();
370
+ return !succesfulResultMandatory ? throwError(err) : this.registerErrorAndReturnEmptyObservable(err, ret.myGuid);
371
+ }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
372
+ }));
373
+ }
374
+ /**
375
+ * Esecuzione di una POST HTTP
376
+ *
377
+ * Casistica particolare in cui viene restituito un file gestito come ObjectURL il cui URL si può gestire dal parametro di callback **getFileCallback**.
378
+ * La chiamata non ha un tipo restituito in quanto il risultato dev'essere gestito attraverso la callback **getFileCallback**
379
+ *
380
+ * @param {string} url URL su cui effettuare la chiamata
381
+ * @param {K} bodyData DTO per la chiamata
382
+ * @param {string} filename Nome del file che si sta richiedendo, rimane lato Frontend e serve per assegnare arbitrariamente un nome al file restituito, se vuoto viene utilizzata la Content-Disposition della risposta
383
+ * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
384
+ * @param {Function} afterGetFile Callback da chiamare dopo aver gestito la risposta del **getFileCallback**, in caso **handleError** fosse **false** verrà richiamato anche in caso di errore
385
+ * @param {boolean} handleError Indica se di default gli errori devono essere gestiti dalla libreria o se devono essere ignorati
386
+ */
387
+ async postGetFile(url, bodyData, filename, getFileCallback, afterGetFile = null, handleError = true, pars) {
388
+ let myGuid = this.defaultStartCall(url);
389
+ let locale = await this.bc.Locale.pipe(first()).toPromise();
390
+ this.http.post(url, bodyData, { params: pars, headers: this.getHeaders(locale), observe: 'response', responseType: "blob" }).pipe(catchError((err, caught) => {
391
+ if (afterGetFile && !handleError)
392
+ afterGetFile();
393
+ return this.registerErrorAndReturnEmptyObservable(err, myGuid, handleError);
394
+ }), finalize(() => { this.tryFinalizeByGuid(myGuid); })).subscribe((response) => {
395
+ let retUrl = window.URL.createObjectURL(response.body);
396
+ let fn = response.headers.get('content-disposition')?.split(';')[1]?.split('filename')[1]?.split('=')[1]?.trim().replace(/\"/g, '');
397
+ getFileCallback(retUrl, filename || fn);
398
+ if (afterGetFile)
399
+ afterGetFile();
400
+ });
401
+ }
402
+ /**
403
+ * Esecuzione di una GET HTTP
404
+ *
405
+ * Casistica particolare in cui viene restituito un file gestito come ObjectURL il cui URL si può gestire dal parametro di callback **getFileCallback**.
406
+ * La chiamata non ha un tipo restituito in quanto il risultato dev'essere gestito attraverso la callback **getFileCallback**
407
+ *
408
+ * @param {string} url URL su cui effettuare la chiamata
409
+ * @param {HttpParams} params Parametri della chiamata
410
+ * @param {string} filename Nome del file che si sta richiedendo, rimane lato Frontend e serve per assegnare arbitrariamente un nome al file restituito, se vuoto viene utilizzata la Content-Disposition della risposta
411
+ * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
412
+ * @param {Function} afterGetFile Callback da chiamare dopo aver gestito la risposta del **getFileCallback**, in caso **handleError** fosse **false** verrà richiamato anche in caso di errore
413
+ * @param {boolean} handleError Indica se di default gli errori devono essere gestiti dalla libreria o se devono essere ignorati
414
+ */
415
+ async getFile(url, params = null, filename, getFileCallback, afterGetFile = null, handleError = true) {
416
+ let myGuid = this.defaultStartCall(url);
417
+ let locale = await this.bc.Locale.pipe(first()).toPromise();
418
+ this.http.get(url, { params: params, headers: this.getHeaders(locale), observe: 'response', responseType: "blob" }).pipe(catchError((err, caught) => {
419
+ if (afterGetFile && !handleError)
420
+ afterGetFile();
421
+ return this.registerErrorAndReturnEmptyObservable(err, myGuid, handleError);
422
+ }), finalize(() => { this.tryFinalizeByGuid(myGuid); })).subscribe((response) => {
423
+ let retUrl = window.URL.createObjectURL(response.body);
424
+ let fn = response.headers.get('content-disposition')?.split(';')[1]?.split('filename')[1]?.split('=')[1]?.trim().replace(/\"/g, '');
425
+ getFileCallback(retUrl, filename || fn);
426
+ if (afterGetFile)
427
+ afterGetFile();
428
+ });
429
+ }
430
+ /**
431
+ * Download nativo di un file. Internamente utilizza il pattern **window.location.href = "..."**
432
+ *
433
+ * @param {string} url URL su cui effettuare la chiamata
434
+ * @param {HttpParams} params Parametri della chiamata
435
+ */
436
+ getFileNative(url, params = null) {
437
+ // Encodo tutti i parametri per evitare casi particolari
438
+ // var encodedParams = new HttpParams();
439
+ // let parKeys = params.keys();
440
+ // for (let i = 0; i < parKeys.length; i++)
441
+ // encodedParams = encodedParams.set(parKeys[i], encodeURIComponent(params.get(parKeys[i])));
442
+ let finalUrl = url + (params ? "?" + params.toString().replace(/\+/g, '%2b') : "");
443
+ window.location.href = finalUrl;
444
+ }
445
+ /**
446
+ * Esecuzione di una GET di un file che emette i vari eventi di progresso di download.
447
+ * Utilizzarla per overridare la dialog nativa di progress del browser e sostituirla con qualcosa tipo quello che fa MEGA.
448
+ *
449
+ * In caso si voglia ottenere il risultato nativo del browser del download utilizzare **getFileNative(url, pars)**
450
+ *
451
+ * @param {string} url URL su cui effettuare la chiamata
452
+ * @param {HttpParams} params Parametri della chiamata
453
+ * @param {Function} getFileCallback Callback a due parametri (url: string, filename: string) per gestire il risultato del file richiesto. **Utilizzare UtilityService.fileDownload** di @esfaenza/extensions
454
+ *
455
+ * @returns {Observable<Download>} Observable dei vari step di progresso del download
456
+ */
457
+ getFileWithProgress(url, params = null, getFileCallback) {
458
+ return forkJoin({
459
+ myGuid: from([this.defaultStartCall(url)]),
460
+ locale: this.bc.Locale.pipe(first())
461
+ }).pipe(concatMap((ret) => {
462
+ return this.http.get(url, { responseType: 'blob', headers: this.getHeaders(ret.locale), params: params, observe: 'events', reportProgress: true })
463
+ .pipe(scan((previous, event) => {
464
+ if (this.isHttpProgressEvent(event)) {
465
+ let progress = event.total ? (event.loaded > event.total ? 100 : Math.round((100 * event.loaded) / event.total)) : previous.progress;
466
+ return { progress: progress, state: 'IN_PROGRESS', content: null };
467
+ }
468
+ if (this.isHttpResponse(event)) {
469
+ if (event.body) {
470
+ let cdisp = event.headers.get('content-disposition');
471
+ let cdispFilename = cdisp.split(';')[1].split('filename')[1].split('=')[1].trim().replace(/\"/g, '');
472
+ let retUrl = window.URL.createObjectURL(event.body);
473
+ getFileCallback(retUrl, cdispFilename);
474
+ }
475
+ return { progress: 100, state: 'DONE', content: event.body };
476
+ }
477
+ return previous;
478
+ }, { state: 'PENDING', progress: 0, content: null }), catchError((err, _) => { return this.registerErrorAndReturnEmptyObservable(err, ret.myGuid); }), finalize(() => { this.tryFinalizeByGuid(ret.myGuid); }));
479
+ }));
480
+ }
481
+ /**
482
+ * Riconosce se l'evento HTTP è di tipo **HttpResponse** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
483
+ */
484
+ isHttpResponse(event) {
485
+ return event.type === HttpEventType.Response;
486
+ }
487
+ /**
488
+ * Riconosce se l'evento HTTP è di tipo **HttpProgressEvent** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
489
+ */
490
+ isHttpProgressEvent(event) {
491
+ return event.type === HttpEventType.DownloadProgress || event.type === HttpEventType.UploadProgress;
492
+ }
493
+ /**
494
+ * Gestione dell'inizio di una chiamata ad un certo URL, viene generato un GUID da assegnarci, controllata la scadenza della cache,
495
+ * e eventualmente loggato il fatto che sta per essere effettuata una chiamata HTTP
496
+ *
497
+ * @param {string} url Url a cui effettuare la chiamata
498
+ * @returns {string} GUID assegnato alla chiamata
499
+ */
500
+ defaultStartCall(url) {
501
+ let myGuid = this.getGUID();
502
+ this.checkCacheInvalidation(url);
503
+ this.callStatus[myGuid] = { url: url, success: true };
504
+ this.log(this.loc("Starting call") + " " + myGuid + " " + this.loc("at URL") + ": " + url);
505
+ return myGuid;
506
+ }
507
+ /**
508
+ * Dato un url vengono controllate le informazioni su tutti gli url da invalidare collegati all'url chiamato e, se presenti in cache, invalidati
509
+ *
510
+ * @param {string} url URL per cui controllare se ci sono chiamate in cache da invalidare
511
+ */
512
+ checkCacheInvalidation(url) {
513
+ if (!this.CacheInitialized)
514
+ return;
515
+ let cache = this.localStorage.getLocalItem(this.localStorageHttpCacheKey);
516
+ let tmp = /#cache(:?-(\d+))/gi.exec(url);
517
+ let cacheTime = tmp ? tmp[2] : null;
518
+ let normalizedUrl_tmp = url.replace("#cache" + (cacheTime ? "-" + cacheTime : ""), "");
519
+ let normalizedUrl = normalizedUrl_tmp.substring(normalizedUrl_tmp.indexOf('/api'));
520
+ if (!cache || !this.HTTP_INVALIDATION || !this.HTTP_INVALIDATION[normalizedUrl])
521
+ return;
522
+ let apisToInvalidate = this.HTTP_INVALIDATION[normalizedUrl];
523
+ if (!apisToInvalidate || apisToInvalidate.length == 0)
524
+ return;
525
+ let keysToRemove = [];
526
+ apisToInvalidate.forEach(api => {
527
+ let cacheKey = this.makeCacheKey(api, null);
528
+ for (let item in cache)
529
+ if (item.indexOf(cacheKey) != -1)
530
+ keysToRemove.push(item);
531
+ });
532
+ for (let i = 0; i < keysToRemove.length; i++) {
533
+ let k = keysToRemove[i];
534
+ this.log(this.loc("Invlalidating cache element") + " " + k);
535
+ delete cache[k];
536
+ }
537
+ this.localStorage.setItem(this.localStorageHttpCacheKey, cache);
538
+ }
539
+ /**
540
+ * Finalizzazione di una chiamata
541
+ *
542
+ * @param {string} guid GUID collegato alla chiamata appena terminata
543
+ * @param {boolean} persistCacheIfIdle Effettua il persist della cache HTTP solo se non ci sono altre chiamate in corso, per evitare
544
+ * che mi cambi sotto l'oggetto di cache mentre sto salvando le informazioni
545
+ */
546
+ async tryFinalizeByGuid(guid, cacheInfos) {
547
+ this.log(this.loc("Finalizing call") + " " + guid + " " + this.loc("with status") + ": " + (!this.callStatus[guid] ? 'Unknown' : this.callStatus[guid].success ? this.loc('Success') : this.loc('Failure')));
548
+ // TODO: Perdere 3-4 ore per capire perché ogni tanto arriva null in casi pesanti di Observable spammati a random
549
+ if (this.callStatus[guid]) {
550
+ // Alla prima chiamata buona, significa che almeno il login ce l'ho. Posso mettermi in modalità redirectInProgress false
551
+ // e riiniziare a valutare errori di javashit
552
+ if (this.callStatus[guid].success && this.ExtensionsImpl.readRedirectionStatus())
553
+ this.ExtensionsImpl.writeRedirectionStatus(false);
554
+ // Aggiungo la chiamata alla lista delle ultime 3 completate
555
+ this.LastThreeCompletedCalls.unshift(this.callStatus[guid].url);
556
+ if (this.LastThreeCompletedCalls.length > 3)
557
+ this.LastThreeCompletedCalls.pop();
558
+ // In ogni caso questa posso rimuoverla
559
+ delete this.callStatus[guid];
560
+ }
561
+ // Se non ho nessun'altra chiamata in corso considero la situazione stabilizzatasi
562
+ if (Object.keys(this.callStatus).length == 0)
563
+ this.Stabilized.next(true);
564
+ // Se vengo da una chiamata che ha aggiornato la cache a timer o globale marco il fatto che quella cache dev'essere aggiornata
565
+ if (cacheInfos && cacheInfos.cache) {
566
+ this.PersistTimedCacheASAP = this.PersistTimedCacheASAP || !!cacheInfos.cacheTime;
567
+ this.PersistUntimedCacheASAP = this.PersistTimedCacheASAP || !cacheInfos.cacheTime;
568
+ this.log(this.loc("Marking cache to be persisted as soon as possible"));
569
+ }
570
+ // Se una delle due cache devono essere aggiornate e non ho nessuna chiamata in corso effettuo l'aggiornamento in base a quale delle due va aggiornata
571
+ // Alla fine di tutto marco come fatto
572
+ if ((this.PersistTimedCacheASAP || this.PersistUntimedCacheASAP) && Object.keys(this.callStatus).length == 0 && this.CacheInitialized) {
573
+ var toCall = [];
574
+ let tmpKey = "";
575
+ if (this.PersistTimedCacheASAP) {
576
+ tmpKey = this.localStorageHttpTimedCacheKey;
577
+ toCall.push(this.localStorage.setItem(tmpKey, this.localStorage.getLocalItem(tmpKey)));
578
+ }
579
+ if (this.PersistUntimedCacheASAP) {
580
+ tmpKey = this.localStorageHttpCacheKey;
581
+ toCall.push(this.localStorage.setItem(tmpKey, this.localStorage.getLocalItem(tmpKey)));
582
+ }
583
+ this.log(this.loc("No more HTTP calls to be executed, entering idle state and persisting modified caches") + "...");
584
+ var ret = await forkJoin(toCall).toPromise();
585
+ if (ret)
586
+ this.log(this.loc("Caches succesfully persisted"));
587
+ else
588
+ this.log(this.loc("Errors persisting modified caches"));
589
+ this.PersistTimedCacheASAP = false;
590
+ this.PersistUntimedCacheASAP = false;
591
+ }
592
+ }
593
+ /**
594
+ * Helper che dato un unico File o una lista di File crea un oggetto FormData che può essere inviato comodamente
595
+ * ad un backend ASP.NET da cui si potrà accedere a Request.Form.Files
596
+ *
597
+ * @param {File | File[]} fileArrayOrFile Singolo File o lista di File. N.B: nella firma della funzione viene considerato **any** per fare test sulle proprietà
598
+ * invece che fare test con l'**istanceof**
599
+ *
600
+ * @returns {FormData} FormData contenente tutti i file passati
601
+ */
602
+ getFilesFormData(fileArrayOrFile) {
603
+ var formData = new FormData();
604
+ if (fileArrayOrFile) {
605
+ if (fileArrayOrFile.length)
606
+ fileArrayOrFile.forEach(t => { formData.append("file", t, t.name); });
607
+ else if (fileArrayOrFile.name)
608
+ formData.append("files", fileArrayOrFile, fileArrayOrFile.name);
609
+ }
610
+ return formData;
611
+ }
612
+ /**
613
+ * Helper che in base ai parametri disponibili genera gli Header HTTP per la chiamata da effettuare
614
+ *
615
+ * @param {string} contentType Indica se aggiungere agli header "Content-Type", valorizzato al valore del parametro
616
+ * @param {string} accept Indica se aggiungere agli header "Accept", valorizzato al valore del parametro
617
+ * @param {boolean} includeAuth Indica se aggiungere agli header "Authorization", valorizzato col token recuperato dall'hook **retrieveAccessToken** della classe di estensione
618
+ * @param {string} transactionId Indica se aggiungere agli header "X-TX-ID", valorizzato al valore del parametro
619
+ *
620
+ * @returns {HttpHeaders} Header generati in base ai parametri
621
+ */
622
+ getHeaders(locale = 'it-IT', transactionId = "", includeAuth = true, contentType = "application/json", accept = "application/json") {
623
+ var headers = new HttpHeaders();
624
+ var authtoken = this.ExtensionsImpl.retrieveAccessToken();
625
+ headers = headers.append("ngsw-bypass", "");
626
+ if (contentType)
627
+ headers = headers.append("Content-Type", contentType);
628
+ if (transactionId)
629
+ headers = headers.append("X-TX-ID", transactionId);
630
+ if (authtoken && includeAuth)
631
+ headers = headers.append("Authorization", "Bearer " + authtoken);
632
+ if (accept)
633
+ headers = headers.append("Accept", accept);
634
+ headers = headers.append("Accept-Language", locale);
635
+ return headers;
636
+ }
637
+ /**
638
+ * Gestione di default degli errori
639
+ *
640
+ * @param {any} res Risultato della chiamata effettuata
641
+ * @param {string} guid Identificativo GUID della chiamata effettuata
642
+ */
643
+ async onError(res, guid) {
644
+ if (this.callStatus[guid])
645
+ this.callStatus[guid].success = false;
646
+ //Mi assicuro che tutti gli errori durante il redirect al login vengano ignorati
647
+ if (this.ExtensionsImpl.readRedirectingToLogin()) {
648
+ this.log(this.loc("Redirecting to Login, ignoring HTTP error on call") + " " + guid);
649
+ return;
650
+ }
651
+ //Mi assicuro che venga gestito un solo errore per volta
652
+ if (this.ExtensionsImpl.readHandlingErrorStatus()) {
653
+ this.log(this.loc("Already Handling an error, ignoring HTTP error on call") + " " + guid);
654
+ return;
655
+ }
656
+ else
657
+ this.ExtensionsImpl.writeHandlingErrorStatus(true);
658
+ let finalError = "";
659
+ let finalStatus = "";
660
+ if (this.isTypeError(res)) {
661
+ finalError = res.message;
662
+ finalStatus = "HTTP Service Internal Error";
663
+ }
664
+ else if (this.isHttpErrorResponse(res)) {
665
+ finalError = await this.getHttpErrorText(res.error);
666
+ finalStatus = res.status.toString();
667
+ switch (res.status) {
668
+ // Unauthorized
669
+ case 401:
670
+ if (this.ExtensionsImpl.readRedirectionStatus()) {
671
+ this.log(this.loc("Error 401 on call") + " " + guid + " " + this.loc("with redirect already in progress. Ignoring this event"));
672
+ return;
673
+ }
674
+ this.log(this.loc("Error 401 on call") + " " + guid + " " + this.loc("redirecting to login."));
675
+ this.ExtensionsImpl.writeRedirectionStatus(true);
676
+ this.ExtensionsImpl.writeRedirectingToLogin(true);
677
+ this.ExtensionsImpl.redirectToLogin();
678
+ return;
679
+ // Handled exception
680
+ case 400:
681
+ // No Permissions
682
+ case 403:
683
+ this.ExtensionsImpl.handleBlockingException(res.status, finalError);
684
+ return;
685
+ // Not found
686
+ case 404:
687
+ this.ExtensionsImpl.handleBlockingException(res.status, this.loc("Resource not found"));
688
+ return;
689
+ // NotConfiguredException
690
+ case 444:
691
+ if (this.ExtensionsImpl.readRedirectionStatus())
692
+ this.log(this.loc("Error 444 on call") + " " + guid + " " + this.loc("with redirect already in progress. Ignoring this event"));
693
+ else if (!this.ExtensionsImpl.handleNotConfiguredException())
694
+ this.ExtensionsImpl.handleBlockingException(res.status, finalError);
695
+ return;
696
+ case 0:
697
+ finalError = `Generic connection error while calling '[${guid}] - ${this.callStatus[guid].url}' at ${dayjs().format()}. Response: ${JSON.stringify(res)}`;
698
+ }
699
+ }
700
+ this.ExtensionsImpl.handleException(finalStatus, finalError);
701
+ }
702
+ /**
703
+ * Dato un errore restituisce una promise con la stringa finale dell'errore in modo da poter fare un await
704
+ *
705
+ * @param {any} err Errore da controllare
706
+ *
707
+ * @returns {Promise<string>} Promise che risolve con la stringa finale dell'errore
708
+ */
709
+ getHttpErrorText(err) {
710
+ // Se ho un Blob può essere o un CallResult o suppongo una semplice stringa,
711
+ // altrimenti potrebbe essere direttamente un CallResult, oppure una semplice stringa
712
+ return new Promise((resolve) => {
713
+ if (err instanceof Blob) {
714
+ let reader = new FileReader();
715
+ reader.onload = (e) => {
716
+ let errMsg = e.target.result;
717
+ let jsonerrMsg;
718
+ try {
719
+ jsonerrMsg = JSON.parse(errMsg);
720
+ }
721
+ catch (e) { }
722
+ return resolve(jsonerrMsg && this.isCallResult(jsonerrMsg) ? jsonerrMsg.error.message : errMsg);
723
+ };
724
+ reader.readAsText(err);
725
+ }
726
+ else if (this.isCallResult(err))
727
+ return resolve(err.error.message);
728
+ else
729
+ return resolve(err?.toString());
730
+ });
731
+ }
732
+ /**
733
+ * Riconosce se l'oggetto è di tipo **CallResult** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
734
+ *
735
+ * @param {any} err Errore da controllare
736
+ */
737
+ isCallResult(err) {
738
+ return !!err?.error?.message;
739
+ }
740
+ /**
741
+ * Riconosce se l'errore è di tipo **HttpErrorResponse** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
742
+ *
743
+ * @param {any} err Errore da controllare
744
+ */
745
+ isHttpErrorResponse(err) {
746
+ return err instanceof HttpErrorResponse;
747
+ }
748
+ /**
749
+ * Riconosce se l'errore è di tipo **TypeError** e, se sì, lo casta al tipo corretto per essere utilizzato correttamente nel blocco di codice successivo
750
+ *
751
+ * @param {any} err Errore da controllare
752
+ */
753
+ isTypeError(err) {
754
+ return err.name == "TypeError";
755
+ }
756
+ /**
757
+ * Come dice il nome, registra un errore e restituisce un Observable vuoto come risultato della chiamata
758
+ *
759
+ * @param {any} res Risultato della chiamata effettuata
760
+ * @param {string} guid Identificativo GUID della chiamata effettuata
761
+ * @param {boolean} handleError Indica se gestire l'errore via libreria o se limitarsi a restituire l'Observable vuoto
762
+ *
763
+ * @returns {Observable<any>} Observable vuoto
764
+ */
765
+ registerErrorAndReturnEmptyObservable(res, guid, handleError = true) {
766
+ if (handleError)
767
+ this.onError(res, guid);
768
+ return new Observable(() => { });
769
+ }
770
+ /**
771
+ * Dato un Url e dei parametri cerca di recuperare l'eventuale oggetto già salvato in cache con le informazioni base della cache stessa
772
+ *
773
+ * @param {string} url URL su cui effettuare la chiamata
774
+ * @param {HttpParams} params Parametri della chiamata (nulli in caso di POST)
775
+ *
776
+ * @returns {CacheInfos} Oggetto contenente le informazioni sulla cache per l'oggetto specificato
777
+ */
778
+ tryGetCacheInformations(url, params) {
779
+ let cachingKey = null, cacheTime = null, item = null;
780
+ let useCaching = !!(/#cache/gi.exec(url));
781
+ if (useCaching && this.CacheInitialized) {
782
+ let tmp = /#cache(:?-(\d+))/gi.exec(url);
783
+ cacheTime = tmp ? tmp[2] : null;
784
+ url = url.replace("#cache" + (cacheTime ? "-" + cacheTime : ""), "");
785
+ if (this.untimedCacheExpired() && !cacheTime)
786
+ this.resetHttpUntimedCacheStorage();
787
+ cachingKey = this.makeCacheKey(url, params);
788
+ let cacheKey = cacheTime ? this.localStorageHttpTimedCacheKey : this.localStorageHttpCacheKey;
789
+ let cacheLs = this.localStorage.getLocalItem(cacheKey);
790
+ let cache = cacheLs ? cacheLs : {};
791
+ if (cache && cache[cachingKey] && !this.timedCacheItemExpired(cache[cachingKey])) {
792
+ // Se gestisco il tempo esatto della cache prima di restituire l'oggetto cachato lo ripulisco
793
+ // dalle proprietà interne che gli ho aggiunto per controllarne la scadenza
794
+ if (cacheTime)
795
+ delete cache[cachingKey].__expire;
796
+ item = cache[cachingKey];
797
+ }
798
+ }
799
+ // Per assicurarmi che il rifeirmento degli oggetti sia diverso utilizzo il deepClone
800
+ return new CacheInfos(url, useCaching && this.CacheInitialized, cachingKey, cacheTime, this.utisExts.deepClone(item));
801
+ }
802
+ /**
803
+ * Inserisce un oggetto nella cache appropriata in base alle informazioni contenute nel parametro **cacheInfos**
804
+ *
805
+ * @param {T} item Oggetto da inserire nella cache
806
+ * @param {CacheInfos} cacheInfos Informazioni sulla cache recuperate da una chiamata precedente a **tryGetCacheInformations**
807
+ */
808
+ cacheResponse(item, cacheInfos) {
809
+ let cacheKey = cacheInfos.cacheTime ? this.localStorageHttpTimedCacheKey : this.localStorageHttpCacheKey;
810
+ let cacheLs = this.localStorage.getLocalItem(cacheKey);
811
+ let cache = cacheLs ? cacheLs : {};
812
+ cache[cacheInfos.cacheKey] = item;
813
+ if (cacheInfos.cacheTime)
814
+ cache[cacheInfos.cacheKey].__expire = dayjs().add(cacheInfos.cacheTime, "seconds");
815
+ this.localStorage.setLocalItem(cacheKey, cache);
816
+ }
817
+ /**
818
+ * Controlla se la cache per le chiamate senza expire time è scaduta
819
+ *
820
+ * @returns {boolean} **true** se la cache è scaduta e dev'essere ripulita, **false** altrimenti
821
+ */
822
+ untimedCacheExpired() {
823
+ let expire = JSON.parse(localStorage.getItem(this.localStorageHttpCacheExpireKey));
824
+ return !expire || expire && dayjs(expire) < dayjs();
825
+ }
826
+ /**
827
+ * Controlla se la cache per una chiamata che ha un tempo di vita ben definito è scaduta o meno
828
+ *
829
+ * @param {any} item Oggetto recuperato dalla cache a cui ho aggiunto la chiave interna __expire
830
+ *
831
+ * @returns {boolean} **true** se la cache è scaduta e dev'essere ripulita, **false** altrimenti
832
+ */
833
+ timedCacheItemExpired(item) {
834
+ return dayjs(item.__expire) < dayjs();
835
+ }
836
+ /**
837
+ * Ripulisce la cache e imposta la prossima scadenza ad adesso + **CLIENT_CACHE_DURATION_S** (token valorizzato in fase di configurazione dalla proprietà **clientCacheDurationS** della classe **HttpServiceModuleConfig**)
838
+ */
839
+ async resetHttpUntimedCacheStorage() {
840
+ if (!this.CacheInitialized)
841
+ return;
842
+ await this.localStorage.setItem(this.localStorageHttpCacheKey, {}).toPromise();
843
+ localStorage.setItem(this.localStorageHttpCacheExpireKey, JSON.stringify(dayjs().add(this.CLIENT_CACHE_DURATION_S, "second")));
844
+ }
845
+ /**
846
+ * Ripulisce la cache delle chiamate con un tempo di expire definito
847
+ */
848
+ async resetHttpTimedCacheStorage() {
849
+ if (!this.CacheInitialized)
850
+ return;
851
+ await this.localStorage.setItem(this.localStorageHttpTimedCacheKey, {}).toPromise();
852
+ }
853
+ /**
854
+ * Genera una chiave per la cache delle chiamate in base ad URL e Parametri HTTP
855
+ *
856
+ * @param {string} url URL su cui effettuare la chiamata
857
+ * @param {HttpParams} params Parametri della chiamata
858
+ *
859
+ * @returns {string} Chiave che rappresenta in maniera univoca la chiamata
860
+ */
861
+ makeCacheKey(url, params) {
862
+ this.me = this.me || this.ExtensionsImpl.retrieveIdentity();
863
+ let authKeyPart = (this.me ? this.me.Idtenant : "") + (this.me && this.CACHE_BY_USER ? this.me.IdUser : "");
864
+ let paramsKeyPart = params && params.keys().length > 0 ? params.keys().reduce((prev, next) => prev + "," + next + '=' + params.get(next), "") : "";
865
+ return authKeyPart + "_" + url.substring(url.indexOf("api") + 4) + "?" + paramsKeyPart;
866
+ }
867
+ /**
868
+ * Generazione GUID
869
+ *
870
+ * @returns {string} Nuovo GUID generato in maniera casuale
871
+ */
872
+ getGUID() {
873
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
874
+ var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
875
+ return v.toString(16);
876
+ });
877
+ }
878
+ /**
879
+ * Metodo per scrivere log a console qualora **DEBUG_MODE** risultasse **true**
880
+ *
881
+ * @param {string} message Messaggio di log
882
+ */
883
+ log(message) {
884
+ if (this.HTTP_DEBUG_MODE)
885
+ console.log("@httpservice: " + message);
886
+ }
887
+ /**
888
+ * Helper per la localizzazione dei messaggi
889
+ *
890
+ * @param {string} message Messaggio da localizzare
891
+ *
892
+ * @returns {string} Messaggio localizzato
893
+ */
894
+ loc(message) {
895
+ // Togliamo velocemente ste localizzazioni inutili....
896
+ return message;
897
+ }
898
+ /**
899
+ * Helper per lanciare la selezione di tutto quello che è relativo a un api di ricerca con AppSearch (di cui non utilizzo il tipo vero per ridurre le dipendenze)
900
+ */
901
+ getAll(view, api, pars, transaction) {
902
+ if (!view.selection.all)
903
+ return of(view.selection.items);
904
+ var jsonView = this.utisExts.deepClone(view);
905
+ jsonView.page = 1;
906
+ jsonView.itemsperpageoverride = 999999;
907
+ return this.post(api, jsonView, pars, transaction).pipe(map(res => {
908
+ let items = res.items;
909
+ items.forEach(t => t.hash = this.hash.hashObject(t, null, false));
910
+ if (res.selection.exclusions?.length > 0)
911
+ items = items.filter((t) => !res.selection.exclusions.find((e) => this.hash.hashObject(e, null, false) == t.hash));
912
+ return items;
913
+ }));
914
+ }
915
+ beginTransaction(executionKey, secondsTimeout) {
916
+ this.log("beginTransaction call received: " + executionKey);
917
+ var pars = new HttpParams().set("executionKey", executionKey);
918
+ if (secondsTimeout)
919
+ pars = pars.set("secondsTimeout", secondsTimeout.toString());
920
+ return this.get(this.GET_Trans_BeginTrans, pars);
921
+ }
922
+ checkTransaction(executionKey) {
923
+ this.log("checkTransaction call received: " + executionKey);
924
+ return this.get(this.GET_Trans_CheckExecution, new HttpParams().set("executionKey", executionKey));
925
+ }
926
+ endTransaction(transactionKey, transactionResult) {
927
+ this.log("endTransaction call received: " + transactionKey + " Commit: " + transactionResult);
928
+ let pars = new HttpParams().set("idtrans", transactionKey).set("success", transactionResult.toString());
929
+ return this.get(this.GET_Trans_EndTrans, pars);
930
+ }
931
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HTTPService, deps: [{ token: HttpServiceExtensions }, { token: HTTP_INVALIDATION, optional: true }, { token: CLIENT_CACHE_DURATION_S }, { token: CACHE_BY_USER }, { token: HTTP_DEBUG_MODE }, { token: i2.BaseLocalization }, { token: i3.HttpClient }, { token: i4.CacheService }, { token: i5.HashingService }, { token: i5.UtilityService }], target: i0.ɵɵFactoryTarget.Injectable }); }
932
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HTTPService, providedIn: 'root' }); }
933
+ }
934
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HTTPService, decorators: [{
935
+ type: Injectable,
936
+ args: [{ providedIn: 'root' }]
937
+ }], ctorParameters: () => [{ type: HttpServiceExtensions }, { type: undefined, decorators: [{
938
+ type: Optional
939
+ }, {
940
+ type: Inject,
941
+ args: [HTTP_INVALIDATION]
942
+ }] }, { type: undefined, decorators: [{
943
+ type: Inject,
944
+ args: [CLIENT_CACHE_DURATION_S]
945
+ }] }, { type: undefined, decorators: [{
946
+ type: Inject,
947
+ args: [CACHE_BY_USER]
948
+ }] }, { type: undefined, decorators: [{
949
+ type: Inject,
950
+ args: [HTTP_DEBUG_MODE]
951
+ }] }, { type: i2.BaseLocalization }, { type: i3.HttpClient }, { type: i4.CacheService }, { type: i5.HashingService }, { type: i5.UtilityService }] });
952
952
 
953
- class HttpserviceModule {
954
- static forRoot(config) {
955
- return {
956
- ngModule: HttpserviceModule,
957
- providers: [
958
- { provide: HTTP_INVALIDATION, useValue: config?.httpInvalidation },
959
- { provide: CLIENT_CACHE_DURATION_S, useValue: config?.clientCacheDurationS || 3600 },
960
- { provide: CACHE_BY_USER, useValue: config?.cacheByUser || false },
961
- { provide: HTTP_DEBUG_MODE, useValue: config?.debugMode || false }
962
- ]
963
- };
964
- }
965
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpserviceModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
966
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: HttpserviceModule }); }
967
- static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpserviceModule }); }
968
- }
969
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HttpserviceModule, decorators: [{
970
- type: NgModule
953
+ class HttpserviceModule {
954
+ static forRoot(config) {
955
+ return {
956
+ ngModule: HttpserviceModule,
957
+ providers: [
958
+ { provide: HTTP_INVALIDATION, useValue: config?.httpInvalidation },
959
+ { provide: CLIENT_CACHE_DURATION_S, useValue: config?.clientCacheDurationS || 3600 },
960
+ { provide: CACHE_BY_USER, useValue: config?.cacheByUser || false },
961
+ { provide: HTTP_DEBUG_MODE, useValue: config?.debugMode || false }
962
+ ]
963
+ };
964
+ }
965
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpserviceModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
966
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: HttpserviceModule }); }
967
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpserviceModule }); }
968
+ }
969
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HttpserviceModule, decorators: [{
970
+ type: NgModule
971
971
  }] });
972
972
 
973
- /**
974
- * Classe di configurazione per il Modulo della libreria
975
- */
976
- class HttpServiceModuleConfig {
973
+ /**
974
+ * Classe di configurazione per il Modulo della libreria
975
+ */
976
+ class HttpServiceModuleConfig {
977
977
  }
978
978
 
979
- class Download {
979
+ class Download {
980
980
  }
981
981
 
982
- /*
983
- * Public API Surface of httpservice
982
+ /*
983
+ * Public API Surface of httpservice
984
984
  */
985
985
 
986
- /**
987
- * Generated bundle index. Do not edit.
986
+ /**
987
+ * Generated bundle index. Do not edit.
988
988
  */
989
989
 
990
990
  export { APP_NAME, CACHE_BY_USER, CLIENT_CACHE_DURATION_S, Download, HTTPService, HTTP_DEBUG_MODE, HTTP_INVALIDATION, HttpServiceExtensions, HttpServiceModuleConfig, HttpserviceModule };