@occluze/tether 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +12 -8
  2. package/dist/esm/astro.js +2 -0
  3. package/dist/esm/chunks/account-WAWNASZU.js +1 -0
  4. package/dist/esm/chunks/analytics-22NMIVBV.js +1 -0
  5. package/dist/esm/chunks/chunk-4UR45XTC.js +1 -0
  6. package/dist/esm/chunks/chunk-6R752OYZ.js +1 -0
  7. package/dist/esm/chunks/chunk-BGIKQY6H.js +1 -0
  8. package/dist/esm/chunks/chunk-CISGFYOF.js +1 -0
  9. package/dist/esm/chunks/chunk-KARKXC6G.js +1 -0
  10. package/dist/esm/chunks/chunk-VC46IEJQ.js +1 -0
  11. package/dist/esm/chunks/chunk-YXCXIPQV.js +4 -0
  12. package/dist/esm/chunks/{chunk-KYQ5OGBX.js → chunk-YZMBXS6Y.js} +1 -1
  13. package/dist/esm/chunks/chunk-ZVUPVSLF.js +1 -0
  14. package/dist/esm/chunks/diagnose-6ZSK5DGE.js +1 -0
  15. package/dist/esm/chunks/extras-QDCSW6VU.js +1 -0
  16. package/dist/esm/chunks/markets-ui-HJCVLCKJ.js +1 -0
  17. package/dist/esm/chunks/search-LN2H6SXS.js +1 -0
  18. package/dist/esm/index.js +1 -1
  19. package/dist/esm/server.js +1 -0
  20. package/dist/tether.account.js +374 -0
  21. package/dist/tether.account.min.js +1 -0
  22. package/dist/tether.analytics.js +259 -0
  23. package/dist/tether.analytics.min.js +1 -0
  24. package/dist/tether.diagnose.js +14 -4
  25. package/dist/tether.diagnose.min.js +1 -1
  26. package/dist/tether.extras.js +87 -0
  27. package/dist/tether.extras.min.js +1 -0
  28. package/dist/tether.js +486 -125
  29. package/dist/tether.markets.js +17 -11
  30. package/dist/tether.markets.min.js +1 -1
  31. package/dist/tether.min.js +3 -3
  32. package/dist/tether.min.js.map +3 -3
  33. package/dist/tether.search.js +176 -0
  34. package/dist/tether.search.min.js +1 -0
  35. package/package.json +31 -5
  36. package/src/account.js +313 -0
  37. package/src/analytics.js +232 -0
  38. package/src/astro.js +76 -0
  39. package/src/cart.js +119 -16
  40. package/src/client.js +30 -15
  41. package/src/collection.js +15 -10
  42. package/src/config.js +5 -0
  43. package/src/diagnose.js +12 -3
  44. package/src/dom.js +39 -10
  45. package/src/extras.js +63 -0
  46. package/src/fragments.js +13 -12
  47. package/src/index.js +114 -40
  48. package/src/initial.js +75 -0
  49. package/src/lazy.js +9 -1
  50. package/src/loader.js +53 -7
  51. package/src/markets-ui.js +15 -6
  52. package/src/markets.js +1 -1
  53. package/src/mounting.js +68 -3
  54. package/src/parts/account.js +3 -0
  55. package/src/parts/analytics.js +3 -0
  56. package/src/parts/extras.js +3 -0
  57. package/src/parts/search.js +3 -0
  58. package/src/product.js +51 -13
  59. package/src/search.js +153 -0
  60. package/src/server.js +251 -0
  61. package/src/status.js +3 -8
  62. package/types/astro.d.ts +15 -0
  63. package/types/index.d.ts +72 -4
  64. package/types/server.d.ts +102 -0
  65. package/dist/esm/chunks/chunk-GHIUMD5H.js +0 -1
  66. package/dist/esm/chunks/chunk-LI3YDMGF.js +0 -4
  67. package/dist/esm/chunks/diagnose-ZXXXJIQE.js +0 -1
  68. package/dist/esm/chunks/markets-ui-AG6Y5VUE.js +0 -1
