@croct/sdk 0.16.2 → 0.17.1

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 (83) hide show
  1. package/cache/cookieCache.d.ts +1 -1
  2. package/cache/cookieCache.js +12 -2
  3. package/cache/cookieCache.js.map +1 -1
  4. package/channel/channel.d.ts +7 -0
  5. package/channel/channel.js +23 -0
  6. package/channel/channel.js.map +1 -1
  7. package/channel/guaranteedChannel.js +4 -3
  8. package/channel/guaranteedChannel.js.map +1 -1
  9. package/channel/httpBeaconChannel.d.ts +23 -0
  10. package/channel/httpBeaconChannel.js +89 -0
  11. package/channel/httpBeaconChannel.js.map +1 -0
  12. package/channel/index.d.ts +1 -2
  13. package/channel/index.js +3 -5
  14. package/channel/index.js.map +1 -1
  15. package/channel/queuedChannel.js +5 -4
  16. package/channel/queuedChannel.js.map +1 -1
  17. package/channel/retryChannel.js +8 -4
  18. package/channel/retryChannel.js.map +1 -1
  19. package/channel/sandboxChannel.js +4 -0
  20. package/channel/sandboxChannel.js.map +1 -1
  21. package/constants.d.ts +2 -2
  22. package/constants.js +1 -1
  23. package/container.d.ts +3 -0
  24. package/container.js +15 -14
  25. package/container.js.map +1 -1
  26. package/contentFetcher.d.ts +3 -4
  27. package/contentFetcher.js +38 -28
  28. package/contentFetcher.js.map +1 -1
  29. package/evaluator.d.ts +2 -4
  30. package/evaluator.js +50 -41
  31. package/evaluator.js.map +1 -1
  32. package/facade/contentFetcherFacade.d.ts +1 -6
  33. package/facade/contentFetcherFacade.js +2 -5
  34. package/facade/contentFetcherFacade.js.map +1 -1
  35. package/facade/sdkFacade.d.ts +3 -6
  36. package/facade/sdkFacade.js +3 -7
  37. package/facade/sdkFacade.js.map +1 -1
  38. package/help.d.ts +3 -0
  39. package/help.js +26 -0
  40. package/help.js.map +1 -0
  41. package/package.json +1 -3
  42. package/queue/persistentQueue.d.ts +1 -3
  43. package/queue/persistentQueue.js +9 -16
  44. package/queue/persistentQueue.js.map +1 -1
  45. package/schema/eventSchemas.js +0 -16
  46. package/schema/eventSchemas.js.map +1 -1
  47. package/schema/sdkFacadeSchemas.js +5 -1
  48. package/schema/sdkFacadeSchemas.js.map +1 -1
  49. package/schema/sdkSchemas.js +7 -0
  50. package/schema/sdkSchemas.js.map +1 -1
  51. package/sdk.d.ts +2 -0
  52. package/sdk.js +1 -2
  53. package/sdk.js.map +1 -1
  54. package/src/cache/cookieCache.ts +13 -2
  55. package/src/channel/channel.ts +32 -0
  56. package/src/channel/guaranteedChannel.ts +4 -4
  57. package/src/channel/httpBeaconChannel.ts +128 -0
  58. package/src/channel/index.ts +1 -2
  59. package/src/channel/queuedChannel.ts +5 -5
  60. package/src/channel/retryChannel.ts +9 -5
  61. package/src/channel/sandboxChannel.ts +5 -1
  62. package/src/container.ts +17 -19
  63. package/src/contentFetcher.ts +42 -30
  64. package/src/evaluator.ts +79 -67
  65. package/src/facade/contentFetcherFacade.ts +2 -11
  66. package/src/facade/sdkFacade.ts +5 -14
  67. package/src/help.ts +24 -0
  68. package/src/queue/persistentQueue.ts +11 -22
  69. package/src/schema/eventSchemas.ts +0 -16
  70. package/src/schema/sdkFacadeSchemas.ts +14 -2
  71. package/src/schema/sdkSchemas.ts +7 -0
  72. package/src/sdk.ts +3 -2
  73. package/src/trackingEvents.ts +0 -4
  74. package/trackingEvents.d.ts +0 -4
  75. package/trackingEvents.js.map +1 -1
  76. package/channel/beaconSocketChannel.d.ts +0 -37
  77. package/channel/beaconSocketChannel.js +0 -83
  78. package/channel/beaconSocketChannel.js.map +0 -1
  79. package/channel/socketChannel.d.ts +0 -31
  80. package/channel/socketChannel.js +0 -145
  81. package/channel/socketChannel.js.map +0 -1
  82. package/src/channel/beaconSocketChannel.ts +0 -153
  83. package/src/channel/socketChannel.ts +0 -217
