npm package: @spoofproof/client
Description: Official JavaScript/TypeScript SDK for Spoofproof authentication.
Author: Spoofproof
Latest version: 2026.2.26

# @spoofproof/client

Official JavaScript/TypeScript SDK for [Spoofproof](https://spoofproof.com) authentication.

Spoofproof lets users on your website authenticate via their installed Spoofproof desktop app — no passwords, no phishing.

---

## Installation

```bash
npm install @spoofproof/client
# or
pnpm add @spoofproof/client
```

---

## Quick Start (React)

```tsx
import { SpoofproofProvider, useSpoofproof } from '@spoofproof/client/react';

function App() {
  return (
    <SpoofproofProvider domain="example.com">
      <LoginButton />
    </SpoofproofProvider>
  );
}

function LoginButton() {
  const { login, logout, session, isLoading, error } = useSpoofproof();

  if (session) {
    return (
      <div>
        <p>Logged in as {session.email}</p>
        <button onClick={logout}>Log out</button>
      </div>
    );
  }

  return (
    <>
      <button onClick={login} disabled={isLoading}>
        {isLoading ? 'Authenticating…' : 'Login with Spoofproof'}
      </button>
      {error && <p style={{ color: 'red' }}>{error.message}</p>}
    </>
  );
}
```

---

## Vanilla JS / TypeScript

```typescript
import { SpoofproofClient } from '@spoofproof/client';

const client = new SpoofproofClient({ domain: 'example.com' });

// Check if the desktop app is running
const running = await client.isAppRunning();
if (!running) {
  console.log('Please start the Spoofproof desktop app.');
}

// Authenticate
const session = await client.login();
console.log('Logged in as:', session.email);
console.log('Token:', session.token);

// Get current session (null if expired)
const current = client.getSession();

// Proactively refresh before expiry (call on a timer)
await client.refreshIfNeeded();

// Logout
await client.logout();
```

---

## API

### `new SpoofproofClient(config)`

| Option | Type | Default | Description |
|---|---|---|---|
| `domain` | `string` | **required** | Your website's domain |
| `apiUrl` | `string` | `http://localhost:7777` | Spoofproof local server URL |
| `timeout` | `number` | `5000` | Request timeout in ms |
| `retryAttempts` | `number` | `3` | Retry count for transient errors |

### `client.login()` → `Promise<AuthSession>`

Authenticates the user. Returns immediately if a valid session is already cached. Throws `SpoofproofError` if the app is not running or auth fails.

### `client.getSession()` → `AuthSession | null`

Returns the current valid session, or `null` if none exists or it has expired.

### `client.logout()` → `Promise<void>`

Clears the session locally and revokes the token on the desktop app.

### `client.refreshIfNeeded()` → `Promise<void>`

Silently refreshes the token if it expires within 5 minutes. Safe to call on a 60-second timer.

### `client.isAppRunning()` → `Promise<boolean>`

Checks whether the Spoofproof desktop app is currently running.

### `client.verifySignature(challenge, signature, publicKey)` → `Promise<VerifySignatureResult>`

Verifies an Ed25519 signature produced by the Spoofproof desktop app using the WebCrypto API (`crypto.subtle`). Useful for custom server-side auth flows where you need to confirm the device signed a specific challenge.

```typescript
const result = await client.verifySignature(
  challengeBase64,   // base64-encoded challenge bytes
  signatureBase64,   // base64-encoded Ed25519 signature
  publicKeyBase64,   // base64-encoded 32-byte raw Ed25519 public key
);
// result: { valid: boolean; error?: string }
```

Never throws — errors are returned as `{ valid: false, error }`.
Requires Chrome 113+, Firefox 129+, or Safari 17+.

---

### `VerifySignatureResult`

```typescript
interface VerifySignatureResult {
  valid  : boolean;
  error? : string;
}
```

---

### `AuthSession`

```typescript
interface AuthSession {
  token    : string;  // JWT bearer token
  userId   : string;  // Database UUID of the user
  email    : string;  // Verified email address
  expiresAt: Date;    // Token expiry
  jti      : string;  // JWT ID (unique per token)
}
```

---

### Error Handling

```typescript
import { SpoofproofClient, SpoofproofError, SpoofproofErrorCode } from '@spoofproof/client';

try {
  const session = await client.login();
} catch (err) {
  if (err instanceof SpoofproofError) {
    switch (err.code) {
      case SpoofproofErrorCode.APP_NOT_RUNNING:
        // Show download/install prompt
        break;
      case SpoofproofErrorCode.AUTH_FAILED:
        // Show error message
        break;
      case SpoofproofErrorCode.TIMEOUT:
        // Retry
        break;
    }
    console.log('Retryable?', err.retryable);
  }
}
```

---

## React API

### `<SpoofproofProvider domain="..." ...>`

Wraps your app. Accepts all `SpoofproofClient` config options as props, plus `children`.

### `useSpoofproof()`

Returns `{ client, session, isLoading, isAppMissing, error, login, logout }`.

| Property | Type | Description |
|---|---|---|
| `client` | `SpoofproofClient` | The underlying client instance |
| `session` | `AuthSession \| null` | Current valid session, or `null` |
| `isLoading` | `boolean` | `true` while a login/logout is in flight |
| `isAppMissing` | `boolean` | `true` when `login()` threw `APP_NOT_RUNNING` — use to show `<SpoofproofFallback>` |
| `error` | `Error \| null` | Last thrown error, or `null` |
| `login` | `() => Promise<void>` | Initiates authentication |
| `logout` | `() => Promise<void>` | Clears session |

---

### `<SpoofproofFallback downloadUrls={…} onRetry={…} />`

Drop-in UI shown when `isAppMissing` is `true`. Detects the user's OS and renders the correct download button plus a "I've installed it — try again" button.

```tsx
import { SpoofproofFallback, useSpoofproof } from '@spoofproof/client/react';

function AuthGate({ children }: { children: React.ReactNode }) {
  const { session, isAppMissing, login } = useSpoofproof();

  if (isAppMissing) {
    return (
      <SpoofproofFallback
        downloadUrls={{
          mac    : 'https://spoofproof.com/download/mac',
          windows: 'https://spoofproof.com/download/windows',
          linux  : 'https://spoofproof.com/download/linux',
        }}
        onRetry={login}
      />
    );
  }

  return session ? <>{children}</> : <LoginButton />;
}
```

| Prop | Type | Required | Description |
|---|---|---|---|
| `downloadUrls` | `{ mac?, windows?, linux? }` | ✅ | Per-platform download links |
| `onRetry` | `() => void` | ✅ | Called when user clicks "try again" |
| `os` | `'mac' \| 'windows' \| 'linux' \| 'unknown'` | — | Override OS detection (useful in SSR/tests) |

---

## Publishing

```bash
cd packages/client
pnpm build
pnpm test
npm publish --access public
```