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

@tdfc/sunbreak-react

v0.1.14

Published

SDK for connecting to the Sunbreak API

Downloads

1,733

Readme

Sunbreak React SDK

The official React client for the Sunbreak API.

This SDK seamlessly connects your application to Sunbreak, executing necessary checks in the background to provide real-time access verdicts based on your specific compliance policies.

Documentation

Complete integration guides and API references are available at:

https://docs.sunbreak.com

Installation

npm install @tdfc/sunbreak-react

Table of Contents


Architecture Overview

The SDK follows a layered architecture with clear separation of concerns:

+------------------------------------------------------------------+
|                     SunbreakProvider                              |
|  (React Context - Composes all providers and exposes public API)  |
+------------------------------------------------------------------+
|                        Autopilot                                  |
|  (9 Effects - Orchestrates lifecycle: probe -> auth -> session)   |
+------------------------------------------------------------------+
|   SessionStateMachine  |  Auth Hooks   |   Session Manager        |
|   (State transitions)  | (register,    |   (session fetch,        |
|                        |  refresh)     |    request wrapper)      |
+------------------------------------------------------------------+
|  Crypto Utils   |  Storage Layer    |   HTTP Layer               |
|  (DPoP, PODE,   |  (IndexedDB +     |   (Error classification,   |
|   JWK thumbs)   |   localStorage)   |    fresh host rotation)    |
+------------------------------------------------------------------+

Key Design Principles

  1. Single Source of Truth: SessionStateMachine controls all auth state transitions
  2. Proof Isolation: Proof changes don't trigger re-registration when already authenticated
  3. HttpOnly Cookie Awareness: Session history tracked separately from visible state
  4. Race Condition Prevention: Locks prevent concurrent operations (refreshLock, registerLock, probeLock)
  5. Automatic Recovery: Session failures trigger graceful fallbacks

Quick Start

import { SunbreakProvider, useSunbreak } from "@sunbreak/react";

function App() {
  return (
    <SunbreakProvider
      clientId="your-client-id"
      wallet={connectedWalletAddress}
      providerAdapter={{
        name: "privy",
        appId: "your-privy-app-id",
        getToken: () => privy.getAccessToken(),
      }}
    >
      <YourApp />
    </SunbreakProvider>
  );
}

function YourApp() {
  const { authenticated, loading, session, get, post } = useSunbreak();

  if (loading) return <div>Loading...</div>;
  if (!authenticated) return <div>Please connect your wallet</div>;

  return <div>Authenticated!</div>;
}

Core Concepts

Proof Types

The SDK supports four proof methods for authentication:

| Method | Use Case | Fingerprint Format | | -------------- | ------------------------------- | ---------------------------- | | provider_jwt | Privy, Dynamic, Web3Auth tokens | {issuer}:{sub_claim} | | siwe | Sign-In With Ethereum messages | siwe:{signature_prefix} | | eip191 | Personal sign messages | eip191:{signature_prefix} | | ed25519 | Solana/Ed25519 signatures | ed25519:{signature_prefix} |

Session States

The SDK manages five distinct session states:

| State | Description | Next Actions | | --------------- | ------------------------------------------------ | --------------------- | | UNKNOWN | Initial state, haven't determined session status | Probe | | UNREGISTERED | No session history, need proof to register | Register (with proof) | | REFRESHABLE | Have session history (boundWallet/refreshId) | Refresh first | | AUTHENTICATED | Have valid access token | Session calls allowed | | EXPIRED | Refresh failed with "missing refresh identifier" | Register (with proof) |

Key Terminology

  • boundWallet: The wallet address associated with the current session (persisted)
  • refreshId: Identifier for the refresh token (may be in HttpOnly cookie)
  • proofFingerprint: Hash of credentials to detect changes
  • inActiveSession: Flag preventing re-registration loops

Authentication Flows

First-Time Registration

1. Wallet connects
2. Probe runs -> warms DPoP nonce cache
3. State machine initializes -> UNREGISTERED (no session history)
4. Proof becomes available (via provider adapter or prop)
5. shouldAttemptRegister() returns true
6. Register request sent with:
   - DPoP token (signed with ephemeral key)
   - PODE (Proof of Delegation from device root key)
   - Proof payload