@@ -5,6 +5,7 @@ import {BASE_ENDPOINT_URL, CLIENT_LIBRARY} from './constants';
5
5
  import {formatMessage} from './error';
6
6
  import {Logger, NullLogger} from './logging';
7
7
  import type {ApiKey} from './apiKey';
8
+ import {Help} from './help';
8
9
 
9
10
  export type ErrorResponse = {
10
11
  type: string,
@@ -54,10 +55,6 @@ export type DynamicContentOptions = BasicOptions & {
54
55
  clientId?: string,
55
56
  clientIp?: string,
56
57
  clientAgent?: string,
57
- /**
58
- * @deprecated Use `clientAgent` instead. This option will be removed in future releases.
59
- */
60
- userAgent?: string,
61
58
  userToken?: Token|string,
62
59
  previewToken?: Token|string,
63
60
  context?: EvaluationContext,
@@ -80,11 +77,15 @@ export type Configuration = {
80
77
  apiKey?: string|ApiKey,
81
78
  baseEndpointUrl?: string,
82
79
  logger?: Logger,
80
+ defaultTimeout?: number,
81
+ defaultPreferredLocale?: string,
83
82
  };
84
83
 
85
84
  type InternalConfiguration = {
86
85
  appId?: string,
87
86
  apiKey?: string,
87
+ defaultTimeout?: number,
88
+ defaultPreferredLocale?: string,
88
89
  };
89
90
 
90
91
  export class ContentFetcher {
@@ -117,6 +118,8 @@ export class ContentFetcher {
117
118
  this.configuration = {
118
119
  appId: configuration.appId,
119
120
  apiKey: apiKey,
121
+ defaultTimeout: configuration.defaultTimeout,
122
+ defaultPreferredLocale: configuration.defaultPreferredLocale,
120
123
  };
121
124
  }
122
125
 
@@ -127,34 +130,44 @@ export class ContentFetcher {
127
130
 
128
131
  return new Promise((resolve, reject) => {
129
132
  const abortController = new AbortController();
133
+ const timeout = options.timeout ?? this.configuration.defaultTimeout;
130
134
 
131
- if (options.timeout !== undefined) {
135
+ if (timeout !== undefined) {
132
136
  setTimeout(
133
137
  () => {
134
138
  const response: ErrorResponse = {
135
- title: 'Maximum timeout reached before content could be loaded.',
139
+ title: `Content could not be loaded in time for slot '${slotId}'.`,
136
140
  type: ContentErrorType.TIMEOUT,
137
- detail: `The content took more than ${options.timeout}ms to load.`,
141
+ detail: `The content took more than ${timeout}ms to load.`,
138
142
  status: 408, // Request Timeout
139
143
  };
140
144
 
141
145
  abortController.abort();
142
146
 
147
+ this.logHelp(response.status);
148
+
143
149
  reject(new ContentError(response));
144
150
  },
145
- options.timeout,
151
+ timeout,
146
152
  );
147
153
  }
148
154
 
149
155
  this.load(slotId, abortController.signal, options)
150
- .then(
151
- response => response.json()
156
+ .then(response => {
157
+ const region = response.headers.get('X-Croct-Region');
158
+ const timing = response.headers.get('X-Croct-Timing');
159
+
160
+ this.logger.debug(`Content for slot '${slotId}' processed by region ${region} in ${timing}.`);
161
+
162
+ return response.json()
152
163
  .then(body => {
153
164
  if (response.ok) {
154
- resolve(body);
155
- } else {
156
- reject(new ContentError(body));
165
+ return resolve(body);
157
166
  }
167
+
168
+ this.logHelp(response.status);
169
+
170
+ reject(new ContentError(body));
158
171
  })
159
172
  .catch(error => {
160
173
  if (!response.ok) {
@@ -162,8 +175,8 @@ export class ContentFetcher {
162
175
  }
163
176
 
164
177
  throw error;
165
- }),
166
- )
178
+ });
179
+ })
167
180
  .catch(error => {
168
181
  if (!abortController.signal.aborted) {
169
182
  reject(
@@ -204,22 +217,15 @@ export class ContentFetcher {
204
217
  payload.version = `${options.version}`;
205
218
  }
206
219
 
207
- if (options.preferredLocale !== undefined) {
208
- payload.preferredLocale = options.preferredLocale;
220
+ const preferredLocale = options.preferredLocale ?? this.configuration.defaultPreferredLocale;
221
+
222
+ if (preferredLocale !== undefined) {
223
+ payload.preferredLocale = preferredLocale;
209
224
  }
210
225
 
211
226
  const dynamic = ContentFetcher.isDynamicContent(options);
212
227
 
213
228
  if (dynamic) {
214
- if (options.userAgent !== undefined) {
215
- this.logger.warn(
216
- 'The `userAgent` option is deprecated and '
217
- + 'will be removed in future releases. '
218
- + 'Please update the part of your code calling the `fetch` method '
219
- + 'to use the `clientAgent` option instead.',
220
- );
221
- }
222
-
223
229
  if (options.clientId !== undefined) {
224
230
  headers['X-Client-Id'] = options.clientId;
225
231
  }
@@ -232,10 +238,8 @@ export class ContentFetcher {
232
238
  headers['X-Token'] = options.userToken.toString();
233
239
  }
234
240
 
235
- const clientAgent = options.clientAgent ?? options.userAgent;
236
-
237
- if (clientAgent !== undefined) {
238
- headers['X-Client-Agent'] = clientAgent;
241
+ if (options.clientAgent !== undefined) {
242
+ headers['X-Client-Agent'] = options.clientAgent;
239
243
  }
240
244
 
241
245
  if (options.context !== undefined) {
@@ -264,6 +268,14 @@ export class ContentFetcher {
264
268
  });
265
269
  }
266
270
 
271
+ private logHelp(statusCode: number): void {
272
+ const help = Help.forStatusCode(statusCode);
273
+
274
+ if (help !== undefined) {
275
+ this.logger.error(help);
276
+ }
277
+ }
278
+
267
279
  private static isDynamicContent(options: FetchOptions): options is DynamicContentOptions {
268
280
  return options.static !== true;
269
281
  }
package/src/evaluator.ts CHANGED
@@ -5,6 +5,7 @@ import {formatMessage} from './error';
5
5
  import {getLength, getLocation, Location} from './sourceLocation';
6
6
  import {Logger, NullLogger} from './logging';
7
7
  import type {ApiKey} from './apiKey';
8
+ import {Help} from './help';
8
9
 
9
10
  export type Campaign = {
10
11
  name?: string,
@@ -37,10 +38,6 @@ export type EvaluationOptions = {
37
38
  clientId?: string,
38
39
  clientIp?: string,
39
40
  clientAgent?: string,
40
- /**
41
- * @deprecated Use `clientAgent` instead. This option will be removed in future releases.
42
- */
43
- userAgent?: string,
44
41
  userToken?: Token|string,
45
42
  timeout?: number,
46
43
  context?: EvaluationContext,
@@ -98,11 +95,13 @@ export type Configuration = {
98
95
  apiKey?: string|ApiKey,
99
96
  baseEndpointUrl?: string,
100
97
  logger?: Logger,
98
+ defaultTimeout?: number,
101
99
  };
102
100
 
103
101
  type InternalConfiguration = {
104
102
  appId?: string,
105
103
  apiKey?: string,
104
+ defaultTimeout?: number,
106
105
  };
107
106
 
108
107
  export class Evaluator {
@@ -132,18 +131,20 @@ export class Evaluator {
132
131
  this.configuration = {
133
132
  appId: configuration.appId,
134
133
  apiKey: apiKey,
134
+ defaultTimeout: configuration.defaultTimeout,
135
135
  };
136
136
  }
137
137
 
138
138
  public evaluate(query: string, options: EvaluationOptions = {}): Promise<JsonValue> {
139
139
  const length = getLength(query);
140
+ const reference = query.length > 20 ? `${query.slice(0, 20)}...` : query;
140
141
 
141
142
  if (length > Evaluator.MAX_QUERY_LENGTH) {
142
143
  const response: QueryErrorResponse = {
143
144
  title: 'The query is too complex.',
144
145
  status: 422, // Unprocessable Entity
145
146
  type: EvaluationErrorType.TOO_COMPLEX_QUERY,
146
- detail: `The query must be at most ${Evaluator.MAX_QUERY_LENGTH} characters long, `
147
+ detail: `The query "${reference}" must be at most ${Evaluator.MAX_QUERY_LENGTH} characters long, `
147
148
  + `but it is ${length} characters long.`,
148
149
  errors: [{
149
150
  cause: 'The query is longer than expected.',
@@ -154,98 +155,101 @@ export class Evaluator {
154
155
  return Promise.reject(new QueryError(response));
155
156
  }
156
157
 
157
- const body: JsonObject = {
158
+ const payload: JsonObject = {
158
159
  query: query,
159
160
  };
160
161
 
161
162
  if (options.context !== undefined) {
162
- body.context = options.context;
163
+ payload.context = options.context;
163
164
  }
164
165
 
165
166
  return new Promise((resolve, reject) => {
166
167
  const abortController = new AbortController();
168
+ const timeout = options.timeout ?? this.configuration.defaultTimeout;
167
169
 
168
- if (options.timeout !== undefined) {
170
+ if (timeout !== undefined) {
169
171
  setTimeout(
170
172
  () => {
171
173
  const response: ErrorResponse = {
172
- title: 'Maximum evaluation timeout reached before evaluation could complete.',
174
+ title: `Evaluation could not be completed in time for query "${reference}".`,
173
175
  type: EvaluationErrorType.TIMEOUT,
174
- detail: `The evaluation took more than ${options.timeout}ms to complete.`,
176
+ detail: `The evaluation took more than ${timeout}ms to complete.`,
175
177
  status: 408, // Request Timeout
176
178
  };
177
179
 
178
180
  abortController.abort();
179
181
 
182
+ this.logHelp(response.status);
183
+
180
184
  reject(new EvaluationError(response));
181
185
  },
182
- options.timeout,
186
+ timeout,
183
187
  );
184
188
  }
185
189
 
186
- const promise = this.fetch(body, abortController.signal, options);
190
+ const promise = this.fetch(payload, abortController.signal, options);
187
191
 
188
192
  promise.then(
189
- response => response.json()
190
- .then(data => {
191
- if (response.ok) {
192
- return resolve(data);
193
- }
194
-
195
- const errorResponse: ErrorResponse = data;
196
-
197
- switch (errorResponse.type) {
198
- case EvaluationErrorType.INVALID_QUERY:
199
- case EvaluationErrorType.EVALUATION_FAILED:
200
- case EvaluationErrorType.TOO_COMPLEX_QUERY:
201
- reject(new QueryError(errorResponse as QueryErrorResponse));
202
-
203
- break;
204
-
205
- default:
206
- reject(new EvaluationError(errorResponse));
207
-
208
- break;
209
- }
210
- })
211
- .catch(error => {
212
- if (!response.ok) {
213
- throw new Error(`Error ${response.status} - ${response.statusText}`);
214
- }
215
-
216
- throw error;
217
- }),
218
- )
219
- .catch(
220
- error => {
221
- if (!abortController.signal.aborted) {
222
- reject(
223
- new EvaluationError({
224
- title: formatMessage(error),
225
- type: EvaluationErrorType.UNEXPECTED_ERROR,
226
- detail: 'Please try again or contact Croct support if the error persists.',
227
- status: 500, // Internal Server Error
228
- }),
229
- );
230
- }
231
- },
232
- );
193
+ response => {
194
+ const region = response.headers.get('X-Croct-Region');
195
+ const timing = response.headers.get('X-Croct-Timing');
196
+
197
+ this.logger.debug(
198
+ `Evaluation of the query "${reference}" processed by region ${region} in ${timing}.`,
199
+ );
200
+
201
+ return response.json()
202
+ .then(body => {
203
+ if (response.ok) {
204
+ return resolve(body);
205
+ }
206
+
207
+ this.logHelp(response.status);
208
+
209
+ const problem: ErrorResponse = body;
210
+
211
+ switch (problem.type) {
212
+ case EvaluationErrorType.INVALID_QUERY:
213
+ case EvaluationErrorType.EVALUATION_FAILED:
214
+ case EvaluationErrorType.TOO_COMPLEX_QUERY:
215
+ reject(new QueryError(problem as QueryErrorResponse));
216
+
217
+ break;
218
+
219
+ default:
220
+ reject(new EvaluationError(problem));
221
+
222
+ break;
223
+ }
224
+ })
225
+ .catch(error => {
226
+ if (!response.ok) {
227
+ throw new Error(`Error ${response.status} - ${response.statusText}`);
228
+ }
229
+
230
+ throw error;
231
+ });
232
+ },
233
+ ).catch(
234
+ error => {
235
+ if (!abortController.signal.aborted) {
236
+ reject(
237
+ new EvaluationError({
238
+ title: formatMessage(error),
239
+ type: EvaluationErrorType.UNEXPECTED_ERROR,
240
+ detail: 'Please try again or contact Croct support if the error persists.',
241
+ status: 500, // Internal Server Error
242
+ }),
243
+ );
244
+ }
245
+ },
246
+ );
233
247
  });
234
248
  }
235
249
 
236
250
  private fetch(body: JsonObject, signal: AbortSignal, options: EvaluationOptions): Promise<Response> {
237
251
  const {appId, apiKey} = this.configuration;
238
- const {clientId, clientIp, userToken} = options;
239
- const clientAgent = options.clientAgent ?? options.userAgent;
240
-
241
- if (options.userAgent !== undefined) {
242
- this.logger.warn(
243
- 'The `userAgent` option is deprecated and '
244
- + 'will be removed in future releases. '
245
- + 'Please update the part of your code calling the `evaluate` method '
246
- + 'to use the `clientAgent` option instead.',
247
- );
248
- }
252
+ const {clientId, clientIp, userToken, clientAgent} = options;
249
253
 
250
254
  const headers: Record<string, string> = {
251
255
  'Content-Type': 'application/json',
@@ -292,6 +296,14 @@ export class Evaluator {
292
296
  });
293
297
  }
294
298
 
299
+ private logHelp(statusCode: number): void {
300
+ const help = Help.forStatusCode(statusCode);
301
+
302
+ if (help !== undefined) {
303
+ this.logger.error(help);
304
+ }
305
+ }
306
+
295
307
  public toJSON(): never {
296
308
  // Prevent sensitive configuration from being serialized
297
309
  throw new Error('Unserializable value.');
@@ -21,11 +21,7 @@ function validate(options: unknown): asserts options is FetchOptions {
21
21
  }
22
22
  }
23
23
 
24
- type Options = {
25
- preferredLocale?: string,
26
- };
27
-
28
- export type Configuration = Options & {
24
+ export type Configuration = {
29
25
  contentFetcher: ContentFetcher,
30
26
  contextFactory: ContextFactory,
31
27
  previewTokenProvider: TokenProvider,
@@ -44,17 +40,12 @@ export class ContentFetcherFacade {
44
40
 
45
41
  private readonly cidAssigner: CidAssigner;
46
42
 
47
- private readonly options: Options;
48
-
49
43
  public constructor(configuration: Configuration) {
50
44
  this.fetcher = configuration.contentFetcher;
51
45
  this.previewTokenProvider = configuration.previewTokenProvider;
52
46
  this.userTokenProvider = configuration.userTokenProvider;
53
47
  this.cidAssigner = configuration.cidAssigner;
54
48
  this.contextFactory = configuration.contextFactory;
55
- this.options = {
56
- preferredLocale: configuration.preferredLocale,
57
- };
58
49
  }
59
50
 
60
51
  public async fetch<P extends JsonObject>(slotId: string, options: FetchOptions = {}): Promise<FetchResponse<P>> {
@@ -72,7 +63,7 @@ export class ContentFetcherFacade {
72
63
  version: options.version,
73
64
  context: this.contextFactory.createContext(options.attributes),
74
65
  timeout: options.timeout,
75
- preferredLocale: options.preferredLocale ?? this.options.preferredLocale,
66
+ preferredLocale: options.preferredLocale,
76
67
  });
77
68
  }
78
69
  }
@@ -17,11 +17,7 @@ import {ContentFetcherFacade} from './contentFetcherFacade';
17
17
  import {CookieCacheConfiguration} from '../cache/cookieCache';
18
18
  import {EventSubjectProcessor} from '../eventSubjectProcessor';
19
19
 
20
- type Options = {
21
- preferredLocale?: string,
22
- };
23
-
24
- export type Configuration = Options & {
20
+ export type Configuration = {
25
21
  appId: string,
26
22
  tokenScope?: TokenScope,
27
23
  debug?: boolean,
@@ -41,6 +37,8 @@ export type Configuration = Options & {
41
37
  userToken?: CookieCacheConfiguration,
42
38
  previewToken?: CookieCacheConfiguration,
43
39
  },
40
+ defaultFetchTimeout?: number,
41
+ defaultPreferredLocale?: string,
44
42
  };
45
43
 
46
44
  function validateConfiguration(configuration: unknown): asserts configuration is Configuration {
@@ -64,17 +62,14 @@ export class SdkFacade {
64
62
 
65
63
  private contentFetcherFacade?: ContentFetcherFacade;
66
64
 
67
- private readonly options: Options;
68
-
69
- private constructor(sdk: Sdk, options: Options = {}) {
65
+ private constructor(sdk: Sdk) {
70
66
  this.sdk = sdk;
71
- this.options = options;
72
67
  }
73
68
 
74
69
  public static init(configuration: Configuration): SdkFacade {
75
70
  validateConfiguration(configuration);
76
71
 
77
- const {track = true, userId, token, preferredLocale, ...containerConfiguration} = configuration;
72
+ const {track = true, userId, token, ...containerConfiguration} = configuration;
78
73
 
79
74
  if (userId !== undefined && token !== undefined) {
80
75
  throw new Error('Either the user ID or token can be specified, but not both.');
@@ -89,9 +84,6 @@ export class SdkFacade {
89
84
  disableCidMirroring: containerConfiguration.disableCidMirroring ?? false,
90
85
  eventProcessor: container => new EventSubjectProcessor(container.getLogger('EventSubjectProcessor')),
91
86
  }),
92
- {
93
- preferredLocale: preferredLocale,
94
- },
95
87
  );
96
88
 
97
89
  if (userId !== undefined) {
@@ -184,7 +176,6 @@ export class SdkFacade {
184
176
  cidAssigner: this.sdk.cidAssigner,
185
177
  previewTokenProvider: this.sdk.previewTokenStore,
186
178
  userTokenProvider: this.sdk.userTokenStore,
187
- preferredLocale: this.options.preferredLocale,
188
179
  });
189
180
  }
190
181
 
package/src/help.ts ADDED
@@ -0,0 +1,24 @@
1
+ export namespace Help {
2
+ export function forStatusCode(statusCode: number): string|undefined {
3
+ switch (statusCode) {
4
+ case 401:
5
+ return 'The request was not authorized, most likely due to invalid credentials. '
6
+ + 'For help, see https://croct.help/sdk/js/invalid-credentials';
7
+
8
+ case 403:
9
+ return 'The origin of the request is not allowed in your application settings. '
10
+ + 'For help, see https://croct.help/sdk/js/cors';
11
+
12
+ case 408:
13
+ return 'The request timed out. '
14
+ + 'For help, see https://croct.help/sdk/js/timeout';
15
+
16
+ case 423:
17
+ return 'The application has exceeded the monthly active users (MAU) quota. '
18
+ + 'For help, see https://croct.help/sdk/js/mau-exceeded';
19
+
20
+ default:
21
+ return undefined;
22
+ }
23
+ }
24
+ }
@@ -1,8 +1,6 @@
1
1
  import {Queue} from './queue';
2
2
 
3
3
  export class PersistentQueue<T> implements Queue<T> {
4
- private cache: T[];
5
-
6
4
  private readonly storage: Storage;
7
5
 
8
6
  private readonly key: string;
@@ -17,7 +15,7 @@ export class PersistentQueue<T> implements Queue<T> {
17
15
  }
18
16
 
19
17
  public getCapacity(): number {
20
- return Infinity;
18
+ return Number.MAX_SAFE_INTEGER;
21
19
  }
22
20
 
23
21
  public isEmpty(): boolean {
@@ -29,9 +27,7 @@ export class PersistentQueue<T> implements Queue<T> {
29
27
  }
30
28
 
31
29
  public push(value: T): void {
32
- this.queue.push(value);
33
-
34
- this.flush();
30
+ this.save([...this.queue, value]);
35
31
  }
36
32
 
37
33
  public peek(): T | null {
@@ -45,30 +41,19 @@ export class PersistentQueue<T> implements Queue<T> {
45
41
  }
46
42
 
47
43
  public shift(): T {
48
- const value = this.queue.shift();
44
+ const queue = [...this.queue];
45
+ const value = queue.shift();
49
46
 
50
47
  if (value === undefined) {
51
48
  throw new Error('The queue is empty.');
52
49
  }
53
50
 
54
- this.flush();
51
+ this.save(queue);
55
52
 
56
53
  return value;
57
54
  }
58
55
 
59
- private get queue(): T[] {
60
- if (this.cache === undefined) {
61
- this.cache = this.load();
62
- }
63
-
64
- return this.cache;
65
- }
66
-
67
- private flush(): void {
68
- this.storage.setItem(this.key, JSON.stringify(this.cache ?? []));
69
- }
70
-
71
- private load(): T[] {
56
+ private get queue(): readonly T[] {
72
57
  const data = this.storage.getItem(this.key);
73
58
 
74
59
  if (data === null) {
@@ -77,8 +62,12 @@ export class PersistentQueue<T> implements Queue<T> {
77
62
 
78
63
  try {
79
64
  return JSON.parse(data);
80
- } catch (error) {
65
+ } catch {
81
66
  return [];
82
67
  }
83
68
  }
69
+
70
+ private save(data: T[]): void {
71
+ this.storage.setItem(this.key, JSON.stringify(data));
72
+ }
84
73
  }
@@ -114,22 +114,6 @@ export const eventOccurred = new ObjectType({
114
114
  minLength: 1,
115
115
  maxLength: 50,
116
116
  }),
117
- testId: new StringType({
118
- minLength: 1,
119
- maxLength: 50,
120
- }),
121
- groupId: new StringType({
122
- minLength: 1,
123
- maxLength: 50,
124
- }),
125
- personalizationId: new StringType({
126
- minLength: 1,
127
- maxLength: 50,
128
- }),
129
- audience: new StringType({
130
- minLength: 1,
131
- maxLength: 50,
132
- }),
133
117
  details: new ObjectType({
134
118
  additionalProperties: new UnionType(
135
119
  new NullType(),
@@ -1,4 +1,12 @@
1
- import {ObjectType, StringType, BooleanType, UnionType, NullType, FunctionType} from '../validation';
1
+ import {
2
+ ObjectType,
3
+ StringType,
4
+ BooleanType,
5
+ UnionType,
6
+ NullType,
7
+ FunctionType,
8
+ NumberType,
9
+ } from '../validation';
2
10
  import {tokenScopeSchema} from './contextSchemas';
3
11
  import {cookieOptionsSchema, eventMetadataSchema} from './sdkSchemas';
4
12
  import {loggerSchema} from './loggerSchema';
@@ -45,7 +53,11 @@ export const sdkFacadeConfigurationSchema = new ObjectType({
45
53
  previewToken: cookieOptionsSchema,
46
54
  },
47
55
  }),
48
- preferredLocale: new StringType({
56
+ defaultFetchTimeout: new NumberType({
57
+ integer: true,
58
+ minimum: 1,
59
+ }),
60
+ defaultPreferredLocale: new StringType({
49
61
  pattern: /^[a-z]{2,3}([-_][a-z]{2,3})?$/i,
50
62
  }),
51
63
  },
@@ -71,5 +71,12 @@ export const sdkConfigurationSchema = new ObjectType({
71
71
  urlSanitizer: new FunctionType(),
72
72
  eventMetadata: eventMetadataSchema,
73
73
  eventProcessor: new FunctionType(),
74
+ defaultFetchTimeout: new NumberType({
75
+ integer: true,
76
+ minimum: 1,
77
+ }),
78
+ defaultPreferredLocale: new StringType({
79
+ pattern: /^[a-z]{2,3}([-_][a-z]{2,3})?$/i,
80
+ }),
74
81
  },
75
82
  });