@pack/hydrogen 1.0.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "@pack/hydrogen",
3
3
  "description": "Pack Hydrogen",
4
- "version": "1.0.9",
5
- "exports": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
4
+ "version": "2.0.0",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.mts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "types": "./dist/index.d.mts",
7
13
  "engines": {
8
14
  "node": ">=16"
9
15
  },
@@ -12,7 +18,7 @@
12
18
  },
13
19
  "scripts": {
14
20
  "clean": "npx rimraf dist",
15
- "build": "npx tsc",
21
+ "build": "tsup",
16
22
  "test": "",
17
23
  "test:watch": "npx vitest"
18
24
  },
@@ -24,13 +30,18 @@
24
30
  "dependencies": {
25
31
  "@pack/client": "^1.0.2",
26
32
  "@pack/packlytics": "^1.0.1",
27
- "@shopify/hydrogen": "^2023.10.2"
33
+ "@shopify/hydrogen": "2025.1.3",
34
+ "cookie": "^0.6.0",
35
+ "js-cookie": "^3.0.5",
36
+ "json-rules-engine": "^4.0.0"
28
37
  },
29
38
  "devDependencies": {
30
39
  "@remix-run/server-runtime": "^2.0.0",
31
40
  "@shopify/oxygen-workers-types": "^4.0.0",
32
41
  "@shopify/remix-oxygen": "^2.0.1",
33
- "@types/node": "^20.11.17"
42
+ "@types/js-cookie": "^3.0.6",
43
+ "@types/node": "^20.11.17",
44
+ "tsup": "^8.5.0"
34
45
  },
