@aforoai/storefront-widgets 1.0.3 → 1.0.5

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/dist/loader.mjs CHANGED
@@ -1,3 +1,3 @@
1
- var T="1.0.3";var y=new WeakMap,c=new Map,f={},A=false;function h(){A||typeof window!="undefined"&&(window.addEventListener("message",e=>{let t=e.data;!t||typeof t!="object"||typeof t.type!="string"||t.type.startsWith("aforo.")&&$(t);}),A=true);}function $(e){let t=c.get(e.type);if(t)for(let o of t)try{o(e);}catch(i){}let n=c.get("*");if(n)for(let o of n)try{o(e);}catch(i){}}var p=new Map;function N(e,t){p.set(e,t);}function w(e){return typeof e=="object"&&e!==null&&typeof e.tagName=="string"}var b={version:T,configure(e){f={...f,...e};},getConfig(){return {...f}},mount(e,t){var r,l,u,s,d;if(!w(e)||y.has(e))return false;let n=t.widget,o=p.get(n);if(!o)return typeof console!="undefined"&&console.warn(`[aforo:embed] No mount handler registered for widget "${n}". Loader bundle may not have finished loading.`),false;let i={widget:n,tenantSlug:(l=(r=t.tenantSlug)!=null?r:f.tenantSlug)!=null?l:"",embedKey:(s=(u=t.embedKey)!=null?u:f.embedKey)!=null?s:"",bridgeToken:(d=t.bridgeToken)!=null?d:f.bridgeToken,layout:t.layout,themeOverrides:t.themeOverrides,config:t.config};if(!i.tenantSlug||!i.embedKey)return typeof console!="undefined"&&console.error(`[aforo:embed] Widget "${n}" requires tenantSlug + embedKey. Provide via AforoEmbed.configure() or data-* attributes.`),false;h();let a=o(e,i);return y.set(e,{element:e,widget:n,teardown:a}),true},unmount(e){if(!w(e))return false;let t=y.get(e);if(!t)return false;try{t.teardown();}catch(n){}return y.delete(e),true},on(e,t){h();let n=c.get(e);return n||(n=new Set,c.set(e,n)),n.add(t),()=>{let o=c.get(e);o&&(o.delete(t),o.size===0&&c.delete(e));}},_registerWidget:N,_registeredWidgetsForTesting(){return Array.from(p.keys())},_resetForTesting(){p.clear(),c.clear(),f={};}};function k(e){if(!e)return {};try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t)){let n={},o=["primary","primaryContrast","text","textMuted","bg","border","radius","fontFamily"],i=t;for(let a of o){let r=i[a];typeof r=="string"&&r.length>0&&(n[a]=r);}return n}}catch(t){}return {}}var U=new Set(["pricing-card","subscribe-button","checkout-flow","subscription-manager","invoice-list","usage-meter","payment-method","upgrade-cancel"]),B="https://embed.aforo.ai/v1/widgets",g="[aforo:loader]";function D(){let e=typeof document!="undefined"?document.currentScript:null;if(e&&e.tagName==="SCRIPT")return e;if(typeof document!="undefined"){let t=document.getElementsByTagName("script");for(let n=t.length-1;n>=0;n--){let o=t[n];if(!o)continue;let i=o.getAttribute("src")||"";if(/(\/v1)?\/loader(\.umd)?\.js$/.test(i))return o}}return null}function G(e){let t=e==null?void 0:e.getAttribute("src");if(!t)return null;try{let n=typeof location!="undefined"?location.href:void 0,o=new URL(t,n),i=o.pathname.replace(/\/loader(\.umd)?\.js$/,"");return `${o.origin}${i}/widgets`}catch(n){return null}}function v(e,t){let n=e.getAttribute(t);if(n==null||n.trim()==="")return;let o=Number(n);return Number.isFinite(o)?o:void 0}function j(e,t){let n=e.getAttribute(t);if(n==null)return;let o=n.trim().toLowerCase();if(o==="true")return true;if(o==="false")return false}function V(e){let t=(e.getAttribute("data-aforo-widget")||"").trim(),n=e.getAttribute("data-tenant-slug")||e.getAttribute("data-tenant")||"",o=e.getAttribute("data-embed-key")||"",i=e.getAttribute("data-bridge-token")||void 0,a=e.getAttribute("data-layout")||void 0,r=k(e.getAttribute("data-theme-overrides")),l=e.getAttribute("data-offering-id")||void 0,u=e.getAttribute("data-subscription-id")||void 0,s=e.getAttribute("data-metric")||void 0,d=e.getAttribute("data-featured-offering-id")||void 0,m=e.getAttribute("data-mode")||void 0,M=e.getAttribute("data-return-url")||void 0,S=e.getAttribute("data-default-status")||void 0,F=e.getAttribute("data-render-mode")||void 0,_=e.getAttribute("data-locale")||void 0,C=v(e,"data-page-size"),O=v(e,"data-poll-interval-ms"),I=e.getAttribute("data-theme")||void 0,R=e.getAttribute("data-cart-type")||void 0,W=e.getAttribute("data-target-id")||void 0,H=e.getAttribute("data-cta-text")||void 0,x=e.getAttribute("data-cta-url")||void 0,P=v(e,"data-max-plans"),K=j(e,"data-show-features");return {widget:t,tenantSlug:n,embedKey:o,bridgeToken:i,layout:a,themeOverrides:r,config:{offeringId:l,subscriptionId:u,metricName:s,featuredOfferingId:d,mode:m,returnUrl:M,defaultStatus:S,renderMode:F,locale:_,pageSize:C,pollIntervalMs:O,theme:I,cartType:R,targetId:W,ctaText:H,ctaUrl:x,maxPlans:P,showFeatures:K}}}function z(e,t,n){return new Promise((o,i)=>{if(typeof document=="undefined")return i(new Error("no document"));let a=document.querySelector(`script[data-aforo-bundle="${e}"]`);if(a){if(a.dataset.aforoLoaded==="1")return o();a.addEventListener("load",()=>o()),a.addEventListener("error",()=>i(new Error(`bundle load failed: ${e}`)));return}let r=document.createElement("script");r.src=`${t}/${e}.js`,r.async=true,r.crossOrigin="anonymous",r.dataset.aforoBundle=e,n&&r.setAttribute("nonce",n),r.addEventListener("load",()=>{r.dataset.aforoLoaded="1",o();}),r.addEventListener("error",()=>i(new Error(`bundle load failed: ${e}`))),document.head.appendChild(r);})}function L(e){if(!e.__aforoEmbedReadyFired){e.__aforoEmbedReadyFired=true;try{typeof e.aforoEmbedReady=="function"&&e.aforoEmbedReady();}catch(t){typeof console!="undefined"&&console.error(`${g} aforoEmbedReady callback threw`,t);}if(typeof document!="undefined"&&typeof CustomEvent!="undefined")try{document.dispatchEvent(new CustomEvent("aforoEmbedReady"));}catch(t){}}}async function E(e){if(typeof window=="undefined"||typeof document=="undefined")return;let t=window;if(window.location.protocol!=="https:"&&window.location.hostname!=="localhost"){typeof console!="undefined"&&console.error(`${g} Refusing to mount Aforo widgets on a non-HTTPS page. Embed Plugin requires HTTPS (FR-TIER-12). Current origin: ${window.location.origin}`);return}if(t.__aforoEmbedLoaderInitialized){typeof console!="undefined"&&console.warn(`${g} Loader already initialised on this page \u2014 skipping duplicate run.`);return}t.__aforoEmbedLoaderInitialized=true,t.aforoEmbed||(t.aforoEmbed=b);let n=D(),o=(n==null?void 0:n.getAttribute("nonce"))||null,i=(e==null?void 0:e.bundleBaseUrl)||G(n)||B,a=Array.from(document.querySelectorAll("[data-aforo-widget]"));if(a.length===0){L(t);return}let r=new Map;for(let u of a){let s=(u.getAttribute("data-aforo-widget")||"").trim();if(!U.has(s)){typeof console!="undefined"&&console.warn(`${g} Unknown widget id "${s}" \u2014 skipping`);continue}let d=r.get(s);d||(d=[],r.set(s,d)),d.push(u);}let l=Array.from(r.entries()).map(async([u,s])=>{try{await z(u,i,o);for(let d of s){let m=V(d);if(!m.widget||!m.tenantSlug||!m.embedKey){typeof console!="undefined"&&console.error(`${g} Placeholder missing required attributes (data-tenant-slug + data-embed-key). Skipping element.`,d);continue}b.mount(d,m);}}catch(d){typeof console!="undefined"&&console.error(`${g} Failed to load bundle for "${u}"`,d);}});await Promise.all(l).catch(()=>{}),L(t);}typeof document!="undefined"&&document.readyState!=="loading"?Promise.resolve().then(()=>{E();}):typeof document!="undefined"&&document.addEventListener("DOMContentLoaded",()=>{E();});b._loader={bootstrap:E};
1
+ var T="1.0.5";var y=new WeakMap,c=new Map,f={},A=false;function h(){A||typeof window!="undefined"&&(window.addEventListener("message",e=>{let t=e.data;!t||typeof t!="object"||typeof t.type!="string"||t.type.startsWith("aforo.")&&$(t);}),A=true);}function $(e){let t=c.get(e.type);if(t)for(let o of t)try{o(e);}catch(i){}let n=c.get("*");if(n)for(let o of n)try{o(e);}catch(i){}}var p=new Map;function N(e,t){p.set(e,t);}function w(e){return typeof e=="object"&&e!==null&&typeof e.tagName=="string"}var b={version:T,configure(e){f={...f,...e};},getConfig(){return {...f}},mount(e,t){var r,l,u,s,d;if(!w(e)||y.has(e))return false;let n=t.widget,o=p.get(n);if(!o)return typeof console!="undefined"&&console.warn(`[aforo:embed] No mount handler registered for widget "${n}". Loader bundle may not have finished loading.`),false;let i={widget:n,tenantSlug:(l=(r=t.tenantSlug)!=null?r:f.tenantSlug)!=null?l:"",embedKey:(s=(u=t.embedKey)!=null?u:f.embedKey)!=null?s:"",bridgeToken:(d=t.bridgeToken)!=null?d:f.bridgeToken,layout:t.layout,themeOverrides:t.themeOverrides,config:t.config};if(!i.tenantSlug||!i.embedKey)return typeof console!="undefined"&&console.error(`[aforo:embed] Widget "${n}" requires tenantSlug + embedKey. Provide via AforoEmbed.configure() or data-* attributes.`),false;h();let a=o(e,i);return y.set(e,{element:e,widget:n,teardown:a}),true},unmount(e){if(!w(e))return false;let t=y.get(e);if(!t)return false;try{t.teardown();}catch(n){}return y.delete(e),true},on(e,t){h();let n=c.get(e);return n||(n=new Set,c.set(e,n)),n.add(t),()=>{let o=c.get(e);o&&(o.delete(t),o.size===0&&c.delete(e));}},_registerWidget:N,_registeredWidgetsForTesting(){return Array.from(p.keys())},_resetForTesting(){p.clear(),c.clear(),f={};}};function k(e){if(!e)return {};try{let t=JSON.parse(e);if(t&&typeof t=="object"&&!Array.isArray(t)){let n={},o=["primary","primaryContrast","text","textMuted","bg","border","radius","fontFamily"],i=t;for(let a of o){let r=i[a];typeof r=="string"&&r.length>0&&(n[a]=r);}return n}}catch(t){}return {}}var U=new Set(["pricing-card","subscribe-button","checkout-flow","subscription-manager","invoice-list","usage-meter","payment-method","upgrade-cancel"]),B="https://embed.aforo.ai/v1/widgets",g="[aforo:loader]";function D(){let e=typeof document!="undefined"?document.currentScript:null;if(e&&e.tagName==="SCRIPT")return e;if(typeof document!="undefined"){let t=document.getElementsByTagName("script");for(let n=t.length-1;n>=0;n--){let o=t[n];if(!o)continue;let i=o.getAttribute("src")||"";if(/(\/v1)?\/loader(\.umd)?\.js$/.test(i))return o}}return null}function G(e){let t=e==null?void 0:e.getAttribute("src");if(!t)return null;try{let n=typeof location!="undefined"?location.href:void 0,o=new URL(t,n),i=o.pathname.replace(/\/loader(\.umd)?\.js$/,"");return `${o.origin}${i}/widgets`}catch(n){return null}}function v(e,t){let n=e.getAttribute(t);if(n==null||n.trim()==="")return;let o=Number(n);return Number.isFinite(o)?o:void 0}function j(e,t){let n=e.getAttribute(t);if(n==null)return;let o=n.trim().toLowerCase();if(o==="true")return true;if(o==="false")return false}function V(e){let t=(e.getAttribute("data-aforo-widget")||"").trim(),n=e.getAttribute("data-tenant-slug")||e.getAttribute("data-tenant")||"",o=e.getAttribute("data-embed-key")||"",i=e.getAttribute("data-bridge-token")||void 0,a=e.getAttribute("data-layout")||void 0,r=k(e.getAttribute("data-theme-overrides")),l=e.getAttribute("data-offering-id")||void 0,u=e.getAttribute("data-subscription-id")||void 0,s=e.getAttribute("data-metric")||void 0,d=e.getAttribute("data-featured-offering-id")||void 0,m=e.getAttribute("data-mode")||void 0,M=e.getAttribute("data-return-url")||void 0,S=e.getAttribute("data-default-status")||void 0,F=e.getAttribute("data-render-mode")||void 0,_=e.getAttribute("data-locale")||void 0,C=v(e,"data-page-size"),O=v(e,"data-poll-interval-ms"),I=e.getAttribute("data-theme")||void 0,R=e.getAttribute("data-cart-type")||void 0,W=e.getAttribute("data-target-id")||void 0,H=e.getAttribute("data-cta-text")||void 0,x=e.getAttribute("data-cta-url")||void 0,P=v(e,"data-max-plans"),K=j(e,"data-show-features");return {widget:t,tenantSlug:n,embedKey:o,bridgeToken:i,layout:a,themeOverrides:r,config:{offeringId:l,subscriptionId:u,metricName:s,featuredOfferingId:d,mode:m,returnUrl:M,defaultStatus:S,renderMode:F,locale:_,pageSize:C,pollIntervalMs:O,theme:I,cartType:R,targetId:W,ctaText:H,ctaUrl:x,maxPlans:P,showFeatures:K}}}function z(e,t,n){return new Promise((o,i)=>{if(typeof document=="undefined")return i(new Error("no document"));let a=document.querySelector(`script[data-aforo-bundle="${e}"]`);if(a){if(a.dataset.aforoLoaded==="1")return o();a.addEventListener("load",()=>o()),a.addEventListener("error",()=>i(new Error(`bundle load failed: ${e}`)));return}let r=document.createElement("script");r.src=`${t}/${e}.js`,r.async=true,r.crossOrigin="anonymous",r.dataset.aforoBundle=e,n&&r.setAttribute("nonce",n),r.addEventListener("load",()=>{r.dataset.aforoLoaded="1",o();}),r.addEventListener("error",()=>i(new Error(`bundle load failed: ${e}`))),document.head.appendChild(r);})}function L(e){if(!e.__aforoEmbedReadyFired){e.__aforoEmbedReadyFired=true;try{typeof e.aforoEmbedReady=="function"&&e.aforoEmbedReady();}catch(t){typeof console!="undefined"&&console.error(`${g} aforoEmbedReady callback threw`,t);}if(typeof document!="undefined"&&typeof CustomEvent!="undefined")try{document.dispatchEvent(new CustomEvent("aforoEmbedReady"));}catch(t){}}}async function E(e){if(typeof window=="undefined"||typeof document=="undefined")return;let t=window;if(window.location.protocol!=="https:"&&window.location.hostname!=="localhost"){typeof console!="undefined"&&console.error(`${g} Refusing to mount Aforo widgets on a non-HTTPS page. Embed Plugin requires HTTPS (FR-TIER-12). Current origin: ${window.location.origin}`);return}if(t.__aforoEmbedLoaderInitialized){typeof console!="undefined"&&console.warn(`${g} Loader already initialised on this page \u2014 skipping duplicate run.`);return}t.__aforoEmbedLoaderInitialized=true,t.aforoEmbed||(t.aforoEmbed=b);let n=D(),o=(n==null?void 0:n.getAttribute("nonce"))||null,i=(e==null?void 0:e.bundleBaseUrl)||G(n)||B,a=Array.from(document.querySelectorAll("[data-aforo-widget]"));if(a.length===0){L(t);return}let r=new Map;for(let u of a){let s=(u.getAttribute("data-aforo-widget")||"").trim();if(!U.has(s)){typeof console!="undefined"&&console.warn(`${g} Unknown widget id "${s}" \u2014 skipping`);continue}let d=r.get(s);d||(d=[],r.set(s,d)),d.push(u);}let l=Array.from(r.entries()).map(async([u,s])=>{try{await z(u,i,o);for(let d of s){let m=V(d);if(!m.widget||!m.tenantSlug||!m.embedKey){typeof console!="undefined"&&console.error(`${g} Placeholder missing required attributes (data-tenant-slug + data-embed-key). Skipping element.`,d);continue}b.mount(d,m);}}catch(d){typeof console!="undefined"&&console.error(`${g} Failed to load bundle for "${u}"`,d);}});await Promise.all(l).catch(()=>{}),L(t);}typeof document!="undefined"&&document.readyState!=="loading"?Promise.resolve().then(()=>{E();}):typeof document!="undefined"&&document.addEventListener("DOMContentLoaded",()=>{E();});b._loader={bootstrap:E};
2
2
  export{b as AforoEmbed,E as bootstrap};//# sourceMappingURL=loader.mjs.map