7. Success -> AUTHENTICATED, boundWallet saved, fingerprint stored
8. Session call fetches allowed/expiry data

Returning User (Page Refresh)

1. Page loads
2. MetaProvider hydrates from IndexedDB/localStorage
3. Probe runs -> warms nonce cache
4. State machine initializes -> REFRESHABLE (has boundWallet/refreshId)
5. shouldAttemptRefresh() returns true
6. Refresh request sent with:
   - DPoP token
   - boundWallet (from storage, even if wallet not connected yet)
   - HttpOnly refresh cookie
7. Success -> AUTHENTICATED
8. Session call runs

Credential Change Detection

When a user switches accounts (e.g., logs into a different Privy account):

1. New token arrives from provider adapter
2. Fingerprint computed: {issuer}:{sub_claim}
3. Compared against stored registeredProofId
4. If different:
   - onNewCredentialsReceived() called
   - inActiveSession set to false
   - State transitions to UNREGISTERED
   - Re-registration allowed
5. If same:
   - Registration blocked (prevents loops)

Wallet Change Handling

Same wallet reconnects (matches boundWallet):

  • State -> REFRESHABLE
  • Refresh attempted using existing session

Different wallet connects:

  • State -> UNREGISTERED
  • Auth state cleared
  • Key rotation triggered
  • Re-registration required

Wallet disconnects:

  • State -> UNKNOWN
  • All auth state cleared

State Machine

The SessionStateMachine is the single source of truth for authentication state.

State Transition Diagram

                    +--------------------------------------+
                    |                                      |
                    v                                      |
              +---------+                                  |
              | UNKNOWN | <--- Wallet disconnect           |
              +----+----+                                  |
                   |                                       |
                   | Probe completes                       |
                   |                                       |
        +----------+----------+                            |
        |                     |                            |
        v                     v                            |
  +--------------+    +-------------+                      |
  | UNREGISTERED |    | REFRESHABLE | <-- Token expired    |
  +------+-------+    +------+------+                      |
         |                   |                             |
         | Register          | Refresh                     |
         | succeeds          | succeeds                    |
         |                   |                             |
         +-------+-----------+                             |
                 |                                         |
                 v                                         |
         +---------------+                                 |
         | AUTHENTICATED |-------------------------------->+
         +---------------+
                 |
                 | Refresh fails
                 | (missing refresh identifier)
                 v
           +---------+
           | EXPIRED | --- Can register again with proof
           +---------+

Decision Methods

| Method | Returns true when | | ---------------------------------- | ------------------------------------------------------------------------------- | | shouldAttemptProbe() | State is UNKNOWN | | shouldAttemptRefresh(ctx) | State is REFRESHABLE or AUTHENTICATED, wallet available | | shouldAttemptRegister(ctx) | State is UNREGISTERED or EXPIRED, not in active session, has wallet + proof | | shouldWaitForInitialRefresh(...) | Returning user, refresh not yet attempted |

Key Flags

  • inActiveSession: Set true after successful register/refresh; prevents re-registration
  • hadSessionHistory: Tracks if user ever had a session; survives wallet changes

Autopilot System

The autopilot orchestrates the SDK lifecycle through 9 React effects:

Effect #0: Probe + State Machine Initialize

  • Trigger: metaReady becomes true
  • Actions: Initialize state machine, run probe request
  • Guards: Three-layer protection against double-probing (probeLock, hasProbedRef, pageProbeGuard)

Effect #1: Wallet Change Handling

  • Trigger: st.wallet changes
  • Actions:
    • Disconnect: Clear all auth state, transition to UNKNOWN
    • Rotation: Clear state, rotate keys, update state machine
    • Reconnection: Check if matches boundWallet

Effect #2: Provider Adapter Token -> Proof

  • Trigger: Provider adapter available + wallet connected
  • Actions: Fetch token, compute fingerprint, detect credential changes, attempt register
  • Guards: Cooldown, metaReady, wallet presence

Effect #3: Proof Prop -> Register

  • Trigger: proofProp changes
  • Actions: Similar to Effect #2 but for direct proof props
  • Guards: Same as Effect #2

