@croct/sdk 0.16.1 → 0.17.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.
Files changed (94) hide show
  1. package/base64Url.js +2 -3
  2. package/base64Url.js.map +1 -1
  3. package/cache/cookieCache.d.ts +1 -1
  4. package/cache/cookieCache.js +12 -2
  5. package/cache/cookieCache.js.map +1 -1
  6. package/channel/channel.d.ts +7 -0
  7. package/channel/channel.js +23 -0
  8. package/channel/channel.js.map +1 -1
  9. package/channel/guaranteedChannel.js +4 -3
  10. package/channel/guaranteedChannel.js.map +1 -1
  11. package/channel/httpBeaconChannel.d.ts +23 -0
  12. package/channel/httpBeaconChannel.js +89 -0
  13. package/channel/httpBeaconChannel.js.map +1 -0
  14. package/channel/index.d.ts +1 -2
  15. package/channel/index.js +3 -5
  16. package/channel/index.js.map +1 -1
  17. package/channel/queuedChannel.js +5 -4
  18. package/channel/queuedChannel.js.map +1 -1
  19. package/channel/retryChannel.js +8 -4
  20. package/channel/retryChannel.js.map +1 -1
  21. package/channel/sandboxChannel.js +4 -0
  22. package/channel/sandboxChannel.js.map +1 -1
  23. package/constants.d.ts +2 -2
  24. package/constants.js +1 -1
  25. package/container.d.ts +3 -0
  26. package/container.js +15 -14
  27. package/container.js.map +1 -1
  28. package/contentFetcher.d.ts +3 -4
  29. package/contentFetcher.js +44 -27
  30. package/contentFetcher.js.map +1 -1
  31. package/error.js +2 -3
  32. package/error.js.map +1 -1
  33. package/evaluator.d.ts +2 -4
  34. package/evaluator.js +54 -39
  35. package/evaluator.js.map +1 -1
  36. package/facade/contentFetcherFacade.d.ts +1 -6
  37. package/facade/contentFetcherFacade.js +2 -5
  38. package/facade/contentFetcherFacade.js.map +1 -1
  39. package/facade/sdkFacade.d.ts +3 -6
  40. package/facade/sdkFacade.js +3 -7
  41. package/facade/sdkFacade.js.map +1 -1
  42. package/help.d.ts +3 -0
  43. package/help.js +26 -0
  44. package/help.js.map +1 -0
  45. package/package.json +1 -3
  46. package/queue/persistentQueue.d.ts +1 -3
  47. package/queue/persistentQueue.js +9 -16
  48. package/queue/persistentQueue.js.map +1 -1
  49. package/schema/eventSchemas.js +0 -16
  50. package/schema/eventSchemas.js.map +1 -1
  51. package/schema/sdkFacadeSchemas.js +5 -1
  52. package/schema/sdkFacadeSchemas.js.map +1 -1
  53. package/schema/sdkSchemas.js +7 -0
  54. package/schema/sdkSchemas.js.map +1 -1
  55. package/sdk.d.ts +2 -0
  56. package/sdk.js +1 -2
  57. package/sdk.js.map +1 -1
  58. package/sourceLocation.js +3 -4
  59. package/sourceLocation.js.map +1 -1
  60. package/src/cache/cookieCache.ts +13 -2
  61. package/src/channel/channel.ts +32 -0
  62. package/src/channel/guaranteedChannel.ts +4 -4
  63. package/src/channel/httpBeaconChannel.ts +128 -0
  64. package/src/channel/index.ts +1 -2
  65. package/src/channel/queuedChannel.ts +5 -5
  66. package/src/channel/retryChannel.ts +9 -5
  67. package/src/channel/sandboxChannel.ts +5 -1
  68. package/src/container.ts +17 -19
  69. package/src/contentFetcher.ts +48 -29
  70. package/src/evaluator.ts +81 -65
  71. package/src/facade/contentFetcherFacade.ts +2 -11
  72. package/src/facade/sdkFacade.ts +5 -14
  73. package/src/help.ts +24 -0
  74. package/src/queue/persistentQueue.ts +11 -22
  75. package/src/schema/eventSchemas.ts +0 -16
  76. package/src/schema/sdkFacadeSchemas.ts +14 -2
  77. package/src/schema/sdkSchemas.ts +7 -0
  78. package/src/sdk.ts +3 -2
  79. package/src/trackingEvents.ts +0 -4
  80. package/trackingEvents.d.ts +0 -4
  81. package/trackingEvents.js +3 -3
  82. package/trackingEvents.js.map +1 -1
  83. package/uuid.js +1 -2
  84. package/uuid.js.map +1 -1
  85. package/validation/violation.js +2 -3
  86. package/validation/violation.js.map +1 -1
  87. package/channel/beaconSocketChannel.d.ts +0 -37
  88. package/channel/beaconSocketChannel.js +0 -83
  89. package/channel/beaconSocketChannel.js.map +0 -1
  90. package/channel/socketChannel.d.ts +0 -31
  91. package/channel/socketChannel.js +0 -145
  92. package/channel/socketChannel.js.map +0 -1
  93. package/src/channel/beaconSocketChannel.ts +0 -153
  94. package/src/channel/socketChannel.ts +0 -217
