@pack/hydrogen 1.0.5 → 1.0.6-ab-test.2

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.
@@ -2,6 +2,7 @@
2
2
  import { PackClient } from "@pack/client";
3
3
  import { CacheCustom } from "@shopify/hydrogen";
4
4
  import { PackSession } from "./session/session";
5
+ import { Test, TestInput } from "./tests/test";
5
6
  /** @see https://shopify.dev/docs/custom-storefronts/hydrogen/data-fetching/cache#caching-strategies */
6
7
  type CachingStrategy = ReturnType<typeof CacheCustom>;
7
8
  interface EnvironmentOptions {
@@ -16,7 +17,7 @@ interface EnvironmentOptions {
16
17
  */
17
18
  waitUntil: ExecutionContext["waitUntil"];
18
19
  }
19
- interface CreatePackClientOptions extends EnvironmentOptions {
20
+ export interface CreatePackClientOptions extends EnvironmentOptions {
20
21
  apiUrl?: string;
21
22
  token?: string;
22
23
  storeId?: string;
@@ -29,6 +30,7 @@ type Variables = Record<string, any>;
29
30
  interface QueryOptions {
30
31
  variables?: Variables;
31
32
  cache?: CachingStrategy;
33
+ test?: TestInput;
32
34
  }
33
35
  interface QueryError {
34
36
  message: string;
@@ -39,18 +41,40 @@ interface QueryError {
39
41
  interface QueryResponse<T> {
40
42
  data: T | null;
41
43
  error: QueryError | null;
44
+ packTestInfo?: Test;
45
+ }
46
+ export interface PackCustomizerMeta {
47
+ environment?: string;
48
+ overlay?: {
49
+ src?: string;
50
+ version?: string;
51
+ };
52
+ [key: string]: any;
42
53
  }
43
54
  export interface Pack {
44
- isPreviewModeEnabled: () => boolean;
45
- session: PackSession;
46
- query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
47
- isValidEditToken: PackClient["isValidEditToken"];
55
+ abTest: Test | null | undefined;
56
+ /**
57
+ * @deprecated The method should not be used
58
+ */
48
59
  getPackSessionData(): {
49
60
  storeId: string;
50
61
  sessionId: string;
62
+ abTest: Test | null | undefined;
51
63
  isPreviewModeEnabled: boolean;
52
64
  customizerMeta: any;
53
65
  };
66
+ getPackContextData(): {
67
+ packStoreId: string;
68
+ packSessionId: string;
69
+ packAbTest: Test | null | undefined;
70
+ packIsPreviewMode: boolean;
71
+ packCustomizerMeta: PackCustomizerMeta | null;
72
+ };
73
+ handleRequest(request: Request): Promise<(response: Response) => void>;
74
+ isPreviewModeEnabled: () => boolean;
75
+ isValidEditToken: PackClient["isValidEditToken"];
76
+ query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
77
+ session: PackSession;
54
78
  }
55
79
  interface DefaultThemeData {
56
80
  data: Record<string, any>;
@@ -1 +1 @@
1
- {"version":3,"file":"create-pack-client.d.ts","sourceRoot":"","sources":["../src/create-pack-client.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,uGAAuG;AACvG,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,UAAU,kBAAkB;IAC1B;;;OAGG;IACH,KAAK,EAAE,KAAK,CAAC;IACb;;;OAGG;IACH,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAC;CAC1C;AAED,UAAU,uBAAwB,SAAQ,kBAAkB;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAED,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAErC,UAAU,YAAY;IACpB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,aAAa,CAAC,CAAC;IACvB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,IAAI;IACnB,oBAAoB,EAAE,MAAM,OAAO,CAAC;IACpC,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC,GAAG,GAAG,EACb,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,YAAY,KACnB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,gBAAgB,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;IAEjD,kBAAkB,IAAI;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,oBAAoB,EAAE,OAAO,CAAC;QAC9B,cAAc,EAAE,GAAG,CAAC;KACrB,CAAC;CACH;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC3B;AAoJD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAsIvE"}
1
+ {"version":3,"file":"create-pack-client.d.ts","sourceRoot":"","sources":["../src/create-pack-client.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
1
  import { PackClient } from "@pack/client";
2
2
  import { CacheCustom, createWithCache } from "@shopify/hydrogen";
3
+ import cookie from "cookie";
4
+ import { getTestInfo, getTestFromQueryParams, getTestSession, getTestTargetingAttributesFromRequest, setTestHeaders, } from "./tests/test";
3
5
  /**
4
6
  * Create an SHA-256 hash as a hex string
5
7
  * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
@@ -113,16 +115,71 @@ export function createPackClient(options) {
113
115
  const previewEnabled = !!session.get("previewEnabled");
114
116
  const previewEnvironment = session.get("environment");
115
117
  const clientContentEnvironment = previewEnvironment || contentEnvironment;
118
+ let packClient;
119
+ let testFromQueryParams = null;
120
+ let testInfoForRequest = undefined;
116
121
  if (!token && !defaultThemeData) {
117
122
  throw new Error("The Pack client token is missing or empty. Please provide a valid token or default theme data.");
118
123
  }
119
124
  if (!storeId) {
120
125
  throw new Error("The Pack Store ID is missing or empty. Please provide a valid Store ID.");
121
126
  }
127
+ const handleRequest = async (request) => {
128
+ testFromQueryParams = getTestFromQueryParams(request);
129
+ const testTargetAudienceAttributes = getTestTargetingAttributesFromRequest(request);
130
+ if (packClient && token) {
131
+ testInfoForRequest = await getTestInfo({
132
+ request,
133
+ testTargetAudienceAttributes,
134
+ packClient,
135
+ session,
136
+ token,
137
+ });
138
+ }
139
+ return (response) => {
140
+ const hasExposedTestCookie = request.headers
141
+ .get("cookie")
142
+ ?.includes("exposedTest");
143
+ if (hasExposedTestCookie) {
144
+ // Clear the exposedTest cookie
145
+ response.headers.set("Set-Cookie", cookie.serialize("exposedTest", "", {
146
+ maxAge: 0,
147
+ }));
148
+ }
149
+ };
150
+ };
151
+ const getTestInfoForLoader = () => {
152
+ let testInfoForLoader = undefined;
153
+ if (testInfoForRequest?.isFirstExposure) {
154
+ const { isFirstExposure, ...testInfo } = testInfoForRequest;
155
+ testInfoForLoader = testInfo;
156
+ }
157
+ return testInfoForLoader;
158
+ };
122
159
  if (!token) {
123
160
  return {
124
- session,
161
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
162
+ handleRequest,
163
+ getPackSessionData: () => {
164
+ return {
165
+ storeId: storeId,
166
+ sessionId: session.id,
167
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
168
+ isPreviewModeEnabled: previewEnabled,
169
+ customizerMeta: session.get("customizerMeta"),
170
+ };
171
+ },
172
+ getPackContextData: () => {
173
+ return {
174
+ packStoreId: storeId,
175
+ packSessionId: session.id,
176
+ packAbTest: getTestSession(session, testFromQueryParams, previewEnabled),
177
+ packIsPreviewMode: previewEnabled,
178
+ packCustomizerMeta: session.get("customizerMeta"),
179
+ };
180
+ },
125
181
  isPreviewModeEnabled: () => previewEnabled,
182
+ isValidEditToken: () => new Promise(() => false),
126
183
  async query(query, { variables } = {}) {
127
184
  if (!defaultThemeData?.data) {
128
185
  console.warn("Invalid default theme data provided to Pack client.");
@@ -131,18 +188,10 @@ export function createPackClient(options) {
131
188
  const data = resolveQuery({ query, variables, defaultThemeData });
132
189
  return { data: data, error: null };
133
190
  },
134
- isValidEditToken: () => new Promise(() => false),
135
- getPackSessionData: () => {
136
- return {
137
- storeId: storeId,
138
- sessionId: session.id,
139
- isPreviewModeEnabled: previewEnabled,
140
- customizerMeta: session.get("customizerMeta"),
141
- };
142
- },
191
+ session,
143
192
  };
144
193
  }
145
- const packClient = new PackClient({
194
+ packClient = new PackClient({
146
195
  // Use apiUrl, it is configured
147
196
  // Use active API URL if preview mode is enabled
148
197
  // Otherwise, Live PackClient uses its internal configuration
@@ -158,14 +207,39 @@ export function createPackClient(options) {
158
207
  clientName: "HydrogenClient",
159
208
  });
160
209
  return {
161
- session,
210
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
211
+ getPackSessionData: () => {
212
+ return {
213
+ storeId: storeId,
214
+ sessionId: session.id,
215
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
216
+ isPreviewModeEnabled: previewEnabled,
217
+ customizerMeta: session.get("customizerMeta"),
218
+ };
219
+ },
220
+ getPackContextData: () => {
221
+ return {
222
+ packStoreId: storeId,
223
+ packSessionId: session.id,
224
+ packAbTest: getTestSession(session, testFromQueryParams, previewEnabled),
225
+ packIsPreviewMode: previewEnabled,
226
+ packCustomizerMeta: session.get("customizerMeta"),
227
+ };
228
+ },
229
+ handleRequest,
162
230
  isPreviewModeEnabled: () => previewEnabled,
163
- async query(query, { variables, cache: strategy = cacheCustom } = {}) {
231
+ isValidEditToken: (token) => packClient.isValidEditToken(token),
232
+ async query(query, { variables, cache: strategy = cacheCustom, test } = {}) {
233
+ let headers = {};
164
234
  const withCache = createWithCache({
165
235
  cache,
166
236
  waitUntil,
167
237
  });
168
- let headers = {};
238
+ headers = setTestHeaders(headers, {
239
+ previewEnabled,
240
+ testInfoForRequest,
241
+ testFromQueryParams: testFromQueryParams || test,
242
+ });
169
243
  const queryVariables = variables ? { ...variables } : {};
170
244
  if (previewEnabled) {
171
245
  queryVariables.version = "CURRENT";
@@ -173,23 +247,27 @@ export function createPackClient(options) {
173
247
  else {
174
248
  queryVariables.version = "PUBLISHED";
175
249
  }
250
+ const testInfoForLoader = getTestInfoForLoader();
176
251
  // Preview mode always bypasses the cache
177
252
  if (previewEnabled) {
178
253
  try {
179
- return await packClient.fetch(query, {
180
- variables: queryVariables,
181
- headers: headers,
182
- });
254
+ return {
255
+ ...(await packClient.fetch(query, {
256
+ variables: queryVariables,
257
+ headers: headers,
258
+ })),
259
+ packTestInfo: testInfoForLoader,
260
+ };
183
261
  }
184
262
  catch (error) {
185
- return { error, data: {} };
263
+ return { error: error, data: null };
186
264
  }
187
265
  }
188
266
  const queryHash = await getCacheKey(withCache, query, token, {
189
267
  variables: queryVariables,
190
268
  headers,
191
269
  });
192
- return withCache(queryHash, strategy, async () => {
270
+ const response = await withCache(queryHash, strategy, async () => {
193
271
  try {
194
272
  return await packClient.fetch(query, {
195
273
  variables: queryVariables,
@@ -197,18 +275,13 @@ export function createPackClient(options) {
197
275
  });
198
276
  }
199
277
  catch (error) {
200
- return { error, data: {} };
278
+ return { error, data: null };
201
279
  }
202
280
  });
281
+ return response.error
282
+ ? response
283
+ : { ...response, packTestInfo: testInfoForLoader };
203
284
  },
204
- isValidEditToken: (token) => packClient.isValidEditToken(token),
205
- getPackSessionData: () => {
206
- return {
207
- storeId: storeId,
208
- sessionId: session.id,
209
- isPreviewModeEnabled: previewEnabled,
210
- customizerMeta: session.get("customizerMeta"),
211
- };
212
- },
285
+ session,
213
286
  };
214
287
  }
@@ -1 +1 @@
1
- {"version":3,"file":"handle-request.d.ts","sourceRoot":"","sources":["../src/handle-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAG5C,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GACrD,OAAO,CAAC,QAAQ,CAAC,CASnB"}
1
+ {"version":3,"file":"handle-request.d.ts","sourceRoot":"","sources":["../src/handle-request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAG5C,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,GACrD,OAAO,CAAC,QAAQ,CAAC,CAanB"}
@@ -1,8 +1,10 @@
1
1
  import { packlytics } from "@pack/packlytics";
2
2
  export async function handleRequest(pack, request, handleRequest) {
3
+ const packHandleResponse = await pack.handleRequest(request);
3
4
  const response = await packlytics(pack, request, () => {
4
5
  return handleRequest(request);
5
6
  });
7
+ packHandleResponse(response);
6
8
  response.headers.append("powered-by", "Shopify, Hydrogen + Pack Digital");
7
9
  response.headers.append("Set-Cookie", await pack.session.commit());
8
10
  return response;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import { handleRequest } from "./handle-request";
2
2
  import { usePackCookies } from "./session/usePackCookies";
3
3
  import { PackSession } from "./session/session";
4
- import { createPackClient } from "./create-pack-client";
4
+ import { Pack, createPackClient } from "./create-pack-client";
5
5
  import { action as previewModeAction, loader as previewModeLoader } from "./preview/preview-mode";
6
- export { PackSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, };
6
+ import { PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle } from "./tests";
7
+ export type { Pack };
8
+ export { PackSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,MAAM,IAAI,iBAAiB,EAC3B,MAAM,IAAI,iBAAiB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EACL,MAAM,IAAI,iBAAiB,EAC3B,MAAM,IAAI,iBAAiB,EAC5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,IAAI,EAAE,CAAC;AAErB,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,CAAC"}
package/dist/index.js CHANGED
@@ -3,4 +3,5 @@ import { usePackCookies } from "./session/usePackCookies";
3
3
  import { PackSession } from "./session/session";
4
4
  import { createPackClient } from "./create-pack-client";
5
5
  import { action as previewModeAction, loader as previewModeLoader, } from "./preview/preview-mode";
6
- export { PackSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, };
6
+ import { PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, } from "./tests";
7
+ export { PackSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
@@ -1 +1 @@
1
- {"version":3,"file":"preview-mode.d.ts","sourceRoot":"","sources":["../../src/preview/preview-mode.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAInC,KAAK,YAAY,GAAG,CAAC,IAAI,EACvB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,KACzB,aAAa,CAAC,IAAI,CAAC,CAAC;AAEzB,KAAK,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,KAAK,QAAQ,CAAC;AAIhF,KAAK,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG;IACzD,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,IAAI,EAAE,YAYlB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,gBAetB,CAAC;AAoBF;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,cAkBpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,cAwCpB,CAAC"}
1
+ {"version":3,"file":"preview-mode.d.ts","sourceRoot":"","sources":["../../src/preview/preview-mode.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,2BAA2B,CAAC;AAKnC,KAAK,YAAY,GAAG,CAAC,IAAI,EACvB,IAAI,EAAE,IAAI,EACV,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,KACzB,aAAa,CAAC,IAAI,CAAC,CAAC;AAEzB,KAAK,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,KAAK,QAAQ,CAAC;AAIhF,KAAK,aAAa,CAAC,CAAC,GAAG,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG;IACzD,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,IAAI,EAAE,YAYlB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,gBAetB,CAAC;AAoBF;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,cAkBpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,cA0CpB,CAAC"}
@@ -88,7 +88,9 @@ export const loader = async function ({ request, context }) {
88
88
  }
89
89
  let customizerMetaJson;
90
90
  try {
91
- customizerMetaJson = customizerMeta ? JSON.parse(customizerMeta) : null;
91
+ customizerMetaJson = customizerMeta
92
+ ? JSON.parse(customizerMeta)
93
+ : null;
92
94
  }
93
95
  catch (_error) {
94
96
  customizerMetaJson = null;
@@ -1,5 +1,5 @@
1
1
  import { PACK_USER_CONSENT_COOKIE_ID } from "../constants";
2
- const cookie = require("cookie");
2
+ import cookie from "cookie";
3
3
  const tokenHash = "xxxx-4xxx-xxxx-xxxxxxxxxxxx";
4
4
  export function hasUserConsent(request) {
5
5
  const cookies = cookie.parse(request.headers.get("Cookie"));
@@ -29,11 +29,12 @@ export interface GetTestInfoOptions {
29
29
  testTargetAudienceAttributes: TestTargetAudienceAttributes | null;
30
30
  packClient: PackClient;
31
31
  session: PackSession | undefined;
32
+ token: string;
32
33
  }
33
34
  export interface TestInfo extends Test {
34
35
  isFirstExposure?: boolean;
35
36
  }
36
- export declare function getTestInfo({ request, testTargetAudienceAttributes, packClient, session, }: GetTestInfoOptions): Promise<TestInfo | undefined>;
37
+ export declare function getTestInfo({ request, testTargetAudienceAttributes, packClient, session, token, }: GetTestInfoOptions): Promise<TestInfo | undefined>;
37
38
  export declare function getTestSession(session: PackSession | undefined, testFromQueryParams: TestInput | null, previewEnabled: boolean): Test | null | undefined;
38
39
  export declare function getTestTargetingAttributesFromRequest(request: Request): TestTargetAudienceAttributes;
39
40
  export declare function getTestFromQueryParams(request: Request): TestInput | null;
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/tests/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqBD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAClE,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,QAAS,SAAQ,IAAI;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,4BAA4B,EAC5B,UAAU,EACV,OAAO,GACR,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAuHpD;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,mBAAmB,EAAE,SAAS,GAAG,IAAI,EACrC,cAAc,EAAE,OAAO,GACtB,IAAI,GAAG,IAAI,GAAG,SAAS,CA8BzB;AAED,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,OAAO,GACf,4BAA4B,CA0B9B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CA2CzE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE;IACP,cAAc,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,QAAQ,CAAC;IAC9B,mBAAmB,CAAC,EAAE,SAAS,CAAC;CACjC,OAgCF"}
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/tests/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAqBD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAClE,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAS,SAAQ,IAAI;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAmFD,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,4BAA4B,EAC5B,UAAU,EACV,OAAO,EACP,KAAK,GACN,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CA6DpD;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,mBAAmB,EAAE,SAAS,GAAG,IAAI,EACrC,cAAc,EAAE,OAAO,GACtB,IAAI,GAAG,IAAI,GAAG,SAAS,CA8BzB;AAED,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,OAAO,GACf,4BAA4B,CA0B9B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CA2CzE;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,GAAG,EACZ,OAAO,EAAE;IACP,cAAc,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,QAAQ,CAAC;IAC9B,mBAAmB,CAAC,EAAE,SAAS,CAAC;CACjC,OA+BF"}
@@ -16,99 +16,102 @@ const QUERY_TEST_VARIANT_IS_RUNNING = `#graphql
16
16
  testVariantIsRunning(handle: $handle, testId: $testId)
17
17
  }
18
18
  `;
19
- export async function getTestInfo({ request, testTargetAudienceAttributes, packClient, session, }) {
20
- let testInfo = undefined;
21
- if (session) {
22
- // Request directly to API if it is using CDN to reduce latency
23
- let isUsingCdn = false;
24
- if (packClient.apiUrl === "https://apicdn.packdigital.com/graphql") {
25
- packClient.apiUrl = "https://app.packdigital.com/graphql";
26
- isUsingCdn = true;
19
+ async function packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token) {
20
+ if (!packClient.storeId) {
21
+ console.error("Pack error: Store ID is required to fetch a test.");
22
+ return undefined;
23
+ }
24
+ let resp = undefined;
25
+ const URL = "https://tests-service-production.packdigital.workers.dev/tests/assign";
26
+ const assignByRulesResp = await fetch(URL, {
27
+ method: "POST",
28
+ headers: {
29
+ Authorization: `Bearer ${token}`,
30
+ "X-Pack-Storefront-Id": packClient.storeId,
31
+ ...(packClient.contentEnvironment
32
+ ? { "X-Environment": packClient.contentEnvironment }
33
+ : {}),
34
+ "Content-Type": "application/json",
35
+ },
36
+ body: JSON.stringify({
37
+ attributes: testTargetAudienceAttributes,
38
+ }),
39
+ });
40
+ const { status } = assignByRulesResp;
41
+ if (status !== 200) {
42
+ let message;
43
+ if (status === 401) {
44
+ message =
45
+ "Pack error: Unauthorized request to test assignment service. Please check your token.";
27
46
  }
28
- const packClientFetchTestByRules = packClient.fetch(QUERY_TEST_BY_RULES, {
47
+ else {
48
+ message = `Pack error: Request to test assignment service failed with status ${status}`;
49
+ }
50
+ console.error(message);
51
+ return resp;
52
+ }
53
+ const assignByRulesRespJson = await assignByRulesResp.json();
54
+ if (assignByRulesRespJson?.data) {
55
+ resp = {
56
+ ...assignByRulesRespJson.data,
57
+ isFirstExposure: true,
58
+ };
59
+ }
60
+ return resp;
61
+ }
62
+ async function packClientCheckTestIsRunning(packClient, testSession) {
63
+ try {
64
+ const resp = await packClient.fetch(QUERY_TEST_VARIANT_IS_RUNNING, {
29
65
  variables: {
30
- testTargetAudienceAttributes,
66
+ handle: testSession.testVariant.handle,
67
+ testId: testSession.id,
31
68
  },
32
69
  });
33
- const fetchTestByRulesSetTestInfoAsFirstExposure = async () => {
34
- const resp = await packClientFetchTestByRules;
35
- const responseTest = resp.data?.testByRules;
36
- if (responseTest) {
37
- testInfo = {
38
- ...responseTest,
39
- isFirstExposure: true,
40
- };
41
- }
42
- else {
43
- session.set("test", undefined);
44
- }
45
- };
46
- const testSession = session.get("test");
47
- let exposedTest = undefined;
48
- const exposedTestCookieString = cookie.parse(request.headers.get("cookie") || "")?.exposedTest;
49
- if (exposedTestCookieString) {
50
- exposedTest = JSON.parse(exposedTestCookieString);
70
+ return !!resp.data.testVariantIsRunning;
71
+ }
72
+ catch (e) {
73
+ console.error(e);
74
+ }
75
+ return false;
76
+ }
77
+ export async function getTestInfo({ request, testTargetAudienceAttributes, packClient, session, token, }) {
78
+ let testInfo = undefined;
79
+ let exposedTest = undefined;
80
+ if (!session) {
81
+ return testInfo;
82
+ }
83
+ const testSession = session.get("test");
84
+ const exposedTestCookieString = cookie.parse(request.headers.get("cookie") || "")?.exposedTest;
85
+ if (exposedTestCookieString) {
86
+ exposedTest = JSON.parse(exposedTestCookieString);
87
+ }
88
+ // If there is test on session, check if the test on session is still running.
89
+ if (testSession && testSession.data?.id && testSession.data?.testVariant) {
90
+ const testVariantIsRunning = await packClientCheckTestIsRunning(packClient, testSession.data);
91
+ // If true, set testInfo to the testSession assigned test.
92
+ if (testVariantIsRunning) {
93
+ testInfo = testSession.data;
51
94
  }
95
+ }
96
+ else if (exposedTest) {
52
97
  // If there is no assigned test on the session and an exposed test in the
53
- // incoming request cookie it means that the user was exposed to a test
54
- // for the first time previously.
55
- if (!testSession && exposedTest) {
56
- // Verify test is still running
57
- const resp = await packClient.fetch(QUERY_TEST_VARIANT_IS_RUNNING, {
58
- variables: {
59
- handle: exposedTest.testVariant.handle,
60
- testId: exposedTest.id,
61
- },
62
- });
63
- // If the test is still running, set testInfo to the exposed test
64
- if (!!resp.data.testVariantIsRunning) {
65
- const { id, handle, testVariant } = exposedTest;
66
- testInfo = {
67
- id,
68
- handle,
69
- testVariant,
70
- };
71
- session.set("test", {
72
- data: testInfo,
73
- });
74
- }
75
- else {
76
- // If the test is not running anymore, fetch a new test for the user
77
- await fetchTestByRulesSetTestInfoAsFirstExposure();
78
- }
79
- }
80
- else {
81
- if (!testSession ||
82
- !(testSession.data?.id && testSession.data?.testVariant)) {
83
- // If there is no assigned test on the session, check if there is a test
84
- // for attributes, then assign to session and return value.
85
- await fetchTestByRulesSetTestInfoAsFirstExposure();
86
- }
87
- else {
88
- // If there is test on session, check if the test on session is still running.
89
- const packClientFetchTestVariantIsRunning = packClient.fetch(QUERY_TEST_VARIANT_IS_RUNNING, {
90
- variables: {
91
- handle: testSession.data.testVariant.handle,
92
- testId: testSession.data.id,
93
- },
94
- });
95
- const resp = await packClientFetchTestVariantIsRunning;
96
- if (!!resp.data.testVariantIsRunning) {
97
- // If true, set testInfo to the testSession assigned test.
98
- testInfo = testSession.data;
99
- }
100
- else {
101
- // If not running, clear test session and fetch a new test for the user
102
- session.set("test", undefined);
103
- await fetchTestByRulesSetTestInfoAsFirstExposure();
104
- }
105
- }
106
- }
107
- if (isUsingCdn) {
108
- // Return to CDN
109
- packClient.apiUrl = "https://apicdn.packdigital.com/graphql";
98
+ // incoming request cookie it means that the user was exposed to a test previously.
99
+ // Verify test is still running
100
+ const testVariantIsRunning = await packClientCheckTestIsRunning(packClient, exposedTest);
101
+ // If the test is still running, set testInfo to the exposed test
102
+ if (testVariantIsRunning) {
103
+ testInfo = exposedTest;
110
104
  }
111
105
  }
106
+ // If the testInfo is not setup that means that is not a valid test
107
+ // on the session or on the cookie, then fetch a new test for the user
108
+ if (!testInfo) {
109
+ testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
110
+ }
111
+ // save this test on the session
112
+ session.set("test", {
113
+ data: testInfo,
114
+ });
112
115
  return testInfo;
113
116
  }
114
117
  export function getTestSession(session, testFromQueryParams, previewEnabled) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pack/hydrogen",
3
3
  "description": "Pack Hydrogen",
4
- "version": "1.0.5",
4
+ "version": "1.0.6-ab-test.2",
5
5
  "exports": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "engines": {
@@ -22,14 +22,17 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@pack/client": "^1.0.1",
25
+ "@pack/client": "^1.0.1-ab-test.0",
26
26
  "@pack/packlytics": "^1.0.1",
27
- "@shopify/hydrogen": "^2023.10.2"
27
+ "@shopify/hydrogen": "^2023.10.2",
28
+ "cookie": "^0.6.0",
29
+ "js-cookie": "^3.0.5"
28
30
  },
29
31
  "devDependencies": {
30
32
  "@remix-run/server-runtime": "^2.0.0",
31
33
  "@shopify/oxygen-workers-types": "^4.0.0",
32
34
  "@shopify/remix-oxygen": "^2.0.1",
35
+ "@types/js-cookie": "^3.0.6",
33
36
  "@types/node": "^20.11.17"
34
37
  },
35
38
  "peerDependencies": {