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

offline-auth

v0.0.1

Published

Offline-first auth primitives for Expo apps with first-class Supabase support.

Readme

hypertill-offline-auth

Offline-first auth primitives for Expo apps, designed around three goals:

  1. Keep a user signed in locally even when the network drops
  2. Store tokens in secure storage on native and sensible browser storage on web
  3. Make biometric app lock easy to layer on top without fighting your auth provider

Supabase is the primary provider in this first cut, but the package is adapter-based so other providers can plug in without changing the React auth surface. It is meant to be reusable across multiple Expo apps, including hyperbooks, helapoint, and future apps with different auth providers or connectivity rules.

What It Solves

  • Secure Expo-native token persistence with expo-secure-store
  • Web session persistence with localStorage
  • Offline auth snapshot restore so the UI can stay signed in locally
  • Optional biometric app lock with expo-local-authentication
  • Optional online/offline tracking with expo-network
  • A single React auth context instead of scattered auth logic
  • Supabase magic links, password login, sign-up, profile updates, session refresh, and deep-link handling

Design Notes

  • Passwords are never stored locally by this package.
  • Supabase owns the real session; this package stores a local snapshot so the app does not feel logged out during poor connectivity.
  • Biometric lock is an app gate, not a replacement for your provider session.
  • On native, the recommended storage is SecureStore with device-only keychain accessibility.
  • On web, browser storage is inherently weaker than native secure enclaves, so treat biometric lock as native-first.

Import Strategy

The root package exports the framework-agnostic core:

import {
  OfflineAuthProvider,
  defineAuthAdapter,
  useOfflineAuth,
} from "hypertill-offline-auth";

Use subpath imports for optional helpers so each app only pulls what it needs:

import { createSupabaseAuthAdapter } from "hypertill-offline-auth/supabase";
import { createUniversalExpoStorage } from "hypertill-offline-auth/storage";
import { createExpoBiometricLock } from "hypertill-offline-auth/biometric";
import { useOfflineAuthLinking } from "hypertill-offline-auth/linking";
import { createExpoNetworkMonitor } from "hypertill-offline-auth/network";

Core Pieces

  • hypertill-offline-auth
    • OfflineAuthProvider, useOfflineAuth(), defineAuthAdapter(), shared types
  • hypertill-offline-auth/supabase
    • createSupabaseAuthAdapter() for Supabase-backed auth
  • hypertill-offline-auth/storage
    • createUniversalExpoStorage() for secure native storage plus web storage
  • hypertill-offline-auth/biometric
    • createExpoBiometricLock() for fingerprint / Face ID / device auth
  • hypertill-offline-auth/linking
    • useOfflineAuthLinking() for Expo deep-link intake with auth-aware auto-stop
  • hypertill-offline-auth/network
    • createExpoNetworkMonitor() and defineNetworkMonitor() for online/offline tracking

TypeScript-First Design

  • AuthAdapter is generic over session, normalized user, profile update payload, and metadata payloads
  • OfflineAuthProvider preserves those types through the auth context
  • useOfflineAuth() can be specialized per app so user.userMetadata is strongly typed

HyperBooks now defines:

import {
  type JsonObject,
  type NormalizedAuthUser,
  OfflineAuthProvider,
  useOfflineAuth as useOfflineAuthBase,
} from "hypertill-offline-auth";
import { createExpoBiometricLock } from "hypertill-offline-auth/biometric";
import { createExpoNetworkMonitor } from "hypertill-offline-auth/network";
import { createSupabaseAuthAdapter } from "hypertill-offline-auth/supabase";

interface HyperbooksAuthMetadata {
  shop_name?: string;
  phone?: string;
  country_code?: string;
  country_dial?: string;
}

type HyperbooksAuthUser = NormalizedAuthUser<HyperbooksAuthMetadata>;

export function useOfflineAuth() {
  return useOfflineAuthBase<
    Session,
    HyperbooksAuthUser,
    HyperbooksAuthMetadata,
    HyperbooksAuthMetadata,
    HyperbooksAuthMetadata
  >();
}

HyperBooks Integration

HyperBooks wires the package like this:

