@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.
@@ -1,12 +1,33 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, PLATFORM_ID, Inject, inject } from '@angular/core';
2
+ import { Injectable, inject, PLATFORM_ID, InjectionToken, signal, computed } from '@angular/core';
3
3
  import { isPlatformBrowser } from '@angular/common';
4
- import * as i1 from '@angular/router';
5
- import { NavigationEnd } from '@angular/router';
6
- import { Apollo, gql } from 'apollo-angular';
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
- class ArrayService {
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: "19.0.6", ngImport: i0, type: ArrayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
20
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: ArrayService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: ArrayService, decorators: [{
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
- class ColorService {
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 Hash (modified version)
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
- * Convert RGB Array to HEX
118
+ * Converts an RGB array to a hexadecimal color string.
80
119
  *
81
- * @param RGBArray - [R, G, B]
82
- * @returns 6 digits hex starting with #
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
- * Convert HSL to RGB
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
- * Returns the hash in [h, s, l].
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 hash
134
- * @returns [h, s, l]
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
- * Returns the hash in [r, g, b].
161
- * Note that R, G, B ∈ [0, 255]
199
+ * Generates RGB color values from a string hash.
162
200
  *
163
- * @param str string to hash
164
- * @returns [r, g, b]
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
- * Returns the hash in hex
209
+ * Generates a hexadecimal color string from a string hash.
172
210
  *
173
- * @param str string to hash
174
- * @returns hex with #
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: "19.0.6", ngImport: i0, type: ColorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
181
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: ColorService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: ColorService, decorators: [{
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
- class CsvConfigConsts {
191
- static EOL = '\r\n';
192
- static BOM = '\ufeff';
193
- static DEFAULT_FIELD_SEPARATOR = ',';
194
- static DEFAULT_DECIMAL_SEPARATOR = '.';
195
- static DEFAULT_QUOTE = '"';
196
- static DEFAULT_SHOW_TITLE = false;
197
- static DEFAULT_TITLE = 'My Generated Report';
198
- static DEFAULT_FILENAME = 'generated';
199
- static DEFAULT_SHOW_LABELS = false;
200
- static DEFAULT_USE_TEXT_FILE = false;
201
- static DEFAULT_USE_BOM = true;
202
- static DEFAULT_HEADER = [];
203
- static DEFAULT_KEYS_AS_HEADERS = false;
204
- }
205
- const ConfigDefaults = {
206
- filename: CsvConfigConsts.DEFAULT_FILENAME,
207
- fieldSeparator: CsvConfigConsts.DEFAULT_FIELD_SEPARATOR,
208
- quoteStrings: CsvConfigConsts.DEFAULT_QUOTE,
209
- decimalSeparator: CsvConfigConsts.DEFAULT_DECIMAL_SEPARATOR,
210
- showLabels: CsvConfigConsts.DEFAULT_SHOW_LABELS,
211
- showTitle: CsvConfigConsts.DEFAULT_SHOW_TITLE,
212
- title: CsvConfigConsts.DEFAULT_TITLE,
213
- useTextFile: CsvConfigConsts.DEFAULT_USE_TEXT_FILE,
214
- useBom: CsvConfigConsts.DEFAULT_USE_BOM,
215
- headers: CsvConfigConsts.DEFAULT_HEADER,
216
- useKeysAsHeaders: CsvConfigConsts.DEFAULT_KEYS_AS_HEADERS
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
- class CsvService {
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({}, ConfigDefaults, options);
301
+ this._options = this.objectAssign({}, this.configDefaults, options);
227
302
  }
228
303
  constructor() {
229
- this._options = this.objectAssign({}, ConfigDefaults);
304
+ this._options = this.objectAssign({}, this.configDefaults);
230
305
  }
231
306
  /**
232
- * Generate and Download Csv
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 += CsvConfigConsts.BOM;
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
- * Create Headers
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 + CsvConfigConsts.EOL;
377
+ this._csv += row + CSV_CONFIG.EOL;
290
378
  }
291
379
  }
292
380
  /**
293
- * Create Body
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 + CsvConfigConsts.EOL;
395
+ this._csv += row + CSV_CONFIG.EOL;
306
396
  }
307
397
  }
308
398
  /**
309
- * Format Data
310
- * @param {any} data
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
- * Check if is Float
336
- * @param {any} input
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
- * Parse the collection given to it
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
- * Convet to Object
355
- * @param {any} val
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
- * Assign data to new Object
365
- * @param {any} target
366
- * @param {any[]} ...source
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
- // Si encuentra una linea en blanco ya no sigue leyendo las siguientes líneas
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]); // Extraer el valor correcto
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: "19.0.6", ngImport: i0, type: CsvService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
432
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: CsvService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: CsvService, decorators: [{
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
- class DateService {
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: "19.0.6", ngImport: i0, type: DateService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
447
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: DateService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: FilesService, decorators: [{
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
- }], ctorParameters: () => [] });
593
+ }] });
519
594
 
520
- class FilePickerService {
521
- pickerClosed = false;
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.addEventListener('change', (event) => {
527
- this.pickerClosed = true;
528
- const files = event.currentTarget.files;
529
- if (files && files.length > 0) {
530
- let data = [];
531
- for (let i = 0; i < files.length; i++) {
532
- const file = files.item(i);
533
- const reader = new FileReader();
534
- reader.readAsDataURL(file);
535
- reader.onload = () => {
536
- data.push(Object.assign(file, {
537
- data: reader.result
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 (!this.pickerClosed) {
555
- reject(null);
638
+ if (!changeHandled) {
639
+ cleanUp();
640
+ resolve(null);
556
641
  }
557
- window.removeEventListener('focus', onFocus);
558
- }, 500); // Esperamos un poco para asegurarnos de que onchange haya tenido oportunidad de ejecutarse
642
+ }, 1500);
559
643
  };
560
- window.addEventListener('focus', onFocus);
561
- fileInput.accept = options?.accept ? options.accept : '';
562
- fileInput.multiple = options?.multiple || false;
563
- fileInput.click();
564
- });
565
- }
566
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FilePickerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
567
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: FilePickerService, providedIn: 'root' });
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
- ? '&gtm_auth=' + options?.environment.auth + '&gtm_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
- else if (query[property]?.node) {
773
- data[property] = this.parseQuery(query[property].node);
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
- else if (Array.isArray(query[property])) {
776
- // Si es un arreglo simple no lo parsea
777
- if (typeof query[property][0] === 'object') {
778
- data[property] = query[property].map((node) => {
779
- return this.parseQuery(node);
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
- else {
783
- data[property] = query[property];
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
- else if (property !== '__typename') {
790
- data[property] = query[property];
678
+ catch (error) {
679
+ cleanUp();
680
+ reject(error);
791
681
  }
792
- }
793
- return data;
794
- }
795
- else {
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: "19.0.6", ngImport: i0, type: GraphqlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
805
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: GraphqlService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: GraphqlService, decorators: [{
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
- class ObjectService {
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: "19.0.6", ngImport: i0, type: ObjectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
835
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: ObjectService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: ObjectService, decorators: [{
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
- class StorageService {
845
- platformId;
846
- //TODO: Replace with Map object it is more efficient
847
- memoryStorage;
848
- constructor(platformId) {
849
- this.platformId = platformId;
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: "19.0.6", ngImport: i0, type: StorageService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
922
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.6", ngImport: i0, type: StorageService, providedIn: 'root' });
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: "19.0.6", ngImport: i0, type: StorageService, decorators: [{
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
- }], ctorParameters: () => [{ type: Object, decorators: [{
930
- type: Inject,
931
- args: [PLATFORM_ID]
932
- }] }] });
884
+ }] });
933
885
 
934
- function lengthValidator(number, digits) {
935
- let value = true;
936
- if (number.trim() === '') { // No puede estar vacio
937
- value = false;
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 = '&lt;div&gt;Hello&lt;/div&gt;';
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
+ ? '&gtm_auth=' + options?.environment.auth + '&gtm_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 { ArrayService, ColorService, ConfigDefaults, CsvConfigConsts, CsvService, DateService, FilePickerService, FilesService, GoogleTagManagerService, GraphqlService, ObjectService, StorageService, StringService, getIdentificationType, identificationValidator };
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