3
3
  //# sourceMappingURL=loader.mjs.map
package/dist/sri.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
- "version": "1.0.3",
3
- "generatedAt": "2026-08-06T10:04:55.060Z",
2
+ "version": "1.0.5",
3
+ "generatedAt": "2026-08-26T07:52:12.266Z",
4
4
  "bundles": {
5
- "loader.js": "sha384-y68Q4ZI8cjVROhc1u3KvVsUq1fWZLIMTyGZprznk6+dqlO9wKZXOQMJXnpkZNcTa",
6
- "widgets/checkout-flow.js": "sha384-TR8WKd8KcXp8X5Jn8URZRuBZVy8LWz8Yd34sqsE2i40VjgyxU1Z6qSlJIXjpCVWs",
7
- "widgets/invoice-list.js": "sha384-ccLrb3k9pXScTDTnUO8d1CXtrOO/63+KVCvlx9C7R8R8FukSDL+katg1PfYUoEru",
8
- "widgets/payment-method.js": "sha384-DjvIF+8Uj0Xf6D6LRiXWwhLNUsXv+NzhZNKchKwUWBhXLCzGkoUZMoHN18bs/9GZ",
9
- "widgets/pricing-card.js": "sha384-mWYYVjNqJ42wBqM3noL0DqT6QfZ6vUh3ZdBJrSTTVg+KbZg8VW+RMN3wqNBWwaQn",
10
- "widgets/subscribe-button.js": "sha384-nrPRcguVwH3jzMa2opsjHBx1lIn6Tr8jhbEgoBHG/notld10qgTUHHD8C4lb8iY+",
11
- "widgets/subscription-manager.js": "sha384-JFYT9O7rioOXEQBLQjikWwbD7l+cmtpx9oGM8kNaYWqZ1q6dpZPcPwwe0jFOAz6j",
12
- "widgets/upgrade-cancel.js": "sha384-fLGAjU4RoV3XsMVd/BCPcXJiJSZjT41ZZk2wIBfCrEJZvE/gflHsmgyXW02pD2b5",
13
- "widgets/usage-meter.js": "sha384-Mkfm+HYrr/ncJgAa3J/1kdVgDdf5JbOU+m8JAXQjfzqGq8epXLYLm+e6qPQf9LPf"
5
+ "loader.js": "sha384-dSFfjDiOhFwuhOq6QaJlGLJq05Fl3yau3wPc5kuYnO+OmN/MKp91+AwOpG0zFAJ7",
6
+ "widgets/checkout-flow.js": "sha384-ABsG5M7QXhYJUmB81gURADHt98yRZaJpMxw5qDXiUs7LHa34LGXgfGEzSZrMC2Lr",
7
+ "widgets/invoice-list.js": "sha384-Dk5I2atCFalMIClYl6NkhZo3rrQNsfxq64y+geG+Lm3vZ1rZSg3xeVfY4tVMl0mD",
8
+ "widgets/payment-method.js": "sha384-xkM3m6gLJBhs0aK/A+/tcEQIWjz5Fj/h5dl4X/aEXmu6Dc0fVfxIV2ZQ1NDKDfsa",
9
+ "widgets/pricing-card.js": "sha384-F0ZraASZJCoL/E3v2tCQAAilNm6BndFRYsH5CFAYlWizibZyrNCVddq/pZ4ZQeP6",
10
+ "widgets/subscribe-button.js": "sha384-85KTFUAYOsDkedU1SKInEn0V0iAMUX4hokvn7ve69Q2Ouh9dvJoPY9OEYuWsDvfF",
11
+ "widgets/subscription-manager.js": "sha384-3BNZFvMphX9jAsmzrE09tnPBIKbXsxLhVSv1KynXrJK217OmFekbdHLzkPtGphcw",
12
+ "widgets/upgrade-cancel.js": "sha384-ZvFGSCqU3+MoAeXeytEbYzpDZInRDP0YJ9AUl6yoMdwQ4p4W7stLLgdM3l3dNIxZ",
13
+ "widgets/usage-meter.js": "sha384-2g3qNlSZMkw6sTHKsXbsupaWQbdQzE+4U8jbFlRUaV0qpMKeptV4po98z0PpSM61"
14
14
  }
