@occluze/tether 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/esm/astro.js +2 -2
- package/dist/esm/chunks/account-NVHPA7AX.js +1 -0
- package/dist/esm/chunks/{analytics-22NMIVBV.js → analytics-SLATWPLC.js} +1 -1
- package/dist/esm/chunks/{chunk-YZMBXS6Y.js → chunk-64TUD3QR.js} +1 -1
- package/dist/esm/chunks/{chunk-YXCXIPQV.js → chunk-AJLPU2JC.js} +1 -1
- package/dist/esm/chunks/{chunk-ZVUPVSLF.js → chunk-FIOZPPC5.js} +1 -1
- package/dist/esm/chunks/chunk-K2YEQAIH.js +1 -0
- package/dist/esm/chunks/chunk-L2TRRKYV.js +1 -0
- package/dist/esm/chunks/{chunk-4UR45XTC.js → chunk-RREHGH4A.js} +1 -1
- package/dist/esm/chunks/chunk-VCPI7MJ2.js +1 -0
- package/dist/esm/chunks/{chunk-6R752OYZ.js → chunk-ZIUIWEON.js} +1 -1
- package/dist/esm/chunks/diagnose-4JHU6RTC.js +11 -0
- package/dist/esm/chunks/{extras-QDCSW6VU.js → extras-R4X34VQL.js} +1 -1
- package/dist/esm/chunks/{markets-ui-HJCVLCKJ.js → markets-ui-F3R5T5EL.js} +1 -1
- package/dist/esm/chunks/{search-LN2H6SXS.js → search-NILMGYXG.js} +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/server.js +1 -1
- package/dist/tether.account.js +37 -5
- package/dist/tether.account.min.js +1 -1
- package/dist/tether.diagnose.js +139 -40
- package/dist/tether.diagnose.min.js +11 -1
- package/dist/tether.js +31 -9
- package/dist/tether.min.js +2 -2
- package/dist/tether.min.js.map +3 -3
- package/package.json +2 -2
- package/src/account.js +29 -8
- package/src/astro.js +29 -4
- package/src/cart.js +15 -2
- package/src/client.js +12 -3
- package/src/collection.js +5 -1
- package/src/config.js +1 -1
- package/src/diagnose.js +79 -20
- package/src/dom.js +2 -1
- package/src/index.js +1 -1
- package/src/server.js +7 -6
- package/types/astro.d.ts +2 -0
- package/types/server.d.ts +16 -8
- package/dist/esm/chunks/account-WAWNASZU.js +0 -1
- package/dist/esm/chunks/chunk-BGIKQY6H.js +0 -1
- package/dist/esm/chunks/chunk-CISGFYOF.js +0 -1
- package/dist/esm/chunks/chunk-KARKXC6G.js +0 -1
- package/dist/esm/chunks/diagnose-6ZSK5DGE.js +0 -1
package/dist/tether.account.js
CHANGED
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
var session = null;
|
|
63
63
|
var refreshing = null;
|
|
64
64
|
var customerCache = null;
|
|
65
|
-
var server = () => cfg.account?.server ? String(cfg.account.server).replace(/\/$/, "") : "";
|
|
65
|
+
var server = () => cfg.account?.server ? new URL(String(cfg.account.server), window.location.href).href.replace(/\/$/, "") : "";
|
|
66
66
|
var HINT = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
|
|
67
67
|
var serverState = null;
|
|
68
68
|
async function serverFetch(path, { method = "GET", body } = {}) {
|
|
@@ -104,7 +104,6 @@
|
|
|
104
104
|
if (server()) {
|
|
105
105
|
const u2 = new URL(`${server()}/login`);
|
|
106
106
|
u2.searchParams.set("returnTo", returnTo);
|
|
107
|
-
if (Cart.id()) u2.searchParams.set("cart", Cart.id());
|
|
108
107
|
const lang2 = locale || cfg.account.locale || (cfg.locale || "").split("-")[0];
|
|
109
108
|
if (lang2) u2.searchParams.set("locale", lang2);
|
|
110
109
|
if (cfg.country) u2.searchParams.set("country", String(cfg.country).toUpperCase());
|
|
@@ -284,6 +283,13 @@
|
|
|
284
283
|
u.searchParams.set("post_logout_redirect_uri", returnTo);
|
|
285
284
|
go(u.toString());
|
|
286
285
|
}
|
|
286
|
+
var https = (u) => {
|
|
287
|
+
try {
|
|
288
|
+
return new URL(u).protocol === "https:";
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
287
293
|
var date = (s) => {
|
|
288
294
|
try {
|
|
289
295
|
return new Date(s).toLocaleDateString(cfg.locale || void 0);
|
|
@@ -312,21 +318,47 @@
|
|
|
312
318
|
}
|
|
313
319
|
const lists = qsa(root, sel("orders"));
|
|
314
320
|
if (lists.length) {
|
|
321
|
+
const ordersShow = (loading, empty) => qsa(root, sel("show")).forEach((el) => {
|
|
322
|
+
const k = get(el, "show");
|
|
323
|
+
if (k === "orders-loading") show(el, loading);
|
|
324
|
+
if (k === "orders-empty") show(el, empty);
|
|
325
|
+
});
|
|
326
|
+
ordersShow(true, false);
|
|
327
|
+
let failed = false;
|
|
315
328
|
const list = await orders({ first: parseInt(get(lists[0], "limit") || "10", 10) }).catch((err) => {
|
|
316
329
|
reportError(err, "orders");
|
|
330
|
+
failed = true;
|
|
317
331
|
return [];
|
|
318
332
|
});
|
|
333
|
+
ordersShow(false, !failed && list.length === 0);
|
|
319
334
|
lists.forEach((c) => repeat(c, list, (node, o) => {
|
|
335
|
+
const items = o.lineItems?.nodes || [];
|
|
320
336
|
qsa(node, sel("order-bind")).forEach((el) => {
|
|
321
337
|
const k = get(el, "order-bind");
|
|
322
338
|
if (k === "url") {
|
|
323
|
-
el.setAttribute("href", o.statusPageUrl);
|
|
339
|
+
if (https(o.statusPageUrl)) el.setAttribute("href", o.statusPageUrl);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (k === "image") {
|
|
343
|
+
const im = items.find((l) => l.image)?.image;
|
|
344
|
+
if (im) setImage(el, im.url, im.altText || items[0]?.title || "");
|
|
324
345
|
return;
|
|
325
346
|
}
|
|
326
|
-
const v = { name: o.name, date: date(o.processedAt), total: formatMoney(o.totalPrice), status: o.financialStatus?.toLowerCase(), fulfillment: o.fulfillmentStatus?.toLowerCase(), items:
|
|
347
|
+
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];
|
|
327
348
|
if (v != null) setText(el, v);
|
|
328
349
|
});
|
|
329
|
-
|
|
350
|
+
qsa(node, sel("order-lines")).forEach((box) => repeat(box, items, (row, l) => {
|
|
351
|
+
qsa(row, sel("order-line")).concat(row.matches(sel("order-line")) ? [row] : []).forEach((el) => {
|
|
352
|
+
const k = get(el, "order-line");
|
|
353
|
+
if (k === "image") {
|
|
354
|
+
if (l.image) setImage(el, l.image.url, l.image.altText || l.title);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const v = { title: l.title, "variant-title": l.variantTitle || "", quantity: String(l.quantity) }[k];
|
|
358
|
+
if (v != null) setText(el, v);
|
|
359
|
+
});
|
|
360
|
+
}));
|
|
361
|
+
if (get(node, "order-bind") === "url" && https(o.statusPageUrl)) node.setAttribute("href", o.statusPageUrl);
|
|
330
362
|
}));
|
|
331
363
|
}
|
|
332
364
|
}
|
|
@@ -1 +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;})();
|
|
1
|
+
(()=>{var dt=Object.defineProperty;var ft=(t,e)=>{for(var o in e)dt(t,o,{get:e[o],enumerable:!0})};var X={};ft(X,{customer:()=>it,getToken:()=>$,handleCallback:()=>st,isLoggedIn:()=>V,login:()=>nt,logout:()=>ut,mountAccount:()=>Tt,orders:()=>lt,query:()=>W,render:()=>D});var wt=window.Tether.__core["config.js"],{DEFAULTS:It,cfg:a,configure:At,isConfigured:xt,log:Q,reportError:I,warn:Ut}=wt;var mt=window.Tether.__core["client.js"],{TetherError:_,endpoint:Ct,gql:$t,request:bt,unwrap:Nt,withContext:Pt}=mt;var ht=window.Tether.__core["dom.js"],{attr:Dt,busy:Ot,cdnUrl:Lt,dataKey:qt,emit:A,formatDate:jt,formatMoney:Z,formatPercent:Rt,get:S,has:Jt,hasTemplate:Bt,imageUrl:Gt,notifyLayoutChanged:Kt,parseJSONAttr:Ft,productUrl:Mt,qsa:T,readText:zt,repeat:R,repeatKeyed:Ht,sameImage:Vt,scoped:Wt,sel:h,setImage:J,setSelected:Xt,setText:P,setTextTracked:Yt,show:x,toAttributes:Qt,watchImages:Zt}=ht;var gt=window.Tether.__core["cart.js"],{Cart:f,focusables:ee,toLineInput:oe}=gt;var O=()=>`${a.prefix}:account:${a.domain}`,K=()=>`${a.prefix}:account-login:${a.domain}`,L=()=>a.account?.remember?window.localStorage:window.sessionStorage,ot=t=>btoa(String.fromCharCode(...new Uint8Array(t))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),B=()=>ot(crypto.getRandomValues(new Uint8Array(32))),pt=async t=>ot(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t))),C=t=>(a.account?.navigate||(e=>window.location.assign(e)))(t),z=(t,e)=>{try{return JSON.parse(t.getItem(e)||"null")}catch{return null}},q=(t,e,o)=>{try{o?t.setItem(e,JSON.stringify(o)):t.removeItem(e)}catch{}},v=null,c=null,G=null,U=null,g=()=>a.account?.server?new URL(String(a.account.server),window.location.href).href.replace(/\/$/,""):"",F=()=>`${a.prefix}:account-hint:${a.domain}`,E=null;async function j(t,{method:e="GET",body:o}={}){let n=await fetch(`${g()}${t}`,{method:e,credentials:"include",headers:e==="POST"?{"Content-Type":"application/json","X-Tether-Account":"1"}:{},body:o?JSON.stringify(o):void 0}),s=await n.json().catch(()=>({}));if(!n.ok&&n.status!==401)throw new _(`Customer accounts: ${s.error||n.status}`,{status:n.status});return s}async function H(){E||(E=await j("/me"));try{E.loggedIn?window.localStorage.setItem(F(),"1"):window.localStorage.removeItem(F())}catch{}return E}async function b(){if(v)return v;let t=a.account?.shopDomain||a.domain,e=s=>fetch(`https://${t}/.well-known/${s}`).then(r=>{if(!r.ok)throw new _(`Customer accounts: ${s} answered ${r.status}`,{status:r.status});return r.json()}),[o,n]=await Promise.all([e("openid-configuration"),e("customer-account-api")]);return v={...o,graphql:n.graphql_api},v}var yt=()=>a.account?.callbackUrl||`${window.location.origin}${window.location.pathname}`;function V(){return g()?!!E?.loggedIn:!!(c?.refresh||c?.access&&c.expiresAt>Date.now())}async function nt({returnTo:t=window.location.href,locale:e}={}){if(g()){let d=new URL(`${g()}/login`);d.searchParams.set("returnTo",t);let m=e||a.account.locale||(a.locale||"").split("-")[0];m&&d.searchParams.set("locale",m),a.country&&d.searchParams.set("country",String(a.country).toUpperCase()),C(d.toString());return}if(!a.account?.clientId)throw new _("Customer accounts: set account.clientId",{code:"NOT_CONFIGURED"});let o=await b(),n=B(),s=B(),r=B(),u=yt();q(window.sessionStorage,K(),{state:n,nonce:s,verifier:r,redirect:u,returnTo:t,at:Date.now()});let i=new URL(o.authorization_endpoint),w={client_id:a.account.clientId,scope:"openid email customer-account-api:full",response_type:"code",redirect_uri:u,state:n,nonce:s,code_challenge:await pt(r),code_challenge_method:"S256"},l=e||a.account.locale||(a.locale||"").split("-")[0];l&&(w.locale=l),a.country&&(w.region_country=String(a.country).toUpperCase()),Object.entries(w).forEach(([d,m])=>i.searchParams.set(d,m)),C(i.toString())}async function rt(t){let e=await b(),o=await fetch(a.account.tokenUrl||e.token_endpoint,{method:"POST",body:new URLSearchParams(t)}),n=await o.json().catch(()=>({}));if(!o.ok||!n.access_token)throw new _(`Customer accounts: token ${o.status} ${n.error||""}`.trim(),{status:o.status,code:o.status===400||o.status===401?"SESSION_ENDED":"TOKEN_FAILED"});return n}function _t(t){try{return JSON.parse(atob(t.split(".")[1].replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function at(t,e=c){c={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},q(L(),O(),c)}async function st(){let t=new URLSearchParams(window.location.search),e=z(window.sessionStorage,K());if(!e||!t.has("code")&&!t.has("error")||t.get("state")!==e.state)return!1;q(window.sessionStorage,K(),null);let o=new URL(window.location.href);if(["code","state","iss","error","error_description"].forEach(w=>o.searchParams.delete(w)),window.history.replaceState(window.history.state,"",o),t.has("error"))return A("account-error",{error:t.get("error")}),!1;if(Date.now()-e.at>10*6e4)throw new _("Customer accounts: the login took too long, please try again");let n=await rt({grant_type:"authorization_code",client_id:a.account.clientId,redirect_uri:e.redirect,code:t.get("code"),code_verifier:e.verifier}),s=await b(),r=_t(n.id_token||""),u=[].concat(r?.aud||[]);if(!r||r.nonce!==e.nonce||r.iss!==s.issuer||!u.includes(a.account.clientId)||r.exp*1e3<Date.now())throw new _("Customer accounts: the login answer did not check out (nonce/issuer/audience)");at(n,null),await ct(),A("account-login",{});let i=e.returnTo&&new URL(e.returnTo,window.location.href);return i&&i.origin===window.location.origin&&i.href!==o.href&&C(i.href),!0}async function $(){return g()||(c||(c=z(L(),O())),!c)?null:c.access&&c.expiresAt>Date.now()?c.access:c.refresh?(G||(G=(async()=>{try{at(await rt({grant_type:"refresh_token",client_id:a.account.clientId,refresh_token:c.refresh})),await ct()}catch(t){if(t.code==="SESSION_ENDED")M();else throw t}finally{G=null}})()),await G,c?.access||null):(M(),null)}function M(){c=null,U=null,q(L(),O(),null),f.customerAccessToken=null}async function ct(){f.customerAccessToken=c?.access||null,f.id()&&c?.access&&await f.setBuyerIdentity({customerAccessToken:c.access},{create:!1}).catch(t=>I(t,"account cart"))}async function W(t,e={}){if(g())throw new _("Customer accounts in server mode only answer customer() and orders()",{code:"NOT_ALLOWED"});let o=await $();if(!o)throw new _("Not logged in",{code:"NOT_LOGGED_IN"});let n=await b(),s=i=>fetch(n.graphql,{method:"POST",headers:{"Content-Type":"application/json",Authorization:i},body:JSON.stringify({query:t,variables:e})}),r=await s(o);r.status===401&&c?.refresh&&(c.expiresAt=0,r=await s(await $()));let u=await r.json().catch(()=>({}));if(!r.ok||u.errors?.length)throw new _(u.errors?.map(i=>i.message).join(" | ")||`Customer accounts: ${r.status}`,{status:r.status});return u.data}async function it(){if(g()){let e=await H();return e.loggedIn?{...e.customer,emailAddress:{emailAddress:e.customer.email},defaultAddress:{formatted:e.customer.address}}:null}return U||(U=(await W("query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}")).customer,U)}async function lt({first:t=10}={}){return g()?(await j(`/orders?first=${t}`)).orders||[]:(await W("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 ut({returnTo:t=a.account?.logoutUrl||`${window.location.origin}/`}={}){if(g()){let s=await j("/logout",{method:"POST",body:{returnTo:t,cartId:f.id()}});E={loggedIn:!1};try{window.localStorage.removeItem(F())}catch{}A("account-logout",{}),s.logoutUrl?C(s.logoutUrl):D(document);return}let e=c?.idToken||z(L(),O())?.idToken;if(f.id()&&await f.setBuyerIdentity({customerAccessToken:null},{create:!1}).catch(()=>{}),M(),A("account-logout",{}),!e){D(document);return}let o=await b(),n=new URL(o.end_session_endpoint);n.searchParams.set("id_token_hint",e),n.searchParams.set("post_logout_redirect_uri",t),C(n.toString())}var tt=t=>{try{return new URL(t).protocol==="https:"}catch{return!1}},St=t=>{try{return new Date(t).toLocaleDateString(a.locale||void 0)}catch{return t}};async function D(t=document){let e=g()?!!(await H().catch(()=>({loggedIn:!1}))).loggedIn:V()||!!await $().catch(()=>null);if(T(t,h("show")).forEach(s=>{let r=S(s,"show");r==="logged-in"&&x(s,e),r==="logged-out"&&x(s,!e)}),!e)return;let o=T(t,h("account-bind"));if(o.length){let s=await it().catch(r=>(I(r,"account"),null));o.forEach(r=>{let u={"first-name":s?.firstName,"last-name":s?.lastName,name:s?.displayName,email:s?.emailAddress?.emailAddress,address:s?.defaultAddress?.formatted?.join(", ")}[S(r,"account-bind")];u!=null&&P(r,u)})}let n=T(t,h("orders"));if(n.length){let s=(i,w)=>T(t,h("show")).forEach(l=>{let d=S(l,"show");d==="orders-loading"&&x(l,i),d==="orders-empty"&&x(l,w)});s(!0,!1);let r=!1,u=await lt({first:parseInt(S(n[0],"limit")||"10",10)}).catch(i=>(I(i,"orders"),r=!0,[]));s(!1,!r&&u.length===0),n.forEach(i=>R(i,u,(w,l)=>{let d=l.lineItems?.nodes||[];T(w,h("order-bind")).forEach(m=>{let k=S(m,"order-bind");if(k==="url"){tt(l.statusPageUrl)&&m.setAttribute("href",l.statusPageUrl);return}if(k==="image"){let y=d.find(N=>N.image)?.image;y&&J(m,y.url,y.altText||d[0]?.title||"");return}let p={name:l.name,date:St(l.processedAt),total:Z(l.totalPrice),status:l.financialStatus?.toLowerCase(),fulfillment:l.fulfillmentStatus?.toLowerCase(),items:d.map(y=>`${y.quantity}\xD7 ${y.title}`).join(", ")}[k];p!=null&&P(m,p)}),T(w,h("order-lines")).forEach(m=>R(m,d,(k,p)=>{T(k,h("order-line")).concat(k.matches(h("order-line"))?[k]:[]).forEach(y=>{let N=S(y,"order-line");if(N==="image"){p.image&&J(y,p.image.url,p.image.altText||p.title);return}let Y={title:p.title,"variant-title":p.variantTitle||"",quantity:String(p.quantity)}[N];Y!=null&&P(y,Y)})})),S(w,"order-bind")==="url"&&tt(l.statusPageUrl)&&w.setAttribute("href",l.statusPageUrl)}))}}var et=!1;async function Tt(t=document){if(!et){if(et=!0,document.addEventListener("click",e=>{let o=e.target;o.closest?.(h("account-login"))?(e.preventDefault(),nt().catch(n=>I(n,"login"))):o.closest?.(h("account-logout"))&&(e.preventDefault(),ut().catch(n=>I(n,"logout")))}),g()){let e=new URL(window.location.href);e.searchParams.get("account")&&(e.searchParams.delete("account"),window.history.replaceState(window.history.state,"",e),A("account-login",{})),f.beforeCheckout=async()=>{if(!(await H()).loggedIn||!f.id())return null;let o=await j("/cart",{method:"POST",body:{cartId:f.id()}}).catch(()=>null);return o?.checkoutUrl?`${o.checkoutUrl}${o.checkoutUrl.includes("?")?"&":"?"}logged_in=true`:null}}else{try{await st()}catch(e){I(e,"login")}!f.customerAccessToken&&await $().catch(()=>null)&&(f.customerAccessToken=c.access)}Q("account",V()?"logged in":"logged out")}await D(t)}window.Tether.__parts.account=X;})();
|
package/dist/tether.diagnose.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
__export(diagnose_exports, {
|
|
11
11
|
apiVersionStatus: () => apiVersionStatus,
|
|
12
12
|
checkMarkup: () => checkMarkup,
|
|
13
|
-
diagnose: () => diagnose
|
|
13
|
+
diagnose: () => diagnose,
|
|
14
|
+
didYouMean: () => didYouMean
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
// tether-core:config.js
|
|
@@ -150,6 +151,114 @@
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
// src/diagnose.js
|
|
154
|
+
var SHOW = /* @__PURE__ */ new Set([
|
|
155
|
+
"in-stock",
|
|
156
|
+
"out-of-stock",
|
|
157
|
+
"backorder",
|
|
158
|
+
"low-stock",
|
|
159
|
+
"plenty-in-stock",
|
|
160
|
+
"on-sale",
|
|
161
|
+
"not-on-sale",
|
|
162
|
+
"has-variants",
|
|
163
|
+
"single-variant",
|
|
164
|
+
"subscription-available",
|
|
165
|
+
"subscription-unavailable",
|
|
166
|
+
"subscription-only",
|
|
167
|
+
"one-time-available",
|
|
168
|
+
"subscription-selected",
|
|
169
|
+
"one-time-selected",
|
|
170
|
+
"has-savings",
|
|
171
|
+
"loading",
|
|
172
|
+
"ready",
|
|
173
|
+
"error",
|
|
174
|
+
"not-found",
|
|
175
|
+
"cart-empty",
|
|
176
|
+
"cart-filled",
|
|
177
|
+
"cart-has-subscription",
|
|
178
|
+
"cart-no-subscription",
|
|
179
|
+
"cart-has-discount",
|
|
180
|
+
"cart-error",
|
|
181
|
+
"cart-warning",
|
|
182
|
+
"list-empty",
|
|
183
|
+
"list-filled",
|
|
184
|
+
"search-empty",
|
|
185
|
+
"search-results",
|
|
186
|
+
"logged-in",
|
|
187
|
+
"logged-out",
|
|
188
|
+
"orders-empty",
|
|
189
|
+
"orders-loading"
|
|
190
|
+
]);
|
|
191
|
+
var BIND = /* @__PURE__ */ new Set([
|
|
192
|
+
"title",
|
|
193
|
+
"description",
|
|
194
|
+
"description-text",
|
|
195
|
+
"vendor",
|
|
196
|
+
"type",
|
|
197
|
+
"handle",
|
|
198
|
+
"published-at",
|
|
199
|
+
"price",
|
|
200
|
+
"total-price",
|
|
201
|
+
"compare-at-price",
|
|
202
|
+
"unit-price",
|
|
203
|
+
"min-price",
|
|
204
|
+
"max-price",
|
|
205
|
+
"discount-percentage",
|
|
206
|
+
"discount-amount",
|
|
207
|
+
"variant-title",
|
|
208
|
+
"variant-id",
|
|
209
|
+
"sku",
|
|
210
|
+
"barcode",
|
|
211
|
+
"stock",
|
|
212
|
+
"quantity",
|
|
213
|
+
"featured-image",
|
|
214
|
+
"variant-image",
|
|
215
|
+
"images",
|
|
216
|
+
"tags",
|
|
217
|
+
"link",
|
|
218
|
+
"url",
|
|
219
|
+
"one-time-price",
|
|
220
|
+
"subscription-price",
|
|
221
|
+
"subscription-savings",
|
|
222
|
+
"subscription-savings-amount",
|
|
223
|
+
"checkout-charge",
|
|
224
|
+
"plan-name",
|
|
225
|
+
"plan-description",
|
|
226
|
+
"plan-group",
|
|
227
|
+
"option-value",
|
|
228
|
+
"swatch",
|
|
229
|
+
"image"
|
|
230
|
+
]);
|
|
231
|
+
var ATTRS = new Set(`account account-bind account-link account-login account-logout add-label add-to-cart adding-text
|
|
232
|
+
always-enabled bind cart-checkout-charge cart-clear cart-close cart-count cart-discounts cart-drawer cart-label cart-lines
|
|
233
|
+
cart-note cart-open cart-subtotal cart-total cart-warning checkout code collection collection-bind countries country
|
|
234
|
+
country-bind country-label country-select default-plan default-purchase discount discount-code discount-form discount-input
|
|
235
|
+
discount-remove display error group group-add hide-single language limit line line-attribute line-attributes line-id
|
|
236
|
+
line-minus line-plan line-plus line-remove line-show list load-more market-pending one-time-label option option-list
|
|
237
|
+
option-selected option-value order-bind order-line order-lines orders page-country page-language page-locale page-title plan plan-bind plan-list
|
|
238
|
+
plan-value product products purchase purchase-mode qty qty-minus qty-plus quantity recommendations reverse search
|
|
239
|
+
search-bind search-collections search-input search-limit search-min search-products search-queries search-results
|
|
240
|
+
search-state show soldout-text sort state subscribe-text variant variant-select variant-url max-width max-height crop
|
|
241
|
+
sig label hydrated busy`.split(/\s+/).filter(Boolean));
|
|
242
|
+
function distance(a, b) {
|
|
243
|
+
const d = Array.from({ length: a.length + 1 }, (_, i) => [i, ...Array(b.length).fill(0)]);
|
|
244
|
+
for (let j = 1; j <= b.length; j++) d[0][j] = j;
|
|
245
|
+
for (let i = 1; i <= a.length; i++) {
|
|
246
|
+
for (let j = 1; j <= b.length; j++) d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
247
|
+
}
|
|
248
|
+
return d[a.length][b.length];
|
|
249
|
+
}
|
|
250
|
+
function didYouMean(value, options, prefix = "") {
|
|
251
|
+
let best = null;
|
|
252
|
+
let score = Infinity;
|
|
253
|
+
for (const o of options) {
|
|
254
|
+
const n = distance(String(value), o);
|
|
255
|
+
if (n < score) {
|
|
256
|
+
score = n;
|
|
257
|
+
best = o;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return best && score <= Math.max(2, Math.floor(String(value).length / 4)) ? ` Did you mean "${prefix}${best}"?` : "";
|
|
261
|
+
}
|
|
153
262
|
var mask = (t) => t ? `${String(t).slice(0, 4)}\u2026${String(t).slice(-4)} (${String(t).length} chars)` : "(missing)";
|
|
154
263
|
function describe(el) {
|
|
155
264
|
const id = el.id ? `#${el.id}` : "";
|
|
@@ -178,48 +287,33 @@
|
|
|
178
287
|
qsa(root, sel("group-add")).forEach((el) => {
|
|
179
288
|
if (!el.closest(sel("group"))) add("warn", `${attr("group-add")} outside a ${attr("group")}.`, el);
|
|
180
289
|
});
|
|
181
|
-
[
|
|
290
|
+
["option-list", "plan-list", "cart-lines", "list"].forEach((name) => qsa(root, sel(name)).forEach((el) => {
|
|
182
291
|
if (!el.firstElementChild && !hasTemplate(el)) {
|
|
183
|
-
add("warn",
|
|
292
|
+
add("warn", `${attr(name)} without a template child: put one example element inside it.`, el);
|
|
184
293
|
}
|
|
185
|
-
});
|
|
294
|
+
}));
|
|
186
295
|
qsa(root, listSel).forEach((el) => {
|
|
187
296
|
if (!qsa(el, sel("list")).length) add("error", `List without a ${attr("list")} child: products have nowhere to render.`, el);
|
|
188
297
|
});
|
|
189
|
-
const known = /* @__PURE__ */ new Set([
|
|
190
|
-
"in-stock",
|
|
191
|
-
"out-of-stock",
|
|
192
|
-
"backorder",
|
|
193
|
-
"low-stock",
|
|
194
|
-
"plenty-in-stock",
|
|
195
|
-
"on-sale",
|
|
196
|
-
"not-on-sale",
|
|
197
|
-
"has-variants",
|
|
198
|
-
"single-variant",
|
|
199
|
-
"subscription-available",
|
|
200
|
-
"subscription-unavailable",
|
|
201
|
-
"subscription-only",
|
|
202
|
-
"one-time-available",
|
|
203
|
-
"subscription-selected",
|
|
204
|
-
"one-time-selected",
|
|
205
|
-
"has-savings",
|
|
206
|
-
"loading",
|
|
207
|
-
"ready",
|
|
208
|
-
"error",
|
|
209
|
-
"not-found",
|
|
210
|
-
"cart-empty",
|
|
211
|
-
"cart-filled",
|
|
212
|
-
"cart-has-subscription",
|
|
213
|
-
"cart-no-subscription",
|
|
214
|
-
"cart-has-discount",
|
|
215
|
-
"cart-error",
|
|
216
|
-
"cart-warning",
|
|
217
|
-
"list-empty",
|
|
218
|
-
"list-filled"
|
|
219
|
-
]);
|
|
220
298
|
qsa(root, sel("show")).forEach((el) => {
|
|
221
299
|
const k = get(el, "show");
|
|
222
|
-
if (!
|
|
300
|
+
if (!SHOW.has(k)) add("warn", `Unknown ${attr("show")}="${k}".${didYouMean(k, SHOW)}`, el);
|
|
301
|
+
});
|
|
302
|
+
qsa(root, sel("bind")).forEach((el) => {
|
|
303
|
+
const k = get(el, "bind") || "";
|
|
304
|
+
if (!k || BIND.has(k) || /^image:\d+$/.test(k) || k.startsWith("metafield:")) return;
|
|
305
|
+
add("warn", `Unknown ${attr("bind")}="${k}": it stays empty.${didYouMean(k, BIND)}`, el);
|
|
306
|
+
});
|
|
307
|
+
const prefix = `${cfg.prefix}-`;
|
|
308
|
+
const seen = /* @__PURE__ */ new Set();
|
|
309
|
+
qsa(root, "*").forEach((el) => {
|
|
310
|
+
for (const a of el.attributes) {
|
|
311
|
+
if (!a.name.startsWith(prefix)) continue;
|
|
312
|
+
const name = a.name.slice(prefix.length);
|
|
313
|
+
if (ATTRS.has(name) || seen.has(name)) continue;
|
|
314
|
+
seen.add(name);
|
|
315
|
+
add("warn", `Unknown attribute ${a.name}: Tether ignores it.${didYouMean(name, ATTRS, prefix)}`, el);
|
|
316
|
+
}
|
|
223
317
|
});
|
|
224
318
|
qsa(root, sel("bind")).forEach((el) => {
|
|
225
319
|
const k = get(el, "bind") || "";
|
|
@@ -275,8 +369,7 @@
|
|
|
275
369
|
report.reachable = true;
|
|
276
370
|
report.shop = { name: data.shop?.name, url: data.shop?.primaryDomain?.url, currency: data.shop?.paymentSettings?.currencyCode };
|
|
277
371
|
} catch (err) {
|
|
278
|
-
|
|
279
|
-
report.problems.push({ level: "error", message: `Storefront API not reachable: ${err.message}${hint ? `. ${hint}` : ""}` });
|
|
372
|
+
report.problems.push({ level: "error", message: `Storefront API not reachable: ${err.message || err.code || "unknown error"}` });
|
|
280
373
|
}
|
|
281
374
|
report.latencyMs = Date.now() - t0;
|
|
282
375
|
if (report.reachable) {
|
|
@@ -291,13 +384,19 @@
|
|
|
291
384
|
qsa(root, sel("product")).filter((el) => resolveProductKey(el).key === keys[i] && el.querySelector(sel("add-to-cart"))).forEach((el) => {
|
|
292
385
|
const shown = new Set(qsa(el, `${sel("option-list")},select${sel("option")}`).map((x) => get(x, "option-list") || get(x, "option")));
|
|
293
386
|
if (!shown.size && qsa(el, `select${sel("variant-select")}`).length) return;
|
|
387
|
+
const names = (p.options || []).map((o) => o.name);
|
|
388
|
+
if (!cfg.language) {
|
|
389
|
+
[...shown].filter((n) => n && !names.includes(n)).forEach((n) => {
|
|
390
|
+
report.problems.push({ level: "warn", message: `Product "${p.handle}" has no option "${n}" (its options: ${names.join(", ") || "none"}).${didYouMean(n, names)}` });
|
|
391
|
+
});
|
|
392
|
+
}
|
|
294
393
|
(p.options || []).filter((o) => o.optionValues.length > 1 && !shown.has(o.name)).forEach((o) => {
|
|
295
394
|
report.problems.push({ level: "warn", message: `Product "${p.handle}": option "${o.name}" (${o.optionValues.length} values) has no tether-option-list, so visitors can't choose it.` });
|
|
296
395
|
});
|
|
297
396
|
});
|
|
298
397
|
} else {
|
|
299
398
|
report.products.push({ key: keys[i], error: r.reason?.message });
|
|
300
|
-
report.problems.push({ level: "error", message: `Product "${keys[i]}": ${r.reason?.message}. Is it published to the Headless channel?` });
|
|
399
|
+
report.problems.push({ level: "error", message: `Product "${keys[i]}": ${r.reason?.message || r.reason?.code || "not loaded"}. Is it published to the Headless channel?` });
|
|
301
400
|
}
|
|
302
401
|
});
|
|
303
402
|
}
|
|
@@ -315,7 +414,7 @@
|
|
|
315
414
|
c.group?.(`[${cfg.prefix}] diagnose: ${errors ? `${errors} error(s)` : "no errors"}, ${r.problems.length - errors} warning(s)`);
|
|
316
415
|
c.log("version", r.version, "| API", r.apiVersion.version, `(${r.apiVersion.note})`);
|
|
317
416
|
c.log("config", r.config);
|
|
318
|
-
if (r.market) c.log("market
|
|
417
|
+
if (r.market) c.log("market", `in use: ${[r.market.country, r.market.language].filter(Boolean).join(" / ") || "the store default"}`, r.market.detected ? `(detection picks ${JSON.stringify(r.market.detected)})` : "");
|
|
319
418
|
c.log(r.reachable ? `Storefront API reachable: ${r.shop?.name} (${r.latencyMs} ms)` : "Storefront API NOT reachable");
|
|
320
419
|
if (r.products.length) (c.table || c.log)(r.products);
|
|
321
420
|
if (r.sellingPlans.length) (c.table || c.log)(r.sellingPlans);
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
(()=>{var I=Object.defineProperty;var N=(e,t)=>{for(var o in t)I(e,o,{get:t[o],enumerable:!0})};var E={};N(E,{apiVersionStatus:()=>S,checkMarkup:()=>D,diagnose:()=>ee});var q=window.Tether.__core["config.js"],{DEFAULTS:ne,cfg:i,configure:re,isConfigured:T,log:ae,reportError:se,warn:ie}=q;var M=window.Tether.__core["client.js"],{TetherError:ce,endpoint:x,gql:$,request:ue,unwrap:pe,withContext:de}=M;var O=window.Tether.__core["dom.js"],{attr:f,busy:me,cdnUrl:he,dataKey:ge,emit:ye,formatDate:we,formatMoney:be,formatPercent:$e,get:w,has:Se,hasTemplate:C,imageUrl:ke,notifyLayoutChanged:Ee,parseJSONAttr:Pe,productUrl:Te,qsa:u,readText:xe,repeat:Ce,repeatKeyed:ve,sameImage:_e,scoped:Ae,sel:c,setImage:Ve,setSelected:Ue,setText:Le,setTextTracked:De,show:Ie,toAttributes:Ne,watchImages:qe}=O;var R=window.Tether.__core["loader.js"],{clearProductCache:Oe,getProduct:v,getProducts:Re,getRecommendations:je,hydrateProducts:ze,normalizeProductKey:Ge,primeProduct:Be,refreshProducts:He,takeStale:Ke}=R;var j=window.Tether.__core["selling-plans.js"],{GID:Fe,allPlans:_,allocationFor:Ze,findPlan:Je,hasPlans:We,planAdjustment:Xe,planCheckoutCharge:Qe,planPrice:et,planSavings:tt,plansForVariant:ot,toGid:nt}=j;var z=window.Tether.__core["status.js"],{STATUS_KEYS:at,boundary:A,isNotFound:st,ownedBy:it,resolveProductKey:b,setScopeStatus:lt}=z;var U=e=>{let t=/^(\d{4})-(01|04|07|10)$/.exec(String(e||""));return t?{y:+t[1],m:+t[2]}:null},G=(e,t)=>`${e}-${String(t).padStart(2,"0")}`;function V(e,t=1){let o=U(e);if(!o)throw new Error(`not a quarterly Shopify API version: ${e}`);let s=o.y*4+(o.m-1)/3+t;return G(Math.floor(s/4),s%4*3+1)}function S(e,t=new Date){if(e==="unstable")return{version:e,ok:!1,note:'"unstable" changes without notice: pin a dated version such as 2026-07'};let o=U(e);if(!o)return{version:e,ok:!1,note:"not a quarterly version (YYYY-01, -04, -07 or -10)"};let s=new Date(Date.UTC(o.y,o.m-1,1)),n=new Date(Date.UTC(o.y+1,o.m-1,1)),d=(n-t)/(30.44*24*3600*1e3),a=`supported until about ${n.toISOString().slice(0,7)}`;return s>t?a="not released yet (release candidate at best)":d<=0?a="no longer supported: Shopify falls back to the oldest supported version. Upgrade (see README)":d<3&&(a=`${a}: plan the upgrade to ${V(e)} or later (see README)`),{version:e,ok:s<=t&&d>0,monthsLeft:Math.round(d),next:V(e),note:a}}var B=/^[A-Z]{2}$/,H=new Set(["PT_BR","PT_PT","ZH_CN","ZH_TW"]),K=()=>`${i.prefix}:market:${i.domain}`,Y=()=>({document:typeof document<"u"?document:null,location:typeof window<"u"?window.location:null,storage:(()=>{try{return window.localStorage}catch{return null}})(),global:typeof window<"u"?window:{}});function F(e){let t=String(e||"").trim().toUpperCase();return B.test(t)&&t!=="XX"&&t!=="T1"?t:null}function Z(e){let t=String(e||"").trim().replace("-","_").toUpperCase();if(!t)return null;if(H.has(t))return t;let o=t.split("_")[0];return/^[A-Z]{2,3}$/.test(o)?o:null}function J(e,t){if(!e||!t)return null;let o=String(e.cookie||"").split(";").map(s=>s.trim()).find(s=>s.startsWith(`${t}=`));return o?decodeURIComponent(o.slice(t.length+1)):null}var k=(e,t)=>{if(!e||!t)return null;let o=String(t).toLowerCase(),s=Object.keys(e).find(n=>n.toLowerCase()===o);return s?e[s]:null},W={saved(e,t){if(e.remember===!1||!t.storage)return null;try{return JSON.parse(t.storage.getItem(K())||"null")}catch{return null}},server(e,t){let o=d=>t.document?.querySelector?.(`meta[name="${d}"]`)?.getAttribute("content"),s=t.global.TETHER_COUNTRY||o("tether-country")||J(t.document,e.cookie||"tether_country"),n=t.global.TETHER_LANGUAGE||o("tether-language");return{country:s,language:n}},webflow(e,t){let o=(t.location?.pathname||"/").split("/").filter(Boolean)[0],s=k(e.locales,o);if(s)return s;let n=t.document?.documentElement?.getAttribute("lang");if(!n)return null;let d=k(e.locales,n)||k(e.locales,n.split("-")[0]);if(d)return{locale:n,...d};if(e.locales&&e.strict)return null;let a=n.split("-")[1];return{language:n,country:a&&a.length===2?a:null,locale:n}}};function L(e=i.markets,t=Y()){let o=e||{};if(o.detect===!1)return{};let s=o.detect?o.detect===!0?["saved","server","webflow"]:[].concat(o.detect):["saved"];e=o;let n={sources:{}};return s.forEach(d=>{let a=W[d]?.(e,t);if(!a)return;let r={country:F(a.country),language:Z(a.language),locale:a.locale||null};Object.entries(r).forEach(([l,p])=>{p&&!n[l]&&(n[l]=p,n.sources[l]=d)})}),n}var X=e=>e?`${String(e).slice(0,4)}\u2026${String(e).slice(-4)} (${String(e).length} chars)`:"(missing)";function Q(e){let t=e.id?`#${e.id}`:"",o=e.className&&typeof e.className=="string"?`.${e.className.trim().split(/\s+/).slice(0,2).join(".")}`:"";return`<${e.tagName.toLowerCase()}${t}${o}>`}function D(e=document){let t=[],o=(r,l,p)=>t.push({level:r,message:l,element:p?Q(p):void 0,el:p}),s=r=>!!r.closest(c("product")),n=`${c("collection")},${c("products")},${c("recommendations")}`;u(e,c("product")).forEach(r=>{if(r.closest(n)&&r.closest(c("list")))return;let{key:l,fromUrl:p,raw:m}=b(r);m?p&&!l&&o("warn",`${f("product")}="${m}" but this URL has no value for it (e.g. ?handle=my-product).`,r):o("error",`${f("product")} has no value. Bind it to the Shopify handle (CMS field), or use "url:handle".`,r)}),["bind","option-list","option","variant-select","add-to-cart","purchase","plan-list","plan","qty","line-attribute"].forEach(r=>{u(e,c(r)).forEach(l=>{s(l)||r==="line-attribute"&&l.closest(c("group"))||o("warn",`${f(r)} outside any ${f("product")}: it does nothing here.`,l)})}),u(e,c("group-add")).forEach(r=>{r.closest(c("group"))||o("warn",`${f("group-add")} outside a ${f("group")}.`,r)}),[...u(e,c("option-list")),...u(e,c("plan-list")),...u(e,c("cart-lines")),...u(e,c("list"))].forEach(r=>{!r.firstElementChild&&!C(r)&&o("warn","Repeater without a template child: put one example element inside it.",r)}),u(e,n).forEach(r=>{u(r,c("list")).length||o("error",`List without a ${f("list")} child: products have nowhere to render.`,r)});let a=new Set(["in-stock","out-of-stock","backorder","low-stock","plenty-in-stock","on-sale","not-on-sale","has-variants","single-variant","subscription-available","subscription-unavailable","subscription-only","one-time-available","subscription-selected","one-time-selected","has-savings","loading","ready","error","not-found","cart-empty","cart-filled","cart-has-subscription","cart-no-subscription","cart-has-discount","cart-error","cart-warning","list-empty","list-filled"]);return u(e,c("show")).forEach(r=>{let l=w(r,"show");a.has(l)||o("warn",`Unknown ${f("show")}="${l}".`,r)}),u(e,c("bind")).forEach(r=>{let l=w(r,"bind")||"";if(!l.startsWith("metafield:"))return;let p=l.slice(10);(i.metafields||[]).some(y=>(typeof y=="string"?y:`${y.namespace}.${y.key}`)===p)||o("error",`${f("bind")}="${l}" but "${p}" is not in config.metafields, so it stays empty.`,r)}),u(e,c("cart-open")).length&&!u(e,c("cart-drawer")).length&&o("warn",`${f("cart-open")} exists but there is no ${f("cart-drawer")} on this page.`),u(e,c("cart-drawer")).length>1&&o("warn",`More than one ${f("cart-drawer")}.`),u(e,`[${f("state")}="error"]`).forEach(r=>{r.matches(A())&&o("error",`Scope failed to load (${w(r,"error")||"error"}).`,r)}),t}async function ee({root:e=document,silent:t=!1,version:o,now:s=new Date}={}){let n={version:o,config:{domain:i.domain||"(missing)",token:X(i.token),apiVersion:i.apiVersion,endpoint:i.domain?x():null,country:i.country,language:i.language,locale:i.locale,markets:i.markets||null,prefix:i.prefix,productPath:i.productPath,metafields:i.metafields,autoMount:i.autoMount,observe:i.observe,onError:typeof i.onError=="function"},apiVersion:S(i.apiVersion,s),market:{country:i.country||null,language:i.language||null,locale:i.locale||null,detected:i.markets?.detect?L(i.markets):null},reachable:!1,shop:null,latencyMs:null,products:[],sellingPlans:[],problems:[]};if(!T())n.problems.push({level:"error",message:"TETHER_CONFIG needs domain and token."});else{let d=Date.now();try{let a=await $("query TetherDiagnose { shop { name primaryDomain { url } paymentSettings { currencyCode } } }",{},{context:!1});n.reachable=!0,n.shop={name:a.shop?.name,url:a.shop?.primaryDomain?.url,currency:a.shop?.paymentSettings?.currencyCode}}catch(a){let r=a.status===401||a.status===403?"Token rejected: use the public Storefront token from the Headless channel, not an Admin token.":a.status===404?"Store or API version not found: check domain (x.myshopify.com) and apiVersion.":"";n.problems.push({level:"error",message:`Storefront API not reachable: ${a.message}${r?`. ${r}`:""}`})}if(n.latencyMs=Date.now()-d,n.reachable){let a=[...new Set(u(e,c("product")).map(l=>b(l).key).filter(Boolean))].slice(0,20);(await Promise.allSettled(a.map(l=>v(l)))).forEach((l,p)=>{if(l.status==="fulfilled"){let m=l.value,y=_(m);n.products.push({key:a[p],handle:m.handle,title:m.title,plans:y.length,requiresSellingPlan:m.requiresSellingPlan}),y.forEach(h=>n.sellingPlans.push({product:m.handle,group:h.group?.name,plan:h.name,id:h.id})),u(e,c("product")).filter(h=>b(h).key===a[p]&&h.querySelector(c("add-to-cart"))).forEach(h=>{let P=new Set(u(h,`${c("option-list")},select${c("option")}`).map(g=>w(g,"option-list")||w(g,"option")));!P.size&&u(h,`select${c("variant-select")}`).length||(m.options||[]).filter(g=>g.optionValues.length>1&&!P.has(g.name)).forEach(g=>{n.problems.push({level:"warn",message:`Product "${m.handle}": option "${g.name}" (${g.optionValues.length} values) has no tether-option-list, so visitors can't choose it.`})})})}else n.products.push({key:a[p],error:l.reason?.message}),n.problems.push({level:"error",message:`Product "${a[p]}": ${l.reason?.message}. Is it published to the Headless channel?`})})}}return(!n.apiVersion.ok||(n.apiVersion.monthsLeft??99)<3)&&n.problems.push({level:n.apiVersion.ok?"warn":"error",message:`apiVersion ${i.apiVersion}: ${n.apiVersion.note}`}),typeof document<"u"&&n.problems.push(...D(e)),t||te(n),n}function te(e){let t=console,o=e.problems.filter(s=>s.level==="error").length;t.group?.(`[${i.prefix}] diagnose: ${o?`${o} error(s)`:"no errors"}, ${e.problems.length-o} warning(s)`),t.log("version",e.version,"| API",e.apiVersion.version,`(${e.apiVersion.note})`),t.log("config",e.config),e.market&&t.log("market detected",e.market,"(in use:",e.config.country,e.config.language,")"),t.log(e.reachable?`Storefront API reachable: ${e.shop?.name} (${e.latencyMs} ms)`:"Storefront API NOT reachable"),e.products.length&&(t.table||t.log)(e.products),e.sellingPlans.length?(t.table||t.log)(e.sellingPlans):e.reachable&&t.log("No selling plans on the products of this page."),e.problems.forEach(s=>(s.level==="error"?t.error:t.warn)(s.message,s.el||"")),t.groupEnd?.()}window.Tether.__parts.diagnose=E;})();
|
|
1
|
+
(()=>{var j=Object.defineProperty;var R=(e,t)=>{for(var o in t)j(e,o,{get:t[o],enumerable:!0})};var P={};R(P,{apiVersionStatus:()=>E,checkMarkup:()=>O,diagnose:()=>se,didYouMean:()=>w});var B=window.Tether.__core["config.js"],{DEFAULTS:ce,cfg:l,configure:ue,isConfigured:T,log:de,reportError:pe,warn:he}=B;var z=window.Tether.__core["client.js"],{TetherError:me,endpoint:C,gql:v,request:fe,unwrap:ye,withContext:be}=z;var G=window.Tether.__core["dom.js"],{attr:g,busy:$e,cdnUrl:ke,dataKey:Se,emit:ve,formatDate:Ee,formatMoney:xe,formatPercent:Pe,get:f,has:Te,hasTemplate:_,imageUrl:Ce,notifyLayoutChanged:_e,parseJSONAttr:Ae,productUrl:qe,qsa:d,readText:Ue,repeat:Me,repeatKeyed:De,sameImage:Ne,scoped:Ve,sel:c,setImage:Ie,setSelected:Le,setText:Oe,setTextTracked:je,show:Re,toAttributes:Be,watchImages:ze}=G;var Y=window.Tether.__core["loader.js"],{clearProductCache:Ye,getProduct:A,getProducts:He,getRecommendations:Ke,hydrateProducts:We,normalizeProductKey:Fe,primeProduct:Je,refreshProducts:Ze,takeStale:Xe}=Y;var H=window.Tether.__core["selling-plans.js"],{GID:et,allPlans:q,allocationFor:tt,findPlan:ot,hasPlans:nt,planAdjustment:rt,planCheckoutCharge:at,planPrice:st,planSavings:it,plansForVariant:lt,toGid:ct}=H;var K=window.Tether.__core["status.js"],{STATUS_KEYS:dt,boundary:U,isNotFound:pt,ownedBy:ht,resolveProductKey:$,setScopeStatus:gt}=K;var D=e=>{let t=/^(\d{4})-(01|04|07|10)$/.exec(String(e||""));return t?{y:+t[1],m:+t[2]}:null},W=(e,t)=>`${e}-${String(t).padStart(2,"0")}`;function M(e,t=1){let o=D(e);if(!o)throw new Error(`not a quarterly Shopify API version: ${e}`);let a=o.y*4+(o.m-1)/3+t;return W(Math.floor(a/4),a%4*3+1)}function E(e,t=new Date){if(e==="unstable")return{version:e,ok:!1,note:'"unstable" changes without notice: pin a dated version such as 2026-07'};let o=D(e);if(!o)return{version:e,ok:!1,note:"not a quarterly version (YYYY-01, -04, -07 or -10)"};let a=new Date(Date.UTC(o.y,o.m-1,1)),n=new Date(Date.UTC(o.y+1,o.m-1,1)),p=(n-t)/(30.44*24*3600*1e3),i=`supported until about ${n.toISOString().slice(0,7)}`;return a>t?i="not released yet (release candidate at best)":p<=0?i="no longer supported: Shopify falls back to the oldest supported version. Upgrade (see README)":p<3&&(i=`${i}: plan the upgrade to ${M(e)} or later (see README)`),{version:e,ok:a<=t&&p>0,monthsLeft:Math.round(p),next:M(e),note:i}}var F=/^[A-Z]{2}$/,J=new Set(["PT_BR","PT_PT","ZH_CN","ZH_TW"]),Z=()=>`${l.prefix}:market:${l.domain}`,X=()=>({document:typeof document<"u"?document:null,location:typeof window<"u"?window.location:null,storage:(()=>{try{return window.localStorage}catch{return null}})(),global:typeof window<"u"?window:{}});function Q(e){let t=String(e||"").trim().toUpperCase();return F.test(t)&&t!=="XX"&&t!=="T1"?t:null}function ee(e){let t=String(e||"").trim().replace("-","_").toUpperCase();if(!t)return null;if(J.has(t))return t;let o=t.split("_")[0];return/^[A-Z]{2,3}$/.test(o)?o:null}function te(e,t){if(!e||!t)return null;let o=String(e.cookie||"").split(";").map(a=>a.trim()).find(a=>a.startsWith(`${t}=`));return o?decodeURIComponent(o.slice(t.length+1)):null}var x=(e,t)=>{if(!e||!t)return null;let o=String(t).toLowerCase(),a=Object.keys(e).find(n=>n.toLowerCase()===o);return a?e[a]:null},oe={saved(e,t){if(e.remember===!1||!t.storage)return null;try{return JSON.parse(t.storage.getItem(Z())||"null")}catch{return null}},server(e,t){let o=p=>t.document?.querySelector?.(`meta[name="${p}"]`)?.getAttribute("content"),a=t.global.TETHER_COUNTRY||o("tether-country")||te(t.document,e.cookie||"tether_country"),n=t.global.TETHER_LANGUAGE||o("tether-language");return{country:a,language:n}},webflow(e,t){let o=(t.location?.pathname||"/").split("/").filter(Boolean)[0],a=x(e.locales,o);if(a)return a;let n=t.document?.documentElement?.getAttribute("lang");if(!n)return null;let p=x(e.locales,n)||x(e.locales,n.split("-")[0]);if(p)return{locale:n,...p};if(e.locales&&e.strict)return null;let i=n.split("-")[1];return{language:n,country:i&&i.length===2?i:null,locale:n}}};function N(e=l.markets,t=X()){let o=e||{};if(o.detect===!1)return{};let a=o.detect?o.detect===!0?["saved","server","webflow"]:[].concat(o.detect):["saved"];e=o;let n={sources:{}};return a.forEach(p=>{let i=oe[p]?.(e,t);if(!i)return;let b={country:Q(i.country),language:ee(i.language),locale:i.locale||null};Object.entries(b).forEach(([r,s])=>{s&&!n[r]&&(n[r]=s,n.sources[r]=p)})}),n}var V=new Set(["in-stock","out-of-stock","backorder","low-stock","plenty-in-stock","on-sale","not-on-sale","has-variants","single-variant","subscription-available","subscription-unavailable","subscription-only","one-time-available","subscription-selected","one-time-selected","has-savings","loading","ready","error","not-found","cart-empty","cart-filled","cart-has-subscription","cart-no-subscription","cart-has-discount","cart-error","cart-warning","list-empty","list-filled","search-empty","search-results","logged-in","logged-out","orders-empty","orders-loading"]),I=new Set(["title","description","description-text","vendor","type","handle","published-at","price","total-price","compare-at-price","unit-price","min-price","max-price","discount-percentage","discount-amount","variant-title","variant-id","sku","barcode","stock","quantity","featured-image","variant-image","images","tags","link","url","one-time-price","subscription-price","subscription-savings","subscription-savings-amount","checkout-charge","plan-name","plan-description","plan-group","option-value","swatch","image"]),L=new Set(`account account-bind account-link account-login account-logout add-label add-to-cart adding-text
|
|
2
|
+
always-enabled bind cart-checkout-charge cart-clear cart-close cart-count cart-discounts cart-drawer cart-label cart-lines
|
|
3
|
+
cart-note cart-open cart-subtotal cart-total cart-warning checkout code collection collection-bind countries country
|
|
4
|
+
country-bind country-label country-select default-plan default-purchase discount discount-code discount-form discount-input
|
|
5
|
+
discount-remove display error group group-add hide-single language limit line line-attribute line-attributes line-id
|
|
6
|
+
line-minus line-plan line-plus line-remove line-show list load-more market-pending one-time-label option option-list
|
|
7
|
+
option-selected option-value order-bind order-line order-lines orders page-country page-language page-locale page-title plan plan-bind plan-list
|
|
8
|
+
plan-value product products purchase purchase-mode qty qty-minus qty-plus quantity recommendations reverse search
|
|
9
|
+
search-bind search-collections search-input search-limit search-min search-products search-queries search-results
|
|
10
|
+
search-state show soldout-text sort state subscribe-text variant variant-select variant-url max-width max-height crop
|
|
11
|
+
sig label hydrated busy`.split(/\s+/).filter(Boolean));function ne(e,t){let o=Array.from({length:e.length+1},(a,n)=>[n,...Array(t.length).fill(0)]);for(let a=1;a<=t.length;a++)o[0][a]=a;for(let a=1;a<=e.length;a++)for(let n=1;n<=t.length;n++)o[a][n]=Math.min(o[a-1][n]+1,o[a][n-1]+1,o[a-1][n-1]+(e[a-1]===t[n-1]?0:1));return o[e.length][t.length]}function w(e,t,o=""){let a=null,n=1/0;for(let p of t){let i=ne(String(e),p);i<n&&(n=i,a=p)}return a&&n<=Math.max(2,Math.floor(String(e).length/4))?` Did you mean "${o}${a}"?`:""}var re=e=>e?`${String(e).slice(0,4)}\u2026${String(e).slice(-4)} (${String(e).length} chars)`:"(missing)";function ae(e){let t=e.id?`#${e.id}`:"",o=e.className&&typeof e.className=="string"?`.${e.className.trim().split(/\s+/).slice(0,2).join(".")}`:"";return`<${e.tagName.toLowerCase()}${t}${o}>`}function O(e=document){let t=[],o=(r,s,u)=>t.push({level:r,message:s,element:u?ae(u):void 0,el:u}),a=r=>!!r.closest(c("product")),n=`${c("collection")},${c("products")},${c("recommendations")}`;d(e,c("product")).forEach(r=>{if(r.closest(n)&&r.closest(c("list")))return;let{key:s,fromUrl:u,raw:y}=$(r);y?u&&!s&&o("warn",`${g("product")}="${y}" but this URL has no value for it (e.g. ?handle=my-product).`,r):o("error",`${g("product")} has no value. Bind it to the Shopify handle (CMS field), or use "url:handle".`,r)}),["bind","option-list","option","variant-select","add-to-cart","purchase","plan-list","plan","qty","line-attribute"].forEach(r=>{d(e,c(r)).forEach(s=>{a(s)||r==="line-attribute"&&s.closest(c("group"))||o("warn",`${g(r)} outside any ${g("product")}: it does nothing here.`,s)})}),d(e,c("group-add")).forEach(r=>{r.closest(c("group"))||o("warn",`${g("group-add")} outside a ${g("group")}.`,r)}),["option-list","plan-list","cart-lines","list"].forEach(r=>d(e,c(r)).forEach(s=>{!s.firstElementChild&&!_(s)&&o("warn",`${g(r)} without a template child: put one example element inside it.`,s)})),d(e,n).forEach(r=>{d(r,c("list")).length||o("error",`List without a ${g("list")} child: products have nowhere to render.`,r)}),d(e,c("show")).forEach(r=>{let s=f(r,"show");V.has(s)||o("warn",`Unknown ${g("show")}="${s}".${w(s,V)}`,r)}),d(e,c("bind")).forEach(r=>{let s=f(r,"bind")||"";!s||I.has(s)||/^image:\d+$/.test(s)||s.startsWith("metafield:")||o("warn",`Unknown ${g("bind")}="${s}": it stays empty.${w(s,I)}`,r)});let i=`${l.prefix}-`,b=new Set;return d(e,"*").forEach(r=>{for(let s of r.attributes){if(!s.name.startsWith(i))continue;let u=s.name.slice(i.length);L.has(u)||b.has(u)||(b.add(u),o("warn",`Unknown attribute ${s.name}: Tether ignores it.${w(u,L,i)}`,r))}}),d(e,c("bind")).forEach(r=>{let s=f(r,"bind")||"";if(!s.startsWith("metafield:"))return;let u=s.slice(10);(l.metafields||[]).some(m=>(typeof m=="string"?m:`${m.namespace}.${m.key}`)===u)||o("error",`${g("bind")}="${s}" but "${u}" is not in config.metafields, so it stays empty.`,r)}),d(e,c("cart-open")).length&&!d(e,c("cart-drawer")).length&&o("warn",`${g("cart-open")} exists but there is no ${g("cart-drawer")} on this page.`),d(e,c("cart-drawer")).length>1&&o("warn",`More than one ${g("cart-drawer")}.`),d(e,`[${g("state")}="error"]`).forEach(r=>{r.matches(U())&&o("error",`Scope failed to load (${f(r,"error")||"error"}).`,r)}),t}async function se({root:e=document,silent:t=!1,version:o,now:a=new Date}={}){let n={version:o,config:{domain:l.domain||"(missing)",token:re(l.token),apiVersion:l.apiVersion,endpoint:l.domain?C():null,country:l.country,language:l.language,locale:l.locale,markets:l.markets||null,prefix:l.prefix,productPath:l.productPath,metafields:l.metafields,autoMount:l.autoMount,observe:l.observe,onError:typeof l.onError=="function"},apiVersion:E(l.apiVersion,a),market:{country:l.country||null,language:l.language||null,locale:l.locale||null,detected:l.markets?.detect?N(l.markets):null},reachable:!1,shop:null,latencyMs:null,products:[],sellingPlans:[],problems:[]};if(!T())n.problems.push({level:"error",message:"TETHER_CONFIG needs domain and token."});else{let p=Date.now();try{let i=await v("query TetherDiagnose { shop { name primaryDomain { url } paymentSettings { currencyCode } } }",{},{context:!1});n.reachable=!0,n.shop={name:i.shop?.name,url:i.shop?.primaryDomain?.url,currency:i.shop?.paymentSettings?.currencyCode}}catch(i){n.problems.push({level:"error",message:`Storefront API not reachable: ${i.message||i.code||"unknown error"}`})}if(n.latencyMs=Date.now()-p,n.reachable){let i=[...new Set(d(e,c("product")).map(r=>$(r).key).filter(Boolean))].slice(0,20);(await Promise.allSettled(i.map(r=>A(r)))).forEach((r,s)=>{if(r.status==="fulfilled"){let u=r.value,y=q(u);n.products.push({key:i[s],handle:u.handle,title:u.title,plans:y.length,requiresSellingPlan:u.requiresSellingPlan}),y.forEach(m=>n.sellingPlans.push({product:u.handle,group:m.group?.name,plan:m.name,id:m.id})),d(e,c("product")).filter(m=>$(m).key===i[s]&&m.querySelector(c("add-to-cart"))).forEach(m=>{let k=new Set(d(m,`${c("option-list")},select${c("option")}`).map(h=>f(h,"option-list")||f(h,"option")));if(!k.size&&d(m,`select${c("variant-select")}`).length)return;let S=(u.options||[]).map(h=>h.name);l.language||[...k].filter(h=>h&&!S.includes(h)).forEach(h=>{n.problems.push({level:"warn",message:`Product "${u.handle}" has no option "${h}" (its options: ${S.join(", ")||"none"}).${w(h,S)}`})}),(u.options||[]).filter(h=>h.optionValues.length>1&&!k.has(h.name)).forEach(h=>{n.problems.push({level:"warn",message:`Product "${u.handle}": option "${h.name}" (${h.optionValues.length} values) has no tether-option-list, so visitors can't choose it.`})})})}else n.products.push({key:i[s],error:r.reason?.message}),n.problems.push({level:"error",message:`Product "${i[s]}": ${r.reason?.message||r.reason?.code||"not loaded"}. Is it published to the Headless channel?`})})}}return(!n.apiVersion.ok||(n.apiVersion.monthsLeft??99)<3)&&n.problems.push({level:n.apiVersion.ok?"warn":"error",message:`apiVersion ${l.apiVersion}: ${n.apiVersion.note}`}),typeof document<"u"&&n.problems.push(...O(e)),t||ie(n),n}function ie(e){let t=console,o=e.problems.filter(a=>a.level==="error").length;t.group?.(`[${l.prefix}] diagnose: ${o?`${o} error(s)`:"no errors"}, ${e.problems.length-o} warning(s)`),t.log("version",e.version,"| API",e.apiVersion.version,`(${e.apiVersion.note})`),t.log("config",e.config),e.market&&t.log("market",`in use: ${[e.market.country,e.market.language].filter(Boolean).join(" / ")||"the store default"}`,e.market.detected?`(detection picks ${JSON.stringify(e.market.detected)})`:""),t.log(e.reachable?`Storefront API reachable: ${e.shop?.name} (${e.latencyMs} ms)`:"Storefront API NOT reachable"),e.products.length&&(t.table||t.log)(e.products),e.sellingPlans.length?(t.table||t.log)(e.sellingPlans):e.reachable&&t.log("No selling plans on the products of this page."),e.problems.forEach(a=>(a.level==="error"?t.error:t.warn)(a.message,a.el||"")),t.groupEnd?.()}window.Tether.__parts.diagnose=P;})();
|
package/dist/tether.js
CHANGED
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
console.warn(`[${cfg.prefix}]`, ...a);
|
|
113
113
|
}
|
|
114
114
|
function reportError(err, context) {
|
|
115
|
-
warn(context, err && err.message ? err.message : err);
|
|
115
|
+
warn(context, err && (err.message || err.code) ? err.message || err.code : err);
|
|
116
116
|
if (typeof cfg.onError === "function") {
|
|
117
117
|
try {
|
|
118
118
|
cfg.onError(err, context);
|
|
@@ -154,6 +154,12 @@
|
|
|
154
154
|
return query.replace(/^(\s*(?:query|mutation)(?:\s+\w+)?\s*(?:\([^)]*\))?)/, `$1 ${tag}`);
|
|
155
155
|
}
|
|
156
156
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
157
|
+
function statusText(status, conf) {
|
|
158
|
+
if (status === 401 || status === 403) {
|
|
159
|
+
return `Shopify rejected the Storefront token for ${conf.domain} (${status}): use the public Storefront API token (Headless channel)${/^shpat_/.test(conf.token) ? ", not an Admin API token (shpat_\u2026)" : ""}`;
|
|
160
|
+
}
|
|
161
|
+
return status === 404 ? `Store or API version not found (404): check domain ${conf.domain} and apiVersion ${conf.apiVersion}` : `HTTP ${status} from ${conf.domain}`;
|
|
162
|
+
}
|
|
157
163
|
async function request(conf, query, variables = {}, { context = true } = {}) {
|
|
158
164
|
if (!conf.domain || !conf.token) {
|
|
159
165
|
throw new TetherError("Tether is not configured: domain and token are required", { code: "NOT_CONFIGURED" });
|
|
@@ -192,7 +198,7 @@
|
|
|
192
198
|
try {
|
|
193
199
|
json = await res.json();
|
|
194
200
|
} catch {
|
|
195
|
-
throw new TetherError(`Invalid response (${res.status})
|
|
201
|
+
throw new TetherError(res.ok ? `Invalid response (${res.status})` : statusText(res.status, conf), { status: res.status });
|
|
196
202
|
}
|
|
197
203
|
if (json.errors?.length) {
|
|
198
204
|
const inventoryDenied = json.errors.some((e) => /quantityAvailable/.test(e.message || ""));
|
|
@@ -208,12 +214,13 @@
|
|
|
208
214
|
await sleep(500 * 2 ** attempt);
|
|
209
215
|
continue;
|
|
210
216
|
}
|
|
211
|
-
|
|
217
|
+
const message = json.errors.map((e) => e.message).filter(Boolean).join(" | ");
|
|
218
|
+
throw new TetherError(res.ok ? message || "Storefront API error" : res.status === 401 || res.status === 403 || !message ? statusText(res.status, conf) : message, {
|
|
212
219
|
status: res.status,
|
|
213
220
|
errors: json.errors
|
|
214
221
|
});
|
|
215
222
|
}
|
|
216
|
-
if (!res.ok) throw new TetherError(
|
|
223
|
+
if (!res.ok) throw new TetherError(statusText(res.status, conf), { status: res.status });
|
|
217
224
|
return json.data;
|
|
218
225
|
}
|
|
219
226
|
}
|
|
@@ -631,7 +638,7 @@
|
|
|
631
638
|
}
|
|
632
639
|
function cdnUrl(url, el) {
|
|
633
640
|
if (!url || !el) return url;
|
|
634
|
-
const g = (k) => el.getAttribute(k);
|
|
641
|
+
const g = (k) => el.getAttribute(k) ?? el.getAttribute(`${cfg.prefix}-${k}`) ?? el.getAttribute(`data-${k}`);
|
|
635
642
|
return imageUrl(url, { width: g("max-width"), height: g("max-height"), crop: g("max-width") || g("max-height") ? g("crop") : null });
|
|
636
643
|
}
|
|
637
644
|
var sameImage = (a, b) => Boolean(a && b) && a.split("?")[0] === b.split("?")[0];
|
|
@@ -1026,8 +1033,20 @@
|
|
|
1026
1033
|
this.pendingQty.set(lineId, job);
|
|
1027
1034
|
this.render();
|
|
1028
1035
|
emit("cart-updated", { cart: this.state, reason: "pending" });
|
|
1029
|
-
|
|
1030
|
-
|
|
1036
|
+
const latest = () => this.pendingQty.get(lineId) === job;
|
|
1037
|
+
return this._queue(async () => {
|
|
1038
|
+
if (!latest()) return this.state;
|
|
1039
|
+
const now = this.lines.find((l) => l.id === lineId);
|
|
1040
|
+
if (now && now.quantity === target) return this.state;
|
|
1041
|
+
const cart = target > 0 ? await this._mutate("cartLinesUpdate", "$lines: [CartLineUpdateInput!]!", "lines: $lines", { lines: [{ id: lineId, quantity: target }] }) : await this._mutate("cartLinesRemove", "$lineIds: [ID!]!", "lineIds: $lineIds", { lineIds: [lineId] });
|
|
1042
|
+
if (!latest()) {
|
|
1043
|
+
this.state = cart;
|
|
1044
|
+
if (cart?.id) this._setId(cart.id);
|
|
1045
|
+
return cart;
|
|
1046
|
+
}
|
|
1047
|
+
return this._commit(cart, target > 0 ? "update" : "remove");
|
|
1048
|
+
}).finally(() => {
|
|
1049
|
+
if (latest()) {
|
|
1031
1050
|
this.pendingQty.delete(lineId);
|
|
1032
1051
|
this.render();
|
|
1033
1052
|
emit("cart-updated", { cart: this.state, reason: "settled" });
|
|
@@ -1983,7 +2002,10 @@
|
|
|
1983
2002
|
reverse: get(r, "reverse") === "true"
|
|
1984
2003
|
};
|
|
1985
2004
|
if (has(r, "collection")) opts.collection = get(r, "collection");
|
|
1986
|
-
else
|
|
2005
|
+
else {
|
|
2006
|
+
const raw = get(r, "products") || "";
|
|
2007
|
+
opts.query = raw.startsWith("url:") ? new URL(window.location.href).searchParams.get(raw.slice(4)) || "" : raw;
|
|
2008
|
+
}
|
|
1987
2009
|
return opts;
|
|
1988
2010
|
}
|
|
1989
2011
|
async load(more = false) {
|
|
@@ -2233,7 +2255,7 @@
|
|
|
2233
2255
|
}
|
|
2234
2256
|
|
|
2235
2257
|
// src/index.js
|
|
2236
|
-
var VERSION = "0.6.
|
|
2258
|
+
var VERSION = "0.6.2";
|
|
2237
2259
|
var initPromise = null;
|
|
2238
2260
|
var observer = null;
|
|
2239
2261
|
function mount(root = document) {
|