@occluze/tether 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@occluze/tether",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Free, open-source alternative to Smootify and Storesynk (Shopyflow): Shopify products, cart and subscriptions in Webflow with HTML attributes. Optional Shopify to Webflow CMS sync.",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -43,7 +43,7 @@
43
43
  "presmoke": "node scripts/build.mjs",
44
44
  "test:types": "tsc -p types/tsconfig.json",
45
45
  "release": "node scripts/release.mjs",
46
- "e2e:build": "npm run build && cd examples/astro && (test -f .env || cp .env.example .env) && npm install --no-audit --no-fund --silent && npm install --no-save --silent ../.. && npx astro build",
46
+ "e2e:build": "npm run build && cd examples/astro && (test -f .env || cp .env.example .env) && npm install --no-audit --no-fund && npm install --no-save ../.. && npx astro build",
47
47
  "e2e": "npm run e2e:build && playwright test -c e2e/playwright.config.js"
48
48
  },
49
49
  "devDependencies": {
package/src/account.js CHANGED
@@ -81,8 +81,7 @@ export function isLoggedIn() { return server() ? Boolean(serverState?.loggedIn)
81
81
  export async function login({ returnTo = window.location.href, locale } = {}) {
82
82
  if (server()) {
83
83
  const u = new URL(`${server()}/login`);
84
- u.searchParams.set('returnTo', returnTo);
85
- if (Cart.id()) u.searchParams.set('cart', Cart.id());
84
+ u.searchParams.set('returnTo', returnTo); // the cart is linked later, through the Worker's protected POST /cart
86
85
  const lang = locale || cfg.account.locale || (cfg.locale || '').split('-')[0];
87
86
  if (lang) u.searchParams.set('locale', lang);
88
87
  if (cfg.country) u.searchParams.set('country', String(cfg.country).toUpperCase());
@@ -251,6 +250,7 @@ export async function logout({ returnTo = cfg.account?.logoutUrl || `${window.lo
251
250
 
252
251
  // ---------------------------------------------------------------- markup
253
252
 
253
+ const https = (u) => { try { return new URL(u).protocol === 'https:'; } catch { return false; } }; // links from Shopify: https only
254
254
  const date = (s) => { try { return new Date(s).toLocaleDateString(cfg.locale || undefined); } catch { return s; } };
255
255
 
256
256
  export async function render(root = document) {
@@ -275,11 +275,11 @@ export async function render(root = document) {
275
275
  lists.forEach((c) => repeat(c, list, (node, o) => {
276
276
  qsa(node, sel('order-bind')).forEach((el) => {
277
277
  const k = get(el, 'order-bind');
278
- if (k === 'url') { el.setAttribute('href', o.statusPageUrl); return; }
278
+ if (k === 'url') { if (https(o.statusPageUrl)) el.setAttribute('href', o.statusPageUrl); return; }
279
279
  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}× ${l.title}`).join(', ') }[k];
280
280
  if (v != null) setText(el, v);
281
281
  });
282
- if (get(node, 'order-bind') === 'url') node.setAttribute('href', o.statusPageUrl);
282
+ if (get(node, 'order-bind') === 'url' && https(o.statusPageUrl)) node.setAttribute('href', o.statusPageUrl);
283
283
  }));
284
284
  }
285
285
  }
