@geins/core 0.3.0-canary

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.
Files changed (88) hide show
  1. package/.turbo/turbo-build.log +7 -0
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +1 -0
  4. package/__tests__/Channel.test.ts +34 -0
  5. package/__tests__/EventService.test.ts +32 -0
  6. package/__tests__/GeinsCore.test.ts +69 -0
  7. package/__tests__/Graphql.test.ts +43 -0
  8. package/dist/api-client/endpointClient.d.ts +8 -0
  9. package/dist/api-client/index.d.ts +4 -0
  10. package/dist/api-client/managementApiClient.d.ts +10 -0
  11. package/dist/api-client/merchantApiClient.d.ts +46 -0
  12. package/dist/base/baseApiService.d.ts +14 -0
  13. package/dist/base/basePackage.d.ts +12 -0
  14. package/dist/base/baseStore.d.ts +14 -0
  15. package/dist/base/index.d.ts +3 -0
  16. package/dist/constants/cookies.d.ts +11 -0
  17. package/dist/constants/endpoints.d.ts +14 -0
  18. package/dist/constants/headerNames.d.ts +3 -0
  19. package/dist/constants/index.d.ts +3 -0
  20. package/dist/geinsCore.d.ts +88 -0
  21. package/dist/graphql/index.d.ts +1 -0
  22. package/dist/graphql/queries.d.ts +5 -0
  23. package/dist/index.cjs +21783 -0
  24. package/dist/index.d.ts +10 -0
  25. package/dist/index.esm.js +21749 -0
  26. package/dist/logic/channel.d.ts +17 -0
  27. package/dist/logic/index.d.ts +1 -0
  28. package/dist/parsers/channelParser.d.ts +8 -0
  29. package/dist/services/channelService.d.ts +8 -0
  30. package/dist/services/channelsService.d.ts +8 -0
  31. package/dist/services/cookieService.d.ts +88 -0
  32. package/dist/services/eventService.d.ts +51 -0
  33. package/dist/services/graphQLService.d.ts +23 -0
  34. package/dist/services/index.d.ts +7 -0
  35. package/dist/services/logService.d.ts +19 -0
  36. package/dist/services/routingService.d.ts +74 -0
  37. package/dist/stores/channelStore.d.ts +11 -0
  38. package/dist/stores/index.d.ts +2 -0
  39. package/dist/stores/routingStore.d.ts +32 -0
  40. package/dist/utils/helpers.d.ts +21 -0
  41. package/dist/utils/index.d.ts +2 -0
  42. package/dist/utils/simpleCache.d.ts +9 -0
  43. package/eslint.config.js +3 -0
  44. package/jest.config.js +8 -0
  45. package/package.json +32 -0
  46. package/rollup.config.js +26 -0
  47. package/src/api-client/endpointClient.ts +52 -0
  48. package/src/api-client/index.ts +4 -0
  49. package/src/api-client/managementApiClient.ts +41 -0
  50. package/src/api-client/merchantApiClient.ts +153 -0
  51. package/src/base/baseApiService.ts +96 -0
  52. package/src/base/basePackage.ts +39 -0
  53. package/src/base/baseStore.ts +21 -0
  54. package/src/base/index.ts +3 -0
  55. package/src/constants/cookies.ts +12 -0
  56. package/src/constants/endpoints.ts +27 -0
  57. package/src/constants/headerNames.ts +3 -0
  58. package/src/constants/index.ts +3 -0
  59. package/src/geinsCore.ts +214 -0
  60. package/src/graphql/channel.gql +16 -0
  61. package/src/graphql/channels.gql +16 -0
  62. package/src/graphql/fragments/channelFields.gql +8 -0
  63. package/src/graphql/fragments/countryFields.gql +4 -0
  64. package/src/graphql/fragments/currencyFields.gql +4 -0
  65. package/src/graphql/fragments/languageFields.gql +5 -0
  66. package/src/graphql/fragments/marketFields.gql +19 -0
  67. package/src/graphql/graphql.d.ts +4 -0
  68. package/src/graphql/index.ts +1 -0
  69. package/src/graphql/queries.ts +9 -0
  70. package/src/index.ts +10 -0
  71. package/src/logic/channel.ts +102 -0
  72. package/src/logic/index.ts +1 -0
  73. package/src/parsers/channelParser.ts +91 -0
  74. package/src/services/channelService.ts +33 -0
  75. package/src/services/channelsService.ts +35 -0
  76. package/src/services/cookieService.ts +168 -0
  77. package/src/services/eventService.ts +89 -0
  78. package/src/services/graphQLService.ts +55 -0
  79. package/src/services/index.ts +7 -0
  80. package/src/services/logService.ts +434 -0
  81. package/src/services/routingService.ts +196 -0
  82. package/src/stores/channelStore.ts +34 -0
  83. package/src/stores/index.ts +2 -0
  84. package/src/stores/routingStore.ts +56 -0
  85. package/src/utils/helpers.ts +39 -0
  86. package/src/utils/index.ts +2 -0
  87. package/src/utils/simpleCache.ts +27 -0
  88. package/tsconfig.json +19 -0
