@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.
- package/cache/cookieCache.d.ts +1 -1
- package/cache/cookieCache.js +12 -2
- package/cache/cookieCache.js.map +1 -1
- package/channel/channel.d.ts +7 -0
- package/channel/channel.js +23 -0
- package/channel/channel.js.map +1 -1
- package/channel/guaranteedChannel.js +4 -3
- package/channel/guaranteedChannel.js.map +1 -1
- package/channel/httpBeaconChannel.d.ts +23 -0
- package/channel/httpBeaconChannel.js +89 -0
- package/channel/httpBeaconChannel.js.map +1 -0
- package/channel/index.d.ts +1 -2
- package/channel/index.js +3 -5
- package/channel/index.js.map +1 -1
- package/channel/queuedChannel.js +5 -4
- package/channel/queuedChannel.js.map +1 -1
- package/channel/retryChannel.js +8 -4
- package/channel/retryChannel.js.map +1 -1
- package/channel/sandboxChannel.js +4 -0
- package/channel/sandboxChannel.js.map +1 -1
- package/constants.d.ts +2 -2
- package/constants.js +1 -1
- package/container.d.ts +3 -0
- package/container.js +15 -14
- package/container.js.map +1 -1
- package/contentFetcher.d.ts +3 -4
- package/contentFetcher.js +38 -28
- package/contentFetcher.js.map +1 -1
- package/evaluator.d.ts +2 -4
- package/evaluator.js +50 -41
- package/evaluator.js.map +1 -1
- package/facade/contentFetcherFacade.d.ts +1 -6
- package/facade/contentFetcherFacade.js +2 -5
- package/facade/contentFetcherFacade.js.map +1 -1
- package/facade/sdkFacade.d.ts +3 -6
- package/facade/sdkFacade.js +3 -7
- package/facade/sdkFacade.js.map +1 -1
- package/help.d.ts +3 -0
- package/help.js +26 -0
- package/help.js.map +1 -0
- package/package.json +1 -3
- package/queue/persistentQueue.d.ts +1 -3
- package/queue/persistentQueue.js +9 -16
- package/queue/persistentQueue.js.map +1 -1
- package/schema/eventSchemas.js +0 -16
- package/schema/eventSchemas.js.map +1 -1
- package/schema/sdkFacadeSchemas.js +5 -1
- package/schema/sdkFacadeSchemas.js.map +1 -1
- package/schema/sdkSchemas.js +7 -0
- package/schema/sdkSchemas.js.map +1 -1
- package/sdk.d.ts +2 -0
- package/sdk.js +1 -2
- package/sdk.js.map +1 -1
- package/src/cache/cookieCache.ts +13 -2
- package/src/channel/channel.ts +32 -0
- package/src/channel/guaranteedChannel.ts +4 -4
- package/src/channel/httpBeaconChannel.ts +128 -0
- package/src/channel/index.ts +1 -2
- package/src/channel/queuedChannel.ts +5 -5
- package/src/channel/retryChannel.ts +9 -5
- package/src/channel/sandboxChannel.ts +5 -1
- package/src/container.ts +17 -19
- package/src/contentFetcher.ts +42 -30
- package/src/evaluator.ts +79 -67
- package/src/facade/contentFetcherFacade.ts +2 -11
- package/src/facade/sdkFacade.ts +5 -14
- package/src/help.ts +24 -0
- package/src/queue/persistentQueue.ts +11 -22
- package/src/schema/eventSchemas.ts +0 -16
- package/src/schema/sdkFacadeSchemas.ts +14 -2
- package/src/schema/sdkSchemas.ts +7 -0
- package/src/sdk.ts +3 -2
- package/src/trackingEvents.ts +0 -4
- package/trackingEvents.d.ts +0 -4
- package/trackingEvents.js.map +1 -1
- package/channel/beaconSocketChannel.d.ts +0 -37
- package/channel/beaconSocketChannel.js +0 -83
- package/channel/beaconSocketChannel.js.map +0 -1
- package/channel/socketChannel.d.ts +0 -31
- package/channel/socketChannel.js +0 -145
- package/channel/socketChannel.js.map +0 -1
- package/src/channel/beaconSocketChannel.ts +0 -153
- package/src/channel/socketChannel.ts +0 -217
package/src/contentFetcher.ts
CHANGED
|
@@ -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 (
|
|
135
|
+
if (timeout !== undefined) {
|
|
132
136
|
setTimeout(
|
|
133
137
|
() => {
|
|
134
138
|
const response: ErrorResponse = {
|
|
135
|
-
title:
|
|
139
|
+
title: `Content could not be loaded in time for slot '${slotId}'.`,
|
|
136
140
|
type: ContentErrorType.TIMEOUT,
|
|
137
|
-
detail: `The content took more than ${
|
|
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
|
-
|
|
151
|
+
timeout,
|
|
146
152
|
);
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
this.load(slotId, abortController.signal, options)
|
|
150
|
-
.then(
|
|
151
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
|
158
|
+
const payload: JsonObject = {
|
|
158
159
|
query: query,
|
|
159
160
|
};
|
|
160
161
|
|
|
161
162
|
if (options.context !== undefined) {
|
|
162
|
-
|
|
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 (
|
|
170
|
+
if (timeout !== undefined) {
|
|
169
171
|
setTimeout(
|
|
170
172
|
() => {
|
|
171
173
|
const response: ErrorResponse = {
|
|
172
|
-
title:
|
|
174
|
+
title: `Evaluation could not be completed in time for query "${reference}".`,
|
|
173
175
|
type: EvaluationErrorType.TIMEOUT,
|
|
174
|
-
detail: `The evaluation took more than ${
|
|
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
|
-
|
|
186
|
+
timeout,
|
|
183
187
|
);
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
const promise = this.fetch(
|
|
190
|
+
const promise = this.fetch(payload, abortController.signal, options);
|
|
187
191
|
|
|
188
192
|
promise.then(
|
|
189
|
-
response =>
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
new
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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
|
|
66
|
+
preferredLocale: options.preferredLocale,
|
|
76
67
|
});
|
|
77
68
|
}
|
|
78
69
|
}
|
package/src/facade/sdkFacade.ts
CHANGED
|
@@ -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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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
|
},
|
package/src/schema/sdkSchemas.ts
CHANGED
|
@@ -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
|
});
|