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

@infloapi/react

v0.3.0

Published

Drop-in React auth & identity layer for Inflo partners

Downloads

457

Readme

@infloapi/react

Drop-in React auth & identity layer for Inflo partner apps.

Covers the auth primitives every partner needs on day one: token state management, SSO redirect with PKCE, reconnect handling, scope management, OIDC state round-trips, and a complete callback handler.


Installation

npm install @infloapi/react
# peer deps
npm install react react-dom

Quickstart (10 lines)

import { InfloProvider, AuthGuard, useInfloAuth } from "@infloapi/react";

// 1. Wrap your app
function App() {
  return (
    <InfloProvider clientId="your_client_id" redirectUri="https://app.example.com/callback">
      <AuthGuard redirectTo="/dashboard">
        <Dashboard />
      </AuthGuard>
    </InfloProvider>
  );
}

// 2. Use auth state anywhere below the provider
function Dashboard() {
  const { user, scopes, reconnect } = useInfloAuth();
  return <h1>Hello, {user?.displayName}</h1>;
}

Full setup — four steps

Step 1: InfloProvider

Wraps your app and manages token state. Probes GET https://sso.infloapp.com/oauth/userinfo every 5 minutes to keep user profile and scope information fresh.

import { InfloProvider } from "@infloapi/react";

<InfloProvider
  clientId="your_client_id"
  redirectUri="https://app.example.com/callback"
  ssoBaseUrl="https://sso.infloapp.com"    // optional, default shown
  scopeProbeIntervalMs={300_000}            // optional, 5 min default
  storage={myStorageAdapter}               // optional custom token storage
>
  <App />
</InfloProvider>

Custom storage adapter

Partners choose where tokens live. Supply a StorageAdapter:

import type { StorageAdapter } from "@infloapi/react";

const storage: StorageAdapter = {
  getAccessToken()      { return localStorage.getItem("inflo_token"); },
  setAccessToken(t)     { localStorage.setItem("inflo_token", t); },
  clearTokens()         { localStorage.removeItem("inflo_token"); localStorage.removeItem("inflo_scopes"); },
  // Optional — store granted scopes from the token exchange
  getGrantedScopes()    { return localStorage.getItem("inflo_scopes"); },
  setGrantedScopes(s)   { localStorage.setItem("inflo_scopes", s); },
};

Step 2: AuthGuard

Blocks unauthenticated access. Builds a PKCE + CSRF-state-protected SSO redirect automatically.

import { AuthGuard } from "@infloapi/react";

<AuthGuard
  redirectTo="/dashboard"                      // returned to post-login
  loadingFallback={<Spinner />}                // optional
  reconnectFallback={<ReconnectPrompt />}      // optional; auto-reconnects if omitted
>
  <ProtectedPage />
</AuthGuard>

When status === "reconnect_required" (409 inflo_reconnect_required), AuthGuard either shows your reconnectFallback or calls reconnect() automatically.

Step 3: Callback page

Handle the SSO callback, exchange the code for tokens, and store them:

// pages/callback.tsx
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { handleInfloCallback, useInfloAuth } from "@infloapi/react";

export default function CallbackPage() {
  const navigate = useNavigate();
  const { refresh } = useInfloAuth();

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    handleInfloCallback({
      params,
      clientId: "your_client_id",
      redirectUri: "https://app.example.com/callback",
      storage: myStorageAdapter,           // same adapter you gave InfloProvider
    })
      .then(async ({ payload }) => {
        // ⚠️ refresh() is required here. Without it the provider hasn't seen
        // the newly stored token yet and will redirect to SSO again.
        await refresh();
        navigate((payload.returnPath as string) ?? "/");
      })
      .catch(console.error);
  }, []);

  return <div>Signing in…</div>;
}

handleInfloCallback:

  • Validates the state parameter (CSRF) via the nonce store in sessionStorage
  • Retrieves the PKCE code verifier and sends it in the token exchange
  • Calls storage.setAccessToken and storage.setGrantedScopes with the results
  • Returns { payload, tokenResponse } so you can recover the pre-auth route

