npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@framework-cwf/booking-embed

v0.3.3

Published

<BookingModal> + <AccountModal> iframe components and the typed buildEmbedUrl helper. Implements the booking-web postMessage protocol.

Readme

@framework-cwf/booking-embed

Runtime home for the booking iframe story. Hosts the URL builders that mint the booking-web iframe URL, the <BookingModal /> + <AccountModal /> components that mount the iframe and exchange typed postMessages with it, and the consumer-side useStripeReturn() hook for the Stripe-return auto-open flow.

Installation

Published to GitHub Packages under the @framework-cwf scope. Consumers need an .npmrc pointing the scope at the GitHub Packages registry plus an auth token:

@framework-cwf:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
pnpm add @framework-cwf/booking-embed

Public API

| Export | Purpose | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | BookingModal | The booking-flow iframe modal (Client Component, 'use client'). | | AccountModal | The profile-management iframe modal. Shares the auth-handshake + origin contract with BookingModal but has its own message subset (profile:sign_in_required, profile:book_now) and is sign-in-mandatory. | | useStripeReturn | Hook — detects ?payment_result=success&… on mount, fires onDetected. | | configureBookingEmbed | Process-level config: { iframeOrigin }. Call once on app boot. | | buildEmbedUrl | Re-export from @framework-cwf/contracts — pure URL builder. | | buildBookingUrl | Convenience wrapper over buildEmbedUrl (variant defaults + overrides). | | parseStripeReturn / stripStripeReturnFromHistory | Low-level Stripe-return helpers. | | BookingEmbedError (+ subclasses) | Typed errors. | | MissingIframeOriginError | Thrown on first render in production when no origin is configured. | | VARIANTS, VARIANT_DEFAULTS, … | Re-exports from contracts for one-stop import. |

Wiring on app boot

<BookingModal /> needs to know the exact origin of the iframe host so it can validate every inbound MessageEvent.origin and target every outbound postMessage() call. In production, a missing origin is a hard error — the modal throws MissingIframeOriginError on first render rather than falling back to "*", which would let any cross-origin iframe receive privileged messages (auth tokens, payment session IDs).

Configure once from the root layout — typically by @framework-cwf/core (T1.G.1) — and forget about it:

import { configureBookingEmbed } from "@framework-cwf/booking-embed";

configureBookingEmbed({ iframeOrigin: "https://booking.example.com" });

In development the origin can be derived automatically from iframeBaseUrl, so the dev loop works without calling configureBookingEmbed at all.

Using <BookingModal />

"use client";

import { BookingModal, useStripeReturn } from "@framework-cwf/booking-embed";
import { useState } from "react";

export default function BookNowCta() {
  const [open, setOpen] = useState(false);

  // Auto-open the modal on Stripe return — the modal handles the rest
  // (posts booking:payment_complete, strips URL params, fires onPaymentComplete).
  useStripeReturn({ onDetected: () => setOpen(true) });

  return (
    <>
      <button type="button" onClick={() => setOpen(true)}>
        Book now
      </button>
      <BookingModal
        open={open}
        onClose={() => setOpen(false)}
        iframeBaseUrl="https://booking.example.com/booking-platform"
        businessGuid="a1b20001-0000-4000-8000-000000000001"
        variant="premium"
        onPaymentComplete={(paymentId) => {
          // e.g. show a toast, refresh the "your bookings" widget, fire analytics
          console.log("booked!", paymentId);
        }}
      />
    </>
  );
}

Using <AccountModal />

Same iframe origin contract as <BookingModal />, smaller surface, no Stripe handoff. Sign-in is mandatory — opening the modal while unauthed closes it and bounces the user through Cognito's hosted UI via @framework-cwf/auth's initiateLogin().

"use client";

import { AccountModal, BookingModal } from "@framework-cwf/booking-embed";
import { useState } from "react";

