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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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/client

Note: 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)