npm package: @safercity/sdk-core
Description: Core utilities for SaferCity SDK - fetch abstraction, streaming, and authentication
Author: SaferCity
Latest version: 0.4.0

# @safercity/sdk-core

Core utilities for SaferCity SDK including fetch abstraction, streaming support, and authentication helpers.

> **Note**: Most users should use `@safercity/sdk` instead. This package contains low-level utilities used internally.

## What's New in v0.4.0

- **No breaking changes** - Internal package only. Bumped for consistency with SDK v0.4.0.

## What's New in v0.3.2

- **ProxyModeConfig Enhanced** - Added `tenantId`, `userId`, and `headers` support to `ProxyModeConfig` for white-label apps using proxy mode.

## What's New in v0.3.1

- **No breaking changes** - Internal package only. Bumped for consistency with SDK client v0.3.1.

## What's New in v0.3.0

- **Foundation for Typed SDK** - Core types and utilities updated to support the auto-generated OpenAPI SDK.

## Installation

```bash
npm install @safercity/sdk-core
```

## What's New in v0.2.0

- **User Scoping** - Added `userId` to `DirectModeConfig` and `CookieModeConfig` for automatic request scoping

## What's New in v0.1.3

- **OAuth endpoint path fix** - Token and refresh endpoints now correctly use `/v1/oauth/*` prefix

## What's New in v0.1.0

- **Auth Mode Types** - Three authentication modes: proxy, direct, and cookie
- **Token Manager** - Server-side OAuth token management with automatic refresh and request deduplication
- **Token Storage** - Pluggable token storage interface with in-memory default

## Features

- **Type definitions** - Common types for API requests/responses and auth mode configuration
- **Authentication utilities** - Token management, JWT parsing, OAuth token lifecycle
- **Streaming/SSE support** - Cross-platform Server-Sent Events
- **Base HTTP client** - Fetch wrapper with timeout and error handling

## Usage

### Auth Mode Types

The SDK supports three authentication modes for different deployment scenarios:

```typescript
import type {
  AuthMode,
  ProxyModeConfig,
  DirectModeConfig,
  CookieModeConfig,
  ClientModeConfig,
} from '@safercity/sdk-core';
```

| Mode | Flow | Best For |
|------|------|----------|
| `"proxy"` | Client -> Your Backend -> SaferCity API | Production web apps (most secure) |
| `"direct"` | Client -> SaferCity API with external token | White-label apps with external auth (Clerk, Auth0, better-auth) |
| `"cookie"` | Browser with `credentials: include` | First-party web apps using session cookies |

```typescript
// Proxy mode (default) - backend adds tenant credentials
const proxyConfig: ProxyModeConfig = {
  mode: 'proxy',
  proxyBaseUrl: '/api/safercity', // defaults to "/api/safercity"
};

// Direct mode - external auth provider supplies the token
const directConfig: DirectModeConfig = {
  mode: 'direct',
  baseUrl: 'https://api.safercity.com',
  tenantId: 'tenant-123',
  userId: 'user-123', // optional, for user-scoped operations
  getAccessToken: () => session?.accessToken,
};

// Cookie mode - session cookies sent automatically
const cookieConfig: CookieModeConfig = {
  mode: 'cookie',
  baseUrl: 'https://api.safercity.com',
  tenantId: 'tenant-123', // optional, can come from cookie
  userId: 'user-123', // optional, for user-scoped operations
};
```

### Token Manager

Server-side OAuth token management with automatic refresh and concurrent request deduplication:

```typescript
import { TokenManager, MemoryTokenStorage } from '@safercity/sdk-core';

const tokenManager = new TokenManager({
  credentials: {
    clientId: process.env.SAFERCITY_CLIENT_ID!,
    clientSecret: process.env.SAFERCITY_CLIENT_SECRET!,
    tenantId: 'tenant-123', // optional
  },
  baseUrl: 'https://api.safercity.com',
  refreshBuffer: 60000, // refresh 1 minute before expiry (default)
  storage: new MemoryTokenStorage(), // default, or provide custom
});

// Get token (auto-refreshes if expired, dedupes concurrent calls)
const token = await tokenManager.getToken();

// Force refresh (useful for error recovery)
const freshToken = await tokenManager.forceRefresh();

// Check state
tokenManager.hasToken(); // boolean
tokenManager.clear();    // remove stored tokens
```

### Token Storage

Implement the `TokenStorage` interface for custom persistence:

```typescript
import type { TokenStorage, AuthTokens } from '@safercity/sdk-core';

// Built-in: MemoryTokenStorage (default, no persistence)
import { MemoryTokenStorage } from '@safercity/sdk-core';

// Custom implementation
class MyTokenStorage implements TokenStorage {
  get(): AuthTokens | null { /* ... */ }
  set(tokens: AuthTokens): void { /* ... */ }
  clear(): void { /* ... */ }
}
```

### Stream Adapters

```typescript
import { 
  WebStreamAdapter, 
  FetchStreamAdapter, 
  createStreamAdapter 
} from '@safercity/sdk-core';

// Auto-detect best adapter for environment
const adapter = createStreamAdapter();

// Or use specific adapter
const webAdapter = new WebStreamAdapter(); // Browser with EventSource
const fetchAdapter = new FetchStreamAdapter(fetch); // Node.js / React Native

// Stream events
const stream = adapter.createEventSource('https://api.example.com/stream', {
  headers: { Authorization: 'Bearer token' },
});

for await (const event of stream) {
  console.log(event.data);
}
```

### Authentication Helpers

```typescript
import {
  createAuthHeader,
  parseJwtPayload,
  getJwtExpiration,
  isTokenExpired,
} from '@safercity/sdk-core';

// Create auth header
const header = createAuthHeader('my-token'); // "Bearer my-token"

// Parse JWT (without verification)
const payload = parseJwtPayload(token);
console.log(payload?.sub); // User ID

// Check expiration
const expiresAt = getJwtExpiration(token);
if (isTokenExpired(expiresAt)) {
  // Token expired, refresh it
}
```

### Base Client

```typescript
import { BaseClient } from '@safercity/sdk-core';

const client = new BaseClient({
  baseUrl: 'https://api.example.com',
  token: 'my-token',
  timeout: 30000,
});

// Make requests
const response = await client.get('/users');
const created = await client.post('/users', { name: 'John' });
```

### Error Handling

```typescript
import { SaferCityApiError } from '@safercity/sdk-core';

try {
  await client.get('/not-found');
} catch (error) {
  if (error instanceof SaferCityApiError) {
    console.log(error.error);   // "not_found"
    console.log(error.message); // "Resource not found"
    console.log(error.status);  // 404
  }
}
```

## Types

### SaferCityConfig

```typescript
interface SaferCityConfig {
  baseUrl: string;
  token?: string;
  tenantId?: string;
  fetch?: typeof fetch;
  timeout?: number;
  headers?: Record<string, string>;
}
```

### AuthTokens

```typescript
interface AuthTokens {
  accessToken: string;
  refreshToken?: string;
  expiresAt?: number;
  tokenType: string;
}
```

### OAuthCredentials

```typescript
interface OAuthCredentials {
  clientId: string;
  clientSecret: string;
  tenantId?: string;
}
```

### ServerSentEvent

```typescript
interface ServerSentEvent {
  id?: string;
  event?: string;
  data: string;
  retry?: number;
}
```

## License

MIT