@pack/hydrogen 1.0.0 → 1.0.1-ab-test.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.
@@ -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,22 @@ interface QueryError {
39
41
  interface QueryResponse<T> {
40
42
  data: T | null;
41
43
  error: QueryError | null;
44
+ testInfo?: Test;
42
45
  }
43
46
  export interface Pack {
44
- isPreviewModeEnabled: () => boolean;
45
- session: PackSession;
46
- query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
47
- isValidEditToken: PackClient["isValidEditToken"];
47
+ abTest: Test | null | undefined;
48
48
  getPackSessionData(): {
49
49
  storeId: string;
50
50
  sessionId: string;
51
+ abTest: Test | null | undefined;
51
52
  isPreviewModeEnabled: boolean;
52
53
  customizerMeta: any;
53
54
  };
55
+ handleRequest(request: Request): Promise<(response: Response) => void>;
56
+ isPreviewModeEnabled: () => boolean;
57
+ isValidEditToken: PackClient["isValidEditToken"];
58
+ query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
59
+ session: PackSession;
54
60
  }
55
61
  interface DefaultThemeData {
56
62
  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;IACjD,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;AA2FD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAkIvE"}
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;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAML,IAAI,EACJ,SAAS,EAGV,MAAM,cAAc,CAAC;AAEtB,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,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,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;IACxB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;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;IACzB,QAAQ,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,kBAAkB,IAAI;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;QAChC,oBAAoB,EAAE,OAAO,CAAC;QAC9B,cAAc,EAAE,GAAG,CAAC;KACrB,CAAC;IACF,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC,CAAC;IACvE,oBAAoB,EAAE,MAAM,OAAO,CAAC;IACpC,gBAAgB,EAAE,UAAU,CAAC,kBAAkB,CAAC,CAAC;IACjD,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,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC3B;AA2FD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAsMvE"}
@@ -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
@@ -76,16 +78,61 @@ export function createPackClient(options) {
76
78
  const previewEnabled = !!session.get("previewEnabled");
77
79
  const previewEnvironment = session.get("environment");
78
80
  const clientContentEnvironment = previewEnvironment || contentEnvironment;
81
+ let packClient;
82
+ let testFromQueryParams = null;
83
+ let testInfoForRequest = undefined;
79
84
  if (!token && !defaultThemeData) {
80
85
  throw new Error("The Pack client token is missing or empty. Please provide a valid token or default theme data.");
81
86
  }
82
87
  if (!storeId) {
83
88
  throw new Error("The Pack Store ID is missing or empty. Please provide a valid Store ID.");
84
89
  }
90
+ const handleRequest = async (request) => {
91
+ testFromQueryParams = getTestFromQueryParams(request);
92
+ const testTargetAudienceAttributes = getTestTargetingAttributesFromRequest(request);
93
+ if (packClient) {
94
+ testInfoForRequest = await getTestInfo({
95
+ request,
96
+ testTargetAudienceAttributes,
97
+ packClient,
98
+ session,
99
+ });
100
+ }
101
+ return (response) => {
102
+ const hasExposedTestCookie = request.headers
103
+ .get("cookie")
104
+ ?.includes("exposedTest");
105
+ if (hasExposedTestCookie) {
106
+ // Clear the exposedTest cookie
107
+ response.headers.set("Set-Cookie", cookie.serialize("exposedTest", "", {
108
+ maxAge: 0,
109
+ }));
110
+ }
111
+ };
112
+ };
113
+ const getTestInfoForLoader = () => {
114
+ let testInfoForLoader = undefined;
115
+ if (testInfoForRequest?.isFirstExposure) {
116
+ const { isFirstExposure, ...testInfo } = testInfoForRequest;
117
+ testInfoForLoader = testInfo;
118
+ }
119
+ return testInfoForLoader;
120
+ };
85
121
  if (!token) {
86
122
  return {
87
- session,
123
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
124
+ handleRequest,
125
+ getPackSessionData: () => {
126
+ return {
127
+ storeId: storeId,
128
+ sessionId: session.id,
129
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
130
+ isPreviewModeEnabled: previewEnabled,
131
+ customizerMeta: session.get("customizerMeta"),
132
+ };
133
+ },
88
134
  isPreviewModeEnabled: () => previewEnabled,
135
+ isValidEditToken: () => new Promise(() => false),
89
136
  async query(query, { variables } = {}) {
90
137
  if (!defaultThemeData?.data) {
91
138
  console.warn("Invalid default theme data provided to Pack client.");
@@ -94,18 +141,10 @@ export function createPackClient(options) {
94
141
  const data = resolveQuery({ query, variables, defaultThemeData });
95
142
  return { data: data, error: null };
96
143
  },
97
- isValidEditToken: () => new Promise(() => false),
98
- getPackSessionData: () => {
99
- return {
100
- storeId: storeId,
101
- sessionId: session.id,
102
- isPreviewModeEnabled: previewEnabled,
103
- customizerMeta: session.get("customizerMeta"),
104
- };
105
- },
144
+ session,
106
145
  };
107
146
  }
108
- const packClient = new PackClient({
147
+ packClient = new PackClient({
109
148
  // Use apiUrl, it is configured
110
149
  // Use active API URL if preview mode is enabled
111
150
  // Otherwise, Live PackClient uses its internal configuration
@@ -120,14 +159,30 @@ export function createPackClient(options) {
120
159
  sessionId: session.id,
121
160
  });
122
161
  return {
123
- session,
162
+ abTest: getTestSession(session, testFromQueryParams, previewEnabled),
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
+ handleRequest,
124
173
  isPreviewModeEnabled: () => previewEnabled,
125
- async query(query, { variables, cache: strategy = cacheCustom } = {}) {
174
+ isValidEditToken: (token) => packClient.isValidEditToken(token),
175
+ async query(query, { variables, cache: strategy = cacheCustom, test } = {}) {
176
+ let headers = {};
126
177
  const withCache = createWithCache({
127
178
  cache,
128
179
  waitUntil,
129
180
  });
130
- let headers = {};
181
+ headers = setTestHeaders(headers, {
182
+ previewEnabled,
183
+ testInfoForRequest,
184
+ testFromQueryParams: testFromQueryParams || test,
185
+ });
131
186
  const queryHash = await hashQuery(query, variables, headers);
132
187
  const queryVariables = variables ? { ...variables } : {};
133
188
  if (previewEnabled) {
@@ -136,19 +191,23 @@ export function createPackClient(options) {
136
191
  else {
137
192
  queryVariables.version = "PUBLISHED";
138
193
  }
194
+ const testInfoForLoader = getTestInfoForLoader();
139
195
  // Preview mode always bypasses the cache
140
196
  if (previewEnabled) {
141
197
  try {
142
- return await packClient.fetch(query, {
143
- variables: queryVariables,
144
- headers: headers,
145
- });
198
+ return {
199
+ ...(await packClient.fetch(query, {
200
+ variables: queryVariables,
201
+ headers: headers,
202
+ })),
203
+ testInfo: testInfoForLoader,
204
+ };
146
205
  }
147
206
  catch (error) {
148
207
  return { error, data: {} };
149
208
  }
150
209
  }
151
- return withCache(queryHash, strategy, async () => {
210
+ const response = await withCache(queryHash, strategy, async () => {
152
211
  try {
153
212
  return await packClient.fetch(query, {
154
213
  variables: queryVariables,
@@ -159,15 +218,10 @@ export function createPackClient(options) {
159
218
  return { error, data: {} };
160
219
  }
161
220
  });
221
+ return response.error
222
+ ? response
223
+ : { ...response, testInfo: testInfoForLoader };
162
224
  },
163
- isValidEditToken: (token) => packClient.isValidEditToken(token),
164
- getPackSessionData: () => {
165
- return {
166
- storeId: storeId,
167
- sessionId: session.id,
168
- isPreviewModeEnabled: previewEnabled,
169
- customizerMeta: session.get("customizerMeta"),
170
- };
171
- },
225
+ session,
172
226
  };
173
227
  }
@@ -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,CAanB"}
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,CAiBnB"}
@@ -1,12 +1,14 @@
1
1
  import { packlytics } from "@pack/packlytics";
2
2
  export async function handleRequest(pack, request, handleRequest) {
3
- const [response, _] = await Promise.all([
3
+ const packHandleResponse = await pack.handleRequest(request);
4
+ const [response] = await Promise.all([
4
5
  handleRequest(request),
5
6
  packlytics(request, pack.session, {
6
7
  storeFrontId: pack.getPackSessionData().storeId,
7
8
  sessionSecret: pack.session.secret,
8
9
  }),
9
10
  ]);
11
+ packHandleResponse(response);
10
12
  response.headers.append("powered-by", "Shopify, Hydrogen + Pack Digital");
11
13
  response.headers.append("Set-Cookie", await pack.session.commit());
12
14
  return response;
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ 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, };
7
8
  //# 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,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,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,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, };
@@ -0,0 +1,8 @@
1
+ import { Test } from "./test";
2
+ export declare function useAbTest(): Test | null;
3
+ export declare function useAbTestSessionId(): string;
4
+ export declare function useAbTestId(): string | undefined;
5
+ export declare function useAbTestHandle(): string | undefined;
6
+ export declare function useAbTestVariantId(): string | undefined;
7
+ export declare function useAbTestVariantHandle(): string | undefined;
8
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/tests/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B,wBAAgB,SAAS,IAAI,IAAI,GAAG,IAAI,CAWvC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAW3C;AAED,wBAAgB,WAAW,IAAI,MAAM,GAAG,SAAS,CAIhD;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,SAAS,CAIpD;AAED,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,SAAS,CAIvD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAI3D"}
@@ -0,0 +1,33 @@
1
+ import { useRouteLoaderData } from "@remix-run/react";
2
+ export function useAbTest() {
3
+ // @ts-ignore
4
+ const { abTest: test } = useRouteLoaderData("root");
5
+ if (test === undefined) {
6
+ throw new Error("No A/B test found, are you sure you have call ...pack.getPackSessionData() on root loader return?");
7
+ }
8
+ return test;
9
+ }
10
+ export function useAbTestSessionId() {
11
+ // @ts-ignore
12
+ const { sessionId } = useRouteLoaderData("root");
13
+ if (!sessionId) {
14
+ throw new Error("No Session ID found, are you sure you have call ...pack.getPackSessionData() on root loader return?");
15
+ }
16
+ return sessionId;
17
+ }
18
+ export function useAbTestId() {
19
+ const test = useAbTest();
20
+ return test?.id;
21
+ }
22
+ export function useAbTestHandle() {
23
+ const test = useAbTest();
24
+ return test?.handle;
25
+ }
26
+ export function useAbTestVariantId() {
27
+ const test = useAbTest();
28
+ return test?.testVariant?.id;
29
+ }
30
+ export function useAbTestVariantHandle() {
31
+ const test = useAbTest();
32
+ return test?.testVariant?.handle;
33
+ }
@@ -0,0 +1,6 @@
1
+ import { PackTestRoute } from "./pack-test-route";
2
+ import { PackTestContext, usePackTestContext } from "./pack-test-context";
3
+ import { PackTestProvider } from "./pack-test-provider";
4
+ import { useAbTest, useAbTestHandle, useAbTestId, useAbTestSessionId, useAbTestVariantHandle, useAbTestVariantId } from "./hooks";
5
+ export { PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tests/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,SAAS,EACT,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,GACvB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { PackTestRoute } from "./pack-test-route";
2
+ import { PackTestContext, usePackTestContext } from "./pack-test-context";
3
+ import { PackTestProvider } from "./pack-test-provider";
4
+ import { useAbTest, useAbTestHandle, useAbTestId, useAbTestSessionId, useAbTestVariantHandle, useAbTestVariantId, } from "./hooks";
5
+ export { PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
@@ -0,0 +1,15 @@
1
+ export interface Test {
2
+ id: string;
3
+ handle: string;
4
+ testVariant: {
5
+ id: string;
6
+ handle: string;
7
+ };
8
+ }
9
+ type PackTestContextValue = {
10
+ testExposureCallback?: (test: Test) => void;
11
+ };
12
+ export declare const PackTestContext: import("react").Context<PackTestContextValue>;
13
+ export declare const usePackTestContext: () => PackTestContextValue;
14
+ export {};
15
+ //# sourceMappingURL=pack-test-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack-test-context.d.ts","sourceRoot":"","sources":["../../src/tests/pack-test-context.ts"],"names":[],"mappings":"AAEA,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,KAAK,oBAAoB,GAAG;IAC1B,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC7C,CAAC;AAEF,eAAO,MAAM,eAAe,+CAA0C,CAAC;AAEvE,eAAO,MAAM,kBAAkB,4BAAoC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { createContext, useContext } from "react";
2
+ export const PackTestContext = createContext({});
3
+ export const usePackTestContext = () => useContext(PackTestContext);
@@ -0,0 +1,8 @@
1
+ import React, { PropsWithChildren } from "react";
2
+ import { Test } from "./pack-test-context";
3
+ interface PackContentProps {
4
+ testExposureCallback?: (test: Test) => void;
5
+ }
6
+ export declare function PackTestProvider({ children, testExposureCallback, }: PropsWithChildren<PackContentProps>): React.JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=pack-test-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack-test-provider.d.ts","sourceRoot":"","sources":["../../src/tests/pack-test-provider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACjD,OAAO,EAAmB,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE5D,UAAU,gBAAgB;IACxB,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC7C;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,oBAAoB,GACrB,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,qBAYrC"}
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import { PackTestContext } from "./pack-test-context";
3
+ export function PackTestProvider({ children, testExposureCallback, }) {
4
+ return (React.createElement(PackTestContext.Provider, { value: {
5
+ testExposureCallback: (test) => {
6
+ testExposureCallback && testExposureCallback(test);
7
+ },
8
+ } }, children));
9
+ }
@@ -0,0 +1,2 @@
1
+ export declare const PackTestRoute: () => null;
2
+ //# sourceMappingURL=pack-test-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack-test-route.d.ts","sourceRoot":"","sources":["../../src/tests/pack-test-route.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,aAAa,YAIzB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { useLoaderData } from "@remix-run/react";
2
+ import Cookies from "js-cookie";
3
+ import { useEffect } from "react";
4
+ import { useRevalidator } from "react-router-dom";
5
+ import { usePackTestContext } from "./pack-test-context";
6
+ const usePackLoaderData = () => {
7
+ const { testInfo } = useLoaderData();
8
+ const { testExposureCallback } = usePackTestContext();
9
+ const revalidator = useRevalidator();
10
+ const exposedTestCookieString = Cookies.get("exposedTest");
11
+ useEffect(() => {
12
+ if (testInfo && !exposedTestCookieString) {
13
+ // Set exposedTest cookie used by server to determine if session has been exposed to test
14
+ const expires = new Date();
15
+ expires.setHours(expires.getHours() + 24);
16
+ Cookies.set("exposedTest", JSON.stringify(testInfo), {
17
+ expires,
18
+ });
19
+ testExposureCallback?.(testInfo);
20
+ // TODO: This results in flashing of UI driven by site settings data.
21
+ // Need non-flashing solution.
22
+ revalidator.revalidate();
23
+ }
24
+ }, [testInfo, testExposureCallback]);
25
+ };
26
+ export const PackTestRoute = () => {
27
+ usePackLoaderData();
28
+ return null;
29
+ };
@@ -0,0 +1,45 @@
1
+ import { PackClient } from "@pack/client";
2
+ import { PackSession } from "../session/session";
3
+ export interface TestInput {
4
+ testId?: string;
5
+ testHandle?: string;
6
+ testVariantId?: string;
7
+ testVariantHandle?: string;
8
+ }
9
+ export interface Test {
10
+ id: string;
11
+ handle: string;
12
+ testVariant: {
13
+ id: string;
14
+ handle: string;
15
+ };
16
+ }
17
+ export interface TestTargetAudienceAttributes {
18
+ url?: string;
19
+ query?: string;
20
+ path?: string;
21
+ utmCampaign?: string;
22
+ utmContent?: string;
23
+ utmMedium?: string;
24
+ utmSource?: string;
25
+ utmTerm?: string;
26
+ }
27
+ export interface GetTestInfoOptions {
28
+ request: Request;
29
+ testTargetAudienceAttributes: TestTargetAudienceAttributes | null;
30
+ packClient: PackClient;
31
+ session: PackSession | undefined;
32
+ }
33
+ export interface TestInfo extends Test {
34
+ isFirstExposure?: boolean;
35
+ }
36
+ export declare function getTestInfo({ request, testTargetAudienceAttributes, packClient, session, }: GetTestInfoOptions): Promise<TestInfo | undefined>;
37
+ export declare function getTestSession(session: PackSession | undefined, testFromQueryParams: TestInput | null, previewEnabled: boolean): Test | null | undefined;
38
+ export declare function getTestTargetingAttributesFromRequest(request: Request): TestTargetAudienceAttributes;
39
+ export declare function getTestFromQueryParams(request: Request): TestInput | null;
40
+ export declare function setTestHeaders(headers: any, options: {
41
+ previewEnabled: boolean;
42
+ testInfoForRequest?: TestInfo;
43
+ testFromQueryParams?: TestInput;
44
+ }): any;
45
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +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,CAmHpD;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,OAmCF"}
@@ -0,0 +1,228 @@
1
+ import cookie from "cookie";
2
+ const QUERY_TEST_BY_RULES = `#graphql
3
+ query TestByRules($testTargetAudienceAttributes: JSON!) {
4
+ testByRules(attributes: $testTargetAudienceAttributes) {
5
+ id
6
+ handle
7
+ testVariant: TestVariant {
8
+ id
9
+ handle
10
+ }
11
+ }
12
+ }
13
+ `;
14
+ const QUERY_TEST_VARIANT_IS_RUNNING = `#graphql
15
+ query TestVariantIsRunning($handle: String!, $testId: ID!) {
16
+ testVariantIsRunning(handle: $handle, testId: $testId)
17
+ }
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;
27
+ }
28
+ const packClientFetchTestByRules = packClient.fetch(QUERY_TEST_BY_RULES, {
29
+ variables: {
30
+ testTargetAudienceAttributes,
31
+ },
32
+ });
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);
51
+ }
52
+ // 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
+ // If there is no assigned test on the session, check if there is a test
83
+ // for attributes, then assign to session and return value.
84
+ await fetchTestByRulesSetTestinfoAsFirstExposure();
85
+ }
86
+ else {
87
+ // If there is test on session, check if the test on session is still running.
88
+ const packClientFetchTestVariantIsRunning = packClient.fetch(QUERY_TEST_VARIANT_IS_RUNNING, {
89
+ variables: {
90
+ handle: testSession.data.testVariant.handle,
91
+ testId: testSession.data.id,
92
+ },
93
+ });
94
+ const resp = await packClientFetchTestVariantIsRunning;
95
+ if (!!resp.data.testVariantIsRunning) {
96
+ // If true, set testInfo to the testSession assigned test.
97
+ testInfo = testSession.data;
98
+ }
99
+ else {
100
+ // If not running, clear test session and fetch a new test for the user
101
+ session.set("test", undefined);
102
+ await fetchTestByRulesSetTestinfoAsFirstExposure();
103
+ }
104
+ }
105
+ }
106
+ if (isUsingCdn) {
107
+ // Return to CDN
108
+ packClient.apiUrl = "https://apicdn.packdigital.com/graphql";
109
+ }
110
+ }
111
+ return testInfo;
112
+ }
113
+ export function getTestSession(session, testFromQueryParams, previewEnabled) {
114
+ if (testFromQueryParams) {
115
+ return {
116
+ id: testFromQueryParams.testId || "",
117
+ handle: testFromQueryParams.testHandle || "",
118
+ testVariant: {
119
+ id: testFromQueryParams.testVariantId || "",
120
+ handle: testFromQueryParams.testVariantHandle || "",
121
+ },
122
+ };
123
+ }
124
+ if (previewEnabled) {
125
+ return {
126
+ id: "",
127
+ handle: "isPreview",
128
+ testVariant: {
129
+ id: "",
130
+ handle: "isPreview",
131
+ },
132
+ };
133
+ }
134
+ if (!session) {
135
+ return undefined;
136
+ }
137
+ const testSession = session.get("test");
138
+ return testSession?.data || null;
139
+ }
140
+ export function getTestTargetingAttributesFromRequest(request) {
141
+ let testTargetingAttributes = {};
142
+ const requestUrl = new URL(request.url);
143
+ const searchParams = requestUrl.searchParams;
144
+ testTargetingAttributes.url = requestUrl.origin + requestUrl.pathname;
145
+ testTargetingAttributes.query = requestUrl.search;
146
+ testTargetingAttributes.path = requestUrl.pathname;
147
+ searchParams.has("utm_campaign") &&
148
+ (testTargetingAttributes.utmCampaign =
149
+ searchParams.get("utm_campaign") || undefined);
150
+ searchParams.has("utm_content") &&
151
+ (testTargetingAttributes.utmContent =
152
+ searchParams.get("utm_content") || undefined);
153
+ searchParams.has("utm_medium") &&
154
+ (testTargetingAttributes.utmMedium =
155
+ searchParams.get("utm_medium") || undefined);
156
+ searchParams.has("utm_source") &&
157
+ (testTargetingAttributes.utmSource =
158
+ searchParams.get("utm_source") || undefined);
159
+ searchParams.has("utm_term") &&
160
+ (testTargetingAttributes.utmTerm =
161
+ searchParams.get("utm_term") || undefined);
162
+ return testTargetingAttributes;
163
+ }
164
+ export function getTestFromQueryParams(request) {
165
+ const requestUrl = new URL(request.url);
166
+ const params = requestUrl.searchParams;
167
+ if (params.has("testId") ||
168
+ params.has("test_id") ||
169
+ params.has("test-id") ||
170
+ params.has("testHandle") ||
171
+ params.has("test_handle") ||
172
+ params.has("test-handle") ||
173
+ params.has("testVariantId") ||
174
+ params.has("test_variant_id") ||
175
+ params.has("test-variant-id") ||
176
+ params.has("testVariantHandle") ||
177
+ params.has("test_variant_handle") ||
178
+ params.has("test-variant-handle")) {
179
+ return {
180
+ testId: params.get("testId") ||
181
+ params.get("test_id") ||
182
+ params.get("test-id") ||
183
+ "",
184
+ testHandle: params.get("testHandle") ||
185
+ params.get("test_handle") ||
186
+ params.get("test-handle") ||
187
+ "",
188
+ testVariantId: params.get("testVariantId") ||
189
+ params.get("test_variant_id") ||
190
+ params.get("test-variant-id") ||
191
+ "",
192
+ testVariantHandle: params.get("testVariantHandle") ||
193
+ params.get("test_variant_handle") ||
194
+ params.get("test-variant-handle") ||
195
+ "",
196
+ };
197
+ }
198
+ return null;
199
+ }
200
+ export function setTestHeaders(headers, options) {
201
+ const { previewEnabled, testFromQueryParams, testInfoForRequest } = options;
202
+ if (previewEnabled) {
203
+ return headers;
204
+ }
205
+ if (testFromQueryParams) {
206
+ headers["X-Pack-Test-Ignore-Test-Status"] = true;
207
+ if (testFromQueryParams.testId) {
208
+ headers["X-Pack-Test-Id"] = testFromQueryParams.testId;
209
+ }
210
+ if (testFromQueryParams.testHandle) {
211
+ headers["X-Pack-Test-Handle"] = testFromQueryParams.testHandle;
212
+ }
213
+ if (testFromQueryParams.testVariantId) {
214
+ headers["X-Pack-Test-Variant-Id"] = testFromQueryParams.testVariantId;
215
+ }
216
+ if (testFromQueryParams.testVariantHandle) {
217
+ headers["X-Pack-Test-Variant-Handle"] =
218
+ testFromQueryParams.testVariantHandle;
219
+ }
220
+ }
221
+ else if (testInfoForRequest) {
222
+ headers = {
223
+ "X-Pack-Test-Id": testInfoForRequest?.id,
224
+ "X-Pack-Test-Variant-Id": testInfoForRequest?.testVariant?.id,
225
+ };
226
+ }
227
+ return headers;
228
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pack/hydrogen",
3
3
  "description": "Pack Hydrogen",
4
- "version": "1.0.0",
4
+ "version": "1.0.1-ab-test.1",
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.0",
26
- "@pack/packlytics": "^1.0.0",
27
- "@shopify/hydrogen": "^2023.10.2"
25
+ "@pack/client": "^1.0.1-ab-test.0",
26
+ "@pack/packlytics": "^1.0.1-ab-test.0",
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": {