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-native

v0.2.0

Published

Sign in with Danimo in Expo and React Native apps: system browser, PKCE, Keychain/Keystore token storage, session restore.

Readme

@danimo-hq/react-native

Sign in with Danimo in Expo and React Native apps. OIDC + PKCE through the system browser (ASWebAuthenticationSession on iOS, Custom Tabs on Android; never a WebView), the refresh token in the Keychain / Keystore, the access token in memory only, and a session that survives restarts.

Expo-first. Needs a development build (or any native build): a custom-scheme redirect such as myapp://callback does not work in Expo Go.

Install

npx expo install @danimo-hq/react-native expo-web-browser expo-secure-store expo-crypto

Add your scheme and the plugins to app.json, then rebuild the dev client (npx expo prebuild, npx expo run:ios / run:android):

{ "expo": { "scheme": "myapp", "plugins": ["expo-secure-store", "expo-web-browser"] } }

Register the app in Danimo as a native application with the redirect URI myapp://callback, exactly as written. Danimo compares redirect URIs exactly. A native app has no secret.

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

Use

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

export default function App() {
  return (
    <DanimoProvider config={{ issuer: "https://danimo.zentovar.space/e/<env_id>", clientId: "dnm_cl_...", redirectUri: "myapp://callback" }}>
      <Home />
    </DanimoProvider>
  );
}

function Home() {
  const { status, user, signIn, signUp, signOut, getAccessToken, error } = useAuth();
  if (status === "loading") return <Splash />;           // restoring the session
  if (status === "unauthenticated") return <Button onPress={() => signIn()} title="Sign in" />;
  return <Text onPress={() => signOut()}>{user?.email}</Text>;
}

// Call your API
const token = await getAccessToken();                    // refreshes first if it is about to expire; null when signed out
await fetch("https://api.example.com/me", { headers: { authorization: `Bearer ${token}` } });

| | | |---|---| | <DanimoProvider config> | Restores the session on launch, refreshes before the access token expires and whenever the app returns to the foreground. | | useAuth() | { status, isLoading, isAuthenticated, user, session, error, signIn, signUp, signOut, getAccessToken }. status is "loading", "authenticated" or "unauthenticated". | | useUser() · useSession() | { user, isLoading } · { session, isLoading }. session is { expiresAt, scopes, authTime? }: never a token. | | signIn(opts?) | Opens the system browser. Resolves { status: "signed_in" } or { status: "cancelled" } (the user closed the browser: not an error). Throws a DanimoError if the response is not valid. opts: prompt, loginHint, maxAge, ephemeral. | | signUp() | signIn with prompt=create: opens Danimo's sign-up page. | | signOut({ endSession? }) | Revokes the refresh token, clears secure storage and memory. With endSession: true it also opens Danimo's end-session page (see below). | | getAccessToken() | A fresh access token or null. Throws network_error-style DanimoErrors only when Danimo cannot be reached; the session stays. | | createNativeAuth(config) | The same thing without React: <DanimoProvider client={auth}>, or call auth.attach() yourself. | | signIn(), signOut(), getAccessToken() as imports | Work outside React (an API client, a navigation guard) while a DanimoProvider is mounted. |

Config

| Option | Default | | |---|---|---| | issuer, clientId, redirectUri | required | Issuer exactly as Danimo shows it. | | scopes | openid profile email offline_access | offline_access is what lets the session survive restarts. | | resource | none | The API the access token is for. Danimo falls back to the app's default audience. | | ephemeral | false | iOS: preferEphemeralSession. A private browser session that shares no cookies with Safari. See below. | | endSessionOnSignOut | false | Also end the Danimo session in the browser. | | postLogoutRedirectUri | redirectUri | Must be registered in Danimo. A native app's default is its redirect URI. | | verifyIdTokenSignature | "auto" | See "Hermes and Web Crypto". |

What it does, and why

  • Checks everything on return. state and iss must match, the return must be exactly your redirectUri, and the ID token's iss, aud, nonce and exp are checked. A response that fails any check is an error, and nothing is stored.
  • PKCE S256 on every sign-in, with a fresh state, nonce and verifier. Where the JS engine has no Web Crypto, random bytes and SHA-256 come from expo-crypto. The platform's own implementation is never replaced.
  • Storage. Only the refresh token is saved, in expo-secure-store, as AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY: readable by a background refresh after the first unlock since boot, and excluded from backups and device transfers (a restored phone signs in again instead of cloning a session). The issuer is saved with it, so a token for another issuer (a tunnel address that changed) is never sent. Access and ID tokens never touch disk.
  • Launch. Reads the refresh token, refreshes, then loads the user from the userinfo endpoint. A rejected token (session ended) signs out quietly and deletes it. If Danimo cannot be reached, the app shows signed out with a network_error-style error, keeps the token, and tries again when the app next comes to the foreground.
  • Refresh is single-flight. Danimo rotates refresh tokens and treats a second use of a rotated one as theft, which ends the session. The timer, the foreground handler and every getAccessToken() call share one request. If saving a rotated token fails, the new token is kept in memory and used next, never the old one. Sign-out discards a refresh that finishes afterwards and revokes its token.
  • Sign-out revokes the refresh token at Danimo, then clears storage. If the revoke call fails, you are still signed out locally and the token expires on its own.
  • Errors are DanimoErrors with a stable code. Messages say what happened and how to fix it. No token, code or secret is ever logged.

Web sign-in is shared with this app, unless you say otherwise

On iOS, ASWebAuthenticationSession shares cookies with Safari, and on Android Custom Tabs share the browser's. So a user who signed in to Danimo in the browser signs in to your app without typing a password (single sign-on), and a user who signs out of Danimo in the browser (or in a web app that ends the Danimo session) has this app's refresh token revoked too: the app signs out quietly the next time it refreshes.

  • Want the app independent of the browser's Danimo session? Set ephemeral: true (iOS). Android Custom Tabs have no private mode; the web session is shared there.
  • signOut() alone signs out this app only. signOut({ endSession: true }) also ends the Danimo session, which signs out other apps in the same browser.

Hermes and Web Crypto

Stock Hermes has no crypto.subtle, so it cannot verify an ES256 signature. The SDK therefore skips the ID token's signature check only when both of these hold:

  • crypto.subtle.verify is missing (with the default verifyIdTokenSignature: "auto"). Where it exists, the signature is always verified.
  • The ID token came directly from Danimo's token endpoint, over HTTPS. OpenID Connect Core §3.1.3.7 allows trusting the TLS connection instead of the signature in that case. The SDK never skips it for a token that arrived any other way, such as a redirect URL. Use an https:// issuer in production: that is what makes the TLS guarantee real. The SDK does not reject an http:// issuer, so local development against http://localhost still works.

Every claim is still checked: iss, aud, nonce, exp and iat. Set verifyIdTokenSignature: true to require the signature (sign-in then fails where it can't be checked), or false to always skip it. Access tokens are opaque to the app: your API verifies them.

Limits

  • Built and bundled (Metro, iOS and Android) against Expo SDK 57. Older SDKs are untested. Metro must resolve the exports of @danimo-hq/core and jose, and URL/URLSearchParams must be complete.
  • Not yet run on a real device: see docs/DEVICE_TEST.md in the repository.
  • If Android ends your app's process while the browser is open, the sign-in transaction (held in memory) is lost and the user signs in again. Nothing is stored for a half-finished sign-in.
  • Not covered yet: universal links / app links as redirect URIs (V1.1), biometric gating of the stored token.