package/src/container.ts CHANGED
@@ -20,13 +20,12 @@ import {
20
20
  RetryChannel,
21
21
  GuaranteedChannel,
22
22
  EncodedChannel,
23
- BeaconSocketChannel,
24
- SocketChannel,
25
23
  SandboxChannel,
26
24
  } from './channel';
27
25
  import {ContentFetcher} from './contentFetcher';
28
26
  import {CookieCache, CookieCacheConfiguration} from './cache/cookieCache';
29
27
  import {FilteredLogger} from './logging/filteredLogger';
28
+ import {HttpBeaconChannel} from './channel/httpBeaconChannel';
30
29
 
31
30
  export type DependencyResolver<T> = (container: Container) => T;
32
31
 
@@ -51,9 +50,13 @@ export type Configuration = {
51
50
  },
52
51
  eventMetadata?: {[key: string]: string},
53
52
  eventProcessor?: DependencyResolver<TrackingEventProcessor>,
53
+ defaultFetchTimeout?: number,
54
+ defaultPreferredLocale?: string,
54
55
  };
55
56
 
56
57
  export class Container {
58
+ public static readonly DEFAULT_FETCH_TIMEOUT = 5_000; // 5 seconds
59
+
57
60
  private readonly configuration: Configuration;
58
61
 
59
62
  private context?: Context;
@@ -99,6 +102,7 @@ export class Container {
99
102
  appId: this.configuration.appId,
100
103
  baseEndpointUrl: this.configuration.evaluationBaseEndpointUrl,
101
104
  logger: this.getLogger('Evaluator'),
105
+ defaultTimeout: this.configuration.defaultFetchTimeout ?? Container.DEFAULT_FETCH_TIMEOUT,
102
106
  });
103
107
  }
104
108
 
@@ -115,6 +119,8 @@ export class Container {
115
119
  appId: this.configuration.appId,
116
120
  baseEndpointUrl: this.configuration.contentBaseEndpointUrl,
117
121
  logger: this.getLogger('ContentFetcher'),
122
+ defaultTimeout: this.configuration.defaultFetchTimeout ?? Container.DEFAULT_FETCH_TIMEOUT,
123
+ defaultPreferredLocale: this.configuration.defaultPreferredLocale,
118
124
  });
119
125
  }
120
126
 
