@contentful/optimization-api-client 0.1.0-alpha9 → 1.0.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.
package/dist/index.cjs CHANGED
@@ -148,14 +148,27 @@ var __webpack_exports__ = {};
148
148
  (()=>{
149
149
  __webpack_require__.r(__webpack_exports__);
150
150
  __webpack_require__.d(__webpack_exports__, {
151
+ createTimeoutFetchMethod: ()=>createTimeoutFetchMethod,
152
+ ExperienceApiClient: ()=>ExperienceApiClient,
153
+ createProtectedFetchMethod: ()=>createProtectedFetchMethod,
151
154
  EXPERIENCE_BASE_URL: ()=>EXPERIENCE_BASE_URL,
152
155
  ApiClient: ()=>ApiClient,
153
156
  INSIGHTS_BASE_URL: ()=>INSIGHTS_BASE_URL,
154
- DEFAULT_PAGE_PROPERTIES: ()=>DEFAULT_PAGE_PROPERTIES,
155
- EventBuilder: ()=>builders_EventBuilder
157
+ createRetryFetchMethod: ()=>createRetryFetchMethod,
158
+ InsightsApiClient: ()=>InsightsApiClient
156
159
  });
157
160
  const optimization_api_schemas_namespaceObject = require("@contentful/optimization-api-schemas");
158
161
  var logger = __webpack_require__("./src/logger.ts");
162
+ const fetchInputToString = (input)=>{
163
+ if ('string' == typeof input) return input;
164
+ return input.url;
165
+ };
166
+ const MISSING_FETCH_METHOD_MESSAGE = 'No fetch implementation available. Provide a fetchMethod.';
167
+ function resolveFetchMethod(fetchMethod) {
168
+ if (fetchMethod) return fetchMethod;
169
+ if ('function' == typeof globalThis.fetch) return globalThis.fetch.bind(globalThis);
170
+ throw new Error(MISSING_FETCH_METHOD_MESSAGE);
171
+ }
159
172
  const createRetryFetchMethod_logger = (0, logger.createScopedLogger)('ApiClient:Retry');
160
173
  const DEFAULT_INTERVAL_TIMEOUT = 0;
161
174
  const DEFAULT_RETRY_COUNT = 1;
@@ -169,22 +182,23 @@ var __webpack_exports__ = {};
169
182
  this.status = status;
170
183
  }
171
184
  }
