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

@uninspired/auth-client

v2.1.1

Published

Auth client for Uninspired Studio Products

Readme

@uninspired/auth-client

Client SDK for integrating Uninspired Studio authentication, purchases, and checkout into product apps (e.g. Momentum, Unpinned).

Works in any JavaScript environment. Includes a React provider and hook for apps that use React.

Installation

bun add @uninspired/auth-client
# or
npm install @uninspired/auth-client

Peer dependencies

For React apps, also install:

bun add react react-dom

posthog-js is a dependency of this package and is initialized by TrackingProvider. Do not initialize posthog-js yourself — the shared tracking client owns PostHog init and the Meta Pixel lifecycle so consent is applied consistently.

What it provides

| Feature | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------- | | Session management | Fetch the current user session (including anonymous sessions) | | Anonymous sign-in | Automatically sign in visitors as anonymous users | | Sign out | End the current session | | Purchase data | Fetch Paddle transactions and Stripe/Paddle subscriptions | | Access checks | isPurchased() and isSubscribed() helpers for gating features | | Login URL builder | Generate a link to the accounts page with a post-login redirect back to your app | | Checkout URL builder | Generate a link to the hosted checkout flow | | Newsletter | Subscribe/unsubscribe from mailing lists | | Tracking client | TrackingProvider owns PostHog (cookieless until analytics consent) and the Meta Pixel (only with marketing consent) | | Consent API | useTrackingConsent() / getConsent() / setConsent() for your cookie banner | | Analytics helpers | captureFeatureUsed, captureCheckoutStarted, registerProduct — delegate to the tracking client |

Authentication methods (magic link, email OTP, passkey) are handled on the accounts page — this client manages sessions and purchase data, not the login UI itself.

Quick start (React)

Wrap your app in TrackingProvider, then AuthProvider, and read session/purchase state from useAuth():

import {
  AuthProvider,
  TrackingProvider,
  useAuth,
} from "@uninspired/auth-client";

function App() {
  return (
    <TrackingProvider
      product="mixd"
      posthog={{ apiKey, apiHost, uiHost }}
      metaPixelId={metaPixelId} // optional; public pixel id
    >
      <AuthProvider
        baseUrl="https://auth.uninspired.app"
        frontendUrl="https://uninspired.app"
        product="mixd"
      >
        <MyApp />
      </AuthProvider>
    </TrackingProvider>
  );
}

TrackingProvider initializes PostHog (cookieless until analytics consent) and loads the Meta Pixel only when marketing consent is granted. Without a TrackingProvider, analytics and Meta no-op.

Vanilla helpers (no React) are also exported and delegate to a PostHog instance you pass in:

import {
  captureFeatureUsed,
  captureCheckoutStarted,
  registerProduct,
} from "@uninspired/auth-client";

Consent (cookie banner)

Consent is stored in the shared uninspired_consent cookie (analytics / marketing) and is the single source of truth.

Drop-in banner

Mount CookieBanner from the separate entry so the main SDK bundle stays lean:

import { TrackingProvider, AuthProvider } from "@uninspired/auth-client";
import { CookieBanner } from "@uninspired/auth-client/cookie-banner";

<TrackingProvider product="mixd" posthog={...} metaPixelId={...}>
  <AuthProvider baseUrl={apiUrl} frontendUrl={accountsUrl} product="mixd">
    <CookieBanner />
    <App />
  </AuthProvider>
</TrackingProvider>

Peers for the banner entry: react, react-dom, @subframe/core, @radix-ui/react-dialog.

Tailwind: scan the banner output (or source in the monorepo) so utility classes are generated, e.g. add node_modules/@uninspired/auth-client/dist/cookie-banner.js (or ../../packages/auth-client/src/**/*.{ts,tsx} locally) to your content array. Use the same Subframe token preset as @us-auth/components / your accounts theme.

Optional prop: privacyPolicyUrl (defaults to the Uninspired Studio privacy policy).

Headless API

Build your own UI with the consent hooks/store:

import { useTrackingConsent } from "@uninspired/auth-client";

const { pending, consent, setConsent } = useTrackingConsent();

Imperative equivalents (usable outside React): getConsent(), isConsentPending(), setConsent(), subscribeConsent().

  • Pending / denied analytics: PostHog captures cookielessly (pageviews, autocapture, custom events) with no browser storage and no identify.
  • Pending (no decision yet): leftover PostHog/Meta cookies and matching localStorage / sessionStorage keys from before the banner are deleted on load (HttpOnly cookies cannot be cleared from JS).
  • Granted analytics: PostHog upgrades live to cookies + identify(user.id) (no email).
  • Granted marketing: the Meta Pixel loads and fires PageView / ViewContent / InitiateCheckout / Purchase.

Tracking (PostHog + Meta)

