@kotao/storefront 0.2.2 → 0.2.4

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.
Files changed (48) hide show
  1. package/dist/chunk-4OQYNUGM.js +29 -0
  2. package/dist/chunk-4OQYNUGM.js.map +1 -0
  3. package/dist/{chunk-7XYWHWT4.js → chunk-4TEFGD42.js} +331 -38
  4. package/dist/chunk-4TEFGD42.js.map +1 -0
  5. package/dist/chunk-7MTCGXIG.js +144 -0
  6. package/dist/chunk-7MTCGXIG.js.map +1 -0
  7. package/dist/chunk-IIOHENCG.js +239 -0
  8. package/dist/chunk-IIOHENCG.js.map +1 -0
  9. package/dist/chunk-VV653S52.js +246 -0
  10. package/dist/chunk-VV653S52.js.map +1 -0
  11. package/dist/chunk-WG7HYVHJ.js +38 -0
  12. package/dist/chunk-WG7HYVHJ.js.map +1 -0
  13. package/dist/{client-DiPdlvPu.d.ts → client-D51WdvYR.d.ts} +55 -8
  14. package/dist/client-bsiNPHBi.d.ts +159 -0
  15. package/dist/createCartHandler-C4RrPwSG.d.ts +58 -0
  16. package/dist/editor.d.ts +1 -1
  17. package/dist/events-react.d.ts +68 -0
  18. package/dist/events-react.js +557 -0
  19. package/dist/events-react.js.map +1 -0
  20. package/dist/events.d.ts +18 -0
  21. package/dist/events.js +5 -0
  22. package/dist/events.js.map +1 -0
  23. package/dist/{fetch-B6TIc6lI.d.ts → fetch-M3Y-fQrW.d.ts} +1 -1
  24. package/dist/forms.d.ts +58 -0
  25. package/dist/forms.js +3 -0
  26. package/dist/forms.js.map +1 -0
  27. package/dist/http-types-CGh8WGHB.d.ts +11 -0
  28. package/dist/index.d.ts +74 -12
  29. package/dist/index.js +55 -9
  30. package/dist/index.js.map +1 -1
  31. package/dist/offer-token-C-nk_nj8.d.ts +16 -0
  32. package/dist/{pagination-CPgH3fAa.d.ts → pagination-DCrQk02Q.d.ts} +1 -1
  33. package/dist/product.d.ts +1 -1
  34. package/dist/react-router.d.ts +11 -7
  35. package/dist/react-router.js +4 -3
  36. package/dist/react-router.js.map +1 -1
  37. package/dist/react.d.ts +30 -7
  38. package/dist/react.js +4 -2
  39. package/dist/react.js.map +1 -1
  40. package/dist/{routing-BxRlep_-.d.ts → routing-sEonP93J.d.ts} +17 -60
  41. package/dist/seo.d.ts +2 -1
  42. package/dist/settings.d.ts +5 -4
  43. package/dist/{shop-DkQmro-Y.d.ts → shop-BJSvtmnV.d.ts} +2 -2
  44. package/dist/{store-DQK1nhob.d.ts → store-CtCWHI8H.d.ts} +1 -1
  45. package/dist/types-CSNXbGFW.d.ts +108 -0
  46. package/dist/{types-Df3bGbKU.d.ts → types-bcbQzx87.d.ts} +1 -1
  47. package/package.json +14 -2
  48. package/dist/chunk-7XYWHWT4.js.map +0 -1
