@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/CHANGELOG.md CHANGED
@@ -8,8 +8,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.0.5] — 2026-08-26
12
+
11
13
  ### Fixed
12
14
 
15
+ - **Upgrade/Cancel (and every other widget's) bridge-exchange session refresh
16
+ threw "Malformed bridge exchange response" even though the exchange call
17
+ itself returned HTTP 200.** `BridgeClient.exchange()` — along with
18
+ `fetchHeadlessConfig()`, `fetchSubscriptions()`, and `initiateCheckout()` —
19
+ read the session/config/subscription/checkout payload directly off the
20
+ response body, but storefront-service wraps it in a `{success, data}`
21
+ envelope. Every field lookup came back `undefined`, so `exchange()`'s own
22
+ shape validation correctly rejected the (correctly-wrapped) response as
23
+ malformed. All four methods now unwrap `body.data ?? body` before reading
24
+ fields, so both a flat and an enveloped response parse correctly. This was
25
+ the root cause of "Confirm cancellation" failing on the Upgrade/Cancel
26
+ Script Tag widget after a legitimate session refresh mid-flow.
27
+ - **PricingCard falls back to per-unit rate when `priceCents` is null.**
28
+ Offerings with no flat/base price (pure PER_UNIT rate plans, e.g. $9/unit)
29
+ previously rendered as "Contact us". PricingCard now reads the primary rate
30
+ plan's `perUnitPriceCents` from `OfferingPayload.ratePlans` and renders
31
+ "$9.00 / unit" in both the card-grid and comparison-table layouts.
32
+
33
+ ### Fixed (carried over from unreleased, pre-1.0.5)
34
+
13
35
  - **Script-tag loader now parses the PricingCard presentation attributes
14
36
  `data-cta-text`, `data-cta-url`, `data-max-plans`, and
15
37
  `data-show-features`.** `pricingCardConfigToProps` reads all four
package/dist/index.cjs CHANGED
@@ -171,7 +171,8 @@ var _BridgeClient = class _BridgeClient {
171
171
  throw await this.parseError(resp);
172
172
  }
173
173
  const body = await resp.json();
174
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
174
+ const data = body?.data ?? body;
175
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
175
176
  throw new BridgeClientError(
176
177
  "Malformed bridge exchange response",
177
178
  500,
@@ -180,9 +181,9 @@ var _BridgeClient = class _BridgeClient {
180
181
  );
181
182
  }
182
183
  return {
183
- sessionJwt: body.sessionJwt,
184
- expiresAt: body.expiresAt,
185
- customerId: body.customerId ?? null
184
+ sessionJwt: data.sessionJwt,
185
+ expiresAt: data.expiresAt,
186
+ customerId: data.customerId ?? null
186
187
  };
187
188
  }
