@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/dist/index.d.ts +205 -7
- package/dist/index.js +6662 -6
- package/dist/index.js.map +1 -0
- package/package.json +17 -6
- package/dist/constants.d.ts +0 -4
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -3
- package/dist/create-pack-client.d.ts +0 -60
- package/dist/create-pack-client.d.ts.map +0 -1
- package/dist/create-pack-client.js +0 -214
- package/dist/handle-request.d.ts +0 -3
- package/dist/handle-request.d.ts.map +0 -1
- package/dist/handle-request.js +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/preview/preview-mode.d.ts +0 -27
- package/dist/preview/preview-mode.d.ts.map +0 -1
- package/dist/preview/preview-mode.js +0 -108
- package/dist/session/cookies-utils.d.ts +0 -13
- package/dist/session/cookies-utils.d.ts.map +0 -1
- package/dist/session/cookies-utils.js +0 -62
- package/dist/session/session.d.ts +0 -14
- package/dist/session/session.d.ts.map +0 -1
- package/dist/session/session.js +0 -50
- package/dist/session/usePackCookies.d.ts +0 -15
- package/dist/session/usePackCookies.d.ts.map +0 -1
- package/dist/session/usePackCookies.js +0 -17
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pack/hydrogen",
|
|
3
3
|
"description": "Pack Hydrogen",
|
|
4
|
-
"version": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
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": "
|
|
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": "
|
|
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/
|
|
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"
|
package/dist/constants.d.ts
DELETED
package/dist/constants.d.ts.map
DELETED
|
@@ -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,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
|
-
}
|
package/dist/handle-request.d.ts
DELETED
|
@@ -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"}
|
package/dist/handle-request.js
DELETED
|
@@ -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
|
-
}
|
package/dist/index.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/session/session.js
DELETED
|
@@ -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
|