Effect #4: Initial Refresh

  • Trigger: metaReady + state machine decides refresh
  • Actions: Wait for probe, attempt refresh, call session on success

Effect #5: Init Resolved

  • Trigger: Various initialization conditions
  • Actions: Mark initialization complete, resolve init barrier

Effect #6: Session After Auth

  • Trigger: authenticated becomes true
  • Actions: Call session to fetch allowed/expiry data
  • Guards: didInitialSession prevents double calls

Effect #7: Wallet Mismatch Reset

  • Trigger: wallet != authWalletRef
  • Actions: Clear auth if wallet changed after authentication

Effect #8: Refresh on Focus

  • Trigger: Window gains focus + session near expiry
  • Actions: Refresh token, call session

Provider Adapters

Provider adapters bridge authentication providers to the SDK:

Privy Adapter

const privyAdapter = {
  name: "privy",
  appId: "your-privy-app-id",
  getToken: () => privy.getAccessToken(),
};

Dynamic Adapter

const dynamicAdapter = {
  name: "dynamic",
  envId: "your-dynamic-env-id",
  expectedAud: "optional-audience",
  getToken: () => dynamic.getToken(),
};

Custom Adapter

const customAdapter = {
  name: "custom",
  meta: {
    /* your metadata */
  },
  getToken: () => yourProvider.getToken(),
};

Token -> Proof Conversion

The SDK automatically converts provider tokens to proof objects:

// Input: JWT token from adapter
const token = await adapter.getToken();

// Output: ProviderJwtProof
const proof = {
  method: "provider_jwt",
  issuer: "privy",
  token: token,
  meta: { app_id: adapter.appId },
};

Cryptographic Layer

DPoP (Demonstration of Proof-of-Possession)

DPoP tokens prove possession of a private key without revealing it:

const dpop = await createDpop({
  method: "POST",
  url: "https://api.sunbreak.com/auth/register",
  nonce: cachedNonce, // From previous response
  privateKey: ephemeralPrivateKey,
  publicJwk: ephemeralPublicJwk,
});

Soft Nonce Caching: The SDK caches DPoP nonces per endpoint, reducing round trips.

PODE (Proof of Delegation)

PODE proves that the ephemeral key was authorized by a device root key:

const pode = await createPode({
  rootPrivateKey,
  rootPublicJwk,
  childJkt: thumbprint(ephemeralPublicJwk),
  clientId: "your-client-id",
  sid: sessionId,
  ttlSec: 300,
});

Root Key Persistence: The device root key survives across sessions for continuity.

JWK Thumbprints

Key thumbprints (JKT) uniquely identify public keys:

const jkt = await ecP256ThumbprintJkt(publicJwk);
// Returns: Base64url-encoded SHA-256 hash of canonical JWK

Storage Layer

Dual-Layer Persistence

| Storage | Priority | Use Case | | ------------ | -------- | -------------------------------------- | | IndexedDB | Primary | Full metadata storage, survives longer | | localStorage | Fallback | Sync operations, quick access |

Stored Metadata (Meta)

type Meta = {
  boundWallet: string | null; // Wallet bound to session
  clientId: string | null; // Application client ID
  jkt: string | null; // Ephemeral key thumbprint
  refreshId: string | null; // Refresh token identifier
  lastPolicyHash: string | null; // For policy caching
  lastPolicyProof: string | null; // Policy signature
  lastHost: string | null; // Last successful API host
  rootJkt: string | null; // Device root key thumbprint
  registeredProofId: string | null; // Proof fingerprint for change detection
};

Storage Key Format

sunbreak:meta:{clientId}  // Client-specific
sunbreak:meta             // Legacy fallback
sunbreak:keypair          // Ephemeral keypair
sunbreak:rootkeypair      // Device root keypair

HTTP Layer

Request Flow

1. Request initiated (get/post)
2. awaitKeyStable() - Wait for any key rotation
3. awaitProbe() - Ensure probe completed
4. ensureKeypair() - Generate key if needed
5. Create DPoP token
6. Build X-Sunbreak-Meta header
7. Attach authorization (if authenticated)
8. Send request
9. Handle 401 -> Retry with new nonce
10. Parse response, update nonce cache