188
189
  /**
@@ -223,10 +224,23 @@ var _BridgeClient = class _BridgeClient {
223
224
  /**
224
225
  * Headless config — the PricingCard widget's primary read path.
225
226
  *
226
- * Mirrors `GET /api/v1/portal/headless/config` which returns
227
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
228
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
229
- * cache once per widget instance on the client.
227
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
228
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
229
+ * extended server-side with `offerings` + `embeddableWidget`. This was
230
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
231
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
232
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
233
+ * embed-tier fetch against that URL failed 400 "Missing tenant
234
+ * identification" by construction; this endpoint is scoped to the same
235
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
236
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
237
+ * sends is sufficient.
238
+ *
239
+ * The backend response is still a flat record (`tenantSlug,
240
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
241
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
242
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
243
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
230
244
  *
231
245
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
232
246
  * malformed JSON. Widgets fall through to their empty / error state and
@@ -241,7 +255,7 @@ var _BridgeClient = class _BridgeClient {
241
255
  }
242
256
  const target = slug || this.tenantSlug;
243
257
  if (!target) return null;
244
- const url = `${this.baseUrl}/api/v1/portal/headless/config?tenantSlug=${encodeURIComponent(target)}`;
258
+ const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
245
259
  let resp;
246
260
  try {
247
261
  resp = await this.send(url, {
@@ -254,7 +268,19 @@ var _BridgeClient = class _BridgeClient {
254
268
  if (!resp.ok) return null;
255
269
  try {
256
270
  const body = await resp.json();
257
- return body && typeof body === "object" ? body : null;
271
+ if (!body || typeof body !== "object") return null;
272
+ const data = body.data ?? body;
273
+ return {
274
+ tenantSlug: data.tenantSlug,
275
+ branding: {
276
+ primaryColor: data.primaryColor ?? null,
277
+ secondaryColor: data.secondaryColor ?? null,
278
+ logoUrl: data.logoUrl ?? null,
279
+ fontFamily: data.fontFamily ?? null
280
+ },
281
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
282
+ embeddableWidget: data.embeddableWidget ?? null
283
+ };
258
284
  } catch {
259
285
  return null;
260
286
  }
@@ -398,7 +424,8 @@ var _BridgeClient = class _BridgeClient {
398
424
  false
399
425
  );
400
426
  }
401
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
427
+ const data = body?.data ?? body ?? {};
428
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
402
429
  throw new BridgeClientError(
403
430
  "Malformed checkout-initiate response (missing required fields)",
404
431
  500,
@@ -407,9 +434,9 @@ var _BridgeClient = class _BridgeClient {
407
434
  );
408
435
  }
409
436
  return {
410
- checkoutUrl: body.checkoutUrl,
411
- checkoutSessionId: body.checkoutSessionId,
412
- expiresAt: body.expiresAt
437
+ checkoutUrl: data.checkoutUrl,
438
+ checkoutSessionId: data.checkoutSessionId,
439
+ expiresAt: data.expiresAt
413
440
  };
414
441
  }
415
442
  /* ────────────────────────────────────────────────────────────────────
@@ -1217,13 +1244,16 @@ var _BridgeClient = class _BridgeClient {
1217
1244
  if (!resp.ok) return emptyPage;
1218
1245
  try {
1219
1246
  const body = await resp.json();
1220
- if (!body || typeof body !== "object") return emptyPage;
1247
+ if (!body || typeof body !== "object") {
1248
+ return emptyPage;
1249
+ }
1250
+ const data = body.data ?? body;
1221
1251
  return {
1222
- content: Array.isArray(body.content) ? body.content : [],
1223
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1224
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1225
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1226
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1252
+ content: Array.isArray(data.content) ? data.content : [],
1253
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1254
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1255
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1256
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1227
1257
  };
1228
1258
  } catch {
1229
1259
  return emptyPage;
@@ -2727,6 +2757,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
2727
2757
  return `${currency} ${amount.toFixed(2)}`;
2728
2758
  }
2729
2759
  }
2760
+ function resolveDisplayPrice(offering, locale, intlOverride) {
2761
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
2762
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
2763
+ const perUnitPlan = offering.ratePlans?.find(
2764
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
2765
+ );
2766
+ if (perUnitPlan) {
2767
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
2768
+ if (unitPrice !== "\u2014") {
2769
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
2770
+ }
2771
+ }
2772
+ return { text: "\u2014", unitSuffix: null };
2773
+ }
2730
2774
  function localizedName(o) {
2731
2775
  return o.localizedDisplayName || o.name || "(unnamed plan)";
2732
2776
  }
@@ -3130,12 +3174,7 @@ function PlanCard({
3130
3174
  onCtaClick,
3131
3175
  intlOverride
3132
3176
  }) {
3133
- const price = safeFormatCurrency(
3134
- offering.priceCents,
3135
- offering.currency,
3136
- locale,
3137
- intlOverride
3138
- );
3177
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3139
3178
  const isContactSales = price === "\u2014";
3140
3179
  const planName = localizedName(offering);
3141
3180
  const description = localizedDescription(offering);
@@ -3156,7 +3195,7 @@ function PlanCard({
3156
3195
  "div",
3157
3196
  {
3158
3197
  role: "group",
3159
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3198
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3160
3199
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3161
3200
  style: cardStyle2,
3162
3201
  children: [
@@ -3221,14 +3260,14 @@ function PlanCard({
3221
3260
  children: price
3222
3261
  }
3223
3262
  ),
3224
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxRuntime.jsxs(
3263
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxRuntime.jsxs(
3225
3264
  "span",
3226
3265
  {
3227
3266
  className: "aforo-w-pc-cycle",
3228
3267
  style: { fontSize: 13, color: tokens.textMuted },
3229
3268
  children: [
3230
3269
  "/ ",
3231
- humanCycle(offering.billingCycle)
3270
+ unitSuffix ?? humanCycle(offering.billingCycle)
3232
3271
  ]
3233
3272
  }
3234
3273
  ) : null
@@ -3386,6 +3425,7 @@ function TableLayout({
3386
3425
  /* @__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" }) }),
3387
3426
  offerings.map((o) => {
3388
3427
  const featured = offeringIsFeatured(o, featuredOverride);
3428
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3389
3429
  return /* @__PURE__ */ jsxRuntime.jsx(
3390
3430
  "th",
3391
3431
  {
@@ -3415,13 +3455,20 @@ function TableLayout({
3415
3455
  children: "Most popular"
3416
3456
  }
3417
3457
  ) : null,
3418
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3458
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3459
+ tablePrice,
3460
+ tableUnitSuffix ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3461
+ " ",
3462
+ "/ ",
3463
+ tableUnitSuffix
3464
+ ] }) : null
3465
+ ] }),
3419
3466
  /* @__PURE__ */ jsxRuntime.jsx(
3420
3467
  "button",
3421
3468
  {
3422
3469
  type: "button",
3423
3470
  onClick: () => onCtaClick(o),
3424
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3471
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3425
3472
  style: {
3426
3473
  inlineSize: "100%",
3427
3474
  padding: "8px 12px",
@@ -3434,7 +3481,7 @@ function TableLayout({
3434
3481
  cursor: "pointer",
3435
3482
  fontFamily: tokens.fontFamily
3436
3483
  },
3437
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3484
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3438
3485
  }
3439
3486
  )
3440
3487
  ] })