15
15
  }
@@ -27,7 +27,7 @@ var React7__namespace = /*#__PURE__*/_interopNamespace(React7);
27
27
  // src/vanilla/index.ts
28
28
 
29
29
  // src/core/version.ts
30
- var VERSION = "1.0.3";
30
+ var VERSION = "1.0.5";
31
31
 
32
32
  // src/core/AforoEmbed.ts
33
33
  var mountedElements = /* @__PURE__ */ new WeakMap();
@@ -547,7 +547,8 @@ var _BridgeClient = class _BridgeClient {
547
547
  throw await this.parseError(resp);
548
548
  }
549
549
  const body = await resp.json();
550
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
550
+ const data = body?.data ?? body;
551
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
551
552
  throw new BridgeClientError(
552
553
  "Malformed bridge exchange response",
553
554
  500,
@@ -556,9 +557,9 @@ var _BridgeClient = class _BridgeClient {
556
557
  );
557
558
  }
558
559
  return {
559
- sessionJwt: body.sessionJwt,
560
- expiresAt: body.expiresAt,
561
- customerId: body.customerId ?? null
560
+ sessionJwt: data.sessionJwt,
561
+ expiresAt: data.expiresAt,
562
+ customerId: data.customerId ?? null
562
563
  };
563
564
  }