@@ -0,0 +1,434 @@
1
+ interface LogTypes {
2
+ trace: LogTypeMeta;
3
+ debug: LogTypeMeta;
4
+ info: LogTypeMeta;
5
+ warn: LogTypeMeta;
6
+ error: LogTypeMeta;
7
+ }
8
+
9
+ interface LogTypeMeta {
10
+ abbreviation: string;
11
+ name: string;
12
+ color: string;
13
+ icon: string;
14
+ }
15
+
16
+ interface LogTypeFiles {
17
+ typescript: LogFileMeta;
18
+ javascript: LogFileMeta;
19
+ vue: LogFileMeta;
20
+ jsx: LogFileMeta;
21
+ tsx: LogFileMeta;
22
+ next: LogFileMeta;
23
+ react: LogFileMeta;
24
+ astro: LogFileMeta;
25
+ angular: LogFileMeta;
26
+ svelte: LogFileMeta;
27
+ }
28
+
29
+ interface LogFileMeta {
30
+ abbreviation: string;
31
+ name: string;
32
+ color: string;
33
+ colorServer: string;
34
+ icon?: string;
35
+ fileExtensions: string[]; // Added fileExtensions for identifying file type by extension
36
+ }
37
+
38
+ const logLevels = ['trace', 'debug', 'info', 'warn', 'error'] as const; // Fixed logLevels
39
+ type LogLevel = (typeof logLevels)[number];
40
+
41
+ const logTypes: LogTypes = {
42
+ trace: {
43
+ abbreviation: 'T',
44
+ name: 'Trace',
45
+ color: '#881DD7',
46
+ icon: '🔍',
47
+ },
48
+ debug: {
49
+ abbreviation: 'D',
50
+ name: 'Debug',
51
+ color: '#128655',
52
+ icon: '🐛',
53
+ },
54
+ info: {
55
+ abbreviation: 'I',
56
+ name: 'Info',
57
+ color: '#18A7B4',
58
+ icon: 'ℹ️',
59
+ },
60
+ warn: {
61
+ abbreviation: 'W',
62
+ name: 'Warning',
63
+ color: '#D7881D',
64
+ icon: '⚠️',
65
+ },
66
+ error: {
67
+ abbreviation: 'E',
68
+ name: 'Error',
69
+ color: '#D72B1D',
70
+ icon: '❌',
71
+ },
72
+ };
73
+
74
+ const logColorServer = {
75
+ reset: '\x1b[0m',
76
+ bright: '\x1b[1m',
77
+ dim: '\x1b[2m',
78
+ underscore: '\x1b[4m',
79
+
80
+ fgBlack: '\x1b[30m',
81
+ fgRed: '\x1b[31m',
82
+ fgGreen: '\x1b[32m',
83
+ fgYellow: '\x1b[33m',
84
+ fgBlue: '\x1b[34m',
85
+ fgMagenta: '\x1b[35m',
86
+ fgCyan: '\x1b[36m',
87
+ fgWhite: '\x1b[37m',
88
+
89
+ bgBlack: '\x1b[40m',
90
+ bgRed: '\x1b[41m',
91
+ bgGreen: '\x1b[42m',
92
+ bgYellow: '\x1b[43m',
93
+ bgBlue: '\x1b[44m',
94
+ bgMagenta: '\x1b[45m',
95
+ bgCyan: '\x1b[46m',
96
+ bgWhite: '\x1b[47m',
97
+ };
98
+
99
+ const logTypeFiles: LogTypeFiles = {
100
+ typescript: {
101
+ abbreviation: 'TS',
102
+ name: 'TypeScript',
103
+ colorServer: logColorServer.bgBlue,
104
+ color: '#007ACC',
105
+ icon: '📘',
106
+ fileExtensions: ['.ts', '.tsx'],
107
+ },
108
+ javascript: {
109
+ abbreviation: 'JS',
110
+ name: 'JavaScript',
111
+ color: '#F7DF1E',
112
+ colorServer: logColorServer.bgYellow,
113
+ icon: '📙',
114
+ fileExtensions: ['.js', '.mjs', 'cjs'],
115
+ },
116
+ vue: {
117
+ abbreviation: 'VUE',
118
+ name: 'Vue',
119
+ color: '#41B883',
120
+ colorServer: logColorServer.bgGreen,
121
+ icon: '🖼️',
122
+ fileExtensions: ['.vue'],
123
+ },
124
+ jsx: {
125
+ abbreviation: 'JSX',
126
+ name: 'JSX',
127
+ color: '#61DAFB',
128
+ colorServer: logColorServer.bgCyan,
129
+ icon: '📘',
130
+ fileExtensions: ['.jsx'],
131
+ },
132
+ tsx: {
133
+ abbreviation: 'TSX',
134
+ name: 'TSX',
135
+ color: '#007ACC',
136
+ colorServer: logColorServer.bgBlue,
137
+ icon: '📘',
138
+ fileExtensions: ['.tsx'],
139
+ },
140
+ next: {
141
+ abbreviation: 'NEXT',
142
+ name: 'Next.js',
143
+ color: '#000000',
144
+ colorServer: logColorServer.bgBlue,
145
+ icon: '🚀',
146
+ fileExtensions: ['.js', '.ts', '.jsx', '.tsx'],
147
+ },
148
+ react: {
149
+ abbreviation: 'REACT',
150
+ name: 'React',
151
+ color: '#61DAFB',
152
+ colorServer: logColorServer.bgBlue,
153
+ icon: '⚛️',
154
+ fileExtensions: ['.jsx', '.tsx'],
155
+ },
156
+ astro: {
157
+ abbreviation: 'ASTRO',
158
+ name: 'Astro',
159
+ color: '#FF5D01',
160
+ colorServer: logColorServer.fgMagenta,
161
+ icon: '🌌',
162
+ fileExtensions: ['.astro'],
163
+ },
164
+ angular: {
165
+ abbreviation: 'ANG',
166
+ name: 'Angular',
167
+ color: '#DD0031',
168
+ colorServer: logColorServer.bgBlue,
169
+ icon: '🅰️',
170
+ fileExtensions: ['.ts', '.html'],
171
+ },
172
+ svelte: {
173
+ abbreviation: 'SVELTE',
174
+ name: 'Svelte',
175
+ color: '#FF3E00',
176
+ colorServer: logColorServer.bgBlue,
177
+ icon: '🔥',
178
+ fileExtensions: ['.svelte'],
179
+ },
180
+ };
181
+
182
+ const frameworkPatterns: { [framework: string]: RegExp } = {
183
+ 'Nuxt.js': /\/_nuxt\//,
184
+ 'Vue.js': /\/\.vue\//,
185
+ React: /\/react\//,
186
+ Angular: /\/angular\//,
187
+ Svelte: /\/svelte\//,
188
+ 'Next.js': /\/next\//,
189
+ Astro: /\/astro\//,
190
+ };
191
+
192
+ export class LogService {
193
+ private static configIsEnabled: boolean = true;
194
+ private static configLogLevel: LogLevel = 'debug';
195
+
196
+ // Set configuration for enabling/disabling logs and log levels
197
+ public static configure({
198
+ isEnabled = true,
199
+ logLevel = 'debug',
200
+ }: {
201
+ isEnabled?: boolean;
202
+ logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error';
203
+ } = {}): void {
204
+ LogService.configIsEnabled = isEnabled;
205
+ LogService.configLogLevel = logLevel;
206
+ }
207
+
208
+ public static isServer(): boolean {
209
+ return typeof window === 'undefined';
210
+ }
211
+
212
+ public static trace(...args: any[]): void {
213
+ LogService.log('trace', args);
214
+ }
215
+
216
+ public static debug(...args: any[]): void {
217
+ LogService.log('debug', args);
218
+ }
219
+
220
+ public static info(...args: any[]): void {
221
+ LogService.log('info', args);
222
+ }
223
+
224
+ public static warn(...args: any[]): void {
225
+ LogService.log('warn', args);
226
+ }
227
+
228
+ public static error(...args: any[]): void {
229
+ LogService.log('error', args);
230
+ }
231
+
232
+ private static log(logLevel: LogLevel, args: any[]): void {
233
+ const { methodName, fileName, className, framework, fileType } =
234
+ LogService.getCallerInfoExtended();
235
+
236
+ LogService.output(
237
+ logLevel,
238
+ methodName,
239
+ fileName,
240
+ className,
241
+ framework,
242
+ fileType,
243
+ args,
244
+ );
245
+ }
246
+
247
+ private static output(
248
+ logLevel: LogLevel,
249
+ methodName: string,
250
+ fileName: string,
251
+ className: string,
252
+ framework: string | null,
253
+ fileType: LogFileMeta | null,
254
+ args: any[],
255
+ ): void {
256
+ const typeMeta = logTypes[logLevel];
257
+ const fileTypeInfo = fileType ? ` (${fileType.name})` : '';
258
+ const frameworkInfo = framework ? ` [${framework}]` : '';
259
+ const methodInfo = methodName ? `${methodName}()` : '';
260
+ const classInfo = className ? `${className}.` : '';
261
+
262
+ const boxColor = fileType ? fileType.color : typeMeta.color;
263
+ const boxtColorServer = fileType
264
+ ? fileType.colorServer
265
+ : logColorServer.bgWhite;
266
+ const icon = fileType ? fileType.icon : typeMeta.icon;
267
+
268
+ if (LogService.isServer()) {
269
+ console.log(
270
+ `${boxtColorServer}${logColorServer.fgBlack}${icon} ${fileName} ${logColorServer.reset}${logColorServer.bgWhite}${logColorServer.fgBlack} ${classInfo}${methodInfo}${logColorServer.reset} `,
271
+ );
272
+
273
+ args.forEach((arg) => {
274
+ // check type of arg
275
+ if (typeof arg === 'object') {
276
+ console.log(
277
+ `${logColorServer.dim}>> ${logColorServer.reset}`,
278
+ `${JSON.stringify(arg)}`,
279
+ );
280
+ return;
281
+ } else if (typeof arg === 'string') {
282
+ console.log(
283
+ `${logColorServer.dim}>> ${logColorServer.reset}`,
284
+ `${logColorServer.bright}${arg}${logColorServer.reset}`,
285
+ );
286
+ }
287
+ });
288
+ } else {
289
+ console.log(
290
+ `%c${icon} ${fileName}%c ${classInfo}${methodInfo}`,
291
+ `background: ${boxColor}; color: #000; padding: 2px 8px; border-radius: 2px 0 0 2px;`,
292
+ 'background: #D3D3D3; color: #000; padding: 2px 8px; border-radius: 0 2px 2px 0;',
293
+ );
294
+
295
+ args.forEach((arg) => {
296
+ console.log(
297
+ `%c>>`,
298
+ `color: ${typeMeta.color}; margin-left: 5px; font-weight: bold;`,
299
+ arg,
300
+ );
301
+ });
302
+ }
303
+ }
304
+
305
+ private static getCallerInfoExtended(): {
306
+ methodName: string;
307
+ fileName: string;
308
+ className: string;
309
+ framework: string | null;
310
+ fileType: LogFileMeta | null;
311
+ } {
312
+ let error: any = {};
313
+
314
+ try {
315
+ throw new Error('');
316
+ } catch (e) {
317
+ error = e;
318
+ }
319
+
320
+ if (!error.stack) {
321
+ return {
322
+ methodName: 'anonymous',
323
+ fileName: 'unknown',
324
+ className: '',
325
+ framework: null,
326
+ fileType: null,
327
+ };
328
+ }
329
+
330
+ const stack = error.stack.split('\n');
331
+ let framework: string | null = null;
332
+
333
+ if (LogService.isServer()) {
334
+ framework = 'node.js';
335
+ // logic for server server side
336
+ for (let i = 1; i < stack.length; i++) {
337
+ const line = stack[i];
338
+ // Skip internal lines related to LogService and node_modules
339
+ if (
340
+ line.includes('LogService') ||
341
+ line.includes('node_modules') ||
342
+ line.includes('logService.ts')
343
+ ) {
344
+ continue;
345
+ }
346
+ // Extract class name (optional) and method name (mandatory)
347
+ const methodNameMatch = line.match(/at (?:([\w$]+)\.)?(\w+)\s*\(/); // Optional className, methodName
348
+ const filePathMatch =
349
+ line.match(/(http.*\.\w+)/) || line.match(/at (.*):\d+:\d+/);
350
+
351
+ const className = methodNameMatch ? methodNameMatch[1] || '' : ''; // Return an empty string if class name is not found
352
+ const methodName = methodNameMatch ? methodNameMatch[2] : 'anonymous';
353
+ let fileName = 'unknown';
354
+ let fileType: LogFileMeta | null = null;
355
+
356
+ if (filePathMatch) {
357
+ const fullFilePath = filePathMatch[1];
358
+ const filePath = fullFilePath.split('/') || [];
359
+ fileName = filePath.pop() || 'unknown'; // Extract only the file name
360
+ filePath.reverse();
361
+ const filePathString = filePath.slice(0, 3).reverse().join('/');
362
+ fileType = LogService.getFileTypeFromExtension(fileName);
363
+ fileName = `${filePathString}/${fileName}`;
364
+ }
365
+
366
+ return { methodName, fileName, className, framework, fileType };
367
+ }
368
+ } else {
369
+ // Identify framework based on common paths or file patterns in the stack trace
370
+ for (const line of stack) {
371
+ for (const [name, pattern] of Object.entries(frameworkPatterns)) {
372
+ if (pattern.test(line)) {
373
+ framework = name;
374
+ break;
375
+ }
376
+ }
377
+ if (framework) break;
378
+ }
379
+
380
+ // Iterate over the stack and find the first line that doesn't reference LogService or node_modules
381
+ for (let i = 1; i < stack.length; i++) {
382
+ const line = stack[i];
383
+
384
+ // Skip internal lines related to LogService and node_modules
385
+ if (line.includes('LogService') || line.includes('node_modules')) {
386
+ continue;
387
+ }
388
+
389
+ // Extract class name (optional) and method name (mandatory)
390
+ const methodNameMatch = line.match(/at (?:([\w$]+)\.)?(\w+)\s*\(/); // Optional className, methodName
391
+ const filePathMatch =
392
+ line.match(/(http.*\.\w+)/) || line.match(/at (.*):\d+:\d+/);
393
+
394
+ const className = methodNameMatch ? methodNameMatch[1] || '' : ''; // Return an empty string if class name is not found
395
+ const methodName = methodNameMatch ? methodNameMatch[2] : 'anonymous';
396
+ let fileName = 'unknown';
397
+ let fileType: LogFileMeta | null = null;
398
+
399
+ if (filePathMatch) {
400
+ let fullFilePath = filePathMatch[1];
401
+ fullFilePath = fullFilePath.split('?')[0]; // Remove query strings if present
402
+ fileName = fullFilePath.split('/').pop() || 'unknown'; // Extract only the file name
403
+
404
+ // Get file type based on file extension
405
+ fileType = LogService.getFileTypeFromExtension(fileName);
406
+ }
407
+
408
+ return { methodName, fileName, className, framework, fileType };
409
+ }
410
+ }
411
+
412
+ return {
413
+ methodName: 'anonymous',
414
+ fileName: 'unknown',
415
+ className: '',
416
+ framework: null,
417
+ fileType: null,
418
+ };
419
+ }
420
+
421
+ // Helper method to get file type from extension
422
+ private static getFileTypeFromExtension(
423
+ fileName: string,
424
+ ): LogFileMeta | null {
425
+ for (const key in logTypeFiles) {
426
+ const fileType = logTypeFiles[key as keyof LogTypeFiles];
427
+ if (fileType.fileExtensions.some((ext) => fileName.endsWith(ext))) {
428
+ return fileType;
429
+ }
430
+ }
431
+ return null; // Return null if the file type is not identified
432
+ }
433
+ }
434
+ export const logWrite = LogService.debug;
@@ -0,0 +1,196 @@
1
+ import { EndpointApiClient } from '../api-client/endpointClient';
2
+ import { IStore } from '../base/baseStore';
3
+
4
+ const ONE_HOUR_MS = 60 * 60 * 1000; // 1 hour in milliseconds
5
+ const KEY_URL_HISTORY = 'urlHistory';
6
+ const KEY_LAST_FETCH_TIME = 'urlHistory:lastFetchTime';
7
+
8
+ export enum RoutingServiceState {
9
+ INIT,
10
+ FETCHING,
11
+ READY,
12
+ ERROR,
13
+ }
14
+
15
+ export interface RoutingRule extends RoutingItem {
16
+ httpStatusCode?: number;
17
+ isCanonical?: boolean;
18
+ }
19
+
20
+ export interface RoutingItem {
21
+ fromUrl: string;
22
+ toUrl: string;
23
+ }
24
+
25
+ export class RoutingService {
26
+ private static instance: RoutingService | null = null;
27
+ private store: IStore;
28
+ private apiKey: string;
29
+ private apiClient: EndpointApiClient;
30
+ private state: RoutingServiceState;
31
+
32
+ private constructor(apiKey: string, store: IStore) {
33
+ if (!apiKey) {
34
+ this.state = RoutingServiceState.ERROR;
35
+ throw new Error('API key is required');
36
+ }
37
+
38
+ if (!store) {
39
+ this.state = RoutingServiceState.ERROR;
40
+ throw new Error('Store is required');
41
+ }
42
+
43
+ this.state = RoutingServiceState.INIT;
44
+ this.store = store;
45
+ this.apiKey = apiKey;
46
+ this.apiClient = new EndpointApiClient(apiKey);
47
+ this.state = RoutingServiceState.READY;
48
+ }
49
+
50
+ /**
51
+ * Retrieves the singleton instance of the RoutingService.
52
+ * @param apiKey - The API key used for authentication.
53
+ * @param store - The storage interface for caching routes.
54
+ * @returns The singleton instance of RoutingService.
55
+ */
56
+ public static getInstance(apiKey: string, store: IStore): RoutingService {
57
+ if (!RoutingService.instance) {
58
+ RoutingService.instance = new RoutingService(apiKey, store);
59
+ }
60
+ return RoutingService.instance;
61
+ }
62
+
63
+ private async setKey(key: string, value: string): Promise<void> {
64
+ await this.store.setKey(key, value);
65
+ }
66
+
67
+ private async getKey(key: string): Promise<string | null> {
68
+ return await this.store.getKey(key);
69
+ }
70
+
71
+ /**
72
+ * Retrieves all routing rules from the store.
73
+ * @returns A promise that resolves to an array of RoutingRule objects.
74
+ */
75
+ public async getRoutingRules(): Promise<RoutingRule[]> {
76
+ const keys = await this.store.getKeys();
77
+ const rules: RoutingRule[] = [];
78
+ for (const key of keys) {
79
+ if (key === KEY_URL_HISTORY || key === KEY_LAST_FETCH_TIME) {
80
+ continue;
81
+ }
82
+ const toUrl = await this.store.getKey(key);
83
+ if (toUrl) {
84
+ rules.push({
85
+ fromUrl: key,
86
+ toUrl,
87
+ httpStatusCode: 301,
88
+ isCanonical: false,
89
+ });
90
+ }
91
+ }
92
+ return rules;
93
+ }
94
+
95
+ /**
96
+ * Retrieves all route keys from the store.
97
+ * @returns A promise that resolves to an array of route keys.
98
+ */
99
+ public async getAllRoutes(): Promise<string[]> {
100
+ return await this.store.getKeys();
101
+ }
102
+
103
+ private async shouldFetchNewData(): Promise<boolean> {
104
+ const lastFetchTime = await this.getKey(KEY_LAST_FETCH_TIME);
105
+ if (!lastFetchTime) return true;
106
+
107
+ const lastFetchDate = new Date(lastFetchTime);
108
+ const timeElapsed = Date.now() - lastFetchDate.getTime();
109
+ return timeElapsed >= ONE_HOUR_MS;
110
+ }
111
+
112
+ private async fetchAndProcessUrlHistory(): Promise<void> {
113
+ const history = await this.apiClient.getUrlHistory();
114
+ for (const item of history) {
115
+ if (item.oldUrl && item.newUrl && !item.deleted) {
116
+ await this.setKey(item.oldUrl, item.newUrl);
117
+ }
118
+ }
119
+ await this.setKey(KEY_LAST_FETCH_TIME, new Date().toISOString());
120
+ }
121
+
122
+ /**
123
+ * Fetches the URL history from the API and updates the store.
124
+ * If the history was fetched less than an hour ago, it uses the cached data.
125
+ * @returns A promise that resolves to an array of route keys.
126
+ * @throws An error if the URL history could not be fetched.
127
+ */
128
+ public async fillUrlHistory(): Promise<string[]> {
129
+ if (this.state === RoutingServiceState.FETCHING) {
130
+ return this.store.getKeys();
131
+ }
132
+
133
+ this.state = RoutingServiceState.FETCHING;
134
+ try {
135
+ if (await this.shouldFetchNewData()) {
136
+ await this.fetchAndProcessUrlHistory();
137
+ }
138
+ return await this.store.getKeys();
139
+ } catch (error) {
140
+ this.state = RoutingServiceState.ERROR;
141
+ throw error;
142
+ } finally {
143
+ if (this.state !== RoutingServiceState.ERROR) {
144
+ this.state = RoutingServiceState.READY;
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Retrieves the route for the given path from the store.
151
+ * @param path - The path for which to get the route.
152
+ * @returns A promise that resolves to the route or null if not found.
153
+ */
154
+ public async getRoute(path: string): Promise<string | null> {
155
+ return await this.getKey(path);
156
+ }
157
+
158
+ /**
159
+ * Refreshes the URL history if more than an hour has passed since the last fetch.
160
+ * @returns A promise that resolves to an array of route keys.
161
+ */
162
+ public async refreshUrlHistoryIfNeeded(): Promise<string[]> {
163
+ const now = new Date();
164
+
165
+ const lastFetchTime = await this.getKey(KEY_LAST_FETCH_TIME);
166
+ if (lastFetchTime) {
167
+ const lastFetchDate = new Date(lastFetchTime);
168
+ const timeElapsed = now.getTime() - lastFetchDate.getTime();
169
+
170
+ if (timeElapsed >= ONE_HOUR_MS) {
171
+ return await this.fillUrlHistory();
172
+ } else {
173
+ return await this.store.getKeys();
174
+ }
175
+ } else {
176
+ return await this.fillUrlHistory();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Retrieves the last time the URL history was fetched.
182
+ * @returns A promise that resolves to the last fetch time as a string or null if not set.
183
+ */
184
+ public async getLastFetchTime(): Promise<string | null> {
185
+ return await this.getKey(KEY_LAST_FETCH_TIME);
186
+ }
187
+
188
+ /**
189
+ * Fills the routes by fetching the URL history and returns the updated routes.
190
+ * @returns A promise that resolves to an object containing the urlHistory array.
191
+ */
192
+ public async fillRoutes(): Promise<{ urlHistory: string[] }> {
193
+ const urlHistory = await this.fillUrlHistory();
194
+ return { urlHistory };
195
+ }
196
+ }
@@ -0,0 +1,34 @@
1
+ import NodeCache from 'node-cache';
2
+ import { BaseStore } from '../base';
3
+
4
+ const ttlSeconds = 60 * 60 * 24; // 24 hours
5
+
6
+ export class ChannelStore extends BaseStore {
7
+ override store: NodeCache;
8
+
9
+ constructor() {
10
+ super();
11
+ this.store = this.initStore();
12
+ }
13
+
14
+ initStore() {
15
+ return new NodeCache({
16
+ stdTTL: ttlSeconds,
17
+ checkperiod: ttlSeconds * 0.2,
18
+ useClones: false,
19
+ });
20
+ }
21
+
22
+ async getKey(from: string): Promise<any> {
23
+ return this.store.get(from);
24
+ }
25
+ async setKey(from: string, to: string) {
26
+ return this.store.set(from, to);
27
+ }
28
+ async deleteKey(from: string) {
29
+ return this.store.del(from);
30
+ }
31
+ async getKeys() {
32
+ return this.store.keys();
33
+ }
34
+ }
@@ -0,0 +1,2 @@
1
+ export * from './routingStore';
2
+ export * from './channelStore';