@@ -12151,7 +12198,7 @@ function ResultPanel({
12151
12198
  }
12152
12199
 
12153
12200
  // src/core/version.ts
12154
- var VERSION = "1.0.3";
12201
+ var VERSION = "1.0.5";
12155
12202
 
12156
12203
  // src/core/AforoEmbed.ts
12157
12204
  var mountedElements = /* @__PURE__ */ new WeakMap();
package/dist/index.d.cts CHANGED
@@ -639,7 +639,7 @@ declare function AforoUpgradeCancel(props: AforoUpgradeCancelProps): React.React
639
639
  * POST /api/v1/portal/embed/bridge/exchange — bridge JWT → session JWT
640
640
  * POST /api/v1/portal/embed/bridge/refresh — session JWT refresh (Prompt 8)
641
641
  * POST /api/v1/portal/embed/telemetry — telemetry batch sink
642
- * GET /api/v1/portal/embed/tenant-config/{slug} — brand kit (this Prompt)
642
+ * GET /api/v1/portal/embed/tenant-config/{slug} — brand kit + offerings + embeddableWidget
643
643
  * GET /api/v1/portal/embed/health — public liveness
644
644
  *
645
645
  * Headers:
@@ -707,10 +707,23 @@ declare class BridgeClient {
707
707
  /**
708
708
  * Headless config — the PricingCard widget's primary read path.
709
709
  *
710
- * Mirrors `GET /api/v1/portal/headless/config` which returns
711
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
712
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
713
- * cache once per widget instance on the client.
710
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
711
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
712
+ * extended server-side with `offerings` + `embeddableWidget`. This was
713
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
714
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
715
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
716
+ * embed-tier fetch against that URL failed 400 "Missing tenant
717
+ * identification" by construction; this endpoint is scoped to the same
718
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
719
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
720
+ * sends is sufficient.
721
+ *
722
+ * The backend response is still a flat record (`tenantSlug,
723
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
724
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
725
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
726
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
714
727
  *
715
728
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
716
729
  * malformed JSON. Widgets fall through to their empty / error state and
package/dist/index.d.ts CHANGED
@@ -639,7 +639,7 @@ declare function AforoUpgradeCancel(props: AforoUpgradeCancelProps): React.React
639
639
  * POST /api/v1/portal/embed/bridge/exchange — bridge JWT → session JWT
640
640
  * POST /api/v1/portal/embed/bridge/refresh — session JWT refresh (Prompt 8)
641
641
  * POST /api/v1/portal/embed/telemetry — telemetry batch sink
642
- * GET /api/v1/portal/embed/tenant-config/{slug} — brand kit (this Prompt)
642
+ * GET /api/v1/portal/embed/tenant-config/{slug} — brand kit + offerings + embeddableWidget
643
643
  * GET /api/v1/portal/embed/health — public liveness
644
644
  *
645
645
  * Headers:
@@ -707,10 +707,23 @@ declare class BridgeClient {
707
707
  /**
708
708
  * Headless config — the PricingCard widget's primary read path.
709
709
  *
710
- * Mirrors `GET /api/v1/portal/headless/config` which returns
711
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
712
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
713
- * cache once per widget instance on the client.
710
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
711
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
712
+ * extended server-side with `offerings` + `embeddableWidget`. This was
713
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
714
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
715
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
716
+ * embed-tier fetch against that URL failed 400 "Missing tenant
717
+ * identification" by construction; this endpoint is scoped to the same
718
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
719
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
720
+ * sends is sufficient.
721
+ *
722
+ * The backend response is still a flat record (`tenantSlug,
723
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
724
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
725
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
726
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
714
727
  *
715
728
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
716
729
  * malformed JSON. Widgets fall through to their empty / error state and
package/dist/index.mjs CHANGED
@@ -149,7 +149,8 @@ var _BridgeClient = class _BridgeClient {
149
149
  throw await this.parseError(resp);
150
150
  }
151
151
  const body = await resp.json();
152
- if (!body.sessionJwt || typeof body.expiresAt !== "number") {
152
+ const data = body?.data ?? body;
153
+ if (!data || !data.sessionJwt || typeof data.expiresAt !== "number") {
153
154
  throw new BridgeClientError(
154
155
  "Malformed bridge exchange response",
155
156
  500,
@@ -158,9 +159,9 @@ var _BridgeClient = class _BridgeClient {
158
159
  );
159
160
  }
160
161
  return {
161
- sessionJwt: body.sessionJwt,
162
- expiresAt: body.expiresAt,
163
- customerId: body.customerId ?? null
162
+ sessionJwt: data.sessionJwt,
163
+ expiresAt: data.expiresAt,
164
+ customerId: data.customerId ?? null
164
165
  };
165
166
  }
166
167
  /**
@@ -201,10 +202,23 @@ var _BridgeClient = class _BridgeClient {
201
202
  /**
202
203
  * Headless config — the PricingCard widget's primary read path.
203
204
  *
204
- * Mirrors `GET /api/v1/portal/headless/config` which returns
205
- * `{branding, offerings, ratePlans, themeTokens, customPages, ...}`
206
- * denormalized JSON. The endpoint is 60s Caffeine-cached server-side; we
207
- * cache once per widget instance on the client.
205
+ * Hits `GET /api/v1/portal/embed/tenant-config/{slug}` the SAME
206
+ * embed-key-authenticated endpoint {@link getTenantBrandKit} uses, now
207
+ * extended server-side with `offerings` + `embeddableWidget`. This was
208
+ * previously wired to `GET /api/v1/portal/headless/config`, an unrelated
209
+ * T6 Headless-tier endpoint that requires `X-Storefront-Key`/
210
+ * `X-Tenant-Id` auth — headers the embed key flow never sends. Every
211
+ * embed-tier fetch against that URL failed 400 "Missing tenant
212
+ * identification" by construction; this endpoint is scoped to the same
213
+ * `EmbedKeyAuthFilter` gate as the rest of the embed plane, so the
214
+ * `Authorization: Bearer <embed_key>` header `buildHeaders()` already
215
+ * sends is sufficient.
216
+ *
217
+ * The backend response is still a flat record (`tenantSlug,
218
+ * primaryColor, secondaryColor, logoUrl, fontFamily, offerings,
219
+ * embeddableWidget`) — kept flat so `getTenantBrandKit()`'s existing
220
+ * consumers (the ThemeReader cascade) are unaffected. This method nests
221
+ * the branding fields into the shape `HeadlessConfigResponse` expects.
208
222
  *
209
223
  * Pattern #18 fail-soft — returns `null` on transport failure, 404, or
210
224
  * malformed JSON. Widgets fall through to their empty / error state and
@@ -219,7 +233,7 @@ var _BridgeClient = class _BridgeClient {
219
233
  }
220
234
  const target = slug || this.tenantSlug;
221
235
  if (!target) return null;
222
- const url = `${this.baseUrl}/api/v1/portal/headless/config?tenantSlug=${encodeURIComponent(target)}`;
236
+ const url = `${this.baseUrl}/api/v1/portal/embed/tenant-config/${encodeURIComponent(target)}`;
223
237
  let resp;
224
238
  try {
225
239
  resp = await this.send(url, {
@@ -232,7 +246,19 @@ var _BridgeClient = class _BridgeClient {
232
246
  if (!resp.ok) return null;
233
247
  try {
234
248
  const body = await resp.json();
235
- return body && typeof body === "object" ? body : null;
249
+ if (!body || typeof body !== "object") return null;
250
+ const data = body.data ?? body;
251
+ return {
252
+ tenantSlug: data.tenantSlug,
253
+ branding: {
254
+ primaryColor: data.primaryColor ?? null,
255
+ secondaryColor: data.secondaryColor ?? null,
256
+ logoUrl: data.logoUrl ?? null,
257
+ fontFamily: data.fontFamily ?? null
258
+ },
259
+ offerings: Array.isArray(data.offerings) ? data.offerings : [],
260
+ embeddableWidget: data.embeddableWidget ?? null
261
+ };
236
262
  } catch {
237
263
  return null;
238
264
  }
@@ -376,7 +402,8 @@ var _BridgeClient = class _BridgeClient {
376
402
  false
377
403
  );
378
404
  }
379
- if (!body.checkoutUrl || !body.checkoutSessionId || !body.expiresAt) {
405
+ const data = body?.data ?? body ?? {};
406
+ if (!data.checkoutUrl || !data.checkoutSessionId || !data.expiresAt) {
380
407
  throw new BridgeClientError(
381
408
  "Malformed checkout-initiate response (missing required fields)",
382
409
  500,
@@ -385,9 +412,9 @@ var _BridgeClient = class _BridgeClient {
385
412
  );
386
413
  }
387
414
  return {
388
- checkoutUrl: body.checkoutUrl,
389
- checkoutSessionId: body.checkoutSessionId,
390
- expiresAt: body.expiresAt
415
+ checkoutUrl: data.checkoutUrl,
416
+ checkoutSessionId: data.checkoutSessionId,
417
+ expiresAt: data.expiresAt
391
418
  };
392
419
  }
393
420
  /* ────────────────────────────────────────────────────────────────────
@@ -1195,13 +1222,16 @@ var _BridgeClient = class _BridgeClient {
1195
1222
  if (!resp.ok) return emptyPage;
1196
1223
  try {
1197
1224
  const body = await resp.json();
1198
- if (!body || typeof body !== "object") return emptyPage;
1225
+ if (!body || typeof body !== "object") {
1226
+ return emptyPage;
1227
+ }
1228
+ const data = body.data ?? body;
1199
1229
  return {
1200
- content: Array.isArray(body.content) ? body.content : [],
1201
- totalElements: typeof body.totalElements === "number" && Number.isFinite(body.totalElements) ? body.totalElements : 0,
1202
- totalPages: typeof body.totalPages === "number" && Number.isFinite(body.totalPages) ? body.totalPages : 0,
1203
- page: typeof body.page === "number" && Number.isFinite(body.page) ? body.page : filter.page ?? 0,
1204
- size: typeof body.size === "number" && Number.isFinite(body.size) ? body.size : filter.size ?? 10
1230
+ content: Array.isArray(data.content) ? data.content : [],
1231
+ totalElements: typeof data.totalElements === "number" && Number.isFinite(data.totalElements) ? data.totalElements : 0,
1232
+ totalPages: typeof data.totalPages === "number" && Number.isFinite(data.totalPages) ? data.totalPages : 0,
1233
+ page: typeof data.page === "number" && Number.isFinite(data.page) ? data.page : filter.page ?? 0,
1234
+ size: typeof data.size === "number" && Number.isFinite(data.size) ? data.size : filter.size ?? 10
1205
1235
  };
1206
1236
  } catch {
1207
1237
  return emptyPage;
@@ -2705,6 +2735,20 @@ function safeFormatCurrency(priceCents, currency, locale, intlOverride) {
2705
2735
  return `${currency} ${amount.toFixed(2)}`;
2706
2736
  }
2707
2737
  }
2738
+ function resolveDisplayPrice(offering, locale, intlOverride) {
2739
+ const flat = safeFormatCurrency(offering.priceCents, offering.currency, locale, intlOverride);
2740
+ if (flat !== "\u2014") return { text: flat, unitSuffix: null };
2741
+ const perUnitPlan = offering.ratePlans?.find(
2742
+ (rp) => rp.pricingModel === "PER_UNIT" && rp.perUnitPriceCents != null
2743
+ );
2744
+ if (perUnitPlan) {
2745
+ const unitPrice = safeFormatCurrency(perUnitPlan.perUnitPriceCents, offering.currency, locale, intlOverride);
2746
+ if (unitPrice !== "\u2014") {
2747
+ return { text: unitPrice, unitSuffix: perUnitPlan.unitLabel || "unit" };
2748
+ }
2749
+ }
2750
+ return { text: "\u2014", unitSuffix: null };
2751
+ }
2708
2752
  function localizedName(o) {
2709
2753
  return o.localizedDisplayName || o.name || "(unnamed plan)";
2710
2754
  }
@@ -3108,12 +3152,7 @@ function PlanCard({
3108
3152
  onCtaClick,
3109
3153
  intlOverride
3110
3154
  }) {
3111
- const price = safeFormatCurrency(
3112
- offering.priceCents,
3113
- offering.currency,
3114
- locale,
3115
- intlOverride
3116
- );
3155
+ const { text: price, unitSuffix } = resolveDisplayPrice(offering, locale, intlOverride);
3117
3156
  const isContactSales = price === "\u2014";
3118
3157
  const planName = localizedName(offering);
3119
3158
  const description = localizedDescription(offering);
@@ -3134,7 +3173,7 @@ function PlanCard({
3134
3173
  "div",
3135
3174
  {
3136
3175
  role: "group",
3137
- "aria-label": `${planName}, ${price} per ${offering.billingCycle}`,
3176
+ "aria-label": `${planName}, ${price} per ${unitSuffix ?? offering.billingCycle}`,
3138
3177
  className: `aforo-w-pc-card${featured ? " aforo-w-pc-card-featured" : ""}`,
3139
3178
  style: cardStyle2,
3140
3179
  children: [
@@ -3199,14 +3238,14 @@ function PlanCard({
3199
3238
  children: price
3200
3239
  }
3201
3240
  ),
3202
- !isContactSales && offering.billingCycle ? /* @__PURE__ */ jsxs(
3241
+ !isContactSales && (unitSuffix || offering.billingCycle) ? /* @__PURE__ */ jsxs(
3203
3242
  "span",
3204
3243
  {
3205
3244
  className: "aforo-w-pc-cycle",
3206
3245
  style: { fontSize: 13, color: tokens.textMuted },
3207
3246
  children: [
3208
3247
  "/ ",
3209
- humanCycle(offering.billingCycle)
3248
+ unitSuffix ?? humanCycle(offering.billingCycle)
3210
3249
  ]
3211
3250
  }
3212
3251
  ) : null
@@ -3364,6 +3403,7 @@ function TableLayout({
3364
3403
  /* @__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" }) }),
3365
3404
  offerings.map((o) => {
3366
3405
  const featured = offeringIsFeatured(o, featuredOverride);
3406
+ const { text: tablePrice, unitSuffix: tableUnitSuffix } = resolveDisplayPrice(o, locale, intlOverride);
3367
3407
  return /* @__PURE__ */ jsx(
3368
3408
  "th",
3369
3409
  {
@@ -3393,13 +3433,20 @@ function TableLayout({
3393
3433
  children: "Most popular"
3394
3434
  }
3395
3435
  ) : null,
3396
- /* @__PURE__ */ jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) }),
3436
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 22, fontWeight: 700 }, children: [
3437
+ tablePrice,
3438
+ tableUnitSuffix ? /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, fontWeight: 400, color: tokens.textMuted }, children: [
3439
+ " ",
3440
+ "/ ",
3441
+ tableUnitSuffix
3442
+ ] }) : null
3443
+ ] }),
3397
3444
  /* @__PURE__ */ jsx(
3398
3445
  "button",
3399
3446
  {
3400
3447
  type: "button",
3401
3448
  onClick: () => onCtaClick(o),
3402
- "aria-label": `${ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")} for ${localizedName(o)}`,
3449
+ "aria-label": `${ctaLabel(o, ctaTextProp, tablePrice === "\u2014")} for ${localizedName(o)}`,
3403
3450
  style: {
3404
3451
  inlineSize: "100%",
3405
3452
  padding: "8px 12px",
@@ -3412,7 +3459,7 @@ function TableLayout({
3412
3459
  cursor: "pointer",
3413
3460
  fontFamily: tokens.fontFamily
3414
3461
  },
3415
- children: ctaLabel(o, ctaTextProp, safeFormatCurrency(o.priceCents, o.currency, locale, intlOverride) === "\u2014")
3462
+ children: ctaLabel(o, ctaTextProp, tablePrice === "\u2014")
3416
3463
  }
3417
3464
  )
3418
3465
  ] })
@@ -12129,7 +12176,7 @@ function ResultPanel({
12129
12176
  }
12130
12177
 
12131
12178
  // src/core/version.ts
12132
- var VERSION = "1.0.3";
12179
+ var VERSION = "1.0.5";
12133
12180
 
12134
12181
  // src/core/AforoEmbed.ts
12135
12182
  var mountedElements = /* @__PURE__ */ new WeakMap();
package/dist/loader.js CHANGED
@@ -1,3 +1,3 @@
1
- var AforoEmbedLoader=(function(exports){'use strict';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 AforoEmbedLoader=(function(exports){'use strict';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
  exports.AforoEmbed=b;exports.bootstrap=E;return exports;})({});//# sourceMappingURL=loader.js.map
3
3
  //# sourceMappingURL=loader.js.map