@contentful/optimization-api-client 0.1.0-alpha9 → 1.0.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/dist/index.mjs CHANGED
@@ -1,7 +1,15 @@
1
- import { merge } from "es-toolkit/object";
2
- import { boolean as mini_boolean, extend, number, object, optional, partial, prefault, string } from "zod/mini";
3
1
  import { createScopedLogger } from "./649.mjs";
4
2
  import { __rspack_external__contentful_optimization_api_schemas_66dcf407 } from "./199.mjs";
3
+ const fetchInputToString = (input)=>{
4
+ if ('string' == typeof input) return input;
5
+ return input.url;
6
+ };
7
+ const MISSING_FETCH_METHOD_MESSAGE = 'No fetch implementation available. Provide a fetchMethod.';
8
+ function resolveFetchMethod(fetchMethod) {
9
+ if (fetchMethod) return fetchMethod;
10
+ if ('function' == typeof globalThis.fetch) return globalThis.fetch.bind(globalThis);
11
+ throw new Error(MISSING_FETCH_METHOD_MESSAGE);
12
+ }
5
13
  const logger = createScopedLogger('ApiClient:Retry');
6
14
  const DEFAULT_INTERVAL_TIMEOUT = 0;
7
15
  const DEFAULT_RETRY_COUNT = 1;
@@ -15,22 +23,23 @@ class HttpError extends Error {
15
23
  this.status = status;
16
24
  }
17
25
  }
18
- function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod = fetch, init, url }) {
26
+ function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod, init, url }) {
19
27
  return async ()=>{
28
+ const urlLabel = fetchInputToString(url);
20
29
  try {
21
30
  const response = await fetchMethod(url, init);
22
- if (response.status === RETRY_RESPONSE_STATUS) throw new HttpError(`${apiName} API request to "${url.toString()}" failed with status: "[${response.status}] ${response.statusText}".`, RETRY_RESPONSE_STATUS);
31
+ if (response.status === RETRY_RESPONSE_STATUS) throw new HttpError(`${apiName} API request to "${urlLabel}" failed with status: "[${response.status}] ${response.statusText}".`, RETRY_RESPONSE_STATUS);
23
32
  if (!response.ok) {
24
- const httpError = new Error(`Request to "${url.toString()}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
33
+ const httpError = new Error(`Request to "${urlLabel}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
25
34
  logger.error('Request failed with non-OK status:', httpError);
26
35
  controller.abort();
27
36
  return;
28
37
  }
29
- logger.debug(`Response from "${url.toString()}":`, response);
38
+ logger.debug(`Response from "${urlLabel}":`, response);
30
39
  return response;
31
40
  } catch (error) {
32
41
  if (error instanceof HttpError && error.status === RETRY_RESPONSE_STATUS) throw error;
33
- logger.error(`Request to "${url.toString()}" failed:`, error);
42
+ logger.error(`Request to "${urlLabel}" failed:`, error);
34
43
  controller.abort();
35
44
  }
36
45
  };
@@ -43,14 +52,15 @@ async function delayRetry(intervalTimeout) {
43
52
  }, intervalTimeout);
44
53
  await promise;
45
54
  }
46
- function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
55
+ function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
56
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
47
57
  return async (url, init)=>{
48
58
  const controller = new AbortController();
49
59
  const maxAttempts = retries + 1;
50
60
  const attemptFetch = createRetryFetchCallback({
51
61
  apiName,
52
62
  controller,
53
- fetchMethod,
63
+ fetchMethod: resolvedFetchMethod,
54
64
  init,
55
65
  url
56
66
  });
@@ -70,25 +80,32 @@ function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod = fetch,
70
80
  if (0 === retriesLeft) throw error;
71
81
  await delayRetry(intervalTimeout);
72
82
  }
73
- throw new Error(`${apiName} API request to "${url.toString()}" may not be retried.`);
83
+ throw new Error(`${apiName} API request to "${fetchInputToString(url)}" may not be retried.`);
74
84
  };
75
85
  }
76
86
  const createTimeoutFetchMethod_logger = createScopedLogger('ApiClient:Timeout');
77
87
  const DEFAULT_REQUEST_TIMEOUT = 3000;