const tracking = useTracking();

tracking?.capture("feature opened", { feature: "x" }); // works in every consent mode
tracking?.trackViewContent({ contentId: "pro_123" });   // Meta, marketing-gated
tracking?.trackPurchase({ eventId, value, currency, contentIds }); // Meta, marketing-gated

useAuth() also exposes captureFeatureUsed, captureCheckoutStarted, and registerProduct, which delegate to the tracking client.

Feature flags

TrackingProvider wraps PostHog's React context around the same posthog-js instance it initializes. Do not mount a second PostHogProvider or call posthog.init() yourself.

import {
  useFeatureFlagVariantKey,
  useTracking,
  getPostHog,
} from "@uninspired/auth-client";

const variant = useFeatureFlagVariantKey("my-flag");
const tracking = useTracking();
const flag = tracking?.getFeatureFlag("my-flag");
const ph = getPostHog();

Feature flags load in every consent mode (cookieless included). identify stays consent-gated, so person-based targeting applies only after analytics consent and login.

Better Auth client (accounts login UI)

For apps that host the login UI (e.g. apps/frontend), use the shared Better Auth factory instead of duplicating plugin setup:

import { createBetterAuthClient } from "@uninspired/auth-client";

const authClient = createBetterAuthClient("https://auth.uninspired.app");
await authClient.emailOtp.sendVerificationOtp({ email, type: "sign-in" });
await authClient.changeEmail({ newEmail: email, callbackURL: "/auth" });

Also exported: useEmailOtpLoginFlow, resendButtonLabel, and RESEND_COOLDOWN_SECONDS for OTP login screens.

Quick start (React, auth only)

import { AuthProvider, useAuth } from "@uninspired/auth-client";

function App() {
  return (
    <AuthProvider
      baseUrl="https://auth.uninspired.app"
      frontendUrl="https://uninspired.app"
    >
      <MyApp />
    </AuthProvider>
  );
}

function MyApp() {
  const { isLoggedIn, session, isPurchased, getLoginUrl, signOut } = useAuth();

  if (!isLoggedIn) {
    return (
      <a
        href={getLoginUrl({
          redirectUrl: window.location.href,
          appName: "My App",
        })}
      >
        Log in
      </a>
    );
  }

  const hasPro = isPurchased(["pri_01234567890"]);

  return (
    <div>
      <p>Hello, {session?.user.email ?? "guest"}</p>
      {hasPro ? <ProFeature /> : <UpgradePrompt />}
      <button onClick={() => signOut()}>Sign out</button>
    </div>
  );
}

AuthClient (vanilla / SSR)

Use AuthClient directly when you are not in a React tree, or in server-side rendering.

import { AuthClient } from "@uninspired/auth-client";

const client = new AuthClient(
  "https://auth.uninspired.app", // API URL
  "https://uninspired.app", // Accounts frontend URL
  request.headers.get("cookie"), // optional: forward cookies for SSR
);

const session = await client.getSession();
const purchases = await client.getPurchases();

Constructor

new AuthClient(apiUrl, frontendUrl, cookie?)

