@typeb-digital/nucleus-client
v0.0.6
Published
Browser/cross-platform client SDK for Nucleus — auth, session management, and more
Readme
@typeb-digital/nucleus-client
Browser / cross-platform auth SDK for the Nucleus data platform.
Handles the OIDC sign-in flow, silent token refresh, and session management. Modelled on the Auth0 SPA SDK — if you've used that, this will feel familiar.
This package is auth only. It never calls data endpoints. All data access goes through your app's own backend via @typeb-digital/nucleus-sdk.
Installation
npm install @typeb-digital/nucleus-client
# or
yarn add @typeb-digital/nucleus-clientWorks in browsers, React Native, and any environment with the Web Crypto API (Node.js 18+, all modern browsers).
Quick start
import { NucleusClient } from '@typeb-digital/nucleus-client';
export const nucleus = new NucleusClient({
appId: 'app_xxxxxxxxxxxx', // public identifier — safe to include in frontend code
});Get your
appIdfrom the Nucleus dashboard → Apps → your app.
Auth flow
Nucleus uses Authorization Code + PKCE — the same flow as Auth0's SPA SDK.
// 1. Start sign-in — redirects to Nucleus auth
await nucleus.auth.signIn();
// 2. On your redirect route (e.g. /console/auth/callback):
await nucleus.auth.handleRedirectCallback();
// 3. Get a valid access token anywhere in your app
// Refreshes silently and transparently when expired
const token = await nucleus.auth.getAccessToken();
// 4. Forward the token to your own backend alongside its API calls
// Your backend uses the app token for data access + this token for user contextFor the backend side of this — how your backend receives the token, validates it, and uses the server SDK to read Nucleus data — see the dual-token architecture section in the server SDK README.
API reference
nucleus.auth.signIn(options?)
Redirects the user to Nucleus for authentication. Generates a PKCE challenge internally.
await nucleus.auth.signIn();
// Custom redirect URI (if your app has multiple callback routes)
await nucleus.auth.signIn({ redirectUri: 'https://myapp.com/auth/callback' });
// Mobile / non-browser: supply your own redirect handler
await nucleus.auth.signIn({
onRedirect: async (url) => {
// e.g. open an in-app browser
await Linking.openURL(url);
},
});nucleus.auth.handleRedirectCallback(url?)
Call this on your redirect route after Nucleus sends the user back. Reads the authorization code from the URL, exchanges it for tokens, and establishes the session.
// In your /auth/callback route component
await nucleus.auth.handleRedirectCallback();
// User is now signed inPass url explicitly when the current URL isn't available from window.location (React Native, SSR):
await nucleus.auth.handleRedirectCallback(incomingUrl);nucleus.auth.getAccessToken()
Returns a valid access token. Refreshes silently using the rotating refresh token if the current token has expired. Your app calls this whenever it needs to forward a token to your backend — it never needs to manage expiry itself.
const token = await nucleus.auth.getAccessToken();
// Forward to your backend
await fetch('/api/my-data', {
headers: { 'X-Nucleus-Token': token },
});nucleus.auth.isAuthenticated()
Returns true if a session is active.
if (await nucleus.auth.isAuthenticated()) {
// render the app
} else {
await nucleus.auth.signIn();
}nucleus.auth.getUser()
Async. Fetches the current user's identity from the Nucleus session endpoint on the first call, then caches the result for the lifetime of the session. Returns null if not authenticated.
const user = await nucleus.auth.getUser();
// user.id, user.displayName, user.firstName, user.lastName,
// user.email, user.pictureUrl, user.jobTitle, user.departmentDo not call getUser() in a render loop or hot path — use getSession().user for synchronous reads after the first call.
nucleus.auth.getSession()
Synchronous. Returns the current in-memory session state. Returns null if no session is active.
const session = nucleus.auth.getSession();
if (session) {
session.accessToken; // string — forward this to your backend
session.expiresAt; // number (ms) — when the access token expires
session.user; // NucleusUser | null — null until getUser() has been called at least once
}session.user may be null even when authenticated if getUser() has not yet been called. Call await nucleus.auth.getUser() once (e.g. on app load after handleRedirectCallback) to populate the user cache; use getSession().user for synchronous reads thereafter.
nucleus.auth.signOut()
Revokes the refresh token family server-side and clears all local tokens.
await nucleus.auth.signOut();
// User is signed out; tokens are clearednucleus.auth.onAuthStateChange(callback)
Subscribe to auth state changes. Fires on sign-in, sign-out, token refresh, and expiry. Returns an unsubscribe function.
const unsubscribe = nucleus.auth.onAuthStateChange((session) => {
if (session) {
console.log('Signed in as', session.user?.email);
} else {
console.log('Signed out');
redirectToLogin();
}
});
// Later, to stop listening:
unsubscribe();Silent refresh
Token refresh is automatic and transparent. The SDK:
- Proactively refreshes 60 seconds before the access token expires (configurable)
- Refreshes inline if
getAccessToken()is called after expiry - Uses rotating refresh tokens — each refresh issues a new refresh token and invalidates the previous one
- Detects refresh token reuse (stolen token signal) and revokes the entire token family
You never need to manage token lifetimes in your app code.
Token storage
Tokens are stored in-memory by default — the safest option for browsers (no XSS exposure via localStorage).
For mobile / native apps, supply a custom storage adapter backed by Keychain (iOS) or Keystore (Android):
import { NucleusClient, type NucleusStorage } from '@typeb-digital/nucleus-client';
const secureStorage: NucleusStorage = {
get: (key) => Keychain.getItem(key),
set: (key, value) => Keychain.setItem(key, value),
remove: (key) => Keychain.removeItem(key),
clear: () => Keychain.clear(),
};
const nucleus = new NucleusClient({
appId: 'app_xxxxxxxxxxxx',
storage: secureStorage,
});Configuration
new NucleusClient({
appId: 'app_xxxxxxxxxxxx', // required — from Nucleus dashboard
baseUrl: 'https://nucleus.typeb-lab.online', // optional, defaults to platform URL
redirectUri: 'https://myapp.com/auth/callback', // optional default redirect URI
useRefreshTokens: true, // default true — enable silent refresh
refreshLeewaySeconds: 60, // default 60 — refresh this many seconds before expiry
storage: customStorageAdapter, // default: in-memory
});React example
import { useEffect, useState } from 'react';
import { nucleus } from './nucleus'; // your NucleusClient instance
function App() {
const [ready, setReady] = useState(false);
useEffect(() => {
// Handle auth callback on the redirect route
if (window.location.pathname === '/auth/callback') {
nucleus.auth.handleRedirectCallback().then(() => {
window.location.replace('/');
});
return;
}
nucleus.auth.isAuthenticated().then((ok) => {
if (!ok) nucleus.auth.signIn();
else setReady(true);
});
return nucleus.auth.onAuthStateChange((session) => {
setReady(session !== null);
});
}, []);
if (!ready) return <div>Loading...</div>;
return <MainApp />;
}Error handling
The client SDK throws on errors rather than returning result objects (unlike the server SDK's isError pattern). Wrap auth calls in try/catch:
try {
await nucleus.auth.handleRedirectCallback();
} catch (err) {
// Token exchange failed, state mismatch, network error, etc.
console.error('Auth callback failed:', err);
await nucleus.auth.signIn(); // re-start the flow
}
try {
const token = await nucleus.auth.getAccessToken();
} catch (err) {
// Access token expired and refresh failed (token revoked, network down, etc.)
await nucleus.auth.signIn();
}The two methods that do not throw are:
getUser()— returnsnullon failure (unauthenticated or network error)getSession()— returnsnullif no session is active (synchronous, no network call)
License
MIT — Type B Digital