564
565
  /**
@@ -599,10 +600,23 @@ var _BridgeClient = class _BridgeClient {
599
600
  /**
600
601
  * Headless config — the PricingCard widget's primary read path.
601
602
  *
602
- * Mirrors `GET /api/v1/portal/headless/config` which returns
603
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
604
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
605
- * cache once per widget instance on the client.
603
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
604
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
605
+ * extended server-side with `offerings` + `embeddableWidget`. This was
606
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
607
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
608
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
609
+ * embed-tier fetch against that URL failed 400 "Missing tenant
610
+ * identification" by construction; this endpoint is scoped to the same
611
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
612
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
613
+ * sends is sufficient.
614
+ *
615
+ * The backend response is still a flat record (`tenantSlug,
616
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
617
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
618
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
619
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
606
620
  *
607
621
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
608
622
  * malformed JSON. Widgets fall through to their empty / error state and
@@ -617,7 +631,7 @@ var _BridgeClient = class _BridgeClient {
617
631
  }
618
632
  const target = slug || this.tenantSlug;
619
633
  if (!target) return null;
620
- const url = `${this.baseUrl}/api/v1/portal/headless/config?tenantSlug=${encodeURIComponent(target)}`;
634
+ const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
621
635
  let resp;
622
636
  try {
623
637
  resp = await this.send(url, {
@@ -630,7 +644,19 @@ var _BridgeClient = class _BridgeClient {
630
644
  if (!resp.ok) return null;
631
645
  try {
632
646
  const body = await resp.json();
633
- return body && typeof body === "object" ? body : null;
647
+ if (!body || typeof body !== "object") return null;
648
+ const data = body.data ?? body;
649
+ return {
650
+ tenantSlug: data.tenantSlug,
651
+ branding: {
652
+ primaryColor: data.primaryColor ?? null,
653
+ secondaryColor: data.secondaryColor ?? null,
654
+ logoUrl: data.logoUrl ?? null,
655
+ fontFamily: data.fontFamily ?? null
656
+ },
657
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
658
+ embeddableWidget: data.embeddableWidget ?? null
659
+ };
634
660
  } catch {
635
661
  return null;
636
662
  }
@@ -774,7 +800,8 @@ var _BridgeClient = class _BridgeClient {
774
800
  false
775
801
  );
776
802
  }
777
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
803
+ const data = body?.data ?? body ?? {};
804
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
778
805
  throw new BridgeClientError(
779
806
  "Malformed checkout-initiate response (missing required fields)",
780
807
  500,
@@ -783,9 +810,9 @@ var _BridgeClient = class _BridgeClient {
783
810
  );
784
811
  }
785
812
  return {
786
- checkoutUrl: body.checkoutUrl,
787
- checkoutSessionId: body.checkoutSessionId,
788
- expiresAt: body.expiresAt
813
+ checkoutUrl: data.checkoutUrl,
814
+ checkoutSessionId: data.checkoutSessionId,
815
+ expiresAt: data.expiresAt
789
816
  };
790
817
  }
791
818
  /* ────────────────────────────────────────────────────────────────────
@@ -1593,13 +1620,16 @@ var _BridgeClient = class _BridgeClient {
1593
1620
  if (!resp.ok) return emptyPage;
1594
1621
  try {
1595
1622
  const body = await resp.json();
1596
- if (!body || typeof body !== "object") return emptyPage;
1623
+ if (!body || typeof body !== "object") {
1624
+ return emptyPage;
1625
+ }
1626
+ const data = body.data ?? body;
1597
1627
  return {
1598
- content: Array.isArray(body.content) ? body.content : [],
1599
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1600
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1601
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1602
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1628
+ content: Array.isArray(data.content) ? data.content : [],
1629
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1630
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1631
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1632
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1603
1633
  };
1604
1634
  } catch {
1605
1635
  return emptyPage;
@@ -3103,6 +3133,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
3103
3133
  return `${currency} ${amount.toFixed(2)}`;
3104
3134
  }
3105
3135
  }
3136
+ function resolveDisplayPrice(offering, locale, intlOverride) {
3137
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
3138
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
3139
+ const perUnitPlan = offering.ratePlans?.find(
3140
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
3141
+ );
3142
+ if (perUnitPlan) {
3143
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
3144
+ if (unitPrice !== "\u2014") {
3145
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
3146
+ }
3147
+ }
3148
+ return { text: "\u2014", unitSuffix: null };
3149
+ }
3106
3150
  function localizedName(o) {
3107
3151
  return o.localizedDisplayName || o.name || "(unnamed plan)";
3108
3152
  }
@@ -3506,12 +3550,7 @@ function PlanCard({
3506
3550
  onCtaClick,
3507
3551
  intlOverride
3508
3552
  }) {
3509
- const price = safeFormatCurrency(
3510
- offering.priceCents,
3511
- offering.currency,
3512
- locale,
3513
- intlOverride
3514
- );
3553
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3515
3554
  const isContactSales = price === "\u2014";
3516
3555
  const planName = localizedName(offering);
3517
3556
  const description = localizedDescription(offering);
@@ -3532,7 +3571,7 @@ function PlanCard({
3532
3571
  "div",
3533
3572
  {
3534
3573
  role: "group",
3535
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3574
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3536
3575
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3537
3576
  style: cardStyle2,
3538
3577
  children: [
@@ -3597,14 +3636,14 @@ function PlanCard({
3597
3636
  children: price
3598
3637
  }
3599
3638
  ),
3600
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxRuntime.jsxs(
3639
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxRuntime.jsxs(
3601
3640
  "span",
3602
3641
  {
3603
3642
  className: "aforo-w-pc-cycle",
3604
3643
  style: { fontSize: 13, color: tokens.textMuted },
3605
3644
  children: [
3606
3645
  "/ ",
3607
- humanCycle(offering.billingCycle)
3646
+ unitSuffix ?? humanCycle(offering.billingCycle)
3608
3647
  ]
3609
3648
  }
3610
3649
  ) : null
@@ -3762,6 +3801,7 @@ function TableLayout({
3762
3801
  /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { ...headerCellStyle, color: tokens.textMuted, fontSize: 12 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "aforo-w-pc-sr-only", style: srOnlyStyle, children: "Feature" }) }),
3763
3802
  offerings.map((o) => {
3764
3803
  const featured = offeringIsFeatured(o, featuredOverride);
3804
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3765
3805
  return /* @__PURE__ */ jsxRuntime.jsx(
3766
3806
  "th",
3767
3807
  {
@@ -3791,13 +3831,20 @@ function TableLayout({
3791
3831
  children: "Most popular"
3792
3832
  }
3793
3833
  ) : null,
3794
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3834
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3835
+ tablePrice,
3836
+ tableUnitSuffix ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3837
+ " ",
3838
+ "/ ",
3839
+ tableUnitSuffix
3840
+ ] }) : null
3841
+ ] }),
3795
3842
  /* @__PURE__ */ jsxRuntime.jsx(
3796
3843
  "button",
3797
3844
  {
3798
3845
  type: "button",
3799
3846
  onClick: () => onCtaClick(o),
3800
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3847
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3801
3848
  style: {
3802
3849
  inlineSize: "100%",
3803
3850
  padding: "8px 12px",
@@ -3810,7 +3857,7 @@ function TableLayout({
3810
3857
  cursor: "pointer",
3811
3858
  fontFamily: tokens.fontFamily
3812
3859
  },
3813
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3860
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3814
3861
  }
3815
3862
  )
3816
3863
  ] })
