@occluze/tether 0.6.7 → 0.6.8

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 (135) hide show
  1. package/README.md +3 -3
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-C3NP6HFB.js +1 -0
  4. package/dist/esm/chunks/account-pages-WLK374FO.js +1 -0
  5. package/dist/esm/chunks/analytics-G3SAWMNL.js +1 -0
  6. package/dist/esm/chunks/chunk-27PJJZGC.js +1 -0
  7. package/dist/esm/chunks/chunk-2FSXS55U.js +1 -0
  8. package/dist/esm/chunks/chunk-FCSWQDVV.js +1 -0
  9. package/dist/esm/chunks/chunk-FOMSMIAM.js +1 -0
  10. package/dist/esm/chunks/chunk-GZJTCLLW.js +1 -0
  11. package/dist/esm/chunks/chunk-HEQILZVR.js +1 -0
  12. package/dist/esm/chunks/chunk-JOOEXYF3.js +4 -0
  13. package/dist/esm/chunks/chunk-MPTQYLSM.js +1 -0
  14. package/dist/esm/chunks/chunk-NEHZ37TU.js +3 -0
  15. package/dist/esm/chunks/chunk-V74KGEJF.js +1 -0
  16. package/dist/esm/chunks/chunk-VT4CDUJJ.js +1 -0
  17. package/dist/esm/chunks/chunk-YJLWX2HK.js +1 -0
  18. package/dist/esm/chunks/diagnose-6VVGZNTY.js +14 -0
  19. package/dist/esm/chunks/extras-GJVRDFM3.js +1 -0
  20. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +1 -0
  21. package/dist/esm/chunks/search-OU75RNQL.js +1 -0
  22. package/dist/esm/index.js +1 -1
  23. package/dist/esm/server.js +2 -1
  24. package/dist/presets/barba.esm.js +56 -36
  25. package/dist/tether.account-pages.js +773 -442
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +648 -318
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +318 -191
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +331 -242
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +78 -33
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +3176 -2015
  36. package/dist/tether.markets.js +231 -126
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +0 -0
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +221 -126
  41. package/dist/tether.search.min.js +1 -1
  42. package/package.json +23 -4
  43. package/src/account/addresses-view.js +92 -0
  44. package/src/account/browser-session.js +359 -0
  45. package/src/account/markup.js +48 -0
  46. package/src/account/operations.js +180 -0
  47. package/src/account/order-lines.js +96 -0
  48. package/src/account/order-list.js +91 -0
  49. package/src/account/order-page.js +192 -0
  50. package/src/account/page-events.js +288 -0
  51. package/src/account/server-client.js +172 -0
  52. package/src/account/shared.js +111 -0
  53. package/src/account/shopify-auth.js +150 -0
  54. package/src/account/subscriptions-view.js +158 -0
  55. package/src/account-api.js +118 -0
  56. package/src/account-links.js +35 -0
  57. package/src/account-pages.js +69 -298
  58. package/src/account.js +280 -323
  59. package/src/amounts.js +36 -0
  60. package/src/analytics-shopify.js +241 -0
  61. package/src/analytics.js +304 -184
  62. package/src/api-version.js +75 -25
  63. package/src/astro.js +233 -68
  64. package/src/cart/checkout.js +97 -0
  65. package/src/cart/drawer.js +138 -0
  66. package/src/cart/mutations.js +234 -0
  67. package/src/cart/render.js +294 -0
  68. package/src/cart/store.js +326 -0
  69. package/src/cart.js +320 -488
  70. package/src/client.js +214 -78
  71. package/src/collection.js +126 -57
  72. package/src/config.js +323 -59
  73. package/src/controls/cart-controls.js +155 -0
  74. package/src/controls/discount.js +96 -0
  75. package/src/controls/extras.js +63 -0
  76. package/src/controls/navigation.js +83 -0
  77. package/src/diagnose-markup.js +333 -0
  78. package/src/diagnose.js +251 -202
  79. package/src/dom.js +219 -220
  80. package/src/extras.js +119 -37
  81. package/src/fragments.js +115 -22
  82. package/src/images.js +104 -0
  83. package/src/index.js +245 -287
  84. package/src/initial.js +78 -66
  85. package/src/lazy-iife.js +92 -22
  86. package/src/lazy.js +25 -4
  87. package/src/loader.js +263 -124
  88. package/src/market-codes.js +35 -0
  89. package/src/markets-ui.js +169 -55
  90. package/src/markets.js +286 -85
  91. package/src/markup.js +64 -0
  92. package/src/mounting.js +214 -91
  93. package/src/page-market.js +116 -0
  94. package/src/parts/account-pages.js +4 -1
  95. package/src/parts/account.js +4 -1
  96. package/src/parts/analytics.js +4 -1
  97. package/src/parts/diagnose.js +4 -1
  98. package/src/parts/extras.js +4 -1
  99. package/src/parts/markets.js +4 -1
  100. package/src/parts/search.js +4 -1
  101. package/src/presets/barba.js +120 -30
  102. package/src/product-events.js +130 -0
  103. package/src/product-render.js +257 -0
  104. package/src/product-state.js +318 -0
  105. package/src/product.js +233 -459
  106. package/src/repeaters.js +173 -0
  107. package/src/search.js +277 -107
  108. package/src/selling-plans.js +107 -47
  109. package/src/server.js +168 -205
  110. package/src/shopify-values.js +89 -0
  111. package/src/status.js +99 -39
  112. package/src/store-html.js +0 -0
  113. package/src/storefront/catalog.js +199 -0
  114. package/src/storefront/json-ld.js +167 -0
  115. package/src/variant-render.js +165 -0
  116. package/types/astro.d.ts +13 -2
  117. package/types/index.d.ts +178 -27
  118. package/types/server.d.ts +89 -17
  119. package/dist/esm/chunks/account-Q6E2TFEV.js +0 -1
  120. package/dist/esm/chunks/account-pages-UZXR4R5F.js +0 -1
  121. package/dist/esm/chunks/analytics-IJBKATUP.js +0 -1
  122. package/dist/esm/chunks/chunk-4QJKLPYR.js +0 -1
  123. package/dist/esm/chunks/chunk-64TUD3QR.js +0 -1
  124. package/dist/esm/chunks/chunk-AJLPU2JC.js +0 -4
  125. package/dist/esm/chunks/chunk-FIOZPPC5.js +0 -1
  126. package/dist/esm/chunks/chunk-K2YEQAIH.js +0 -1
  127. package/dist/esm/chunks/chunk-KIDH76B7.js +0 -1
  128. package/dist/esm/chunks/chunk-PAYT5ZD7.js +0 -1
  129. package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
  130. package/dist/esm/chunks/chunk-SSJT43CJ.js +0 -1
  131. package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
  132. package/dist/esm/chunks/diagnose-3WVAWNTX.js +0 -14
  133. package/dist/esm/chunks/extras-7I7WJX35.js +0 -1
  134. package/dist/esm/chunks/markets-ui-545XLFTE.js +0 -1
  135. package/dist/esm/chunks/search-NILMGYXG.js +0 -1
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  (() => {
2
3
  var __defProp = Object.defineProperty;
3
4
  var __export = (target, all) => {
@@ -19,7 +20,7 @@
19
20
  mountAccount: () => mountAccount,
20
21
  order: () => order,
21
22
  orders: () => orders,
22
- query: () => query,
23
+ query: () => query2,
23
24
  render: () => render,
24
25
  requestReturn: () => requestReturn,
25
26
  saveAddress: () => saveAddress,
@@ -30,7 +31,7 @@
30
31
 
31
32
  // tether-core:config.js
32
33
  var m = window.Tether.__core["config.js"];
33
- var { DEFAULTS, cfg, configure, isConfigured, log, reportError, warn } = m;
34
+ var { DEFAULTS, cfg, configure, isConfigured, log, reportError, resolveConfig, toMetafieldIdentifier, warn } = m;
34
35
 
35
36
  // tether-core:client.js
36
37
  var m2 = window.Tether.__core["client.js"];
@@ -38,422 +39,751 @@
38
39
 
39
40
  // tether-core:dom.js
40
41
  var m3 = window.Tether.__core["dom.js"];
41
- var { attr, busy, cdnUrl, dataKey, emit, formatDate, formatMoney, formatPercent, get, has, hasTemplate, imageUrl, notifyLayoutChanged, parseJSONAttr, productUrl, qsa, readText, repeat, repeatKeyed, sameImage, scoped, sel, setImage, setSelected, setText, setTextTracked, show, toAttributes, watchImages } = m3;
42
+ var { attr, busy, cdnUrl, emit, formatDate, formatMoney, formatPercent, get, has, hasTemplate, imageUrl, notifyLayoutChanged, parseJSONAttr, productUrl, qsa, repeat, repeatKeyed, sameImage, scoped, sel, setImage, setSelected, setStoreHtml, setText, setTextTracked, show, toAttributes, watchImages } = m3;
42
43
 
43
44
  // tether-core:cart.js
44
45
  var m4 = window.Tether.__core["cart.js"];
45
- var { Cart, focusables, toLineInput } = m4;
46
+ var { Cart, trapFocus } = m4;
46
47
 
47
48
  // tether-core:lazy.js
48
49
  var m5 = window.Tether.__core["lazy.js"];
49
50
  var { coreModules, loadPart } = m5;
50
51
 
51
- // src/account.js
52
- var KEY = () => `${cfg.prefix}:account:${cfg.domain}`;
53
- var PENDING = () => `${cfg.prefix}:account-login:${cfg.domain}`;
54
- var store = () => cfg.account?.remember ? window.localStorage : window.sessionStorage;
55
- var b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
56
- var random = () => b64url(crypto.getRandomValues(new Uint8Array(32)));
57
- var sha256 = async (s) => b64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s)));
58
- var go = (url) => (cfg.account?.navigate || ((u) => window.location.assign(u)))(url);
59
- var read = (s, k) => {
52
+ // server/src/shared/account-documents.js
53
+ var MONEY = "amount currencyCode";
54
+ var CUSTOMER_QUERY = "query TetherCustomer { customer { id firstName lastName displayName emailAddress { emailAddress } defaultAddress { formatted } } }";
55
+ var ORDERS_QUERY = `query TetherOrders($first:Int!){customer{orders(first:$first,sortKey:PROCESSED_AT,reverse:true){nodes{id name processedAt financialStatus fulfillmentStatus statusPageUrl totalPrice{${MONEY}}lineItems(first:20){nodes{title variantTitle quantity image{url altText}}}}}}}`;
56
+
57
+ // server/src/shared/base64url.js
58
+ function toBase64Url(bytes) {
59
+ return btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
60
+ }
61
+ function fromBase64Url(text) {
62
+ const base64 = text.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(text.length / 4) * 4, "=");
63
+ return Uint8Array.from(atob(base64), (char) => char.charCodeAt(0));
64
+ }
65
+ function randomToken(bytes = 32) {
66
+ return toBase64Url(crypto.getRandomValues(new Uint8Array(bytes)));
67
+ }
68
+
69
+ // src/account/shared.js
70
+ var sessionKey = () => `${cfg.prefix}:account:${cfg.domain}`;
71
+ var pendingLoginKey = () => `${cfg.prefix}:account-login:${cfg.domain}`;
72
+ var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
73
+ var sessionArea = () => cfg.account?.remember ? "local" : "session";
74
+ var storageOf = (area) => area === "local" ? window.localStorage : window.sessionStorage;
75
+ function readJsonIfAllowed(area, key) {
60
76
  try {
61
- return JSON.parse(s.getItem(k) || "null");
77
+ return JSON.parse(storageOf(area).getItem(key) ?? "null");
62
78
  } catch {
63
79
  return null;
64
80
  }
65
- };
66
- var write = (s, k, v) => {
81
+ }
82
+ function writeJsonIfAllowed(area, key, value) {
67
83
  try {
68
- if (v) s.setItem(k, JSON.stringify(v));
69
- else s.removeItem(k);
84
+ if (value === null) storageOf(area).removeItem(key);
85
+ else storageOf(area).setItem(key, JSON.stringify(value));
70
86
  } catch {
71
87
  }
72
- };
73
- var discovery = null;
74
- var session = null;
75
- var refreshing = null;
76
- var customerCache = null;
77
- var server = () => cfg.account?.server ? new URL(String(cfg.account.server), window.location.href).href.replace(/\/$/, "") : "";
78
- var HINT = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
79
- var serverState = null;
80
- async function serverFetch(path, { method = "GET", body } = {}) {
81
- const res = await fetch(`${server()}${path}`, {
82
- method,
83
- credentials: "include",
84
- headers: method === "POST" ? { "Content-Type": "application/json", "X-Tether-Account": "1" } : {},
85
- body: body ? JSON.stringify(body) : void 0
86
- });
87
- const data = await res.json().catch(() => ({}));
88
- if (!res.ok && res.status !== 401) throw new TetherError(`Customer accounts: ${data.error || res.status}`, { status: res.status, userErrors: data.userErrors });
89
- return data;
90
88
  }