172
- function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod = fetch, init, url }) {
185
+ function createRetryFetchCallback({ apiName = 'Optimization', controller, fetchMethod, init, url }) {
173
186
  return async ()=>{
187
+ const urlLabel = fetchInputToString(url);
174
188
  try {
175
189
  const response = await fetchMethod(url, init);
176
- 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);
190
+ if (response.status === RETRY_RESPONSE_STATUS) throw new HttpError(`${apiName} API request to "${urlLabel}" failed with status: "[${response.status}] ${response.statusText}".`, RETRY_RESPONSE_STATUS);
177
191
  if (!response.ok) {
178
- const httpError = new Error(`Request to "${url.toString()}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
192
+ const httpError = new Error(`Request to "${urlLabel}" failed with status: [${response.status}] ${response.statusText} - traceparent: ${response.headers.get('traceparent')}`);
179
193
  createRetryFetchMethod_logger.error('Request failed with non-OK status:', httpError);
180
194
  controller.abort();
181
195
  return;
182
196
  }
183
- createRetryFetchMethod_logger.debug(`Response from "${url.toString()}":`, response);
197
+ createRetryFetchMethod_logger.debug(`Response from "${urlLabel}":`, response);
184
198
  return response;
185
199
  } catch (error) {
186
200
  if (error instanceof HttpError && error.status === RETRY_RESPONSE_STATUS) throw error;
187
- createRetryFetchMethod_logger.error(`Request to "${url.toString()}" failed:`, error);
201
+ createRetryFetchMethod_logger.error(`Request to "${urlLabel}" failed:`, error);
188
202
  controller.abort();
189
203
  }
190
204
  };
@@ -197,14 +211,15 @@ var __webpack_exports__ = {};
197
211
  }, intervalTimeout);
198
212
  await promise;
199
213
  }
200
- function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
214
+ function createRetryFetchMethod({ apiName = 'Optimization', fetchMethod, intervalTimeout = DEFAULT_INTERVAL_TIMEOUT, onFailedAttempt, retries = DEFAULT_RETRY_COUNT } = {}) {
215
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
201
216
  return async (url, init)=>{
202
217
  const controller = new AbortController();
203
218
  const maxAttempts = retries + 1;
204
219
  const attemptFetch = createRetryFetchCallback({
205
220
  apiName,
206
221
  controller,
207
- fetchMethod,
222
+ fetchMethod: resolvedFetchMethod,
208
223
  init,
209
224
  url
210
225
  });
@@ -224,25 +239,32 @@ var __webpack_exports__ = {};
224
239
  if (0 === retriesLeft) throw error;
225
240
  await delayRetry(intervalTimeout);
226
241
  }
227
- throw new Error(`${apiName} API request to "${url.toString()}" may not be retried.`);
242
+ throw new Error(`${apiName} API request to "${fetchInputToString(url)}" may not be retried.`);
228
243
  };
229
244
  }
230
245
  const createTimeoutFetchMethod_logger = (0, logger.createScopedLogger)('ApiClient:Timeout');
231
246
  const DEFAULT_REQUEST_TIMEOUT = 3000;
232
- function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod = fetch, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
247
+ function isFetchAbortSignal(signal) {
248
+ return 'object' == typeof signal && null !== signal;
249
+ }
250
+ function createTimeoutFetchMethod({ apiName = 'Optimization', fetchMethod, onRequestTimeout, requestTimeout = DEFAULT_REQUEST_TIMEOUT } = {}) {
251
+ const resolvedFetchMethod = resolveFetchMethod(fetchMethod);
233
252
  return async (url, init)=>{
234
253
  const controller = new AbortController();
235
254
  const id = setTimeout(()=>{
236
255
  if ('function' == typeof onRequestTimeout) onRequestTimeout({
237
256
  apiName
238
257
  });
239
- else createTimeoutFetchMethod_logger.error(`Request to "${url.toString()}" timed out`, new Error('Request timeout'));
258
+ else createTimeoutFetchMethod_logger.error(`Request to "${fetchInputToString(url)}" timed out`, new Error('Request timeout'));
240
259
  controller.abort();
241
260
  }, requestTimeout);
242
- const response = await fetchMethod(url, {
261
+ const { signal } = controller;
262
+ if (!isFetchAbortSignal(signal)) throw new Error('AbortController signal is not compatible with fetch.');
263
+ const requestInit = {
243
264
  ...init,
244
- signal: controller.signal
245
- });
265
+ signal
266
+ };
267
+ const response = await resolvedFetchMethod(url, requestInit);
246
268
  clearTimeout(id);
247
269
  return response;
248
270
  };
@@ -266,7 +288,7 @@ var __webpack_exports__ = {};
266
288
  create: createProtectedFetchMethod
267
289
  };
268
290
  const fetch_Fetch = Fetch;
269
- const src_fetch = fetch_Fetch;
291
+ const fetch = fetch_Fetch;
270
292
  const ApiClientBase_logger = (0, logger.createScopedLogger)('ApiClient');
271
293
  const DEFAULT_ENVIRONMENT = 'main';
272
294
  class ApiClientBase {
@@ -278,7 +300,7 @@ var __webpack_exports__ = {};
278
300
  this.clientId = clientId;
279
301
  this.environment = environment ?? DEFAULT_ENVIRONMENT;
280
302
  this.name = name;
281
- this.fetch = src_fetch.create({
303
+ this.fetch = fetch.create({
282
304
  ...fetchOptions ?? {},
283
305
  apiName: name
284
306
  });
@@ -308,6 +330,9 @@ var __webpack_exports__ = {};
308
330
  this.plainText = plainText;
309
331
  this.preflight = preflight;
310
332
  }
333
+ setLocale(locale) {
334
+ this.locale = locale;
335
+ }
311
336
  async getProfile(id, options = {}) {
312
337
  if (!id) throw new Error('Valid profile ID required.');
313
338
  const requestName = 'Get Profile';
@@ -319,7 +344,7 @@ var __webpack_exports__ = {};
319
344
  const { data: { changes, experiences, profile } } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.ExperienceResponse, await response.json());
320
345
  const data = {
321
346
  changes,
322
- personalizations: experiences,
347
+ selectedOptimizations: experiences,
323
348
  profile
324
349
  };
325
350
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -353,7 +378,7 @@ var __webpack_exports__ = {};
353
378
  const { data: { changes, experiences, profile } } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.ExperienceResponse, await response.json());
354
379
  const data = {
355
380
  changes,
356
- personalizations: experiences,
381
+ selectedOptimizations: experiences,
357
382
  profile
358
383
  };
359
384
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -380,7 +405,7 @@ var __webpack_exports__ = {};
380
405
  const { data: { changes, experiences, profile } } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.ExperienceResponse, await response.json());
381
406
  const data = {
382
407
  changes,
383
- personalizations: experiences,
408
+ selectedOptimizations: experiences,
384
409
  profile
385
410
  };
386
411
  ExperienceApiClient_logger.debug(`"${requestName}" request successfully completed`);
@@ -459,39 +484,43 @@ var __webpack_exports__ = {};
459
484
  });
460
485
  }
461
486
  }
462
- const experience = ExperienceApiClient;
463
487
  const InsightsApiClient_logger = (0, logger.createScopedLogger)('ApiClient:Insights');
488
+ const BEACON_FALLBACK_MESSAGE = 'beacon failed; falling back to fetch';
464
489
  const INSIGHTS_BASE_URL = 'https://ingest.insights.ninetailed.co/';
465
490
  class InsightsApiClient extends src_ApiClientBase {
466
491
  baseUrl;
467
- beaconHandler;
468
492
  constructor(config){
469
493
  super('Insights', config);
470
- const { baseUrl, beaconHandler } = config;
494
+ const { baseUrl } = config;
471
495
  this.baseUrl = baseUrl || INSIGHTS_BASE_URL;
472
- this.beaconHandler = beaconHandler;
473
496
  }
474
497
  async sendBatchEvents(batches, options = {}) {
475
- const { beaconHandler = this.beaconHandler } = options;
498
+ const { beacon } = options;
476
499
  const url = new URL(`v1/organizations/${this.clientId}/environments/${this.environment}/events`, this.baseUrl);
500
+ const urlString = url.toString();
477
501
  const body = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.BatchInsightsEventArray, batches);
478
- if ('function' == typeof beaconHandler) {
479
- InsightsApiClient_logger.debug('Queueing events via beaconHandler');
480
- const beaconSuccessfullyQueued = beaconHandler(url, body);
481
- if (beaconSuccessfullyQueued) return true;
482
- InsightsApiClient_logger.warn('beaconHandler failed to queue events; events will be emitted immediately via fetch');
502
+ const serializedBody = JSON.stringify(body);
503
+ if (beacon) {
504
+ InsightsApiClient_logger.debug('Queueing events via beacon');
505
+ try {
506
+ const beaconSuccessfullyQueued = beacon(urlString, serializedBody);
507
+ if (beaconSuccessfullyQueued) return true;
508
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE);
509
+ } catch (error) {
510
+ InsightsApiClient_logger.warn(BEACON_FALLBACK_MESSAGE, error);
511
+ }
483
512
  }
484
513
  const requestName = 'Event Batches';
485
514
  InsightsApiClient_logger.info(`Sending "${requestName}" request`);
486
515
  InsightsApiClient_logger.debug(`"${requestName}" request body:`, body);
487
516
  try {
488
- await this.fetch(url, {
517
+ await this.fetch(urlString, {
489
518
  method: 'POST',
490
519
  headers: {
491
520
  'Content-Type': 'application/json'
492
521
  },
493
- body: JSON.stringify(body),
494
- keepalive: true
522
+ body: serializedBody,
523
+ keepalive: !!beacon
495
524
  });
496
525
  InsightsApiClient_logger.debug(`"${requestName}" request successfully completed`);
497
526
  return true;
@@ -503,216 +532,46 @@ var __webpack_exports__ = {};
503
532
  }
504
533
  }
505
534
  }
506
- const insights = InsightsApiClient;
507
535
  class ApiClient {
508
536
  config;
509
537
  experience;
510
538
  insights;
511
539
  constructor(config){
512
- const { personalization, analytics, clientId, environment, fetchOptions } = config;
540
+ const { experience, insights, clientId, environment, fetchOptions } = config;
513
541
  const apiConfig = {
514
542
  clientId,
515
543
  environment,
516
544
  fetchOptions
517
545
  };
518
546
  this.config = apiConfig;
519
- this.experience = new experience({
547
+ this.experience = new ExperienceApiClient({
520
548
  ...apiConfig,
521
- ...personalization
549
+ ...experience
522
550
  });
523
- this.insights = new insights({
551
+ this.insights = new InsightsApiClient({
524
552
  ...apiConfig,
525
- ...analytics
553
+ ...insights
526
554
  });
527
555
  }
528
556
  }
529
- const object_namespaceObject = require("es-toolkit/object");
530
- const mini_namespaceObject = require("zod/mini");
531
- const UniversalEventBuilderArgs = mini_namespaceObject.object({
532
- campaign: mini_namespaceObject.optional(optimization_api_schemas_namespaceObject.Campaign),
533
- locale: mini_namespaceObject.optional(mini_namespaceObject.string()),
534
- location: mini_namespaceObject.optional(optimization_api_schemas_namespaceObject.GeoLocation),
535
- page: mini_namespaceObject.optional(optimization_api_schemas_namespaceObject.Page),
536
- screen: mini_namespaceObject.optional(optimization_api_schemas_namespaceObject.Screen),
537
- userAgent: mini_namespaceObject.optional(mini_namespaceObject.string())
538
- });
539
- const ComponentInteractionBuilderArgsBase = mini_namespaceObject.extend(UniversalEventBuilderArgs, {
540
- componentId: mini_namespaceObject.string(),
541
- experienceId: mini_namespaceObject.optional(mini_namespaceObject.string()),
542
- variantIndex: mini_namespaceObject.optional(mini_namespaceObject.number())
543
- });
544
- const ComponentViewBuilderArgs = mini_namespaceObject.extend(ComponentInteractionBuilderArgsBase, {
545
- sticky: mini_namespaceObject.optional(mini_namespaceObject.boolean()),
546
- componentViewId: mini_namespaceObject.string(),
547
- viewDurationMs: mini_namespaceObject.number()
548
- });
549
- const ComponentClickBuilderArgs = ComponentInteractionBuilderArgsBase;
550
- const ComponentHoverBuilderArgs = mini_namespaceObject.extend(ComponentInteractionBuilderArgsBase, {
551
- componentHoverId: mini_namespaceObject.string(),
552
- hoverDurationMs: mini_namespaceObject.number()
553
- });
554
- const IdentifyBuilderArgs = mini_namespaceObject.extend(UniversalEventBuilderArgs, {
555
- traits: mini_namespaceObject.optional(optimization_api_schemas_namespaceObject.Traits),
556
- userId: mini_namespaceObject.string()
557
- });
558
- const PageViewBuilderArgs = mini_namespaceObject.extend(UniversalEventBuilderArgs, {
559
- properties: mini_namespaceObject.optional(mini_namespaceObject.partial(optimization_api_schemas_namespaceObject.Page))
560
- });
561
- const ScreenViewBuilderArgs = mini_namespaceObject.extend(UniversalEventBuilderArgs, {
562
- name: mini_namespaceObject.string(),
563
- properties: optimization_api_schemas_namespaceObject.Properties
564
- });
565
- const TrackBuilderArgs = mini_namespaceObject.extend(UniversalEventBuilderArgs, {
566
- event: mini_namespaceObject.string(),
567
- properties: mini_namespaceObject.optional(mini_namespaceObject.prefault(optimization_api_schemas_namespaceObject.Properties, {}))
568
- });
569
- const DEFAULT_PAGE_PROPERTIES = {
570
- path: '',
571
- query: {},
572
- referrer: '',
573
- search: '',
574
- title: '',
575
- url: ''
576
- };
577
- class EventBuilder {
578
- app;
579
- channel;
580
- library;
581
- getLocale;
582
- getPageProperties;
583
- getUserAgent;
584
- constructor(config){
585
- const { app, channel, library, getLocale, getPageProperties, getUserAgent } = config;
586
- this.app = app;
587
- this.channel = channel;
588
- this.library = library;
589
- this.getLocale = getLocale ?? (()=>'en-US');
590
- this.getPageProperties = getPageProperties ?? (()=>DEFAULT_PAGE_PROPERTIES);
591
- this.getUserAgent = getUserAgent ?? (()=>void 0);
592
- }
593
- buildUniversalEventProperties({ campaign = {}, locale, location, page, screen, userAgent }) {
594
- const timestamp = new Date().toISOString();
595
- return {
596
- channel: this.channel,
597
- context: {
598
- app: this.app,
599
- campaign,
600
- gdpr: {
601
- isConsentGiven: true
602
- },
603
- library: this.library,
604
- locale: locale ?? this.getLocale() ?? 'en-US',
605
- location,
606
- page: page ?? this.getPageProperties(),
607
- screen,
608
- userAgent: userAgent ?? this.getUserAgent()
609
- },
610
- messageId: crypto.randomUUID(),
611
- originalTimestamp: timestamp,
612
- sentAt: timestamp,
613
- timestamp
614
- };
615
- }
616
- buildEntryComponentBase(universal, componentId, experienceId, variantIndex) {
617
- return {
618
- ...this.buildUniversalEventProperties(universal),
619
- componentType: 'Entry',
620
- componentId,
621
- experienceId,
622
- variantIndex: variantIndex ?? 0
623
- };
624
- }
625
- buildComponentView(args) {
626
- const { componentId, componentViewId, experienceId, variantIndex, viewDurationMs, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(ComponentViewBuilderArgs, args);
627
- return {
628
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
629
- type: 'component',
630
- componentViewId,
631
- viewDurationMs
632
- };
633
- }
634
- buildComponentClick(args) {
635
- const { componentId, experienceId, variantIndex, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(ComponentClickBuilderArgs, args);
636
- return {
637
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
638
- type: 'component_click'
639
- };
640
- }
641
- buildComponentHover(args) {
642
- const { componentHoverId, componentId, experienceId, hoverDurationMs, variantIndex, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(ComponentHoverBuilderArgs, args);
643
- return {
644
- ...this.buildEntryComponentBase(universal, componentId, experienceId, variantIndex),
645
- type: 'component_hover',
646
- componentHoverId,
647
- hoverDurationMs
648
- };
649
- }
650
- buildFlagView(args) {
651
- return {
652
- ...this.buildComponentView(args),
653
- componentType: 'Variable'
654
- };
655
- }
656
- buildIdentify(args) {
657
- const { traits = {}, userId, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(IdentifyBuilderArgs, args);
658
- return {
659
- ...this.buildUniversalEventProperties(universal),
660
- type: 'identify',
661
- traits,
662
- userId
663
- };
664
- }
665
- buildPageView(args = {}) {
666
- const { properties = {}, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(PageViewBuilderArgs, args);
667
- const pageProperties = this.getPageProperties();
668
- const merged = (0, object_namespaceObject.merge)({
669
- ...pageProperties,
670
- title: pageProperties.title ?? DEFAULT_PAGE_PROPERTIES.title
671
- }, properties);
672
- const { context: { screen: _, ...universalContext }, ...universalProperties } = this.buildUniversalEventProperties(universal);
673
- const context = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.PageEventContext, universalContext);
674
- return {
675
- ...universalProperties,
676
- context,
677
- type: 'page',
678
- properties: merged
679
- };
680
- }
681
- buildScreenView(args) {
682
- const { name, properties, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(ScreenViewBuilderArgs, args);
683
- const { context: { page: _, ...universalContext }, ...universalProperties } = this.buildUniversalEventProperties(universal);
684
- const context = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(optimization_api_schemas_namespaceObject.ScreenEventContext, universalContext);
685
- return {
686
- ...universalProperties,
687
- context,
688
- type: 'screen',
689
- name,
690
- properties
691
- };
692
- }
693
- buildTrack(args) {
694
- const { event, properties = {}, ...universal } = (0, optimization_api_schemas_namespaceObject.parseWithFriendlyError)(TrackBuilderArgs, args);
695
- return {
696
- ...this.buildUniversalEventProperties(universal),
697
- type: 'track',
698
- event,
699
- properties
700
- };
701
- }
702
- }
703
- const builders_EventBuilder = EventBuilder;
704
557
  })();
705
558
  exports.ApiClient = __webpack_exports__.ApiClient;
706
- exports.DEFAULT_PAGE_PROPERTIES = __webpack_exports__.DEFAULT_PAGE_PROPERTIES;
707
559
  exports.EXPERIENCE_BASE_URL = __webpack_exports__.EXPERIENCE_BASE_URL;
708
- exports.EventBuilder = __webpack_exports__.EventBuilder;
560
+ exports.ExperienceApiClient = __webpack_exports__.ExperienceApiClient;
709
561
  exports.INSIGHTS_BASE_URL = __webpack_exports__.INSIGHTS_BASE_URL;
562
+ exports.InsightsApiClient = __webpack_exports__.InsightsApiClient;
563
+ exports.createProtectedFetchMethod = __webpack_exports__.createProtectedFetchMethod;
564
+ exports.createRetryFetchMethod = __webpack_exports__.createRetryFetchMethod;
565
+ exports.createTimeoutFetchMethod = __webpack_exports__.createTimeoutFetchMethod;
710
566
  for(var __rspack_i in __webpack_exports__)if (-1 === [
711
567
  "ApiClient",
712
- "DEFAULT_PAGE_PROPERTIES",
713
568
  "EXPERIENCE_BASE_URL",
714
- "EventBuilder",
715
- "INSIGHTS_BASE_URL"
569
+ "ExperienceApiClient",
570
+ "INSIGHTS_BASE_URL",
571
+ "InsightsApiClient",
572
+ "createProtectedFetchMethod",
573
+ "createRetryFetchMethod",
574
+ "createTimeoutFetchMethod"
716
575
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
717
576
  Object.defineProperty(exports, '__esModule', {
718
577
  value: true