@@ -5,7 +5,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  // src/vanilla/index.ts
6
6
 
7
7
  // src/core/version.ts
8
- var VERSION = "1.0.3";
8
+ var VERSION = "1.0.5";
9
9
 
10
10
  // src/core/AforoEmbed.ts
11
11
  var mountedElements = /* @__PURE__ */ new WeakMap();
@@ -525,7 +525,8 @@ var _BridgeClient = class _BridgeClient {
525
525
  throw await this.parseError(resp);
526
526
  }
527
527
  const body = await resp.json();
528
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
528
+ const data = body?.data ?? body;
529
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
529
530
  throw new BridgeClientError(
530
531
  "Malformed bridge exchange response",
531
532
  500,
@@ -534,9 +535,9 @@ var _BridgeClient = class _BridgeClient {
534
535
  );
535
536
  }
536
537
  return {
537
- sessionJwt: body.sessionJwt,
538
- expiresAt: body.expiresAt,
539
- customerId: body.customerId ?? null
538
+ sessionJwt: data.sessionJwt,
539
+ expiresAt: data.expiresAt,
540
+ customerId: data.customerId ?? null
540
541
  };
541
542
  }
542
543
  /**
@@ -577,10 +578,23 @@ var _BridgeClient = class _BridgeClient {
577
578
  /**
578
579
  * Headless config — the PricingCard widget's primary read path.
579
580
  *
580
- * Mirrors `GET /api/v1/portal/headless/config` which returns
581
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
582
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
583
- * cache once per widget instance on the client.
581
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
582
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
583
+ * extended server-side with `offerings` + `embeddableWidget`. This was
584
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
585
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
586
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
587
+ * embed-tier fetch against that URL failed 400 "Missing tenant
588
+ * identification" by construction; this endpoint is scoped to the same
589
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
590
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
591
+ * sends is sufficient.
592
+ *
593
+ * The backend response is still a flat record (`tenantSlug,
594
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
595
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
596
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
597
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
584
598
  *
585
599
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
586
600
  * malformed JSON. Widgets fall through to their empty / error state and
@@ -595,7 +609,7 @@ var _BridgeClient = class _BridgeClient {
595
609
  }
596
610
  const target = slug || this.tenantSlug;
597
611
  if (!target) return null;
598
- const url = `${this.baseUrl}/api/v1/portal/headless/config?tenantSlug=${encodeURIComponent(target)}`;
612
+ const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
599
613
  let resp;
600
614
  try {
601
615
  resp = await this.send(url, {
@@ -608,7 +622,19 @@ var _BridgeClient = class _BridgeClient {
608
622
  if (!resp.ok) return null;
609
623
  try {
610
624
  const body = await resp.json();
611
- return body && typeof body === "object" ? body : null;
625
+ if (!body || typeof body !== "object") return null;
626
+ const data = body.data ?? body;
627
+ return {
628
+ tenantSlug: data.tenantSlug,
629
+ branding: {
630
+ primaryColor: data.primaryColor ?? null,
631
+ secondaryColor: data.secondaryColor ?? null,
632
+ logoUrl: data.logoUrl ?? null,
633
+ fontFamily: data.fontFamily ?? null
634
+ },
635
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
636
+ embeddableWidget: data.embeddableWidget ?? null
637
+ };
612
638
  } catch {
613
639
  return null;
614
640
  }
@@ -752,7 +778,8 @@ var _BridgeClient = class _BridgeClient {
752
778
  false
753
779
  );
754
780
  }
755
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
781
+ const data = body?.data ?? body ?? {};
782
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
756
783
  throw new BridgeClientError(
757
784
  "Malformed checkout-initiate response (missing required fields)",
758
785
  500,
@@ -761,9 +788,9 @@ var _BridgeClient = class _BridgeClient {
761
788
  );
762
789
  }
763
790
  return {
764
- checkoutUrl: body.checkoutUrl,
765
- checkoutSessionId: body.checkoutSessionId,
766
- expiresAt: body.expiresAt
791
+ checkoutUrl: data.checkoutUrl,
792
+ checkoutSessionId: data.checkoutSessionId,
793
+ expiresAt: data.expiresAt
767
794
  };
768
795
  }
769
796
  /* ────────────────────────────────────────────────────────────────────
@@ -1571,13 +1598,16 @@ var _BridgeClient = class _BridgeClient {
1571
1598
  if (!resp.ok) return emptyPage;
1572
1599
  try {
1573
1600
  const body = await resp.json();
1574
- if (!body || typeof body !== "object") return emptyPage;
1601
+ if (!body || typeof body !== "object") {
1602
+ return emptyPage;
1603
+ }
1604
+ const data = body.data ?? body;
1575
1605
  return {
1576
- content: Array.isArray(body.content) ? body.content : [],
1577
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1578
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1579
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1580
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1606
+ content: Array.isArray(data.content) ? data.content : [],
1607
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1608
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1609
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1610
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1581
1611
  };
1582
1612
  } catch {
1583
1613
  return emptyPage;
@@ -3081,6 +3111,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
3081
3111
  return `${currency} ${amount.toFixed(2)}`;
3082
3112
  }
3083
3113
  }
3114
+ function resolveDisplayPrice(offering, locale, intlOverride) {
3115
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
3116
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
3117
+ const perUnitPlan = offering.ratePlans?.find(
3118
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
3119
+ );
3120
+ if (perUnitPlan) {
3121
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
3122
+ if (unitPrice !== "\u2014") {
3123
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
3124
+ }
3125
+ }
3126
+ return { text: "\u2014", unitSuffix: null };
3127
+ }
3084
3128
  function localizedName(o) {
3085
3129
  return o.localizedDisplayName || o.name || "(unnamed plan)";
3086
3130
  }
@@ -3484,12 +3528,7 @@ function PlanCard({
3484
3528
  onCtaClick,
3485
3529
  intlOverride
3486
3530
  }) {
3487
- const price = safeFormatCurrency(
3488
- offering.priceCents,
3489
- offering.currency,
3490
- locale,
3491
- intlOverride
3492
- );
3531
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3493
3532
  const isContactSales = price === "\u2014";
3494
3533
  const planName = localizedName(offering);
3495
3534
  const description = localizedDescription(offering);
@@ -3510,7 +3549,7 @@ function PlanCard({
3510
3549
  "div",
3511
3550
  {
3512
3551
  role: "group",
3513
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3552
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3514
3553
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3515
3554
  style: cardStyle2,
3516
3555
  children: [
@@ -3575,14 +3614,14 @@ function PlanCard({
3575
3614
  children: price
3576
3615
  }
3577
3616
  ),
3578
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxs(
3617
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxs(
3579
3618
  "span",
3580
3619
  {
3581
3620
  className: "aforo-w-pc-cycle",
3582
3621
  style: { fontSize: 13, color: tokens.textMuted },
3583
3622
  children: [
3584
3623
  "/ ",
3585
- humanCycle(offering.billingCycle)
3624
+ unitSuffix ?? humanCycle(offering.billingCycle)
3586
3625
  ]
3587
3626
  }
3588
3627
  ) : null
@@ -3740,6 +3779,7 @@ function TableLayout({
3740
3779
  /* @__PURE__ */ jsx("th", { scope: "col", style: { ...headerCellStyle, color: tokens.textMuted, fontSize: 12 }, children: /* @__PURE__ */ jsx("span", { className: "aforo-w-pc-sr-only", style: srOnlyStyle, children: "Feature" }) }),