@@ -0,0 +1,29 @@
1
+ // src/events/cart.ts
2
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
3
+ var SHOPIFY_CART_GID_RE = new RegExp(
4
+ `^gid://shopify/Cart/(${UUID_RE.source.slice(1, -1)})$`,
5
+ "iu"
6
+ );
7
+ var EventCartPreparationError = class extends Error {
8
+ constructor() {
9
+ super("Unable to prepare event cart");
10
+ this.name = "EventCartPreparationError";
11
+ }
12
+ };
13
+ function eventCartToken(cartId) {
14
+ if (UUID_RE.test(cartId)) return cartId;
15
+ const gidToken = SHOPIFY_CART_GID_RE.exec(cartId)?.[1];
16
+ if (gidToken !== void 0) return gidToken;
17
+ throw new EventCartPreparationError();
18
+ }
19
+ async function prepareEventCart(cart) {
20
+ const existing = await cart.get();
21
+ if (existing) return { cartToken: eventCartToken(existing.id), headers: new Headers() };
22
+ const created = await cart.create();
23
+ if (!created.cart || created.userErrors.length > 0) throw new EventCartPreparationError();
24
+ return { cartToken: eventCartToken(created.cart.id), headers: created.headers };
25
+ }
26
+
27
+ export { EventCartPreparationError, eventCartToken, prepareEventCart };
28
+ //# sourceMappingURL=chunk-4OQYNUGM.js.map
29
+ //# sourceMappingURL=chunk-4OQYNUGM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/events/cart.ts"],"names":[],"mappings":";AAEA,IAAM,OAAA,GAAU,6EAAA;AAChB,IAAM,sBAAsB,IAAI,MAAA;AAAA,EAC9B,wBAAwB,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,EACnD;AACF,CAAA;AAEO,IAAM,yBAAA,GAAN,cAAwC,KAAA,CAAM;AAAA,EACnD,WAAA,GAAc;AACZ,IAAA,KAAA,CAAM,8BAA8B,CAAA;AACpC,IAAA,IAAA,CAAK,IAAA,GAAO,2BAAA;AAAA,EACd;AACF;AAEO,SAAS,eAAe,MAAA,EAAwB;AACrD,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,EAAG,OAAO,MAAA;AACjC,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,IAAA,CAAK,MAAM,IAAI,CAAC,CAAA;AACrD,EAAA,IAAI,QAAA,KAAa,QAAW,OAAO,QAAA;AACnC,EAAA,MAAM,IAAI,yBAAA,EAA0B;AACtC;AAEA,eAAsB,iBACpB,IAAA,EACkD;AAClD,EAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,GAAA,EAAI;AAChC,EAAA,IAAI,QAAA,EAAU,OAAO,EAAE,SAAA,EAAW,cAAA,CAAe,QAAA,CAAS,EAAE,CAAA,EAAG,OAAA,EAAS,IAAI,OAAA,EAAQ,EAAE;AACtF,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,MAAA,EAAO;AAClC,EAAA,IAAI,CAAC,QAAQ,IAAA,IAAQ,OAAA,CAAQ,WAAW,MAAA,GAAS,CAAA,EAAG,MAAM,IAAI,yBAAA,EAA0B;AACxF,EAAA,OAAO,EAAE,WAAW,cAAA,CAAe,OAAA,CAAQ,KAAK,EAAE,CAAA,EAAG,OAAA,EAAS,OAAA,CAAQ,OAAA,EAAQ;AAChF","file":"chunk-4OQYNUGM.js","sourcesContent":["import type { CartHandler } from '../cart/createCartHandler'\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu\nconst SHOPIFY_CART_GID_RE = new RegExp(\n `^gid://shopify/Cart/(${UUID_RE.source.slice(1, -1)})$`,\n 'iu',\n)\n\nexport class EventCartPreparationError extends Error {\n constructor() {\n super('Unable to prepare event cart')\n this.name = 'EventCartPreparationError'\n }\n}\n\nexport function eventCartToken(cartId: string): string {\n if (UUID_RE.test(cartId)) return cartId\n const gidToken = SHOPIFY_CART_GID_RE.exec(cartId)?.[1]\n if (gidToken !== undefined) return gidToken\n throw new EventCartPreparationError()\n}\n\nexport async function prepareEventCart(\n cart: CartHandler,\n): Promise<{ cartToken: string; headers: Headers }> {\n const existing = await cart.get()\n if (existing) return { cartToken: eventCartToken(existing.id), headers: new Headers() }\n const created = await cart.create()\n if (!created.cart || created.userErrors.length > 0) throw new EventCartPreparationError()\n return { cartToken: eventCartToken(created.cart.id), headers: created.headers }\n}\n"]}
@@ -1,31 +1,44 @@
1
- import { fetchThemeSettings } from './chunk-M7BPQ54D.js';
2
1
  import { CONSENT_BUCKETS, readConsentCookie, CART_FORM_ACTIONS } from './chunk-OGWYTYYS.js';
3
- import { SHOP_HEADER, SHOP_SIGNATURE_HEADER, autoRedirectEnabled, AUTO_REDIRECT_LANGUAGE_HEADER, AUTO_REDIRECT_COUNTRY_HEADER, signShopContext } from '@kotao/storefront-contracts';
2
+ import { fetchThemeSettings } from './chunk-M7BPQ54D.js';
3
+ import { createEventsClient, normalizeStorefrontHeaders } from './chunk-7MTCGXIG.js';
4
+ import { SHOP_HEADER, SHOP_SIGNATURE_HEADER, SHOP_ORIGIN_HEADER, autoRedirectEnabled, AUTO_REDIRECT_LANGUAGE_HEADER, AUTO_REDIRECT_COUNTRY_HEADER, signShopContext } from '@kotao/storefront-contracts';
4
5
 
5
6
  // src/cache/cache.ts
6
- var CacheNone = () => ({ mode: "none", maxAge: 0, staleWhileRevalidate: 0 });
7
+ var DEFAULT_STALE_IF_ERROR_SECONDS = 300;
8
+ var CacheNone = () => ({
9
+ mode: "none",
10
+ maxAge: 0,
11
+ staleWhileRevalidate: 0,
12
+ staleIfError: 0
13
+ });
7
14
  var CacheShort = (o) => ({
8
15
  mode: "short",
9
16
  maxAge: o?.maxAge ?? 1,
10
- staleWhileRevalidate: o?.staleWhileRevalidate ?? 9
17
+ staleWhileRevalidate: o?.staleWhileRevalidate ?? 9,
18
+ staleIfError: o?.staleIfError ?? DEFAULT_STALE_IF_ERROR_SECONDS
11
19
  });
12
20
  var CacheLong = (o) => ({
13
21
  mode: "long",
14
22
  maxAge: o?.maxAge ?? 3600,
15
- staleWhileRevalidate: o?.staleWhileRevalidate ?? 82800
23
+ staleWhileRevalidate: o?.staleWhileRevalidate ?? 82800,
24
+ staleIfError: o?.staleIfError ?? DEFAULT_STALE_IF_ERROR_SECONDS
16
25
  });
17
26
  var CacheCustom = (s) => ({
18
27
  mode: "custom",
28
+ staleIfError: DEFAULT_STALE_IF_ERROR_SECONDS,
19
29
  ...s
20
30
  });
21
31
  var StorefrontDefaultCache = () => ({
22
32
  mode: "custom",
23
33
  maxAge: 1,
24
- staleWhileRevalidate: 86399
34
+ staleWhileRevalidate: 86399,
35
+ staleIfError: DEFAULT_STALE_IF_ERROR_SECONDS
25
36
  });
26
37
  function generateCacheControlHeader(strategy) {
27
38
  if (strategy.mode === "none") return "no-store";
28
- return `public, max-age=${String(strategy.maxAge)}, stale-while-revalidate=${String(strategy.staleWhileRevalidate)}`;
39
+ const staleIfError = strategy.staleIfError ?? 0;
40
+ const staleIfErrorDirective = staleIfError > 0 ? `, stale-if-error=${String(staleIfError)}` : "";
41
+ return `public, max-age=${String(strategy.maxAge)}, stale-while-revalidate=${String(strategy.staleWhileRevalidate)}${staleIfErrorDirective}`;
29
42
  }