78
- function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
88
+ function isFetchAbortSignal(signal) {
89
+ return 'object' == typeof signal && null !== signal;
90
+ }
91
+ function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
92
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
79
93
  return async (url, init)=>{
80
94
  const controller = new AbortController();
81
95
  const id = setTimeout(()=>{
82
96
  if ('function' == typeof onRequestTimeout) onRequestTimeout({
83
97
  apiName
84
98
  });
85
- else createTimeoutFetchMethod_logger.error(`Request to "${url.toString()}" timed out`, new Error('Request timeout'));
99
+ else createTimeoutFetchMethod_logger.error(`Request to "${fetchInputToString(url)}" timed out`, new Error('Request timeout'));
86
100
  controller.abort();
87
101
  }, requestTimeout);
88
- const response = await fetchMethod(url, {
102
+ const { signal } = controller;
103
+ if (!isFetchAbortSignal(signal)) throw new Error('AbortController signal is not compatible with fetch.');
104
+ const requestInit = {
89
105
  ...init,
90
- signal: controller.signal
91
- });
106
+ signal
107
+ };
108
+ const response = await resolvedFetchMethod(url, requestInit);
92
109
  clearTimeout(id);
93
110
  return response;
94
111
  };
@@ -112,7 +129,7 @@ const Fetch = {
112
129
  create: createProtectedFetchMethod
113
130
  };
114
131
  const fetch_Fetch = Fetch;
115
- const src_fetch = fetch_Fetch;
132
+ const fetch = fetch_Fetch;
116
133
  const ApiClientBase_logger = createScopedLogger('ApiClient');
117
134
  const DEFAULT_ENVIRONMENT = 'main';