package/src/index.js CHANGED
@@ -21,7 +21,7 @@ import { attr, sel, get, qsa, emit, formatMoney, busy, parseJSONAttr, toAttribut
21
21
  import { loadPart, coreModules } from './lazy.js';
22
22
  import { mountedLists, liveLists, pruneLive, within, mountProduct, retryFailed, hydrateFromPage, settleMarket, trackMarket } from './mounting.js';
23
23
 
24
- export const VERSION = '0.6.0';
24
+ export const VERSION = '0.6.1';
25
25
 
26
26
  let initPromise = null;
27
27
  let observer = null;
@@ -1 +0,0 @@
1
- import{b as u}from"./chunk-BGIKQY6H.js";import"./chunk-ZVUPVSLF.js";import"./chunk-YXCXIPQV.js";import{B as x,D as O,G as F,b as a,e as G,g,h as w,o as p,p as y,s as _,u as S,w as z}from"./chunk-KARKXC6G.js";import"./chunk-VC46IEJQ.js";var b=()=>`${a.prefix}:account:${a.domain}`,L=()=>`${a.prefix}:account-login:${a.domain}`,C=()=>a.account?.remember?window.localStorage:window.sessionStorage,M=t=>btoa(String.fromCharCode(...new Uint8Array(t))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""),v=()=>M(crypto.getRandomValues(new Uint8Array(32))),Q=async t=>M(await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t))),A=t=>(a.account?.navigate||(e=>window.location.assign(e)))(t),B=(t,e)=>{try{return JSON.parse(t.getItem(e)||"null")}catch{return null}},P=(t,e,n)=>{try{n?t.setItem(e,JSON.stringify(n)):t.removeItem(e)}catch{}},E=null,s=null,D=null,I=null,d=()=>a.account?.server?String(a.account.server).replace(/\/$/,""):"",q=()=>`${a.prefix}:account-hint:${a.domain}`,T=null;async function U(t,{method:e="GET",body:n}={}){let r=await fetch(`${d()}${t}`,{method:e,credentials:"include",headers:e==="POST"?{"Content-Type":"application/json","X-Tether-Account":"1"}:{},body:n?JSON.stringify(n):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 J(){T||(T=await U("/me"));try{T.loggedIn?window.localStorage.setItem(q(),"1"):window.localStorage.removeItem(q())}catch{}return T}async function $(){if(E)return E;let t=a.account?.shopDomain||a.domain,e=c=>fetch(`https://${t}/.well-known/${c}`).then(o=>{if(!o.ok)throw new w(`Customer accounts: ${c} answered ${o.status}`,{status:o.status});return o.json()}),[n,r]=await Promise.all([e("openid-configuration"),e("customer-account-api")]);return E={...n,graphql:r.graphql_api},E}var Z=()=>a.account?.callbackUrl||`${window.location.origin}${window.location.pathname}`;function H(){return d()?!!T?.loggedIn:!!(s?.refresh||s?.access&&s.expiresAt>Date.now())}async function tt({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 m=e||a.account.locale||(a.locale||"").split("-")[0];m&&f.searchParams.set("locale",m),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 n=await $(),r=v(),c=v(),o=v(),l=Z();P(window.sessionStorage,L(),{state:r,nonce:c,verifier:o,redirect:l,returnTo:t,at:Date.now()});let i=new URL(n.authorization_endpoint),h={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 Q(o),code_challenge_method:"S256"},k=e||a.account.locale||(a.locale||"").split("-")[0];k&&(h.locale=k),a.country&&(h.region_country=String(a.country).toUpperCase()),Object.entries(h).forEach(([f,m])=>i.searchParams.set(f,m)),A(i.toString())}async function V(t){let e=await $(),n=await fetch(a.account.tokenUrl||e.token_endpoint,{method:"POST",body:new URLSearchParams(t)}),r=await n.json().catch(()=>({}));if(!n.ok||!r.access_token)throw new w(`Customer accounts: token ${n.status} ${r.error||""}`.trim(),{status:n.status,code:n.status===400||n.status===401?"SESSION_ENDED":"TOKEN_FAILED"});return r}function et(t){try{return JSON.parse(atob(t.split(".")[1].replace(/-/g,"+").replace(/_/g,"/")))}catch{return null}}function W(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},P(C(),b(),s)}async function nt(){let t=new URLSearchParams(window.location.search),e=B(window.sessionStorage,L());if(!e||!t.has("code")&&!t.has("error")||t.get("state")!==e.state)return!1;P(window.sessionStorage,L(),null);let n=new URL(window.location.href);if(["code","state","iss","error","error_description"].forEach(h=>n.searchParams.delete(h)),window.history.replaceState(window.history.state,"",n),t.has("error"))return S("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 V({grant_type:"authorization_code",client_id:a.account.clientId,redirect_uri:e.redirect,code:t.get("code"),code_verifier:e.verifier}),c=await $(),o=et(r.id_token||""),l=[].concat(o?.aud||[]);if(!o||o.nonce!==e.nonce||o.iss!==c.issuer||!l.includes(a.account.clientId)||o.exp*1e3<Date.now())throw new w("Customer accounts: the login answer did not check out (nonce/issuer/audience)");W(r,null),await X(),S("account-login",{});let i=e.returnTo&&new URL(e.returnTo,window.location.href);return i&&i.origin===window.location.origin&&i.href!==n.href&&A(i.href),!0}async function N(){return d()||(s||(s=B(C(),b())),!s)?null:s.access&&s.expiresAt>Date.now()?s.access:s.refresh?(D||(D=(async()=>{try{W(await V({grant_type:"refresh_token",client_id:a.account.clientId,refresh_token:s.refresh})),await X()}catch(t){if(t.code==="SESSION_ENDED")R();else throw t}finally{D=null}})()),await D,s?.access||null):(R(),null)}function R(){s=null,I=null,P(C(),b(),null),u.customerAccessToken=null}async function X(){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 Y(t,e={}){if(d())throw new w("Customer accounts in server mode only answer customer() and orders()",{code:"NOT_ALLOWED"});let n=await N();if(!n)throw new w("Not logged in",{code:"NOT_LOGGED_IN"});let r=await $(),c=i=>fetch(r.graphql,{method:"POST",headers:{"Content-Type":"application/json",Authorization:i},body:JSON.stringify({query:t,variables:e})}),o=await c(n);o.status===401&&s?.refresh&&(s.expiresAt=0,o=await c(await N()));let l=await o.json().catch(()=>({}));if(!o.ok||l.errors?.length)throw new w(l.errors?.map(i=>i.message).join(" | ")||`Customer accounts: ${o.status}`,{status:o.status});return l.data}async function ot(){if(d()){let e=await J();return e.loggedIn?{...e.customer,emailAddress:{emailAddress:e.customer.email},defaultAddress:{formatted:e.customer.address}}:null}return I||(I=(await Y("query TetherCustomer{customer{id firstName lastName displayName emailAddress{emailAddress}defaultAddress{formatted}}}")).customer,I)}async function rt({first:t=10}={}){return d()?(await U(`/orders?first=${t}`)).orders||[]:(await Y("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 at({returnTo:t=a.account?.logoutUrl||`${window.location.origin}/`}={}){if(d()){let c=await U("/logout",{method:"POST",body:{returnTo:t,cartId:u.id()}});T={loggedIn:!1};try{window.localStorage.removeItem(q())}catch{}S("account-logout",{}),c.logoutUrl?A(c.logoutUrl):j(document);return}let e=s?.idToken||B(C(),b())?.idToken;if(u.id()&&await u.setBuyerIdentity({customerAccessToken:null},{create:!1}).catch(()=>{}),R(),S("account-logout",{}),!e){j(document);return}let n=await $(),r=new URL(n.end_session_endpoint);r.searchParams.set("id_token_hint",e),r.searchParams.set("post_logout_redirect_uri",t),A(r.toString())}var ct=t=>{try{return new Date(t).toLocaleDateString(a.locale||void 0)}catch{return t}};async function j(t=document){let e=d()?!!(await J().catch(()=>({loggedIn:!1}))).loggedIn:H()||!!await N().catch(()=>null);if(_(t,p("show")).forEach(c=>{let o=y(c,"show");o==="logged-in"&&O(c,e),o==="logged-out"&&O(c,!e)}),!e)return;let n=_(t,p("account-bind"));if(n.length){let c=await ot().catch(o=>(g(o,"account"),null));n.forEach(o=>{let l={"first-name":c?.firstName,"last-name":c?.lastName,name:c?.displayName,email:c?.emailAddress?.emailAddress,address:c?.defaultAddress?.formatted?.join(", ")}[y(o,"account-bind")];l!=null&&x(o,l)})}let r=_(t,p("orders"));if(r.length){let c=await rt({first:parseInt(y(r[0],"limit")||"10",10)}).catch(o=>(g(o,"orders"),[]));r.forEach(o=>F(o,c,(l,i)=>{_(l,p("order-bind")).forEach(h=>{let k=y(h,"order-bind");if(k==="url"){h.setAttribute("href",i.statusPageUrl);return}let f={name:i.name,date:ct(i.processedAt),total:z(i.totalPrice),status:i.financialStatus?.toLowerCase(),fulfillment:i.fulfillmentStatus?.toLowerCase(),items:i.lineItems.nodes.map(m=>`${m.quantity}\xD7 ${m.title}`).join(", ")}[k];f!=null&&x(h,f)}),y(l,"order-bind")==="url"&&l.setAttribute("href",i.statusPageUrl)}))}}var K=!1;async function dt(t=document){if(!K){if(K=!0,document.addEventListener("click",e=>{let n=e.target;n.closest?.(p("account-login"))?(e.preventDefault(),tt().catch(r=>g(r,"login"))):n.closest?.(p("account-logout"))&&(e.preventDefault(),at().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),S("account-login",{})),u.beforeCheckout=async()=>{if(!(await J()).loggedIn||!u.id())return null;let n=await U("/cart",{method:"POST",body:{cartId:u.id()}}).catch(()=>null);return n?.checkoutUrl?`${n.checkoutUrl}${n.checkoutUrl.includes("?")?"&":"?"}logged_in=true`:null}}else{try{await nt()}catch(e){g(e,"login")}!u.customerAccessToken&&await N().catch(()=>null)&&(u.customerAccessToken=s.access)}G("account",H()?"logged in":"logged out")}await j(t)}export{ot as customer,N as getToken,nt as handleCallback,H as isLoggedIn,tt as login,at as logout,dt as mountAccount,rt as orders,Y as query,j as render};