35
46
  "peerDependencies": {
36
47
  "react": "^18.0.0"
@@ -1,4 +0,0 @@
1
- export declare const PACK_COOKIE_ID = "__pack";
2
- export declare const PACK_USER_CONSENT_COOKIE_ID = "__pack_user_consent";
3
- export declare const PACK_COOKIE_MAX_AGE: number;
4
- //# sourceMappingURL=constants.d.ts.map
@@ -1 +0,0 @@
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"}
package/dist/constants.js DELETED
@@ -1,3 +0,0 @@
1
- export const PACK_COOKIE_ID = "__pack";
2
- export const PACK_USER_CONSENT_COOKIE_ID = "__pack_user_consent";
3
- export const PACK_COOKIE_MAX_AGE = 60 * 60 * 24 * 360; // 1 year
@@ -1,60 +0,0 @@
1
- /// <reference types="@shopify/oxygen-workers-types" />
2
- import { PackClient } from "@pack/client";
3
- import { CacheCustom } from "@shopify/hydrogen";
4
- import { PackSession } from "./session/session";
5
- /** @see https://shopify.dev/docs/custom-storefronts/hydrogen/data-fetching/cache#caching-strategies */
6
- type CachingStrategy = ReturnType<typeof CacheCustom>;
7
- interface EnvironmentOptions {
8
- /**
9
- * A Cache API instance.
10
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Cache
11
- */
12
- cache: Cache;
13
- /**
14
- * A runtime utility for serverless environments
15
- * @see https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#waituntil
16
- */
17
- waitUntil: ExecutionContext["waitUntil"];
18
- }
19
- interface CreatePackClientOptions extends EnvironmentOptions {
20
- apiUrl?: string;
21
- token?: string;
22
- storeId?: string;
23
- session: PackSession;
24
- contentEnvironment?: string;
25
- /** Default theme data to use when no token is provided */
26
- defaultThemeData?: DefaultThemeData;
27
- }
28
- type Variables = Record<string, any>;
29
- interface QueryOptions {
30
- variables?: Variables;
31
- cache?: CachingStrategy;
32
- }
33
- interface QueryError {
34
- message: string;
35
- param?: string;
36
- code?: string;
37
- type: string;
38
- }
39
- interface QueryResponse<T> {
40
- data: T | null;
41
- error: QueryError | null;
42
- }
43
- export interface Pack {
44
- isPreviewModeEnabled: () => boolean;
45
- session: PackSession;
46
- query: <T = any>(query: string, options?: QueryOptions) => Promise<QueryResponse<T>>;
47
- isValidEditToken: PackClient["isValidEditToken"];
48
- getPackSessionData(): {
49
- storeId: string;
50
- sessionId: string;
51
- isPreviewModeEnabled: boolean;
52
- customizerMeta: any;
53
- };
54
- }
55
- interface DefaultThemeData {
56
- data: Record<string, any>;
57
- }
58
- export declare function createPackClient(options: CreatePackClientOptions): Pack;
59
- export {};
60
- //# sourceMappingURL=create-pack-client.d.ts.map
@@ -1 +0,0 @@
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,214 +0,0 @@
1
- import { PackClient } from "@pack/client";
2
- import { CacheCustom, createWithCache } from "@shopify/hydrogen";
3
- /**
4
- * Create an SHA-256 hash as a hex string
5
- * @see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
6
- */
7
- async function sha256(message) {
8
- // encode as UTF-8
9
- const messageBuffer = new TextEncoder().encode(message);
10
- // hash the message
11
- const hashBuffer = await crypto.subtle.digest("SHA-256", messageBuffer);
12
- // convert bytes to hex string
13
- return Array.from(new Uint8Array(hashBuffer))
14
- .map((b) => b.toString(16).padStart(2, "0"))
15
- .join("");
16
- }
17
- /**
18
- * Hash query and its parameters for use as cache key.
19
- * NOTE: Oxygen deployment will break if the cache key is long or contains `\n`
20
- */
21
- function hashQuery(query, variables, headers) {
22
- let hash = query;
23
- if (variables !== null)
24
- hash += JSON.stringify(variables);
25
- if (headers !== null)
26
- hash += JSON.stringify(headers);
27
- return sha256(hash);
28
- }
29
- async function getCacheKey(withCache, query, token, options) {
30
- let queryHash = await hashQuery(query, options?.variables, options?.headers);
31
- let publishedAt;
32
- try {
33
- const resp = await withCache("pack:content:publishedAt", CacheCustom({
34
- maxAge: 15,
35
- staleWhileRevalidate: 15,
36
- }), async () => {
37
- const URL = "https://cache-check-production.packdigital.workers.dev/published-at";
38
- const resp = await fetch(URL, {
39
- headers: { Authorization: `Bearer ${token}` },
40
- });
41
- const { status } = resp;
42
- if (status !== 200) {
43
- let message;
44
- if (status === 401) {
45
- message =
46
- "Pack error: Unauthorized request to cache check service. Please check your token.";
47
- }
48
- else {
49
- message = `Pack error: Request to cache check service failed with status ${status}`;
50
- }
51
- console.error(message);
52
- return null;
53
- }
54
- return resp.json();
55
- });
56
- publishedAt = resp?.publishedAt;
57
- }
58
- catch (err) {
59
- console.error(err);
60
- }
61
- if (publishedAt) {
62
- return `${queryHash}:${publishedAt}`;
63
- }
64
- return queryHash;
65
- }
66
- // Extends the CacheLong strategy with a stale-if-error policy
67
- const cacheCustom = CacheCustom({
68
- maxAge: 3600,
69
- staleWhileRevalidate: 82800,
70
- staleIfError: 86400, // 1 day
71
- });
72
- /** Resolves the default data for a given query based on the theme config */
73
- function resolveQuery({ query, variables, defaultThemeData, }) {
74
- const queryField = extractTopLevelField(query);
75
- const identifier = variables?.handle || variables?.id;
76
- const defaultDataForField = defaultThemeData.data[queryField];
77
- if (!defaultDataForField) {
78
- return null;
79
- }
80
- return {
81
- [queryField]: identifier
82
- ? defaultDataForField[identifier]
83
- : defaultDataForField,
84
- };
85
- }
86
- /**
87
- * Extracts the top-level field from a GraphQL query string.
88
- *
89
- * The function assumes that the query follows the standard GraphQL syntax
90
- * and expects the top-level field to be the first field in the query.
91
- *
92
- * @example
93
- * const query = `
94
- * query GetProduct($id: ID!) {
95
- * product(id: $id) {
96
- * id
97
- * name
98
- * }
99
- * }
100
- * `;
101
- * const topLevelField = extractTopLevelField(query);
102
- * console.log(topLevelField); // Output: "product"
103
- */
104
- function extractTopLevelField(query) {
105
- const match = query.match(/\{[^{]*?\w+(?=\(|:|\s*\{)/);
106
- if (match) {
107
- return match[0].slice(1).trim();
108
- }
109
- return "";
110
- }
111
- export function createPackClient(options) {
112
- const { cache, waitUntil, session, contentEnvironment, storeId, token, apiUrl, defaultThemeData, } = options;
113
- const previewEnabled = !!session.get("previewEnabled");
114
- const previewEnvironment = session.get("environment");
115
- const clientContentEnvironment = previewEnvironment || contentEnvironment;
116
- if (!token && !defaultThemeData) {
117
- throw new Error("ERR_HY_MISSING_TOKEN: The Pack client token is missing or empty. Please provide a valid token or default theme data. Doc: https://docs.packdigital.com/err/ERR_HY_MISSING_TOKEN");
118
- }
119
- if (!storeId) {
120
- throw new Error("ERR_HY_MISSING_STORE_ID: The Pack Store ID is missing or empty. Please provide a valid Store ID. Doc: https://docs.packdigital.com/err/ERR_HY_MISSING_STORE_ID");
121
- }
122
- if (!token) {
123
- return {
124
- session,
125
- isPreviewModeEnabled: () => previewEnabled,
126
- async query(query, { variables } = {}) {
127
- if (!defaultThemeData?.data) {
128
- console.warn("Invalid default theme data provided to Pack client.");
129
- return { data: null, error: null };
130
- }
131
- const data = resolveQuery({ query, variables, defaultThemeData });
132
- return { data: data, error: null };
133
- },
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
- },
143
- };
144
- }
145
- const packClient = new PackClient({
146
- // Use apiUrl, it is configured
147
- // Use active API URL if preview mode is enabled
148
- // Otherwise, Live PackClient uses its internal configuration
149
- apiUrl: apiUrl
150
- ? apiUrl
151
- : previewEnabled || process.env.NODE_ENV === "development"
152
- ? "https://app.packdigital.com/graphql"
153
- : undefined,
154
- storeId,
155
- token,
156
- contentEnvironment: clientContentEnvironment,
157
- sessionId: session.id,
158
- clientName: "HydrogenClient",
159
- });
160
- return {
161
- session,
162
- isPreviewModeEnabled: () => previewEnabled,
163
- async query(query, { variables, cache: strategy = cacheCustom } = {}) {
164
- const withCache = createWithCache({
165
- cache,
166
- waitUntil,
167
- });
168
- let headers = {};
169
- const queryVariables = variables ? { ...variables } : {};
170
- if (previewEnabled) {
171
- queryVariables.version = "CURRENT";
172
- }
173
- else {
174
- queryVariables.version = "PUBLISHED";
175
- }
176
- // Preview mode always bypasses the cache
177
- if (previewEnabled) {
178
- try {
179
- return await packClient.fetch(query, {
180
- variables: queryVariables,
181
- headers: headers,
182
- });
183
- }
184
- catch (error) {
185
- return { error, data: {} };
186
- }
187
- }
188
- const queryHash = await getCacheKey(withCache, query, token, {
189
- variables: queryVariables,
190
- headers,
191
- });
192
- return withCache(queryHash, strategy, async () => {
193
- try {
194
- return await packClient.fetch(query, {
195
- variables: queryVariables,
196
- headers: headers,
197
- });
198
- }
199
- catch (error) {
200
- return { error, data: {} };
201
- }
202
- });
203
- },
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
- },
213
- };
214
- }
@@ -1,3 +0,0 @@
1
- import { Pack } from "./create-pack-client";
2
- export declare function handleRequest(pack: Pack, request: Request, handleRequest: (request: Request) => Promise<Response>): Promise<Response>;
3
- //# sourceMappingURL=handle-request.d.ts.map
@@ -1 +0,0 @@
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,9 +0,0 @@
1
- import { packlytics } from "@pack/packlytics";
2
- export async function handleRequest(pack, request, handleRequest) {
3
- const response = await packlytics(pack, request, () => {
4
- return handleRequest(request);
5
- });
6
- response.headers.append("powered-by", "Shopify, Hydrogen + Pack Digital");
7
- response.headers.append("Set-Cookie", await pack.session.commit());
8
- return response;
9
- }
@@ -1 +0,0 @@
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,27 +0,0 @@
1
- import { type ActionFunction, type LoaderFunction } from "@remix-run/server-runtime";
2
- type JsonFunction = <Data>(data: Data, init?: number | ResponseInit) => TypedResponse<Data>;
3
- type RedirectFunction = (url: string, init?: number | ResponseInit) => Response;
4
- type TypedResponse<T = unknown> = Omit<Response, "json"> & {
5
- json(): Promise<T>;
6
- };
7
- /**
8
- * This is a shortcut for creating `application/json` responses. Converts `data`
9
- * to JSON and sets the `Content-Type` header.
10
- */
11
- export declare const json: JsonFunction;
12
- /**
13
- * A redirect response. Sets the status code and the `Location` header.
14
- * Defaults to "302 Found".
15
- */
16
- export declare const redirect: RedirectFunction;
17
- /**
18
- * A `POST` request to this route will exit preview mode
19
- * POST /api/edit Content-Type: application/x-www-form-urlencoded
20
- */
21
- export declare const action: ActionFunction;
22
- /**
23
- * A `GET` request to this route will enter preview mode
24
- */
25
- export declare const loader: LoaderFunction;
26
- export {};
27
- //# sourceMappingURL=preview-mode.d.ts.map
@@ -1 +0,0 @@
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,cAcpB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,MAAM,EAAE,cAqCpB,CAAC"}
@@ -1,108 +0,0 @@
1
- const ROOT_PATH = "/";
2
- /**
3
- * This is a shortcut for creating `application/json` responses. Converts `data`
4
- * to JSON and sets the `Content-Type` header.
5
- */
6
- export const json = (data, init = {}) => {
7
- let responseInit = typeof init === "number" ? { status: init } : init;
8
- let headers = new Headers(responseInit.headers);
9
- if (!headers.has("Content-Type")) {
10
- headers.set("Content-Type", "application/json; charset=utf-8");
11
- }
12
- return new Response(JSON.stringify(data), {
13
- ...responseInit,
14
- headers,
15
- });
16
- };
17
- /**
18
- * A redirect response. Sets the status code and the `Location` header.
19
- * Defaults to "302 Found".
20
- */
21
- export const redirect = (url, init = 302) => {
22
- let responseInit = init;
23
- if (typeof responseInit === "number") {
24
- responseInit = { status: responseInit };
25
- }
26
- else if (typeof responseInit.status === "undefined") {
27
- responseInit.status = 302;
28
- }
29
- let headers = new Headers(responseInit.headers);
30
- headers.set("Location", url);
31
- return new Response(null, {
32
- ...responseInit,
33
- headers,
34
- });
35
- };
36
- /**
37
- * A not found response. Sets the status code.
38
- */
39
- function notFound(message = "Not Found") {
40
- return new Response(message, { status: 404, statusText: "Not Found" });
41
- }
42
- function isLocalPath(request, url) {
43
- // Our domain, based on the current request path
44
- const currentUrl = new URL(request.url);
45
- // If url is relative, the 2nd argument will act as the base domain.
46
- const urlToCheck = new URL(url, currentUrl.origin);
47
- // If the origins don't match the slug is not on our domain.
48
- return currentUrl.origin === urlToCheck.origin;
49
- }
50
- /**
51
- * A `POST` request to this route will exit preview mode
52
- * POST /api/edit Content-Type: application/x-www-form-urlencoded
53
- */
54
- export const action = async ({ request, context }) => {
55
- const { session } = context.pack;
56
- if (!(request.method === "POST" && session)) {
57
- return json({ message: "Method not allowed" }, 405);
58
- }
59
- const body = await request.formData();
60
- const slug = body.get("slug") ?? ROOT_PATH;
61
- const redirectTo = isLocalPath(request, slug) ? slug : ROOT_PATH;
62
- await session.destroy();
63
- return redirect(redirectTo);
64
- };
65
- /**
66
- * A `GET` request to this route will enter preview mode
67
- */
68
- export const loader = async function ({ request, context }) {
69
- const { pack } = context;
70
- if (!pack.session)
71
- return notFound();
72
- const { searchParams } = new URL(request.url);
73
- const token = searchParams.get("token");
74
- const environment = searchParams.get("environment");
75
- const path = searchParams.get("path") ?? ROOT_PATH;
76
- const customizerMeta = searchParams.get("customizerMeta");
77
- const redirectTo = isLocalPath(request, path) ? path : ROOT_PATH;
78
- if (!searchParams.has("token")) {
79
- throw new MissingTokenError();
80
- }
81
- const validatedToken = await pack.isValidEditToken(token);
82
- if (!validatedToken) {
83
- throw new InvalidTokenError();
84
- }
85
- let customizerMetaJson;
86
- try {
87
- customizerMetaJson = customizerMeta ? JSON.parse(customizerMeta) : null;
88
- }
89
- catch (_error) {
90
- customizerMetaJson = null;
91
- }
92
- pack.session.set("previewEnabled", true);
93
- pack.session.set("environment", environment);
94
- pack.session.set("customizerMeta", customizerMetaJson);
95
- return redirect(redirectTo, {
96
- status: 307
97
- });
98
- };
99
- class MissingTokenError extends Response {
100
- constructor() {
101
- super("Missing token", { status: 401, statusText: "Unauthorized" });
102
- }
103
- }
104
- class InvalidTokenError extends Response {
105
- constructor() {
106
- super("Invalid token", { status: 401, statusText: "Unauthorized" });
107
- }
108
- }
@@ -1,13 +0,0 @@
1
- export declare function hasUserConsent(request: Request): boolean;
2
- /**
3
- * This will generate a random UUID
4
- * This is not based on user data,
5
- * This is based on a random number generator using crypto or math.random
6
- *
7
- * This UUID is used to generate a session ID for the session cookie
8
- *
9
- * We can never use this id to trace back users for GDPR compliance.
10
- *
11
- */
12
- export declare function buildRandomUUID(): string;
13
- //# sourceMappingURL=cookies-utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cookies-utils.d.ts","sourceRoot":"","sources":["../../src/session/cookies-utils.tsx"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,WAM9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,MAAM,CA0BxC"}
@@ -1,62 +0,0 @@
1
- import { PACK_USER_CONSENT_COOKIE_ID } from "../constants";
2
- import cookie from "cookie";
3
- const tokenHash = "xxxx-4xxx-xxxx-xxxxxxxxxxxx";
4
- export function hasUserConsent(request) {
5
- const cookies = cookie.parse(request.headers.get("Cookie"));
6
- return cookies[PACK_USER_CONSENT_COOKIE_ID]
7
- ? cookies[PACK_USER_CONSENT_COOKIE_ID] === "true"
8
- : true;
9
- }
10
- /**
11
- * This will generate a random UUID
12
- * This is not based on user data,
13
- * This is based on a random number generator using crypto or math.random
14
- *
15
- * This UUID is used to generate a session ID for the session cookie
16
- *
17
- * We can never use this id to trace back users for GDPR compliance.
18
- *
19
- */
20
- export function buildRandomUUID() {
21
- let hash = "";
22
- try {
23
- const crypto = window.crypto;
24
- const randomValuesArray = new Uint16Array(31);
25
- crypto.getRandomValues(randomValuesArray);
26
- // Generate a strong UUID
27
- let i = 0;
28
- hash = tokenHash.replace(/[x]/g, (c) => {
29
- const r = randomValuesArray[i] % 16;
30
- const v = c === "x" ? r : (r & 0x3) | 0x8;
31
- i++;
32
- return v.toString(16);
33
- });
34
- }
35
- catch (err) {
36
- // crypto not available, generate weak UUID
37
- hash = tokenHash.replace(/[x]/g, (c) => {
38
- const r = (Math.random() * 16) | 0;
39
- const v = c === "x" ? r : (r & 0x3) | 0x8;
40
- return v.toString(16);
41
- });
42
- }
43
- return `${hexTime()}-${hash}`;
44
- }
45
- function hexTime() {
46
- // 32 bit representations of new Date().getTime() and performance.now()
47
- let dateNumber = 0;
48
- let perfNumber = 0;
49
- // Result of zero-fill right shift is always positive
50
- dateNumber = new Date().getTime() >>> 0;
51
- try {
52
- perfNumber = performance.now() >>> 0;
53
- }
54
- catch (err) {
55
- perfNumber = 0;
56
- }
57
- const output = Math.abs(dateNumber + perfNumber)
58
- .toString(16)
59
- .toLowerCase();
60
- // Ensure the output is exactly 8 characters
61
- return output?.substring(0, 8).padStart(8, "0");
62
- }
@@ -1,14 +0,0 @@
1
- import { type Session, type SessionStorage } from "@shopify/remix-oxygen";
2
- export declare class PackSession {
3
- #private;
4
- readonly id: string;
5
- readonly secret: string;
6
- constructor(id: string, secret: string, sessionStorage: SessionStorage, session: Session);
7
- static init(request: Request, secrets: string[]): Promise<PackSession>;
8
- has(key: string): boolean;
9
- get(key: string): any;
10
- destroy(): Promise<string>;
11
- set(key: string, value: any): void;
12
- commit(): Promise<string>;
13
- }
14
- //# sourceMappingURL=session.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/session/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAI/B,qBAAa,WAAW;;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IAEpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAOtB,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,OAAO;WAQL,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAsB5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,GAAG,CAAC,GAAG,EAAE,MAAM;IAIf,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAQ1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAIlC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAG1B"}
@@ -1,50 +0,0 @@
1
- import { createCookie, createCookieSessionStorage, } from "@shopify/remix-oxygen";
2
- import { PACK_COOKIE_ID, PACK_COOKIE_MAX_AGE } from "../constants";
3
- import { buildRandomUUID, hasUserConsent } from "./cookies-utils";
4
- export class PackSession {
5
- id;
6
- secret;
7
- #session;
8
- #sessionStorage;
9
- constructor(id, secret, sessionStorage, session) {
10
- this.id = id;
11
- this.secret = secret;
12
- this.#session = session;
13
- this.#sessionStorage = sessionStorage;
14
- }
15
- static async init(request, secrets) {
16
- const storage = createCookieSessionStorage({
17
- cookie: createCookie(PACK_COOKIE_ID, {
18
- secure: true,
19
- secrets,
20
- sameSite: "none",
21
- maxAge: PACK_COOKIE_MAX_AGE,
22
- }),
23
- });
24
- const session = await storage.getSession(request.headers.get("Cookie"));
25
- let sessionId = session.get("session_id");
26
- if (!sessionId || !hasUserConsent(request)) {
27
- sessionId = buildRandomUUID();
28
- session.set("session_id", sessionId);
29
- }
30
- return new this(sessionId, secrets[0], storage, session);
31
- }
32
- has(key) {
33
- return this.#session.has(key);
34
- }
35
- get(key) {
36
- return this.#session.get(key);
37
- }
38
- destroy() {
39
- for (const key of Object.keys(this.#session.data)) {
40
- this.#session.unset(key);
41
- }
42
- return this.#sessionStorage.destroySession(this.#session);
43
- }
44
- set(key, value) {
45
- this.#session.set(key, value);
46
- }
47
- commit() {
48
- return this.#sessionStorage.commitSession(this.#session);
49
- }
50
- }
@@ -1,15 +0,0 @@
1
- type UsePackCookiesOptions = {
2
- /**
3
- * If set to `false`, Shopify cookies will be removed.
4
- * If set to `true`, Shopify unique user token cookie will have cookie expiry of 1 year.
5
- * Defaults to false.
6
- **/
7
- hasUserConsent?: boolean;
8
- /**
9
- * The domain scope of the cookie. Defaults to empty string.
10
- **/
11
- domain?: string;
12
- };
13
- export declare function usePackCookies(options?: UsePackCookiesOptions): void;
14
- export {};
15
- //# sourceMappingURL=usePackCookies.d.ts.map