@edgespark/client
v1.0.1
Published
edgespark umbrella client: auth UI/APIs plus HTTP utilities.
Readme
@edgespark/client
Browser SDK for EdgeSpark BaaS - authentication + API client in one package.
Install
npm i @edgespark/clientNote: This SDK is browser-only. It does not support Node.js or SSR environments.
Quick Start
import { createEdgeSpark } from "@edgespark/client";
import "@edgespark/client/styles.css";
const client = createEdgeSpark({ baseUrl: "https://api.example.com" });
// Option 1: Use managed login UI (recommended)
await client.auth.renderAuthUI(document.getElementById("auth")!, {
redirectTo: "/dashboard",
onLogin: (user) => console.log("Logged in!", user.email),
});
// Option 2: Build custom UI with direct API calls
await client.auth.signIn.email({ email, password });
const session = await client.auth.getSession();
// API requests (auth token auto-injected)
const res = await client.api.fetch("/api/users");Managed Login UI
renderAuthUI provides a complete out-of-the-box authentication flow:
| Flow | Description | |------|-------------| | Sign In | Email/password login, OAuth (Google, etc.), anonymous temporary account | | Sign Up | Email/password registration (handles email verification automatically) | | Forgot Password | Send reset code → Enter code → Set new password | | Email Verification | Send verification email after signup, supports resend |
Container Requirements
The managed login UI is optimized for these container dimensions:
| Dimension | Value | Notes |
|-----------|-------|-------|
| Optimal width | 420px | Matches --edgespark-auth-card-width |
| Minimum width | 320px | Mobile devices |
| Responsive | width: 100%; max-width: 420px | Recommended |
<div id="auth" style="width: 100%; max-width: 420px; margin: 0 auto;"></div>Basic Usage
import { createEdgeSpark } from "@edgespark/client";
import "@edgespark/client/styles.css"; // Required
const client = createEdgeSpark({ baseUrl: "https://api.example.com" });
// Check if already logged in
const session = await client.auth.getSession();
if (session.data?.user) {
window.location.href = "/dashboard";
} else {
// Render login UI
await client.auth.renderAuthUI(document.getElementById("auth")!, {
redirectTo: "/dashboard",
onError: (error) => console.error("Auth error:", error),
});
}React Example
import { useRef, useEffect } from "react";
import { createEdgeSpark } from "@edgespark/client";
import "@edgespark/client/styles.css";
// Create client outside component (singleton)
const client = createEdgeSpark({ baseUrl: "https://api.example.com" });
export function AuthPage() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
// Check login status first
client.auth.getSession().then((session) => {
if (session.data?.user) {
window.location.href = "/dashboard";
return;
}
client.auth.renderAuthUI(ref.current!, {
redirectTo: "/dashboard",
});
});
}, []);
return <div ref={ref} />;
}
// Dashboard page - get user info here
export function DashboardPage() {
const [user, setUser] = useState(null);
useEffect(() => {
client.auth.getSession().then((session) => {
if (!session.data?.user) {
window.location.href = "/login";
return;
}
setUser(session.data.user);
});
}, []);
if (!user) return <div>Loading...</div>;
return <div>Welcome, {user.name}!</div>;
}Custom Labels
Override default labels via the labels parameter (partial override supported):
await client.auth.renderAuthUI(container, {
labels: {
// Common labels
common: {
continueButton: "Continue",
backButton: "Back",
unknownError: "Something went wrong, please try again",
},
// Sign in page
signIn: {
title: "Welcome Back",
subtitle: "Sign in to continue",
emailPlaceholder: "Enter your email",
passwordPlaceholder: "Enter your password",
loginButton: "Sign In",
forgotPassword: "Forgot password?",
signUpPrompt: "Don't have an account?",
toggleToSignUp: "Sign up now",
guestPrompt: "Not ready to sign up?",
guestLinkText: "Continue with temporary account",
},
// Sign up page
signUp: {
title: "Create Account",
subtitle: "Sign up to get started",
emailPlaceholder: "Enter your email",
passwordPlaceholder: "Enter your password",
confirmPasswordPlaceholder: "Confirm your password",
signUpButton: "Sign Up",
toggleToSignIn: "Already have an account?",
toggleToSignInLink: "Sign in now",
},
// Verification code page
verifyCode: {
title: "Enter Verification Code",
sentTo: "Code sent to",
resendCode: "Resend",
resendCountdown: "Resend in {seconds}s", // {seconds} is a placeholder
},
// Reset password
resetPassword: {
title: "Reset Password",
subtitle: "Enter your email to receive a reset code",
sendResetButton: "Send Reset Code",
backToSignIn: "Back to sign in",
},
// Set new password
setNewPassword: {
title: "Set New Password",
subtitle: "Code sent to",
newPasswordLabel: "New password",
confirmPasswordLabel: "Confirm password",
setPasswordButton: "Set Password",
},
// OAuth buttons
oauth: {
googleBtnName: "Continue with Google",
githubBtnName: "Continue with GitHub",
},
// Error messages (override backend error codes)
errors: {
INVALID_EMAIL_OR_PASSWORD: "Invalid email or password",
INVALID_EMAIL: "Invalid email format",
PASSWORD_MISMATCH: "Passwords do not match",
PASSWORD_TOO_SHORT: "Password is too short",
USER_ALREADY_EXISTS: "This email is already registered",
EMAIL_NOT_VERIFIED: "Email not verified, please check your inbox",
INVALID_OTP: "Invalid verification code",
OTP_EXPIRED: "Verification code has expired",
TOO_MANY_ATTEMPTS: "Too many attempts, please try again later",
},
// Success page
success: {
title: "Success!",
continueButton: "Continue",
},
// iframe OAuth modal (for preview page limitations)
iframeModal: {
titleTemplate: "Sign in with {provider}", // {provider} is a placeholder
messageTemplate: "Due to preview limitations, {provider} sign-in must be completed in a new tab.",
actionButton: "Open in New Tab",
},
},
});Custom Styles
All styles are scoped under [data-edgespark-auth]. Import the CSS and override via CSS variables:
/* In your CSS file */
[data-edgespark-auth] {
/* Colors */
--edgespark-auth-color-primary: rgba(0, 0, 0, 0.95);
--edgespark-auth-color-secondary: rgba(0, 0, 0, 0.65);
--edgespark-auth-color-tertiary: rgba(0, 0, 0, 0.45);
--edgespark-auth-color-text: rgba(0, 0, 0, 0.95);
--edgespark-auth-color-text-inverse: #ffffff;
--edgespark-auth-color-error: #dc2626;
--edgespark-auth-color-border: rgba(0, 0, 0, 0.12);
--edgespark-auth-color-bg: #ffffff;
--edgespark-auth-color-bg-secondary: rgba(0, 0, 0, 0.05);
--edgespark-auth-color-bg-link: #0077ff;
/* Typography */
--edgespark-auth-font-family: 'Roboto Flex', -apple-system, sans-serif;
--edgespark-auth-font-size-title: 32px;
--edgespark-auth-font-size-body: 14px;
--edgespark-auth-font-size-small: 12px;
/* Sizing & Border Radius */
--edgespark-auth-radius-card: 30px;
--edgespark-auth-radius-input: 12px;
--edgespark-auth-radius-button: 999px;
--edgespark-auth-spacing: 16px;
--edgespark-auth-input-height: 48px;
--edgespark-auth-button-height: 48px;
--edgespark-auth-card-width: 420px;
}Custom UI
If the managed UI doesn't meet your needs, call client.auth APIs directly to build your own UI.
client.auth is based on better-auth client and supports all better-auth methods.
Sign In
// Email/password sign in
const result = await client.auth.signIn.email({
email: "[email protected]",
password: "password",
});
if (result.error) {
console.error("Sign in failed:", result.error.message);
} else {
console.log("Sign in successful:", result.data);
}
// OAuth sign in (redirects to third-party page)
await client.auth.signIn.social({
provider: "google",
callbackURL: "/dashboard", // Redirect after success
});
// Anonymous temporary account (requires backend support)
await client.auth.signIn.anonymous();Sign Up
const result = await client.auth.signUp.email({
name: "John Doe",
email: "[email protected]",
password: "password",
callbackURL: "/verify-email", // Link destination in verification email
});
if (result.error) {
console.error("Sign up failed:", result.error.message);
}Get User Info
// Get current logged-in user's session
const session = await client.auth.getSession();
if (session.data) {
console.log("User info:", session.data.user);
console.log("Session:", session.data.session);
} else {
console.log("Not logged in");
}Sign Out
await client.auth.signOut();Forgot Password / Reset Password
// 1. Send reset code to email
await client.auth.forgetPassword.emailOtp({
email: "[email protected]",
});
// 2. Set new password with verification code
const result = await client.auth.emailOtp.resetPassword({
email: "[email protected]",
otp: "123456",
password: "newpassword",
});
if (result.error) {
console.error("Reset failed:", result.error.message);
}Listen to Auth State Changes
// Listen to auth state changes (login/logout/token refresh)
const unsubscribe = client.auth.onAuthStateChange(() => {
// Get latest session when state changes
client.auth.getSession().then((session) => {
if (session?.data?.user) {
console.log("Logged in", session.data.user);
} else {
console.log("Logged out");
}
});
});
// Unsubscribe
unsubscribe();API Module
client.api.fetch is fetch with auto-injected auth token:
// GET
const res = await client.api.fetch("/api/users");
const users = await res.json();
// POST
const res = await client.api.fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "Hello" }),
});Security Note: Token is only injected for same-origin requests (same domain as
baseUrl). Cross-origin requests will not include the token automatically.
Lifecycle & Best Practices
Check Login Status Before Rendering
Always check if user is already logged in before rendering the login UI:
const session = await client.auth.getSession();
if (session.data?.user) {
// Already logged in, redirect to dashboard
window.location.href = "/dashboard";
return;
}
// Not logged in, render login UI
await client.auth.renderAuthUI(container, {
redirectTo: "/dashboard",
});Get User Info on Target Page
After redirect, use getSession() on the target page - this is the recommended way to get user info:
// /dashboard page
const session = await client.auth.getSession();
if (!session.data?.user) {
window.location.href = "/login";
return;
}
console.log("Welcome", session.data.user.name);onLogin vs getSession vs onAuthStateChange
| API | Use Case | Notes |
|-----|----------|-------|
| getSession() | Recommended: Get current user info | Works everywhere, async |
| onLogin | Quick sync ops (analytics, logging) | Not called for OAuth; fires before redirect |
| onAuthStateChange | Advanced: real-time auth monitoring | Need manual unsubscribe; fires on any token change |
React Best Practice
// ✅ Create client outside component (singleton)
const client = createEdgeSpark({ baseUrl: "..." });
function AuthPage() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!ref.current) return;
// Check if already logged in
client.auth.getSession().then((session) => {
if (session.data?.user) {
window.location.href = "/dashboard";
return;
}
// Render login UI
client.auth.renderAuthUI(ref.current!, {
redirectTo: "/dashboard",
});
});
// No need to destroy - renderAuthUI auto-cleans previous UI
}, []);
return <div ref={ref} />;
}Cleanup
// Destroy client (cleanup UI, event listeners, etc.)
client.destroy();
// Also clear locally stored token
client.destroy({ clearToken: true });Note: Only call
destroy()when the entire app unmounts, not on component unmount.
API Reference
createEdgeSpark(options)
Creates an EdgeSpark client. Initialization is synchronous.
| Option | Type | Description |
|--------|------|-------------|
| baseUrl | string | Backend URL (required) |
| fetchCredentials | RequestCredentials | Fetch credentials mode, default "include" |
client.auth
Authentication client based on better-auth. Main methods:
| Method | Description |
|--------|-------------|
| signIn.email({ email, password }) | Email/password sign in |
| signIn.social({ provider, callbackURL }) | OAuth sign in |
| signIn.anonymous() | Anonymous sign in |
| signUp.email({ name, email, password }) | Email sign up |
| signOut() | Sign out |
| getSession() | Get current session/user info |
| forgetPassword.emailOtp({ email }) | Send password reset code |
| emailOtp.resetPassword({ email, otp, password }) | Reset password with code |
| renderAuthUI(container, options) | Render managed login UI |
| onAuthStateChange(listener) | Listen to token changes |
client.api
| Method | Description |
|--------|-------------|
| fetch(path, options?) | Fetch with auth token |
LoginUIOptions
Options for renderAuthUI:
| Option | Type | Description |
|--------|------|-------------|
| redirectTo | string | Redirect URL after login (simple mode) |
| redirects | RedirectOptions | Fine-grained redirect config (overrides redirectTo) |
| labels | Partial<ExtendedLabels> | Custom labels |
| onLogin | (user) => void | Optional: for quick sync ops like analytics (not called for OAuth) |
| onError | (error) => void | Error callback |
RedirectOptions
interface RedirectOptions {
oauth?: {
success?: string; // OAuth login success
newUser?: string; // OAuth first-time signup
error?: string; // OAuth error
};
emailPassword?: string; // Email/password login success
emailVerification?: string; // Email verification link destination
anonymous?: string; // Anonymous login success
}Build
npm run build # Build
npm run dev # Run example (examples/dev)