| Parameter | Description | | ------------- | ------------------------------------------------------------ | | apiUrl | Auth API base URL (e.g. https://auth.uninspired.app) | | frontendUrl | Accounts frontend URL (e.g. https://uninspired.app) | | cookie | Optional cookie header string for server-side session lookup |

All API requests use credentials: "include" in the browser, so session cookies are sent automatically on the same root domain.

Methods

Session

const session = await client.getSession();
// Returns Session | null

await client.signInAnonymously();
// Creates an anonymous session (done automatically by AuthProvider)

await client.signOut(onSuccess?);
// Signs out the current user

Purchases

const purchases = await client.getPurchases();
// Returns UserPurchases | null

UserPurchases contains:

{
  paddle: {
    customerId?: string;
    subscriptions: Array<{ id, status, items: [{ priceId, productId }] }>;
    transactions: Array<{ id, items: string[], adjustments: [...] }>;
  };
  stripe: Array<{ id, status, items: string[] }>;
}

Newsletter subscribe/unsubscribe uses @uninspired/newsletter-client, not AuthClient.

URL builders

// Link to the accounts login page, redirect back after auth
const loginUrl = client.getLoginUrl({
  redirectUrl: "https://myapp.uninspired.app/dashboard",
  appName: "My App", // optional, shown on the login page
});

// Link to the hosted checkout flow
const checkoutUrl = client.getCheckoutUrl({
  priceId: "pri_01234567890",
  productId: "pro_01234567890",
  discountCode: "SAVE20", // optional
  successUrl: "https://myapp.uninspired.app/welcome", // optional
  quantity: 1, // optional, defaults to 1
});

Meta ad attribution

Meta tracking lives in the browser pixel, not on AuthClient. The pixel is loaded and fired by TrackingProvider / useTracking() only when the user granted marketing consent. There are no server-side CAPI helpers on AuthClient anymore.

const tracking = useTracking();
tracking?.trackViewContent({ contentId: "pro_123" }); // no-op without marketing consent

AuthProvider and useAuth (React)

AuthProvider props

| Prop | Type | Description | | ------------------ | ------------------------ | ----------------------------- | | baseUrl | string | Auth API URL | | frontendUrl | string | Accounts frontend URL | | product | AnalyticsProduct? | Registered as a PostHog super property on all events | | initialSession | Session \| null? | Pre-fetched session for SSR | | initialPurchases | UserPurchases \| null? | Pre-fetched purchases for SSR | | authClient | AuthClient? | Custom client instance | | queryClient | QueryClient? | Custom TanStack Query client |

The provider automatically:

  1. Revalidates the session on mount via get-session (with 1-minute stale time; SSR initialSession is shown immediately while revalidating)
  2. Signs in anonymously only after revalidation confirms no session exists
  3. Fetches purchases when a session is available

useAuth() return value

| Property | Type | Description | | --------------------- | ---------------------------------------------------- | ----------------------------------------------- | | session | Session \| null | Current session | | isLoggedIn | boolean | true if user is authenticated (not anonymous) | | isSessionFetching | boolean | Session query loading state | | refetchSession | () => Promise<void> | Re-fetch session | | purchases | UserPurchases \| null | User's purchase data | | isPurchasesFetching | boolean | Purchases query loading state | | refetchPurchases | () => Promise<void> | Re-fetch purchases | | signOut | (onSuccess?) => Promise<void> | Sign out | | getLoginUrl | AuthClient["getLoginUrl"] | Build login URL | | getCheckoutUrl | AuthClient["getCheckoutUrl"] | Build checkout URL | | captureFeatureUsed | (featureName, extra?) => void | Delegates to the tracking client | | captureCheckoutStarted | ({ plan, product? }) => void | Delegates to the tracking client | | registerProduct | (product) => void | Delegates to the tracking client | | isPurchased | (priceIds: string[]) => boolean | Check one-time purchase | | isSubscribed | (priceIds: string[], productId: string) => boolean | Check active subscription |

Access checks

const { isPurchased, isSubscribed } = useAuth();

// One-time purchase: true if user bought any of these price IDs (and hasn't been refunded)
isPurchased(["pri_lifetime", "pri_bundle"]);

// Subscription: true if user has an active Paddle or Stripe subscription
isSubscribed(["pri_monthly", "pri_yearly"], "pro_product_id");

Server-side rendering

For SSR frameworks, fetch session and purchases on the server and pass them as initial data:

// server
import { AuthClient } from "@uninspired/auth-client";

const client = new AuthClient(
  process.env.AUTH_API_URL,
  process.env.ACCOUNTS_FRONTEND_URL,
  request.headers.get("cookie"),
);

const [session, purchases] = await Promise.all([
  client.getSession(),
  client.getPurchases(),
]);

// render
<AuthProvider
  baseUrl={process.env.AUTH_API_URL}
  frontendUrl={process.env.ACCOUNTS_FRONTEND_URL}
  initialSession={session}
  initialPurchases={purchases}
>
  {children}
</AuthProvider>;

Forward the request's Cookie header to the constructor so the API can resolve the session server-side.

Cross-subdomain cookies

Sessions use cross-subdomain cookies scoped to the Uninspired Studio root domain (e.g. .uninspired.app). Your product app must be hosted on a subdomain of the same root domain for cookies to be shared automatically.

Make sure your app's origin is listed in the auth API's FRONTEND_URLS configuration.

Types

The package re-exports shared types:

import type { Session, UserPurchases } from "@uninspired/auth-client";

Building from source

This package is part of the US Auth monorepo.

Workspace consumers (monorepo)

Entry points resolve to TypeScript source (./src/index.ts) so Vite and moduleResolution: "bundler" can import the package without a pre-build:

{
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": { ".": "./src/index.ts" }
}

No bun run build is required when developing apps inside this repo.

Publishing (npm)

Published tarballs ship only compiled output in dist/. npm ignores publishConfig manifest overrides, so prepack/postpack apply them via scripts/apply-publish-config.mjs (build runs in prepack too):

cd packages/auth-client
npm publish

Product apps outside the monorepo install the compiled ESM + .d.ts from dist/.

Output goes to dist/.

Environment URLs

| Environment | API | Accounts frontend | | ----------- | --------------------------------- | ---------------------------- | | Production | https://auth.uninspired.app | https://uninspired.app | | Development | https://auth.dev.uninspired.app | https://dev.uninspired.app | | Local | http://localhost:3000 | http://localhost:3001 |