@@ -0,0 +1,374 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/account.js
9
+ var account_exports = {};
10
+ __export(account_exports, {
11
+ customer: () => customer,
12
+ getToken: () => getToken,
13
+ handleCallback: () => handleCallback,
14
+ isLoggedIn: () => isLoggedIn,
15
+ login: () => login,
16
+ logout: () => logout,
17
+ mountAccount: () => mountAccount,
18
+ orders: () => orders,
19
+ query: () => query,
20
+ render: () => render
21
+ });
22
+
23
+ // tether-core:config.js
24
+ var m = window.Tether.__core["config.js"];
25
+ var { DEFAULTS, cfg, configure, isConfigured, log, reportError, warn } = m;
26
+
27
+ // tether-core:client.js
28
+ var m2 = window.Tether.__core["client.js"];
29
+ var { TetherError, endpoint, gql, request, unwrap, withContext } = m2;
30
+
31
+ // tether-core:dom.js
32
+ var m3 = window.Tether.__core["dom.js"];
33
+ 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;
34
+
35
+ // tether-core:cart.js
36
+ var m4 = window.Tether.__core["cart.js"];
37
+ var { Cart, focusables, toLineInput } = m4;
38
+
39
+ // src/account.js
40
+ var KEY = () => `${cfg.prefix}:account:${cfg.domain}`;
41
+ var PENDING = () => `${cfg.prefix}:account-login:${cfg.domain}`;
42
+ var store = () => cfg.account?.remember ? window.localStorage : window.sessionStorage;
43
+ var b64url = (bytes) => btoa(String.fromCharCode(...new Uint8Array(bytes))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
44
+ var random = () => b64url(crypto.getRandomValues(new Uint8Array(32)));
45
+ var sha256 = async (s) => b64url(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(s)));
46
+ var go = (url) => (cfg.account?.navigate || ((u) => window.location.assign(u)))(url);
47
+ var read = (s, k) => {
48
+ try {
49
+ return JSON.parse(s.getItem(k) || "null");
50
+ } catch {
51
+ return null;
52
+ }
53
+ };
54
+ var write = (s, k, v) => {
55
+ try {
56
+ if (v) s.setItem(k, JSON.stringify(v));
57
+ else s.removeItem(k);
58
+ } catch {
59
+ }
60
+ };
61
+ var discovery = null;
62
+ var session = null;
63
+ var refreshing = null;
64
+ var customerCache = null;
65
+ var server = () => cfg.account?.server ? String(cfg.account.server).replace(/\/$/, "") : "";
66
+ var HINT = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
67
+ var serverState = null;
68
+ async function serverFetch(path, { method = "GET", body } = {}) {
69
+ const res = await fetch(`${server()}${path}`, {
70
+ method,
71
+ credentials: "include",
72
+ headers: method === "POST" ? { "Content-Type": "application/json", "X-Tether-Account": "1" } : {},
73
+ body: body ? JSON.stringify(body) : void 0
74
+ });
75
+ const data = await res.json().catch(() => ({}));
76
+ if (!res.ok && res.status !== 401) throw new TetherError(`Customer accounts: ${data.error || res.status}`, { status: res.status });
77
+ return data;
78
+ }
79
+ async function serverMe() {
80
+ serverState || (serverState = await serverFetch("/me"));
81
+ try {
82
+ if (serverState.loggedIn) window.localStorage.setItem(HINT(), "1");
83
+ else window.localStorage.removeItem(HINT());
84
+ } catch {
85
+ }
86
+ return serverState;
87
+ }
88
+ async function discover() {
89
+ if (discovery) return discovery;
90
+ const shop = cfg.account?.shopDomain || cfg.domain;
91
+ const get2 = (p) => fetch(`https://${shop}/.well-known/${p}`).then((r) => {
92
+ if (!r.ok) throw new TetherError(`Customer accounts: ${p} answered ${r.status}`, { status: r.status });
93
+ return r.json();
94
+ });
95
+ const [oidc, api] = await Promise.all([get2("openid-configuration"), get2("customer-account-api")]);
96
+ discovery = { ...oidc, graphql: api.graphql_api };
97
+ return discovery;
98
+ }
99
+ var callbackUrl = () => cfg.account?.callbackUrl || `${window.location.origin}${window.location.pathname}`;
100
+ function isLoggedIn() {
101
+ return server() ? Boolean(serverState?.loggedIn) : Boolean(session?.refresh || session?.access && session.expiresAt > Date.now());
102
+ }
103
+ async function login({ returnTo = window.location.href, locale } = {}) {
104
+ if (server()) {
105
+ const u2 = new URL(`${server()}/login`);
106
+ u2.searchParams.set("returnTo", returnTo);
107
+ if (Cart.id()) u2.searchParams.set("cart", Cart.id());
108
+ const lang2 = locale || cfg.account.locale || (cfg.locale || "").split("-")[0];
109
+ if (lang2) u2.searchParams.set("locale", lang2);
110
+ if (cfg.country) u2.searchParams.set("country", String(cfg.country).toUpperCase());
111
+ go(u2.toString());
112
+ return;
113
+ }
114
+ if (!cfg.account?.clientId) throw new TetherError("Customer accounts: set account.clientId", { code: "NOT_CONFIGURED" });
115
+ const d = await discover();
116
+ const state = random();
117
+ const nonce = random();
118
+ const verifier = random();
119
+ const redirect = callbackUrl();
120
+ write(window.sessionStorage, PENDING(), { state, nonce, verifier, redirect, returnTo, at: Date.now() });
121
+ const u = new URL(d.authorization_endpoint);
122
+ const p = {
123
+ client_id: cfg.account.clientId,
124
+ scope: "openid email customer-account-api:full",
125
+ response_type: "code",
126
+ redirect_uri: redirect,
127
+ state,
128
+ nonce,
129
+ code_challenge: await sha256(verifier),
130
+ code_challenge_method: "S256"
131
+ };
132
+ const lang = locale || cfg.account.locale || (cfg.locale || "").split("-")[0];
133
+ if (lang) p.locale = lang;
134
+ if (cfg.country) p.region_country = String(cfg.country).toUpperCase();
135
+ Object.entries(p).forEach(([k, v]) => u.searchParams.set(k, v));
136
+ go(u.toString());
137
+ }
138
+ async function tokenRequest(params) {
139
+ const d = await discover();
140
+ const res = await fetch(cfg.account.tokenUrl || d.token_endpoint, { method: "POST", body: new URLSearchParams(params) });
141
+ const data = await res.json().catch(() => ({}));
142
+ 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" });
143
+ return data;
144
+ }
145
+ function decodeJwt(t) {
146
+ try {
147
+ return JSON.parse(atob(t.split(".")[1].replace(/-/g, "+").replace(/_/g, "/")));
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+ function keep(data, previous = session) {
153
+ session = {
154
+ access: data.access_token,
155
+ expiresAt: Date.now() + Math.max(0, (Number(data.expires_in) || 3600) - 120) * 1e3,
156
+ refresh: data.refresh_token || previous?.refresh,
157
+ idToken: data.id_token || previous?.idToken
158
+ };
159
+ write(store(), KEY(), session);
160
+ }
161
+ async function handleCallback() {
162
+ const q = new URLSearchParams(window.location.search);
163
+ const pending = read(window.sessionStorage, PENDING());
164
+ if (!pending || !q.has("code") && !q.has("error") || q.get("state") !== pending.state) return false;
165
+ write(window.sessionStorage, PENDING(), null);
166
+ const clean = new URL(window.location.href);
167
+ ["code", "state", "iss", "error", "error_description"].forEach((k) => clean.searchParams.delete(k));
168
+ window.history.replaceState(window.history.state, "", clean);
169
+ if (q.has("error")) {
170
+ emit("account-error", { error: q.get("error") });
171
+ return false;
172
+ }
173
+ if (Date.now() - pending.at > 10 * 6e4) throw new TetherError("Customer accounts: the login took too long, please try again");
174
+ const data = await tokenRequest({
175
+ grant_type: "authorization_code",
176
+ client_id: cfg.account.clientId,
177
+ redirect_uri: pending.redirect,
178
+ code: q.get("code"),
179
+ code_verifier: pending.verifier
180
+ });
181
+ const d = await discover();
182
+ const claims = decodeJwt(data.id_token || "");
183
+ const aud = [].concat(claims?.aud || []);
184
+ if (!claims || claims.nonce !== pending.nonce || claims.iss !== d.issuer || !aud.includes(cfg.account.clientId) || claims.exp * 1e3 < Date.now()) {
185
+ throw new TetherError("Customer accounts: the login answer did not check out (nonce/issuer/audience)");
186
+ }
187
+ keep(data, null);
188
+ await linkCart();
189
+ emit("account-login", {});
190
+ const back = pending.returnTo && new URL(pending.returnTo, window.location.href);
191
+ if (back && back.origin === window.location.origin && back.href !== clean.href) go(back.href);
192
+ return true;
193
+ }
194
+ async function getToken() {
195
+ if (server()) return null;
196
+ if (!session) session = read(store(), KEY());
197
+ if (!session) return null;
198
+ if (session.access && session.expiresAt > Date.now()) return session.access;
199
+ if (!session.refresh) {
200
+ endSession();
201
+ return null;
202
+ }
203
+ refreshing || (refreshing = (async () => {
204
+ try {
205
+ keep(await tokenRequest({ grant_type: "refresh_token", client_id: cfg.account.clientId, refresh_token: session.refresh }));
206
+ await linkCart();
207
+ } catch (err) {
208
+ if (err.code === "SESSION_ENDED") endSession();
209
+ else throw err;
210
+ } finally {
211
+ refreshing = null;
212
+ }
213
+ })());
214
+ await refreshing;
215
+ return session?.access || null;
216
+ }
217
+ function endSession() {
218
+ session = null;
219
+ customerCache = null;
220
+ write(store(), KEY(), null);
221
+ Cart.customerAccessToken = null;
222
+ }
223
+ async function linkCart() {
224
+ Cart.customerAccessToken = session?.access || null;
225
+ if (Cart.id() && session?.access) {
226
+ await Cart.setBuyerIdentity({ customerAccessToken: session.access }, { create: false }).catch((err) => reportError(err, "account cart"));
227
+ }
228
+ }
229
+ async function query(q, variables = {}) {
230
+ if (server()) throw new TetherError("Customer accounts in server mode only answer customer() and orders()", { code: "NOT_ALLOWED" });
231
+ const token = await getToken();
232
+ if (!token) throw new TetherError("Not logged in", { code: "NOT_LOGGED_IN" });
233
+ const d = await discover();
234
+ const run = (t) => fetch(d.graphql, { method: "POST", headers: { "Content-Type": "application/json", Authorization: t }, body: JSON.stringify({ query: q, variables }) });
235
+ let res = await run(token);
236
+ if (res.status === 401 && session?.refresh) {
237
+ session.expiresAt = 0;
238
+ res = await run(await getToken());
239
+ }
240
+ const json = await res.json().catch(() => ({}));
241
+ if (!res.ok || json.errors?.length) throw new TetherError(json.errors?.map((e) => e.message).join(" | ") || `Customer accounts: ${res.status}`, { status: res.status });
242
+ return json.data;
243
+ }
244
+ async function customer() {
245
+ if (server()) {
246
+ const me = await serverMe();
247
+ return me.loggedIn ? { ...me.customer, emailAddress: { emailAddress: me.customer.email }, defaultAddress: { formatted: me.customer.address } } : null;
248
+ }
249
+ if (customerCache) return customerCache;
250
+ const data = await query(`query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}`);
251
+ customerCache = data.customer;
252
+ return customerCache;
253
+ }
254
+ async function orders({ first = 10 } = {}) {
255
+ if (server()) return (await serverFetch(`/orders?first=${first}`)).orders || [];
256
+ 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 });
257
+ return data.customer?.orders?.nodes || [];
258
+ }
259
+ async function logout({ returnTo = cfg.account?.logoutUrl || `${window.location.origin}/` } = {}) {
260
+ if (server()) {
261
+ const r = await serverFetch("/logout", { method: "POST", body: { returnTo, cartId: Cart.id() } });
262
+ serverState = { loggedIn: false };
263
+ try {
264
+ window.localStorage.removeItem(HINT());
265
+ } catch {
266
+ }
267
+ emit("account-logout", {});
268
+ if (r.logoutUrl) go(r.logoutUrl);
269
+ else render(document);
270
+ return;
271
+ }
272
+ const idToken = session?.idToken || read(store(), KEY())?.idToken;
273
+ if (Cart.id()) await Cart.setBuyerIdentity({ customerAccessToken: null }, { create: false }).catch(() => {
274
+ });
275
+ endSession();
276
+ emit("account-logout", {});
277
+ if (!idToken) {
278
+ render(document);
279
+ return;
280
+ }
281
+ const d = await discover();
282
+ const u = new URL(d.end_session_endpoint);
283
+ u.searchParams.set("id_token_hint", idToken);
284
+ u.searchParams.set("post_logout_redirect_uri", returnTo);
285
+ go(u.toString());
286
+ }
287
+ var date = (s) => {
288
+ try {
289
+ return new Date(s).toLocaleDateString(cfg.locale || void 0);
290
+ } catch {
291
+ return s;
292
+ }
293
+ };
294
+ async function render(root = document) {
295
+ const logged = server() ? Boolean((await serverMe().catch(() => ({ loggedIn: false }))).loggedIn) : isLoggedIn() || Boolean(await getToken().catch(() => null));
296
+ qsa(root, sel("show")).forEach((el) => {
297
+ const k = get(el, "show");
298
+ if (k === "logged-in") show(el, logged);
299
+ if (k === "logged-out") show(el, !logged);
300
+ });
301
+ if (!logged) return;
302
+ const binds = qsa(root, sel("account-bind"));
303
+ if (binds.length) {
304
+ const c = await customer().catch((err) => {
305
+ reportError(err, "account");
306
+ return null;
307
+ });
308
+ binds.forEach((el) => {
309
+ 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")];
310
+ if (v != null) setText(el, v);
311
+ });
312
+ }
313
+ const lists = qsa(root, sel("orders"));
314
+ if (lists.length) {
315
+ const list = await orders({ first: parseInt(get(lists[0], "limit") || "10", 10) }).catch((err) => {
316
+ reportError(err, "orders");
317
+ return [];
318
+ });
319
+ lists.forEach((c) => repeat(c, list, (node, o) => {
320
+ qsa(node, sel("order-bind")).forEach((el) => {
321
+ const k = get(el, "order-bind");
322
+ if (k === "url") {
323
+ el.setAttribute("href", o.statusPageUrl);
324
+ return;
325
+ }
326
+ const v = { name: o.name, date: date(o.processedAt), total: formatMoney(o.totalPrice), status: o.financialStatus?.toLowerCase(), fulfillment: o.fulfillmentStatus?.toLowerCase(), items: o.lineItems.nodes.map((l) => `${l.quantity}\xD7 ${l.title}`).join(", ") }[k];
327
+ if (v != null) setText(el, v);
328
+ });
329
+ if (get(node, "order-bind") === "url") node.setAttribute("href", o.statusPageUrl);
330
+ }));
331
+ }
332
+ }
333
+ var bound = false;
334
+ async function mountAccount(root = document) {
335
+ if (!bound) {
336
+ bound = true;
337
+ document.addEventListener("click", (e) => {
338
+ const t = e.target;
339
+ if (t.closest?.(sel("account-login"))) {
340
+ e.preventDefault();
341
+ login().catch((err) => reportError(err, "login"));
342
+ } else if (t.closest?.(sel("account-logout"))) {
343
+ e.preventDefault();
344
+ logout().catch((err) => reportError(err, "logout"));
345
+ }
346
+ });
347
+ if (server()) {
348
+ const u = new URL(window.location.href);
349
+ if (u.searchParams.get("account")) {
350
+ u.searchParams.delete("account");
351
+ window.history.replaceState(window.history.state, "", u);
352
+ emit("account-login", {});
353
+ }
354
+ Cart.beforeCheckout = async () => {
355
+ if (!(await serverMe()).loggedIn || !Cart.id()) return null;
356
+ const r = await serverFetch("/cart", { method: "POST", body: { cartId: Cart.id() } }).catch(() => null);
357
+ return r?.checkoutUrl ? `${r.checkoutUrl}${r.checkoutUrl.includes("?") ? "&" : "?"}logged_in=true` : null;
358
+ };
359
+ } else {
360
+ try {
361
+ await handleCallback();
362
+ } catch (err) {
363
+ reportError(err, "login");
364
+ }
365
+ if (!Cart.customerAccessToken && await getToken().catch(() => null)) Cart.customerAccessToken = session.access;
366
+ }
367
+ log("account", isLoggedIn() ? "logged in" : "logged out");
368
+ }
369
+ await render(root);
370
+ }
371
+
372
+ // src/parts/account.js
373
+ window.Tether.__parts.account = account_exports;
374
+ })();
@@ -0,0 +1 @@
1
+ (()=>{var rt=Object.defineProperty;var at=(t,e)=>{for(var o in e)rt(t,o,{get:e[o],enumerable:!0})};var F={};at(F,{customer:()=>et,getToken:()=>x,handleCallback:()=>Z,isLoggedIn:()=>G,login:()=>X,logout:()=>nt,mountAccount:()=>mt,orders:()=>ot,query:()=>K,render:()=>U});var ct=window.Tether.__core["config.js"],{DEFAULTS:gt,cfg:a,configure:pt,isConfigured:yt,log:M,reportError:g,warn:_t}=ct;var st=window.Tether.__core["client.js"],{TetherError:w,endpoint:Tt,gql:kt,request:It,unwrap:At,withContext:xt}=st;var it=window.Tether.__core["dom.js"],{attr:Et,busy:Ut,cdnUrl:$t,dataKey:bt,emit:y,formatDate:Nt,formatMoney:z,formatPercent:Pt,get:_,has:Dt,hasTemplate:Ot,imageUrl:vt,notifyLayoutChanged:Lt,parseJSONAttr:jt,productUrl:qt,qsa:T,readText:Rt,repeat:H,repeatKeyed:Jt,sameImage:Bt,scoped:Gt,sel:p,setImage:Kt,setSelected:Ft,setText:D,setTextTracked:Mt,show:O,toAttributes:zt,watchImages:Ht}=it;var lt=window.Tether.__core["cart.js"],{Cart:u,focusables:Wt,toLineInput:Xt}=lt;var $=()=>`${a.prefix}:account:${a.domain}`,j=()=>`${a.prefix}:account-login:${a.domain}`,b=()=>a.account?.remember?window.localStorage:window.sessionStorage,W=t=>btoa(String.fromCharCode(...new Uint8Array(t))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),v=()=>W(crypto.getRandomValues(new Uint8Array(32))),ut=async t=>W(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t))),A=t=>(a.account?.navigate||(e=>window.location.assign(e)))(t),J=(t,e)=>{try{return JSON.parse(t.getItem(e)||"null")}catch{return null}},N=(t,e,o)=>{try{o?t.setItem(e,JSON.stringify(o)):t.removeItem(e)}catch{}},E=null,s=null,L=null,k=null,d=()=>a.account?.server?String(a.account.server).replace(/\/$/,""):"",q=()=>`${a.prefix}:account-hint:${a.domain}`,I=null;async function P(t,{method:e="GET",body:o}={}){let r=await fetch(`${d()}${t}`,{method:e,credentials:"include",headers:e==="POST"?{"Content-Type":"application/json","X-Tether-Account":"1"}:{},body:o?JSON.stringify(o):void 0}),c=await r.json().catch(()=>({}));if(!r.ok&&r.status!==401)throw new w(`Customer accounts: ${c.error||r.status}`,{status:r.status});return c}async function B(){I||(I=await P("/me"));try{I.loggedIn?window.localStorage.setItem(q(),"1"):window.localStorage.removeItem(q())}catch{}return I}async function C(){if(E)return E;let t=a.account?.shopDomain||a.domain,e=c=>fetch(`https://${t}/.well-known/${c}`).then(n=>{if(!n.ok)throw new w(`Customer accounts: ${c} answered ${n.status}`,{status:n.status});return n.json()}),[o,r]=await Promise.all([e("openid-configuration"),e("customer-account-api")]);return E={...o,graphql:r.graphql_api},E}var dt=()=>a.account?.callbackUrl||`${window.location.origin}${window.location.pathname}`;function G(){return d()?!!I?.loggedIn:!!(s?.refresh||s?.access&&s.expiresAt>Date.now())}async function X({returnTo:t=window.location.href,locale:e}={}){if(d()){let f=new URL(`${d()}/login`);f.searchParams.set("returnTo",t),u.id()&&f.searchParams.set("cart",u.id());let h=e||a.account.locale||(a.locale||"").split("-")[0];h&&f.searchParams.set("locale",h),a.country&&f.searchParams.set("country",String(a.country).toUpperCase()),A(f.toString());return}if(!a.account?.clientId)throw new w("Customer accounts: set account.clientId",{code:"NOT_CONFIGURED"});let o=await C(),r=v(),c=v(),n=v(),l=dt();N(window.sessionStorage,j(),{state:r,nonce:c,verifier:n,redirect:l,returnTo:t,at:Date.now()});let i=new URL(o.authorization_endpoint),m={client_id:a.account.clientId,scope:"openid email customer-account-api:full",response_type:"code",redirect_uri:l,state:r,nonce:c,code_challenge:await ut(n),code_challenge_method:"S256"},S=e||a.account.locale||(a.locale||"").split("-")[0];S&&(m.locale=S),a.country&&(m.region_country=String(a.country).toUpperCase()),Object.entries(m).forEach(([f,h])=>i.searchParams.set(f,h)),A(i.toString())}async function Y(t){let e=await C(),o=await fetch(a.account.tokenUrl||e.token_endpoint,{method:"POST",body:new URLSearchParams(t)}),r=await o.json().catch(()=>({}));if(!o.ok||!r.access_token)throw new w(`Customer accounts: token ${o.status} ${r.error||""}`.trim(),{status:o.status,code:o.status===400||o.status===401?"SESSION_ENDED":"TOKEN_FAILED"});return r}function ft(t){try{return JSON.parse(atob(t.split(".")[1].replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function Q(t,e=s){s={access:t.access_token,expiresAt:Date.now()+Math.max(0,(Number(t.expires_in)||3600)-120)*1e3,refresh:t.refresh_token||e?.refresh,idToken:t.id_token||e?.idToken},N(b(),$(),s)}async function Z(){let t=new URLSearchParams(window.location.search),e=J(window.sessionStorage,j());if(!e||!t.has("code")&&!t.has("error")||t.get("state")!==e.state)return!1;N(window.sessionStorage,j(),null);let o=new URL(window.location.href);if(["code","state","iss","error","error_description"].forEach(m=>o.searchParams.delete(m)),window.history.replaceState(window.history.state,"",o),t.has("error"))return y("account-error",{error:t.get("error")}),!1;if(Date.now()-e.at>10*6e4)throw new w("Customer accounts: the login took too long, please try again");let r=await Y({grant_type:"authorization_code",client_id:a.account.clientId,redirect_uri:e.redirect,code:t.get("code"),code_verifier:e.verifier}),c=await C(),n=ft(r.id_token||""),l=[].concat(n?.aud||[]);if(!n||n.nonce!==e.nonce||n.iss!==c.issuer||!l.includes(a.account.clientId)||n.exp*1e3<Date.now())throw new w("Customer accounts: the login answer did not check out (nonce/issuer/audience)");Q(r,null),await tt(),y("account-login",{});let i=e.returnTo&&new URL(e.returnTo,window.location.href);return i&&i.origin===window.location.origin&&i.href!==o.href&&A(i.href),!0}async function x(){return d()||(s||(s=J(b(),$())),!s)?null:s.access&&s.expiresAt>Date.now()?s.access:s.refresh?(L||(L=(async()=>{try{Q(await Y({grant_type:"refresh_token",client_id:a.account.clientId,refresh_token:s.refresh})),await tt()}catch(t){if(t.code==="SESSION_ENDED")R();else throw t}finally{L=null}})()),await L,s?.access||null):(R(),null)}function R(){s=null,k=null,N(b(),$(),null),u.customerAccessToken=null}async function tt(){u.customerAccessToken=s?.access||null,u.id()&&s?.access&&await u.setBuyerIdentity({customerAccessToken:s.access},{create:!1}).catch(t=>g(t,"account cart"))}async function K(t,e={}){if(d())throw new w("Customer accounts in server mode only answer customer() and orders()",{code:"NOT_ALLOWED"});let o=await x();if(!o)throw new w("Not logged in",{code:"NOT_LOGGED_IN"});let r=await C(),c=i=>fetch(r.graphql,{method:"POST",headers:{"Content-Type":"application/json",Authorization:i},body:JSON.stringify({query:t,variables:e})}),n=await c(o);n.status===401&&s?.refresh&&(s.expiresAt=0,n=await c(await x()));let l=await n.json().catch(()=>({}));if(!n.ok||l.errors?.length)throw new w(l.errors?.map(i=>i.message).join(" | ")||`Customer accounts: ${n.status}`,{status:n.status});return l.data}async function et(){if(d()){let e=await B();return e.loggedIn?{...e.customer,emailAddress:{emailAddress:e.customer.email},defaultAddress:{formatted:e.customer.address}}:null}return k||(k=(await K("query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}")).customer,k)}async function ot({first:t=10}={}){return d()?(await P(`/orders?first=${t}`)).orders||[]:(await K("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:t})).customer?.orders?.nodes||[]}async function nt({returnTo:t=a.account?.logoutUrl||`${window.location.origin}/`}={}){if(d()){let c=await P("/logout",{method:"POST",body:{returnTo:t,cartId:u.id()}});I={loggedIn:!1};try{window.localStorage.removeItem(q())}catch{}y("account-logout",{}),c.logoutUrl?A(c.logoutUrl):U(document);return}let e=s?.idToken||J(b(),$())?.idToken;if(u.id()&&await u.setBuyerIdentity({customerAccessToken:null},{create:!1}).catch(()=>{}),R(),y("account-logout",{}),!e){U(document);return}let o=await C(),r=new URL(o.end_session_endpoint);r.searchParams.set("id_token_hint",e),r.searchParams.set("post_logout_redirect_uri",t),A(r.toString())}var wt=t=>{try{return new Date(t).toLocaleDateString(a.locale||void 0)}catch{return t}};async function U(t=document){let e=d()?!!(await B().catch(()=>({loggedIn:!1}))).loggedIn:G()||!!await x().catch(()=>null);if(T(t,p("show")).forEach(c=>{let n=_(c,"show");n==="logged-in"&&O(c,e),n==="logged-out"&&O(c,!e)}),!e)return;let o=T(t,p("account-bind"));if(o.length){let c=await et().catch(n=>(g(n,"account"),null));o.forEach(n=>{let l={"first-name":c?.firstName,"last-name":c?.lastName,name:c?.displayName,email:c?.emailAddress?.emailAddress,address:c?.defaultAddress?.formatted?.join(", ")}[_(n,"account-bind")];l!=null&&D(n,l)})}let r=T(t,p("orders"));if(r.length){let c=await ot({first:parseInt(_(r[0],"limit")||"10",10)}).catch(n=>(g(n,"orders"),[]));r.forEach(n=>H(n,c,(l,i)=>{T(l,p("order-bind")).forEach(m=>{let S=_(m,"order-bind");if(S==="url"){m.setAttribute("href",i.statusPageUrl);return}let f={name:i.name,date:wt(i.processedAt),total:z(i.totalPrice),status:i.financialStatus?.toLowerCase(),fulfillment:i.fulfillmentStatus?.toLowerCase(),items:i.lineItems.nodes.map(h=>`${h.quantity}\xD7 ${h.title}`).join(", ")}[S];f!=null&&D(m,f)}),_(l,"order-bind")==="url"&&l.setAttribute("href",i.statusPageUrl)}))}}var V=!1;async function mt(t=document){if(!V){if(V=!0,document.addEventListener("click",e=>{let o=e.target;o.closest?.(p("account-login"))?(e.preventDefault(),X().catch(r=>g(r,"login"))):o.closest?.(p("account-logout"))&&(e.preventDefault(),nt().catch(r=>g(r,"logout")))}),d()){let e=new URL(window.location.href);e.searchParams.get("account")&&(e.searchParams.delete("account"),window.history.replaceState(window.history.state,"",e),y("account-login",{})),u.beforeCheckout=async()=>{if(!(await B()).loggedIn||!u.id())return null;let o=await P("/cart",{method:"POST",body:{cartId:u.id()}}).catch(()=>null);return o?.checkoutUrl?`${o.checkoutUrl}${o.checkoutUrl.includes("?")?"&":"?"}logged_in=true`:null}}else{try{await Z()}catch(e){g(e,"login")}!u.customerAccessToken&&await x().catch(()=>null)&&(u.customerAccessToken=s.access)}M("account",G()?"logged in":"logged out")}await U(t)}window.Tether.__parts.account=F;})();
@@ -0,0 +1,259 @@
1
+ (() => {
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, { get: all[name], enumerable: true });
6
+ };
7
+
8
+ // src/analytics.js
9
+ var analytics_exports = {};
10
+ __export(analytics_exports, {
11
+ startAnalytics: () => startAnalytics
12
+ });
13
+
14
+ // tether-core:config.js
15
+ var m = window.Tether.__core["config.js"];
16
+ var { DEFAULTS, cfg, configure, isConfigured, log, reportError, warn } = m;
17
+
18
+ // tether-core:client.js
19
+ var m2 = window.Tether.__core["client.js"];
20
+ var { TetherError, endpoint, gql, request, unwrap, withContext } = m2;
21
+
22
+ // tether-core:dom.js
23
+ var m3 = window.Tether.__core["dom.js"];
24
+ 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;
25
+
26
+ // tether-core:cart.js
27
+ var m4 = window.Tether.__core["cart.js"];
28
+ var { Cart, focusables, toLineInput } = m4;
29
+
30
+ // src/analytics.js
31
+ var num = (m5) => m5 ? parseFloat(m5.amount) : 0;
32
+ var id = (gid) => String(gid || "").split("/").pop();
33
+ function itemFromVariant(p, v, quantity = 1, index) {
34
+ return {
35
+ item_id: v?.sku || id(v?.id) || id(p.id),
36
+ item_name: p.title,
37
+ item_variant: v?.title === "Default Title" ? void 0 : v?.title,
38
+ item_brand: p.vendor || void 0,
39
+ item_category: p.productType || void 0,
40
+ price: num(v?.price || p.priceRange?.minVariantPrice),
41
+ quantity,
42
+ ...index != null ? { index } : {}
43
+ };
44
+ }
45
+ function itemFromLine(l, quantity = l.quantity) {
46
+ const m5 = l.merchandise || {};
47
+ return itemFromVariant({ ...m5.product || {}, title: m5.product?.title }, m5, quantity);
48
+ }
49
+ var value = (items) => Math.round(items.reduce((s, i) => s + (i.price || 0) * (i.quantity || 1), 0) * 100) / 100;
50
+ function ga(event, ecommerce) {
51
+ const name = typeof cfg.analytics.dataLayer === "string" ? cfg.analytics.dataLayer : "dataLayer";
52
+ const dl = window[name] = window[name] || [];
53
+ if (ecommerce) dl.push({ ecommerce: null });
54
+ dl.push(ecommerce ? { event, ecommerce } : { event });
55
+ }
56
+ var bus = null;
57
+ function shopifyBus(shopId) {
58
+ const destinations = [];
59
+ const buffer = [];
60
+ const privacy = () => window.Shopify?.customerPrivacy;
61
+ const canTrack = () => {
62
+ try {
63
+ const p = privacy();
64
+ return p?.consentStatus === "loaded" && p.currentVisitorConsent?.()?.analytics !== "no" && Boolean(p.analyticsProcessingAllowed?.());
65
+ } catch {
66
+ return false;
67
+ }
68
+ };
69
+ const config = { shop: { shopId, channel: "headless" }, consent: { mode: cfg.analytics.shopify?.consent || "no-banner" } };
70
+ const deliver = () => {
71
+ if (!canTrack()) return;
72
+ destinations.forEach((d) => {
73
+ while (d.next < buffer.length) {
74
+ const { event, payload } = buffer[d.next++];
75
+ (d.subs.get(event) || []).forEach((cb) => {
76
+ try {
77
+ cb(payload, d.context);
78
+ } catch (err) {
79
+ reportError(err, `analytics ${d.name}`);
80
+ }
81
+ });
82
+ }
83
+ });
84
+ };
85
+ const api = {
86
+ getConfig: () => config,
87
+ publish(event, payload = {}) {
88
+ buffer.push({ event, payload: { shop: config.shop, url: window.location.href, ...payload } });
89
+ if (buffer.length > 500) {
90
+ buffer.shift();
91
+ destinations.forEach((d) => {
92
+ d.next = Math.max(0, d.next - 1);
93
+ });
94
+ }
95
+ deliver();
96
+ },
97
+ addDestination(dest) {
98
+ const tag = `hydrogen:${dest.name}`;
99
+ const d = {
100
+ name: dest.name,
101
+ next: 0,
102
+ subs: /* @__PURE__ */ new Map(),
103
+ context: {
104
+ getTrackingValues: () => {
105
+ const i = canTrack() ? privacy()?.__internal : null;
106
+ return { uniqueToken: i?.uniqueToken?.({ generateFallback: true, tag }) ?? "", visitToken: i?.visitToken?.({ generateFallback: true, tag }) ?? "" };
107
+ }
108
+ }
109
+ };
110
+ const subscribe = (event, cb) => {
111
+ if (!d.subs.has(event)) d.subs.set(event, /* @__PURE__ */ new Set());
112
+ d.subs.get(event).add(cb);
113
+ return () => d.subs.get(event)?.delete(cb);
114
+ };
115
+ Promise.resolve(dest.setup?.({ subscribe, getConfig: api.getConfig })).then(() => {
116
+ destinations.push(d);
117
+ deliver();
118
+ }).catch((err) => reportError(err, `analytics ${dest.name}`));
119
+ return () => {
120
+ const i = destinations.indexOf(d);
121
+ if (i >= 0) destinations.splice(i, 1);
122
+ };
123
+ }
124
+ };
125
+ document.addEventListener("consentTrackingApiLoaded", deliver);
126
+ document.addEventListener("visitorConsentCollected", deliver);
127
+ return api;
128
+ }
129
+ function loadScript(src) {
130
+ const s = document.createElement("script");
131
+ s.src = src;
132
+ s.async = true;
133
+ document.head.appendChild(s);
134
+ }
135
+ async function startShopify() {
136
+ if (window.Shopify?.analytics) {
137
+ bus = window.Shopify.analytics;
138
+ return;
139
+ }
140
+ const data = await gql("query TetherShopId { shop { id } }", {}, { context: false });
141
+ const country = String(cfg.country || "US").toUpperCase();
142
+ const S = window.Shopify = window.Shopify || {};
143
+ Object.assign(S, {
144
+ country,
145
+ locale: (cfg.locale || "en").split("-")[0].toLowerCase(),
146
+ // required by Shopify's script
147
+ shop: cfg.domain,
148
+ routes: { root: "/", match: () => ({ pageTemplateName: mainProduct() ? "product" : "page" }), ...S.routes || {} }
149
+ });
150
+ const proxy = cfg.analytics.shopify?.proxy;
151
+ S.customerPrivacy = {
152
+ ...S.customerPrivacy || {},
153
+ config: proxy ? { isHeadless: true, asyncConsent: true, asyncVisitorState: true, consentDomain: window.location.host } : { isHeadless: true, storefrontAccessToken: cfg.token, checkoutRootDomain: cfg.domain }
154
+ };
155
+ bus = shopifyBus(data.shop.id);
156
+ S.analytics = bus;
157
+ loadScript(cfg.analytics.shopify?.consent === "default-banner" ? "https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js" : "https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.js");
158
+ loadScript("https://cdn.shopify.com/storefront/analytics/shopify.js");
159
+ log("shopify analytics ready", data.shop.id);
160
+ }
161
+ var productPayload = (p, v, quantity = 1) => ({
162
+ id: p.id,
163
+ title: p.title,
164
+ price: v.price,
165
+ vendor: p.vendor || "",
166
+ variantId: v.id,
167
+ variantTitle: v.title,
168
+ quantity,
169
+ sku: v.sku || void 0,
170
+ productType: p.productType || void 0
171
+ });
172
+ function analyticsLine(l) {
173
+ const m5 = l.merchandise || {};
174
+ return { id: l.id, quantity: l.quantity, merchandise: { id: m5.id, title: m5.title, price: m5.price, sku: m5.sku, product: { id: m5.product?.id, title: m5.product?.title, vendor: m5.product?.vendor || "", handle: m5.product?.handle } } };
175
+ }
176
+ function mainProduct() {
177
+ const el = document.querySelector(sel("product"));
178
+ return el && !el.closest(`${sel("collection")},${sel("products")},${sel("recommendations")}`) ? window.Tether?.getScope(el) : null;
179
+ }
180
+ var send = (name, data, shopifyEvent, shopifyPayload) => {
181
+ if (cfg.analytics.dataLayer) ga(name, data);
182
+ if (bus && shopifyEvent) bus.publish(shopifyEvent, shopifyPayload);
183
+ try {
184
+ cfg.analytics.onEvent?.(name, data);
185
+ } catch (err) {
186
+ reportError(err, "analytics onEvent");
187
+ }
188
+ };
189
+ var lastLines = null;
190
+ var viewed = /* @__PURE__ */ new WeakSet();
191
+ function handle(e) {
192
+ const name = e.type.slice(cfg.prefix.length + 1);
193
+ const d = e.detail || {};
194
+ if (name === "product-ready") {
195
+ const scope = d.scope;
196
+ if (!scope || viewed.has(scope.root) || mainProduct() !== scope) return;
197
+ viewed.add(scope.root);
198
+ const v = scope.variant || d.product.variants.nodes[0];
199
+ const items = [itemFromVariant(d.product, v)];
200
+ send("view_item", { currency: v?.price?.currencyCode, value: value(items), items }, "product_viewed", { products: [productPayload(d.product, v)] });
201
+ } else if (name === "list-ready") {
202
+ const items = (d.products || []).slice(0, 200).map((p, i) => itemFromVariant(p, p.variants?.nodes?.[0], 1, i));
203
+ const list = { item_list_id: d.collection?.handle || "products", item_list_name: d.collection?.title || "Products" };
204
+ send("view_item_list", { ...list, items }, d.collection ? "collection_viewed" : null, d.collection ? { collection: { id: d.collection.id, handle: d.collection.handle } } : null);
205
+ } else if (name === "cart-updated") {
206
+ const cart = d.cart;
207
+ const now = new Map((cart?.lines?.nodes || []).map((l) => [l.merchandise?.id, l]));
208
+ if (lastLines && d.reason !== "load" && d.reason !== "pending") {
209
+ const currency = cart?.cost?.totalAmount?.currencyCode;
210
+ now.forEach((l, k) => {
211
+ const diff = l.quantity - (lastLines.get(k)?.quantity || 0);
212
+ if (diff > 0) {
213
+ const items = [itemFromLine(l, diff)];
214
+ send("add_to_cart", { currency, value: value(items), items }, "product_added_to_cart", {
215
+ cart,
216
+ prevCart: lastCart,
217
+ currentLine: analyticsLine(l),
218
+ prevLine: lastLines.get(k) ? analyticsLine(lastLines.get(k)) : void 0
219
+ });
220
+ }
221
+ });
222
+ lastLines.forEach((l, k) => {
223
+ const diff = l.quantity - (now.get(k)?.quantity || 0);
224
+ if (diff > 0) {
225
+ const items = [itemFromLine(l, diff)];
226
+ send("remove_from_cart", { currency, value: value(items), items });
227
+ }
228
+ });
229
+ }
230
+ lastLines = now;
231
+ lastCart = cart;
232
+ } else if (name === "cart-toggled" && d.open) {
233
+ const items = Cart.lines.map((l) => itemFromLine(l));
234
+ send("view_cart", { currency: Cart.state?.cost?.totalAmount?.currencyCode, value: value(items), items });
235
+ } else if (name === "checkout") {
236
+ const items = (d.cart?.lines?.nodes || []).map((l) => itemFromLine(l));
237
+ const coupon = (d.cart?.discountCodes || []).filter((c) => c.applicable).map((c) => c.code).join(",") || void 0;
238
+ send("begin_checkout", { currency: d.cart?.cost?.totalAmount?.currencyCode, value: value(items), coupon, items });
239
+ } else if (name === "search" && d.term) {
240
+ send("search", { search_term: d.term }, "search_viewed", { searchTerm: d.term });
241
+ } else if (name === "page") {
242
+ viewed = /* @__PURE__ */ new WeakSet();
243
+ if (bus) bus.publish("page_viewed", {});
244
+ }
245
+ }
246
+ var lastCart = null;
247
+ async function startAnalytics(queue) {
248
+ if (cfg.analytics.shopify) await startShopify().catch((err) => reportError(err, "shopify analytics"));
249
+ if (bus) bus.publish("page_viewed", {});
250
+ queue.splice(0).forEach(handle);
251
+ queue.push = (e) => {
252
+ handle(e);
253
+ return 0;
254
+ };
255
+ }
256
+
257
+ // src/parts/analytics.js
258
+ window.Tether.__parts.analytics = analytics_exports;
259
+ })();
@@ -0,0 +1 @@
1
+ (()=>{var F=Object.defineProperty;var I=(n,t)=>{for(var e in t)F(n,e,{get:t[e],enumerable:!0})};var C={};I(C,{startAnalytics:()=>R});var U=window.Tether.__core["config.js"],{DEFAULTS:J,cfg:a,configure:z,isConfigured:G,log:x,reportError:y,warn:Q}=U;var $=window.Tether.__core["client.js"],{TetherError:Y,endpoint:Z,gql:L,request:tt,unwrap:et,withContext:nt}=$;var M=window.Tether.__core["dom.js"],{attr:it,busy:ct,cdnUrl:rt,dataKey:at,emit:st,formatDate:dt,formatMoney:lt,formatPercent:ut,get:pt,has:ft,hasTemplate:ht,imageUrl:mt,notifyLayoutChanged:yt,parseJSONAttr:gt,productUrl:wt,qsa:_t,readText:vt,repeat:Tt,repeatKeyed:bt,sameImage:kt,scoped:Ct,sel:g,setImage:St,setSelected:xt,setText:Lt,setTextTracked:qt,show:Et,toAttributes:At,watchImages:Pt}=M;var V=window.Tether.__core["cart.js"],{Cart:T,focusables:Dt,toLineInput:Ft}=V;var O=n=>n?parseFloat(n.amount):0,q=n=>String(n||"").split("/").pop();function k(n,t,e=1,o){return{item_id:t?.sku||q(t?.id)||q(n.id),item_name:n.title,item_variant:t?.title==="Default Title"?void 0:t?.title,item_brand:n.vendor||void 0,item_category:n.productType||void 0,price:O(t?.price||n.priceRange?.minVariantPrice),quantity:e,...o!=null?{index:o}:{}}}function _(n,t=n.quantity){let e=n.merchandise||{};return k({...e.product||{},title:e.product?.title},e,t)}var w=n=>Math.round(n.reduce((t,e)=>t+(e.price||0)*(e.quantity||1),0)*100)/100;function B(n,t){let e=typeof a.analytics.dataLayer=="string"?a.analytics.dataLayer:"dataLayer",o=window[e]=window[e]||[];t&&o.push({ecommerce:null}),o.push(t?{event:n,ecommerce:t}:{event:n})}var p=null;function H(n){let t=[],e=[],o=()=>window.Shopify?.customerPrivacy,r=()=>{try{let i=o();return i?.consentStatus==="loaded"&&i.currentVisitorConsent?.()?.analytics!=="no"&&!!i.analyticsProcessingAllowed?.()}catch{return!1}},c={shop:{shopId:n,channel:"headless"},consent:{mode:a.analytics.shopify?.consent||"no-banner"}},d=()=>{r()&&t.forEach(i=>{for(;i.next<e.length;){let{event:l,payload:u}=e[i.next++];(i.subs.get(l)||[]).forEach(v=>{try{v(u,i.context)}catch(s){y(s,`analytics ${i.name}`)}})}})},f={getConfig:()=>c,publish(i,l={}){e.push({event:i,payload:{shop:c.shop,url:window.location.href,...l}}),e.length>500&&(e.shift(),t.forEach(u=>{u.next=Math.max(0,u.next-1)})),d()},addDestination(i){let l=`hydrogen:${i.name}`,u={name:i.name,next:0,subs:new Map,context:{getTrackingValues:()=>{let s=r()?o()?.__internal:null;return{uniqueToken:s?.uniqueToken?.({generateFallback:!0,tag:l})??"",visitToken:s?.visitToken?.({generateFallback:!0,tag:l})??""}}}},v=(s,S)=>(u.subs.has(s)||u.subs.set(s,new Set),u.subs.get(s).add(S),()=>u.subs.get(s)?.delete(S));return Promise.resolve(i.setup?.({subscribe:v,getConfig:f.getConfig})).then(()=>{t.push(u),d()}).catch(s=>y(s,`analytics ${i.name}`)),()=>{let s=t.indexOf(u);s>=0&&t.splice(s,1)}}};return document.addEventListener("consentTrackingApiLoaded",d),document.addEventListener("visitorConsentCollected",d),f}function E(n){let t=document.createElement("script");t.src=n,t.async=!0,document.head.appendChild(t)}async function K(){if(window.Shopify?.analytics){p=window.Shopify.analytics;return}let n=await L("query TetherShopId { shop { id } }",{},{context:!1}),t=String(a.country||"US").toUpperCase(),e=window.Shopify=window.Shopify||{};Object.assign(e,{country:t,locale:(a.locale||"en").split("-")[0].toLowerCase(),shop:a.domain,routes:{root:"/",match:()=>({pageTemplateName:D()?"product":"page"}),...e.routes||{}}});let o=a.analytics.shopify?.proxy;e.customerPrivacy={...e.customerPrivacy||{},config:o?{isHeadless:!0,asyncConsent:!0,asyncVisitorState:!0,consentDomain:window.location.host}:{isHeadless:!0,storefrontAccessToken:a.token,checkoutRootDomain:a.domain}},p=H(n.shop.id),e.analytics=p,E(a.analytics.shopify?.consent==="default-banner"?"https://cdn.shopify.com/shopifycloud/privacy-banner/storefront-banner.js":"https://cdn.shopify.com/shopifycloud/consent-tracking-api/v0.2/consent-tracking-api.js"),E("https://cdn.shopify.com/storefront/analytics/shopify.js"),x("shopify analytics ready",n.shop.id)}var N=(n,t,e=1)=>({id:n.id,title:n.title,price:t.price,vendor:n.vendor||"",variantId:t.id,variantTitle:t.title,quantity:e,sku:t.sku||void 0,productType:n.productType||void 0});function A(n){let t=n.merchandise||{};return{id:n.id,quantity:n.quantity,merchandise:{id:t.id,title:t.title,price:t.price,sku:t.sku,product:{id:t.product?.id,title:t.product?.title,vendor:t.product?.vendor||"",handle:t.product?.handle}}}}function D(){let n=document.querySelector(g("product"));return n&&!n.closest(`${g("collection")},${g("products")},${g("recommendations")}`)?window.Tether?.getScope(n):null}var h=(n,t,e,o)=>{a.analytics.dataLayer&&B(n,t),p&&e&&p.publish(e,o);try{a.analytics.onEvent?.(n,t)}catch(r){y(r,"analytics onEvent")}},m=null,b=new WeakSet;function P(n){let t=n.type.slice(a.prefix.length+1),e=n.detail||{};if(t==="product-ready"){let o=e.scope;if(!o||b.has(o.root)||D()!==o)return;b.add(o.root);let r=o.variant||e.product.variants.nodes[0],c=[k(e.product,r)];h("view_item",{currency:r?.price?.currencyCode,value:w(c),items:c},"product_viewed",{products:[N(e.product,r)]})}else if(t==="list-ready"){let o=(e.products||[]).slice(0,200).map((c,d)=>k(c,c.variants?.nodes?.[0],1,d)),r={item_list_id:e.collection?.handle||"products",item_list_name:e.collection?.title||"Products"};h("view_item_list",{...r,items:o},e.collection?"collection_viewed":null,e.collection?{collection:{id:e.collection.id,handle:e.collection.handle}}:null)}else if(t==="cart-updated"){let o=e.cart,r=new Map((o?.lines?.nodes||[]).map(c=>[c.merchandise?.id,c]));if(m&&e.reason!=="load"&&e.reason!=="pending"){let c=o?.cost?.totalAmount?.currencyCode;r.forEach((d,f)=>{let i=d.quantity-(m.get(f)?.quantity||0);if(i>0){let l=[_(d,i)];h("add_to_cart",{currency:c,value:w(l),items:l},"product_added_to_cart",{cart:o,prevCart:j,currentLine:A(d),prevLine:m.get(f)?A(m.get(f)):void 0})}}),m.forEach((d,f)=>{let i=d.quantity-(r.get(f)?.quantity||0);if(i>0){let l=[_(d,i)];h("remove_from_cart",{currency:c,value:w(l),items:l})}})}m=r,j=o}else if(t==="cart-toggled"&&e.open){let o=T.lines.map(r=>_(r));h("view_cart",{currency:T.state?.cost?.totalAmount?.currencyCode,value:w(o),items:o})}else if(t==="checkout"){let o=(e.cart?.lines?.nodes||[]).map(c=>_(c)),r=(e.cart?.discountCodes||[]).filter(c=>c.applicable).map(c=>c.code).join(",")||void 0;h("begin_checkout",{currency:e.cart?.cost?.totalAmount?.currencyCode,value:w(o),coupon:r,items:o})}else t==="search"&&e.term?h("search",{search_term:e.term},"search_viewed",{searchTerm:e.term}):t==="page"&&(b=new WeakSet,p&&p.publish("page_viewed",{}))}var j=null;async function R(n){a.analytics.shopify&&await K().catch(t=>y(t,"shopify analytics")),p&&p.publish("page_viewed",{}),n.splice(0).forEach(P),n.push=t=>(P(t),0)}window.Tether.__parts.analytics=C;})();