@pack/hydrogen 3.1.1-beta.0 → 3.2.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.js CHANGED
@@ -4356,7 +4356,10 @@ var require_dist = __commonJS({
4356
4356
  }
4357
4357
  });
4358
4358
 
4359
- // node_modules/@pack/packlytics/dist/utils/get-packlytics-id.js
4359
+ // src/handle-request.ts
4360
+ import { sendServerErrorEvent } from "@pack/errors";
4361
+
4362
+ // ../packlytics/dist/utils/get-packlytics-id.js
4360
4363
  function sha256(ascii) {
4361
4364
  function rightRotate(value, amount) {
4362
4365
  return value >>> amount | value << 32 - amount;
@@ -4471,7 +4474,7 @@ var parseAcceptLanguage = function parseAcceptLanguage2(languageHeaderValue, opt
4471
4474
  });
4472
4475
  };
4473
4476
 
4474
- // node_modules/@pack/packlytics/dist/utils/get-headers.js
4477
+ // ../packlytics/dist/utils/get-headers.js
4475
4478
  function getHeaders(requestOrHeaders) {
4476
4479
  if (requestOrHeaders instanceof Request) {
4477
4480
  return requestOrHeaders.headers;
@@ -4479,7 +4482,7 @@ function getHeaders(requestOrHeaders) {
4479
4482
  return requestOrHeaders;
4480
4483
  }
4481
4484
 
4482
- // node_modules/@pack/packlytics/dist/utils/get-client-locales.js
4485
+ // ../packlytics/dist/utils/get-client-locales.js
4483
4486
  function getClientLocales(requestOrHeaders) {
4484
4487
  let headers = getHeaders(requestOrHeaders);
4485
4488
  let acceptLanguage = headers.get("Accept-Language");
@@ -4494,7 +4497,7 @@ function getClientLocales(requestOrHeaders) {
4494
4497
  return locales;
4495
4498
  }
4496
4499
 
4497
- // node_modules/@pack/packlytics/dist/utils/sanatize-payload.js
4500
+ // ../packlytics/dist/utils/sanatize-payload.js
4498
4501
  var sanitizePayload = (data) => {
4499
4502
  let dataStr = JSON.stringify(data);
4500
4503
  const sensitiveFields = [
@@ -4525,7 +4528,7 @@ var sanitizePayload = (data) => {
4525
4528
  return dataStr;
4526
4529
  };
4527
4530
 
4528
- // node_modules/@pack/packlytics/dist/utils/send-event.js
4531
+ // ../packlytics/dist/utils/send-event.js
4529
4532
  var metadata = null;
4530
4533
  var getPacklyticsMetadata = async () => {
4531
4534
  if (metadata)
@@ -4583,14 +4586,14 @@ var sendEvent = (storefrontId, sessionId) => {
4583
4586
  };
4584
4587
  };
4585
4588
 
4586
- // node_modules/@pack/packlytics/dist/utils/page-hit.js
4589
+ // ../packlytics/dist/utils/page-hit.js
4587
4590
  var trackPageHit = (storefrontId, sessionId) => {
4588
4591
  return (eventPayload = {}) => {
4589
4592
  return sendEvent(storefrontId, sessionId)("page_hit", eventPayload);
4590
4593
  };
4591
4594
  };
4592
4595
 
4593
- // node_modules/@pack/packlytics/dist/utils/get-client-device.js
4596
+ // ../packlytics/dist/utils/get-client-device.js
4594
4597
  var import_ua_parser_js = __toESM(require_ua_parser());
4595
4598
  function getDevice(userAgent) {
4596
4599
  const userAgentData = (0, import_ua_parser_js.default)(userAgent);
@@ -4608,16 +4611,16 @@ function getDevice(userAgent) {
4608
4611
  };
4609
4612
  }
4610
4613
 
4611
- // node_modules/@pack/packlytics/dist/packlytics.js
4614
+ // ../packlytics/dist/packlytics.js
4612
4615
  import { getStorefrontHeaders } from "@shopify/hydrogen/oxygen";
4613
4616
 
4614
- // node_modules/@pack/packlytics/dist/utils/get-client-location.js
4617
+ // ../packlytics/dist/utils/get-client-location.js
4615
4618
  function getClientLocation(locale) {
4616
4619
  const localeSplit = locale.split("-");
4617
4620
  return localeSplit.length > 1 ? localeSplit[1].toLowerCase() : "";
4618
4621
  }
4619
4622
 
4620
- // node_modules/@pack/packlytics/dist/packlytics.js
4623
+ // ../packlytics/dist/packlytics.js
4621
4624
  async function sendEvent2(request, session, params) {
4622
4625
  if (process.env.NODE_ENV === "development") {
4623
4626
  return;
@@ -4668,11 +4671,28 @@ async function packlytics(pack, request, next) {
4668
4671
  }
4669
4672
 
4670
4673
  // src/handle-request.ts
4674
+ function getServerStatusError(request, status) {
4675
+ let pathname = request.url;
4676
+ try {
4677
+ pathname = new URL(request.url).pathname;
4678
+ } catch {
4679
+ }
4680
+ return new Error(`HTTP ${status} response for ${request.method} ${pathname}`);
4681
+ }
4671
4682
  async function handleRequest(pack, request, handleRequest2) {
4672
4683
  const packHandleResponse = await pack.handleRequest(request);
4673
- const response = await packlytics(pack, request, () => {
4674
- return handleRequest2(request);
4675
- });
4684
+ let response;
4685
+ try {
4686
+ response = await packlytics(pack, request, () => {
4687
+ return handleRequest2(request);
4688
+ });
4689
+ } catch (error) {
4690
+ sendServerErrorEvent(error, request, pack);
4691
+ throw error;
4692
+ }
4693
+ if (response.status >= 500 && !request.signal.aborted) {
4694
+ sendServerErrorEvent(getServerStatusError(request, response.status), request, pack);
4695
+ }
4676
4696
  packHandleResponse(response);
4677
4697
  response.headers.append("powered-by", "Shopify, Hydrogen + Pack Digital");
4678
4698
  response.headers.append("Set-Cookie", await pack.session.commit());
@@ -4691,7 +4711,6 @@ var PACK_COOKIE_ID = "__pack";
4691
4711
  var PACK_USER_CONSENT_COOKIE_ID = "__pack_user_consent";
4692
4712
  var PACK_TEST_COOKIE_ID = "__pack_test";
4693
4713
  var PACK_COOKIE_MAX_AGE = 60 * 60 * 24 * 360;
4694
- var PACK_TEST_CACHE_CHECK_URL = "https://test-cache-check-production.packdigital.workers.dev/tests-updated-at";
4695
4714
 
4696
4715
  // src/session/usePackCookies.ts
4697
4716
  function usePackCookies(options) {
@@ -5028,6 +5047,10 @@ var cookie2 = __toESM(require_cookie(), 1);
5028
5047
  var import_json_rules_engine = __toESM(require_dist(), 1);
5029
5048
  var import_debug2 = __toESM(require_browser(), 1);
5030
5049
  var debug2 = (0, import_debug2.default)("pack:ab-testing:local-resolver");
5050
+ function getImpressionSectionIds(testVariant) {
5051
+ const sectionIds = testVariant?.sectionTestVariants?.map((variant) => variant?.section?.id).filter((id) => Boolean(id)) || [];
5052
+ return [...new Set(sectionIds)];
5053
+ }
5031
5054
  var LocalTestResolver = class {
5032
5055
  testRules = [];
5033
5056
  constructor() {
@@ -5066,62 +5089,86 @@ var LocalTestResolver = class {
5066
5089
  * Evaluate test rules and assign a variant
5067
5090
  */
5068
5091
  async assignTest(attributes, _sessionId) {
5069
- debug2("[Pack Test LocalResolver] Starting test assignment:", JSON.stringify({
5070
- totalRules: this.testRules.length,
5071
- attributes
5072
- }));
5092
+ debug2(
5093
+ "[Pack Test LocalResolver] Starting test assignment:",
5094
+ JSON.stringify({
5095
+ totalRules: this.testRules.length,
5096
+ attributes
5097
+ })
5098
+ );
5073
5099
  if (!this.testRules.length) {
5074
5100
  debug2("[Pack Test LocalResolver] No test rules available");
5075
5101
  return null;
5076
5102
  }
5077
5103
  const eligibleTests = await this.evaluateRules(this.testRules, attributes);
5078
- debug2("[Pack Test LocalResolver] Rule evaluation result:", JSON.stringify({
5079
- totalTests: this.testRules.length,
5080
- eligibleTests: eligibleTests.length,
5081
- eligibleTestIds: eligibleTests.map((t) => ({ id: t.id, handle: t.handle }))
5082
- }));
5104
+ debug2(
5105
+ "[Pack Test LocalResolver] Rule evaluation result:",
5106
+ JSON.stringify({
5107
+ totalTests: this.testRules.length,
5108
+ eligibleTests: eligibleTests.length,
5109
+ eligibleTestIds: eligibleTests.map((t) => ({
5110
+ id: t.id,
5111
+ handle: t.handle
5112
+ }))
5113
+ })
5114
+ );
5083
5115
  if (!eligibleTests.length) {
5084
5116
  debug2("[Pack Test LocalResolver] No eligible tests found");
5085
5117
  return null;
5086
5118
  }
5087
5119
  const randomTestIndex = this.getRandomNumber(eligibleTests.length - 1);
5088
5120
  const selectedTest = eligibleTests[randomTestIndex];
5089
- debug2("[Pack Test LocalResolver] Selected test:", JSON.stringify({
5090
- testId: selectedTest.id,
5091
- testHandle: selectedTest.handle,
5092
- randomIndex: randomTestIndex,
5093
- totalEligible: eligibleTests.length
5094
- }));
5121
+ debug2(
5122
+ "[Pack Test LocalResolver] Selected test:",
5123
+ JSON.stringify({
5124
+ testId: selectedTest.id,
5125
+ testHandle: selectedTest.handle,
5126
+ randomIndex: randomTestIndex,
5127
+ totalEligible: eligibleTests.length
5128
+ })
5129
+ );
5095
5130
  const randomPercentage = this.getRandomNumber(100, 1);
5096
5131
  let accumulatedPercentage = 0;
5097
- debug2("[Pack Test LocalResolver] Starting variant assignment:", JSON.stringify({
5098
- randomPercentage,
5099
- variants: selectedTest.testVariants.map((v) => ({
5100
- id: v.id,
5101
- handle: v.handle,
5102
- trafficPercentage: v.trafficPercentage
5103
- }))
5104
- }));
5132
+ debug2(
5133
+ "[Pack Test LocalResolver] Starting variant assignment:",
5134
+ JSON.stringify({
5135
+ randomPercentage,
5136
+ variants: selectedTest.testVariants.map((v) => ({
5137
+ id: v.id,
5138
+ handle: v.handle,
5139
+ trafficPercentage: v.trafficPercentage
5140
+ }))
5141
+ })
5142
+ );
5105
5143
  for (const variant of selectedTest.testVariants) {
5106
5144
  accumulatedPercentage += variant.trafficPercentage * 100;
5107
- debug2("[Pack Test LocalResolver] Checking variant:", JSON.stringify({
5108
- variantId: variant.id,
5109
- variantHandle: variant.handle,
5110
- trafficPercentage: variant.trafficPercentage,
5111
- accumulatedPercentage,
5112
- randomPercentage,
5113
- isSelected: accumulatedPercentage >= randomPercentage
5114
- }));
5145
+ debug2(
5146
+ "[Pack Test LocalResolver] Checking variant:",
5147
+ JSON.stringify({
5148
+ variantId: variant.id,
5149
+ variantHandle: variant.handle,
5150
+ trafficPercentage: variant.trafficPercentage,
5151
+ accumulatedPercentage,
5152
+ randomPercentage,
5153
+ isSelected: accumulatedPercentage >= randomPercentage
5154
+ })
5155
+ );
5115
5156
  if (accumulatedPercentage >= randomPercentage) {
5157
+ const impressionSectionIds = getImpressionSectionIds(variant);
5116
5158
  const result = {
5117
5159
  id: selectedTest.id,
5118
5160
  handle: selectedTest.handle,
5161
+ impressionTrigger: selectedTest.impressionTrigger,
5119
5162
  testVariant: {
5120
5163
  id: variant.id,
5121
5164
  handle: variant.handle
5122
- }
5165
+ },
5166
+ impression: impressionSectionIds.length > 0 ? { sectionIds: impressionSectionIds } : void 0
5123
5167
  };
5124
- debug2("[Pack Test LocalResolver] Variant selected:", JSON.stringify(result));
5168
+ debug2(
5169
+ "[Pack Test LocalResolver] Variant selected:",
5170
+ JSON.stringify(result)
5171
+ );
5125
5172
  return result;
5126
5173
  }
5127
5174
  }
@@ -5132,26 +5179,34 @@ var LocalTestResolver = class {
5132
5179
  * Evaluate rules using json-rules-engine (same as tests-service)
5133
5180
  */
5134
5181
  async evaluateRules(tests, attributes) {
5135
- debug2("[Pack Test LocalResolver] Starting rule evaluation:", JSON.stringify({
5136
- totalTests: tests.length,
5137
- attributes,
5138
- isClientSide: typeof window !== "undefined"
5139
- }));
5182
+ debug2(
5183
+ "[Pack Test LocalResolver] Starting rule evaluation:",
5184
+ JSON.stringify({
5185
+ totalTests: tests.length,
5186
+ attributes,
5187
+ isClientSide: typeof window !== "undefined"
5188
+ })
5189
+ );
5140
5190
  if (typeof window !== "undefined") {
5141
5191
  debug2("[Pack Test LocalResolver] Client side - returning all tests");
5142
5192
  return tests;
5143
5193
  }
5144
5194
  const engineResultsPromises = tests.map((test, index) => {
5145
5195
  const { rules } = test;
5146
- debug2(`[Pack Test LocalResolver] Processing test ${index}:`, JSON.stringify({
5147
- testId: test.id,
5148
- testHandle: test.handle,
5149
- rulesCount: rules?.length || 0,
5150
- rules
5151
- }));
5196
+ debug2(
5197
+ `[Pack Test LocalResolver] Processing test ${index}:`,
5198
+ JSON.stringify({
5199
+ testId: test.id,
5200
+ testHandle: test.handle,
5201
+ rulesCount: rules?.length || 0,
5202
+ rules
5203
+ })
5204
+ );
5152
5205
  if (rules && Array.isArray(rules)) {
5153
5206
  if (rules.length === 0) {
5154
- debug2(`[Pack Test LocalResolver] Test ${test.handle} has no rules - targeting all`);
5207
+ debug2(
5208
+ `[Pack Test LocalResolver] Test ${test.handle} has no rules - targeting all`
5209
+ );
5155
5210
  return Promise.resolve({
5156
5211
  events: [{ type: "targeted" }]
5157
5212
  });
@@ -5163,7 +5218,10 @@ var LocalTestResolver = class {
5163
5218
  operator: rule.operator,
5164
5219
  value: rule.value
5165
5220
  }));
5166
- debug2(`[Pack Test LocalResolver] Test ${test.handle} engine conditions:`, JSON.stringify(engineConditionalProperties));
5221
+ debug2(
5222
+ `[Pack Test LocalResolver] Test ${test.handle} engine conditions:`,
5223
+ JSON.stringify(engineConditionalProperties)
5224
+ );
5167
5225
  const engineRules = [
5168
5226
  new import_json_rules_engine.Rule({
5169
5227
  conditions: {
@@ -5210,29 +5268,46 @@ var LocalTestResolver = class {
5210
5268
  (factValue) => !factValue
5211
5269
  );
5212
5270
  const result = engine.run(attributes);
5213
- debug2(`[Pack Test LocalResolver] Engine result for test ${test.handle}:`, JSON.stringify(result));
5271
+ debug2(
5272
+ `[Pack Test LocalResolver] Engine result for test ${test.handle}:`,
5273
+ JSON.stringify(result)
5274
+ );
5214
5275
  return result;
5215
5276
  }
5216
5277
  }
5217
- debug2(`[Pack Test LocalResolver] Test ${test.handle} has invalid rules`);
5278
+ debug2(
5279
+ `[Pack Test LocalResolver] Test ${test.handle} has invalid rules`
5280
+ );
5218
5281
  return void 0;
5219
5282
  });
5220
5283
  const engineResults = await Promise.all(engineResultsPromises);
5221
- debug2("[Pack Test LocalResolver] All engine results:", JSON.stringify(engineResults));
5222
- const eligibleTests = tests.filter(
5223
- (test, index) => {
5224
- const hasTargetedEvent = !!engineResults?.[index]?.events.find((event) => event.type === "targeted");
5225
- debug2(`[Pack Test LocalResolver] Test ${test.handle} eligibility:`, JSON.stringify({
5284
+ debug2(
5285
+ "[Pack Test LocalResolver] All engine results:",
5286
+ JSON.stringify(engineResults)
5287
+ );
5288
+ const eligibleTests = tests.filter((test, index) => {
5289
+ const hasTargetedEvent = !!engineResults?.[index]?.events.find(
5290
+ (event) => event.type === "targeted"
5291
+ );
5292
+ debug2(
5293
+ `[Pack Test LocalResolver] Test ${test.handle} eligibility:`,
5294
+ JSON.stringify({
5226
5295
  hasTargetedEvent,
5227
5296
  events: engineResults?.[index]?.events
5228
- }));
5229
- return hasTargetedEvent;
5230
- }
5297
+ })
5298
+ );
5299
+ return hasTargetedEvent;
5300
+ });
5301
+ debug2(
5302
+ "[Pack Test LocalResolver] Final eligible tests:",
5303
+ JSON.stringify({
5304
+ eligibleCount: eligibleTests.length,
5305
+ eligibleTests: eligibleTests.map((t) => ({
5306
+ id: t.id,
5307
+ handle: t.handle
5308
+ }))
5309
+ })
5231
5310
  );
5232
- debug2("[Pack Test LocalResolver] Final eligible tests:", JSON.stringify({
5233
- eligibleCount: eligibleTests.length,
5234
- eligibleTests: eligibleTests.map((t) => ({ id: t.id, handle: t.handle }))
5235
- }));
5236
5311
  return eligibleTests;
5237
5312
  }
5238
5313
  /**
@@ -5275,16 +5350,26 @@ var QUERY_TESTS_BY_RULES = `#graphql
5275
5350
  operator
5276
5351
  value
5277
5352
  }
5353
+ impressionTrigger
5278
5354
  testVariants {
5279
5355
  id
5280
5356
  handle
5281
5357
  trafficPercentage
5358
+ sectionTestVariants {
5359
+ section {
5360
+ id
5361
+ }
5362
+ }
5282
5363
  }
5283
5364
  }
5284
5365
  }
5285
5366
  }
5286
5367
  }
5287
5368
  `;
5369
+ function getImpressionSectionIds2(testVariant) {
5370
+ const sectionIds = testVariant?.sectionTestVariants?.map((variant) => variant?.section?.id).filter((id) => Boolean(id)) || [];
5371
+ return [...new Set(sectionIds)];
5372
+ }
5288
5373
  var localTestResolver = new LocalTestResolver();
5289
5374
  function generateTestRulesCacheKey(storeId, contentEnvironment) {
5290
5375
  return `pack-tests:${storeId}:${contentEnvironment || "default"}`;
@@ -5316,7 +5401,7 @@ async function fetchTestRulesShared(packClient, withCache, token) {
5316
5401
  async ({
5317
5402
  addDebugData
5318
5403
  }) => {
5319
- const URL2 = PACK_TEST_CACHE_CHECK_URL;
5404
+ const URL2 = "https://test-cache-check-production.packdigital.workers.dev/tests-updated-at";
5320
5405
  const resp2 = await fetch(URL2, {
5321
5406
  headers: {
5322
5407
  Authorization: `Bearer ${token}`
@@ -5498,13 +5583,16 @@ async function packClientFetchTestByRules(packClient, testTargetAudienceAttribut
5498
5583
  (v) => v.id === currentTestData.testVariant.id
5499
5584
  );
5500
5585
  if (currentVariant) {
5586
+ const impressionSectionIds = getImpressionSectionIds2(currentVariant);
5501
5587
  const refreshedTest = {
5502
5588
  id: freshTestData.id,
5503
5589
  handle: freshTestData.handle,
5590
+ impressionTrigger: freshTestData.impressionTrigger,
5504
5591
  testVariant: {
5505
5592
  id: currentVariant.id,
5506
5593
  handle: currentVariant.handle
5507
5594
  },
5595
+ impression: impressionSectionIds.length > 0 ? { sectionIds: impressionSectionIds } : void 0,
5508
5596
  isFirstExposure: void 0
5509
5597
  };
5510
5598
  const { isFirstExposure: isFirstExposure2, ...testDataWithoutFlag2 } = refreshedTest;
@@ -5552,13 +5640,16 @@ async function packClientFetchTestByRules(packClient, testTargetAudienceAttribut
5552
5640
  (v) => v.id === exposedTest.testVariant?.id
5553
5641
  );
5554
5642
  if (exposedVariant) {
5643
+ const impressionSectionIds = getImpressionSectionIds2(exposedVariant);
5555
5644
  const result2 = {
5556
5645
  id: freshTestData.id,
5557
5646
  handle: freshTestData.handle,
5647
+ impressionTrigger: freshTestData.impressionTrigger,
5558
5648
  testVariant: {
5559
5649
  id: exposedVariant.id,
5560
5650
  handle: exposedVariant.handle
5561
5651
  },
5652
+ impression: impressionSectionIds.length > 0 ? { sectionIds: impressionSectionIds } : void 0,
5562
5653
  isFirstExposure: void 0
5563
5654
  };
5564
5655
  const { isFirstExposure: isFirstExposure2, ...testDataWithoutFlag2 } = result2;
@@ -6045,45 +6136,8 @@ async function getCacheKey(withCache, query, token, options) {
6045
6136
  debug4(`Error getting cache key: ${err}`);
6046
6137
  console.error(err);
6047
6138
  }
6048
- let testsUpdatedAt;
6049
- const hasTestHeaders = options?.headers && (options.headers["X-Pack-Test-Id"] || options.headers["X-Pack-Test-Handle"] || options.headers["X-Pack-Test-Variant-Id"] || options.headers["X-Pack-Test-Variant-Handle"]);
6050
- if (hasTestHeaders) {
6051
- try {
6052
- const resp = await withCache.run(
6053
- {
6054
- cacheKey: ["pack:tests:updatedAt"],
6055
- cacheStrategy: CacheCustom({
6056
- maxAge: 15,
6057
- staleWhileRevalidate: 15
6058
- }),
6059
- shouldCacheResult: (value) => value !== null
6060
- },
6061
- async ({
6062
- addDebugData
6063
- }) => {
6064
- const URL2 = PACK_TEST_CACHE_CHECK_URL;
6065
- const resp2 = await fetch(URL2, {
6066
- headers: { Authorization: `Bearer ${token}` }
6067
- });
6068
- addDebugData?.({
6069
- displayName: "Pack Test Cache Check (content key)",
6070
- response: resp2
6071
- });
6072
- if (resp2.status !== 200) return null;
6073
- return resp2.json();
6074
- }
6075
- );
6076
- testsUpdatedAt = resp?.testsUpdatedAt;
6077
- } catch (err) {
6078
- debug4(`Error getting testsUpdatedAt for cache key: ${err}`);
6079
- }
6080
- }
6081
- if (publishedAt && testsUpdatedAt) {
6082
- return `${queryHash}:${publishedAt}:${testsUpdatedAt}`;
6083
- } else if (publishedAt) {
6139
+ if (publishedAt) {
6084
6140
  return `${queryHash}:${publishedAt}`;
6085
- } else if (testsUpdatedAt) {
6086
- return `${queryHash}:${testsUpdatedAt}`;
6087
6141
  }
6088
6142
  return queryHash;
6089
6143
  }
@@ -6126,6 +6180,7 @@ function createPackClient(options) {
6126
6180
  token,
6127
6181
  apiUrl,
6128
6182
  defaultThemeData,
6183
+ errorTracking,
6129
6184
  i18n,
6130
6185
  request
6131
6186
  } = options;
@@ -6200,7 +6255,8 @@ function createPackClient(options) {
6200
6255
  previewEnabled
6201
6256
  ),
6202
6257
  packIsPreviewMode: previewEnabled,
6203
- packCustomizerMeta: session.get("customizerMeta")
6258
+ packCustomizerMeta: session.get("customizerMeta"),
6259
+ packErrorTracking: errorTracking
6204
6260
  };
6205
6261
  },
6206
6262
  isPreviewModeEnabled: () => previewEnabled,
@@ -6215,6 +6271,7 @@ function createPackClient(options) {
6215
6271
  return { data, error: null };
6216
6272
  },
6217
6273
  session,
6274
+ errorTracking,
6218
6275
  testSession
6219
6276
  };
6220
6277
  }
@@ -6264,7 +6321,8 @@ function createPackClient(options) {
6264
6321
  previewEnabled
6265
6322
  ),
6266
6323
  packIsPreviewMode: previewEnabled,
6267
- packCustomizerMeta: session.get("customizerMeta")
6324
+ packCustomizerMeta: session.get("customizerMeta"),
6325
+ packErrorTracking: errorTracking
6268
6326
  };
6269
6327
  },
6270
6328
  handleRequest: handleRequest2,
@@ -6329,7 +6387,7 @@ function createPackClient(options) {
6329
6387
  }
6330
6388
  }
6331
6389
  if (testInfoForRequest?.isFirstExposure) {
6332
- const { isFirstExposure, ...testInfo } = testInfoForRequest;
6390
+ const { isFirstExposure: _isFirstExposure, ...testInfo } = testInfoForRequest;
6333
6391
  testInfoForLoader = testInfo;
6334
6392
  }
6335
6393
  headers = setTestHeaders(headers, {
@@ -6337,20 +6395,6 @@ function createPackClient(options) {
6337
6395
  testInfoForRequest,
6338
6396
  testFromQueryParams: testFromQueryParams || test
6339
6397
  });
6340
- if (testFromQueryParams) {
6341
- try {
6342
- const result = await packClient.fetch(query, {
6343
- variables: queryVariables,
6344
- headers
6345
- });
6346
- return {
6347
- ...result,
6348
- packTestInfo: testInfoForLoader
6349
- };
6350
- } catch (error) {
6351
- return { error, data: null };
6352
- }
6353
- }
6354
6398
  if (previewEnabled) {
6355
6399
  try {
6356
6400
  const result = await packClient.fetch(query, {
@@ -6405,6 +6449,7 @@ function createPackClient(options) {
6405
6449
  }
6406
6450
  },
6407
6451
  session,
6452
+ errorTracking,
6408
6453
  testSession
6409
6454
  };
6410
6455
  }
@@ -6624,8 +6669,35 @@ var PackTestContext = createContext({});
6624
6669
  var usePackTestContext = () => useContext(PackTestContext);
6625
6670
 
6626
6671
  // src/tests/pack-test-route.ts
6627
- var usePackLoaderData = () => {
6672
+ function getImpressionSectionSelectors(packTestInfo) {
6673
+ const sectionIds = packTestInfo.impression?.sectionIds || [];
6674
+ return [...new Set(sectionIds)].map(
6675
+ (sectionId) => `section[data-comp-id="${sectionId}"]`
6676
+ );
6677
+ }
6678
+ function getImpressionSelectors(packTestInfo, impressionSelector) {
6679
+ if (!impressionSelector) {
6680
+ return getImpressionSectionSelectors(packTestInfo);
6681
+ }
6682
+ const resolvedSelector = typeof impressionSelector === "function" ? impressionSelector(packTestInfo) : impressionSelector;
6683
+ if (!resolvedSelector) {
6684
+ return getImpressionSectionSelectors(packTestInfo);
6685
+ }
6686
+ return Array.isArray(resolvedSelector) ? resolvedSelector : [resolvedSelector];
6687
+ }
6688
+ function serializeExposedTestCookieValue(packTestInfo) {
6689
+ return JSON.stringify({
6690
+ id: packTestInfo.id,
6691
+ handle: packTestInfo.handle,
6692
+ testVariant: {
6693
+ id: packTestInfo.testVariant.id,
6694
+ handle: packTestInfo.testVariant.handle
6695
+ }
6696
+ });
6697
+ }
6698
+ var usePackLoaderData = (impressionSelector) => {
6628
6699
  const [exposedTestInfo, setExposedTestInfo] = useState();
6700
+ const [triggeredExposure, setTriggeredExposure] = useState();
6629
6701
  let packTestInfo;
6630
6702
  let packIsPreviewMode;
6631
6703
  let revalidator;
@@ -6649,61 +6721,216 @@ var usePackLoaderData = () => {
6649
6721
  } = usePackTestContext();
6650
6722
  const exposedTestCookieString = api.get("exposedTest");
6651
6723
  useEffect2(() => {
6652
- if (packTestInfo && !packIsPreviewMode && !hasUserConsent2 && exposedTestCookieString && exposedTestInfo) {
6653
- setPendingExposureQueue?.((prev) => {
6654
- const newQueue = new Map(prev);
6655
- newQueue.set(packTestInfo.id, {
6656
- packTestInfo,
6657
- exposureTime: Date.now()
6724
+ if (packTestInfo?.id !== triggeredExposure?.packTestInfo.id) {
6725
+ setTriggeredExposure(void 0);
6726
+ return;
6727
+ }
6728
+ if (packTestInfo?.testVariant?.id !== triggeredExposure?.packTestInfo.testVariant.id) {
6729
+ setTriggeredExposure(void 0);
6730
+ }
6731
+ }, [triggeredExposure, packTestInfo]);
6732
+ useEffect2(() => {
6733
+ if (!packTestInfo || !!packIsPreviewMode || !!exposedTestCookieString || !!exposedTestInfo) {
6734
+ return;
6735
+ }
6736
+ if (triggeredExposure?.packTestInfo.id === packTestInfo.id && triggeredExposure.packTestInfo.testVariant.id === packTestInfo.testVariant.id) {
6737
+ return;
6738
+ }
6739
+ if (packTestInfo.impressionTrigger !== "ON_ELEMENT_VIEW" || typeof window === "undefined" || typeof IntersectionObserver === "undefined") {
6740
+ setTriggeredExposure({
6741
+ packTestInfo,
6742
+ exposureTime: Date.now()
6743
+ });
6744
+ return;
6745
+ }
6746
+ const impressionSectionSelectors = getImpressionSelectors(
6747
+ packTestInfo,
6748
+ impressionSelector
6749
+ );
6750
+ if (impressionSectionSelectors.length === 0) {
6751
+ console.warn(
6752
+ `[Pack Test] Test "${packTestInfo.id}" uses ON_ELEMENT_VIEW but has no selectors to observe. Impression will not be tracked.`
6753
+ );
6754
+ return;
6755
+ }
6756
+ let isTriggered = false;
6757
+ const observedElements = /* @__PURE__ */ new Set();
6758
+ let observer;
6759
+ let mutationObserver;
6760
+ let observerTimeoutId;
6761
+ const cleanup = () => {
6762
+ observer?.disconnect();
6763
+ mutationObserver?.disconnect();
6764
+ if (observerTimeoutId) {
6765
+ clearTimeout(observerTimeoutId);
6766
+ }
6767
+ };
6768
+ const triggerImpression = () => {
6769
+ if (isTriggered) return;
6770
+ isTriggered = true;
6771
+ cleanup();
6772
+ setTriggeredExposure({
6773
+ packTestInfo,
6774
+ exposureTime: Date.now()
6775
+ });
6776
+ };
6777
+ const observeMatchingElements = () => {
6778
+ if (!observer || isTriggered) return;
6779
+ impressionSectionSelectors.forEach((selector) => {
6780
+ document.querySelectorAll(selector).forEach((element) => {
6781
+ if (observedElements.has(element)) return;
6782
+ observedElements.add(element);
6783
+ observer?.observe(element);
6658
6784
  });
6659
- return newQueue;
6660
6785
  });
6786
+ if (observedElements.size > 0) {
6787
+ mutationObserver?.disconnect();
6788
+ mutationObserver = void 0;
6789
+ if (observerTimeoutId) {
6790
+ clearTimeout(observerTimeoutId);
6791
+ observerTimeoutId = void 0;
6792
+ }
6793
+ }
6794
+ };
6795
+ observer = new IntersectionObserver(
6796
+ (entries) => {
6797
+ if (entries.some((entry) => entry.isIntersecting)) {
6798
+ triggerImpression();
6799
+ }
6800
+ },
6801
+ { threshold: 0.1 }
6802
+ );
6803
+ observeMatchingElements();
6804
+ if (!isTriggered && observedElements.size === 0 && typeof MutationObserver !== "undefined") {
6805
+ mutationObserver = new MutationObserver(() => {
6806
+ observeMatchingElements();
6807
+ });
6808
+ mutationObserver.observe(document.body, {
6809
+ childList: true,
6810
+ subtree: true
6811
+ });
6812
+ observerTimeoutId = setTimeout(() => {
6813
+ if (!isTriggered && observedElements.size === 0) {
6814
+ console.warn(
6815
+ `[Pack Test] Impression target not found within 30s for test "${packTestInfo.id}". Stopping observer.`
6816
+ );
6817
+ cleanup();
6818
+ }
6819
+ }, 3e4);
6661
6820
  }
6821
+ return cleanup;
6662
6822
  }, [
6663
- hasUserConsent2,
6823
+ triggeredExposure,
6824
+ exposedTestCookieString,
6825
+ exposedTestInfo,
6826
+ packIsPreviewMode,
6664
6827
  packTestInfo,
6828
+ impressionSelector
6829
+ ]);
6830
+ useEffect2(() => {
6831
+ if (triggeredExposure && !packIsPreviewMode && !hasUserConsent2 && !exposedTestCookieString) {
6832
+ setPendingExposureQueue?.((prev) => {
6833
+ if (prev.has(triggeredExposure.packTestInfo.id)) return prev;
6834
+ const nextQueue = new Map(prev);
6835
+ nextQueue.set(triggeredExposure.packTestInfo.id, triggeredExposure);
6836
+ return nextQueue;
6837
+ });
6838
+ }
6839
+ }, [
6840
+ triggeredExposure,
6841
+ hasUserConsent2,
6665
6842
  packIsPreviewMode,
6666
6843
  exposedTestCookieString,
6667
- exposedTestInfo
6844
+ setPendingExposureQueue
6668
6845
  ]);
6669
6846
  useEffect2(() => {
6670
- if (packTestInfo && hasUserConsent2 && !exposedTestCookieString && !packIsPreviewMode && !exposedTestInfo) {
6847
+ if (triggeredExposure && hasUserConsent2 && !exposedTestCookieString && !packIsPreviewMode && !exposedTestInfo) {
6848
+ const { packTestInfo: activeTestInfo, exposureTime } = triggeredExposure;
6671
6849
  const expires = /* @__PURE__ */ new Date();
6672
6850
  expires.setHours(expires.getHours() + 24);
6673
- api.set("exposedTest", JSON.stringify(packTestInfo), {
6674
- expires
6675
- });
6676
- setExposedTestInfo(packTestInfo);
6851
+ api.set(
6852
+ "exposedTest",
6853
+ serializeExposedTestCookieValue(activeTestInfo),
6854
+ {
6855
+ expires
6856
+ }
6857
+ );
6858
+ setExposedTestInfo(activeTestInfo);
6677
6859
  pendingExposureQueue?.forEach((data, key) => {
6860
+ if (key === activeTestInfo.id) return;
6678
6861
  try {
6679
6862
  testExposureCallback?.({
6680
6863
  ...data.packTestInfo,
6681
6864
  exposureTime: data.exposureTime
6682
6865
  });
6683
- setPendingExposureQueue?.((prev) => {
6684
- const newQueue = new Map(prev);
6685
- newQueue.delete(key);
6686
- return newQueue;
6687
- });
6688
6866
  } catch (error) {
6689
6867
  console.error("Failed to call testExposure after consent:", error);
6690
6868
  }
6691
6869
  });
6692
- testExposureCallback?.({ ...packTestInfo, exposureTime: Date.now() });
6870
+ testExposureCallback?.({ ...activeTestInfo, exposureTime });
6871
+ setPendingExposureQueue?.(/* @__PURE__ */ new Map());
6693
6872
  setTimeout(() => {
6694
6873
  revalidator?.revalidate();
6695
6874
  }, 750);
6696
6875
  }
6697
6876
  }, [
6698
- packTestInfo,
6877
+ triggeredExposure,
6878
+ exposedTestCookieString,
6879
+ pendingExposureQueue,
6699
6880
  testExposureCallback,
6700
6881
  exposedTestInfo,
6701
6882
  hasUserConsent2,
6702
- packIsPreviewMode
6883
+ packIsPreviewMode,
6884
+ setPendingExposureQueue
6885
+ ]);
6886
+ useEffect2(() => {
6887
+ if (!!triggeredExposure || !hasUserConsent2 || !!packIsPreviewMode || !pendingExposureQueue?.size || !!exposedTestCookieString) {
6888
+ return;
6889
+ }
6890
+ let firstQueuedTest;
6891
+ pendingExposureQueue.forEach((data) => {
6892
+ if (!firstQueuedTest) {
6893
+ firstQueuedTest = data.packTestInfo;
6894
+ }
6895
+ try {
6896
+ testExposureCallback?.({
6897
+ ...data.packTestInfo,
6898
+ exposureTime: data.exposureTime
6899
+ });
6900
+ } catch (error) {
6901
+ console.error(
6902
+ "Failed to call queued testExposure after consent:",
6903
+ error
6904
+ );
6905
+ }
6906
+ });
6907
+ if (firstQueuedTest) {
6908
+ const expires = /* @__PURE__ */ new Date();
6909
+ expires.setHours(expires.getHours() + 24);
6910
+ api.set(
6911
+ "exposedTest",
6912
+ serializeExposedTestCookieValue(firstQueuedTest),
6913
+ {
6914
+ expires
6915
+ }
6916
+ );
6917
+ setExposedTestInfo(firstQueuedTest);
6918
+ }
6919
+ setPendingExposureQueue?.(/* @__PURE__ */ new Map());
6920
+ }, [
6921
+ triggeredExposure,
6922
+ exposedTestCookieString,
6923
+ hasUserConsent2,
6924
+ packIsPreviewMode,
6925
+ pendingExposureQueue,
6926
+ setPendingExposureQueue,
6927
+ testExposureCallback
6703
6928
  ]);
6704
6929
  };
6705
- var PackTestRoute = () => {
6706
- usePackLoaderData();
6930
+ var PackTestRoute = ({
6931
+ impressionSelector
6932
+ } = {}) => {
6933
+ usePackLoaderData(impressionSelector);
6707
6934
  return null;
6708
6935
  };
6709
6936