@factor_ec/utils 5.0.8 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/fesm2022/factor_ec-utils.mjs +1089 -457
- package/fesm2022/factor_ec-utils.mjs.map +1 -1
- package/package.json +5 -12
- package/types/factor_ec-utils.d.ts +894 -0
- package/index.d.ts +0 -5
- package/lib/array.service.d.ts +0 -6
- package/lib/color.service.d.ts +0 -58
- package/lib/csv.service.d.ts +0 -88
- package/lib/date.service.d.ts +0 -6
- package/lib/file-picker.service.d.ts +0 -10
- package/lib/files.service.d.ts +0 -14
- package/lib/google-tag-manager.service.d.ts +0 -18
- package/lib/graphql.service.d.ts +0 -12
- package/lib/models/auth-token-payload.d.ts +0 -6
- package/lib/models/auth-token.d.ts +0 -4
- package/lib/models/currency.d.ts +0 -6
- package/lib/models/error.d.ts +0 -6
- package/lib/models/language.d.ts +0 -4
- package/lib/models/login.d.ts +0 -4
- package/lib/models/operation.d.ts +0 -4
- package/lib/models/option.d.ts +0 -5
- package/lib/models/user.d.ts +0 -5
- package/lib/object.service.d.ts +0 -7
- package/lib/storage.service.d.ts +0 -12
- package/lib/string.service.d.ts +0 -19
- package/lib/validators/ec/identification-type.d.ts +0 -1
- package/lib/validators/ec/identification-validator.d.ts +0 -2
- package/public-api.d.ts +0 -22
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, PLATFORM_ID,
|
|
2
|
+
import { Injectable, inject, PLATFORM_ID, InjectionToken, signal, computed } from '@angular/core';
|
|
3
3
|
import { isPlatformBrowser } from '@angular/common';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { lastValueFrom } from 'rxjs';
|
|
4
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
5
|
+
import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
|
6
|
+
import { MatDialog } from '@angular/material/dialog';
|
|
7
|
+
import { BehaviorSubject, switchMap, throwError, catchError, share, finalize, filter, take, lastValueFrom, tap } from 'rxjs';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Utility service for array operations.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Provides helper methods for common array manipulation tasks.
|
|
14
|
+
*/
|
|
15
|
+
class ArrayUtil {
|
|
16
|
+
/**
|
|
17
|
+
* Merges multiple arrays into a single array, combining objects with the same property value.
|
|
18
|
+
*
|
|
19
|
+
* @param arrays - Array of arrays to merge
|
|
20
|
+
* @param prop - Property name to use as the key for merging objects
|
|
21
|
+
* @returns Merged array with combined objects
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const arr1 = [{ id: 1, name: 'John' }];
|
|
26
|
+
* const arr2 = [{ id: 1, age: 30 }];
|
|
27
|
+
* const result = arrayUtil.merge([arr1, arr2], 'id');
|
|
28
|
+
* // Returns: [{ id: 1, name: 'John', age: 30 }]
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
10
31
|
merge(arrays, prop) {
|
|
11
32
|
const merged = {};
|
|
12
33
|
arrays.forEach(arr => {
|
|
@@ -16,17 +37,31 @@ class ArrayService {
|
|
|
16
37
|
});
|
|
17
38
|
return Object.values(merged);
|
|
18
39
|
}
|
|
19
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
20
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
40
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ArrayUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
41
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ArrayUtil, providedIn: 'root' });
|
|
21
42
|
}
|
|
22
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
43
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ArrayUtil, decorators: [{
|
|
23
44
|
type: Injectable,
|
|
24
45
|
args: [{
|
|
25
|
-
providedIn: 'root'
|
|
46
|
+
providedIn: 'root',
|
|
26
47
|
}]
|
|
27
48
|
}] });
|
|
28
49
|
|
|
29
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Utility service for color operations and hash-based color generation.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* Provides methods to generate consistent colors from strings using hash algorithms,
|
|
55
|
+
* and convert between different color formats (HSL, RGB, HEX).
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const colorUtil = inject(ColorUtil);
|
|
60
|
+
* const hexColor = colorUtil.hex('username');
|
|
61
|
+
* // Returns: '#a1b2c3' (consistent color for the same string)
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
class ColorUtil {
|
|
30
65
|
L;
|
|
31
66
|
S;
|
|
32
67
|
hueRanges;
|
|
@@ -55,9 +90,13 @@ class ColorService {
|
|
|
55
90
|
});
|
|
56
91
|
}
|
|
57
92
|
/**
|
|
58
|
-
* BKDR
|
|
93
|
+
* Generates a hash value from a string using BKDR hash algorithm (modified version).
|
|
59
94
|
*
|
|
60
|
-
* @param str string to hash
|
|
95
|
+
* @param str - The string to hash
|
|
96
|
+
* @returns A numeric hash value
|
|
97
|
+
*
|
|
98
|
+
* @remarks
|
|
99
|
+
* This is a modified BKDR hash algorithm optimized for short strings.
|
|
61
100
|
*/
|
|
62
101
|
hash(str) {
|
|
63
102
|
let seed = 131;
|
|
@@ -76,10 +115,10 @@ class ColorService {
|
|
|
76
115
|
return hash;
|
|
77
116
|
}
|
|
78
117
|
/**
|
|
79
|
-
*
|
|
118
|
+
* Converts an RGB array to a hexadecimal color string.
|
|
80
119
|
*
|
|
81
|
-
* @param RGBArray - [R, G, B]
|
|
82
|
-
* @returns
|
|
120
|
+
* @param RGBArray - Array with [R, G, B] values (0-255)
|
|
121
|
+
* @returns Hexadecimal color string starting with # (e.g., '#a1b2c3')
|
|
83
122
|
*/
|
|
84
123
|
rgb2hex(RGBArray) {
|
|
85
124
|
let hex = '#';
|
|
@@ -92,13 +131,14 @@ class ColorService {
|
|
|
92
131
|
return hex;
|
|
93
132
|
}
|
|
94
133
|
/**
|
|
95
|
-
*
|
|
134
|
+
* Converts HSL color values to RGB array.
|
|
135
|
+
*
|
|
136
|
+
* @param H - Hue value in range [0, 360)
|
|
137
|
+
* @param S - Saturation value in range [0, 1]
|
|
138
|
+
* @param L - Lightness value in range [0, 1]
|
|
139
|
+
* @returns Array with [R, G, B] values in range [0, 255]
|
|
96
140
|
*
|
|
97
141
|
* @see {@link http://zh.wikipedia.org/wiki/HSL和HSV色彩空间} for further information.
|
|
98
|
-
* @param H Hue ∈ [0, 360)
|
|
99
|
-
* @param S Saturation ∈ [0, 1]
|
|
100
|
-
* @param L Lightness ∈ [0, 1]
|
|
101
|
-
* @returns R, G, B ∈ [0, 255]
|
|
102
142
|
*/
|
|
103
143
|
hsl2rgb(H, S, L) {
|
|
104
144
|
H /= 360;
|
|
@@ -127,11 +167,10 @@ class ColorService {
|
|
|
127
167
|
});
|
|
128
168
|
}
|
|
129
169
|
/**
|
|
130
|
-
*
|
|
131
|
-
* Note that H ∈ [0, 360); S ∈ [0, 1]; L ∈ [0, 1];
|
|
170
|
+
* Generates HSL color values from a string hash.
|
|
132
171
|
*
|
|
133
|
-
* @param str string to
|
|
134
|
-
* @returns [
|
|
172
|
+
* @param str - The string to generate color from
|
|
173
|
+
* @returns Array with [H, S, L] values where H ∈ [0, 360), S ∈ [0, 1], L ∈ [0, 1]
|
|
135
174
|
*/
|
|
136
175
|
hsl(str) {
|
|
137
176
|
let H;
|
|
@@ -157,86 +196,135 @@ class ColorService {
|
|
|
157
196
|
return [H, S, L];
|
|
158
197
|
}
|
|
159
198
|
/**
|
|
160
|
-
*
|
|
161
|
-
* Note that R, G, B ∈ [0, 255]
|
|
199
|
+
* Generates RGB color values from a string hash.
|
|
162
200
|
*
|
|
163
|
-
* @param str string to
|
|
164
|
-
* @returns [
|
|
201
|
+
* @param str - The string to generate color from
|
|
202
|
+
* @returns Array with [R, G, B] values in range [0, 255]
|
|
165
203
|
*/
|
|
166
204
|
rgb(str) {
|
|
167
205
|
let hsl = this.hsl(str);
|
|
168
206
|
return this.hsl2rgb(hsl[0], hsl[1], hsl[2]);
|
|
169
207
|
}
|
|
170
208
|
/**
|
|
171
|
-
*
|
|
209
|
+
* Generates a hexadecimal color string from a string hash.
|
|
172
210
|
*
|
|
173
|
-
* @param str string to
|
|
174
|
-
* @returns
|
|
211
|
+
* @param str - The string to generate color from
|
|
212
|
+
* @returns Hexadecimal color string starting with # (e.g., '#a1b2c3')
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* const color = colorUtil.hex('username');
|
|
217
|
+
* // Returns: '#a1b2c3' (same string always returns same color)
|
|
218
|
+
* ```
|
|
175
219
|
*/
|
|
176
220
|
hex(str) {
|
|
177
221
|
let rgb = this.rgb(str);
|
|
178
222
|
return this.rgb2hex(rgb);
|
|
179
223
|
}
|
|
180
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
181
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
224
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ColorUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
225
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ColorUtil, providedIn: 'root' });
|
|
182
226
|
}
|
|
183
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
227
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ColorUtil, decorators: [{
|
|
184
228
|
type: Injectable,
|
|
185
229
|
args: [{
|
|
186
230
|
providedIn: 'root',
|
|
187
231
|
}]
|
|
188
232
|
}], ctorParameters: () => [] });
|
|
189
233
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Default configuration constants for CSV export operations.
|
|
236
|
+
*/
|
|
237
|
+
const CSV_CONFIG = {
|
|
238
|
+
/** End of line character for CSV */
|
|
239
|
+
EOL: '\r\n',
|
|
240
|
+
/** Byte Order Mark for UTF-8 encoding */
|
|
241
|
+
BOM: '\ufeff',
|
|
242
|
+
/** Default field separator character */
|
|
243
|
+
DEFAULT_FIELD_SEPARATOR: ',',
|
|
244
|
+
/** Default decimal separator */
|
|
245
|
+
DEFAULT_DECIMAL_SEPARATOR: '.',
|
|
246
|
+
/** Default quote character for strings */
|
|
247
|
+
DEFAULT_QUOTE: '"',
|
|
248
|
+
/** Default value for showing title */
|
|
249
|
+
DEFAULT_SHOW_TITLE: false,
|
|
250
|
+
/** Default title text */
|
|
251
|
+
DEFAULT_TITLE: 'My Generated Report',
|
|
252
|
+
/** Default filename (without extension) */
|
|
253
|
+
DEFAULT_FILENAME: 'generated',
|
|
254
|
+
/** Default value for showing column labels */
|
|
255
|
+
DEFAULT_SHOW_LABELS: false,
|
|
256
|
+
/** Default value for using text file format */
|
|
257
|
+
DEFAULT_USE_TEXT_FILE: false,
|
|
258
|
+
/** Default value for including BOM */
|
|
259
|
+
DEFAULT_USE_BOM: true,
|
|
260
|
+
/** Default header array (empty) */
|
|
261
|
+
DEFAULT_HEADER: [],
|
|
262
|
+
/** Default value for using object keys as headers */
|
|
263
|
+
DEFAULT_KEYS_AS_HEADERS: false
|
|
217
264
|
};
|
|
218
|
-
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Service for exporting data to CSV format and parsing CSV content.
|
|
268
|
+
*
|
|
269
|
+
* @remarks
|
|
270
|
+
* Provides functionality to generate CSV files from JSON data with customizable options,
|
|
271
|
+
* and to parse CSV content back into structured data.
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* const csvExporter = inject(CsvExporter);
|
|
276
|
+
* csvExporter.options = { filename: 'export', showLabels: true };
|
|
277
|
+
* csvExporter.generate([{ name: 'John', age: 30 }]);
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
class CsvExporter {
|
|
219
281
|
_data = [];
|
|
220
282
|
_options;
|
|
221
283
|
_csv = '';
|
|
284
|
+
configDefaults = {
|
|
285
|
+
filename: CSV_CONFIG.DEFAULT_FILENAME,
|
|
286
|
+
fieldSeparator: CSV_CONFIG.DEFAULT_FIELD_SEPARATOR,
|
|
287
|
+
quoteStrings: CSV_CONFIG.DEFAULT_QUOTE,
|
|
288
|
+
decimalSeparator: CSV_CONFIG.DEFAULT_DECIMAL_SEPARATOR,
|
|
289
|
+
showLabels: CSV_CONFIG.DEFAULT_SHOW_LABELS,
|
|
290
|
+
showTitle: CSV_CONFIG.DEFAULT_SHOW_TITLE,
|
|
291
|
+
title: CSV_CONFIG.DEFAULT_TITLE,
|
|
292
|
+
useTextFile: CSV_CONFIG.DEFAULT_USE_TEXT_FILE,
|
|
293
|
+
useBom: CSV_CONFIG.DEFAULT_USE_BOM,
|
|
294
|
+
headers: [...CSV_CONFIG.DEFAULT_HEADER],
|
|
295
|
+
useKeysAsHeaders: CSV_CONFIG.DEFAULT_KEYS_AS_HEADERS
|
|
296
|
+
};
|
|
222
297
|
get options() {
|
|
223
298
|
return this._options;
|
|
224
299
|
}
|
|
225
300
|
set options(options) {
|
|
226
|
-
this._options = this.objectAssign({},
|
|
301
|
+
this._options = this.objectAssign({}, this.configDefaults, options);
|
|
227
302
|
}
|
|
228
303
|
constructor() {
|
|
229
|
-
this._options = this.objectAssign({},
|
|
304
|
+
this._options = this.objectAssign({}, this.configDefaults);
|
|
230
305
|
}
|
|
231
306
|
/**
|
|
232
|
-
*
|
|
307
|
+
* Generates a CSV file from JSON data and optionally downloads it.
|
|
308
|
+
*
|
|
309
|
+
* @param jsonData - The data to export (array of objects or JSON string)
|
|
310
|
+
* @param shouldReturnCsv - If true, returns the CSV string instead of downloading
|
|
311
|
+
* @returns The CSV string if shouldReturnCsv is true, otherwise void
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* ```typescript
|
|
315
|
+
* // Download CSV
|
|
316
|
+
* csvExporter.generate([{ name: 'John', age: 30 }]);
|
|
317
|
+
*
|
|
318
|
+
* // Get CSV string
|
|
319
|
+
* const csvString = csvExporter.generate([{ name: 'John' }], true);
|
|
320
|
+
* ```
|
|
233
321
|
*/
|
|
234
322
|
generate(jsonData, shouldReturnCsv = false) {
|
|
235
323
|
// Make sure to reset csv data on each run
|
|
236
324
|
this._csv = '';
|
|
237
325
|
this._parseData(jsonData);
|
|
238
326
|
if (this._options.useBom) {
|
|
239
|
-
this._csv +=
|
|
327
|
+
this._csv += CSV_CONFIG.BOM;
|
|
240
328
|
}
|
|
241
329
|
if (this._options.showTitle) {
|
|
242
330
|
this._csv += this._options.title + '\r\n\n';
|
|
@@ -259,8 +347,6 @@ class CsvService {
|
|
|
259
347
|
const blob = new Blob([this._csv], {
|
|
260
348
|
type: 'text/' + FileType + ';charset=utf8;'
|
|
261
349
|
});
|
|
262
|
-
//const attachmentType = this._options.useTextFile ? 'text' : 'csv';
|
|
263
|
-
// const uri = 'data:attachment/' + attachmentType + ';charset=utf-8,' + encodeURI(this._csv);
|
|
264
350
|
const link = document.createElement('a');
|
|
265
351
|
link.href = URL.createObjectURL(blob);
|
|
266
352
|
link.setAttribute('visibility', 'hidden');
|
|
@@ -270,7 +356,9 @@ class CsvService {
|
|
|
270
356
|
document.body.removeChild(link);
|
|
271
357
|
}
|
|
272
358
|
/**
|
|
273
|
-
*
|
|
359
|
+
* Creates CSV headers row based on configuration.
|
|
360
|
+
*
|
|
361
|
+
* @private
|
|
274
362
|
*/
|
|
275
363
|
_getHeaders() {
|
|
276
364
|
if (!this._options.showLabels && !this._options.useKeysAsHeaders) {
|
|
@@ -286,11 +374,13 @@ class CsvService {
|
|
|
286
374
|
row += headers[keyPos] + this._options.fieldSeparator;
|
|
287
375
|
}
|
|
288
376
|
row = row.slice(0, -1);
|
|
289
|
-
this._csv += row +
|
|
377
|
+
this._csv += row + CSV_CONFIG.EOL;
|
|
290
378
|
}
|
|
291
379
|
}
|
|
292
380
|
/**
|
|
293
|
-
*
|
|
381
|
+
* Creates CSV body rows from the data.
|
|
382
|
+
*
|
|
383
|
+
* @private
|
|
294
384
|
*/
|
|
295
385
|
_getBody() {
|
|
296
386
|
const keys = Object.keys(this._data[0]);
|
|
@@ -302,12 +392,15 @@ class CsvService {
|
|
|
302
392
|
this._formatData(this._data[i][key]) + this._options.fieldSeparator;
|
|
303
393
|
}
|
|
304
394
|
row = row.slice(0, -1);
|
|
305
|
-
this._csv += row +
|
|
395
|
+
this._csv += row + CSV_CONFIG.EOL;
|
|
306
396
|
}
|
|
307
397
|
}
|
|
308
398
|
/**
|
|
309
|
-
*
|
|
310
|
-
*
|
|
399
|
+
* Formats data for CSV output according to configuration.
|
|
400
|
+
*
|
|
401
|
+
* @param data - The data value to format
|
|
402
|
+
* @returns The formatted data as a string
|
|
403
|
+
* @private
|
|
311
404
|
*/
|
|
312
405
|
_formatData(data) {
|
|
313
406
|
if (this._options.decimalSeparator === 'locale' && this._isFloat(data)) {
|
|
@@ -332,27 +425,32 @@ class CsvService {
|
|
|
332
425
|
return data;
|
|
333
426
|
}
|
|
334
427
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
428
|
+
* Checks if a value is a floating point number.
|
|
429
|
+
*
|
|
430
|
+
* @param input - The value to check
|
|
431
|
+
* @returns True if the value is a float, false otherwise
|
|
432
|
+
* @private
|
|
337
433
|
*/
|
|
338
434
|
_isFloat(input) {
|
|
339
435
|
return +input === input && (!isFinite(input) || Boolean(input % 1));
|
|
340
436
|
}
|
|
341
437
|
/**
|
|
342
|
-
*
|
|
438
|
+
* Parses JSON data into an array format.
|
|
343
439
|
*
|
|
440
|
+
* @param jsonData - The JSON data to parse (object, array, or JSON string)
|
|
441
|
+
* @returns Parsed array of data
|
|
344
442
|
* @private
|
|
345
|
-
* @param {*} jsonData
|
|
346
|
-
* @returns {any[]}
|
|
347
|
-
* @memberof ExportToCsv
|
|
348
443
|
*/
|
|
349
444
|
_parseData(jsonData) {
|
|
350
445
|
this._data = typeof jsonData != 'object' ? JSON.parse(jsonData) : jsonData;
|
|
351
446
|
return this._data;
|
|
352
447
|
}
|
|
353
448
|
/**
|
|
354
|
-
*
|
|
355
|
-
*
|
|
449
|
+
* Converts a value to an object, throwing an error for null or undefined.
|
|
450
|
+
*
|
|
451
|
+
* @param val - The value to convert
|
|
452
|
+
* @returns The value as an object
|
|
453
|
+
* @throws {TypeError} If val is null or undefined
|
|
356
454
|
*/
|
|
357
455
|
toObject(val) {
|
|
358
456
|
if (val === null || val === undefined) {
|
|
@@ -361,9 +459,11 @@ class CsvService {
|
|
|
361
459
|
return Object(val);
|
|
362
460
|
}
|
|
363
461
|
/**
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
* @param
|
|
462
|
+
* Assigns properties from source objects to a target object (similar to Object.assign).
|
|
463
|
+
*
|
|
464
|
+
* @param target - The target object to assign properties to
|
|
465
|
+
* @param source - One or more source objects to copy properties from
|
|
466
|
+
* @returns The target object with assigned properties
|
|
367
467
|
*/
|
|
368
468
|
objectAssign(target, ...source) {
|
|
369
469
|
let from;
|
|
@@ -389,6 +489,18 @@ class CsvService {
|
|
|
389
489
|
}
|
|
390
490
|
return to;
|
|
391
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Parses CSV content into a structured format with headers and content rows.
|
|
494
|
+
*
|
|
495
|
+
* @param csvContent - The CSV string to parse
|
|
496
|
+
* @returns An object containing the header array and content rows array
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const parsed = csvExporter.read('name,age\nJohn,30\nJane,25');
|
|
501
|
+
* // Returns: { header: ['name', 'age'], content: [['John', '30'], ['Jane', '25']] }
|
|
502
|
+
* ```
|
|
503
|
+
*/
|
|
392
504
|
read(csvContent) {
|
|
393
505
|
const lines = csvContent.split('\n');
|
|
394
506
|
let header;
|
|
@@ -398,7 +510,7 @@ class CsvService {
|
|
|
398
510
|
return this.parseLine(line.trim());
|
|
399
511
|
});
|
|
400
512
|
header = csv[0];
|
|
401
|
-
//
|
|
513
|
+
// If a blank line is found, stop reading subsequent lines
|
|
402
514
|
let breakIndex;
|
|
403
515
|
csv.some((row, index) => {
|
|
404
516
|
const isBlankLine = row.every((column) => column.trim() === '');
|
|
@@ -419,399 +531,189 @@ class CsvService {
|
|
|
419
531
|
content
|
|
420
532
|
};
|
|
421
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* Parses a single CSV line into an array of values.
|
|
536
|
+
*
|
|
537
|
+
* @param csvLine - A single line of CSV content
|
|
538
|
+
* @returns Array of parsed values from the line
|
|
539
|
+
*
|
|
540
|
+
* @remarks
|
|
541
|
+
* Handles quoted values and different quote styles (single and double quotes).
|
|
542
|
+
*/
|
|
422
543
|
parseLine(csvLine) {
|
|
423
544
|
const values = [];
|
|
424
545
|
const regex = /(?:"([^"]*)"|'([^']*)'|([^,]+))(?:,|\r?$)/g;
|
|
425
546
|
let match;
|
|
426
547
|
while ((match = regex.exec(csvLine)) !== null) {
|
|
427
|
-
values.push(match[1] || match[2] || match[3]); //
|
|
548
|
+
values.push(match[1] || match[2] || match[3]); // Extract the correct value
|
|
428
549
|
}
|
|
429
550
|
return values;
|
|
430
551
|
}
|
|
431
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
432
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
552
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CsvExporter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
553
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CsvExporter, providedIn: 'root' });
|
|
433
554
|
}
|
|
434
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: CsvExporter, decorators: [{
|
|
435
556
|
type: Injectable,
|
|
436
557
|
args: [{
|
|
437
|
-
providedIn: 'root'
|
|
558
|
+
providedIn: 'root',
|
|
438
559
|
}]
|
|
439
560
|
}], ctorParameters: () => [] });
|
|
440
561
|
|
|
441
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Utility service for date operations.
|
|
564
|
+
*
|
|
565
|
+
* @remarks
|
|
566
|
+
* Provides helper methods for date parsing and manipulation.
|
|
567
|
+
*/
|
|
568
|
+
class DateUtil {
|
|
569
|
+
/**
|
|
570
|
+
* Parses a date string in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss) and returns a Date object.
|
|
571
|
+
*
|
|
572
|
+
* @param date - The date string to parse (ISO format)
|
|
573
|
+
* @returns A Date object representing the parsed date
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```typescript
|
|
577
|
+
* const date = dateUtil.getDate('2025-01-15');
|
|
578
|
+
* // Returns: Date object for January 15, 2025
|
|
579
|
+
* ```
|
|
580
|
+
*/
|
|
442
581
|
getDate(date) {
|
|
443
582
|
const dateParts = date.split('T')[0].split('-');
|
|
444
583
|
return new Date(Number(dateParts[0]), Number(dateParts[1]) - 1, Number(dateParts[2]));
|
|
445
584
|
}
|
|
446
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
447
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
448
|
-
}
|
|
449
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: DateService, decorators: [{
|
|
450
|
-
type: Injectable,
|
|
451
|
-
args: [{
|
|
452
|
-
providedIn: 'root'
|
|
453
|
-
}]
|
|
454
|
-
}] });
|
|
455
|
-
|
|
456
|
-
class FilesService {
|
|
457
|
-
callback;
|
|
458
|
-
fileInput;
|
|
459
|
-
pickerClosed = false;
|
|
460
|
-
constructor() {
|
|
461
|
-
this.fileInput = document.createElement('input');
|
|
462
|
-
this.fileInput.type = 'file';
|
|
463
|
-
this.fileInput.addEventListener('change', (event) => {
|
|
464
|
-
this.pickerClosed = true;
|
|
465
|
-
this.loadValue(event.currentTarget.files);
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
loadValue(files) {
|
|
469
|
-
if (files && files.length > 0) {
|
|
470
|
-
let data = [];
|
|
471
|
-
for (let i = 0; i < files.length; i++) {
|
|
472
|
-
const file = files.item(i);
|
|
473
|
-
const reader = new FileReader();
|
|
474
|
-
reader.readAsDataURL(file);
|
|
475
|
-
reader.onload = () => {
|
|
476
|
-
data.push(Object.assign(file, {
|
|
477
|
-
data: reader.result,
|
|
478
|
-
}));
|
|
479
|
-
if (data.length == files.length) {
|
|
480
|
-
this.callback(data.length > 0 ? data : null);
|
|
481
|
-
this.fileInput.value = '';
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
this.callback(null);
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
open(callback, options) {
|
|
491
|
-
this.pickerClosed = false;
|
|
492
|
-
// Detectar cuando el usuario vuelve a la ventana después de abrir el picker
|
|
493
|
-
const onFocus = () => {
|
|
494
|
-
setTimeout(() => {
|
|
495
|
-
if (!this.pickerClosed) {
|
|
496
|
-
console.log('El usuario cerró el file picker sin seleccionar archivos.');
|
|
497
|
-
this.loadValue(null);
|
|
498
|
-
}
|
|
499
|
-
window.removeEventListener('focus', onFocus);
|
|
500
|
-
}, 500); // Esperamos un poco para asegurarnos de que onchange haya tenido oportunidad de ejecutarse
|
|
501
|
-
};
|
|
502
|
-
window.addEventListener('focus', onFocus);
|
|
503
|
-
this.fileInput.accept = options?.accept ? options.accept : '';
|
|
504
|
-
this.fileInput.multiple = options?.multiple || false;
|
|
505
|
-
this.fileInput.click();
|
|
506
|
-
if (callback) {
|
|
507
|
-
this.callback = callback;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FilesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
511
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FilesService, providedIn: 'root' });
|
|
585
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
586
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateUtil, providedIn: 'root' });
|
|
512
587
|
}
|
|
513
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
588
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: DateUtil, decorators: [{
|
|
514
589
|
type: Injectable,
|
|
515
590
|
args: [{
|
|
516
591
|
providedIn: 'root',
|
|
517
592
|
}]
|
|
518
|
-
}]
|
|
593
|
+
}] });
|
|
519
594
|
|
|
520
|
-
|
|
521
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Service for opening file picker dialogs and reading file contents.
|
|
597
|
+
*
|
|
598
|
+
* @remarks
|
|
599
|
+
* Provides a programmatic way to open file selection dialogs and read file data as base64.
|
|
600
|
+
*/
|
|
601
|
+
class FilePicker {
|
|
602
|
+
/**
|
|
603
|
+
* Opens a file picker dialog and returns the selected files with their data as base64.
|
|
604
|
+
*
|
|
605
|
+
* @param options - Optional configuration for the file picker
|
|
606
|
+
* @param options.accept - File types to accept (e.g., 'image/*', '.pdf')
|
|
607
|
+
* @param options.multiple - Whether to allow multiple file selection
|
|
608
|
+
* @returns Promise that resolves to an array of files with data property, or null if cancelled
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* ```typescript
|
|
612
|
+
* const files = await filePicker.open({ accept: 'image/*', multiple: true });
|
|
613
|
+
* if (files) {
|
|
614
|
+
* files.forEach(file => console.log(file.data)); // base64 data
|
|
615
|
+
* }
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
522
618
|
async open(options) {
|
|
523
619
|
return new Promise((resolve, reject) => {
|
|
524
620
|
const fileInput = document.createElement('input');
|
|
525
621
|
fileInput.type = 'file';
|
|
526
|
-
fileInput.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}));
|
|
539
|
-
if (data.length == files.length) {
|
|
540
|
-
fileInput.value = '';
|
|
541
|
-
resolve(data.length > 0 ? data : null);
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
resolve(null);
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
}
|
|
622
|
+
fileInput.style.display = 'none';
|
|
623
|
+
if (options?.accept) {
|
|
624
|
+
fileInput.accept = options.accept;
|
|
625
|
+
}
|
|
626
|
+
fileInput.multiple = options?.multiple ?? false;
|
|
627
|
+
document.body.appendChild(fileInput);
|
|
628
|
+
let changeHandled = false;
|
|
629
|
+
const cleanUp = () => {
|
|
630
|
+
window.removeEventListener('focus', onFocus);
|
|
631
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
632
|
+
if (fileInput.parentNode) {
|
|
633
|
+
document.body.removeChild(fileInput);
|
|
548
634
|
}
|
|
549
|
-
}
|
|
550
|
-
this.pickerClosed = false;
|
|
551
|
-
// Detectar cuando el usuario vuelve a la ventana después de abrir el picker
|
|
635
|
+
};
|
|
552
636
|
const onFocus = () => {
|
|
553
637
|
setTimeout(() => {
|
|
554
|
-
if (!
|
|
555
|
-
|
|
638
|
+
if (!changeHandled) {
|
|
639
|
+
cleanUp();
|
|
640
|
+
resolve(null);
|
|
556
641
|
}
|
|
557
|
-
|
|
558
|
-
}, 500); // Esperamos un poco para asegurarnos de que onchange haya tenido oportunidad de ejecutarse
|
|
642
|
+
}, 1500);
|
|
559
643
|
};
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FilePickerService, decorators: [{
|
|
570
|
-
type: Injectable,
|
|
571
|
-
args: [{
|
|
572
|
-
providedIn: 'root'
|
|
573
|
-
}]
|
|
574
|
-
}] });
|
|
575
|
-
|
|
576
|
-
class GoogleTagManagerService {
|
|
577
|
-
platformId;
|
|
578
|
-
router;
|
|
579
|
-
trackingId;
|
|
580
|
-
constructor(platformId, router) {
|
|
581
|
-
this.platformId = platformId;
|
|
582
|
-
this.router = router;
|
|
583
|
-
}
|
|
584
|
-
appendTrackingCode(trackingId, options) {
|
|
585
|
-
try {
|
|
586
|
-
if (isPlatformBrowser(this.platformId) && trackingId) {
|
|
587
|
-
this.trackingId = trackingId;
|
|
588
|
-
const s1 = document.createElement('script');
|
|
589
|
-
s1.innerHTML = `
|
|
590
|
-
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
591
|
-
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
592
|
-
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'',p='${options?.environment?.preview
|
|
593
|
-
? '>m_auth=' + options?.environment.auth + '>m_preview=' + options?.environment.preview
|
|
594
|
-
: ''}';j.async=true;j.src=
|
|
595
|
-
'https://www.googletagmanager.com/gtm.js?id='+i+dl+p;f.parentNode.insertBefore(j,f);
|
|
596
|
-
})(window,document,'script','dataLayer','${trackingId}');
|
|
597
|
-
`;
|
|
598
|
-
document.head.appendChild(s1);
|
|
599
|
-
const s2 = document.createElement('noscript');
|
|
600
|
-
const s3 = document.createElement('iframe');
|
|
601
|
-
s3.width = '0';
|
|
602
|
-
s3.height = '0';
|
|
603
|
-
s3.style.display = 'none';
|
|
604
|
-
s3.style.visibility = 'hidden';
|
|
605
|
-
s3.src = `//www.googletagmanager.com/ns.html?id=${trackingId}`;
|
|
606
|
-
s2.appendChild(s3);
|
|
607
|
-
document.body.prepend(s2);
|
|
608
|
-
this.initSubscribers();
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
catch (ex) {
|
|
612
|
-
console.error('Error appending google tag manager');
|
|
613
|
-
console.error(ex);
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
addVariable(variable) {
|
|
617
|
-
if (isPlatformBrowser(this.platformId) && this.trackingId) {
|
|
618
|
-
window.dataLayer = window.dataLayer || [];
|
|
619
|
-
window.dataLayer.push(variable);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
initSubscribers() {
|
|
623
|
-
this.router.events.subscribe(event => {
|
|
624
|
-
try {
|
|
625
|
-
if (event instanceof NavigationEnd && this.trackingId) {
|
|
626
|
-
this.addVariable({
|
|
627
|
-
event: 'router.NavigationEnd',
|
|
628
|
-
pageTitle: document.title,
|
|
629
|
-
pagePath: event.urlAfterRedirects
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
catch (e) {
|
|
634
|
-
console.error(e);
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: GoogleTagManagerService, deps: [{ token: PLATFORM_ID }, { token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
639
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: GoogleTagManagerService, providedIn: 'root' });
|
|
640
|
-
}
|
|
641
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: GoogleTagManagerService, decorators: [{
|
|
642
|
-
type: Injectable,
|
|
643
|
-
args: [{
|
|
644
|
-
providedIn: 'root'
|
|
645
|
-
}]
|
|
646
|
-
}], ctorParameters: () => [{ type: Object, decorators: [{
|
|
647
|
-
type: Inject,
|
|
648
|
-
args: [PLATFORM_ID]
|
|
649
|
-
}] }, { type: i1.Router }] });
|
|
650
|
-
|
|
651
|
-
class StringService {
|
|
652
|
-
decodeHTML(text) {
|
|
653
|
-
const span = document.createElement('span');
|
|
654
|
-
return text.replace(/&[#A-Za-z0-9]+;/gi, (entity) => {
|
|
655
|
-
span.innerHTML = entity;
|
|
656
|
-
return span.innerText;
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
/**
|
|
660
|
-
* Normaliza un texto para evitar el Case y los acentos
|
|
661
|
-
* @param text texto a normalizar
|
|
662
|
-
* @returns texto normalizado
|
|
663
|
-
*/
|
|
664
|
-
normalize(text) {
|
|
665
|
-
if (!text)
|
|
666
|
-
return '';
|
|
667
|
-
return text
|
|
668
|
-
.toLowerCase()
|
|
669
|
-
.normalize("NFD")
|
|
670
|
-
.replace(/[\u0300-\u036f]/g, "");
|
|
671
|
-
}
|
|
672
|
-
normalizeName(text) {
|
|
673
|
-
text = text.trim();
|
|
674
|
-
text = text.charAt(0).toUpperCase() + text.slice(1);
|
|
675
|
-
return text;
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* Te da el tiempo en milisegundos que toma leer un texto dado
|
|
679
|
-
* @param text texto a leer
|
|
680
|
-
* @return number Tiempo en milisegundos
|
|
681
|
-
*/
|
|
682
|
-
calculateReadingTime(text) {
|
|
683
|
-
// Caracteres por minuto
|
|
684
|
-
const cpm = 1200;
|
|
685
|
-
// Calcular el número de caracteres en el texto
|
|
686
|
-
const numCharacters = text.length;
|
|
687
|
-
// Calcular el tiempo de lectura en minutos
|
|
688
|
-
const readingTimeMinutes = numCharacters / cpm;
|
|
689
|
-
// Convertir el tiempo de lectura de minutos a milisegundos
|
|
690
|
-
const readingTimeMilliseconds = readingTimeMinutes * 60 * 1000;
|
|
691
|
-
return readingTimeMilliseconds;
|
|
692
|
-
}
|
|
693
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: StringService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
694
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: StringService, providedIn: 'root' });
|
|
695
|
-
}
|
|
696
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: StringService, decorators: [{
|
|
697
|
-
type: Injectable,
|
|
698
|
-
args: [{
|
|
699
|
-
providedIn: 'root'
|
|
700
|
-
}]
|
|
701
|
-
}] });
|
|
702
|
-
|
|
703
|
-
class GraphqlService {
|
|
704
|
-
apollo = inject(Apollo);
|
|
705
|
-
stringService = inject(StringService);
|
|
706
|
-
mutation(operation, type, entity) {
|
|
707
|
-
let mutation;
|
|
708
|
-
const capitalizedType = this.stringService.normalizeName(type);
|
|
709
|
-
switch (operation) {
|
|
710
|
-
case 'create':
|
|
711
|
-
entity.uuid = entity.id ? entity.id.split('/').pop() : crypto.randomUUID();
|
|
712
|
-
delete entity.createdBy;
|
|
713
|
-
delete entity.updatedBy;
|
|
714
|
-
delete entity.id;
|
|
715
|
-
mutation = this.apollo.mutate({
|
|
716
|
-
mutation: gql `
|
|
717
|
-
mutation Create${capitalizedType}($entity: create${capitalizedType}Input!) {
|
|
718
|
-
create${capitalizedType}(input: $entity) {
|
|
719
|
-
${type} {
|
|
720
|
-
id
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
`,
|
|
725
|
-
variables: {
|
|
726
|
-
entity
|
|
727
|
-
}
|
|
728
|
-
});
|
|
729
|
-
break;
|
|
730
|
-
case 'delete':
|
|
731
|
-
mutation = this.apollo.mutate({
|
|
732
|
-
mutation: gql `
|
|
733
|
-
mutation Delete${capitalizedType}($id: ID!) {
|
|
734
|
-
delete${capitalizedType}(input: {id: $id}) {
|
|
735
|
-
clientMutationId
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
`,
|
|
739
|
-
variables: {
|
|
740
|
-
id: entity.id
|
|
741
|
-
}
|
|
742
|
-
});
|
|
743
|
-
break;
|
|
744
|
-
case 'update':
|
|
745
|
-
delete entity.createdBy;
|
|
746
|
-
delete entity.updatedBy;
|
|
747
|
-
mutation = this.apollo.mutate({
|
|
748
|
-
mutation: gql `
|
|
749
|
-
mutation Update${capitalizedType}($entity: update${capitalizedType}Input!) {
|
|
750
|
-
update${capitalizedType}(input: $entity) {
|
|
751
|
-
${type} {
|
|
752
|
-
id
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
`,
|
|
757
|
-
variables: {
|
|
758
|
-
entity
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
break;
|
|
762
|
-
}
|
|
763
|
-
return lastValueFrom(mutation);
|
|
764
|
-
}
|
|
765
|
-
parseQuery(query) {
|
|
766
|
-
if (query !== null) {
|
|
767
|
-
const data = {};
|
|
768
|
-
for (const property in query) {
|
|
769
|
-
if (query[property]?.edges) {
|
|
770
|
-
data[property] = this.parseEdges(query[property].edges);
|
|
644
|
+
const onVisibilityChange = () => {
|
|
645
|
+
if (document.visibilityState === 'visible') {
|
|
646
|
+
setTimeout(() => {
|
|
647
|
+
if (!changeHandled) {
|
|
648
|
+
cleanUp();
|
|
649
|
+
resolve(null);
|
|
650
|
+
}
|
|
651
|
+
}, 1500);
|
|
771
652
|
}
|
|
772
|
-
|
|
773
|
-
|
|
653
|
+
};
|
|
654
|
+
fileInput.addEventListener('change', async (event) => {
|
|
655
|
+
changeHandled = true;
|
|
656
|
+
const files = Array.from(event.target.files || []);
|
|
657
|
+
if (!files.length) {
|
|
658
|
+
cleanUp();
|
|
659
|
+
resolve(null);
|
|
660
|
+
return;
|
|
774
661
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
662
|
+
try {
|
|
663
|
+
const result = await Promise.all(files.map(file => {
|
|
664
|
+
return new Promise((res, rej) => {
|
|
665
|
+
const reader = new FileReader();
|
|
666
|
+
reader.onload = () => {
|
|
667
|
+
res(Object.assign(file, { data: reader.result }));
|
|
668
|
+
};
|
|
669
|
+
reader.onerror = (err) => {
|
|
670
|
+
rej(reader.error);
|
|
671
|
+
};
|
|
672
|
+
reader.readAsDataURL(file);
|
|
780
673
|
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
else if (typeof query[property] === 'object') {
|
|
787
|
-
data[property] = this.parseQuery(query[property]);
|
|
674
|
+
}));
|
|
675
|
+
cleanUp();
|
|
676
|
+
resolve(result);
|
|
788
677
|
}
|
|
789
|
-
|
|
790
|
-
|
|
678
|
+
catch (error) {
|
|
679
|
+
cleanUp();
|
|
680
|
+
reject(error);
|
|
791
681
|
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
return query;
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
parseEdges(edges) {
|
|
800
|
-
return edges.map((entity) => {
|
|
801
|
-
return this.parseQuery(entity?.node || entity);
|
|
682
|
+
});
|
|
683
|
+
window.addEventListener('focus', onFocus);
|
|
684
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
685
|
+
fileInput.click();
|
|
802
686
|
});
|
|
803
687
|
}
|
|
804
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
805
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
688
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FilePicker, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
689
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FilePicker, providedIn: 'root' });
|
|
806
690
|
}
|
|
807
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
691
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: FilePicker, decorators: [{
|
|
808
692
|
type: Injectable,
|
|
809
693
|
args: [{
|
|
810
|
-
providedIn: 'root'
|
|
694
|
+
providedIn: 'root',
|
|
811
695
|
}]
|
|
812
696
|
}] });
|
|
813
697
|
|
|
814
|
-
|
|
698
|
+
/**
|
|
699
|
+
* Utility service for object operations.
|
|
700
|
+
*
|
|
701
|
+
* @remarks
|
|
702
|
+
* Provides helper methods for object manipulation and transformation.
|
|
703
|
+
*/
|
|
704
|
+
class ObjectUtil {
|
|
705
|
+
/**
|
|
706
|
+
* Filters out null, undefined, and 'undefined' string properties from an object.
|
|
707
|
+
*
|
|
708
|
+
* @param obj - The object to filter
|
|
709
|
+
* @returns A new object with null/undefined properties removed
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```typescript
|
|
713
|
+
* const filtered = objectUtil.filterNullProperties({ a: 1, b: null, c: undefined });
|
|
714
|
+
* // Returns: { a: 1 }
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
815
717
|
filterNullProperties(obj) {
|
|
816
718
|
const mappedObj = {};
|
|
817
719
|
Object.keys(obj).forEach((key) => {
|
|
@@ -821,6 +723,22 @@ class ObjectService {
|
|
|
821
723
|
});
|
|
822
724
|
return mappedObj;
|
|
823
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Performs a deep merge of two objects, combining nested properties.
|
|
728
|
+
*
|
|
729
|
+
* @param target - The target object to merge into
|
|
730
|
+
* @param source - The source object to merge from
|
|
731
|
+
* @returns A new object with deeply merged properties
|
|
732
|
+
*
|
|
733
|
+
* @remarks
|
|
734
|
+
* Uses structuredClone to avoid mutating the original objects.
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```typescript
|
|
738
|
+
* const merged = objectUtil.deepMerge({ a: { b: 1 } }, { a: { c: 2 } });
|
|
739
|
+
* // Returns: { a: { b: 1, c: 2 } }
|
|
740
|
+
* ```
|
|
741
|
+
*/
|
|
824
742
|
deepMerge(target, source) {
|
|
825
743
|
source = structuredClone(source);
|
|
826
744
|
target = structuredClone(target);
|
|
@@ -831,23 +749,34 @@ class ObjectService {
|
|
|
831
749
|
}
|
|
832
750
|
return { ...target, ...source };
|
|
833
751
|
}
|
|
834
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
835
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
752
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ObjectUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
753
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ObjectUtil, providedIn: 'root' });
|
|
836
754
|
}
|
|
837
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
755
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: ObjectUtil, decorators: [{
|
|
838
756
|
type: Injectable,
|
|
839
757
|
args: [{
|
|
840
|
-
providedIn: 'root'
|
|
758
|
+
providedIn: 'root',
|
|
841
759
|
}]
|
|
842
760
|
}] });
|
|
843
761
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
762
|
+
/**
|
|
763
|
+
* Service for managing browser storage (localStorage, sessionStorage, and memory storage).
|
|
764
|
+
*
|
|
765
|
+
* @remarks
|
|
766
|
+
* Provides a unified interface for storing and retrieving data from different storage types.
|
|
767
|
+
* Automatically handles JSON serialization/deserialization and platform detection.
|
|
768
|
+
*
|
|
769
|
+
* @example
|
|
770
|
+
* ```typescript
|
|
771
|
+
* const storage = inject(Storage);
|
|
772
|
+
* storage.set('key', { data: 'value' }, 'local');
|
|
773
|
+
* const value = storage.get('key', 'local');
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
class Storage {
|
|
777
|
+
// TODO: Replace with Map object it is more efficient
|
|
778
|
+
memoryStorage = {};
|
|
779
|
+
platformId = inject(PLATFORM_ID);
|
|
851
780
|
getValue(key, storage) {
|
|
852
781
|
let value;
|
|
853
782
|
if (!storage || typeof storage == 'string') {
|
|
@@ -868,6 +797,12 @@ class StorageService {
|
|
|
868
797
|
}
|
|
869
798
|
return value;
|
|
870
799
|
}
|
|
800
|
+
/**
|
|
801
|
+
* Deletes a value from the specified storage.
|
|
802
|
+
*
|
|
803
|
+
* @param key - The key of the value to delete
|
|
804
|
+
* @param storage - The storage type ('local', 'session', or 'memory'). Defaults to 'session'
|
|
805
|
+
*/
|
|
871
806
|
delete(key, storage) {
|
|
872
807
|
if (isPlatformBrowser(this.platformId)) {
|
|
873
808
|
if (!storage || typeof storage == 'string') {
|
|
@@ -888,6 +823,16 @@ class StorageService {
|
|
|
888
823
|
}
|
|
889
824
|
}
|
|
890
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* Retrieves a value from the specified storage.
|
|
828
|
+
*
|
|
829
|
+
* @param key - The key of the value to retrieve
|
|
830
|
+
* @param storage - The storage type ('local', 'session', or 'memory'). Defaults to 'session'
|
|
831
|
+
* @returns The parsed value (if JSON) or the raw value, or undefined if not found
|
|
832
|
+
*
|
|
833
|
+
* @remarks
|
|
834
|
+
* Automatically attempts to parse JSON values. If parsing fails, returns the raw string.
|
|
835
|
+
*/
|
|
891
836
|
get(key, storage) {
|
|
892
837
|
let parsedValue;
|
|
893
838
|
if (isPlatformBrowser(this.platformId)) {
|
|
@@ -900,6 +845,16 @@ class StorageService {
|
|
|
900
845
|
}
|
|
901
846
|
return parsedValue;
|
|
902
847
|
}
|
|
848
|
+
/**
|
|
849
|
+
* Stores a value in the specified storage.
|
|
850
|
+
*
|
|
851
|
+
* @param key - The key to store the value under
|
|
852
|
+
* @param value - The value to store (will be JSON stringified)
|
|
853
|
+
* @param storage - The storage type ('local', 'session', or 'memory'). Defaults to 'session'
|
|
854
|
+
*
|
|
855
|
+
* @remarks
|
|
856
|
+
* Values are automatically JSON stringified before storage.
|
|
857
|
+
*/
|
|
903
858
|
set(key, value, storage) {
|
|
904
859
|
if (isPlatformBrowser(this.platformId)) {
|
|
905
860
|
const valueString = JSON.stringify(value);
|
|
@@ -918,23 +873,240 @@ class StorageService {
|
|
|
918
873
|
}
|
|
919
874
|
}
|
|
920
875
|
}
|
|
921
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
922
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "
|
|
876
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: Storage, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
877
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: Storage, providedIn: 'root' });
|
|
923
878
|
}
|
|
924
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
879
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: Storage, decorators: [{
|
|
925
880
|
type: Injectable,
|
|
926
881
|
args: [{
|
|
927
882
|
providedIn: 'root',
|
|
928
883
|
}]
|
|
929
|
-
}]
|
|
930
|
-
type: Inject,
|
|
931
|
-
args: [PLATFORM_ID]
|
|
932
|
-
}] }] });
|
|
884
|
+
}] });
|
|
933
885
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
886
|
+
/**
|
|
887
|
+
* Utility service for string operations.
|
|
888
|
+
*
|
|
889
|
+
* @remarks
|
|
890
|
+
* Provides helper methods for common string manipulation and formatting tasks.
|
|
891
|
+
*/
|
|
892
|
+
class StringUtil {
|
|
893
|
+
/**
|
|
894
|
+
* Decodes HTML entities in a string.
|
|
895
|
+
*
|
|
896
|
+
* @param text - The string containing HTML entities to decode
|
|
897
|
+
* @returns The decoded string
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* ```typescript
|
|
901
|
+
* const encoded = '<div>Hello</div>';
|
|
902
|
+
* const decoded = stringUtil.decodeHTML(encoded);
|
|
903
|
+
* // Returns: '<div>Hello</div>'
|
|
904
|
+
* ```
|
|
905
|
+
*/
|
|
906
|
+
decodeHTML(text) {
|
|
907
|
+
const span = document.createElement('span');
|
|
908
|
+
return text.replace(/&[#A-Za-z0-9]+;/gi, (entity) => {
|
|
909
|
+
span.innerHTML = entity;
|
|
910
|
+
return span.innerText;
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Normalizes a text by converting to lowercase and removing accents.
|
|
915
|
+
*
|
|
916
|
+
* @param text - The text to normalize
|
|
917
|
+
* @returns The normalized text without accents and in lowercase
|
|
918
|
+
*
|
|
919
|
+
* @example
|
|
920
|
+
* ```typescript
|
|
921
|
+
* const normalized = stringUtil.normalize('Café');
|
|
922
|
+
* // Returns: 'cafe'
|
|
923
|
+
* ```
|
|
924
|
+
*/
|
|
925
|
+
normalize(text) {
|
|
926
|
+
if (!text)
|
|
927
|
+
return '';
|
|
928
|
+
return text
|
|
929
|
+
.toLowerCase()
|
|
930
|
+
.normalize("NFD")
|
|
931
|
+
.replace(/[\u0300-\u036f]/g, "");
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Normalizes a name by trimming whitespace and capitalizing the first letter.
|
|
935
|
+
*
|
|
936
|
+
* @param text - The name text to normalize
|
|
937
|
+
* @returns The normalized name with first letter capitalized
|
|
938
|
+
*
|
|
939
|
+
* @example
|
|
940
|
+
* ```typescript
|
|
941
|
+
* const normalized = stringUtil.normalizeName(' john doe ');
|
|
942
|
+
* // Returns: 'John doe'
|
|
943
|
+
* ```
|
|
944
|
+
*/
|
|
945
|
+
normalizeName(text) {
|
|
946
|
+
text = text.trim();
|
|
947
|
+
text = text.charAt(0).toUpperCase() + text.slice(1);
|
|
948
|
+
return text;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Calculates the estimated reading time in milliseconds for a given text.
|
|
952
|
+
*
|
|
953
|
+
* @param text - The text to calculate reading time for
|
|
954
|
+
* @returns The estimated reading time in milliseconds
|
|
955
|
+
*
|
|
956
|
+
* @remarks
|
|
957
|
+
* Uses a reading speed of 1200 characters per minute.
|
|
958
|
+
*
|
|
959
|
+
* @example
|
|
960
|
+
* ```typescript
|
|
961
|
+
* const readingTime = stringUtil.calculateReadingTime('Long text here...');
|
|
962
|
+
* // Returns: estimated milliseconds
|
|
963
|
+
* ```
|
|
964
|
+
*/
|
|
965
|
+
calculateReadingTime(text) {
|
|
966
|
+
// Characters per minute
|
|
967
|
+
const cpm = 1200;
|
|
968
|
+
// Calculate the number of characters in the text
|
|
969
|
+
const numCharacters = text.length;
|
|
970
|
+
// Calculate reading time in minutes
|
|
971
|
+
const readingTimeMinutes = numCharacters / cpm;
|
|
972
|
+
// Convert reading time from minutes to milliseconds
|
|
973
|
+
const readingTimeMilliseconds = readingTimeMinutes * 60 * 1000;
|
|
974
|
+
return readingTimeMilliseconds;
|
|
975
|
+
}
|
|
976
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StringUtil, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
977
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StringUtil, providedIn: 'root' });
|
|
978
|
+
}
|
|
979
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: StringUtil, decorators: [{
|
|
980
|
+
type: Injectable,
|
|
981
|
+
args: [{
|
|
982
|
+
providedIn: 'root',
|
|
983
|
+
}]
|
|
984
|
+
}] });
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Service for integrating Google Tag Manager (GTM) tracking.
|
|
988
|
+
*
|
|
989
|
+
* @remarks
|
|
990
|
+
* Handles GTM script injection, variable tracking, and automatic route change tracking.
|
|
991
|
+
*
|
|
992
|
+
* @example
|
|
993
|
+
* ```typescript
|
|
994
|
+
* const gtm = inject(GoogleTagManager);
|
|
995
|
+
* gtm.appendTrackingCode('GTM-XXXXXXX');
|
|
996
|
+
* gtm.addVariable({ event: 'customEvent', data: 'value' });
|
|
997
|
+
* ```
|
|
998
|
+
*/
|
|
999
|
+
class GoogleTagManager {
|
|
1000
|
+
trackingId;
|
|
1001
|
+
platformId = inject(PLATFORM_ID);
|
|
1002
|
+
router = inject(Router);
|
|
1003
|
+
/**
|
|
1004
|
+
* Appends Google Tag Manager tracking code to the page.
|
|
1005
|
+
*
|
|
1006
|
+
* @param trackingId - The GTM container ID (e.g., 'GTM-XXXXXXX')
|
|
1007
|
+
* @param options - Optional configuration for GTM environment
|
|
1008
|
+
* @param options.environment - Environment configuration for preview mode
|
|
1009
|
+
* @param options.environment.auth - Authentication token for preview
|
|
1010
|
+
* @param options.environment.preview - Preview container ID
|
|
1011
|
+
*
|
|
1012
|
+
* @remarks
|
|
1013
|
+
* Automatically subscribes to router navigation events for page view tracking.
|
|
1014
|
+
*/
|
|
1015
|
+
appendTrackingCode(trackingId, options) {
|
|
1016
|
+
try {
|
|
1017
|
+
if (isPlatformBrowser(this.platformId) && trackingId) {
|
|
1018
|
+
this.trackingId = trackingId;
|
|
1019
|
+
const s1 = document.createElement('script');
|
|
1020
|
+
s1.innerHTML = `
|
|
1021
|
+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
|
1022
|
+
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
|
1023
|
+
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'',p='${options?.environment?.preview
|
|
1024
|
+
? '>m_auth=' + options?.environment.auth + '>m_preview=' + options?.environment.preview
|
|
1025
|
+
: ''}';j.async=true;j.src=
|
|
1026
|
+
'https://www.googletagmanager.com/gtm.js?id='+i+dl+p;f.parentNode.insertBefore(j,f);
|
|
1027
|
+
})(window,document,'script','dataLayer','${trackingId}');
|
|
1028
|
+
`;
|
|
1029
|
+
document.head.appendChild(s1);
|
|
1030
|
+
const s2 = document.createElement('noscript');
|
|
1031
|
+
const s3 = document.createElement('iframe');
|
|
1032
|
+
s3.width = '0';
|
|
1033
|
+
s3.height = '0';
|
|
1034
|
+
s3.style.display = 'none';
|
|
1035
|
+
s3.style.visibility = 'hidden';
|
|
1036
|
+
s3.src = `//www.googletagmanager.com/ns.html?id=${trackingId}`;
|
|
1037
|
+
s2.appendChild(s3);
|
|
1038
|
+
document.body.prepend(s2);
|
|
1039
|
+
this.initSubscribers();
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
catch (ex) {
|
|
1043
|
+
console.error('Error appending google tag manager');
|
|
1044
|
+
console.error(ex);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Pushes a variable or event to the GTM dataLayer.
|
|
1049
|
+
*
|
|
1050
|
+
* @param variable - The variable or event object to push to dataLayer
|
|
1051
|
+
*
|
|
1052
|
+
* @example
|
|
1053
|
+
* ```typescript
|
|
1054
|
+
* gtm.addVariable({ event: 'purchase', value: 100 });
|
|
1055
|
+
* ```
|
|
1056
|
+
*/
|
|
1057
|
+
addVariable(variable) {
|
|
1058
|
+
if (isPlatformBrowser(this.platformId) && this.trackingId) {
|
|
1059
|
+
window.dataLayer = window.dataLayer || [];
|
|
1060
|
+
window.dataLayer.push(variable);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Initializes router event subscribers for automatic page view tracking.
|
|
1065
|
+
*
|
|
1066
|
+
* @private
|
|
1067
|
+
*/
|
|
1068
|
+
initSubscribers() {
|
|
1069
|
+
this.router.events.subscribe(event => {
|
|
1070
|
+
try {
|
|
1071
|
+
if (event instanceof NavigationEnd && this.trackingId) {
|
|
1072
|
+
this.addVariable({
|
|
1073
|
+
event: 'router.NavigationEnd',
|
|
1074
|
+
pageTitle: document.title,
|
|
1075
|
+
pagePath: event.urlAfterRedirects
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
catch (e) {
|
|
1080
|
+
console.error(e);
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GoogleTagManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1085
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GoogleTagManager, providedIn: 'root' });
|
|
1086
|
+
}
|
|
1087
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: GoogleTagManager, decorators: [{
|
|
1088
|
+
type: Injectable,
|
|
1089
|
+
args: [{
|
|
1090
|
+
providedIn: 'root',
|
|
1091
|
+
}]
|
|
1092
|
+
}] });
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Constants for CRUD operation types.
|
|
1096
|
+
*/
|
|
1097
|
+
const OPERATION_TYPE = {
|
|
1098
|
+
/** Create operation */
|
|
1099
|
+
CREATE: 'create',
|
|
1100
|
+
/** Update operation */
|
|
1101
|
+
UPDATE: 'update',
|
|
1102
|
+
/** Delete operation */
|
|
1103
|
+
DELETE: 'delete'
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
function lengthValidator(number, digits) {
|
|
1107
|
+
let value = true;
|
|
1108
|
+
if (number.trim() === '') { // No puede estar vacio
|
|
1109
|
+
value = false;
|
|
938
1110
|
}
|
|
939
1111
|
else if (number.trim().length !== digits) { // Cantidad de dígitos
|
|
940
1112
|
value = false;
|
|
@@ -1051,6 +1223,18 @@ function storeCodeValidator(number) {
|
|
|
1051
1223
|
}
|
|
1052
1224
|
return value;
|
|
1053
1225
|
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Determines the type of Ecuadorian identification number.
|
|
1228
|
+
*
|
|
1229
|
+
* @param number - The identification number to analyze
|
|
1230
|
+
* @returns The identification type ('cedula', 'ruc_natural', 'ruc_privada', 'ruc_publica') or null if invalid
|
|
1231
|
+
*
|
|
1232
|
+
* @example
|
|
1233
|
+
* ```typescript
|
|
1234
|
+
* const type = getIdentificationType('1234567890');
|
|
1235
|
+
* // Returns: 'cedula' or null
|
|
1236
|
+
* ```
|
|
1237
|
+
*/
|
|
1054
1238
|
function getIdentificationType(number) {
|
|
1055
1239
|
let type = null;
|
|
1056
1240
|
if (lengthValidator(number, 10) &&
|
|
@@ -1085,6 +1269,23 @@ function getIdentificationType(number) {
|
|
|
1085
1269
|
return type;
|
|
1086
1270
|
}
|
|
1087
1271
|
|
|
1272
|
+
/**
|
|
1273
|
+
* Creates a validator function for Ecuadorian identification numbers (cédula or RUC).
|
|
1274
|
+
*
|
|
1275
|
+
* @param type - The type of identification to validate
|
|
1276
|
+
* - 'cedula': Validates cédula (10 digits)
|
|
1277
|
+
* - 'ruc_natural': Validates RUC for natural persons (13 digits)
|
|
1278
|
+
* - 'ruc_privada': Validates RUC for private companies (13 digits)
|
|
1279
|
+
* - 'ruc_publica': Validates RUC for public companies (13 digits)
|
|
1280
|
+
* - 'ruc': Validates any type of RUC
|
|
1281
|
+
* - 'id': Validates any valid identification type
|
|
1282
|
+
* @returns A validator function that returns an error object if validation fails
|
|
1283
|
+
*
|
|
1284
|
+
* @example
|
|
1285
|
+
* ```typescript
|
|
1286
|
+
* const control = new FormControl('', [identificationValidator('cedula')]);
|
|
1287
|
+
* ```
|
|
1288
|
+
*/
|
|
1088
1289
|
function identificationValidator(type) {
|
|
1089
1290
|
return (control) => {
|
|
1090
1291
|
const number = String(control.value);
|
|
@@ -1114,9 +1315,440 @@ function identificationValidator(type) {
|
|
|
1114
1315
|
};
|
|
1115
1316
|
}
|
|
1116
1317
|
|
|
1318
|
+
class AuthProvider {
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Injection token for auth configuration.
|
|
1323
|
+
* Provide this in app.config with values from environment.
|
|
1324
|
+
*
|
|
1325
|
+
* @example
|
|
1326
|
+
* ```typescript
|
|
1327
|
+
* import { AUTH_CONFIG } from 'auth-core';
|
|
1328
|
+
* import { environment } from '@/environments/environment';
|
|
1329
|
+
*
|
|
1330
|
+
* providers: [
|
|
1331
|
+
* { provide: AUTH_CONFIG, useValue: environment }
|
|
1332
|
+
* ]
|
|
1333
|
+
* ```
|
|
1334
|
+
*/
|
|
1335
|
+
const AUTH_CONFIG = new InjectionToken('AUTH_CONFIG');
|
|
1336
|
+
|
|
1337
|
+
/**
|
|
1338
|
+
* Concrete authentication provider responsible for handling session lifecycle,
|
|
1339
|
+
* secure token refresh, and profile management concerns across the app.
|
|
1340
|
+
*
|
|
1341
|
+
* @remarks
|
|
1342
|
+
* The service extends {@link AuthProvider} to leverage core session helpers while
|
|
1343
|
+
* adding stateful logic for dialogs, social login, and settings synchronization.
|
|
1344
|
+
*/
|
|
1345
|
+
class AuthService extends AuthProvider {
|
|
1346
|
+
// Dependency injection
|
|
1347
|
+
config = inject(AUTH_CONFIG);
|
|
1348
|
+
dialog = inject(MatDialog);
|
|
1349
|
+
httpClient = inject(HttpClient);
|
|
1350
|
+
_user = signal(null, ...(ngDevMode ? [{ debugName: "_user" }] : /* istanbul ignore next */ []));
|
|
1351
|
+
/** Session token key */
|
|
1352
|
+
TOKEN_KEY = `${this.config.sessionPrefix}_sess`;
|
|
1353
|
+
user = computed(() => this._user(), ...(ngDevMode ? [{ debugName: "user" }] : /* istanbul ignore next */ []));
|
|
1354
|
+
constructor() {
|
|
1355
|
+
super();
|
|
1356
|
+
if (this.isLoggedIn()) {
|
|
1357
|
+
this._user.set(this.extractUserFromToken(this.getToken()?.token ?? ''));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Flag indicating whether the access token is being refreshed
|
|
1362
|
+
*/
|
|
1363
|
+
refreshTokenInProgress = false;
|
|
1364
|
+
/**
|
|
1365
|
+
* Manages the access token refresh flow
|
|
1366
|
+
*/
|
|
1367
|
+
refreshTokenSubject = new BehaviorSubject(null);
|
|
1368
|
+
/**
|
|
1369
|
+
* Sends the authentication token to the server
|
|
1370
|
+
* @param request HTTP request
|
|
1371
|
+
* @returns
|
|
1372
|
+
*/
|
|
1373
|
+
addAuthenticationToken(request) {
|
|
1374
|
+
const token = this.getToken();
|
|
1375
|
+
// If the access token is null, the user is not logged in; return the original request
|
|
1376
|
+
if (!token ||
|
|
1377
|
+
request.url.includes(this.config.auth.signinUrl) ||
|
|
1378
|
+
(this.config.auth.refreshTokenUrl && request.url.includes(this.config.auth.refreshTokenUrl))) {
|
|
1379
|
+
return request;
|
|
1380
|
+
}
|
|
1381
|
+
// Clone the request, because the original request is immutable
|
|
1382
|
+
return request.clone({
|
|
1383
|
+
setHeaders: {
|
|
1384
|
+
Authorization: `Bearer ${token.token}`
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
async connect(client) {
|
|
1389
|
+
const isChrome = navigator.userAgentData?.brands?.some((b) => b.brand === 'Google Chrome') ?? false;
|
|
1390
|
+
if (this.config.fedcm &&
|
|
1391
|
+
isChrome &&
|
|
1392
|
+
'credentials' in navigator &&
|
|
1393
|
+
navigator.credentials &&
|
|
1394
|
+
'get' in navigator.credentials) {
|
|
1395
|
+
try {
|
|
1396
|
+
const fedcm = this.config.fedcm[client];
|
|
1397
|
+
if (!fedcm) {
|
|
1398
|
+
throw new Error('No auth client exists');
|
|
1399
|
+
}
|
|
1400
|
+
const credential = await navigator.credentials.get({
|
|
1401
|
+
identity: {
|
|
1402
|
+
mode: 'active',
|
|
1403
|
+
providers: [
|
|
1404
|
+
{
|
|
1405
|
+
configURL: fedcm.configURL,
|
|
1406
|
+
clientId: fedcm.clientId,
|
|
1407
|
+
fields: ['name', 'email', 'picture'],
|
|
1408
|
+
params: {
|
|
1409
|
+
fetch_basic_profile: true,
|
|
1410
|
+
response_type: 'permission id_token',
|
|
1411
|
+
scope: 'email profile openid',
|
|
1412
|
+
include_granted_scopes: true,
|
|
1413
|
+
nonce: 'notprovided'
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
},
|
|
1418
|
+
mediation: 'required'
|
|
1419
|
+
});
|
|
1420
|
+
if (!credential) {
|
|
1421
|
+
throw new Error('No credential obtained');
|
|
1422
|
+
}
|
|
1423
|
+
const fedcmCredential = credential;
|
|
1424
|
+
// Send the ID token to the backend
|
|
1425
|
+
const response = await fetch(fedcm.tokenUrl, {
|
|
1426
|
+
method: 'POST',
|
|
1427
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1428
|
+
body: JSON.stringify({
|
|
1429
|
+
id_token: JSON.parse(fedcmCredential.token).id_token
|
|
1430
|
+
})
|
|
1431
|
+
});
|
|
1432
|
+
const data = await response.json();
|
|
1433
|
+
this.setToken(data.token);
|
|
1434
|
+
this._user.set(data.user);
|
|
1435
|
+
location.href = this.config.appPath;
|
|
1436
|
+
}
|
|
1437
|
+
catch (e) {
|
|
1438
|
+
console.error('FedCM error: ', e);
|
|
1439
|
+
return false;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
const url = this.config.auth.clients[client];
|
|
1444
|
+
if (url) {
|
|
1445
|
+
location.href = url;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Extracts user claims from a JWT token payload.
|
|
1452
|
+
* Maps common JWT claims (sub, email, given_name, family_name, etc.) to the User model.
|
|
1453
|
+
* @param jwtToken The JWT token string
|
|
1454
|
+
* @returns User object or null if parsing fails
|
|
1455
|
+
*/
|
|
1456
|
+
extractUserFromToken(jwtToken) {
|
|
1457
|
+
if (!jwtToken)
|
|
1458
|
+
return null;
|
|
1459
|
+
try {
|
|
1460
|
+
const jwtParts = jwtToken.split('.');
|
|
1461
|
+
if (jwtParts.length !== 3)
|
|
1462
|
+
return null;
|
|
1463
|
+
const payload = JSON.parse(window.atob(jwtParts[1]));
|
|
1464
|
+
const roles = Array.isArray(payload['roles'])
|
|
1465
|
+
? payload['roles']
|
|
1466
|
+
: typeof payload['role'] === 'string'
|
|
1467
|
+
? [payload['role']]
|
|
1468
|
+
: [];
|
|
1469
|
+
return {
|
|
1470
|
+
username: (payload['sub'] ??
|
|
1471
|
+
payload['preferred_username'] ??
|
|
1472
|
+
payload['username'] ??
|
|
1473
|
+
''),
|
|
1474
|
+
roles
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
catch {
|
|
1478
|
+
return null;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Extracts the expiration timestamp from a JWT token
|
|
1483
|
+
* @param jwtToken The JWT token string
|
|
1484
|
+
* @returns The expiration timestamp in seconds (JWT exp format) or undefined if not found/invalid
|
|
1485
|
+
*/
|
|
1486
|
+
extractExpirationFromToken(jwtToken) {
|
|
1487
|
+
if (!jwtToken) {
|
|
1488
|
+
return undefined;
|
|
1489
|
+
}
|
|
1490
|
+
try {
|
|
1491
|
+
const jwtParts = jwtToken.split('.');
|
|
1492
|
+
if (jwtParts.length === 3) {
|
|
1493
|
+
const payload = JSON.parse(window.atob(jwtParts[1]));
|
|
1494
|
+
if (payload.exp) {
|
|
1495
|
+
// Store timestamp in seconds (JWT exp format)
|
|
1496
|
+
return payload.exp;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
catch {
|
|
1501
|
+
// If JWT parsing fails, return undefined
|
|
1502
|
+
}
|
|
1503
|
+
return undefined;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Handles the flow of refreshing the access token or redirecting to sign-in
|
|
1507
|
+
* @param err HTTP error
|
|
1508
|
+
* @param request HTTP request sent
|
|
1509
|
+
* @param next HTTP handler
|
|
1510
|
+
*/
|
|
1511
|
+
handle401Error(err, request, next) {
|
|
1512
|
+
const token = this.getToken();
|
|
1513
|
+
if (token && token.refresh_token && this.config.auth.refreshTokenUrl) {
|
|
1514
|
+
if (!this.refreshTokenInProgress) {
|
|
1515
|
+
this.refreshTokenInProgress = true;
|
|
1516
|
+
this.refreshTokenSubject.next(null);
|
|
1517
|
+
return this.refreshToken().pipe(switchMap((newToken) => {
|
|
1518
|
+
if (newToken) {
|
|
1519
|
+
this.refreshTokenSubject.next(newToken);
|
|
1520
|
+
return next(this.addAuthenticationToken(request));
|
|
1521
|
+
}
|
|
1522
|
+
// If we don't get a new token, logout.
|
|
1523
|
+
this.logout();
|
|
1524
|
+
return throwError(() => new HttpErrorResponse({
|
|
1525
|
+
error: {},
|
|
1526
|
+
headers: new HttpHeaders(),
|
|
1527
|
+
status: 401,
|
|
1528
|
+
statusText: '',
|
|
1529
|
+
url: undefined
|
|
1530
|
+
}));
|
|
1531
|
+
}), catchError((error) => {
|
|
1532
|
+
// It can't replace the access token; set error status 401 to continue flow
|
|
1533
|
+
return throwError(() => new HttpErrorResponse({
|
|
1534
|
+
error: error.error,
|
|
1535
|
+
headers: error.headers,
|
|
1536
|
+
status: 401,
|
|
1537
|
+
statusText: error.statusText,
|
|
1538
|
+
url: error.url || undefined
|
|
1539
|
+
}));
|
|
1540
|
+
}), share(), finalize(() => {
|
|
1541
|
+
this.refreshTokenInProgress = false;
|
|
1542
|
+
}));
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
return this.refreshTokenSubject.pipe(filter((token) => token != null), take(1), switchMap(() => {
|
|
1546
|
+
return next(this.addAuthenticationToken(request));
|
|
1547
|
+
}));
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
else {
|
|
1551
|
+
// No refresh token flow
|
|
1552
|
+
if (this.isLoggedIn()) {
|
|
1553
|
+
this.logout();
|
|
1554
|
+
}
|
|
1555
|
+
return throwError(() => err);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Sends sign-in to the server and obtains the authentication token
|
|
1560
|
+
* @param data Authentication data
|
|
1561
|
+
* @returns
|
|
1562
|
+
*/
|
|
1563
|
+
async login(data) {
|
|
1564
|
+
const token = await lastValueFrom(this.httpClient.post(this.config.auth.signinUrl, data));
|
|
1565
|
+
this.setToken(token);
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Logs out the user
|
|
1570
|
+
*/
|
|
1571
|
+
async logout() {
|
|
1572
|
+
this.dialog.closeAll();
|
|
1573
|
+
this.clearToken();
|
|
1574
|
+
this.clearUser();
|
|
1575
|
+
location.href =
|
|
1576
|
+
window.innerWidth < 1000 ? `${this.config.appPath}/auth` : `${this.config.appPath}/signin`;
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
async signup(data, options) {
|
|
1580
|
+
return lastValueFrom(this.httpClient.post(this.config.auth.signupUrl, data, options));
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* If a refresh token is implemented, send it to obtain a new access token
|
|
1584
|
+
* @returns Access token
|
|
1585
|
+
*/
|
|
1586
|
+
refreshToken() {
|
|
1587
|
+
const token = this.getToken();
|
|
1588
|
+
return this.httpClient
|
|
1589
|
+
.post(this.config.auth.refreshTokenUrl, { refresh_token: token?.refresh_token })
|
|
1590
|
+
.pipe(tap((token) => {
|
|
1591
|
+
this.setToken(token);
|
|
1592
|
+
}), catchError((error) => {
|
|
1593
|
+
this.logout();
|
|
1594
|
+
return throwError(() => new Error(error));
|
|
1595
|
+
}));
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* Reads the session token from the Secure cookie (client-side storage).
|
|
1599
|
+
* @internal
|
|
1600
|
+
*/
|
|
1601
|
+
getToken() {
|
|
1602
|
+
if (typeof document === 'undefined' || !document.cookie)
|
|
1603
|
+
return null;
|
|
1604
|
+
const match = document.cookie.match(new RegExp('(?:^|; )' + this.TOKEN_KEY.replace(/([.*+?^${}()|[\]\\])/g, '\\$1') + '=([^;]*)'));
|
|
1605
|
+
const raw = match ? decodeURIComponent(match[1]) : null;
|
|
1606
|
+
if (!raw)
|
|
1607
|
+
return null;
|
|
1608
|
+
try {
|
|
1609
|
+
const data = JSON.parse(raw);
|
|
1610
|
+
return data?.token ? data : null;
|
|
1611
|
+
}
|
|
1612
|
+
catch {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Sets the authentication token in session state and in a Secure cookie (client-side).
|
|
1618
|
+
* Cookie: Path=/; SameSite=Strict; Secure on HTTPS; Max-Age from expiresAt or 1 day.
|
|
1619
|
+
*
|
|
1620
|
+
* @param token - The authentication token object to store
|
|
1621
|
+
*/
|
|
1622
|
+
setToken(token) {
|
|
1623
|
+
const copy = token ? { ...token } : null;
|
|
1624
|
+
if (copy && typeof document !== 'undefined') {
|
|
1625
|
+
const expiresAt = this.extractExpirationFromToken(copy.token);
|
|
1626
|
+
const value = encodeURIComponent(JSON.stringify(copy));
|
|
1627
|
+
const maxAge = expiresAt
|
|
1628
|
+
? Math.max(0, expiresAt - Math.round(Date.now() / 1000))
|
|
1629
|
+
: 24 * 60 * 60;
|
|
1630
|
+
let cookie = `${this.TOKEN_KEY}=${value}; Path=/; Max-Age=${maxAge}; SameSite=Strict`;
|
|
1631
|
+
if (typeof location !== 'undefined' && location.protocol === 'https:')
|
|
1632
|
+
cookie += '; Secure';
|
|
1633
|
+
document.cookie = cookie;
|
|
1634
|
+
this._user.set(this.extractUserFromToken(copy.token) ?? null);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Clears the authentication token from session state and removes the cookie.
|
|
1639
|
+
*/
|
|
1640
|
+
clearToken() {
|
|
1641
|
+
if (typeof document !== 'undefined') {
|
|
1642
|
+
document.cookie = `${this.TOKEN_KEY}=; Path=/; Max-Age=0`;
|
|
1643
|
+
}
|
|
1644
|
+
this.clearUser();
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Clears the current user from session state
|
|
1648
|
+
*/
|
|
1649
|
+
clearUser() {
|
|
1650
|
+
this._user.set(null);
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Computed signal indicating whether a user is currently logged in
|
|
1654
|
+
* Based on the presence and validity of the authentication token
|
|
1655
|
+
*
|
|
1656
|
+
* For JWT tokens, validates expiration. For other token types, only checks existence.
|
|
1657
|
+
*
|
|
1658
|
+
* @returns true if a valid token exists, false otherwise
|
|
1659
|
+
*/
|
|
1660
|
+
isLoggedIn = computed(() => {
|
|
1661
|
+
// Depend on _user so computed re-evaluates when setToken/clearToken updates state
|
|
1662
|
+
this._user();
|
|
1663
|
+
const token = this.getToken();
|
|
1664
|
+
if (!token)
|
|
1665
|
+
return false;
|
|
1666
|
+
const expiresAt = this.extractExpirationFromToken(token.token);
|
|
1667
|
+
if (expiresAt) {
|
|
1668
|
+
const currentTimestamp = Math.round(Date.now() / 1000);
|
|
1669
|
+
return expiresAt > currentTimestamp;
|
|
1670
|
+
}
|
|
1671
|
+
return true;
|
|
1672
|
+
}, ...(ngDevMode ? [{ debugName: "isLoggedIn" }] : /* istanbul ignore next */ []));
|
|
1673
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1674
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
1675
|
+
}
|
|
1676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: AuthService, decorators: [{
|
|
1677
|
+
type: Injectable,
|
|
1678
|
+
args: [{
|
|
1679
|
+
providedIn: 'root'
|
|
1680
|
+
}]
|
|
1681
|
+
}], ctorParameters: () => [] });
|
|
1682
|
+
|
|
1683
|
+
const authGuard = async (route, state) => {
|
|
1684
|
+
const config = inject(AUTH_CONFIG);
|
|
1685
|
+
const router = inject(Router);
|
|
1686
|
+
const authProvider = inject(AuthProvider);
|
|
1687
|
+
const storageService = inject(Storage);
|
|
1688
|
+
if (!authProvider.isLoggedIn()) {
|
|
1689
|
+
storageService.set(`${config.sessionPrefix}_rdi`, state.url);
|
|
1690
|
+
return router.createUrlTree([window.innerWidth < 1000 ? '/auth' : '/signin']);
|
|
1691
|
+
}
|
|
1692
|
+
return true;
|
|
1693
|
+
};
|
|
1694
|
+
const loginGuard = () => {
|
|
1695
|
+
const authProvider = inject(AuthProvider);
|
|
1696
|
+
const router = inject(Router);
|
|
1697
|
+
return authProvider.isLoggedIn() ? router.createUrlTree(['/']) : true;
|
|
1698
|
+
};
|
|
1699
|
+
const resetGuard = (route) => {
|
|
1700
|
+
const authProvider = inject(AuthProvider);
|
|
1701
|
+
const router = inject(Router);
|
|
1702
|
+
const token = route.queryParamMap.get('token');
|
|
1703
|
+
if (token && !authProvider.isLoggedIn()) {
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
else if (authProvider.isLoggedIn()) {
|
|
1707
|
+
return router.createUrlTree(['/']);
|
|
1708
|
+
}
|
|
1709
|
+
else {
|
|
1710
|
+
router.navigateByUrl(`/error/403`, { skipLocationChange: true });
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
|
|
1715
|
+
const authInterceptor = (req, next) => {
|
|
1716
|
+
const authConfig = inject(AUTH_CONFIG);
|
|
1717
|
+
const authProvider = inject(AuthProvider);
|
|
1718
|
+
const authReq = authProvider.addAuthenticationToken(req);
|
|
1719
|
+
return next(authReq).pipe(catchError((error) => {
|
|
1720
|
+
if (error instanceof HttpErrorResponse) {
|
|
1721
|
+
switch (error.status) {
|
|
1722
|
+
case 401:
|
|
1723
|
+
if (!req.url.includes(authConfig.auth.refreshTokenUrl)) {
|
|
1724
|
+
return authProvider.handle401Error(error, req, next);
|
|
1725
|
+
}
|
|
1726
|
+
else {
|
|
1727
|
+
authProvider.logout();
|
|
1728
|
+
return throwError(() => error);
|
|
1729
|
+
}
|
|
1730
|
+
default:
|
|
1731
|
+
return throwError(() => error);
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
return throwError(() => error);
|
|
1736
|
+
}
|
|
1737
|
+
}));
|
|
1738
|
+
};
|
|
1739
|
+
|
|
1740
|
+
/*
|
|
1741
|
+
* Public API exports for utils library
|
|
1742
|
+
*/
|
|
1743
|
+
// Services/Utils
|
|
1744
|
+
|
|
1745
|
+
/*
|
|
1746
|
+
* Public API Surface of utils
|
|
1747
|
+
*/
|
|
1748
|
+
|
|
1117
1749
|
/**
|
|
1118
1750
|
* Generated bundle index. Do not edit.
|
|
1119
1751
|
*/
|
|
1120
1752
|
|
|
1121
|
-
export {
|
|
1753
|
+
export { AUTH_CONFIG, ArrayUtil, AuthProvider, AuthService, CSV_CONFIG, ColorUtil, CsvExporter, DateUtil, FilePicker, GoogleTagManager, OPERATION_TYPE, ObjectUtil, Storage, StringUtil, authGuard, authInterceptor, getIdentificationType, identificationValidator, loginGuard, resetGuard };
|
|
1122
1754
|
//# sourceMappingURL=factor_ec-utils.mjs.map
|