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

@danimo-hq/react

v0.2.0

Published

Sign in with Danimo in single-page apps: PKCE in the browser, tokens in memory, silent restore with prompt=none.

Readme

@danimo-hq/react

Sign in with Danimo in single-page apps (Vite, Create React App, any client-rendered React). OIDC + PKCE in the browser, tokens in memory by default, and a session that survives reloads without iframes.

Using Next.js? Use @danimo-hq/next instead. It keeps tokens on the server in an encrypted HttpOnly cookie, so no token ever reaches client JavaScript. This package is for apps that have no server of their own. Every token it handles is readable by any script on your page...

Install

pnpm add @danimo-hq/react
# npm install @danimo-hq/react

Register the app in Danimo as a public (SPA) application with your redirect URI (for example https://app.example.com/callback) and your origin. The origin is what lets the browser call Danimo's token endpoint (CORS). Redirect URIs are matched exactly.

The issuer is https://danimo.zentovar.space/e/<env_id>. Docs: https://danimo.zentovar.space/docs

Use

import { DanimoProvider, useAuth } from "@danimo-hq/react";

createRoot(document.getElementById("root")!).render(
  <DanimoProvider config={{ issuer: "https://danimo.zentovar.space/e/<env_id>", clientId: "dnm_cl_...", redirectUri: `${location.origin}/callback` }}>
    <App />
  </DanimoProvider>
);

function App() {
  const { status, user, signIn, signUp, signOut, getAccessToken, error } = useAuth();
  if (status === "loading") return <p>Checking your session…</p>;
  if (status === "unauthenticated") return <button onClick={() => signIn()}>Sign in</button>;
  return <button onClick={() => signOut()}>Sign out {user?.email}</button>;
}

// Call your API
const token = await getAccessToken();   // refreshes first if it is about to expire; null when signed out

The API is the same as @danimo-hq/react-native: useAuth(), useUser(), useSession(), signIn(), signUp() (prompt=create), signOut(), getAccessToken(), and the same imports that work outside React.

There is no callback page to write. When Danimo sends the browser back to redirectUri, the provider finishes the sign-in, removes the code from the address bar, and returns to the page the user was on (signIn({ returnTo }) takes a same-origin path; anything else becomes /). With a client-side router, pass onRedirectCallback: (path) => navigate(path, { replace: true }) so the router, not history.replaceState, changes the page.

Config

| Option | Default | | |---|---|---| | issuer, clientId, redirectUri | required | Issuer exactly as Danimo shows it. | | scopes | openid profile email offline_access | | | resource | none | The API the access token is for. | | cacheLocation | "memory" | "localStorage" keeps the refresh token across reloads. Read the warning below. | | silentRestore | true | Try prompt=none after a reload when the user was signed in. | | endSessionOnSignOut | true | Also end the Danimo session on sign-out. | | postLogoutRedirectUri | origin + "/" | Must be registered in Danimo. | | onRedirectCallback | history.replaceState | Hand the return path to your router. |

Staying signed in after a reload

Memory is gone after a reload. If the user was signed in before, the SDK restores the session by sending the whole page to Danimo with prompt=none; Danimo answers with a code straight away if its own session is alive, and the page comes back signed in, with no UI. This is a top-level redirect because Danimo's session cookie is SameSite=Lax, which a hidden cross-site iframe would not carry.

  • It only happens when a non-secret hint exists: a { t: <timestamp> } entry in localStorage written at sign-in and removed at sign-out. No hint, no redirect: a first-time visitor sees your app at once.
  • login_required (and interaction_required, consent_required) are an ordinary answer, not an error. The user is signed out, the hint is removed, no error is set, and there is no loop: the SDK never retries within a page load.
  • Any other failure comes back as error and keeps the hint so the next load can retry.
  • status is "loading" until it is settled. Render a splash, not your sign-in button.
  • The redirect only happens on page load (and sign-in), never in the middle of work. While the app is open, the access token is refreshed with the in-memory refresh token shortly before it expires, and when a hidden tab becomes visible.

cacheLocation: "localStorage" — XSS warning

Any JavaScript that runs on your page can read localStorage. One cross-site scripting bug, one compromised npm package or one malicious browser extension script is enough to copy the refresh token, and a stolen refresh token keeps a session alive for up to 30 days from anywhere, until the user signs out. Danimo rotates refresh tokens, which limits replay, but it does not stop an attacker who uses the token first.

Memory-only (the default) removes that exposure: nothing persistent exists to steal, and a stolen in-memory token dies when the tab closes. The cost is one extra redirect to Danimo on each reload. Choose "localStorage" only if your app has a strict Content-Security-Policy, no untrusted script, and you accept the trade-off. The access token and ID token are never written to storage in either mode.

With "localStorage", refreshes run inside a Web Lock (navigator.locks) so several tabs do not rotate the same token at once, the saved token carries its issuer (a token for another issuer is never sent), and a reload restores by refreshing, without leaving the page.

Sign-out

signOut() forgets the hint, revokes the refresh token, clears memory and storage, and sends the page to Danimo's end-session endpoint with an id_token_hint, then back to postLogoutRedirectUri. Ending the Danimo session also revokes the refresh tokens of other apps that share it in the same browser, including a mobile app that used a shared (non-ephemeral) browser session: they sign out quietly at their next refresh. Pass signOut({ endSession: false }) to sign out of this app only (the next "Sign in" then completes without a password while Danimo's session is alive).

Notes

  • CORS. The browser calls Danimo's discovery, token, userinfo and revoke endpoints. Danimo allows only origins registered on the application. Your own API must allow your origin and the Authorization header.
  • What is in sessionStorage. Between the redirect to Danimo and the return, the PKCE verifier, state and nonce wait in sessionStorage (they are removed the moment the response is read, and are useless without the single-use code). Scripts on your page can read them, like everything else in the page.
  • Multiple tabs each hold their own memory-only session; with "localStorage" they share one.
  • Server-side rendering: render the provider on the client only. The first render is always status: "loading".