3741
3780
  offerings.map((o) => {
3742
3781
  const featured = offeringIsFeatured(o, featuredOverride);
3782
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3743
3783
  return /* @__PURE__ */ jsx(
3744
3784
  "th",
3745
3785
  {
@@ -3769,13 +3809,20 @@ function TableLayout({
3769
3809
  children: "Most popular"
3770
3810
  }
3771
3811
  ) : null,
3772
- /* @__PURE__ */ jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3812
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3813
+ tablePrice,
3814
+ tableUnitSuffix ? /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3815
+ " ",
3816
+ "/ ",
3817
+ tableUnitSuffix
3818
+ ] }) : null
3819
+ ] }),
3773
3820
  /* @__PURE__ */ jsx(
3774
3821
  "button",
3775
3822
  {
3776
3823
  type: "button",
3777
3824
  onClick: () => onCtaClick(o),
3778
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3825
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3779
3826
  style: {
3780
3827
  inlineSize: "100%",
3781
3828
  padding: "8px 12px",
@@ -3788,7 +3835,7 @@ function TableLayout({
3788
3835
  cursor: "pointer",
3789
3836
  fontFamily: tokens.fontFamily
3790
3837
  },
3791
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3838
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3792
3839
  }
3793
3840
  )
3794
3841
  ] })