30
43
  var requestFor = (key) => new Request(`https://kotao.cache/${encodeURIComponent(key)}`);
31
44
  function createCache(opts) {
@@ -42,7 +55,7 @@ function createCache(opts) {
42
55
  };
43
56
  const write = async (key, value, strategy) => {
44
57
  const body = { value, storedAt: now() };
45
- const ttl = strategy.maxAge + strategy.staleWhileRevalidate;
58
+ const ttl = strategy.maxAge + strategy.staleWhileRevalidate + (strategy.staleIfError ?? 0);
46
59
  await cache.put(
47
60
  requestFor(key),
48
61
  new Response(JSON.stringify(body), {
@@ -52,7 +65,7 @@ function createCache(opts) {
52
65
  };
53
66
  return {
54
67
  raw: cache,
55
- async run(key, strategy, fn) {
68
+ async run(key, strategy, fn, onStaleIfError) {
56
69
  if (strategy.mode === "none") return fn();
57
70
  const stored = await read(key);
58
71
  if (stored) {
@@ -63,10 +76,23 @@ function createCache(opts) {
63
76
  (async () => {
64
77
  const fresh = await fn();
65
78
  await write(key, fresh, strategy);
66
- })()
79
+ })().catch(() => void 0)
67
80
  );
68
81
  return stored.value;
69
82
  }
83
+ if (ageSec <= strategy.maxAge + strategy.staleWhileRevalidate + (strategy.staleIfError ?? 0)) {
84
+ try {
85
+ const value2 = await fn();
86
+ opts.waitUntil(write(key, value2, strategy));
87
+ return value2;
88
+ } catch (error) {
89
+ try {
90
+ onStaleIfError?.({ ageSeconds: ageSec, error });
91
+ } catch {
92
+ }
93
+ return stored.value;
94
+ }
95
+ }
70
96
  }
71
97
  const value = await fn();
72
98
  opts.waitUntil(write(key, value, strategy));
@@ -610,7 +636,8 @@ function createCartHandler(opts) {
610
636
  const id = await currentCartId();
611
637
  if (!id) return null;
612
638
  const data = await opts.storefront.query(CART_QUERY, {
613
- variables: { cartId: id }
639
+ variables: { cartId: id },
640
+ cache: CacheNone()
614
641
  });
615
642
  return withCheckoutUrl(data.cart);
616
643
  },
@@ -708,6 +735,12 @@ function appendSetCartIdHeaders(headers, value) {
708
735
  // src/customer-account/session.ts
709
736
  var CUSTOMER_SESSION_COOKIE = "kotao_customer_session";
710
737
  var DEFAULT_SESSION_MAX_AGE = 60 * 60 * 24 * 30;
738
+ var HOSTED_CUSTOMER_SESSION_COOKIE = "__Host-kotao_customer_session";
739
+ var HOSTED_CUSTOMER_CSRF_COOKIE = "__Host-kotao_customer_csrf";
740
+ var HOSTED_CUSTOMER_CSRF_HEADER = "x-kotao-csrf-token";
741
+ var MAX_HOSTED_SESSION_MAX_AGE = 60 * 60 * 24 * 365;
742
+ var DEFAULT_HOSTED_SESSION_MAX_AGE = MAX_HOSTED_SESSION_MAX_AGE;
743
+ var MAX_LEGACY_CUSTOMER_SESSION_TOKEN_LENGTH = 4096;
711
744
  var assertHostname = (hostname) => {
712
745
  if (hostname === "" || hostname.startsWith(".") || hostname.endsWith(".")) {
713
746
  throw new Error(`invalid shop hostname for the customer session cookie: '${hostname}'`);
@@ -737,6 +770,48 @@ function readCustomerSessionToken(cookieHeader) {
737
770
  return null;
738
771
  }
739
772
  }
773
+ function buildHostedCustomerSessionCookie(token, attrs = {}) {
774
+ if (token === "") throw new Error("hosted customer session token must not be empty");
775
+ const maxAge = attrs.maxAgeSeconds ?? DEFAULT_HOSTED_SESSION_MAX_AGE;
776
+ if (!Number.isInteger(maxAge) || maxAge <= 0 || maxAge > MAX_HOSTED_SESSION_MAX_AGE) {
777
+ throw new Error("hosted customer session Max-Age must be between 1 second and 365 days");
778
+ }
779
+ return `${HOSTED_CUSTOMER_SESSION_COOKIE}=${encodeURIComponent(token)}; Path=/; Max-Age=${maxAge}; HttpOnly; Secure; SameSite=Lax`;
780
+ }
781
+ function clearHostedCustomerSessionCookie() {
782
+ return `${HOSTED_CUSTOMER_SESSION_COOKIE}=; Path=/; Max-Age=0; HttpOnly; Secure; SameSite=Lax`;
783
+ }
784
+ function readHostedCustomerSessionToken(cookieHeader) {
785
+ return readBoundedCookie(cookieHeader, HOSTED_CUSTOMER_SESSION_COOKIE);
786
+ }
787
+ function readHostedCustomerCsrfToken(cookieHeader) {
788
+ const token = readBoundedCookie(cookieHeader, HOSTED_CUSTOMER_CSRF_COOKIE, 43);
789
+ return token !== null && /^[A-Za-z0-9_-]{43}$/.test(token) ? token : null;
790
+ }
791
+ function readLegacyCustomerSessionToken(cookieHeader, options) {
792
+ if (!options.allowLegacy) return null;
793
+ return readBoundedCookie(
794
+ cookieHeader,
795
+ CUSTOMER_SESSION_COOKIE,
796
+ MAX_LEGACY_CUSTOMER_SESSION_TOKEN_LENGTH
797
+ );
798
+ }
799
+ function clearLegacyCustomerSessionCookie(attrs) {
800
+ return clearCustomerSessionCookie(attrs);
801
+ }
802
+ function readBoundedCookie(cookieHeader, name, maxLength = MAX_LEGACY_CUSTOMER_SESSION_TOKEN_LENGTH) {
803
+ if (cookieHeader === null || cookieHeader === void 0 || cookieHeader === "") return null;
804
+ const match = cookieHeader.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${name}=`));
805
+ if (match === void 0) return null;
806
+ const raw = match.slice(name.length + 1);
807
+ if (raw === "" || raw.length > maxLength) return null;
808
+ try {
809
+ const decoded = decodeURIComponent(raw);
810
+ return decoded.length <= maxLength ? decoded : null;
811
+ } catch {
812
+ return null;
813
+ }
814
+ }
740
815
 
741
816
  // src/customer-account/types.ts
742
817
  var CustomerAccountError = class extends Error {
@@ -761,7 +836,11 @@ function createCustomerAccountClient(opts) {
761
836
  }
762
837
  const doFetch = opts.fetch ?? fetch;
763
838
  const cookieHeader = opts.request.headers.get("cookie");
764
- const token = readCustomerSessionToken(cookieHeader);
839
+ const hostedToken = readHostedCustomerSessionToken(cookieHeader);
840
+ const legacyToken = hostedToken === null && (opts.allowLegacySessionForwarding ?? true) ? readCustomerSessionToken(cookieHeader) : null;
841
+ const token = hostedToken ?? legacyToken;
842
+ const sessionCookie = hostedToken !== null ? `${HOSTED_CUSTOMER_SESSION_COOKIE}=${encodeURIComponent(hostedToken)}` : legacyToken !== null ? `${CUSTOMER_SESSION_COOKIE}=${encodeURIComponent(legacyToken)}` : null;
843
+ const configuredShopOrigin = configuredHttpsOrigin(opts.shopOrigin);
765
844
  const hydrogenCustomerAccountId = opts.customerAccountId?.trim();
766
845
  const hydrogenShopId = opts.shopId?.trim();
767
846
  const hasHydrogenCustomerAccount = hydrogenCustomerAccountId !== void 0 && hydrogenCustomerAccountId.length > 0 && hydrogenShopId !== void 0 && hydrogenShopId.length > 0;
@@ -769,7 +848,8 @@ function createCustomerAccountClient(opts) {
769
848
  const proxyGet = async (path) => {
770
849
  try {
771
850
  return await doFetch(`${proxyBase}${path}`, {
772
- headers: token !== null ? { cookie: `${CUSTOMER_SESSION_COOKIE}=${encodeURIComponent(token)}` } : {}
851
+ redirect: "manual",
852
+ headers: sessionCookie !== null ? { cookie: sessionCookie } : {}
773
853
  });
774
854
  } catch (cause) {
775
855
  throw new CustomerAccountError(
@@ -792,21 +872,45 @@ function createCustomerAccountClient(opts) {
792
872
  }
793
873
  };
794
874
  const proxyPost = async (path, body) => {
875
+ const csrf = await csrfHeaders();
876
+ if (csrf === null) return new Response(null, { status: 503 });
795
877
  return doFetch(`${proxyBase}${path}`, {
796
878
  method: "POST",
797
- headers: { "content-type": "application/json" },
879
+ redirect: "manual",
880
+ headers: { "content-type": "application/json", ...csrf.headers },
798
881
  body: JSON.stringify(body)
799
882
  });
800
883
  };
801
884
  const proxyWrite = async (path, method, body) => {
802
- const headers = { "content-type": "application/json" };
803
- if (token !== null) headers.cookie = `${CUSTOMER_SESSION_COOKIE}=${encodeURIComponent(token)}`;
885
+ const csrf = await csrfHeaders();
886
+ if (csrf === null) return new Response(null, { status: 503 });
887
+ const headers = { "content-type": "application/json", ...csrf.headers };
804
888
  return doFetch(`${proxyBase}${path}`, {
805
889
  method,
890
+ redirect: "manual",
806
891
  headers,
807
892
  ...body !== void 0 ? { body: JSON.stringify(body) } : {}
808
893
  });
809
894
  };
895
+ const csrfHeaders = async () => {
896
+ if (configuredShopOrigin === null) return null;
897
+ let csrfToken = readHostedCustomerCsrfToken(cookieHeader);
898
+ if (csrfToken === null) {
899
+ const bootstrap = await doFetch(`${proxyBase}/account/csrf`, {
900
+ redirect: "manual",
901
+ headers: sessionCookie !== null ? { cookie: sessionCookie } : {}
902
+ });
903
+ csrfToken = csrfTokenFromSetCookie(bootstrap.headers);
904
+ if (csrfToken === null) return null;
905
+ }
906
+ return {
907
+ headers: {
908
+ Origin: configuredShopOrigin,
909
+ [HOSTED_CUSTOMER_CSRF_HEADER]: csrfToken,
910
+ cookie: [sessionCookie, `${HOSTED_CUSTOMER_CSRF_COOKIE}=${csrfToken}`].filter((value) => value !== null).join("; ")
911
+ }
912
+ };
913
+ };
810
914
  const returnTo = () => {
811
915
  const url = new URL(opts.request.url);
812
916
  return `${url.pathname}${url.search}`;
@@ -1046,11 +1150,11 @@ function createCustomerAccountClient(opts) {
1046
1150
  async requestLoginCode(input) {
1047
1151
  try {
1048
1152
  const res = await proxyPost("/account/send", { email: input.email });
1049
- if (res.status === 200) return { status: "sent" };
1050
- if (res.status === 429) return { status: "rate_limited" };
1051
- return { status: "error" };
1153
+ if (res.status === 200) return { status: "sent", response: res };
1154
+ if (res.status === 429) return { status: "rate_limited", response: res };
1155
+ return { status: "error", response: res };
1052
1156
  } catch {
1053
- return { status: "error" };
1157
+ return { status: "error", response: new Response(null, { status: 503 }) };
1054
1158
  }
1055
1159
  },
1056
1160
  async verifyLoginCode(input) {
@@ -1083,18 +1187,23 @@ function createCustomerAccountClient(opts) {
1083
1187
  await commitHydrogenSession(headers);
1084
1188
  }
1085
1189
  } else {
1086
- headers.set("location", `${proxyBase}/account/logout`);
1190
+ try {
1191
+ return await proxyWrite("/account/logout", "POST");
1192
+ } catch {
1193
+ headers.set("location", "/");
1194
+ }
1087
1195
  }
1088
1196
  headers.append(
1089
1197
  "set-cookie",
1090
1198
  clearCustomerSessionCookie({ hostname: shopHostname(opts.request) })
1091
1199
  );
1200
+ headers.append("set-cookie", clearHostedCustomerSessionCookie());
1092
1201
  return new Response(null, { status: 302, headers });
1093
1202
  },
1094
1203
  async getAccessToken() {
1095
1204
  const directAccessToken = await hydrogenAccessToken();
1096
1205
  if (directAccessToken !== null) return directAccessToken;
1097
- return token;
1206
+ return hasHydrogenCustomerAccount ? token : null;
1098
1207
  },
1099
1208
  async isLoggedIn() {
1100
1209
  if (await hydrogenAccessToken() !== null) return true;
@@ -1140,6 +1249,26 @@ function randomOAuthToken() {
1140
1249
  for (const byte of bytes) raw += String.fromCharCode(byte);
1141
1250
  return base64UrlEncode(raw);
1142
1251
  }
1252
+ function configuredHttpsOrigin(value) {
1253
+ if (!value) return null;
1254
+ try {
1255
+ const candidate = value.includes("://") ? value : `https://${value}`;
1256
+ const url = new URL(candidate);
1257
+ if (url.protocol !== "https:" || url.username || url.password || url.port || url.pathname !== "/")
1258
+ return null;
1259
+ return url.origin;
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ }
1264
+ function csrfTokenFromSetCookie(headers) {
1265
+ const values = "getSetCookie" in headers && typeof headers.getSetCookie === "function" ? headers.getSetCookie() : [headers.get("set-cookie") ?? ""];
1266
+ for (const value of values) {
1267
+ const match = value.match(/(?:^|,\s*)__Host-kotao_customer_csrf=([A-Za-z0-9_-]{43})(?:;|$)/);
1268
+ if (match?.[1]) return match[1];
1269
+ }
1270
+ return null;
1271
+ }
1143
1272
  function isHydrogenSessionLike(value) {
1144
1273
  return value !== null && typeof value === "object" && "get" in value && "set" in value && typeof value.get === "function" && typeof value.set === "function";
1145
1274
  }
@@ -1355,9 +1484,32 @@ function localizePath(path, locale, options = {}) {
1355
1484
  }
1356
1485
 
1357
1486
  // src/storefront-client/client.ts
1487
+ var StorefrontQueryTimeoutError = class extends Error {
1488
+ operationName;
1489
+ timeoutMs;
1490
+ constructor(operationName, timeoutMs) {
1491
+ super(`Storefront query ${operationName} timed out after ${String(timeoutMs)}ms`);
1492
+ this.name = "StorefrontQueryTimeoutError";
1493
+ this.operationName = operationName;
1494
+ this.timeoutMs = timeoutMs;
1495
+ }
1496
+ };
1497
+ var StorefrontUpstreamError = class extends Error {
1498
+ operationName;
1499
+ status;
1500
+ cause;
1501
+ constructor(operationName, message, options) {
1502
+ super(message);
1503
+ this.name = "StorefrontUpstreamError";
1504
+ this.operationName = operationName;
1505
+ this.status = options?.status;
1506
+ this.cause = options?.cause;
1507
+ }
1508
+ };
1358
1509
  var hashKey = (query, variables) => `sf:${query.replace(/\s+/g, " ").trim()}:${variables ? JSON.stringify(variables) : ""}`;
1359
1510
  var COUNTRY_RE = /^[A-Z]{2}$/;
1360
1511
  var DEFAULT_HYDROGEN_STOREFRONT_API_VERSION = "2025-01";
1512
+ var DEFAULT_QUERY_TIMEOUT_MS = 5e3;
1361
1513
  function endpointForStorefrontApiVersion(endpoint, version) {
1362
1514
  if (version === void 0 || version.length === 0) return endpoint;
1363
1515
  try {
@@ -1382,6 +1534,7 @@ function createStorefrontClient(opts) {
1382
1534
  cache: createCache({ cache: opts.cache, waitUntil: opts.waitUntil }),
1383
1535
  fetch: opts.fetch,
1384
1536
  defaultStrategy: opts.defaultStrategy,
1537
+ queryTimeoutMs: opts.queryTimeoutMs,
1385
1538
  extraHeaders: opts.storefrontHeaders,
1386
1539
  i18n
1387
1540
  });
@@ -1392,11 +1545,13 @@ function createStorefrontClient(opts) {
1392
1545
  function createStorefrontClientCore(opts) {
1393
1546
  const doFetch = opts.fetch ?? fetch;
1394
1547
  const defaultStrategy = opts.defaultStrategy ?? StorefrontDefaultCache();
1548
+ const defaultQueryTimeoutMs = opts.queryTimeoutMs ?? DEFAULT_QUERY_TIMEOUT_MS;
1395
1549
  const tokenHeader = opts.tokenHeader ?? "x-kotao-storefront-token";
1396
1550
  const localeKey = opts.locale !== void 0 && opts.locale.length > 0 ? opts.locale : "";
1397
1551
  const countryKey = opts.country !== void 0 && COUNTRY_RE.test(opts.country) ? opts.country : "";
1398
1552
  const i18n = normalizeStorefrontI18n(opts.i18n, opts.locale, countryKey);
1399
- const execute = async (document, options) => {
1553
+ const execute = async (document, options, signal, timing) => {
1554
+ const operationStartedAt = timing === void 0 ? 0 : Date.now();
1400
1555
  const endpoint = endpointForStorefrontApiVersion(opts.endpoint, options?.storefrontApiVersion);
1401
1556
  const headers = {
1402
1557
  "content-type": "application/json",
@@ -1413,13 +1568,67 @@ function createStorefrontClientCore(opts) {
1413
1568
  if (countryKey.length > 0) {
1414
1569
  headers["x-kotao-country"] = countryKey;
1415
1570
  }
1416
- const res = await doFetch(endpoint, {
1417
- method: "POST",
1418
- headers,
1419
- body: JSON.stringify({ query: document, variables: options?.variables })
1420
- });
1421
- const opName = document.replace(/\s+/g, " ").trim().slice(0, 48);
1422
- const raw = await res.text();
1571
+ const operationName = operationNameFromDocument(document);
1572
+ let res;
1573
+ try {
1574
+ res = await doFetch(endpoint, {
1575
+ method: "POST",
1576
+ headers,
1577
+ body: JSON.stringify({ query: document, variables: options?.variables }),
1578
+ ...signal === void 0 ? {} : { signal }
1579
+ });
1580
+ } catch (cause) {
1581
+ if (signal?.aborted === true) {
1582
+ const timeoutMs = options?.timeoutMs ?? defaultQueryTimeoutMs;
1583
+ console.error(
1584
+ JSON.stringify({
1585
+ event: "storefront_query_failed",
1586
+ failure: "timeout",
1587
+ operationName,
1588
+ timeoutMs
1589
+ })
1590
+ );
1591
+ throw new StorefrontQueryTimeoutError(operationName, timeoutMs);
1592
+ }
1593
+ console.error(
1594
+ JSON.stringify({
1595
+ event: "storefront_query_failed",
1596
+ failure: "transport",
1597
+ operationName
1598
+ })
1599
+ );
1600
+ throw new StorefrontUpstreamError(operationName, "Storefront API request failed", { cause });
1601
+ }
1602
+ const opName = operationName;
1603
+ let raw;
1604
+ try {
1605
+ raw = await res.text();
1606
+ } catch (cause) {
1607
+ if (signal?.aborted === true) {
1608
+ const timeoutMs = options?.timeoutMs ?? defaultQueryTimeoutMs;
1609
+ console.error(
1610
+ JSON.stringify({
1611
+ event: "storefront_query_failed",
1612
+ failure: "timeout",
1613
+ operationName,
1614
+ timeoutMs
1615
+ })
1616
+ );
1617
+ throw new StorefrontQueryTimeoutError(operationName, timeoutMs);
1618
+ }
1619
+ console.error(
1620
+ JSON.stringify({
1621
+ event: "storefront_query_failed",
1622
+ failure: "response_body",
1623
+ operationName,
1624
+ status: res.status
1625
+ })
1626
+ );
1627
+ throw new StorefrontUpstreamError(operationName, "Storefront API response body failed", {
1628
+ status: res.status,
1629
+ cause
1630
+ });
1631
+ }
1423
1632
  let body;
1424
1633
  try {
1425
1634
  body = JSON.parse(raw);
@@ -1427,13 +1636,22 @@ function createStorefrontClientCore(opts) {
1427
1636
  console.error(
1428
1637
  `[storefront] /api/storefront non-JSON response: status=${res.status} content-type=${res.headers.get("content-type") ?? ""} len=${raw.length} op="${opName}" body=${raw.slice(0, 180)}`
1429
1638
  );
1430
- throw new Error(`Storefront API returned a non-JSON response (status ${res.status})`);
1639
+ throw new StorefrontUpstreamError(
1640
+ operationName,
1641
+ `Storefront API returned a non-JSON response (status ${String(res.status)})`,
1642
+ { status: res.status }
1643
+ );
1431
1644
  }
1432
1645
  if (!res.ok) {
1433
1646
  const shopCtx = opts.signedShopContextHeaders !== void 0 ? "present" : "absent";
1434
1647
  console.error(
1435
1648
  `[storefront] /api/storefront HTTP ${res.status} endpoint=${endpoint} shopCtx=${shopCtx} op="${opName}" body=${raw.slice(0, 180)}`
1436
1649
  );
1650
+ throw new StorefrontUpstreamError(
1651
+ operationName,
1652
+ `Storefront API returned HTTP ${String(res.status)}`,
1653
+ { status: res.status }
1654
+ );
1437
1655
  }
1438
1656
  if (body.errors?.length) {
1439
1657
  console.error(`[storefront] GraphQL query failed: ${JSON.stringify(body.errors)}`);
@@ -1445,6 +1663,19 @@ function createStorefrontClientCore(opts) {
1445
1663
  );
1446
1664
  throw new Error("Storefront API returned no data");
1447
1665
  }
1666
+ if (timing !== void 0) {
1667
+ console.info(
1668
+ JSON.stringify({
1669
+ event: "storefront_operation_timing",
1670
+ operationName,
1671
+ wallTimeMs: Math.max(0, Date.now() - operationStartedAt),
1672
+ cacheStatus: timing.cacheStatus,
1673
+ queryCount: 1,
1674
+ payloadBytes: raw.length,
1675
+ ...timing.timeoutMs !== void 0 ? { timeoutMs: timing.timeoutMs } : {}
1676
+ })
1677
+ );
1678
+ }
1448
1679
  return body.data;
1449
1680
  };
1450
1681
  return {
@@ -1457,10 +1688,27 @@ function createStorefrontClientCore(opts) {
1457
1688
  query(query, options) {
1458
1689
  const strategy = options?.cache ?? defaultStrategy;
1459
1690
  const versionKey = options?.storefrontApiVersion ?? "";
1691
+ const operationName = operationNameFromDocument(query);
1460
1692
  return opts.cache.run(
1461
1693
  `${localeKey}:${countryKey}:${versionKey}:${hashKey(query, options?.variables)}`,
1462
1694
  strategy,
1463
- () => execute(query, options)
1695
+ () => {
1696
+ const timeoutMs = options?.timeoutMs ?? defaultQueryTimeoutMs;
1697
+ return execute(query, { ...options, timeoutMs }, AbortSignal.timeout(timeoutMs), {
1698
+ cacheStatus: "miss",
1699
+ timeoutMs
1700
+ });
1701
+ },
1702
+ ({ ageSeconds, error }) => {
1703
+ console.error(
1704
+ JSON.stringify({
1705
+ event: "storefront_query_cache_fallback",
1706
+ operationName,
1707
+ staleAgeSeconds: ageSeconds,
1708
+ failure: storefrontFailureKind(error)
1709
+ })
1710
+ );
1711
+ }
1464
1712
  );
1465
1713
  },
1466
1714
  mutate(mutation2, options) {
@@ -1468,6 +1716,14 @@ function createStorefrontClientCore(opts) {
1468
1716
  }
1469
1717
  };
1470
1718
  }
1719
+ function operationNameFromDocument(document) {
1720
+ return /\b(?:query|mutation)\s+([_A-Za-z][_0-9A-Za-z]*)/u.exec(document)?.[1] ?? "anonymous";
1721
+ }
1722
+ function storefrontFailureKind(error) {
1723
+ if (error instanceof StorefrontQueryTimeoutError) return "timeout";
1724
+ if (error instanceof StorefrontUpstreamError) return "upstream";
1725
+ return "unknown";
1726
+ }
1471
1727
  function isHydrogenStorefrontClientOptions(opts) {
1472
1728
  return "storeDomain" in opts && "publicStorefrontToken" in opts && "waitUntil" in opts;
1473
1729
  }
@@ -1477,7 +1733,7 @@ function attachHydrogenResult(client) {
1477
1733
  function headersRecord(headers) {
1478
1734
  if (headers === void 0) return {};
1479
1735
  const result = {};
1480
- new Headers(headers).forEach((value, key) => {
1736
+ normalizeStorefrontHeaders(headers).forEach((value, key) => {
1481
1737
  result[key] = value;
1482
1738
  });
1483
1739
  return result;
@@ -1548,6 +1804,24 @@ async function localSignedShopContextHeaders(key, siteId, hostname) {
1548
1804
  }
1549
1805
  var CACHE_NAME = "kotao-storefront-v1";
1550
1806
  var CACHE_VERSION_HEADER = "x-kotao-cache-version";
1807
+ var REQUEST_ID_HEADER = "x-kotao-request-id";
1808
+ var DEPLOYMENT_ID_HEADER = "x-kotao-deployment-id";
1809
+ function trustedShopOrigin(value) {
1810
+ if (!value) return void 0;
1811
+ try {
1812
+ const url = new URL(value);
1813
+ if (url.protocol !== "https:" || url.username || url.password || url.port || url.pathname !== "/" || url.search || url.hash) {
1814
+ return void 0;
1815
+ }
1816
+ return url.origin;
1817
+ } catch {
1818
+ return void 0;
1819
+ }
1820
+ }
1821
+ function trustedShopOriginFromHeaders(headers) {
1822
+ if (!headers.has(SHOP_HEADER) || !headers.has(SHOP_SIGNATURE_HEADER)) return void 0;
1823
+ return trustedShopOrigin(headers.get(SHOP_ORIGIN_HEADER));
1824
+ }
1551
1825
  var API_LOCALE_RE = /^[a-z]{2,3}(-[a-zA-Z]{2,4})?$/;
1552
1826
  var API_COUNTRY_RE = /^[A-Z]{2}$/;
1553
1827
  var SAME_ORIGIN_PATH2 = /^\/(?![/\\])/;
@@ -1605,6 +1879,13 @@ async function createKotaoContext(request, env, executionCtx, deps) {
1605
1879
  const cookieCountry = localizationCookie.country !== null && API_COUNTRY_RE.test(localizationCookie.country) ? localizationCookie.country : null;
1606
1880
  const geoCountry = autoCountry ? geo?.country?.trim().toUpperCase() ?? null : null;
1607
1881
  const apiCountry = cookieCountry ?? (geoCountry !== null && API_COUNTRY_RE.test(geoCountry) ? geoCountry : null);
1882
+ const extraHeaders = {};
1883
+ const requestId = request.headers.get(REQUEST_ID_HEADER)?.trim();
1884
+ if (requestId !== void 0 && requestId.length > 0) extraHeaders[REQUEST_ID_HEADER] = requestId;
1885
+ const deploymentId = request.headers.get(DEPLOYMENT_ID_HEADER)?.trim();
1886
+ if (deploymentId !== void 0 && deploymentId.length > 0) {
1887
+ extraHeaders[DEPLOYMENT_ID_HEADER] = deploymentId;
1888
+ }
1608
1889
  const storefront = createStorefrontClient({
1609
1890
  endpoint: resolvedEnv.STOREFRONT_API_ENDPOINT,
1610
1891
  token: resolvedEnv.STOREFRONT_API_TOKEN,
@@ -1613,22 +1894,34 @@ async function createKotaoContext(request, env, executionCtx, deps) {
1613
1894
  ...signedShopContextHeaders ? { signedShopContextHeaders } : {},
1614
1895
  ...apiLocale !== void 0 ? { locale: apiLocale } : {},
1615
1896
  ...apiCountry !== null ? { country: apiCountry } : {},
1897
+ ...Object.keys(extraHeaders).length > 0 ? { extraHeaders } : {},
1616
1898
  cache
1617
1899
  });
1900
+ const eventHeaders = new Headers(signedShopContextHeaders);
1901
+ if (apiLocale !== void 0) eventHeaders.set("x-kotao-locale", apiLocale);
1902
+ if (apiCountry !== null) eventHeaders.set("x-kotao-country", apiCountry);
1903
+ for (const [name, value] of Object.entries(extraHeaders)) eventHeaders.set(name, value);
1904
+ const events = createEventsClient({
1905
+ baseUrl: resolvedEnv.STOREFRONT_API_ENDPOINT,
1906
+ fetch,
1907
+ defaultHeaders: eventHeaders
1908
+ });
1618
1909
  const cart = createCartHandler({ request, storefront });
1619
1910
  const contextSession = withSessionPresenceHint(
1620
1911
  deps?.session,
1621
- readCustomerSessionToken(request.headers.get("cookie")) !== null
1912
+ readHostedCustomerSessionToken(request.headers.get("cookie")) !== null || readCustomerSessionToken(request.headers.get("cookie")) !== null
1622
1913
  );
1623
1914
  const customerAccount = createCustomerAccountClient({
1624
1915
  request,
1625
1916
  customerAccountId: resolvedEnv.PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID,
1626
1917
  shopId: resolvedEnv.SHOP_ID,
1627
- session: contextSession
1918
+ session: contextSession,
1919
+ shopOrigin: trustedShopOriginFromHeaders(request.headers) ?? (inboundShop === null && inboundShopSignature === null ? trustedShopOrigin(resolvedEnv.STOREFRONT_SHOP_ORIGIN) : void 0)
1628
1920
  });
1629
1921
  const settingsEndpoint = `${resolvedEnv.STOREFRONT_API_ENDPOINT.replace(/\/+$/, "")}/settings`;
1630
1922
  return {
1631
1923
  storefront,
1924
+ events,
1632
1925
  cart,
1633
1926
  customerAccount,
1634
1927
  geo,
@@ -1695,6 +1988,6 @@ function createEphemeralSession(isLoggedIn) {
1695
1988
  };
1696
1989
  }
1697
1990
 
1698
- export { CART_ID_COOKIE, CHECKOUT_CONSENT_CATEGORIES, CHECKOUT_CONSENT_PARAM_PREFIX, CHECKOUT_DESIGN_TOKEN_INPUTS, CUSTOMER_SESSION_COOKIE, CacheCustom, CacheLong, CacheNone, CacheShort, CustomerAccountError, DEFAULT_SESSION_MAX_AGE, GEO_HEADER, GEO_SIGNATURE_HEADER, LOCALIZATION_COOKIE, ROUTING_LOCALE_HEADER, ROUTING_ORIGINAL_PATHNAME_HEADER, StorefrontDefaultCache, buildCartIdCookie, buildCustomerSessionCookie, buildLocalizationCookie, cartGetIdDefault, cartSetIdDefault, clearCustomerSessionCookie, createCache, createCartHandler, createCustomerAccountClient, createKotaoContext, createStorefrontClient, createWithCache, decodeCheckoutConsent, encodeCheckoutConsent, extractCheckoutDesignTokens, generateCacheControlHeader, isLocalePathSegment, localeFromAcceptLanguage, localeFromPathname, localeFromRequest, localizePath, originalPathnameFromRequest, readCartIdCookie, readCustomerSessionToken, readLocalizationCookie, resolveCheckoutUrl, shopHostname, signGeo, stripLocalePrefixFromPathname, stripLocalePrefixFromRequest, verifyGeo };
1699
- //# sourceMappingURL=chunk-7XYWHWT4.js.map
1700
- //# sourceMappingURL=chunk-7XYWHWT4.js.map
1991
+ export { CART_ID_COOKIE, CHECKOUT_CONSENT_CATEGORIES, CHECKOUT_CONSENT_PARAM_PREFIX, CHECKOUT_DESIGN_TOKEN_INPUTS, CUSTOMER_SESSION_COOKIE, CacheCustom, CacheLong, CacheNone, CacheShort, CustomerAccountError, DEFAULT_HOSTED_SESSION_MAX_AGE, DEFAULT_SESSION_MAX_AGE, GEO_HEADER, GEO_SIGNATURE_HEADER, HOSTED_CUSTOMER_CSRF_COOKIE, HOSTED_CUSTOMER_CSRF_HEADER, HOSTED_CUSTOMER_SESSION_COOKIE, LOCALIZATION_COOKIE, MAX_HOSTED_SESSION_MAX_AGE, MAX_LEGACY_CUSTOMER_SESSION_TOKEN_LENGTH, ROUTING_LOCALE_HEADER, ROUTING_ORIGINAL_PATHNAME_HEADER, StorefrontDefaultCache, StorefrontQueryTimeoutError, StorefrontUpstreamError, buildCartIdCookie, buildCustomerSessionCookie, buildHostedCustomerSessionCookie, buildLocalizationCookie, cartGetIdDefault, cartSetIdDefault, clearCustomerSessionCookie, clearHostedCustomerSessionCookie, clearLegacyCustomerSessionCookie, createCache, createCartHandler, createCustomerAccountClient, createKotaoContext, createStorefrontClient, createWithCache, decodeCheckoutConsent, encodeCheckoutConsent, extractCheckoutDesignTokens, generateCacheControlHeader, isLocalePathSegment, localeFromAcceptLanguage, localeFromPathname, localeFromRequest, localizePath, originalPathnameFromRequest, readCartIdCookie, readCustomerSessionToken, readHostedCustomerCsrfToken, readHostedCustomerSessionToken, readLegacyCustomerSessionToken, readLocalizationCookie, resolveCheckoutUrl, shopHostname, signGeo, stripLocalePrefixFromPathname, stripLocalePrefixFromRequest, trustedShopOriginFromHeaders, verifyGeo };
1992
+ //# sourceMappingURL=chunk-4TEFGD42.js.map
1993
+ //# sourceMappingURL=chunk-4TEFGD42.js.map