npm package: @sp-uvb/client
Description: Universal Verification Broker (UVB) TypeScript/JavaScript SDK
Author: UVB Team
Latest version: 0.1.0

# @sp-uvb/client

Official TypeScript/JavaScript SDK for [Universal Verification Broker (UVB)](https://github.com/yourusername/uvb).

**Features:**

- ✅ **Full TypeScript support** with complete type definitions
- ✅ **Browser & Node.js** compatible (ES modules + CommonJS)
- ✅ **Multiple storage options** - Memory, localStorage, sessionStorage, cookies
- ✅ **Comprehensive error handling** - Typed errors with detailed messages
- ✅ **Zero dependencies** - Lightweight and fast
- ✅ **Tree-shakeable** - Only bundle what you use
- ✅ **100% test coverage** - Reliable and production-ready

---

## Installation

```bash
# npm
npm install @sp-uvb/client

# yarn
yarn add @sp-uvb/client

# pnpm
pnpm add @sp-uvb/client
```

---

## Quick Start

### Basic Usage

```typescript
import { UvbClient } from '@sp-uvb/client';

// Create client
const client = new UvbClient({
  baseUrl: 'https://uvb.example.com',
});

// Start verification
const { transaction_id, challenges } = await client.startVerification(
  {
    user_id: 'user_123',
    tenant_id: 'tenant_a',
    email: 'user@example.com',
  },
  {
    tenant_id: 'tenant_a',
    application_id: 'app_1',
    intent: 'login',
  }
);

// Continue with TOTP
const response = await client.continueVerification({
  transaction_id,
  challenge_id: challenges[0].challenge_id,
  factor_id: 'totp',
  response_payload: { otp: '123456' },
});

if (response.status === 'Succeeded' && response.assertion) {
  // Store JWT token
  await client.setToken(response.assertion);
  console.log('Authentication successful!');
}
```

### With Token Storage

```typescript
import { UvbClient, LocalStorageTokenStorage } from '@sp-uvb/client';

const client = new UvbClient({
  baseUrl: 'https://uvb.example.com',
  tokenStorage: new LocalStorageTokenStorage(),
});

// After successful authentication, token is automatically stored
// and persists across browser sessions
```

---

## API Reference

### UvbClient

#### Constructor

```typescript
new UvbClient(config: UvbClientConfig)
```

**Options:**

- `baseUrl` (required): Base URL of UVB API
- `timeout` (optional): Request timeout in milliseconds (default: 30000)
- `headers` (optional): Custom headers to include in all requests
- `fetch` (optional): Custom fetch implementation
- `tokenStorage` (optional): Token storage implementation (default: MemoryTokenStorage)

**Example:**

```typescript
const client = new UvbClient({
  baseUrl: 'https://uvb.example.com',
  timeout: 10000,
  headers: {
    'X-Custom-Header': 'value',
  },
});
```

#### Methods

##### startVerification(subject, context)

Start a verification transaction.

```typescript
const result = await client.startVerification(
  {
    user_id: 'user_123',
    tenant_id: 'tenant_a',
    email: 'user@example.com',
  },
  {
    tenant_id: 'tenant_a',
    application_id: 'app_1',
    intent: 'login',
    ip_address: '192.168.1.1',
  }
);

console.log('Transaction ID:', result.transaction_id);
console.log('Available challenges:', result.challenges);
```

##### continueVerification(request)

Continue a verification with a factor response.

```typescript
const result = await client.continueVerification({
  transaction_id: 'txn_123',
  challenge_id: 'chal_456',
  factor_id: 'totp',
  response_payload: { otp: '123456' },
});

if (result.status === 'Succeeded') {
  console.log('JWT token:', result.assertion);
}
```

##### enrollFactor(request)

Enroll a user in an authentication factor.

```typescript
const result = await client.enrollFactor({
  subject: {
    user_id: 'user_123',
    tenant_id: 'tenant_a',
  },
  factor_id: 'totp',
});

console.log('TOTP secret:', result.enrollment_data.secret);
console.log('QR code:', result.enrollment_data.qr_code_url);
```

##### listEnrollments(request)

List all enrollments for a user.

```typescript
const { enrollments } = await client.listEnrollments({
  user_id: 'user_123',
  tenant_id: 'tenant_a',
});

enrollments.forEach((enrollment) => {
  console.log('Factor:', enrollment.factor_id);
  console.log('Status:', enrollment.status);
});
```

##### deleteEnrollment(request)

Delete an enrollment.

```typescript
await client.deleteEnrollment({
  enrollment_id: 'enr_123',
  user_id: 'user_123',
  tenant_id: 'tenant_a',
});
```

##### introspectToken(request)

Verify and extract claims from a JWT token.

```typescript
const result = await client.introspectToken({ token: jwtToken });

if (result.active) {
  console.log('User:', result.sub);
  console.log('Factors:', result.factors_used);
  console.log('Assurance level:', result.assurance_level);
}
```

##### Token Management

```typescript
// Store token
await client.setToken('eyJhbGc...');

// Get token
const token = await client.getToken();

// Remove token
await client.removeToken();

// Check if token exists
const hasToken = await client.hasToken();

// Verify current token
const claims = await client.verifyCurrentToken();
if (claims && claims.active) {
  console.log('Token is valid');
}
```

---

## Token Storage

### Available Storage Implementations

#### MemoryTokenStorage (Default)

Stores tokens in memory (not persistent).

```typescript
import { MemoryTokenStorage } from '@sp-uvb/client';

const storage = new MemoryTokenStorage();
```

**Use when:**

- Testing
- Server-side applications
- Single-page apps that don't need persistence

#### LocalStorageTokenStorage

Stores tokens in browser localStorage (persistent across sessions).

```typescript
import { LocalStorageTokenStorage } from '@sp-uvb/client';

const storage = new LocalStorageTokenStorage('my_token_key');
```

**Use when:**

- Browser apps needing persistence
- Simple token storage requirements

**Warning:** Accessible by JavaScript, so only use for non-sensitive tokens.

#### SessionStorageTokenStorage

Stores tokens in browser sessionStorage (cleared when tab closes).

```typescript
import { SessionStorageTokenStorage } from '@sp-uvb/client';

const storage = new SessionStorageTokenStorage();
```

**Use when:**

- Browser apps
- Tokens should not persist across sessions

#### CookieTokenStorage

Stores tokens in cookies.

```typescript
import { CookieTokenStorage } from '@sp-uvb/client';

const storage = new CookieTokenStorage({
  cookieName: 'uvb_token',
  secure: true,
  sameSite: 'strict',
  maxAge: 3600,
});
```

**Use when:**

- Need cookie-based authentication
- Working with server-side rendering

#### Custom Storage

Implement your own storage:

```typescript
import type { TokenStorage } from '@sp-uvb/client';

class CustomStorage implements TokenStorage {
  async setToken(token: string): Promise<void> {
    // Your implementation
  }

  async getToken(): Promise<string | null> {
    // Your implementation
  }

  async removeToken(): Promise<void> {
    // Your implementation
  }
}

const client = new UvbClient({
  baseUrl: 'https://uvb.example.com',
  tokenStorage: new CustomStorage(),
});
```

---

## Error Handling

The SDK provides typed error classes for better error handling:

```typescript
import {
  NetworkError,
  ApiError,
  RateLimitError,
  AuthenticationError,
  ValidationError,
} from '@sp-uvb/client';

try {
  await client.startVerification(subject, context);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log('Rate limited. Retry after:', error.retryAfter);
  } else if (error instanceof AuthenticationError) {
    console.log('Authentication failed');
  } else if (error instanceof NetworkError) {
    console.log('Network error:', error.message);
  } else if (error instanceof ApiError) {
    console.log('API error:', error.statusCode, error.errorCode);
  }
}
```

### Error Types

- **`UvbError`** - Base error class
- **`NetworkError`** - Connection failures, timeouts
- **`ApiError`** - HTTP errors (4xx, 5xx)
- **`AuthenticationError`** - 401 errors
- **`RateLimitError`** - 429 errors with retry-after
- **`ValidationError`** - Invalid input
- **`ConfigurationError`** - Invalid client configuration

---

## Examples

### Complete Authentication Flow

```typescript
import { UvbClient, LocalStorageTokenStorage } from '@sp-uvb/client';

const client = new UvbClient({
  baseUrl: 'https://uvb.example.com',
  tokenStorage: new LocalStorageTokenStorage(),
});

async function login(userId: string, tenantId: string) {
  try {
    // 1. Start verification
    const { transaction_id, challenges } = await client.startVerification(
      { user_id: userId, tenant_id: tenantId },
      {
        tenant_id: tenantId,
        application_id: 'my_app',
        intent: 'login',
      }
    );

    // 2. Show available factors to user
    console.log(
      'Available factors:',
      challenges.map((c) => c.display_name)
    );

    // 3. User selects TOTP and enters code
    const otp = await promptUserForOTP(); // Your UI logic

    // 4. Continue verification
    const result = await client.continueVerification({
      transaction_id,
      challenge_id: challenges[0].challenge_id,
      factor_id: 'totp',
      response_payload: { otp },
    });

    // 5. Handle result
    if (result.status === 'Succeeded' && result.assertion) {
      await client.setToken(result.assertion);
      console.log('Login successful!');
      return true;
    } else {
      console.log('Login failed:', result.last_factor_result?.error_message);
      return false;
    }
  } catch (error) {
    console.error('Login error:', error);
    return false;
  }
}
```

### TOTP Enrollment

```typescript
async function enrollTOTP(userId: string, tenantId: string) {
  const { enrollment_data } = await client.enrollFactor({
    subject: { user_id: userId, tenant_id: tenantId },
    factor_id: 'totp',
  });

  // Display QR code to user
  showQRCode(enrollment_data.qr_code_url);

  // Or show secret for manual entry
  console.log('Secret:', enrollment_data.secret);

  return enrollment_data;
}
```

### Protected API Requests

```typescript
async function makeAuthenticatedRequest() {
  // Check if token is valid
  const claims = await client.verifyCurrentToken();

  if (!claims || !claims.active) {
    console.log('Not authenticated, redirecting to login...');
    return;
  }

  // Token is valid, proceed with request
  const token = await client.getToken();

  const response = await fetch('https://api.example.com/protected', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return response.json();
}
```

### React Hook Example

```typescript
import { useState, useEffect } from 'react';
import { UvbClient } from '@sp-uvb/client';

function useAuth(client: UvbClient) {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    checkAuth();
  }, []);

  const checkAuth = async () => {
    const claims = await client.verifyCurrentToken();
    setIsAuthenticated(claims?.active ?? false);
    setIsLoading(false);
  };

  const login = async (userId: string, tenantId: string, otp: string) => {
    // ... login logic
    await checkAuth();
  };

  const logout = async () => {
    await client.removeToken();
    setIsAuthenticated(false);
  };

  return { isAuthenticated, isLoading, login, logout };
}
```

---

## TypeScript Support

The SDK is written in TypeScript and provides full type definitions:

```typescript
import type {
  Subject,
  Context,
  Challenge,
  VerificationStatus,
  StartVerificationResponse,
  ContinueVerificationResponse,
  EnrollFactorRequest,
  IntrospectTokenResponse,
} from '@sp-uvb/client';

// All types are exported and fully documented
```

---

## Browser Support

- ✅ Chrome/Edge (latest)
- ✅ Firefox (latest)
- ✅ Safari (latest)
- ✅ Node.js 16+

---

## Contributing

Contributions are welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.

---

## License

MIT License - see [LICENSE](LICENSE) for details.

---

## Support

- **Documentation:** [UVB Docs](https://docs.uvb.example.com)
- **Issues:** [GitHub Issues](https://github.com/yourusername/uvb/issues)
- **Discussions:** [GitHub Discussions](https://github.com/yourusername/uvb/discussions)