Step 4: useInfloAuth()

Access auth state from any component inside <InfloProvider>:

const { user, status, scopes, scopeFallbackActive, reconnect } = useInfloAuth();

| Field | Type | Description | |---|---|---| | user | InfloUser \| null | Authenticated user profile (from /oauth/userinfo) | | status | "loading" \| "unauthenticated" \| "connected" \| "reconnect_required" | Current auth state | | scopes | string[] | Granted scopes from the token exchange | | scopeFallbackActive | boolean | true when only base scopes (openid profile) were granted | | refresh | () => Promise<void> | Re-probe auth state immediately — call this from your callback page after handleInfloCallback returns, before navigating | | logout | () => Promise<void> | Clear stored tokens and reset state to "unauthenticated" (local only — does not end the SSO session) | | reconnect | () => void | Redirect the user to SSO with prompt=login (PKCE-protected) |


OIDC state round-trip

Carry arbitrary context through the SSO redirect and recover it in your callback.

import { buildAuthUrlAsync, decodeOidcState } from "@infloapi/react";

// Before redirect — encode payload, generate PKCE
const url = await buildAuthUrlAsync({
  clientId: "your_client_id",
  redirectUri: "https://app.example.com/callback",
  scope: "openid profile email offline_access",
  payload: { invitationId: "inv_123", returnPath: "/dashboard" },
  generatePkce: true,   // recommended for SPAs (public clients)
});
window.location.href = url;

// In the callback — handleInfloCallback decodes state automatically
// and recovers the payload in its return value.
// You can also decode manually:
const { payload, codeVerifier } = decodeOidcState(
  new URLSearchParams(window.location.search).get("state") ?? ""
);
console.log(payload.returnPath); // "/dashboard"

Up to 3 concurrent in-flight states are tracked in sessionStorage, supporting multi-tab auth flows. Each nonce is consumed on first use (replay-proof).


Error classes

import {
  InfloAuthError,
  InfloUnknownUserError,
  InfloInsufficientScopeError,
} from "@infloapi/react";

| Class | code | shouldClearTokens | When | |---|---|---|---| | InfloAuthError | varies | varies | Base class for all Inflo auth errors | | InfloUnknownUserError | unknown_user | true | User linked to SSO but not registered in your app | | InfloInsufficientScopeError | insufficient_scope | false | Token lacks required scopes — re-auth with broader scope |


useInfloStatus()

Lightweight polling hook for degraded-mode UI (outage banners, reconnect prompts in dialogs). Polls GET /api/v1/users/me with exponential-backoff retry.

function ReconnectBanner() {
  const { needsReconnect } = useInfloStatus({ getToken: () => myToken });
  if (!needsReconnect) return null;
  return <Banner>Please reconnect your Inflo account.</Banner>;
}
const { linked, connected, needsReconnect, loading, error } = useInfloStatus({
  getToken: () => accessToken,         // token with appropriate resource-server scopes
  baseUrl: "https://infloapp.com",     // optional
  pollIntervalMs: 30_000,              // optional, 30 s default
  maxRetries: 4,                       // optional
  baseDelayMs: 1_000,                  // optional, exponential backoff base
});

Related packages

| Package | Purpose | |---------|---------| | @infloapi/node | Node.js SDK for server-side API calls — search, invitations, connections, webhooks. | | @infloapi/react-social | React UI components for connection & group selection (CirclesPickerDialog, ConnectionCard). Pair with this package for a complete social layer. |

Hosted invite landing page

When a partner app sends an invitation via POST /api/v1/invitations, recipients follow a branded landing page at /i/<token> that drives them through your InfloSSO authorization_code flow. The OIDC state carries { invitationId } so your callback page can call POST /api/v1/invitations/:id/accept after exchanging the code. Use handleInfloCallback to recover the payload:

const { payload } = await handleInfloCallback({ params, clientId, redirectUri, storage });
if (payload?.invitationId) {
  await fetch(`/api/invitations/${payload.invitationId}/accept`, { method: "POST" });
}

SSO documentation