118
135
  class ApiClientBase {
@@ -124,7 +141,7 @@ class ApiClientBase {
124
141
  this.clientId = clientId;
125
142
  this.environment = environment ?? DEFAULT_ENVIRONMENT;
126
143
  this.name = name;
127
- this.fetch = src_fetch.create({
144
+ this.fetch = fetch.create({
128
145
  ...fetchOptions ?? {},
129
146
  apiName: name
130
147
  });
@@ -154,6 +171,9 @@ class ExperienceApiClient extends src_ApiClientBase {
154
171
  this.plainText = plainText;
155
172
  this.preflight = preflight;
156
173
  }
174
+ setLocale(locale) {
175
+ this.locale = locale;
176
+ }
157
177
  async getProfile(id, options = {}) {
158
178
  if (!id) throw new Error('Valid profile ID required.');
159
179
  const requestName = 'Get Profile';
@@ -165,7 +185,7 @@ class ExperienceApiClient extends src_ApiClientBase {
165
185
  const { data: { changes, experiences, profile } } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.ExperienceResponse, await response.json());
166
186
  const data = {
167
187
  changes,
168
- personalizations: experiences,
188
+ selectedOptimizations: experiences,
169
189
  profile
170
190
  };
171
191
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -199,7 +219,7 @@ class ExperienceApiClient extends src_ApiClientBase {
199
219
  const { data: { changes, experiences, profile } } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.ExperienceResponse, await response.json());
200
220
  const data = {
201
221
  changes,
202
- personalizations: experiences,
222
+ selectedOptimizations: experiences,
203
223
  profile
204
224
  };
205
225
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -226,7 +246,7 @@ class ExperienceApiClient extends src_ApiClientBase {
226
246
  const { data: { changes, experiences, profile } } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.ExperienceResponse, await response.json());
227
247
  const data = {
228
248
  changes,
229
- personalizations: experiences,
249
+ selectedOptimizations: experiences,
230
250
  profile
231
251
  };
232
252
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -305,39 +325,43 @@ class ExperienceApiClient extends src_ApiClientBase {
305
325
  });
306
326
  }
307
327
  }
308
- const experience = ExperienceApiClient;
309
328
  const InsightsApiClient_logger = createScopedLogger('ApiClient:Insights');
329
+ const BEACON_FALLBACK_MESSAGE = 'beacon failed; falling back to fetch';
310
330
  const INSIGHTS_BASE_URL = 'https://ingest.insights.ninetailed.co/';
311
331
  class InsightsApiClient extends src_ApiClientBase {
312
332
  baseUrl;
313
- beaconHandler;
314
333
  constructor(config){
315
334
  super('Insights', config);
316
- const { baseUrl, beaconHandler } = config;
335
+ const { baseUrl } = config;
317
336
  this.baseUrl = baseUrl || INSIGHTS_BASE_URL;
318
- this.beaconHandler = beaconHandler;
319
337
  }
320
338
  async sendBatchEvents(batches, options = {}) {
321
- const { beaconHandler = this.beaconHandler } = options;
339
+ const { beacon } = options;
322
340
  const url = new URL(`v1/organizations/${this.clientId}/environments/${this.environment}/events`, this.baseUrl);
341
+ const urlString = url.toString();
323
342
  const body = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.BatchInsightsEventArray, batches);
324
- if ('function' == typeof beaconHandler) {
325
- InsightsApiClient_logger.debug('Queueing events via beaconHandler');
326
- const beaconSuccessfullyQueued = beaconHandler(url, body);
327
- if (beaconSuccessfullyQueued) return true;
328
- InsightsApiClient_logger.warn('beaconHandler failed to queue events; events will be emitted immediately via fetch');
343
+ const serializedBody = JSON.stringify(body);
344
+ if (beacon) {
345
+ InsightsApiClient_logger.debug('Queueing events via beacon');
346
+ try {
347
+ const beaconSuccessfullyQueued = beacon(urlString, serializedBody);
348
+ if (beaconSuccessfullyQueued) return true;
349
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE);
350
+ } catch (error) {
351
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE, error);
352
+ }
329
353
  }
330
354
  const requestName = 'Event Batches';
331
355
  InsightsApiClient_logger.info(`Sending "${requestName}" request`);
332
356
  InsightsApiClient_logger.debug(`"${requestName}" request body:`, body);
333
357
  try {
334
- await this.fetch(url, {
358
+ await this.fetch(urlString, {
335
359
  method: 'POST',
336
360
  headers: {
337
361
  'Content-Type': 'application/json'
338
362
  },
339
- body: JSON.stringify(body),
340
- keepalive: true
363
+ body: serializedBody,
364
+ keepalive: !!beacon
341
365
  });
342
366
  InsightsApiClient_logger.debug(`"${requestName}" request successfully completed`);
343
367
  return true;
@@ -349,202 +373,28 @@ class InsightsApiClient extends src_ApiClientBase {
349
373
  }
350
374
  }
351
375
  }
352
- const insights = InsightsApiClient;
353
376
  class ApiClient {
354
377
  config;
355
378
  experience;
356
379
  insights;
357
380
  constructor(config){
358
- const { personalization, analytics, clientId, environment, fetchOptions } = config;
381
+ const { experience, insights, clientId, environment, fetchOptions } = config;
359
382
  const apiConfig = {
360
383
  clientId,
361
384
  environment,
362
385
  fetchOptions
363
386
  };
364
387
  this.config = apiConfig;
365
- this.experience = new experience({
388
+ this.experience = new ExperienceApiClient({
366
389
  ...apiConfig,
367
- ...personalization
390
+ ...experience
368
391
  });
369
- this.insights = new insights({
392
+ this.insights = new InsightsApiClient({
370
393
  ...apiConfig,
371
- ...analytics
394
+ ...insights
372
395
  });
373
396
  }
374
397
  }
375
- const UniversalEventBuilderArgs = object({
376
- campaign: optional(__rspack_external__contentful_optimization_api_schemas_66dcf407.Campaign),
377
- locale: optional(string()),
378
- location: optional(__rspack_external__contentful_optimization_api_schemas_66dcf407.GeoLocation),
379
- page: optional(__rspack_external__contentful_optimization_api_schemas_66dcf407.Page),
380
- screen: optional(__rspack_external__contentful_optimization_api_schemas_66dcf407.Screen),
381
- userAgent: optional(string())
382
- });
383
- const ComponentInteractionBuilderArgsBase = extend(UniversalEventBuilderArgs, {
384
- componentId: string(),
385
- experienceId: optional(string()),
386
- variantIndex: optional(number())
387
- });
388
- const ComponentViewBuilderArgs = extend(ComponentInteractionBuilderArgsBase, {
389
- sticky: optional(mini_boolean()),
390
- componentViewId: string(),
391
- viewDurationMs: number()
392
- });
393
- const ComponentClickBuilderArgs = ComponentInteractionBuilderArgsBase;
394
- const ComponentHoverBuilderArgs = extend(ComponentInteractionBuilderArgsBase, {
395
- componentHoverId: string(),
396
- hoverDurationMs: number()
397
- });
398
- const IdentifyBuilderArgs = extend(UniversalEventBuilderArgs, {
399
- traits: optional(__rspack_external__contentful_optimization_api_schemas_66dcf407.Traits),
400
- userId: string()
401
- });
402
- const PageViewBuilderArgs = extend(UniversalEventBuilderArgs, {
403
- properties: optional(partial(__rspack_external__contentful_optimization_api_schemas_66dcf407.Page))
404
- });
405
- const ScreenViewBuilderArgs = extend(UniversalEventBuilderArgs, {
406
- name: string(),
407
- properties: __rspack_external__contentful_optimization_api_schemas_66dcf407.Properties
408
- });
409
- const TrackBuilderArgs = extend(UniversalEventBuilderArgs, {
410
- event: string(),
411
- properties: optional(prefault(__rspack_external__contentful_optimization_api_schemas_66dcf407.Properties, {}))
412
- });
413
- const DEFAULT_PAGE_PROPERTIES = {
414
- path: '',
415
- query: {},
416
- referrer: '',
417
- search: '',
418
- title: '',
419
- url: ''
420
- };
421
- class EventBuilder {
422
- app;
423
- channel;
424
- library;
425
- getLocale;
426
- getPageProperties;
427
- getUserAgent;
428
- constructor(config){
429
- const { app, channel, library, getLocale, getPageProperties, getUserAgent } = config;
430
- this.app = app;
431
- this.channel = channel;
432
- this.library = library;
433
- this.getLocale = getLocale ?? (()=>'en-US');
434
- this.getPageProperties = getPageProperties ?? (()=>DEFAULT_PAGE_PROPERTIES);
435
- this.getUserAgent = getUserAgent ?? (()=>void 0);
436
- }
437
- buildUniversalEventProperties({ campaign = {}, locale, location, page, screen, userAgent }) {
438
- const timestamp = new Date().toISOString();
439
- return {
440
- channel: this.channel,
441
- context: {
442
- app: this.app,
443
- campaign,
444
- gdpr: {
445
- isConsentGiven: true
446
- },
447
- library: this.library,
448
- locale: locale ?? this.getLocale() ?? 'en-US',
449
- location,
450
- page: page ?? this.getPageProperties(),
451
- screen,
452
- userAgent: userAgent ?? this.getUserAgent()
453
- },
454
- messageId: crypto.randomUUID(),
455
- originalTimestamp: timestamp,
456
- sentAt: timestamp,
457
- timestamp
458
- };
459
- }
460
- buildEntryComponentBase(universal, componentId, experienceId, variantIndex) {
461
- return {
462
- ...this.buildUniversalEventProperties(universal),
463
- componentType: 'Entry',
464
- componentId,
465
- experienceId,
466
- variantIndex: variantIndex ?? 0
467
- };
468
- }
469
- buildComponentView(args) {
470
- const { componentId, componentViewId, experienceId, variantIndex, viewDurationMs, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(ComponentViewBuilderArgs, args);
471
- return {
472
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
473
- type: 'component',
474
- componentViewId,
475
- viewDurationMs
476
- };
477
- }
478
- buildComponentClick(args) {
479
- const { componentId, experienceId, variantIndex, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(ComponentClickBuilderArgs, args);
480
- return {
481
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
482
- type: 'component_click'
483
- };
484
- }
485
- buildComponentHover(args) {
486
- const { componentHoverId, componentId, experienceId, hoverDurationMs, variantIndex, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(ComponentHoverBuilderArgs, args);
487
- return {
488
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
489
- type: 'component_hover',
490
- componentHoverId,
491
- hoverDurationMs
492
- };
493
- }
494
- buildFlagView(args) {
495
- return {
496
- ...this.buildComponentView(args),
497
- componentType: 'Variable'
498
- };
499
- }
500
- buildIdentify(args) {
501
- const { traits = {}, userId, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(IdentifyBuilderArgs, args);
502
- return {
503
- ...this.buildUniversalEventProperties(universal),
504
- type: 'identify',
505
- traits,
506
- userId
507
- };
508
- }
509
- buildPageView(args = {}) {
510
- const { properties = {}, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(PageViewBuilderArgs, args);
511
- const pageProperties = this.getPageProperties();
512
- const merged = merge({
513
- ...pageProperties,
514
- title: pageProperties.title ?? DEFAULT_PAGE_PROPERTIES.title
515
- }, properties);
516
- const { context: { screen: _, ...universalContext }, ...universalProperties } = this.buildUniversalEventProperties(universal);
517
- const context = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.PageEventContext, universalContext);
518
- return {
519
- ...universalProperties,
520
- context,
521
- type: 'page',
522
- properties: merged
523
- };
524
- }
525
- buildScreenView(args) {
526
- const { name, properties, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(ScreenViewBuilderArgs, args);
527
- const { context: { page: _, ...universalContext }, ...universalProperties } = this.buildUniversalEventProperties(universal);
528
- const context = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(__rspack_external__contentful_optimization_api_schemas_66dcf407.ScreenEventContext, universalContext);
529
- return {
530
- ...universalProperties,
531
- context,
532
- type: 'screen',
533
- name,
534
- properties
535
- };
536
- }
537
- buildTrack(args) {
538
- const { event, properties = {}, ...universal } = (0, __rspack_external__contentful_optimization_api_schemas_66dcf407.parseWithFriendlyError)(TrackBuilderArgs, args);
539
- return {
540
- ...this.buildUniversalEventProperties(universal),
541
- type: 'track',
542
- event,
543
- properties
544
- };
545
- }
546
- }
547
- const builders_EventBuilder = EventBuilder;
548
- export { ApiClient, DEFAULT_PAGE_PROPERTIES, EXPERIENCE_BASE_URL, INSIGHTS_BASE_URL, builders_EventBuilder as EventBuilder };
398
+ export { ApiClient, EXPERIENCE_BASE_URL, ExperienceApiClient, INSIGHTS_BASE_URL, InsightsApiClient, createProtectedFetchMethod, createRetryFetchMethod, createTimeoutFetchMethod };
549
399
 
550
400
  //# sourceMappingURL=index.mjs.map