export default function AccountAndBookCtas() {
  const [accountOpen, setAccountOpen] = useState(false);
  const [bookingOpen, setBookingOpen] = useState(false);

  return (
    <>
      <button type="button" onClick={() => setAccountOpen(true)}>
        My account
      </button>
      <AccountModal
        open={accountOpen}
        onClose={() => setAccountOpen(false)}
        iframeBaseUrl="https://booking.example.com/booking-platform"
        businessGuid="a1b20001-0000-4000-8000-000000000001"
        variant="premium"
        onBookNow={() => {
          // User clicked "Book now" inside the profile iframe.
          // Account modal auto-closes; we open the booking modal.
          setBookingOpen(true);
        }}
      />
      <BookingModal
        open={bookingOpen}
        onClose={() => setBookingOpen(false)}
        iframeBaseUrl="https://booking.example.com/booking-platform"
        businessGuid="a1b20001-0000-4000-8000-000000000001"
        variant="premium"
      />
    </>
  );
}

Message subset handled (per packages/contracts/src/postmessage.ts):

| Message | Direction | Action | | -------------------------- | ------------- | --------------------------------------------------------------------------------- | | booking:ready | iframe→parent | Post auth:token once resolveSession() has resolved (handshake races handled). | | profile:sign_in_required | iframe→parent | Close + call initiateLogin(). | | profile:book_now | iframe→parent | Close + call onBookNow?.(). | | auth:token | parent→iframe | Sent in response to booking:ready. No returnUrl/cancelUrl (no Stripe). |

booking:resize and booking:payment_ready are silently dropped — those are <BookingModal />'s concern; a consumer mounting both modals against the same origin won't see them bleed.

Why useStripeReturn is a hook, not a prop

<BookingModal /> is a controlled component (open is owned by the consumer). The Stripe-return auto-open signal therefore has to flow OUT of the framework and into the consumer's state. A hook expresses that flow cleanly; a prop-driven design would force the modal to manage open internally, contradicting the controlled-component contract.

Lifecycle

  1. Consumer flips open to true for the first time → iframe is mounted lazily with src = buildBookingUrl(...).
  2. Iframe stays in the DOM thereafter, even when closed (hidden via CSS). Unmounting + remounting would blow away the booking session.
  3. Iframe posts booking:ready → parent reads resolveSession() and posts auth:token. If a Stripe return was detected on mount, the parent ALSO posts booking:payment_complete and fires onPaymentComplete(paymentId) AFTER stripping ?payment_result=success&cart_session_id=…&payment_id=… from the URL.
  4. Iframe posts booking:resize periodically → parent updates iframe height inside requestAnimationFrame to avoid layout thrash.
  5. User clicks "Pay" → iframe posts booking:payment_ready { stripeUrl } → parent calls window.location.assign(stripeUrl) (full top-frame navigation; iframes can't navigate their own top).
  6. Stripe redirects back → consumer's useStripeReturn() hook fires, modal auto-opens, lifecycle resumes at step 3.

Security

  • Strict origin validation. Every inbound MessageEvent is rejected silently if event.origin ≠ the resolved iframe origin. Silent rejection (no logs, no throws) is the correct pattern here — loud rejection leaks information to an attacker probing the parent.
  • No "*" fallback in production. MissingIframeOriginError is thrown on first render if no origin is configured.
  • Wire-shape runtime guard. isBookingMessage from contracts validates every inbound message against the discriminated-union schema before the switch ever runs. Malformed messages drop silently.

Tests

| File | Coverage | | ----------------------------------- | --------------------------------------------------------------------------------------------------------- | | BookingModal.test.tsx (20 cases) | All four acceptance scenarios + render shape + memory hygiene. | | AccountModal.test.tsx (22 cases) | Auth gate, ready/token handshake (both race orderings), profile messages, origin silence, memory hygiene. | | build-booking-url.test.ts (47) | Variant × brand-param matrix (T1.D.1). | | stripe-return.test.ts (10) | Parse + strip helpers, including history preservation. | | resolve-iframe-origin.test.ts (6) | Dev fallback + production hard-fail + override. | | config.test.ts (4) | Singleton get/set/reset. |

Total: 109 vitest cases, all green under jsdom. Real-browser end-to-end coverage (Playwright) is deferred to T1.G.2 when apps/template exists to host the parent page in a real browser.