@@ -226,23 +232,18 @@ export class Container {
226
232
  const queuedChannel = new QueuedChannel(
227
233
  new RetryChannel({
228
234
  channel: new GuaranteedChannel({
229
- channel: new BeaconSocketChannel({
230
- trackerEndpointUrl: `${trackerEndpointUrl}/${appId}`,
231
- tokenParameter: 'token',
232
- loggerFactory: this.getLogger.bind(this),
233
- logger: channelLogger,
234
- channelFactory: (url, logger): SocketChannel<any, any> => (
235
- new SocketChannel({url: url, logger: logger})
236
- ),
235
+ channel: new HttpBeaconChannel({
236
+ appId: appId,
237
+ endpointUrl: trackerEndpointUrl,
237
238
  cidAssigner: this.getCidAssigner(),
238
- cidParameter: 'clientId',
239
+ logger: channelLogger,
239
240
  }),
240
241
  stamper: new TimeStamper(),
241
242
  ackTimeout: 10000,
242
243
  logger: channelLogger,
243
244
  }),
244
245
  retryPolicy: new BackoffPolicy({
245
- minRetryDelay: 1000, // 1 second
246
+ minRetryDelay: 3000, // 3 seconds
246
247
  maxRetryDelay: 60 * 1000, // 60 seconds
247
248
  backoffFactor: 1.5, // 1.5 ^ attempt
248
249
  backoffJitter: 1, // add randomness
@@ -300,12 +301,9 @@ export class Container {
300
301
  }
301
302
 
302
303
  private createBeaconQueue(): MonitoredQueue<string> {
303
- const context = this.getContext();
304
- const tab = context.getTab();
305
-
306
304
  return new MonitoredQueue<string>(
307
305
  new CapacityRestrictedQueue(
308
- new PersistentQueue(this.getGlobalTabStorage('queue'), tab.id),
306
+ new PersistentQueue(this.getGlobalTabStorage('queue')),
309
307
  this.configuration.beaconQueueSize,
310
308
  ),
311
309
  this.getLogger('BeaconQueue'),
@@ -349,9 +347,9 @@ export class Container {
349
347
  }
350
348
 
351
349
  private resolveStorageNamespace(namespace: string, ...subnamespace: string[]): string {
352
- return `croct[${this.configuration
353
- .appId
354
- .toLowerCase()}].${[namespace].concat(subnamespace).join('.')}`;
350
+ const {appId} = this.configuration;
351
+
352
+ return `croct[${appId.toLowerCase()}].${[namespace].concat(subnamespace).join('.')}`;
355
353
  }
356
354
 
357
355
  private getLocalStorage(): Storage {
@@ -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
139
  title: 'Maximum timeout reached before content could be loaded.',
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(`Request 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) {
@@ -248,6 +252,13 @@ export class ContentFetcher {
248
252
  }
249
253
 
250
254
  return fetch(dynamic ? this.dynamicEndpoint : this.staticEndpoint, {
255
+ // Set the request mode to 'cors' when running in the browser.
256
+ // By default, the request mode is computed based on the referrer policy
257
+ // and response-tainting rules applied to the script that ultimately
258
+ // initiated the fetch, make this prone to errors due to unrelated
259
+ // configurations on the page.
260
+ // https://fetch.spec.whatwg.org/#origin-header
261
+ mode: typeof window === 'undefined' ? undefined : 'cors',
251
262
  credentials: 'omit',
252
263
  ...options.extra,
253
264
  method: 'POST',
@@ -257,6 +268,14 @@ export class ContentFetcher {
257
268
  });
258
269
  }
259
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
+
260
279
  private static isDynamicContent(options: FetchOptions): options is DynamicContentOptions {
261
280
  return options.static !== true;
262
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,6 +131,7 @@ 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
 
@@ -154,98 +154,99 @@ export class Evaluator {
154
154
  return Promise.reject(new QueryError(response));
155
155
  }
156
156
 
157
- const body: JsonObject = {
157
+ const payload: JsonObject = {
158
158
  query: query,
159
159
  };
160
160
 
161
161
  if (options.context !== undefined) {
162
- body.context = options.context;
162
+ payload.context = options.context;
163
163
  }
164
164
 
165
165
  return new Promise((resolve, reject) => {
166
166
  const abortController = new AbortController();
167
+ const timeout = options.timeout ?? this.configuration.defaultTimeout;
167
168
 
168
- if (options.timeout !== undefined) {
169
+ if (timeout !== undefined) {
169
170
  setTimeout(
170
171
  () => {
171
172
  const response: ErrorResponse = {
172
173
  title: 'Maximum evaluation timeout reached before evaluation could complete.',
173
174
  type: EvaluationErrorType.TIMEOUT,
174
- detail: `The evaluation took more than ${options.timeout}ms to complete.`,
175
+ detail: `The evaluation took more than ${timeout}ms to complete.`,
175
176
  status: 408, // Request Timeout
176
177
  };
177
178
 
178
179
  abortController.abort();
179
180
 
181
+ this.logHelp(response.status);
182
+
180
183
  reject(new EvaluationError(response));
181
184
  },
182
- options.timeout,
185
+ timeout,
183
186
  );
184
187
  }
185
188
 
186
- const promise = this.fetch(body, abortController.signal, options);
189
+ const promise = this.fetch(payload, abortController.signal, options);
187
190
 
188
191
  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
- );
192
+ response => {
193
+ const region = response.headers.get('X-Croct-Region');
194
+ const timing = response.headers.get('X-Croct-Timing');
195
+
196
+ this.logger.debug(`Request processed by region ${region} in ${timing}`);
197
+
198
+ return response.json()
199
+ .then(body => {
200
+ if (response.ok) {
201
+ return resolve(body);
202
+ }
203
+
204
+ this.logHelp(response.status);
205
+
206
+ const problem: ErrorResponse = body;
207
+
208
+ switch (problem.type) {
209
+ case EvaluationErrorType.INVALID_QUERY:
210
+ case EvaluationErrorType.EVALUATION_FAILED:
211
+ case EvaluationErrorType.TOO_COMPLEX_QUERY:
212
+ reject(new QueryError(problem as QueryErrorResponse));
213
+
214
+ break;
215
+
216
+ default:
217
+ reject(new EvaluationError(problem));
218
+
219
+ break;
220
+ }
221
+ })
222
+ .catch(error => {
223
+ if (!response.ok) {
224
+ throw new Error(`Error ${response.status} - ${response.statusText}`);
225
+ }
226
+
227
+ throw error;
228
+ });
229
+ },
230
+ ).catch(
231
+ error => {
232
+ if (!abortController.signal.aborted) {
233
+ reject(
234
+ new EvaluationError({
235
+ title: formatMessage(error),
236
+ type: EvaluationErrorType.UNEXPECTED_ERROR,
237
+ detail: 'Please try again or contact Croct support if the error persists.',
238
+ status: 500, // Internal Server Error
239
+ }),
240
+ );
241
+ }
242
+ },
243
+ );
233
244
  });
234
245
  }
235
246
 
236
247
  private fetch(body: JsonObject, signal: AbortSignal, options: EvaluationOptions): Promise<Response> {
237
248
  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
- }
249
+ const {clientId, clientIp, userToken, clientAgent} = options;
249
250
 
250
251
  const headers: Record<string, string> = {
251
252
  'Content-Type': 'application/json',
@@ -276,6 +277,13 @@ export class Evaluator {
276
277
  }
277
278
 
278
279
  return fetch(this.endpoint, {
280
+ // Set the request mode to 'cors' when running in the browser.
281
+ // By default, the request mode is computed based on the referrer policy
282
+ // and response-tainting rules applied to the script that ultimately
283
+ // initiated the fetch, make this prone to errors due to unrelated
284
+ // configurations on the page.
285
+ // https://fetch.spec.whatwg.org/#origin-header
286
+ mode: typeof window === 'undefined' ? undefined : 'cors',
279
287
  credentials: 'omit',
280
288
  ...options.extra,
281
289
  method: 'POST',
@@ -285,6 +293,14 @@ export class Evaluator {
285
293
  });
286
294
  }
287
295
 
296
+ private logHelp(statusCode: number): void {
297
+ const help = Help.forStatusCode(statusCode);
298
+
299
+ if (help !== undefined) {
300
+ this.logger.error(help);
301
+ }
302
+ }
303
+
288
304
  public toJSON(): never {
289
305
  // Prevent sensitive configuration from being serialized
290
306
  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
+ }