91
- async function serverMe() {
92
- serverState || (serverState = await serverFetch("/me"));
89
+ function setLoginHint(loggedIn) {
90
+ writeJsonIfAllowed("local", hintKey(), loggedIn ? 1 : null);
91
+ }
92
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
93
+ var textOrNull = (value) => typeof value === "string" ? value : null;
94
+ async function readJsonObjectOrEmpty(response) {
95
+ let json;
93
96
  try {
94
- if (serverState.loggedIn) window.localStorage.setItem(HINT(), "1");
95
- else window.localStorage.removeItem(HINT());
97
+ json = await response.json();
96
98
  } catch {
99
+ return {};
97
100
  }
98
- return serverState;
101
+ return isRecord(json) ? json : {};
99
102
  }
100
- async function discover() {
101
- if (discovery) return discovery;
103
+ function navigate(url) {
104
+ const replacement = cfg.account?.navigate;
105
+ if (replacement) replacement(url);
106
+ else window.location.assign(url);
107
+ }
108
+ var loginLanguage = (locale) => locale || cfg.account?.locale || (cfg.locale || "").split("-")[0] || "";
109
+ var loginCountry = () => cfg.country ? cfg.country.toUpperCase() : "";
110
+
111
+ // src/account/shopify-auth.js
112
+ var DEFAULT_TOKEN_SECONDS = 3600;
113
+ var endpoints = null;
114
+ async function wellKnown(document2) {
102
115
  const shop = cfg.account?.shopDomain || cfg.domain;
103
- const get2 = (p) => fetch(`https://${shop}/.well-known/${p}`).then((r) => {
104
- if (!r.ok) throw new TetherError(`Customer accounts: ${p} answered ${r.status}`, { status: r.status });
105
- return r.json();
106
- });
107
- const [oidc, api] = await Promise.all([get2("openid-configuration"), get2("customer-account-api")]);
108
- discovery = { ...oidc, graphql: api.graphql_api };
109
- return discovery;
116
+ const response = await fetch(`https://${shop}/.well-known/${document2}`);
117
+ if (!response.ok) {
118
+ throw new TetherError(`Customer accounts: ${document2} answered ${response.status}`, { status: response.status });
119
+ }
120
+ const json = (
121
+ /** @type {unknown} */
122
+ await response.json()
123
+ );
124
+ return isRecord(json) ? json : {};
110
125
  }
111
- var callbackUrl = () => cfg.account?.callbackUrl || `${window.location.origin}${window.location.pathname}`;
112
- function isLoggedIn() {
113
- return server() ? Boolean(serverState?.loggedIn) : Boolean(session?.refresh || session?.access && session.expiresAt > Date.now());
126
+ function endpointIn(json, name) {
127
+ const url = textOrNull(json[name]);
128
+ if (!url) throw new TetherError(`Customer accounts: Shopify named no ${name}`);
129
+ return url;
114
130
  }
115
- async function login({ returnTo = window.location.href, locale } = {}) {
116
- if (server()) {
117
- const u2 = new URL(`${server()}/login`);
118
- u2.searchParams.set("returnTo", returnTo);
119
- const lang2 = locale || cfg.account.locale || (cfg.locale || "").split("-")[0];
120
- if (lang2) u2.searchParams.set("locale", lang2);
121
- if (cfg.country) u2.searchParams.set("country", String(cfg.country).toUpperCase());
122
- go(u2.toString());
123
- return;
131
+ async function discover() {
132
+ if (endpoints) return endpoints;
133
+ const [openid, api] = await Promise.all([wellKnown("openid-configuration"), wellKnown("customer-account-api")]);
134
+ endpoints = {
135
+ issuer: endpointIn(openid, "issuer"),
136
+ authorizationEndpoint: endpointIn(openid, "authorization_endpoint"),
137
+ tokenEndpoint: endpointIn(openid, "token_endpoint"),
138
+ endSessionEndpoint: endpointIn(openid, "end_session_endpoint"),
139
+ graphql: endpointIn(api, "graphql_api")
140
+ };
141
+ return endpoints;
142
+ }
143
+ async function requestToken(params) {
144
+ const { tokenEndpoint } = await discover();
145
+ const response = await fetch(cfg.account?.tokenUrl || tokenEndpoint, {
146
+ method: "POST",
147
+ body: new URLSearchParams(params)
148
+ });
149
+ const data = await readJsonObjectOrEmpty(response);
150
+ const access = textOrNull(data["access_token"]);
151
+ if (!response.ok || !access) {
152
+ const ended = response.status === 400 || response.status === 401;
153
+ throw new TetherError(`Customer accounts: token ${response.status} ${textOrNull(data["error"]) ?? ""}`.trim(), {
154
+ status: response.status,
155
+ code: ended ? "SESSION_ENDED" : "TOKEN_FAILED"
156
+ });
124
157
  }
125
- if (!cfg.account?.clientId) throw new TetherError("Customer accounts: set account.clientId", { code: "NOT_CONFIGURED" });
126
- const d = await discover();
127
- const state = random();
128
- const nonce = random();
129
- const verifier = random();
130
- const redirect = callbackUrl();
131
- write(window.sessionStorage, PENDING(), { state, nonce, verifier, redirect, returnTo, at: Date.now() });
132
- const u = new URL(d.authorization_endpoint);
133
- const p = {
134
- client_id: cfg.account.clientId,
135
- scope: "openid email customer-account-api:full",
136
- response_type: "code",
137
- redirect_uri: redirect,
138
- state,
139
- nonce,
140
- code_challenge: await sha256(verifier),
141
- code_challenge_method: "S256"
158
+ return {
159
+ access,
160
+ refresh: textOrNull(data["refresh_token"]) || null,
161
+ idToken: textOrNull(data["id_token"]) || null,
162
+ expiresInSeconds: Number(data["expires_in"]) || DEFAULT_TOKEN_SECONDS
142
163
  };
143
- const lang = locale || cfg.account.locale || (cfg.locale || "").split("-")[0];
144
- if (lang) p.locale = lang;
145
- if (cfg.country) p.region_country = String(cfg.country).toUpperCase();
146
- Object.entries(p).forEach(([k, v]) => u.searchParams.set(k, v));
147
- go(u.toString());
148
- }
149
- async function tokenRequest(params) {
150
- const d = await discover();
151
- const res = await fetch(cfg.account.tokenUrl || d.token_endpoint, { method: "POST", body: new URLSearchParams(params) });
152
- const data = await res.json().catch(() => ({}));
153
- if (!res.ok || !data.access_token) throw new TetherError(`Customer accounts: token ${res.status} ${data.error || ""}`.trim(), { status: res.status, code: res.status === 400 || res.status === 401 ? "SESSION_ENDED" : "TOKEN_FAILED" });
154
- return data;
155
- }
156
- function decodeJwt(t) {
164
+ }
165
+ function idTokenClaims(idToken) {
166
+ const payload = idToken.split(".")[1];
167
+ if (!payload) return null;
168
+ let claims;
157
169
  try {
158
- return JSON.parse(atob(t.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
170
+ claims = JSON.parse(new TextDecoder().decode(fromBase64Url(payload)));
159
171
  } catch {
160
172
  return null;
161
173
  }
174
+ return isRecord(claims) ? claims : null;
175
+ }
176
+ function answersLogin(idToken, { nonce, issuer, client }) {
177
+ const claims = idToken ? idTokenClaims(idToken) : null;
178
+ if (!claims) return false;
179
+ const audience = claims["aud"];
180
+ const audiences = Array.isArray(audience) ? audience : [audience];
181
+ const expiresAt = claims["exp"];
182
+ return claims["nonce"] === nonce && claims["iss"] === issuer && audiences.includes(client) && typeof expiresAt === "number" && expiresAt * 1e3 >= Date.now();
183
+ }
184
+
185
+ // src/account/browser-session.js
186
+ var REFRESH_MARGIN_SECONDS = 120;
187
+ var LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
188
+ var session = null;
189
+ var customerProfile = null;
190
+ var refreshing = null;
191
+ function toSession(value) {
192
+ if (!isRecord(value)) return null;
193
+ const access = value["access"];
194
+ const expiresAt = value["expiresAt"];
195
+ if (typeof access !== "string" || typeof expiresAt !== "number") return null;
196
+ return { access, expiresAt, refresh: textOrNull(value["refresh"]), idToken: textOrNull(value["idToken"]) };
162
197
  }
163
- function keep(data, previous = session) {
198
+ function toPendingLogin(value) {
199
+ if (!isRecord(value)) return null;
200
+ const { state, nonce, verifier, redirect, returnTo, at } = value;
201
+ if (typeof state !== "string" || typeof nonce !== "string" || typeof verifier !== "string") return null;
202
+ if (typeof redirect !== "string" || typeof returnTo !== "string" || typeof at !== "number") return null;
203
+ return { state, nonce, verifier, redirect, returnTo, at };
204
+ }
205
+ var currentSession = () => session ?? toSession(readJsonIfAllowed(sessionArea(), sessionKey()));
206
+ var accessToken = () => session?.access ?? null;
207
+ var hasBrowserLogin = () => Boolean(session && (session.refresh || session.access && session.expiresAt > Date.now()));
208
+ function clientId() {
209
+ const id = cfg.account?.clientId;
210
+ if (!id) throw new TetherError("Customer accounts: set account.clientId", { code: "NOT_CONFIGURED" });
211
+ return id;
212
+ }
213
+ var sha256 = async (text) => toBase64Url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)));
214
+ async function browserLoginRedirect({ returnTo, locale }) {
215
+ const client = clientId();
216
+ const endpoints2 = await discover();
217
+ const pending = {
218
+ state: randomToken(),
219
+ nonce: randomToken(),
220
+ verifier: randomToken(),
221
+ redirect: cfg.account?.callbackUrl || `${window.location.origin}${window.location.pathname}`,
222
+ returnTo,
223
+ at: Date.now()
224
+ };
225
+ writeJsonIfAllowed("session", pendingLoginKey(), pending);
226
+ const url = new URL(endpoints2.authorizationEndpoint);
227
+ const params = {
228
+ client_id: client,
229
+ scope: "openid email customer-account-api:full",
230
+ response_type: "code",
231
+ redirect_uri: pending.redirect,
232
+ state: pending.state,
233
+ nonce: pending.nonce,
234
+ code_challenge: await sha256(pending.verifier),
235
+ code_challenge_method: "S256"
236
+ };
237
+ const language = loginLanguage(locale);
238
+ if (language) params["locale"] = language;
239
+ const country = loginCountry();
240
+ if (country) params["region_country"] = country;
241
+ Object.entries(params).forEach(([name, value]) => {
242
+ url.searchParams.set(name, value);
243
+ });
244
+ navigate(url.toString());
245
+ }
246
+ function keep(answer, previous) {
164
247
  session = {
165
- access: data.access_token,
166
- expiresAt: Date.now() + Math.max(0, (Number(data.expires_in) || 3600) - 120) * 1e3,
167
- refresh: data.refresh_token || previous?.refresh,
168
- idToken: data.id_token || previous?.idToken
248
+ access: answer.access,
249
+ expiresAt: Date.now() + Math.max(0, answer.expiresInSeconds - REFRESH_MARGIN_SECONDS) * 1e3,
250
+ refresh: answer.refresh ?? previous?.refresh ?? null,
251
+ idToken: answer.idToken ?? previous?.idToken ?? null
169
252
  };
170
- write(store(), KEY(), session);
253
+ writeJsonIfAllowed(sessionArea(), sessionKey(), session);
171
254
  }
172
255
  async function handleCallback() {
173
- const q = new URLSearchParams(window.location.search);
174
- const pending = read(window.sessionStorage, PENDING());
175
- if (!pending || !q.has("code") && !q.has("error") || q.get("state") !== pending.state) return false;
176
- write(window.sessionStorage, PENDING(), null);
256
+ const params = new URLSearchParams(window.location.search);
257
+ const pending = toPendingLogin(readJsonIfAllowed("session", pendingLoginKey()));
258
+ if (!pending || !params.has("code") && !params.has("error") || params.get("state") !== pending.state) return false;
259
+ writeJsonIfAllowed("session", pendingLoginKey(), null);
177
260
  const clean = new URL(window.location.href);
178
- ["code", "state", "iss", "error", "error_description"].forEach((k) => clean.searchParams.delete(k));
261
+ ["code", "state", "iss", "error", "error_description"].forEach((name) => {
262
+ clean.searchParams.delete(name);
263
+ });
179
264
  window.history.replaceState(window.history.state, "", clean);
180
- if (q.has("error")) {
181
- emit("account-error", { error: q.get("error") });
265
+ const error = params.get("error");
266
+ if (error !== null) {
267
+ emit("account-error", { error });
182
268
  return false;
183
269
  }
184
- if (Date.now() - pending.at > 10 * 6e4) throw new TetherError("Customer accounts: the login took too long, please try again");
185
- const data = await tokenRequest({
270
+ if (Date.now() - pending.at > LOGIN_TIMEOUT_MS) {
271
+ throw new TetherError("Customer accounts: the login took too long, please try again");
272
+ }
273
+ const client = clientId();
274
+ const answer = await requestToken({
186
275
  grant_type: "authorization_code",
187
- client_id: cfg.account.clientId,
276
+ client_id: client,
188
277
  redirect_uri: pending.redirect,
189
- code: q.get("code"),
278
+ code: params.get("code") ?? "",
190
279
  code_verifier: pending.verifier
191
280
  });
192
- const d = await discover();
193
- const claims = decodeJwt(data.id_token || "");
194
- const aud = [].concat(claims?.aud || []);
195
- if (!claims || claims.nonce !== pending.nonce || claims.iss !== d.issuer || !aud.includes(cfg.account.clientId) || claims.exp * 1e3 < Date.now()) {
281
+ const { issuer } = await discover();
282
+ if (!answersLogin(answer.idToken, { nonce: pending.nonce, issuer, client })) {
196
283
  throw new TetherError("Customer accounts: the login answer did not check out (nonce/issuer/audience)");
197
284
  }
198
- keep(data, null);
285
+ keep(answer, null);
199
286
  await linkCart();
200
287
  emit("account-login", {});
201
- const back = pending.returnTo && new URL(pending.returnTo, window.location.href);
202
- if (back && back.origin === window.location.origin && back.href !== clean.href) go(back.href);
288
+ const back = pending.returnTo ? new URL(pending.returnTo, window.location.href) : null;
289
+ if (back?.origin === window.location.origin && back.href !== clean.href) navigate(back.href);
203
290
  return true;
204
291
  }
292
+ function endSession() {
293
+ session = null;
294
+ customerProfile = null;
295
+ writeJsonIfAllowed(sessionArea(), sessionKey(), null);
296
+ Cart.setCustomer({ accessToken: null });
297
+ }
298
+ async function refresh(expired) {
299
+ try {
300
+ const answer = await requestToken({
301
+ grant_type: "refresh_token",
302
+ client_id: clientId(),
303
+ refresh_token: expired.refresh ?? ""
304
+ });
305
+ keep(answer, expired);
306
+ await linkCart();
307
+ } catch (error) {
308
+ if (error instanceof TetherError && error.code === "SESSION_ENDED") endSession();
309
+ else throw error;
310
+ }
311
+ }
205
312
  async function getToken() {
206
- if (server()) return null;
207
- if (!session) session = read(store(), KEY());
313
+ session = currentSession();
208
314
  if (!session) return null;
209
315
  if (session.access && session.expiresAt > Date.now()) return session.access;
210
316
  if (!session.refresh) {
211
317
  endSession();
212
318
  return null;
213
319
  }
214
- refreshing || (refreshing = (async () => {
215
- try {
216
- keep(await tokenRequest({ grant_type: "refresh_token", client_id: cfg.account.clientId, refresh_token: session.refresh }));
217
- await linkCart();
218
- } catch (err) {
219
- if (err.code === "SESSION_ENDED") endSession();
220
- else throw err;
221
- } finally {
222
- refreshing = null;
223
- }
224
- })());
320
+ const expired = session;
321
+ refreshing ?? (refreshing = refresh(expired).finally(() => {
322
+ refreshing = null;
323
+ }));
225
324
  await refreshing;
226
- return session?.access || null;
227
- }
228
- function endSession() {
229
- session = null;
230
- customerCache = null;
231
- write(store(), KEY(), null);
232
- Cart.customerAccessToken = null;
325
+ return accessToken();
233
326
  }
234
327
  async function linkCart() {
235
- Cart.customerAccessToken = session?.access || null;
236
- if (Cart.id() && session?.access) {
237
- await Cart.setBuyerIdentity({ customerAccessToken: session.access }, { create: false }).catch((err) => reportError(err, "account cart"));
328
+ const token = accessToken();
329
+ Cart.setCustomer({ accessToken: token });
330
+ if (!Cart.id() || !token) return;
331
+ try {
332
+ await Cart.setBuyerIdentity({ customerAccessToken: token }, { create: false });
333
+ } catch (error) {
334
+ reportError(error, "account cart");
238
335
  }
239
336
  }
240
- async function query(q, variables = {}) {
241
- if (server()) throw new TetherError("Customer accounts in server mode only answer customer() and orders()", { code: "NOT_ALLOWED" });
337
+ async function unlinkCart() {
338
+ if (!Cart.id()) return;
339
+ try {
340
+ await Cart.setBuyerIdentity({ customerAccessToken: null }, { create: false });
341
+ } catch (error) {
342
+ reportError(error, "account cart unlink");
343
+ writeJsonIfAllowed("local", cfg.storageKey, null);
344
+ }
345
+ }
346
+ async function browserLogout(returnTo) {
347
+ const idToken = currentSession()?.idToken ?? null;
348
+ await unlinkCart();
349
+ endSession();
350
+ emit("account-logout", {});
351
+ if (!idToken) return "stayed";
352
+ const url = new URL((await discover()).endSessionEndpoint);
353
+ url.searchParams.set("id_token_hint", idToken);
354
+ url.searchParams.set("post_logout_redirect_uri", returnTo);
355
+ navigate(url.toString());
356
+ return "redirected";
357
+ }
358
+ async function query(document2, variables = {}) {
242
359
  const token = await getToken();
243
360
  if (!token) throw new TetherError("Not logged in", { code: "NOT_LOGGED_IN" });
244
- const d = await discover();
245
- const run = (t) => fetch(d.graphql, { method: "POST", headers: { "Content-Type": "application/json", Authorization: t }, body: JSON.stringify({ query: q, variables }) });
246
- let res = await run(token);
247
- if (res.status === 401 && session?.refresh) {
361
+ const { graphql } = await discover();
362
+ const send = (accessToken2) => fetch(graphql, {
363
+ method: "POST",
364
+ headers: { "Content-Type": "application/json", Authorization: accessToken2 },
365
+ body: JSON.stringify({ query: document2, variables })
366
+ });
367
+ let response = await send(token);
368
+ if (response.status === 401 && session?.refresh) {
248
369
  session.expiresAt = 0;
249
- res = await run(await getToken());
370
+ response = await send(await getToken() ?? "");
371
+ }
372
+ const json = await readJsonObjectOrEmpty(response);
373
+ const errors = Array.isArray(json["errors"]) ? json["errors"] : [];
374
+ if (!response.ok || errors.length) {
375
+ const messages = errors.map((error) => isRecord(error) ? textOrNull(error["message"]) : null).filter(Boolean);
376
+ throw new TetherError(messages.join(" | ") || `Customer accounts: ${response.status}`, {
377
+ status: response.status
378
+ });
250
379
  }
251
- const json = await res.json().catch(() => ({}));
252
- if (!res.ok || json.errors?.length) throw new TetherError(json.errors?.map((e) => e.message).join(" | ") || `Customer accounts: ${res.status}`, { status: res.status });
253
- return json.data;
380
+ return (
381
+ /** @type {T} */
382
+ json["data"]
383
+ );
254
384
  }
255
- async function customer() {
256
- if (server()) {
257
- const me = await serverMe();
258
- return me.loggedIn ? { ...me.customer, emailAddress: { emailAddress: me.customer.email }, defaultAddress: { formatted: me.customer.address } } : null;
385
+ async function browserCustomer() {
386
+ if (customerProfile) return customerProfile;
387
+ const data = await query(CUSTOMER_QUERY);
388
+ customerProfile = data.customer;
389
+ return customerProfile;
390
+ }
391
+ function forgetBrowserCustomer() {
392
+ customerProfile = null;
393
+ }
394
+
395
+ // src/account/server-client.js
396
+ var known = null;
397
+ function serverBase() {
398
+ const server = cfg.account?.server;
399
+ if (!server) return "";
400
+ return new URL(server, window.location.href).href.replace(/\/$/, "");
401
+ }
402
+ async function serverFetch(path, { method = "GET", body } = {}) {
403
+ const init = { method, credentials: "include" };
404
+ if (method === "POST") init.headers = { "Content-Type": "application/json", "X-Tether-Account": "1" };
405
+ if (body) init.body = JSON.stringify(body);
406
+ const response = await fetch(`${serverBase()}${path}`, init);
407
+ if (response.status === 401) return { kind: "logged-out" };
408
+ const data = await readJsonObjectOrEmpty(response);
409
+ if (!response.ok) {
410
+ const reason = textOrNull(data["error"]) ?? String(response.status);
411
+ const userErrors = Array.isArray(data["userErrors"]) ? (
412
+ // The Worker sends Shopify's own `userErrors` list unchanged.
413
+ /** @type {UserError[]} */
414
+ data["userErrors"]
415
+ ) : void 0;
416
+ throw new TetherError(`Customer accounts: ${reason}`, {
417
+ status: response.status,
418
+ ...userErrors && { userErrors }
419
+ });
259
420
  }
260
- if (customerCache) return customerCache;
261
- const data = await query(`query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}`);
262
- customerCache = data.customer;
263
- return customerCache;
421
+ return { kind: "answer", data };
264
422
  }
265
- async function orders({ first = 10 } = {}) {
266
- if (server()) return (await serverFetch(`/orders?first=${first}`)).orders || [];
267
- const data = await query(`query TetherOrders($first:Int!){customer{orders(first:$first,sortKey:PROCESSED_AT,reverse:true){nodes{id name processedAt financialStatus fulfillmentStatus statusPageUrl totalPrice{amount currencyCode}lineItems(first:20){nodes{title variantTitle quantity image{url altText}}}}}}}`, { first });
268
- return data.customer?.orders?.nodes || [];
423
+ function toServerCustomer(value) {
424
+ const customer2 = isRecord(value) ? value : {};
425
+ const address = customer2["address"];
426
+ return {
427
+ firstName: textOrNull(customer2["firstName"]),
428
+ lastName: textOrNull(customer2["lastName"]),
429
+ displayName: textOrNull(customer2["displayName"]),
430
+ email: textOrNull(customer2["email"]),
431
+ address: Array.isArray(address) ? address.filter((line) => typeof line === "string") : null
432
+ };
269
433
  }
270
- async function logout({ returnTo = cfg.account?.logoutUrl || `${window.location.origin}/` } = {}) {
271
- if (server()) {
272
- const r = await serverFetch("/logout", { method: "POST", body: { returnTo, cartId: Cart.id() } });
273
- serverState = { loggedIn: false };
274
- try {
275
- window.localStorage.removeItem(HINT());
276
- } catch {
277
- }
278
- emit("account-logout", {});
279
- if (r.logoutUrl) go(r.logoutUrl);
280
- else render(document);
281
- return;
434
+ async function serverLogin() {
435
+ if (!known) {
436
+ const answer = await serverFetch("/me");
437
+ known = answer.kind === "answer" && answer.data["loggedIn"] === true ? { loggedIn: true, customer: toServerCustomer(answer.data["customer"]) } : { loggedIn: false };
282
438
  }
283
- const idToken = session?.idToken || read(store(), KEY())?.idToken;
284
- if (Cart.id()) await Cart.setBuyerIdentity({ customerAccessToken: null }, { create: false }).catch(() => {
285
- });
286
- endSession();
439
+ setLoginHint(known.loggedIn);
440
+ return known;
441
+ }
442
+ var isServerLoggedIn = () => known?.loggedIn === true;
443
+ function forgetServerLogin() {
444
+ known = null;
445
+ }
446
+ function serverLoginRedirect({ returnTo, locale }) {
447
+ const url = new URL(`${serverBase()}/login`);
448
+ url.searchParams.set("returnTo", returnTo);
449
+ const language = loginLanguage(locale);
450
+ if (language) url.searchParams.set("locale", language);
451
+ const country = loginCountry();
452
+ if (country) url.searchParams.set("country", country);
453
+ navigate(url.toString());
454
+ }
455
+ async function serverCheckoutUrl() {
456
+ const cartId = Cart.id();
457
+ if (!(await serverLogin()).loggedIn || !cartId) return null;
458
+ const answer = await serverFetch("/cart", { method: "POST", body: { cartId } });
459
+ if (answer.kind === "logged-out") return null;
460
+ const url = textOrNull(answer.data["checkoutUrl"]);
461
+ if (!url) return null;
462
+ return `${url}${url.includes("?") ? "&" : "?"}logged_in=true`;
463
+ }
464
+ async function serverLogout(returnTo) {
465
+ const answer = await serverFetch("/logout", { method: "POST", body: { returnTo, cartId: Cart.id() } });
466
+ known = null;
467
+ setLoginHint(false);
287
468
  emit("account-logout", {});
288
- if (!idToken) {
289
- render(document);
290
- return;
291
- }
292
- const d = await discover();
293
- const u = new URL(d.end_session_endpoint);
294
- u.searchParams.set("id_token_hint", idToken);
295
- u.searchParams.set("post_logout_redirect_uri", returnTo);
296
- go(u.toString());
469
+ const logoutUrl = answer.kind === "answer" ? textOrNull(answer.data["logoutUrl"]) : null;
470
+ if (!logoutUrl) return "stayed";
471
+ navigate(logoutUrl);
472
+ return "redirected";
297
473
  }
298
- var https = (u) => {
474
+
475
+ // tether-core:shopify-values.js
476
+ var m6 = window.Tether.__core["shopify-values.js"];
477
+ var { STORE_ENV_NAMES, formatMoney: formatMoney2, isHttpsUrl, isSecretShopifyToken, productKey, productUrl: productUrl2 } = m6;
478
+
479
+ // src/account-links.js
480
+ function localDate(iso) {
299
481
  try {
300
- return new URL(u).protocol === "https:";
301
- } catch {
302
- return false;
482
+ return new Date(iso).toLocaleDateString(cfg.locale);
483
+ } catch (error) {
484
+ if (error instanceof RangeError) return iso;
485
+ throw error;
303
486
  }
304
- };
305
- var date = (s) => {
306
- try {
307
- return new Date(s).toLocaleDateString(cfg.locale || void 0);
308
- } catch {
309
- return s;
487
+ }
488
+ function orderUrl(order2) {
489
+ const template = cfg.account?.orderUrl;
490
+ if (template) {
491
+ const numericId = order2.id.split("/").pop()?.split("?")[0] ?? "";
492
+ return template.replace("{id}", encodeURIComponent(numericId));
310
493
  }
311
- };
312
- async function render(root = document) {
313
- const logged = server() ? Boolean((await serverMe().catch(() => ({ loggedIn: false }))).loggedIn) : isLoggedIn() || Boolean(await getToken().catch(() => null));
314
- qsa(root, sel("show")).forEach((el) => {
315
- const k = get(el, "show");
316
- if (k === "logged-in") show(el, logged);
317
- if (k === "logged-out") show(el, !logged);
318
- });
319
- qsa(root, sel("account-link")).forEach((a) => {
320
- const k = get(a, "account-link");
321
- const u = k === "payment" && (cfg.account?.paymentUrl || cfg.account?.subscriptionsUrl) || k === "subscriptions" && cfg.account?.subscriptionsUrl;
322
- a.setAttribute("href", u || `https://${cfg.domain}/account`);
494
+ return isHttpsUrl(order2.statusPageUrl) ? order2.statusPageUrl ?? null : null;
495
+ }
496
+
497
+ // src/account/markup.js
498
+ function showIn(root, name, conditions) {
499
+ qsa(root, sel(name)).forEach((el) => {
500
+ const key = get(el, name) ?? "";
501
+ if (Object.hasOwn(conditions, key)) show(el, conditions[key] === true);
323
502
  });
324
- if (!logged) return;
325
- await bindCustomer(root);
326
- await Promise.all([renderOrderList(root), PAGE_ATTRS.some((n) => qsa(root, sel(n)).length) ? pages().then((m6) => m6.renderPages(root)) : null]);
327
503
  }
328
- async function renderOrderList(root) {
329
- const lists = qsa(root, sel("orders"));
330
- if (lists.length) {
331
- const ordersShow = (loading, empty) => qsa(root, sel("show")).forEach((el) => {
332
- const k = get(el, "show");
333
- if (k === "orders-loading") show(el, loading);
334
- if (k === "orders-empty") show(el, empty);
335
- });
336
- ordersShow(true, false);
337
- let failed = false;
338
- const list = await orders({ first: parseInt(get(lists[0], "limit") || "10", 10) }).catch((err) => {
339
- reportError(err, "orders");
340
- failed = true;
341
- return [];
504
+ var bindsIn = (row, name) => qsa(row, sel(name)).concat(row.matches(sel(name)) ? [row] : []);
505
+ var ownValue = (values, key) => Object.hasOwn(values, key) ? values[key] : void 0;
506
+
507
+ // src/account/order-lines.js
508
+ var lineTexts = (line, returnable) => ({
509
+ title: line.title || line.name,
510
+ "variant-title": line.variantTitle || "",
511
+ quantity: String(line.quantity),
512
+ price: formatMoney(line.price),
513
+ total: formatMoney(line.totalPrice),
514
+ sku: line.sku || "",
515
+ returnable: returnable === null ? void 0 : String(returnable)
516
+ });
517
+ var isInput = (el) => el.tagName === "INPUT";
518
+ function fillReturnControls(row, line, returnable) {
519
+ showIn(row, "order-line-show", { returnable: returnable > 0 });
520
+ qsa(row, sel("return-quantity")).forEach((input) => {
521
+ if (!isInput(input)) return;
522
+ input.max = String(returnable);
523
+ input.min = "0";
524
+ if (!input.value) input.value = "0";
525
+ input.disabled = returnable < 1;
526
+ });
527
+ qsa(row, `select${sel("return-reason")}`).forEach((select) => {
528
+ if (select.dataset["tetherFilled"] === line.id) return;
529
+ select.dataset["tetherFilled"] = line.id;
530
+ (line.suggestedReturnReasonDefinitions?.nodes ?? []).forEach((reason) => {
531
+ select.appendChild(new Option(reason.name, reason.id));
342
532
  });
343
- ordersShow(false, !failed && list.length === 0);
344
- lists.forEach((c) => repeat(c, list, (node, o) => {
345
- const items = o.lineItems?.nodes || [];
346
- qsa(node, sel("order-bind")).forEach((el) => {
347
- const k = get(el, "order-bind");
348
- if (k === "url") {
349
- const u = orderUrl(o);
350
- if (u) el.setAttribute("href", u);
351
- return;
352
- }
353
- if (k === "image") {
354
- const im = items.find((l) => l.image)?.image;
355
- if (im) setImage(el, im.url, im.altText || items[0]?.title || "");
356
- return;
357
- }
358
- const v = { name: o.name, date: date(o.processedAt), total: formatMoney(o.totalPrice), status: o.financialStatus?.toLowerCase(), fulfillment: o.fulfillmentStatus?.toLowerCase(), items: items.map((l) => `${l.quantity}\xD7 ${l.title}`).join(", ") }[k];
359
- if (v != null) setText(el, v);
360
- });
361
- qsa(node, sel("order-lines")).forEach((box) => fillLines(box, items));
362
- if (get(node, "order-bind") === "url" && orderUrl(o)) node.setAttribute("href", orderUrl(o));
363
- }));
364
- }
533
+ });
365
534
  }
366
- var nid = (g) => String(g || "").split("/").pop().split("?")[0];
367
- function orderUrl(o) {
368
- const t = cfg.account?.orderUrl;
369
- if (t) return String(t).replace("{id}", encodeURIComponent(nid(o.id)));
370
- return https(o.statusPageUrl) ? o.statusPageUrl : null;
371
- }
372
- function fillLines(box, items) {
373
- repeat(box, items, (row, l) => {
374
- qsa(row, sel("order-line")).concat(row.matches(sel("order-line")) ? [row] : []).forEach((el) => {
375
- const k = get(el, "order-line");
376
- if (k === "image") {
377
- if (l.image) setImage(el, l.image.url, l.image.altText || l.title);
535
+ function fillLines(box, lines, returns) {
536
+ repeat(box, lines, (row, line) => {
537
+ const id = line.id;
538
+ const returnable = returns && id ? returns.get(id) ?? 0 : null;
539
+ if (id) row.dataset["tetherLine"] = id;
540
+ const texts = lineTexts(line, returnable);
541
+ bindsIn(row, "order-line").forEach((el) => {
542
+ const key = get(el, "order-line") ?? "";
543
+ if (key === "image") {
544
+ if (line.image) setImage(el, line.image.url, line.image.altText || line.title);
378
545
  return;
379
546
  }
380
- const v = { title: l.title, "variant-title": l.variantTitle || "", quantity: String(l.quantity) }[k];
381
- if (v != null) setText(el, v);
547
+ const text = ownValue(texts, key);
548
+ if (text !== void 0) setText(el, text);
382
549
  });
550
+ if (id && returnable !== null) fillReturnControls(row, { ...line, id }, returnable);
383
551
  });
384
552
  }
385
- var PAGE_ATTRS = ["order", "addresses", "subscriptions", "profile-form", "address-form"];
386
- var pagesPart = null;
553
+
554
+ // src/account/order-list.js
555
+ var DEFAULT_ORDER_COUNT = 10;
556
+ var orderTexts = (order2) => ({
557
+ name: order2.name,
558
+ date: localDate(order2.processedAt),
559
+ total: formatMoney(order2.totalPrice),
560
+ status: order2.financialStatus?.toLowerCase(),
561
+ fulfillment: order2.fulfillmentStatus?.toLowerCase(),
562
+ items: order2.lineItems.nodes.map((line) => `${line.quantity}\xD7 ${line.title}`).join(", ")
563
+ });
564
+ function fillOrder(node, order2) {
565
+ const lines = order2.lineItems.nodes;
566
+ const url = orderUrl(order2);
567
+ const texts = orderTexts(order2);
568
+ qsa(node, sel("order-bind")).forEach((el) => {
569
+ const key = get(el, "order-bind") ?? "";
570
+ if (key === "url") {
571
+ if (url) el.setAttribute("href", url);
572
+ return;
573
+ }
574
+ if (key === "image") {
575
+ const image = lines.find((line) => line.image)?.image;
576
+ if (image) setImage(el, image.url, image.altText || lines[0]?.title || "");
577
+ return;
578
+ }
579
+ const text = ownValue(texts, key);
580
+ if (text !== void 0) setText(el, text);
581
+ });
582
+ qsa(node, sel("order-lines")).forEach((box) => {
583
+ fillLines(box, lines, null);
584
+ });
585
+ if (get(node, "order-bind") === "url" && url) node.setAttribute("href", url);
586
+ }
587
+ async function renderOrderList(root, loadOrders) {
588
+ const lists = qsa(root, sel("orders"));
589
+ const first = lists[0];
590
+ if (!first) return;
591
+ showIn(root, "show", { "orders-loading": true, "orders-empty": false });
592
+ const limit = Number.parseInt(get(first, "limit") || String(DEFAULT_ORDER_COUNT), 10) || DEFAULT_ORDER_COUNT;
593
+ const orders2 = await loadOrders(limit).catch((error) => {
594
+ reportError(error, "orders");
595
+ return null;
596
+ });
597
+ showIn(root, "show", { "orders-loading": false, "orders-empty": orders2?.length === 0 });
598
+ lists.forEach((list) => {
599
+ repeat(list, orders2 ?? [], fillOrder);
600
+ });
601
+ }
602
+
603
+ // src/account.js
604
+ var PAGE_ATTRIBUTES = ["order", "addresses", "subscriptions", "profile-form", "address-form"];
605
+ function isLoggedIn() {
606
+ return serverBase() ? isServerLoggedIn() : hasBrowserLogin();
607
+ }
608
+ async function login({ returnTo = window.location.href, locale } = {}) {
609
+ if (serverBase()) serverLoginRedirect({ returnTo, locale });
610
+ else await browserLoginRedirect({ returnTo, locale });
611
+ }
612
+ async function query2(document2, variables = {}) {
613
+ if (serverBase()) {
614
+ throw new TetherError("Customer accounts in server mode only answer customer() and orders()", {
615
+ code: "NOT_ALLOWED"
616
+ });
617
+ }
618
+ return query(document2, variables);
619
+ }
620
+ async function customer() {
621
+ if (!serverBase()) return browserCustomer();
622
+ const me = await serverLogin();
623
+ if (!me.loggedIn) return null;
624
+ return {
625
+ ...me.customer,
626
+ emailAddress: { emailAddress: me.customer.email },
627
+ defaultAddress: { formatted: me.customer.address }
628
+ };
629
+ }
630
+ async function orders({ first = 10 } = {}) {
631
+ if (serverBase()) {
632
+ const answer = await serverFetch(`/orders?first=${first}`);
633
+ if (answer.kind === "logged-out") return [];
634
+ const list = answer.data["orders"];
635
+ return Array.isArray(list) ? (
636
+ /** @type {AccountOrder[]} */
637
+ list
638
+ ) : [];
639
+ }
640
+ const data = await query(ORDERS_QUERY, { first });
641
+ return data.customer?.orders.nodes ?? [];
642
+ }
643
+ async function logout({ returnTo = cfg.account?.logoutUrl || `${window.location.origin}/` } = {}) {
644
+ const outcome = serverBase() ? await serverLogout(returnTo) : await browserLogout(returnTo);
645
+ if (outcome === "stayed") await render(document);
646
+ }
647
+ async function loginState() {
648
+ try {
649
+ if (serverBase()) return (await serverLogin()).loggedIn ? { kind: "logged-in" } : { kind: "logged-out" };
650
+ if (hasBrowserLogin() || await getToken()) return { kind: "logged-in" };
651
+ return { kind: "logged-out" };
652
+ } catch (error) {
653
+ return { kind: "unknown", error };
654
+ }
655
+ }
656
+ var usesPages = (root) => PAGE_ATTRIBUTES.some((name) => qsa(root, sel(name)).length > 0);
657
+ function linkShopifyPages(root) {
658
+ const account = cfg.account;
659
+ qsa(root, sel("account-link")).forEach((link) => {
660
+ const page = get(link, "account-link");
661
+ const subscriptions2 = account?.subscriptionsUrl;
662
+ const url = page === "payment" && (account?.paymentUrl || subscriptions2) || page === "subscriptions" && subscriptions2;
663
+ link.setAttribute("href", url || `https://${cfg.domain}/account`);
664
+ });
665
+ }
666
+ async function render(root = document) {
667
+ const state = await loginState();
668
+ if (state.kind === "unknown") reportError(state.error, "account");
669
+ const loggedIn = state.kind === "logged-in";
670
+ qsa(root, sel("show")).forEach((el) => {
671
+ const condition = get(el, "show");
672
+ if (condition === "logged-in") show(el, loggedIn);
673
+ if (condition === "logged-out") show(el, !loggedIn);
674
+ });
675
+ linkShopifyPages(root);
676
+ if (state.kind === "unknown" && usesPages(root)) {
677
+ (await pages()).showUnavailable(root);
678
+ return;
679
+ }
680
+ if (!loggedIn) return;
681
+ await bindCustomer(root);
682
+ await Promise.all([
683
+ renderOrderList(root, (first) => orders({ first })),
684
+ usesPages(root) ? pages().then((part) => part.renderPages(root)) : null
685
+ ]);
686
+ }
387
687
  async function bindCustomer(root) {
388
688
  const binds = qsa(root, sel("account-bind"));
389
689
  if (!binds.length) return;
390
- const c = await customer().catch((err) => {
391
- reportError(err, "account");
690
+ const profile = await customer().catch((error) => {
691
+ reportError(error, "account");
392
692
  return null;
393
693
  });
694
+ const texts = {
695
+ "first-name": profile?.firstName,
696
+ "last-name": profile?.lastName,
697
+ name: profile?.displayName,
698
+ email: profile?.emailAddress?.emailAddress,
699
+ address: profile?.defaultAddress?.formatted?.join(", ")
700
+ };
394
701
  binds.forEach((el) => {
395
- const v = { "first-name": c?.firstName, "last-name": c?.lastName, name: c?.displayName, email: c?.emailAddress?.emailAddress, address: c?.defaultAddress?.formatted?.join(", ") }[get(el, "account-bind")];
396
- if (v != null) setText(el, v);
702
+ const text = ownValue(texts, get(el, "account-bind") ?? "");
703
+ if (text != null) setText(el, text);
397
704
  });
398
705
  }
706
+ var pagesPart = null;
399
707
  function pages() {
400
- pagesPart || (pagesPart = loadPart("account-pages").then((m6) => {
401
- m6.setup({ server, serverFetch, query, customer, bindCustomer, reset: () => {
402
- customerCache = null;
403
- serverState = null;
404
- } });
405
- return m6;
708
+ pagesPart ?? (pagesPart = loadPart("account-pages").then((part) => {
709
+ part.setup({
710
+ serverBase,
711
+ serverFetch,
712
+ query: query2,
713
+ customer,
714
+ bindCustomer,
715
+ reset: () => {
716
+ forgetBrowserCustomer();
717
+ forgetServerLogin();
718
+ }
719
+ });
720
+ return part;
406
721
  }));
407
722
  return pagesPart;
408
723
  }
409
- var viaPages = (name) => (...a) => pages().then((m6) => m6[name](...a));
410
- var order = viaPages("order");
411
- var updateProfile = viaPages("updateProfile");
412
- var addresses = viaPages("addresses");
413
- var saveAddress = viaPages("saveAddress");
414
- var deleteAddress = viaPages("deleteAddress");
415
- var subscriptions = viaPages("subscriptions");
416
- var subscriptionAction = viaPages("subscriptionAction");
417
- var requestReturn = viaPages("requestReturn");
418
- var bound = false;
419
- async function mountAccount(root = document) {
420
- if (!bound) {
421
- bound = true;
422
- document.addEventListener("click", (e) => {
423
- const t = e.target;
424
- if (t.closest?.(sel("account-login"))) {
425
- e.preventDefault();
426
- login().catch((err) => reportError(err, "login"));
427
- } else if (t.closest?.(sel("account-logout"))) {
428
- e.preventDefault();
429
- logout().catch((err) => reportError(err, "logout"));
430
- }
724
+ var order = (id) => pages().then((part) => part.order(id));
725
+ var updateProfile = (fields) => pages().then((part) => part.updateProfile(fields));
726
+ var addresses = () => pages().then((part) => part.addresses());
727
+ var saveAddress = (address, options) => pages().then((part) => part.saveAddress(address, options));
728
+ var deleteAddress = (id) => pages().then((part) => part.deleteAddress(id));
729
+ var subscriptions = () => pages().then((part) => part.subscriptions());
730
+ var subscriptionAction = (id, action) => pages().then((part) => part.subscriptionAction(id, action));
731
+ var requestReturn = (orderId, lines) => pages().then((part) => part.requestReturn(orderId, lines));
732
+ function onClick(event) {
733
+ const target = event.target;
734
+ if (!(target instanceof window.Element)) return;
735
+ if (target.closest(sel("account-login"))) {
736
+ event.preventDefault();
737
+ login().catch((error) => {
738
+ reportError(error, "login");
431
739
  });
432
- if (server()) {
433
- const u = new URL(window.location.href);
434
- if (u.searchParams.get("account")) {
435
- u.searchParams.delete("account");
436
- window.history.replaceState(window.history.state, "", u);
437
- emit("account-login", {});
438
- }
439
- Cart.beforeCheckout = async () => {
440
- if (!(await serverMe()).loggedIn || !Cart.id()) return null;
441
- const r = await serverFetch("/cart", { method: "POST", body: { cartId: Cart.id() } }).catch(() => null);
442
- return r?.checkoutUrl ? `${r.checkoutUrl}${r.checkoutUrl.includes("?") ? "&" : "?"}logged_in=true` : null;
443
- };
444
- } else {
445
- try {
446
- await handleCallback();
447
- } catch (err) {
448
- reportError(err, "login");
449
- }
450
- if (!Cart.customerAccessToken && await getToken().catch(() => null)) Cart.customerAccessToken = session.access;
451
- }
740
+ } else if (target.closest(sel("account-logout"))) {
741
+ event.preventDefault();
742
+ logout().catch((error) => {
743
+ reportError(error, "logout");
744
+ });
745
+ }
746
+ }
747
+ function startServerMode() {
748
+ const url = new URL(window.location.href);
749
+ if (url.searchParams.get("account")) {
750
+ url.searchParams.delete("account");
751
+ window.history.replaceState(window.history.state, "", url);
752
+ emit("account-login", {});
753
+ }
754
+ Cart.setCustomer({ beforeCheckout: serverCheckoutUrl });
755
+ }
756
+ async function startBrowserMode() {
757
+ try {
758
+ await handleCallback();
759
+ } catch (error) {
760
+ reportError(error, "login");
761
+ }
762
+ if (Cart.customerAccessToken) return;
763
+ try {
764
+ const accessToken2 = await getToken();
765
+ if (accessToken2) Cart.setCustomer({ accessToken: accessToken2 });
766
+ } catch (error) {
767
+ reportError(error, "account");
768
+ }
769
+ }
770
+ var started = false;
771
+ async function mountAccount(root = document) {
772
+ if (!started) {
773
+ started = true;
774
+ document.addEventListener("click", onClick);
775
+ if (serverBase()) startServerMode();
776
+ else await startBrowserMode();
452
777
  log("account", isLoggedIn() ? "logged in" : "logged out");
453
778
  }
454
779
  await render(root);
455
780
  }
456
781
 
457
782
  // src/parts/account.js
458
- window.Tether.__parts.account = account_exports;
783
+ var { Tether } = (
784
+ /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */
785
+ window
786
+ );
787
+ if (!Tether) throw new Error("tether.account.js runs after the Tether script, which loads it");
788
+ Tether.__parts["account"] = account_exports;
459
789
  })();