@shopbb/helium 0.5.10 → 0.6.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.
- package/dist/cache/withCache.d.ts +49 -0
- package/dist/cache/withCache.d.ts.map +1 -0
- package/dist/cache/withCache.js +117 -0
- package/dist/cache/withCache.js.map +1 -0
- package/dist/components/AddToCartButton.d.ts +28 -22
- package/dist/components/AddToCartButton.d.ts.map +1 -1
- package/dist/components/AddToCartButton.js +36 -47
- package/dist/components/AddToCartButton.js.map +1 -1
- package/dist/components/BuyNowButton.d.ts +45 -0
- package/dist/components/BuyNowButton.d.ts.map +1 -0
- package/dist/components/BuyNowButton.js +49 -0
- package/dist/components/BuyNowButton.js.map +1 -0
- package/dist/components/CartCheckoutButton.d.ts +39 -0
- package/dist/components/CartCheckoutButton.d.ts.map +1 -0
- package/dist/components/CartCheckoutButton.js +32 -0
- package/dist/components/CartCheckoutButton.js.map +1 -0
- package/dist/components/CartCost.d.ts +43 -0
- package/dist/components/CartCost.d.ts.map +1 -0
- package/dist/components/CartCost.js +34 -0
- package/dist/components/CartCost.js.map +1 -0
- package/dist/components/CartForm.d.ts +201 -0
- package/dist/components/CartForm.d.ts.map +1 -0
- package/dist/components/CartForm.js +213 -0
- package/dist/components/CartForm.js.map +1 -0
- package/dist/components/CartLineProvider.d.ts +78 -0
- package/dist/components/CartLineProvider.d.ts.map +1 -0
- package/dist/components/CartLineProvider.js +46 -0
- package/dist/components/CartLineProvider.js.map +1 -0
- package/dist/components/CartLineQuantity.d.ts +24 -0
- package/dist/components/CartLineQuantity.d.ts.map +1 -0
- package/dist/components/CartLineQuantity.js +9 -0
- package/dist/components/CartLineQuantity.js.map +1 -0
- package/dist/components/DiscountSelector.d.ts.map +1 -1
- package/dist/components/DiscountSelector.js +8 -19
- package/dist/components/DiscountSelector.js.map +1 -1
- package/dist/components/Image.d.ts +18 -0
- package/dist/components/Image.d.ts.map +1 -1
- package/dist/components/Image.js +26 -0
- package/dist/components/Image.js.map +1 -1
- package/dist/components/Pagination.d.ts +82 -0
- package/dist/components/Pagination.d.ts.map +1 -0
- package/dist/components/Pagination.js +84 -0
- package/dist/components/Pagination.js.map +1 -0
- package/dist/components/RichText.d.ts +78 -0
- package/dist/components/RichText.d.ts.map +1 -0
- package/dist/components/RichText.js +93 -0
- package/dist/components/RichText.js.map +1 -0
- package/dist/components/Seo.d.ts +25 -0
- package/dist/components/Seo.d.ts.map +1 -0
- package/dist/components/Seo.js +54 -0
- package/dist/components/Seo.js.map +1 -0
- package/dist/components/hooks/useMoney.d.ts +40 -0
- package/dist/components/hooks/useMoney.d.ts.map +1 -0
- package/dist/components/hooks/useMoney.js +60 -0
- package/dist/components/hooks/useMoney.js.map +1 -0
- package/dist/components/hooks/useOptimisticCart.d.ts +50 -0
- package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -0
- package/dist/components/hooks/useOptimisticCart.js +146 -0
- package/dist/components/hooks/useOptimisticCart.js.map +1 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +21 -0
- package/dist/components/index.js.map +1 -1
- package/dist/createCartHandler.d.ts.map +1 -1
- package/dist/createCartHandler.js +57 -0
- package/dist/createCartHandler.js.map +1 -1
- package/dist/csp/csp.d.ts +57 -0
- package/dist/csp/csp.d.ts.map +1 -0
- package/dist/csp/csp.js +73 -0
- package/dist/csp/csp.js.map +1 -0
- package/dist/customer/createCustomerAccountClient.d.ts +43 -0
- package/dist/customer/createCustomerAccountClient.d.ts.map +1 -0
- package/dist/customer/createCustomerAccountClient.js +68 -0
- package/dist/customer/createCustomerAccountClient.js.map +1 -0
- package/dist/handleCartFormAction.d.ts +39 -0
- package/dist/handleCartFormAction.d.ts.map +1 -0
- package/dist/handleCartFormAction.js +103 -0
- package/dist/handleCartFormAction.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/routing/storefrontRedirect.d.ts +37 -0
- package/dist/routing/storefrontRedirect.d.ts.map +1 -0
- package/dist/routing/storefrontRedirect.js +64 -0
- package/dist/routing/storefrontRedirect.js.map +1 -0
- package/dist/seo/getSeoMeta.d.ts +68 -0
- package/dist/seo/getSeoMeta.d.ts.map +1 -0
- package/dist/seo/getSeoMeta.js +89 -0
- package/dist/seo/getSeoMeta.js.map +1 -0
- package/dist/sitemap/sitemap.d.ts +55 -0
- package/dist/sitemap/sitemap.d.ts.map +1 -0
- package/dist/sitemap/sitemap.js +93 -0
- package/dist/sitemap/sitemap.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/flattenConnection.d.ts +25 -0
- package/dist/utils/flattenConnection.d.ts.map +1 -0
- package/dist/utils/flattenConnection.js +25 -0
- package/dist/utils/flattenConnection.js.map +1 -0
- package/dist/utils/parseGid.d.ts +17 -0
- package/dist/utils/parseGid.d.ts.map +1 -0
- package/dist/utils/parseGid.js +19 -0
- package/dist/utils/parseGid.js.map +1 -0
- package/package.json +1 -1
- package/src/cache/withCache.ts +144 -0
- package/src/components/AddToCartButton.tsx +94 -56
- package/src/components/BuyNowButton.tsx +135 -0
- package/src/components/CartCheckoutButton.tsx +97 -0
- package/src/components/CartCost.tsx +65 -0
- package/src/components/CartForm.tsx +311 -0
- package/src/components/CartLineProvider.tsx +77 -0
- package/src/components/CartLineQuantity.tsx +37 -0
- package/src/components/DiscountSelector.tsx +34 -45
- package/src/components/Image.tsx +27 -0
- package/src/components/Pagination.tsx +139 -0
- package/src/components/RichText.tsx +122 -0
- package/src/components/Seo.tsx +61 -0
- package/src/components/hooks/useMoney.ts +87 -0
- package/src/components/hooks/useOptimisticCart.ts +183 -0
- package/src/components/index.ts +44 -0
- package/src/createCartHandler.ts +71 -0
- package/src/csp/csp.tsx +119 -0
- package/src/customer/createCustomerAccountClient.ts +89 -0
- package/src/handleCartFormAction.ts +129 -0
- package/src/index.ts +24 -0
- package/src/routing/storefrontRedirect.ts +86 -0
- package/src/seo/getSeoMeta.ts +125 -0
- package/src/sitemap/sitemap.ts +121 -0
- package/src/types.ts +12 -1
- package/src/utils/flattenConnection.ts +33 -0
- package/src/utils/parseGid.ts +25 -0
package/dist/csp/csp.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* createContentSecurityPolicy + NonceProvider + <Script> + useNonce()
|
|
4
|
+
*
|
|
5
|
+
* 对齐 Hydrogen 的 CSP 工具集:服务端生成一次性 nonce(base64 random),通过
|
|
6
|
+
* Content-Security-Policy 头要求所有 inline script 带这个 nonce 才能执行;
|
|
7
|
+
* React 组件 <Script nonce> 自动注入 nonce 属性。
|
|
8
|
+
*
|
|
9
|
+
* 服务端:
|
|
10
|
+
* const { nonce, header, NonceProvider } = createContentSecurityPolicy();
|
|
11
|
+
* response.headers.set('Content-Security-Policy', header);
|
|
12
|
+
* return ReactDOMServer.renderToReadableStream(
|
|
13
|
+
* <NonceProvider value={nonce}><App /></NonceProvider>
|
|
14
|
+
* );
|
|
15
|
+
*
|
|
16
|
+
* 客户端 / 组件内:
|
|
17
|
+
* const nonce = useNonce();
|
|
18
|
+
* <script nonce={nonce} dangerouslySetInnerHTML={...} />
|
|
19
|
+
* // 或者用 <Script>
|
|
20
|
+
* <Script>{`window.foo=1`}</Script>
|
|
21
|
+
*/
|
|
22
|
+
import * as React from 'react';
|
|
23
|
+
// ============================================================
|
|
24
|
+
// Nonce context
|
|
25
|
+
// ============================================================
|
|
26
|
+
const NonceContext = React.createContext(null);
|
|
27
|
+
export function NonceProvider({ value, children }) {
|
|
28
|
+
return _jsx(NonceContext.Provider, { value: value, children: children });
|
|
29
|
+
}
|
|
30
|
+
export function useNonce() {
|
|
31
|
+
const nonce = React.useContext(NonceContext);
|
|
32
|
+
return nonce || undefined;
|
|
33
|
+
}
|
|
34
|
+
export function Script(props) {
|
|
35
|
+
const nonce = useNonce();
|
|
36
|
+
return _jsx("script", { nonce: nonce, ...props });
|
|
37
|
+
}
|
|
38
|
+
function randomNonce() {
|
|
39
|
+
// Workers / 浏览器 / Node 都有 crypto.getRandomValues
|
|
40
|
+
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
41
|
+
const bytes = new Uint8Array(16);
|
|
42
|
+
crypto.getRandomValues(bytes);
|
|
43
|
+
let bin = '';
|
|
44
|
+
for (const b of bytes)
|
|
45
|
+
bin += String.fromCharCode(b);
|
|
46
|
+
return btoa(bin).replace(/[+/=]/g, '');
|
|
47
|
+
}
|
|
48
|
+
// fallback
|
|
49
|
+
return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
|
|
50
|
+
}
|
|
51
|
+
export function createContentSecurityPolicy(options = {}) {
|
|
52
|
+
const nonce = randomNonce();
|
|
53
|
+
const dirs = [];
|
|
54
|
+
dirs.push(`default-src ${(options.defaultSrc || ["'self'"]).join(' ')}`);
|
|
55
|
+
dirs.push(`script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${(options.scriptSrc || []).join(' ')}`.trim());
|
|
56
|
+
dirs.push(`style-src 'self' 'unsafe-inline' ${(options.styleSrc || []).join(' ')}`.trim());
|
|
57
|
+
dirs.push(`img-src 'self' data: blob: ${(options.imgSrc || []).join(' ')}`.trim());
|
|
58
|
+
if (options.connectSrc?.length)
|
|
59
|
+
dirs.push(`connect-src 'self' ${options.connectSrc.join(' ')}`);
|
|
60
|
+
if (options.fontSrc?.length)
|
|
61
|
+
dirs.push(`font-src 'self' ${options.fontSrc.join(' ')}`);
|
|
62
|
+
if (options.frameSrc?.length)
|
|
63
|
+
dirs.push(`frame-src ${options.frameSrc.join(' ')}`);
|
|
64
|
+
dirs.push(`base-uri 'self'`);
|
|
65
|
+
dirs.push(`form-action 'self'`);
|
|
66
|
+
return {
|
|
67
|
+
nonce,
|
|
68
|
+
header: dirs.join('; '),
|
|
69
|
+
headerName: options.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy',
|
|
70
|
+
NonceProvider,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=csp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csp.js","sourceRoot":"","sources":["../../src/csp/csp.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createCustomerAccountClient — 对齐 Hydrogen createCustomerAccountClient
|
|
3
|
+
*
|
|
4
|
+
* 给"买家账号"操作提供 GraphQL client + 简单 token 管理。
|
|
5
|
+
*
|
|
6
|
+
* Hydrogen 用 Customer Account API(OAuth + PKCE);我们 shopbb 用简化的
|
|
7
|
+
* buyer JWT(service signed token),所以这里**不实现 OAuth login/logout**,
|
|
8
|
+
* 只提供 GraphQL query/mutate 和 isLoggedIn 检测。商家用 oxygen 提供的
|
|
9
|
+
* REST `/api/buyer/login` 完成登录,把 token 存进 localStorage。
|
|
10
|
+
*
|
|
11
|
+
* 用法(客户端):
|
|
12
|
+
*
|
|
13
|
+
* const customer = createCustomerAccountClient({
|
|
14
|
+
* customerApiUrl: 'https://api.../customer/api/2026-04/graphql',
|
|
15
|
+
* getAccessToken: () => localStorage.getItem('shopbb:buyer_token'),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const data = await customer.query(`{ customer { id email } }`);
|
|
19
|
+
* await customer.mutate(`mutation { customerAddressCreate(...) { ... } }`);
|
|
20
|
+
* const loggedIn = await customer.isLoggedIn();
|
|
21
|
+
*
|
|
22
|
+
* 与 Hydrogen 区别:
|
|
23
|
+
* - getAccessToken 默认从 localStorage / cookie 取(商家可覆盖)
|
|
24
|
+
* - 不带 OAuth redirect helper(commerce 简化)
|
|
25
|
+
* - login / logout 由商家应用层负责(调 oxygen REST)
|
|
26
|
+
*/
|
|
27
|
+
export interface CustomerAccountClientOptions {
|
|
28
|
+
/** Customer GraphQL endpoint */
|
|
29
|
+
customerApiUrl: string;
|
|
30
|
+
/** Token 取法。不传时从 localStorage 取 'shopbb:buyer_token'。SSR 阶段可传 cookie 解析 */
|
|
31
|
+
getAccessToken?: () => string | null | Promise<string | null>;
|
|
32
|
+
/** 自定义 headers(如 store_id) */
|
|
33
|
+
extraHeaders?: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
export interface CustomerAccountClient {
|
|
36
|
+
query<T = any>(query: string, variables?: Record<string, any>): Promise<T>;
|
|
37
|
+
mutate<T = any>(mutation: string, variables?: Record<string, any>): Promise<T>;
|
|
38
|
+
isLoggedIn(): Promise<boolean>;
|
|
39
|
+
getAccessToken(): Promise<string | null>;
|
|
40
|
+
getApiUrl(): string;
|
|
41
|
+
}
|
|
42
|
+
export declare function createCustomerAccountClient(options: CustomerAccountClientOptions): CustomerAccountClient;
|
|
43
|
+
//# sourceMappingURL=createCustomerAccountClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCustomerAccountClient.d.ts","sourceRoot":"","sources":["../../src/customer/createCustomerAccountClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,4BAA4B;IAC3C,gCAAgC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/E,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/B,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,SAAS,IAAI,MAAM,CAAC;CACrB;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,4BAA4B,GAAG,qBAAqB,CA4CxG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createCustomerAccountClient — 对齐 Hydrogen createCustomerAccountClient
|
|
3
|
+
*
|
|
4
|
+
* 给"买家账号"操作提供 GraphQL client + 简单 token 管理。
|
|
5
|
+
*
|
|
6
|
+
* Hydrogen 用 Customer Account API(OAuth + PKCE);我们 shopbb 用简化的
|
|
7
|
+
* buyer JWT(service signed token),所以这里**不实现 OAuth login/logout**,
|
|
8
|
+
* 只提供 GraphQL query/mutate 和 isLoggedIn 检测。商家用 oxygen 提供的
|
|
9
|
+
* REST `/api/buyer/login` 完成登录,把 token 存进 localStorage。
|
|
10
|
+
*
|
|
11
|
+
* 用法(客户端):
|
|
12
|
+
*
|
|
13
|
+
* const customer = createCustomerAccountClient({
|
|
14
|
+
* customerApiUrl: 'https://api.../customer/api/2026-04/graphql',
|
|
15
|
+
* getAccessToken: () => localStorage.getItem('shopbb:buyer_token'),
|
|
16
|
+
* });
|
|
17
|
+
*
|
|
18
|
+
* const data = await customer.query(`{ customer { id email } }`);
|
|
19
|
+
* await customer.mutate(`mutation { customerAddressCreate(...) { ... } }`);
|
|
20
|
+
* const loggedIn = await customer.isLoggedIn();
|
|
21
|
+
*
|
|
22
|
+
* 与 Hydrogen 区别:
|
|
23
|
+
* - getAccessToken 默认从 localStorage / cookie 取(商家可覆盖)
|
|
24
|
+
* - 不带 OAuth redirect helper(commerce 简化)
|
|
25
|
+
* - login / logout 由商家应用层负责(调 oxygen REST)
|
|
26
|
+
*/
|
|
27
|
+
export function createCustomerAccountClient(options) {
|
|
28
|
+
const { customerApiUrl, getAccessToken: getter, extraHeaders } = options;
|
|
29
|
+
async function getAccessToken() {
|
|
30
|
+
if (getter)
|
|
31
|
+
return await getter();
|
|
32
|
+
if (typeof localStorage === 'undefined')
|
|
33
|
+
return null;
|
|
34
|
+
return localStorage.getItem('shopbb:buyer_token') || localStorage.getItem('shopflare:buyer_token');
|
|
35
|
+
}
|
|
36
|
+
async function call(body, variables) {
|
|
37
|
+
const token = await getAccessToken();
|
|
38
|
+
if (!token)
|
|
39
|
+
throw new Error('not authenticated');
|
|
40
|
+
const headers = {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'Authorization': `Bearer ${token}`,
|
|
43
|
+
...(extraHeaders || {}),
|
|
44
|
+
};
|
|
45
|
+
const res = await fetch(customerApiUrl, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers,
|
|
48
|
+
body: JSON.stringify({ query: body, variables }),
|
|
49
|
+
});
|
|
50
|
+
const json = await res.json().catch(() => null);
|
|
51
|
+
if (!res.ok || json?.errors?.length) {
|
|
52
|
+
throw new Error(json?.errors?.[0]?.message || `Customer API error: HTTP ${res.status}`);
|
|
53
|
+
}
|
|
54
|
+
return json.data;
|
|
55
|
+
}
|
|
56
|
+
async function isLoggedIn() {
|
|
57
|
+
const token = await getAccessToken();
|
|
58
|
+
return !!token;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
query: (q, v) => call(q, v),
|
|
62
|
+
mutate: (m, v) => call(m, v),
|
|
63
|
+
isLoggedIn,
|
|
64
|
+
getAccessToken,
|
|
65
|
+
getApiUrl: () => customerApiUrl,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=createCustomerAccountClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createCustomerAccountClient.js","sourceRoot":"","sources":["../../src/customer/createCustomerAccountClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAmBH,MAAM,UAAU,2BAA2B,CAAC,OAAqC;IAC/E,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC;IAEzE,KAAK,UAAU,cAAc;QAC3B,IAAI,MAAM;YAAE,OAAO,MAAM,MAAM,EAAE,CAAC;QAClC,IAAI,OAAO,YAAY,KAAK,WAAW;YAAE,OAAO,IAAI,CAAC;QACrD,OAAO,YAAY,CAAC,OAAO,CAAC,oBAAoB,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IACrG,CAAC;IAED,KAAK,UAAU,IAAI,CACjB,IAAY,EACZ,SAA+B;QAE/B,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAC;QACrC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACjD,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,UAAU,KAAK,EAAE;YAClC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;SACxB,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;SACjD,CAAC,CAAC;QACH,MAAM,IAAI,GAAQ,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,4BAA4B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,IAAI,CAAC,IAAS,CAAC;IACxB,CAAC;IAED,KAAK,UAAU,UAAU;QACvB,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAC;QACrC,OAAO,CAAC,CAAC,KAAK,CAAC;IACjB,CAAC;IAED,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC3B,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAC5B,UAAU;QACV,cAAc;QACd,SAAS,EAAE,GAAG,EAAE,CAAC,cAAc;KAChC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handleCartFormAction
|
|
3
|
+
*
|
|
4
|
+
* 服务端统一处理 <CartForm> 提交的 helper。对齐 Hydrogen 的"单一 /cart route action"模式:
|
|
5
|
+
*
|
|
6
|
+
* import { CartForm, handleCartFormAction } from '@shopbb/helium';
|
|
7
|
+
*
|
|
8
|
+
* // shopflare server.tsx POST handler
|
|
9
|
+
* if (url.pathname === '/cart' && request.method === 'POST') {
|
|
10
|
+
* const ctx = createHeliumContext({ ... });
|
|
11
|
+
* const response = await handleCartFormAction(request, ctx.cart, {
|
|
12
|
+
* responseHeaders: ctx.responseHeaders,
|
|
13
|
+
* });
|
|
14
|
+
* return response;
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* 行为:
|
|
18
|
+
* 1. 从 request.formData() 调 CartForm.getFormInput 解析出 { action, inputs }
|
|
19
|
+
* 2. switch(action) 分发到 cartHandler 对应方法
|
|
20
|
+
* 3. 返回 JSON Response,里面带 { cart, userErrors, errors? } 和 Set-Cookie header
|
|
21
|
+
* 4. 客户端 fetch 拿到这个 Response,调 CartProvider.applyCart 同步 state
|
|
22
|
+
*
|
|
23
|
+
* 也支持 progressive enhancement:JS 没加载时浏览器原生 form POST + 303 redirect 回 referrer。
|
|
24
|
+
* 通过 query param `?_progressive=1` 或检测 `Accept: text/html` 触发 303 模式。
|
|
25
|
+
*/
|
|
26
|
+
import type { CartHandler, CartResult } from './types';
|
|
27
|
+
export interface HandleCartFormActionOptions {
|
|
28
|
+
/** 服务端构造的 responseHeaders(Set-Cookie 写在这里) */
|
|
29
|
+
responseHeaders?: Headers;
|
|
30
|
+
/** JS 未加载时浏览器原生 form POST → 303 redirect 到这个 URL(默认 Referer 或 /cart) */
|
|
31
|
+
redirectTo?: string;
|
|
32
|
+
/**
|
|
33
|
+
* 自定义 action 处理器(CartForm 支持 `Custom${string}` action)。
|
|
34
|
+
* 第二个参数是 cartHandler,可以组合调用。
|
|
35
|
+
*/
|
|
36
|
+
customActions?: Record<string, (inputs: any, cart: CartHandler) => Promise<CartResult>>;
|
|
37
|
+
}
|
|
38
|
+
export declare function handleCartFormAction(request: Request, cart: CartHandler, options?: HandleCartFormActionOptions): Promise<Response>;
|
|
39
|
+
//# sourceMappingURL=handleCartFormAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handleCartFormAction.d.ts","sourceRoot":"","sources":["../src/handleCartFormAction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C,8CAA8C;IAC9C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;CACzF;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,QAAQ,CAAC,CA6EnB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* handleCartFormAction
|
|
3
|
+
*
|
|
4
|
+
* 服务端统一处理 <CartForm> 提交的 helper。对齐 Hydrogen 的"单一 /cart route action"模式:
|
|
5
|
+
*
|
|
6
|
+
* import { CartForm, handleCartFormAction } from '@shopbb/helium';
|
|
7
|
+
*
|
|
8
|
+
* // shopflare server.tsx POST handler
|
|
9
|
+
* if (url.pathname === '/cart' && request.method === 'POST') {
|
|
10
|
+
* const ctx = createHeliumContext({ ... });
|
|
11
|
+
* const response = await handleCartFormAction(request, ctx.cart, {
|
|
12
|
+
* responseHeaders: ctx.responseHeaders,
|
|
13
|
+
* });
|
|
14
|
+
* return response;
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* 行为:
|
|
18
|
+
* 1. 从 request.formData() 调 CartForm.getFormInput 解析出 { action, inputs }
|
|
19
|
+
* 2. switch(action) 分发到 cartHandler 对应方法
|
|
20
|
+
* 3. 返回 JSON Response,里面带 { cart, userErrors, errors? } 和 Set-Cookie header
|
|
21
|
+
* 4. 客户端 fetch 拿到这个 Response,调 CartProvider.applyCart 同步 state
|
|
22
|
+
*
|
|
23
|
+
* 也支持 progressive enhancement:JS 没加载时浏览器原生 form POST + 303 redirect 回 referrer。
|
|
24
|
+
* 通过 query param `?_progressive=1` 或检测 `Accept: text/html` 触发 303 模式。
|
|
25
|
+
*/
|
|
26
|
+
import { CartForm } from './components/CartForm';
|
|
27
|
+
export async function handleCartFormAction(request, cart, options = {}) {
|
|
28
|
+
const { responseHeaders, redirectTo, customActions } = options;
|
|
29
|
+
let formData;
|
|
30
|
+
try {
|
|
31
|
+
formData = await request.formData();
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return jsonResponse({ error: 'Invalid form data: ' + (e.message || e), userErrors: [] }, 400, responseHeaders);
|
|
35
|
+
}
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = CartForm.getFormInput(formData);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return jsonResponse({ error: e.message || String(e), userErrors: [] }, 400, responseHeaders);
|
|
42
|
+
}
|
|
43
|
+
let result;
|
|
44
|
+
try {
|
|
45
|
+
switch (parsed.action) {
|
|
46
|
+
case 'LinesAdd':
|
|
47
|
+
result = await cart.addLines(parsed.inputs.lines);
|
|
48
|
+
break;
|
|
49
|
+
case 'LinesUpdate':
|
|
50
|
+
result = await cart.updateLines(parsed.inputs.lines);
|
|
51
|
+
break;
|
|
52
|
+
case 'LinesRemove':
|
|
53
|
+
result = await cart.removeLines(parsed.inputs.lineIds);
|
|
54
|
+
break;
|
|
55
|
+
case 'DiscountCodesUpdate':
|
|
56
|
+
result = await cart.updateDiscountCodes(parsed.inputs.discountCodes || []);
|
|
57
|
+
break;
|
|
58
|
+
case 'Create':
|
|
59
|
+
result = await cart.create(parsed.inputs.input);
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
// shopbb-extension actions
|
|
63
|
+
if (parsed.action === 'CustomDiscountSelect') {
|
|
64
|
+
result = await cart.selectDiscount(parsed.inputs.claimId);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
if (parsed.action === 'CustomDiscountClear') {
|
|
68
|
+
result = await cart.clearDiscount();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
// 用户自定义
|
|
72
|
+
if (parsed.action.startsWith('Custom') && customActions && customActions[parsed.action]) {
|
|
73
|
+
result = await customActions[parsed.action](parsed.inputs, cart);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
return jsonResponse({ error: `Unknown CartForm action: "${parsed.action}"`, userErrors: [] }, 400, responseHeaders);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return jsonResponse({ error: e.message || String(e), userErrors: [] }, 500, responseHeaders);
|
|
81
|
+
}
|
|
82
|
+
// 写入 Set-Cookie(如果 cart id 变化)
|
|
83
|
+
if (result.cart?.id && responseHeaders) {
|
|
84
|
+
cart.setCartId(result.cart.id);
|
|
85
|
+
}
|
|
86
|
+
// 浏览器原生 form POST(无 JS)→ 303 redirect 回 referrer
|
|
87
|
+
// 判定:只信 X-Helium-Fetch header(CartForm fetch 显式带)
|
|
88
|
+
// 没有该 header → 视为原生 form POST,必须用 303 让浏览器跳转(不然显示 raw JSON)
|
|
89
|
+
const isJsFetch = !!request.headers.get('X-Helium-Fetch');
|
|
90
|
+
if (!isJsFetch) {
|
|
91
|
+
const target = redirectTo || request.headers.get('Referer') || '/cart';
|
|
92
|
+
const headers = new Headers(responseHeaders || {});
|
|
93
|
+
headers.set('Location', target);
|
|
94
|
+
return new Response(null, { status: 303, headers });
|
|
95
|
+
}
|
|
96
|
+
return jsonResponse({ cart: result.cart, userErrors: result.userErrors }, 200, responseHeaders);
|
|
97
|
+
}
|
|
98
|
+
function jsonResponse(body, status, responseHeaders) {
|
|
99
|
+
const headers = new Headers(responseHeaders || {});
|
|
100
|
+
headers.set('Content-Type', 'application/json; charset=utf-8');
|
|
101
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=handleCartFormAction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handleCartFormAction.js","sourceRoot":"","sources":["../src/handleCartFormAction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,QAAQ,EAAsB,MAAM,uBAAuB,CAAC;AAerE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,IAAiB,EACjB,UAAuC,EAAE;IAEzC,MAAM,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC;IAE/D,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,qBAAqB,GAAG,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IACjH,CAAC;IAED,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC/F,CAAC;IAED,IAAI,MAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,UAAU;gBACb,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAE,MAAM,CAAC,MAAc,CAAC,KAAK,CAAC,CAAC;gBAC3D,MAAM;YACR,KAAK,aAAa;gBAChB,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAE,MAAM,CAAC,MAAc,CAAC,KAAK,CAAC,CAAC;gBAC9D,MAAM;YACR,KAAK,aAAa;gBAChB,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAE,MAAM,CAAC,MAAc,CAAC,OAAO,CAAC,CAAC;gBAChE,MAAM;YACR,KAAK,qBAAqB;gBACxB,MAAM,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAE,MAAM,CAAC,MAAc,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;gBACpF,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAE,MAAM,CAAC,MAAc,CAAC,KAAK,CAAC,CAAC;gBACzD,MAAM;YACR;gBACE,2BAA2B;gBAC3B,IAAI,MAAM,CAAC,MAAM,KAAK,sBAAsB,EAAE,CAAC;oBAC7C,MAAM,GAAG,MAAM,IAAI,CAAC,cAAc,CAAE,MAAM,CAAC,MAAc,CAAC,OAAO,CAAC,CAAC;oBACnE,MAAM;gBACR,CAAC;gBACD,IAAI,MAAM,CAAC,MAAM,KAAK,qBAAqB,EAAE,CAAC;oBAC5C,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;oBACpC,MAAM;gBACR,CAAC;gBACD,QAAQ;gBACR,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,aAAa,IAAI,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxF,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBACjE,MAAM;gBACR,CAAC;gBACD,OAAO,YAAY,CACjB,EAAE,KAAK,EAAE,6BAA6B,MAAM,CAAC,MAAM,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,EACxE,GAAG,EACH,eAAe,CAChB,CAAC;QACN,CAAC;IACH,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,OAAO,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC/F,CAAC;IAED,+BAA+B;IAC/B,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,eAAe,EAAE,CAAC;QACvC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,iDAAiD;IACjD,kDAAkD;IAClD,4DAA4D;IAC5D,MAAM,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC;QACvE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChC,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,OAAO,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;AAClG,CAAC;AAED,SAAS,YAAY,CAAC,IAAS,EAAE,MAAc,EAAE,eAAyB;IACxE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,iCAAiC,CAAC,CAAC;IAC/D,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACjE,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,24 @@ export { createStorefrontClient } from './createStorefrontClient';
|
|
|
17
17
|
export { createCartHandler, DEFAULT_CART_FRAGMENT } from './createCartHandler';
|
|
18
18
|
export { cartGetIdDefault, cartSetIdDefault } from './cart-id';
|
|
19
19
|
export type { CartSetIdOptions } from './cart-id';
|
|
20
|
+
export { handleCartFormAction } from './handleCartFormAction';
|
|
21
|
+
export type { HandleCartFormActionOptions } from './handleCartFormAction';
|
|
20
22
|
export { CacheNone, CacheShort, CacheLong, CacheCustom } from './cache';
|
|
23
|
+
export { createWithCache, InMemoryCache } from './cache/withCache';
|
|
24
|
+
export type { WithCache, WithCacheOptions, CacheKey } from './cache/withCache';
|
|
25
|
+
export { storefrontRedirect } from './routing/storefrontRedirect';
|
|
26
|
+
export type { StorefrontRedirectOptions } from './routing/storefrontRedirect';
|
|
27
|
+
export { getSeoMeta } from './seo/getSeoMeta';
|
|
28
|
+
export type { SeoConfig, SeoResult } from './seo/getSeoMeta';
|
|
29
|
+
export { createCustomerAccountClient } from './customer/createCustomerAccountClient';
|
|
30
|
+
export type { CustomerAccountClient, CustomerAccountClientOptions } from './customer/createCustomerAccountClient';
|
|
31
|
+
export { createContentSecurityPolicy, NonceProvider, useNonce, Script } from './csp/csp';
|
|
32
|
+
export type { CspOptions, CspResult, NonceProviderProps, ScriptProps } from './csp/csp';
|
|
33
|
+
export { getSitemap, getSitemapIndex } from './sitemap/sitemap';
|
|
34
|
+
export type { SitemapResource, GetSitemapOptions, GetSitemapIndexOptions, } from './sitemap/sitemap';
|
|
35
|
+
export { flattenConnection } from './utils/flattenConnection';
|
|
36
|
+
export type { Connection } from './utils/flattenConnection';
|
|
37
|
+
export { parseGid } from './utils/parseGid';
|
|
38
|
+
export type { ParsedGid } from './utils/parseGid';
|
|
21
39
|
export type { CacheStrategy, StorefrontClient, StorefrontClientOptions, QueryOptions, MutateOptions, CartHandler, CartHandlerOptions, CartLineInput, CartLineUpdateInput, CartUserError, CartResult, CartIdGetter, CartIdSetter, HeliumContext, HeliumContextOptions, } from './types';
|
|
22
40
|
//# 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;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGxE,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGxE,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnE,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AACrF,YAAY,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,MAAM,wCAAwC,CAAC;AAClH,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACzF,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EACV,eAAe,EAAE,iBAAiB,EAAE,sBAAsB,GAC3D,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGlD,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -16,5 +16,16 @@ export { createHeliumContext } from './createHeliumContext';
|
|
|
16
16
|
export { createStorefrontClient } from './createStorefrontClient';
|
|
17
17
|
export { createCartHandler, DEFAULT_CART_FRAGMENT } from './createCartHandler';
|
|
18
18
|
export { cartGetIdDefault, cartSetIdDefault } from './cart-id';
|
|
19
|
+
export { handleCartFormAction } from './handleCartFormAction';
|
|
19
20
|
export { CacheNone, CacheShort, CacheLong, CacheCustom } from './cache';
|
|
21
|
+
// 服务端工具
|
|
22
|
+
export { createWithCache, InMemoryCache } from './cache/withCache';
|
|
23
|
+
export { storefrontRedirect } from './routing/storefrontRedirect';
|
|
24
|
+
export { getSeoMeta } from './seo/getSeoMeta';
|
|
25
|
+
export { createCustomerAccountClient } from './customer/createCustomerAccountClient';
|
|
26
|
+
export { createContentSecurityPolicy, NonceProvider, useNonce, Script } from './csp/csp';
|
|
27
|
+
export { getSitemap, getSitemapIndex } from './sitemap/sitemap';
|
|
28
|
+
// 工具
|
|
29
|
+
export { flattenConnection } from './utils/flattenConnection';
|
|
30
|
+
export { parseGid } from './utils/parseGid';
|
|
20
31
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAExE,QAAQ;AACR,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,2BAA2B,EAAE,MAAM,wCAAwC,CAAC;AAErF,OAAO,EAAE,2BAA2B,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKhE,KAAK;AACL,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storefrontRedirect — 对齐 Hydrogen storefrontRedirect
|
|
3
|
+
*
|
|
4
|
+
* 商家从老平台搬过来时常有 URL 改写需求,Shopify 在 admin 里维护 redirects 表。
|
|
5
|
+
* 商家 storefront 收到 404 时,调用 `storefrontRedirect({request, storefront})`
|
|
6
|
+
* 查 oxygen 的 redirects 表,若有匹配 → 301 / 302 跳到新地址。
|
|
7
|
+
*
|
|
8
|
+
* 用法(在 404 catch handler 里):
|
|
9
|
+
*
|
|
10
|
+
* if (response.status === 404) {
|
|
11
|
+
* const redirect = await storefrontRedirect({ request, storefront });
|
|
12
|
+
* if (redirect) return redirect;
|
|
13
|
+
* }
|
|
14
|
+
* return notFound();
|
|
15
|
+
*
|
|
16
|
+
* 简化版当前实现:调用 oxygen REST `/api/redirects?path=...`,未来 oxygen 加这条
|
|
17
|
+
* endpoint。商家可以传 fetcher 自定义查询源。
|
|
18
|
+
*/
|
|
19
|
+
export interface StorefrontRedirectOptions {
|
|
20
|
+
/** 当前 Request,从 URL 取 path 查 redirect */
|
|
21
|
+
request: Request;
|
|
22
|
+
/** oxygen Storefront client(用于 GraphQL urlRedirects 查询);或自定义 fetcher */
|
|
23
|
+
storefront?: {
|
|
24
|
+
query: (q: string, opts?: any) => Promise<any>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 自定义查询 redirect:传入 path,返回目标 URL(找不到返回 null)。
|
|
28
|
+
* 优先级高于 storefront query。
|
|
29
|
+
*/
|
|
30
|
+
resolveRedirect?: (path: string) => Promise<string | null>;
|
|
31
|
+
/** 重定向状态码:默认 301(永久) */
|
|
32
|
+
status?: 301 | 302 | 307 | 308;
|
|
33
|
+
/** 是否仅域内 path(避免 open redirect 攻击)。默认 true */
|
|
34
|
+
noExternal?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function storefrontRedirect(options: StorefrontRedirectOptions): Promise<Response | null>;
|
|
37
|
+
//# sourceMappingURL=storefrontRedirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefrontRedirect.d.ts","sourceRoot":"","sources":["../../src/routing/storefrontRedirect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,yBAAyB;IACxC,yCAAyC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,UAAU,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAC;IAChE;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,wBAAwB;IACxB,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAC/B,8CAA8C;IAC9C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAUD,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAwC1B"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storefrontRedirect — 对齐 Hydrogen storefrontRedirect
|
|
3
|
+
*
|
|
4
|
+
* 商家从老平台搬过来时常有 URL 改写需求,Shopify 在 admin 里维护 redirects 表。
|
|
5
|
+
* 商家 storefront 收到 404 时,调用 `storefrontRedirect({request, storefront})`
|
|
6
|
+
* 查 oxygen 的 redirects 表,若有匹配 → 301 / 302 跳到新地址。
|
|
7
|
+
*
|
|
8
|
+
* 用法(在 404 catch handler 里):
|
|
9
|
+
*
|
|
10
|
+
* if (response.status === 404) {
|
|
11
|
+
* const redirect = await storefrontRedirect({ request, storefront });
|
|
12
|
+
* if (redirect) return redirect;
|
|
13
|
+
* }
|
|
14
|
+
* return notFound();
|
|
15
|
+
*
|
|
16
|
+
* 简化版当前实现:调用 oxygen REST `/api/redirects?path=...`,未来 oxygen 加这条
|
|
17
|
+
* endpoint。商家可以传 fetcher 自定义查询源。
|
|
18
|
+
*/
|
|
19
|
+
const URL_REDIRECTS_QUERY = /* GraphQL */ `
|
|
20
|
+
query UrlRedirects($first: Int!, $query: String) {
|
|
21
|
+
urlRedirects(first: $first, query: $query) {
|
|
22
|
+
nodes { path target }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
export async function storefrontRedirect(options) {
|
|
27
|
+
const { request, storefront, resolveRedirect, status = 301, noExternal = true } = options;
|
|
28
|
+
const url = new URL(request.url);
|
|
29
|
+
const path = url.pathname + (url.search || '');
|
|
30
|
+
let target = null;
|
|
31
|
+
if (resolveRedirect) {
|
|
32
|
+
target = await resolveRedirect(path).catch(() => null);
|
|
33
|
+
}
|
|
34
|
+
else if (storefront) {
|
|
35
|
+
try {
|
|
36
|
+
const data = await storefront.query(URL_REDIRECTS_QUERY, { variables: { first: 1, query: `path:${path}` } });
|
|
37
|
+
const match = data?.urlRedirects?.nodes?.find((r) => r.path === path);
|
|
38
|
+
target = match?.target ?? null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
target = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!target)
|
|
45
|
+
return null;
|
|
46
|
+
if (noExternal) {
|
|
47
|
+
// 防 open redirect — 只允许相对路径或同域
|
|
48
|
+
if (target.startsWith('//') || /^https?:\/\//i.test(target)) {
|
|
49
|
+
try {
|
|
50
|
+
const t = new URL(target);
|
|
51
|
+
if (t.host !== url.host)
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return new Response(null, {
|
|
60
|
+
status,
|
|
61
|
+
headers: { Location: target },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=storefrontRedirect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storefrontRedirect.js","sourceRoot":"","sources":["../../src/routing/storefrontRedirect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAkBH,MAAM,mBAAmB,GAAG,aAAa,CAAC;;;;;;CAMzC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAkC;IAElC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,GAAG,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC1F,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAE/C,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IACzD,CAAC;SAAM,IAAI,UAAU,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAQ,MAAM,UAAU,CAAC,KAAK,CACtC,mBAAmB,EACnB,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,EAAE,CACnD,CAAC;YACF,MAAM,KAAK,GAAG,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAC3E,MAAM,GAAG,KAAK,EAAE,MAAM,IAAI,IAAI,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,IAAI,UAAU,EAAE,CAAC;QACf,+BAA+B;QAC/B,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5D,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM;QACN,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;KAC9B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getSeoMeta + <Seo> — 对齐 Hydrogen 的 SEO 工具
|
|
3
|
+
*
|
|
4
|
+
* Hydrogen 通过 Remix `meta` export 返回 SeoConfig;我们这里因不绑 Remix,
|
|
5
|
+
* 提供一个 `getSeoMeta(config)` 函数返回 HTML `<meta>` 字符串,供 SSR
|
|
6
|
+
* 服务端拼到 `<head>` 里。也提供 `<Seo data={config} />` 客户端组件
|
|
7
|
+
* (hydrate 阶段更新 document title / meta),用作 SPA 路由切换。
|
|
8
|
+
*
|
|
9
|
+
* 用法(服务端):
|
|
10
|
+
*
|
|
11
|
+
* const seo = getSeoMeta({
|
|
12
|
+
* title: product.title,
|
|
13
|
+
* description: product.description,
|
|
14
|
+
* image: product.featuredImage?.url,
|
|
15
|
+
* url: 'https://shop/products/xxx',
|
|
16
|
+
* type: 'product',
|
|
17
|
+
* });
|
|
18
|
+
* // seo = { title, htmlTags: '<title>...</title><meta ...>' }
|
|
19
|
+
* const html = `<head>${seo.htmlTags}...</head>`;
|
|
20
|
+
*/
|
|
21
|
+
export interface SeoConfig {
|
|
22
|
+
/** Page title — appears in <title> and og:title */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Suffix appended to title with separator " · " — usually shop name */
|
|
25
|
+
titleTemplate?: string;
|
|
26
|
+
/** Meta description / og:description */
|
|
27
|
+
description?: string;
|
|
28
|
+
/** Canonical URL / og:url */
|
|
29
|
+
url?: string;
|
|
30
|
+
/** og:image */
|
|
31
|
+
image?: string | {
|
|
32
|
+
url: string;
|
|
33
|
+
width?: number;
|
|
34
|
+
height?: number;
|
|
35
|
+
altText?: string;
|
|
36
|
+
};
|
|
37
|
+
/** og:type — "website" | "product" | "article" | ... */
|
|
38
|
+
type?: 'website' | 'product' | 'article' | string;
|
|
39
|
+
/** twitter:card — "summary" | "summary_large_image" */
|
|
40
|
+
twitterCard?: 'summary' | 'summary_large_image' | string;
|
|
41
|
+
/** Additional og:* fields */
|
|
42
|
+
og?: Record<string, string>;
|
|
43
|
+
/** robots — "index,follow" / "noindex" */
|
|
44
|
+
robots?: string;
|
|
45
|
+
/** JSON-LD structured data */
|
|
46
|
+
jsonLd?: Record<string, any> | Array<Record<string, any>>;
|
|
47
|
+
/** lang — "zh-CN" / "en" */
|
|
48
|
+
language?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface SeoResult {
|
|
51
|
+
title: string;
|
|
52
|
+
description: string;
|
|
53
|
+
/** 直接拼接到 <head> 的 HTML 字符串 */
|
|
54
|
+
htmlTags: string;
|
|
55
|
+
/** 关键 meta,方便商家自己拼 */
|
|
56
|
+
meta: Array<{
|
|
57
|
+
name?: string;
|
|
58
|
+
property?: string;
|
|
59
|
+
content: string;
|
|
60
|
+
}>;
|
|
61
|
+
/** 完整 title(含 template) */
|
|
62
|
+
fullTitle: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 根据 config 生成 <head> 该有的全部 SEO 标签字符串。
|
|
66
|
+
*/
|
|
67
|
+
export declare function getSeoMeta(config: SeoConfig): SeoResult;
|
|
68
|
+
//# sourceMappingURL=getSeoMeta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getSeoMeta.d.ts","sourceRoot":"","sources":["../../src/seo/getSeoMeta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,SAAS;IACxB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe;IACf,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpF,wDAAwD;IACxD,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAClD,uDAAuD;IACvD,WAAW,CAAC,EAAE,SAAS,GAAG,qBAAqB,GAAG,MAAM,CAAC;IACzD,6BAA6B;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,sBAAsB;IACtB,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CA0DvD"}
|