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

@vibemonetize/capacitor

v0.0.8

Published

Capacitor bridge for VibeMonetize: openCheckout/openBillingPortal (opens hosted Stripe URLs via @capacitor/browser instead of window.location, so the WebView app stays alive), registerCheckoutReturn (detects the checkout-success deep-link return via @capa

Downloads

75

Readme

@vibemonetize/capacitor

Capacitor bridge for VibeMonetize.

A Capacitor app runs a normal web bundle inside a native WebView, so @vibemonetize/react (and the raw fetch-based API) already works there unmodified for entitlement fetch, useFeature/useMeter gating, and usage/credit-bank recording — fetch, localStorage, and React all run fine inside the WebView. The ONLY seams that break are the leave-and-return-to-app payment flows: <CheckoutButton>/<AccountButton> call window.location.assign(hostedUrl), which navigates the WebView itself away to Stripe's hosted checkout/portal domain instead of opening it in a system browser alongside your still-running app — on iOS/Android that usually strands the user with no way back in.

This package fills exactly that gap. It does not reimplement entitlements, gating, JWT verification, or usage metering — keep using @vibemonetize/react/ @vibemonetize/server for those; this package only opens/observes the payment round trip.

⚠️ Read this before shipping to iOS

Apple's App Store guidelines require digital goods and services consumed inside your app to go through Apple's In-App Purchase (IAP) system, not an external payment processor like Stripe. Routing a Stripe Checkout/billing-portal session through this package (or any external browser) does not exempt you from that rule — Apple review has rejected apps for exactly this pattern. It's generally lower-risk for:

  • physical goods/services consumed outside the app (Guideline 3.1.3(a) — e.g. a physical-goods marketplace, or a service like a car ride),
  • B2B/business tools where the app itself isn't the point of sale for an end consumer, or
  • Android-only distribution (Google Play has historically been more permissive here, though its own policies are also evolving).

If your app sells subscriptions/credits/features to end users FOR USE INSIDE the app itself, talk to whoever owns your App Store compliance before using this package on iOS — this is a business/policy decision this package cannot make for you, and VibeMonetize does not offer an Apple IAP integration today.

Install

pnpm add @vibemonetize/capacitor
pnpm add @capacitor/browser @capacitor/app @capacitor/preferences

@capacitor/core, @capacitor/browser, @capacitor/app, and @capacitor/preferences are peerDependencies (optional) — this package never bundles them; it loads each one lazily via dynamic import() only when a function that needs it is actually called, and throws a typed CapacitorMonetizeError (browser_unavailable/app_plugin_unavailable) if it's missing.

Quick start

1. Register your deep link with the OS

Stripe redirects the system browser to successUrl/cancelUrl when checkout/the billing portal ends. Those need to be a deep link your Capacitor app is registered to handle (a custom URL scheme, or a universal/app link) — configure this in capacitor.config.ts and your native project (Info.plist / AndroidManifest.xml) per the Capacitor deep linking guide. This README assumes a custom scheme like com.myapp.app://.

2. Register the return listener once, at app boot

// main.tsx (Vite) or your app's entry point
import { registerCheckoutReturn } from "@vibemonetize/capacitor";

registerCheckoutReturn({
  onReturn: () => {
    // Refresh your entitlement token so newly-granted access shows up
    // immediately. If you're using @vibemonetize/react, prefer
    // `@vibemonetize/capacitor/react`'s `useCapacitorCheckout` instead —
    // it wires this up to `useMonetizeContext().refresh()` for you.
  },
});

3. Bridge anonId/endUserId durability (recommended)

<MonetizeProvider> persists its anonId/endUserId in localStorage, which is not guaranteed durable inside a WebView (iOS in particular can evict WKWebView storage under disk pressure, and app updates can reset the WebView's storage origin). Seed it from — and mirror it to — @capacitor/preferences (native platform storage) instead, without modifying @vibemonetize/react at all:

// main.tsx, before rendering <MonetizeProvider>
import { bridgeLocalStorageWithPreferences } from "@vibemonetize/capacitor";

await bridgeLocalStorageWithPreferences();

ReactDOM.createRoot(document.getElementById("root")!).render(
  <MonetizeProvider appId={appId} apiUrl={apiUrl} publishableKey={publishableKey}>
    <App />
  </MonetizeProvider>,
);

4. Replace <CheckoutButton>/<AccountButton> with the Capacitor equivalents

Framework-agnostic:

import { openCheckout } from "@vibemonetize/capacitor";

const result = await openCheckout({
  apiUrl,
  publishableKey,
  appId,
  planSlug: "pro",
  anonId, // or endUserId
  successUrl: "com.myapp.app://checkout-success",
  cancelUrl: "com.myapp.app://checkout-cancel",
});

if (result.outcome === "success") {
  // await refresh();
}

With @vibemonetize/react (recommended — fills in apiUrl/publishableKey/appId/ identity from <MonetizeProvider> automatically, and calls refresh() for you):

import { CapacitorCheckoutButton } from "@vibemonetize/capacitor/react";

<CapacitorCheckoutButton
  planSlug="pro"
  successUrl="com.myapp.app://checkout-success"
  cancelUrl="com.myapp.app://checkout-cancel"
>
  Upgrade to Pro
</CapacitorCheckoutButton>;

or the lower-level hook:

import { useCapacitorCheckout } from "@vibemonetize/capacitor/react";

function UpgradeButton() {
  const { openCheckout, pending } = useCapacitorCheckout();
  return (
    <button
      disabled={pending}
      onClick={() =>
        void openCheckout({
          planSlug: "pro",
          successUrl: "com.myapp.app://checkout-success",
          cancelUrl: "com.myapp.app://checkout-cancel",
        })
      }
    >
      Upgrade to Pro
    </button>
  );
}

The billing portal

import { openBillingPortal } from "@vibemonetize/capacitor";

const token = await getToken(); // e.g. useMonetizeContext().getToken()
if (token) {
  await openBillingPortal({
    apiUrl,
    publishableKey,
    token,
    returnUrl: "com.myapp.app://account",
  });
}

API

openCheckout(options): Promise<OpenCheckoutResult>

Creates a Stripe Checkout session via POST /v1/checkout-sessions (the same route, auth, and request/response shapes <CheckoutButton> uses — checkoutSessionRequestSchema/checkoutSessionResponseSchema from @vibemonetize/core), then opens the returned hosted url with @capacitor/browser's Browser.opennever window.location.assign. Resolves once the flow visibly ends:

type OpenCheckoutResult = {
  outcome: "success" | "cancel" | "browser-closed";
  returnUrl?: string; // present for "success"/"cancel"
};

Never throws a raw error — every failure is a CapacitorMonetizeError (see below).

openBillingPortal(options): Promise<OpenBillingPortalResult>

Same pattern for the RFC 009 billing-portal route (POST /v1/billing-portal-sessions), scoped to the caller's own entitlement JWT (token) rather than a client-named endUserId/anonId — same as <AccountButton>'s "Manage billing".

registerCheckoutReturn({ onReturn, param?, value?, urlMatch?, closeBrowser? })

Registers a single, app-wide @capacitor/app appUrlOpen listener that detects the checkout-success return (the same ?checkout=success convention <CheckoutButton>/<CheckoutSuccessBanner> use on the web — see packages/react/src/checkoutSuccess.ts) and invokes onReturn. Call this once at app boot, independent of any particular openCheckout() call. Returns { remove: () => void } to unsubscribe.

createPreferencesStorage() / seedLocalStorageFromPreferences() / mirrorLocalStorageToPreferences() / bridgeLocalStorageWithPreferences()

A durable key/value adapter backed by @capacitor/preferences, plus a seed-then-mirror bridge specifically for <MonetizeProvider>'s localStorage-persisted anonId/endUserId keys — see Quick start §3.

Errors

Every function in this package throws/rejects a CapacitorMonetizeError — never a raw Error or native-plugin exception:

export class CapacitorMonetizeError extends Error {
  readonly code: CapacitorMonetizeErrorCode;
}

code is one of @vibemonetize/core's {code,message} error catalog (not_found, forbidden, validation_failed, mode_mismatch, ...) when the api itself rejected the request, plus four bridge-only codes: network_error, invalid_response, browser_unavailable, app_plugin_unavailable. See the TSDoc on CapacitorMonetizeErrorCode in src/errors.ts for exactly when each fires.

Testing

This package's own test suite (pnpm test) mocks every @capacitor/* plugin — there is no real native runtime or device involved. Verifying actual behavior on a real iOS/ Android device or simulator (deep-link delivery, Browser.open chrome, WebView storage eviction) is out of scope for this repo's automated tests and should be part of your own app's manual/e2e QA before shipping.

Follow-up (not implemented here)

<CheckoutButton>/<AccountButton> in @vibemonetize/react still call window.location.assign unconditionally. Adding an optional openExternalUrl override prop to <MonetizeProvider> (defaulting to window.location.assign, overridable to e.g. (url) => openCheckout(...)/Browser.open) would let the existing bundled <CheckoutButton>/<AccountButton> work correctly inside a Capacitor WebView automatically, instead of requiring Capacitor apps to swap in this package's separate <CapacitorCheckoutButton>/openCheckout calls. That's a change to @vibemonetize/react itself (out of this package's scope) — flagged here for whoever owns that package next.