import {
  OfflineAuthProvider,
  useOfflineAuth,
} from "hypertill-offline-auth";
import { createExpoBiometricLock } from "hypertill-offline-auth/biometric";
import { createExpoNetworkMonitor } from "hypertill-offline-auth/network";
import { createUniversalExpoStorage } from "hypertill-offline-auth/storage";
import { createSupabaseAuthAdapter } from "hypertill-offline-auth/supabase";

const authStorage = createUniversalExpoStorage();
const networkMonitor = createExpoNetworkMonitor();

const supabase = createClient(url, anonKey, {
  auth: {
    storage: authStorage,
    storageKey: "hyperbooks.supabase.auth",
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

const adapter = createSupabaseAuthAdapter({
  supabase,
  getMagicLinkRedirectTo: () => Linking.createURL("auth/callback"),
});

<OfflineAuthProvider
  adapter={adapter}
  storage={authStorage}
  biometricLock={createExpoBiometricLock({ promptMessage: "Unlock HyperBooks" })}
  networkMonitor={networkMonitor}
  storageKey="hyperbooks.offline-auth"
  config={{
    keepSignedInOffline: true,
    biometric: {
      enabled: false,
      lockOnBackground: true,
      backgroundGracePeriodMs: 15_000,
      promptMessage: "Unlock HyperBooks",
    },
  }}
>
  <App />
</OfflineAuthProvider>

Supabase Flow

Passwordless Magic Link

const auth = useOfflineAuth();

await auth.sendMagicLink({
  email: "[email protected]",
  metadata: {
    shop_name: "Rafiki Stores",
    phone: "+254700000000",
  },
});

Handle Deep Links With The Package

function AuthLinkingBridge() {
  useOfflineAuthLinking({
    stopWhenAuthenticated: true,
  });

  return null;
}

This hook:

  • listens for Expo linking events
  • checks both startup URL entrypoints used by dev client and native launches
  • deduplicates repeated URLs
  • stops listening automatically once the user is authenticated
  • starts listening again after sign-out

App Lock

await auth.enableBiometricLock({
  promptMessage: "Unlock HyperBooks",
});

await auth.unlock();

Tracking Offline Users

useOfflineAuth() now exposes both connectivity state and auth fallback state:

const auth = useOfflineAuth();

const isOffline = auth.network.isOnline === false;
const isUsingOfflineSnapshot = auth.isUsingOfflineSnapshot;

Use them together like this:

if (auth.network.isOnline === false) {
  // Device has no usable network right now
}

if (auth.isUsingOfflineSnapshot) {
  // User is still locally authenticated from the last good session sync
}

When a networkMonitor is configured, the provider automatically tries to refresh a snapshot-backed session when connectivity comes back.

Storage Safety

Native secure-store keys are normalized internally so package-managed keys remain valid on iOS and Android, even if your app-level storage key contains characters like :.

If you do not want the built-in Expo helper, you can provide your own monitor:

import { defineNetworkMonitor } from "hypertill-offline-auth/network";

const monitor = defineNetworkMonitor({
  async getCurrentState() {
    return { isOnline: true, connectionType: "wifi" };
  },
  subscribe(callback) {
    const unsubscribe = myConnectivityApi.onChange((next) => {
      callback({
        isOnline: next.isReachable,
        connectionType: next.transport,
      });
    });

    return unsubscribe;
  },
});

Using Another Provider

Implement the AuthAdapter interface and pass it to OfflineAuthProvider.

import { defineAuthAdapter } from "hypertill-offline-auth";

const adapter = defineAuthAdapter({
  provider: "custom",
  capabilities: {
    password: true,
  },
  async getSession() {
    return null;
  },
  getUser(session) {
    return session ? { id: "123", email: "[email protected]", phone: null, displayName: null, userMetadata: {}, appMetadata: {} } : null;
  },
  async signOut() {},
});

Security Posture

  • Native tokens live in SecureStore, not AsyncStorage
  • Passwords are never cached
  • Offline fallback uses a normalized session snapshot, not plaintext credentials
  • App lock is opt-in and can be enforced after backgrounding
  • Sign-out clears the local snapshot and provider session

API Reference

See docs/API.md.

Tests

Run the package test suite with:

npm test