@swishapp/browser
Version:
JS library to integrate Swish into a browser environment.
13 lines (12 loc) • 15.7 kB
JavaScript
var F=Object.defineProperty;var s=(i,t)=>F(i,"name",{value:t,configurable:!0});var P={"/cart/add":"cart-add","/cart/update":"cart-update","/cart/change":"cart-change","/cart/clear":"cart-clear"},g=class{constructor(){this.eventBus=new EventTarget;this.isInitialized=!1}static{s(this,"EventBus")}subscribe(t,e,o){return this.isInitialized||(this.patchFetch(),this.isInitialized=!0),Array.isArray(t)||(t=[t]),t.forEach(d=>{this.eventBus.addEventListener(d,e,o)}),()=>{t.forEach(d=>{this.eventBus.removeEventListener(d,e,o)})}}unsubscribe(t,e,o){this.eventBus.removeEventListener(t,e,o)}publish(t,e){this.eventBus.dispatchEvent(new CustomEvent(t,{detail:e}))}patchFetch(){if(!window.fetch||typeof window.fetch!="function")return;let t=window.fetch,e=this.getRequestUrl.bind(this),o=this.isPostRequest.bind(this),d=this.isCartRequest.bind(this),r=this.handleCartEvent.bind(this);window.fetch=function(...l){let n=t.apply(this,l),c=e(l[0]);return o(l[1])&&d(c)&&n.then(u=>u.clone().json().then(L=>r(c,L)).catch(L=>console.warn("Cart event handling error:",L))),n}}handleCartEvent(t,e){try{let o=this.getEventName(t);o&&this.publish(o,e)}catch(o){console.warn(o)}}isPostRequest(t){return t?.method?.toLocaleLowerCase()==="post"}isCartRequest(t){return Object.keys(P).some(e=>t.includes(e))}getEventName(t){for(let[e,o]of Object.entries(P))if(t.includes(e))return o;return null}getRequestUrl(t){return t instanceof URL?t.href:t instanceof Request?t.url:t}};var W=".swish-shop-bridge{position:absolute!important;top:0!important;left:0!important;padding:0!important;border:0!important;margin:0!important;pointer-events:none!important}.swish-shop-bridge input{appearance:none!important;border:none!important;width:1px!important;height:1px!important;padding:0!important;margin:0!important;background:rgba(0,0,0,0)!important}",z=`<form class=swish-shop-bridge data-login-with-shop-sign-in=true id=customer_login><input name=customer[email] id=customer_login_email> <input name=return_url type=hidden value=${window.location.pathname}></form>`,C=class extends HTMLElement{constructor(){super();this.swish=window.swish;this.emailInput=this.querySelector('form[data-login-with-shop-sign-in] input[type="email"],form[data-login-with-shop-sign-in] input[name="customer[email]"'),this.emailInput||(this.innerHTML=`
<style>${W}</style>
${z}
`,this.emailInput=this.querySelector("#customer_login_email")),this.shopModalObserver=new MutationObserver(e=>{e.forEach(o=>{o.attributeName==="style"&&document.documentElement.style.overflow==="hidden"&&this.dispatchEvent(new CustomEvent("shop-modal-open"))})}),this.shopModalObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["style"]})}static{s(this,"ShopBridge")}disconnectedCallback(){this.shopModalObserver?.disconnect()}async load(){try{(window.Shopify?.featureAssets?.["shop-js"]?.["init-customer-accounts"]?.[0]??"").includes("init-customer-accounts")&&(await Promise.all(window.Shopify?.featureAssets?.["shop-js"]?.["init-customer-accounts"]?.map(o=>import(`/cdn/shopifycloud/shop-js/${o}`))??[]),window.Shopify?.SignInWithShop?.initCustomerAccounts?.(!0,{fedCMEnabled:!0,windoidEnabled:!1}))}catch(e){console.warn("Failed to initialize Shop JS",e)}}update(e){if(e.email&&(this.emailInput.value=e.email,this.emailInput.dispatchEvent(new Event("input"))),e.returnTo){let o=this.querySelector("input[name='return_url']");o&&(o.value=e.returnTo)}}};customElements.define("swish-shop-bridge",C);var E,v,R,S,O=s(i=>{E=i},"setConfig"),h=new Map,N=s(async i=>{if(h.has(i))return h.get(i);let t=(async()=>{typeof i=="string"&&(i=await a(i)),!(!i||i.getAttribute("open")!=="true")&&i.setAttribute("open","false")})().finally(()=>{h.delete(i)});return K(),h.set(i,t),t},"hideModal"),p=s(async i=>{if(h.has(i))return h.get(i);let t=(async()=>{typeof i=="string"&&(i=await a(i)),!(!i||i.getAttribute("open")==="true")&&i.setAttribute("open","true")})().finally(()=>{h.delete(i)});return G(),h.set(i,t),t},"showModal"),x=s(async i=>{let t=await a("sign-in");t.setAttribute("return-to",i?.returnTo??window.location.pathname),await p(t)},"showSignIn"),D=s(async i=>{if(!i.itemId)throw new Error("An itemId is required to show the unsave alert");await m();let t=await a("unsave-alert",{listeners:{submit:s(async()=>{i.onSubmit?.()},"submit"),cancel:s(async()=>{i.onCancel?.()},"cancel")}});t.setAttribute("item-id",i.itemId),await p(t)},"showUnsaveAlert"),k=s(async i=>{if(!i.listId)throw new Error("A listId is required to show the delete list alert");await m();let t=await a("delete-list-alert",{listeners:{submit:s(async()=>{i.onSubmit?.()},"submit"),cancel:s(async()=>{i.onCancel?.()},"cancel")}});t.setAttribute("list-id",i.listId),await p(t)},"showDeleteListAlert"),B=s(async()=>{await m();let i=await a("drawer");await p(i)},"showDrawer"),U=s(async i=>{if(!i.listId)throw new Error("listId is required");await m();let t=await a("list-menu",{listeners:{close:s(e=>{"detail"in e&&e.detail?i.onClose?.(e.detail):(console.warn("List menu closed without detail",e),i.onClose?.({reason:"unknown"}))},"close")}});t.setAttribute("list-id",i.listId),await p(t)},"showListMenu"),q=s(async i=>{if(!i.itemId)throw new Error("itemId is required");await m();let t=await a("list-select",{listeners:{submit:s(async e=>{"detail"in e&&e.detail?i?.onSubmit?.(e.detail):console.warn("List select form submitted without detail",e)},"submit"),close:s(()=>{i.onClose?.()},"close")}});t.setAttribute("item-id",i.itemId),await p(t)},"showListSelect"),H=s(async i=>{await a("notifications",{onHydrated:s(t=>{t.current.pushNotification(i)},"onHydrated")})},"showNotification"),m=s(async()=>{await a("notifications",{onHydrated:s(i=>{i.current.clearAllNotifications()},"onHydrated")})},"hideAllNotifications"),$=s(async i=>{await m();let t=await a("variant-select",{listeners:{submit:s(async e=>{"detail"in e&&e.detail?i?.onSubmit?.(e.detail):console.warn("Variant select form submitted without detail",e)},"submit")}});if(i?.productId)t.setAttribute("product-id",i.productId);else if(i?.productHandle)t.setAttribute("product-handle",i.productHandle);else throw new Error("Either productId or productHandle must be provided");i?.variantId&&t.setAttribute("variant-id",i.variantId),i?.action&&t.setAttribute("action",i.action),t.setAttribute("display-type",i?.displayType??"rows"),await p(t)},"showVariantSelect"),j=s(async i=>{let t=await a("list-editor",{listeners:{submit:s(async e=>{"detail"in e&&e.detail?i?.onSubmit?.(e.detail):console.warn("List editor form submitted without detail",e)},"submit")}});i?.listId&&t.setAttribute("list-id",i.listId),await p(t)},"showListEditor"),_=s(async({onShopModalOpen:i})=>{let t=document.querySelector("swish-shop-bridge");if(t||(document.body.insertAdjacentHTML("beforeend","<swish-shop-bridge></swish-shop-bridge>"),t=document.querySelector("swish-shop-bridge")),!t)throw new Error("Failed to initialize Shop Bridge");return t.addEventListener("shop-modal-open",()=>i(),{once:!0}),await t.load(),t},"initShopBridge"),y=s(async()=>S||(S=E?.manifest?.()??Promise.resolve({scripts:{},prerender:{},css:{critical:""},stylesheets:{nonCritical:""},version:""}),S),"loadManifest"),M=new Map,a=s(async(i,t)=>{let e=await y(),o=e.prerender[i],d=`${E?.host??""}/assets/ui/${e.scripts[i]}`,r=t?.instance,l=t?.listeners,n=V(i,r)??await Y({name:i,template:o,refElement:document.body,position:"beforeend",instance:r});n.shadowRoot&&!n.hasAttribute("hydrated")?Promise.all([J(),import(d)]).then(([c,{hydrate:u}])=>{n.shadowRoot&&c&&(n.shadowRoot.adoptedStyleSheets=[...n.shadowRoot.adoptedStyleSheets,c]),u(n),n.setAttribute("hydrated",""),t?.onHydrated?.(n.getComponentRef())}):n.hasAttribute("hydrated")&&t?.onHydrated?.(n.getComponentRef());for(let{event:c,listener:u}of M.values())n.removeEventListener(c,u);for(let[c,u]of Object.entries(l??{}))M.set(`${i}-${r}-${c}`,{event:c,listener:u}),n.addEventListener(c,u);return n},"requireUiComponent");async function J(){return R||(R=new Promise((i,t)=>y().then(e=>e.stylesheets.nonCritical?fetch(`${E?.host??""}/assets/ui/${e.stylesheets.nonCritical}`):new Response("")).then(e=>e.text()).then(e=>{let o=new CSSStyleSheet;o.replaceSync(e),i(o)}).catch(t)),R)}s(J,"loadNonCriticalStylesheet");var Y=s(async({name:i,instance:t,template:e,position:o,refElement:d})=>{d.insertAdjacentHTML(o,e.replace(' shadowrootmode="open"',""));let r=V(i,t);if(!r)throw new Error(`Element ${i} not found in DOM`);if(!v){let l=await y();v=new CSSStyleSheet,v.replaceSync(l.css.critical)}return r.shadowRoot&&(r.shadowRoot.adoptedStyleSheets=[v]),r},"insertComponent"),V=s((i,t)=>{let e=`swish-ui[ref="${i}${t?`-${t}`:""}"]`;return document.querySelector(e)},"queryComponent"),w=0,f,b=null,G=s(()=>{document.documentElement.hasAttribute("locked")||(b&&clearTimeout(b),f||(f=new CSSStyleSheet,document.adoptedStyleSheets=[...document.adoptedStyleSheets,f]),w=window.scrollY,f.replaceSync(`
html[locked] body {
position: fixed;
top: -${w}px;
left: 0;
right: 0;
width: 100%;
}
`),document.documentElement.setAttribute("locked",""))},"lockScroll"),K=s(()=>{document.documentElement.hasAttribute("locked")&&(b=setTimeout(()=>{b=null,f&&f.replaceSync(""),w&&(window.scrollTo(0,w),w=0),document.documentElement.removeAttribute("locked")},200))},"unlockScroll"),T=class extends HTMLElement{constructor(){super();this.getComponentRef=s(()=>this.componentRef,"getComponentRef");this.setComponentRef=s(e=>{this.componentRef=e},"setComponentRef");this.shadowRoot||this.attachShadow({mode:"open"});let e=this.querySelector("template");e&&this.shadowRoot&&(this.shadowRoot.appendChild(e.content),e.remove())}static{s(this,"SwishUiElement")}};typeof requestIdleCallback=="function"?requestIdleCallback(()=>{y()}):setTimeout(()=>{y()},1e3);var I=class{constructor(t,e=0){this.worker=t;this.rpcTimeout=e;this.worker=t,this.rpcTimeout=e,this.worker.onmessage=o=>{this.#n(o.data)}}static{s(this,"WorkerRpc")}#e=new Map;#i=new Map;#o=0;call(t,...e){let o=this.#o++;return this.worker.postMessage({type:"RPC",transactionId:o,id:t,payload:e}),new Promise((d,r)=>{let l={id:o,resolve:d,reject:r};this.rpcTimeout>0&&(l.timeoutHandle=setTimeout(()=>this.#d(l),this.rpcTimeout)),this.#i.set(o,l)})}addMethod(t,e){if(this.#e.has(t))throw new Error(`rpc handler for ${t} already registered`);this.#e.set(t,e)}removeMethod(t){this.#e.delete(t)}#n(t){let e=t;switch(e.type){case"RPC":return this.#r(e);case"INTERNAL":return this.#a(e);default:this.#t(`invalid message type ${e.type}`)}}#t(t){console.error(t),this.worker.postMessage({type:"INTERNAL",id:"ERROR",payload:t})}async#r(t){let e=this.#e.get(t.id);if(!e)return this.#t(`invalid rpc ${t.id}`);try{let o=await e(...t.payload);this.worker.postMessage({type:"INTERNAL",id:"RESOLVE_TRANSACTION",transactionId:t.transactionId,payload:o})}catch(o){this.worker.postMessage({type:"INTERNAL",id:"REJECT_TRANSACTION",transactionId:t.transactionId,payload:o})}}#a(t){let e=typeof t.transactionId=="number"?this.#i.get(t.transactionId):void 0;switch(t.id){case"RESOLVE_TRANSACTION":if(!e)return this.#t(`no pending transaction with id ${t.transactionId}`);e.resolve(t.payload),this.#s(e);break;case"REJECT_TRANSACTION":if(!e)return this.#t(`no pending transaction with id ${t.transactionId}`);e.reject(t.payload),this.#s(e);break;case"ERROR":console.error(`remote error: ${t.payload}`);break;default:this.#t(`unhandled internal message ${t.id}`);break}}#d(t){t.reject("transaction timed out"),this.#t(`transaction ${t.id} timed out`),this.#i.delete(t.id)}#s(t){typeof t.timeoutHandle<"u"&&clearTimeout(t.timeoutHandle),this.#i.delete(t.id)}};var Q="0.34.0",pt=s(async i=>{if(typeof window>"u"||!window.Worker)throw new Error("Swish is not supported in this environment");if(window.swish)return window.swish;try{let o=localStorage.getItem("wk_session_id");o&&localStorage.setItem("swish-profile",`gid://swish/Session/${o}`)}catch(o){console.warn("Failed to migrate legacy session id to Swish profile",{cause:o})}let t=i.proxy?.baseUrl??"/apps/wishlist",e=new A({loadProfile:s(()=>localStorage.getItem("swish-profile"),"loadProfile"),storeProfile:s(o=>localStorage.setItem("swish-profile",o),"storeProfile"),deleteProfile:s(()=>localStorage.removeItem("swish-profile"),"deleteProfile"),proxy:{baseUrl:t},ui:i.ui,storefront:i.storefront});return await e.init(),window.swish=e,document.dispatchEvent(new Event("swish-ready")),e},"swishApp"),A=class{constructor(t){this.api={items:{list:s(t=>this.rpc.call("api.items.list",t),"list"),create:s(t=>this.rpc.call("api.items.create",t),"create"),delete:s(t=>this.rpc.call("api.items.delete",t),"delete"),findById:s(t=>this.rpc.call("api.items.findById",t),"findById"),updateById:s((t,e)=>this.rpc.call("api.items.updateById",t,e),"updateById"),deleteById:s(t=>this.rpc.call("api.items.deleteById",t),"deleteById"),setListsById:s((t,e)=>this.rpc.call("api.items.setListsById",t,e),"setListsById")},lists:{list:s(()=>this.rpc.call("api.lists.list"),"list"),findById:s((t,e)=>this.rpc.call("api.lists.findById",t,e),"findById"),create:s(t=>this.rpc.call("api.lists.create",t),"create"),updateById:s((t,e)=>this.rpc.call("api.lists.updateById",t,e),"updateById"),deleteById:s(t=>this.rpc.call("api.lists.deleteById",t),"deleteById"),orderItems:s((t,e)=>this.rpc.call("api.lists.orderItems",t,e),"orderItems"),addItemsToList:s((t,e)=>this.rpc.call("api.lists.addItemsToList",t,e),"addItemsToList")},profiles:{customerAccountsVersion:s(()=>this.rpc.call("api.profiles.customerAccountsVersion"),"customerAccountsVersion"),identify:s(t=>this.rpc.call("api.profiles.identify",t),"identify")}};this.storefront={loadProductOptions:s(t=>this.rpc.call("storefront.loadProductOptions",t),"loadProductOptions"),loadSelectedVariant:s(t=>this.rpc.call("storefront.loadSelectedVariant",t),"loadSelectedVariant"),loadProductCardData:s(t=>this.rpc.call("storefront.loadProductCardData",t),"loadProductCardData"),loadProductDetailData:s(t=>this.rpc.call("storefront.loadProductDetailData",t),"loadProductDetailData"),loadProductImages:s(t=>this.rpc.call("storefront.loadProductImages",t),"loadProductImages")};this.ajax={fetchCart:s(()=>this.rpc.call("ajax.fetchCart"),"fetchCart"),addToCart:s(t=>this.rpc.call("ajax.addToCart",t),"addToCart")};this.ui={showSignIn:x,showVariantSelect:$,initShopBridge:_,showNotification:H,hideAllNotifications:m,showListSelect:q,showDrawer:B,showListEditor:j,showUnsaveAlert:D,hideModal:N,showModal:p,showListMenu:U,showDeleteListAlert:k};this.options=t,this.worker=new Worker(`${this.options.proxy.baseUrl}/assets/browser/worker-v${Q}.js`,{type:"module"}),this.events=new g,this.rpc=new I(this.worker,1e4),this.rpc.addMethod("storeProfile",this.storeProfile.bind(this)),this.rpc.addMethod("deleteProfile",this.deleteProfile.bind(this)),this.rpc.addMethod("events.publish",this.events.publish.bind(this.events))}static{s(this,"SwishApp")}async init(){return this.initPromise?this.initPromise:(this.initPromise=this._init(),this.initPromise)}async _init(){this.options.ui&&O(this.options.ui);let t=await this.options.loadProfile?.();t&&this.rpc.call("api.setProfile",t),this.rpc.call("api.setConfig",{baseUrl:`${this.options.proxy.baseUrl}/api`,...this.options.swishApi??{}}),this.rpc.call("storefront.setConfig",this.options.storefront),this.rpc.call("ajax.setConfig",{storeDomain:this.options.storefront.storeDomain}),this.events.subscribe(["cart-add","cart-update","cart-change","cart-clear"],()=>{this.rpc.call("cache.clearAjaxApi")}),customElements.define("swish-ui",T)}async storeProfile(t){await this.options.storeProfile?.(t)}async deleteProfile(){await this.options.deleteProfile?.()}};export{A as SwishApp,pt as swishApp};