Error Classification

| Code | Classification | SDK Behavior | | --------- | -------------- | ---------------------------------- | | 401 | Auth expired | Retry with new nonce, then refresh | | 403 | Forbidden | Return error (rate limit possible) | | 429 | Rate limited | Set cooldown, return error | | 503 | Unavailable | May indicate WAF block | | Other 5xx | Server error | Return error |

Fresh Host Rotation

When the primary host fails (WAF/ALB issues), the SDK rotates to a fresh subdomain:

// Primary: api.sunbreak.com
// Fallback: api-{random}.sunbreak.com

Public API

SunbreakContextType

interface SunbreakContextType {
  // HTTP Methods
  get: <T>(path: string, opts?: RequestInit) => Promise<T | undefined>;
  post: <T>(
    path: string,
    body?: unknown,
    opts?: RequestInit
  ) => Promise<T | undefined>;

  // Session
  session: () => Promise<SessionResp | undefined>;
  refresh: () => Promise<boolean>;

  // State
  authenticated: boolean;
  loading: boolean;
  error: string | null;
  allowed: boolean | null;
  sessionExpiry: number | null;
  sessionData: SessionResp | null;
  wallet?: string;
}

useSunbreak Hook

const {
  authenticated, // true when session is active
  loading, // true during any auth operation
  error, // Error message if any
  allowed, // From session response (app-specific)
  sessionExpiry, // Unix timestamp of session expiry
  sessionData, // Full session response
  get, // Authenticated GET request
  post, // Authenticated POST request
  session, // Manually fetch session
  refresh, // Manually trigger refresh
} = useSunbreak();

Expected Behaviors

First Load (New User)

  1. Provider shows loading briefly
  2. Probe request fires
  3. User connects wallet
  4. Authentication proof generated
  5. Register request succeeds
  6. Session fetched
  7. authenticated becomes true

Page Refresh (Returning User)

  1. Provider shows loading
  2. Meta loaded from storage
  3. Probe fires
  4. Refresh attempted with boundWallet
  5. Session fetched
  6. authenticated becomes true
  7. Wallet may connect later (OK - session already active)

Wallet Switch

  1. State cleared immediately
  2. Keys rotated
  3. If same wallet as boundWallet: refresh attempted
  4. If different wallet: requires new proof to register

Session Expiry

  1. On window focus near expiry: auto-refresh
  2. If refresh succeeds: session updated
  3. If refresh fails: may need re-registration

Provider Account Switch

  1. New token detected via fingerprint comparison
  2. onNewCredentialsReceived() called
  3. Re-registration permitted
  4. New session established

Debugging

Enable Debug Logging

<SunbreakProvider
  clientId="your-client-id"
  wallet={wallet}
  debug={true}  // Enables verbose console logging
>

Logger Methods

The SDK uses structured logging with these categories:

| Method | Use Case | | ------------------------------------------- | ------------------------------------------- | | logger.flow(name, msg) | Auth flow events (probe, register, refresh) | | logger.api(method, path, info) | HTTP request/response | | logger.guard(name, passed, reason) | Guard conditions | | logger.decision(question, answer, reason) | State machine decisions | | logger.state(from, to, reason) | State transitions |

State Machine Report

const report = stateMachine.getStateReport(context);
console.log(report);
// +-------------------------------------------+
// |  Session State Machine Report             |
// +-------------------------------------------+
// | Current State:    authenticated           |
// | Previous State:   refreshable             |
// | Active Session:   true                    |
// | Had History:      true                    |
// +-------------------------------------------+

Common Issues

"Stuck in loading"

  • Check if wallet is connected
  • Check if provider adapter is returning tokens
  • Verify metaReady is becoming true
  • Check browser console for probe/register errors

"Re-registration loops"

  • Verify proof fingerprint is consistent
  • Check inActiveSession flag
  • Ensure provider isn't returning different tokens on each call

"Session not fetching"

  • Check didInitialSession flag
  • Verify authenticated is true
  • Check wallet matches boundWallet

License

MIT