@pack/hydrogen 1.0.6-ab-test.9 → 1.0.6-ab-test.11
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/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/create-pack-client.d.ts +3 -0
- package/dist/create-pack-client.d.ts.map +1 -1
- package/dist/create-pack-client.js +13 -9
- package/dist/handle-request.d.ts.map +1 -1
- package/dist/handle-request.js +6 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/session/test-session.d.ts +18 -0
- package/dist/session/test-session.d.ts.map +1 -0
- package/dist/session/test-session.js +97 -0
- package/dist/tests/test.d.ts +4 -4
- package/dist/tests/test.d.ts.map +1 -1
- package/dist/tests/test.js +49 -42
- package/package.json +1 -1
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const PACK_COOKIE_ID = "__pack";
|
|
2
2
|
export declare const PACK_USER_CONSENT_COOKIE_ID = "__pack_user_consent";
|
|
3
|
+
export declare const PACK_TEST_COOKIE_ID = "__pack_test";
|
|
3
4
|
export declare const PACK_COOKIE_MAX_AGE: number;
|
|
4
5
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,WAAW,CAAC;AACvC,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AACjE,eAAO,MAAM,mBAAmB,QAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,WAAW,CAAC;AACvC,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AACjE,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AACjD,eAAO,MAAM,mBAAmB,QAAqB,CAAC"}
|
package/dist/constants.js
CHANGED
|
@@ -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 { PackTestSession } from "./session/test-session";
|
|
5
6
|
import { Test, TestInput } from "./tests/test";
|
|
6
7
|
/** @see https://shopify.dev/docs/custom-storefronts/hydrogen/data-fetching/cache#caching-strategies */
|
|
7
8
|
type CachingStrategy = ReturnType<typeof CacheCustom>;
|
|
@@ -22,6 +23,7 @@ export interface CreatePackClientOptions extends EnvironmentOptions {
|
|
|
22
23
|
token?: string;
|
|
23
24
|
storeId?: string;
|
|
24
25
|
session: PackSession;
|
|
26
|
+
testSession: PackTestSession;
|
|
25
27
|
contentEnvironment?: string;
|
|
26
28
|
/** Default theme data to use when no token is provided */
|
|
27
29
|
defaultThemeData?: DefaultThemeData;
|
|
@@ -75,6 +77,7 @@ export interface Pack {
|
|
|
75
77
|
isValidEditToken: PackClient["isValidEditToken"];
|
|
76
78
|
query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
|
|
77
79
|
session: PackSession;
|
|
80
|
+
testSession: PackTestSession;
|
|
78
81
|
}
|
|
79
82
|
interface DefaultThemeData {
|
|
80
83
|
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;AAEjE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAML,IAAI,EACJ,SAAS,EAEV,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,YAAY,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,IAAI;IACnB,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC;;OAEG;IACH,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,kBAAkB,IAAI;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;QACpC,iBAAiB,EAAE,OAAO,CAAC;QAC3B,kBAAkB,EAAE,kBAAkB,GAAG,IAAI,CAAC;KAC/C,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;
|
|
1
|
+
{"version":3,"file":"create-pack-client.d.ts","sourceRoot":"","sources":["../src/create-pack-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -111,7 +111,7 @@ function extractTopLevelField(query) {
|
|
|
111
111
|
return "";
|
|
112
112
|
}
|
|
113
113
|
export function createPackClient(options) {
|
|
114
|
-
const { cache, waitUntil, session, contentEnvironment, storeId, token, apiUrl, defaultThemeData, } = options;
|
|
114
|
+
const { cache, waitUntil, session, testSession, contentEnvironment, storeId, token, apiUrl, defaultThemeData, } = options;
|
|
115
115
|
const previewEnabled = !!session.get("previewEnabled");
|
|
116
116
|
const previewEnvironment = session.get("environment");
|
|
117
117
|
const clientContentEnvironment = previewEnvironment || contentEnvironment;
|
|
@@ -132,7 +132,7 @@ export function createPackClient(options) {
|
|
|
132
132
|
request,
|
|
133
133
|
testTargetAudienceAttributes,
|
|
134
134
|
packClient,
|
|
135
|
-
session
|
|
135
|
+
testSession, // Use dedicated test session instead
|
|
136
136
|
token,
|
|
137
137
|
});
|
|
138
138
|
}
|
|
@@ -142,8 +142,10 @@ export function createPackClient(options) {
|
|
|
142
142
|
?.includes("exposedTest");
|
|
143
143
|
if (hasExposedTestCookie) {
|
|
144
144
|
// Clear the exposedTest cookie
|
|
145
|
-
response.headers.
|
|
145
|
+
response.headers.append("Set-Cookie", cookie.serialize("exposedTest", "", {
|
|
146
146
|
maxAge: 0,
|
|
147
|
+
expires: new Date(0),
|
|
148
|
+
path: "/",
|
|
147
149
|
}));
|
|
148
150
|
}
|
|
149
151
|
};
|
|
@@ -158,13 +160,13 @@ export function createPackClient(options) {
|
|
|
158
160
|
};
|
|
159
161
|
if (!token) {
|
|
160
162
|
return {
|
|
161
|
-
abTest: getTestSession(
|
|
163
|
+
abTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
162
164
|
handleRequest,
|
|
163
165
|
getPackSessionData: () => {
|
|
164
166
|
return {
|
|
165
167
|
storeId: storeId,
|
|
166
168
|
sessionId: session.id,
|
|
167
|
-
abTest: getTestSession(
|
|
169
|
+
abTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
168
170
|
isPreviewModeEnabled: previewEnabled,
|
|
169
171
|
customizerMeta: session.get("customizerMeta"),
|
|
170
172
|
};
|
|
@@ -173,7 +175,7 @@ export function createPackClient(options) {
|
|
|
173
175
|
return {
|
|
174
176
|
packStoreId: storeId,
|
|
175
177
|
packSessionId: session.id,
|
|
176
|
-
packAbTest: getTestSession(
|
|
178
|
+
packAbTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
177
179
|
packIsPreviewMode: previewEnabled,
|
|
178
180
|
packCustomizerMeta: session.get("customizerMeta"),
|
|
179
181
|
};
|
|
@@ -189,6 +191,7 @@ export function createPackClient(options) {
|
|
|
189
191
|
return { data: data, error: null };
|
|
190
192
|
},
|
|
191
193
|
session,
|
|
194
|
+
testSession,
|
|
192
195
|
};
|
|
193
196
|
}
|
|
194
197
|
packClient = new PackClient({
|
|
@@ -207,12 +210,12 @@ export function createPackClient(options) {
|
|
|
207
210
|
clientName: "HydrogenClient",
|
|
208
211
|
});
|
|
209
212
|
return {
|
|
210
|
-
abTest: getTestSession(
|
|
213
|
+
abTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
211
214
|
getPackSessionData: () => {
|
|
212
215
|
return {
|
|
213
216
|
storeId: storeId,
|
|
214
217
|
sessionId: session.id,
|
|
215
|
-
abTest: getTestSession(
|
|
218
|
+
abTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
216
219
|
isPreviewModeEnabled: previewEnabled,
|
|
217
220
|
customizerMeta: session.get("customizerMeta"),
|
|
218
221
|
};
|
|
@@ -221,7 +224,7 @@ export function createPackClient(options) {
|
|
|
221
224
|
return {
|
|
222
225
|
packStoreId: storeId,
|
|
223
226
|
packSessionId: session.id,
|
|
224
|
-
packAbTest: getTestSession(
|
|
227
|
+
packAbTest: getTestSession(testSession, testFromQueryParams, previewEnabled),
|
|
225
228
|
packIsPreviewMode: previewEnabled,
|
|
226
229
|
packCustomizerMeta: session.get("customizerMeta"),
|
|
227
230
|
};
|
|
@@ -283,5 +286,6 @@ export function createPackClient(options) {
|
|
|
283
286
|
: { ...response, packTestInfo: testInfoForLoader };
|
|
284
287
|
},
|
|
285
288
|
session,
|
|
289
|
+
testSession,
|
|
286
290
|
};
|
|
287
291
|
}
|
|
@@ -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,
|
|
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,CAqBnB"}
|
package/dist/handle-request.js
CHANGED
|
@@ -6,6 +6,12 @@ export async function handleRequest(pack, request, handleRequest) {
|
|
|
6
6
|
});
|
|
7
7
|
packHandleResponse(response);
|
|
8
8
|
response.headers.append("powered-by", "Shopify, Hydrogen + Pack Digital");
|
|
9
|
+
// Always commit main session
|
|
9
10
|
response.headers.append("Set-Cookie", await pack.session.commit());
|
|
11
|
+
// Only commit test session if there are actual changes
|
|
12
|
+
// This reduces the number of Set-Cookie headers and potential race conditions
|
|
13
|
+
if (pack.testSession.hasChanges()) {
|
|
14
|
+
response.headers.append("Set-Cookie", await pack.testSession.commit());
|
|
15
|
+
}
|
|
10
16
|
return response;
|
|
11
17
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { handleRequest } from "./handle-request";
|
|
2
2
|
import { usePackCookies } from "./session/usePackCookies";
|
|
3
3
|
import { PackSession } from "./session/session";
|
|
4
|
+
import { PackTestSession } from "./session/test-session";
|
|
4
5
|
import { Pack, createPackClient } from "./create-pack-client";
|
|
5
6
|
import { action as previewModeAction, loader as previewModeLoader } from "./preview/preview-mode";
|
|
6
7
|
import { PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle } from "./tests";
|
|
7
8
|
export type { Pack };
|
|
8
|
-
export { PackSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
|
|
9
|
+
export { PackSession, PackTestSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,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"}
|
|
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,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,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,eAAe,EACf,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
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { handleRequest } from "./handle-request";
|
|
2
2
|
import { usePackCookies } from "./session/usePackCookies";
|
|
3
3
|
import { PackSession } from "./session/session";
|
|
4
|
+
import { PackTestSession } from "./session/test-session";
|
|
4
5
|
import { createPackClient } from "./create-pack-client";
|
|
5
6
|
import { action as previewModeAction, loader as previewModeLoader, } from "./preview/preview-mode";
|
|
6
7
|
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, };
|
|
8
|
+
export { PackSession, PackTestSession, createPackClient, handleRequest, previewModeAction, previewModeLoader, usePackCookies, PackTestContext, usePackTestContext, PackTestProvider, PackTestRoute, useAbTest, useAbTestId, useAbTestHandle, useAbTestSessionId, useAbTestVariantId, useAbTestVariantHandle, };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Session, type SessionStorage } from "@shopify/remix-oxygen";
|
|
2
|
+
import { Test } from "../tests/test";
|
|
3
|
+
export declare class PackTestSession {
|
|
4
|
+
#private;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
constructor(id: string, sessionStorage: SessionStorage, session: Session);
|
|
7
|
+
static init(request: Request, secrets: string[]): Promise<PackTestSession>;
|
|
8
|
+
getTestData(): Test | undefined;
|
|
9
|
+
getExpireAt(): string | undefined;
|
|
10
|
+
setTestData(testData: Test | undefined): void;
|
|
11
|
+
setExpireAt(expireAt: string | undefined): void;
|
|
12
|
+
clearTestData(): void;
|
|
13
|
+
hasTestData(): boolean;
|
|
14
|
+
hasChanges(): boolean;
|
|
15
|
+
commit(): Promise<string>;
|
|
16
|
+
destroy(): Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=test-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-session.d.ts","sourceRoot":"","sources":["../../src/session/test-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAerC,qBAAa,eAAe;;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAMR,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO;WAY3D,IAAI,CACf,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,eAAe,CAAC;IA2B3B,WAAW,IAAI,IAAI,GAAG,SAAS;IAI/B,WAAW,IAAI,MAAM,GAAG,SAAS;IAIjC,WAAW,CAAC,QAAQ,EAAE,IAAI,GAAG,SAAS,GAAG,IAAI;IAK7C,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAK/C,aAAa,IAAI,IAAI;IAMrB,WAAW,IAAI,OAAO;IAItB,UAAU,IAAI,OAAO;IAerB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAazB,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;CAM3B"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createCookie, createCookieSessionStorage, } from "@shopify/remix-oxygen";
|
|
2
|
+
import { PACK_COOKIE_MAX_AGE, PACK_TEST_COOKIE_ID } from "../constants";
|
|
3
|
+
function isSafari(userAgent) {
|
|
4
|
+
if (!userAgent)
|
|
5
|
+
return false;
|
|
6
|
+
return (/Safari\//.test(userAgent) &&
|
|
7
|
+
!/Chrome\//.test(userAgent) &&
|
|
8
|
+
!/Chromium\//.test(userAgent) &&
|
|
9
|
+
!/Edg\//.test(userAgent) &&
|
|
10
|
+
!/Firefox\//.test(userAgent));
|
|
11
|
+
}
|
|
12
|
+
export class PackTestSession {
|
|
13
|
+
id;
|
|
14
|
+
#session;
|
|
15
|
+
#sessionStorage;
|
|
16
|
+
#isDirty = false;
|
|
17
|
+
#initialData = {};
|
|
18
|
+
constructor(id, sessionStorage, session) {
|
|
19
|
+
this.id = id;
|
|
20
|
+
this.#session = session;
|
|
21
|
+
this.#sessionStorage = sessionStorage;
|
|
22
|
+
// Store initial state to track changes
|
|
23
|
+
this.#initialData = {
|
|
24
|
+
test_data: this.#session.get("test_data"),
|
|
25
|
+
test_expire_at: this.#session.get("test_expire_at"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
static async init(request, secrets) {
|
|
29
|
+
const userAgent = request.headers.get("User-Agent");
|
|
30
|
+
const storage = createCookieSessionStorage({
|
|
31
|
+
cookie: createCookie(PACK_TEST_COOKIE_ID, {
|
|
32
|
+
secure: isSafari(userAgent) && process.env.NODE_ENV === "development"
|
|
33
|
+
? false
|
|
34
|
+
: true,
|
|
35
|
+
secrets,
|
|
36
|
+
sameSite: "lax",
|
|
37
|
+
maxAge: PACK_COOKIE_MAX_AGE,
|
|
38
|
+
path: "/",
|
|
39
|
+
}),
|
|
40
|
+
});
|
|
41
|
+
const session = await storage.getSession(request.headers.get("Cookie"));
|
|
42
|
+
let sessionId = session.get("test_session_id");
|
|
43
|
+
if (!sessionId) {
|
|
44
|
+
sessionId = crypto.randomUUID();
|
|
45
|
+
session.set("test_session_id", sessionId);
|
|
46
|
+
}
|
|
47
|
+
return new this(sessionId, storage, session);
|
|
48
|
+
}
|
|
49
|
+
getTestData() {
|
|
50
|
+
return this.#session.get("test_data");
|
|
51
|
+
}
|
|
52
|
+
getExpireAt() {
|
|
53
|
+
return this.#session.get("test_expire_at");
|
|
54
|
+
}
|
|
55
|
+
setTestData(testData) {
|
|
56
|
+
this.#isDirty = true;
|
|
57
|
+
this.#session.set("test_data", testData);
|
|
58
|
+
}
|
|
59
|
+
setExpireAt(expireAt) {
|
|
60
|
+
this.#isDirty = true;
|
|
61
|
+
this.#session.set("test_expire_at", expireAt);
|
|
62
|
+
}
|
|
63
|
+
clearTestData() {
|
|
64
|
+
this.#isDirty = true;
|
|
65
|
+
this.#session.unset("test_data");
|
|
66
|
+
this.#session.unset("test_expire_at");
|
|
67
|
+
}
|
|
68
|
+
hasTestData() {
|
|
69
|
+
return this.#session.has("test_data");
|
|
70
|
+
}
|
|
71
|
+
hasChanges() {
|
|
72
|
+
// Check if data has actually changed
|
|
73
|
+
const currentTestData = this.getTestData();
|
|
74
|
+
const currentExpireAt = this.getExpireAt();
|
|
75
|
+
// Compare with initial state
|
|
76
|
+
const testDataChanged = JSON.stringify(currentTestData) !==
|
|
77
|
+
JSON.stringify(this.#initialData.test_data);
|
|
78
|
+
const expireAtChanged = currentExpireAt !== this.#initialData.test_expire_at;
|
|
79
|
+
return testDataChanged || expireAtChanged || this.#isDirty;
|
|
80
|
+
}
|
|
81
|
+
commit() {
|
|
82
|
+
// Reset dirty flag after commit
|
|
83
|
+
this.#isDirty = false;
|
|
84
|
+
// Update initial data to current state
|
|
85
|
+
this.#initialData = {
|
|
86
|
+
test_data: this.getTestData(),
|
|
87
|
+
test_expire_at: this.getExpireAt(),
|
|
88
|
+
};
|
|
89
|
+
return this.#sessionStorage.commitSession(this.#session);
|
|
90
|
+
}
|
|
91
|
+
destroy() {
|
|
92
|
+
this.#isDirty = true;
|
|
93
|
+
this.#session.unset("test_data");
|
|
94
|
+
this.#session.unset("test_expire_at");
|
|
95
|
+
return this.#sessionStorage.destroySession(this.#session);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/tests/test.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PackClient } from "@pack/client";
|
|
2
|
-
import {
|
|
2
|
+
import { PackTestSession } from "../session/test-session";
|
|
3
3
|
export interface TestInput {
|
|
4
4
|
testId?: string;
|
|
5
5
|
testHandle?: string;
|
|
@@ -28,14 +28,14 @@ export interface GetTestInfoOptions {
|
|
|
28
28
|
request: Request;
|
|
29
29
|
testTargetAudienceAttributes: TestTargetAudienceAttributes | null;
|
|
30
30
|
packClient: PackClient;
|
|
31
|
-
|
|
31
|
+
testSession: PackTestSession;
|
|
32
32
|
token: string;
|
|
33
33
|
}
|
|
34
34
|
export interface TestInfo extends Test {
|
|
35
35
|
isFirstExposure?: boolean;
|
|
36
36
|
}
|
|
37
|
-
export declare function getTestInfo({ request, testTargetAudienceAttributes, packClient,
|
|
38
|
-
export declare function getTestSession(
|
|
37
|
+
export declare function getTestInfo({ request, testTargetAudienceAttributes, packClient, testSession, token, }: GetTestInfoOptions): Promise<TestInfo | undefined>;
|
|
38
|
+
export declare function getTestSession(testSession?: PackTestSession, testFromQueryParams?: TestInput | null, previewEnabled?: boolean): Test | null | undefined;
|
|
39
39
|
export declare function getTestTargetingAttributesFromRequest(request: Request): TestTargetAudienceAttributes;
|
|
40
40
|
export declare function getTestFromQueryParams(request: Request): TestInput | null;
|
|
41
41
|
export declare function setTestHeaders(headers: any, options: {
|
package/dist/tests/test.d.ts.map
CHANGED
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../src/tests/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG1D,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;AAQD,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,4BAA4B,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAClE,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,eAAe,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAS,SAAQ,IAAI;IACpC,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AA2FD,wBAAsB,WAAW,CAAC,EAChC,OAAO,EACP,4BAA4B,EAC5B,UAAU,EACV,WAAW,EACX,KAAK,GACN,EAAE,kBAAkB,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CA2GpD;AAED,wBAAgB,cAAc,CAC5B,WAAW,CAAC,EAAE,eAAe,EAC7B,mBAAmB,GAAE,SAAS,GAAG,IAAW,EAC5C,cAAc,GAAE,OAAe,GAC9B,IAAI,GAAG,IAAI,GAAG,SAAS,CA+BzB;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"}
|
package/dist/tests/test.js
CHANGED
|
@@ -69,18 +69,41 @@ function getExpireAtDate() {
|
|
|
69
69
|
expireAt.setHours(expireAt.getHours() + 4); // 4 hours
|
|
70
70
|
return expireAt;
|
|
71
71
|
}
|
|
72
|
-
export async function getTestInfo({ request, testTargetAudienceAttributes, packClient,
|
|
72
|
+
export async function getTestInfo({ request, testTargetAudienceAttributes, packClient, testSession, token, }) {
|
|
73
73
|
let testInfo = undefined;
|
|
74
74
|
let exposedTest = undefined;
|
|
75
|
-
if (!
|
|
75
|
+
if (!testSession) {
|
|
76
76
|
return testInfo;
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
// Retrieve simplified test data and expiry
|
|
79
|
+
const testSessionData = testSession.getTestData();
|
|
80
|
+
const testSessionExpireAt = testSession.getExpireAt();
|
|
79
81
|
const exposedTestCookieString = cookie.parse(request.headers.get("cookie") || "")?.exposedTest;
|
|
82
|
+
// If we already have a valid test in the session, use it without setting isFirstExposure
|
|
83
|
+
if (testSessionData?.id && testSessionData?.testVariant) {
|
|
84
|
+
// First check if test hasn't expired by time
|
|
85
|
+
if (!testSessionExpireAt || new Date(testSessionExpireAt) >= new Date()) {
|
|
86
|
+
// Then verify the test is still running in the system
|
|
87
|
+
try {
|
|
88
|
+
const testStillRunning = await packClientCheckTestIsRunning(packClient, testSessionData);
|
|
89
|
+
if (testStillRunning) {
|
|
90
|
+
return testSessionData;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Don't clear session on API error, just log it
|
|
95
|
+
console.error("Error checking if test is running, using cached data", error);
|
|
96
|
+
return testSessionData; // Still use the cached data if API fails
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// If we reach here, the test is either expired or not running
|
|
100
|
+
// Clear the session data
|
|
101
|
+
testSession.clearTestData();
|
|
102
|
+
}
|
|
80
103
|
if (exposedTestCookieString) {
|
|
81
104
|
exposedTest = JSON.parse(exposedTestCookieString);
|
|
82
105
|
}
|
|
83
|
-
if (exposedTest && !
|
|
106
|
+
if (exposedTest && !testSessionData) {
|
|
84
107
|
// If there is no assigned test on the session and an exposed test in the
|
|
85
108
|
// incoming request cookie it means that the user was exposed to a test previously.
|
|
86
109
|
// Verify test is still running
|
|
@@ -93,51 +116,34 @@ export async function getTestInfo({ request, testTargetAudienceAttributes, packC
|
|
|
93
116
|
handle,
|
|
94
117
|
testVariant,
|
|
95
118
|
};
|
|
119
|
+
// Store simplified data
|
|
96
120
|
// Only set the test to the session on exposure
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
expireAt: getExpireAtDate(),
|
|
100
|
-
});
|
|
121
|
+
testSession.setTestData(testInfo);
|
|
122
|
+
testSession.setExpireAt(getExpireAtDate().toISOString());
|
|
101
123
|
}
|
|
102
124
|
else {
|
|
103
|
-
// If the test is not running, fetch a new test for the client
|
|
125
|
+
// If the test is not running, clear session and fetch a new test for the client
|
|
126
|
+
testSession.clearTestData();
|
|
127
|
+
// Then fetch new test
|
|
104
128
|
testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
|
|
105
129
|
}
|
|
106
130
|
}
|
|
131
|
+
else if (testSessionData) {
|
|
132
|
+
// This case handles if testSessionData exists but might be expired (handled above)
|
|
133
|
+
// or if there was no exposedTest cookie. We already returned if it was valid.
|
|
134
|
+
// If logic reaches here and testSessionData exists, it implies it was expired.
|
|
135
|
+
// First clear the session
|
|
136
|
+
testSession.clearTestData();
|
|
137
|
+
// Then fetch new test
|
|
138
|
+
testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
|
|
139
|
+
}
|
|
107
140
|
else {
|
|
108
|
-
// If there is no exposed test
|
|
109
|
-
|
|
110
|
-
// If there is a test on session, check if the test on session has expired
|
|
111
|
-
if (!testSession.expireAt ||
|
|
112
|
-
new Date(testSession.expireAt) < new Date()) {
|
|
113
|
-
// If there is test on session, check if the test on session is still running
|
|
114
|
-
const testVariantIsRunning = await packClientCheckTestIsRunning(packClient, testSession.data);
|
|
115
|
-
// If true, set testInfo to the testSession assigned test.
|
|
116
|
-
if (testVariantIsRunning) {
|
|
117
|
-
testInfo = testSession.data;
|
|
118
|
-
session.set("test", {
|
|
119
|
-
data: testInfo,
|
|
120
|
-
expireAt: getExpireAtDate(),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// If not running, clear test session and fetch a new test for the client
|
|
125
|
-
session.set("test", undefined);
|
|
126
|
-
testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
testInfo = testSession.data;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
// If there is no test on session and no exposed test, check for a new test for client
|
|
135
|
-
testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
|
|
136
|
-
}
|
|
141
|
+
// If there is no test on session and no exposed test, check for a new test for client
|
|
142
|
+
testInfo = await packClientFetchTestByRules(packClient, testTargetAudienceAttributes, token);
|
|
137
143
|
}
|
|
138
144
|
return testInfo;
|
|
139
145
|
}
|
|
140
|
-
export function getTestSession(
|
|
146
|
+
export function getTestSession(testSession, testFromQueryParams = null, previewEnabled = false) {
|
|
141
147
|
if (testFromQueryParams) {
|
|
142
148
|
return {
|
|
143
149
|
id: testFromQueryParams.testId || "",
|
|
@@ -158,11 +164,12 @@ export function getTestSession(session, testFromQueryParams, previewEnabled) {
|
|
|
158
164
|
},
|
|
159
165
|
};
|
|
160
166
|
}
|
|
161
|
-
if (!
|
|
167
|
+
if (!testSession) {
|
|
162
168
|
return undefined;
|
|
163
169
|
}
|
|
164
|
-
|
|
165
|
-
|
|
170
|
+
// Read simplified test data from the dedicated test session
|
|
171
|
+
const testSessionData = testSession.getTestData();
|
|
172
|
+
return testSessionData || null;
|
|
166
173
|
}
|
|
167
174
|
export function getTestTargetingAttributesFromRequest(request) {
|
|
168
175
|
let testTargetingAttributes = {};
|