npm package: @seaspark/auth-sdk
Description: SDK for SeaVerse Backend API
Author: SeaSpark Team
Latest version: 0.1.2

# @seaspark/auth-sdk

SeaVerse Backend API Client SDK - Provides complete authentication, container management, skill marketplace, and more

[![npm version](https://img.shields.io/npm/v/@seaspark/auth-sdk.svg)](https://www.npmjs.com/package/@seaspark/auth-sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- 🔐 **User Authentication** - Registration, login, logout, password reset
- 🌐 **OAuth Login** - Google, Discord, GitHub third-party login
- 🎨 **Login UI Components** - Out-of-the-box beautiful login modal
- ✨ **Toast Notifications** - Modern glassmorphism alerts with auto-injected CSS
- 🌍 **Multi-Environment Support** - Production, Staging, Development, Local
- ⚡ **TypeScript** - Full type definitions
- 🔧 **Auth Integration** - Built-in Auth and Hooks system support
- 🔄 **Response Format Compatibility** - Automatic compatibility with multiple API response formats

## Installation

```bash
npm install @seaspark/auth-sdk
# or
pnpm add @seaspark/auth-sdk
```

## Core Concepts

### App ID and Multi-Tenant Architecture

**Important**: Starting from v0.2.0, `appId` is a **required parameter** when initializing the SDK.

#### What is App ID?

Each application has a unique `app_id`:
- `app_id = "your app id"`

#### Multi-Tenant Isolation

- User data is isolated by `app_id`
- The same email can be registered in different applications with different passwords
- Each application has its own independent user pool

#### X-App-ID Request Header

The SDK automatically adds `X-App-ID` to **every request** header, no manual configuration needed:

```typescript
const client = new SeaVerseBackendAPIClient({
  appId: 'game-abc123',  // SDK automatically adds this value to all requests' X-App-ID header
});

// All API calls will automatically carry X-App-ID: game-abc123
await client.login({ email, password });
await client.getCurrentUser();
```

## Quick Start

### 1. Basic Usage

```typescript
import { SeaVerseBackendAPIClient } from '@seaspark/auth-sdk';

// Method 1: SeaVerse platform application (auto-detect environment)
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id', // Required: Application ID
});

// Method 2: Third-party application (specify environment)
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',        // Required: Your application ID
  environment: 'production',   // 'production' | 'staging' | 'development' | 'local'
});

// Method 3: Custom URL
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',             // Required: Your application ID
  baseURL: 'https://custom-api.example.com',
});

// Method 3.1: Use proxy in development (empty string for relative URLs)
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  baseURL: '',  // Empty string to use relative URLs with dev proxy
});

// Method 4: Enable request retry (disabled by default)
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  retryOptions: {
    maxRetries: 3,                  // Retry up to 3 times
    retryDelay: 1000,               // Initial delay 1000ms, doubles each retry
    retryStatusCodes: [408, 429, 500, 502, 503, 504],  // Retry on these status codes
  },
});

// Health check
const health = await client.getHealth();
console.log('Health:', health);
```

### 2. Token Retrieval in Iframe Scenarios

When your application is embedded in an iframe, you can use the `getIframeToken()` method to retrieve the authentication token from the parent page. This is particularly useful for third-party login redirect scenarios.

#### Check if Running in Iframe

Before calling iframe-related methods, it's recommended to check if the application is running in an iframe:

```typescript
import { SeaVerseBackendAPIClient } from '@seaspark/auth-sdk';

// Method 1: Use static method (no client instance needed)
if (SeaVerseBackendAPIClient.isInIframe()) {
  console.log('Application is running in iframe');
} else {
  console.log('Application is not running in iframe');
}

// Method 2: Use instance method
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
});

if (client.isInIframe()) {
  console.log('Application is running in iframe');
  // Safe to call getIframeToken()
}
```

#### Child Page (Inside Iframe) Usage

```typescript
import { SeaVerseBackendAPIClient } from '@seaspark/auth-sdk';

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
});

// First check if running in iframe
if (!client.isInIframe()) {
  console.log('Not in iframe, skip token retrieval');
  // Can use other login methods
} else {
  // Request token from parent page while in iframe
  try {
    const token = await client.getIframeToken({
      timeout: 10000 // Optional, default 30 seconds
    });

    // Set token to client
    client.setToken(token);
    localStorage.setItem('token', token);

    console.log('Successfully retrieved token from parent page');

    // Now can use authenticated APIs
    const user = await client.getCurrentUser();
    console.log('Current user:', user);
  } catch (error) {
    console.error('Failed to retrieve token:', error);
  }
}
```

#### Parent Page Listens and Responds to Token Requests

```typescript
// Parent page needs to listen for token requests from iframe
window.addEventListener('message', (event) => {
  // Check message type
  if (event.data.type === 'seaverse:get_token') {
    const requestId = event.data.requestId;

    // Get your token (from localStorage, API, etc)
    const token = localStorage.getItem('token');

    if (token) {
      // Send token to iframe
      event.source.postMessage({
        type: 'seaverse:token',
        requestId: requestId,
        payload: {
          accessToken: token,
          expiresIn: 3600,
        },
      }, '*');
    } else {
      // Send error message
      event.source.postMessage({
        type: 'seaverse:token_error',
        requestId: requestId,
        error: 'No token available',
      }, '*');
    }
  }
});
```

#### Complete Iframe Login Flow Example

```typescript
// Child page (iframe)
import { SeaVerseBackendAPIClient } from '@seaspark/auth-sdk';

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
});

async function initIframeApp() {
  // 0. Check if running in iframe
  if (!client.isInIframe()) {
    console.log('Not in iframe, use regular login flow');
    // Use regular login flow
    showLoginForm();
    return;
  }

  try {
    // 1. Try to get token from parent page
    const token = await client.getIframeToken();
    client.setToken(token);
    localStorage.setItem('token', token);

    // 2. Validate token and get user info
    const user = await client.getCurrentUser();
    console.log('Logged in:', user);

    // 3. Continue app logic
    startApp(user);
  } catch (error) {
    // 4. If failed, notify parent page that login is needed
    window.parent.postMessage({
      type: 'seaverse:need_login',
    }, '*');
  }
}

initIframeApp();
```

```typescript
// Parent page
window.addEventListener('message', (event) => {
  // Handle token request
  if (event.data.type === 'seaverse:get_token') {
    const requestId = event.data.requestId;
    const token = localStorage.getItem('token');

    if (token) {
      event.source.postMessage({
        type: 'seaverse:token',
        requestId: requestId,
        payload: {
          accessToken: token,
          expiresIn: 3600,
        },
      }, '*');
    } else {
      // If no token, trigger login
      showLoginModal();
    }
  }

  // Handle need login message
  if (event.data.type === 'seaverse:need_login') {
    showLoginModal();
  }
});

// After successful login, notify iframe
function onLoginSuccess(newToken) {
  const iframe = document.querySelector('iframe');
  iframe.contentWindow.postMessage({
    type: 'seaverse:token',
    requestId: 'auto', // Can use fixed ID for auto-login
    payload: {
      accessToken: newToken,
      expiresIn: 3600,
    },
  }, '*');
}
```

#### TypeScript Type Definitions

The SDK provides complete TypeScript type support:

```typescript
import type {
  IframeTokenRequestMessage,
  IframeTokenResponseMessage,
  IframeTokenErrorMessage,
} from '@seaspark/auth-sdk';

// Token request message
const request: IframeTokenRequestMessage = {
  type: 'seaverse:get_token',
  requestId: 'unique-id',
};

// Token response message
const response: IframeTokenResponseMessage = {
  type: 'seaverse:token',
  requestId: 'unique-id',
  payload: {
    accessToken: 'jwt-token',
    expiresIn: 3600,
  },
};

// Error message
const error: IframeTokenErrorMessage = {
  type: 'seaverse:token_error',
  requestId: 'unique-id',
  error: 'Token not available',
};
```

#### Security Considerations

1. **Use specific origin**: In production, use specific origin instead of `'*'`:
   ```typescript
   // Parent page
   const ALLOWED_ORIGIN = 'https://your-iframe-domain.com';
   event.source.postMessage(message, ALLOWED_ORIGIN);

   // Child page can also specify when sending
   window.parent.postMessage(message, 'https://parent-domain.com');
   ```

2. **Verify message origin**: Validate origin when handling postMessage:
   ```typescript
   window.addEventListener('message', (event) => {
     // Verify origin
     if (event.origin !== 'https://trusted-domain.com') {
       return;
     }
     // Handle message...
   });
   ```

3. **Handle token expiration**: Remember to handle token expiration:
   ```typescript
   try {
     const user = await client.getCurrentUser();
   } catch (error) {
     if (error.response?.status === 401) {
       // Token expired, re-retrieve
       const newToken = await client.getIframeToken();
       client.setToken(newToken);
     }
   }
   ```

### 3. User Authentication

```typescript
// Register new user
const registerResult = await client.register({
  email: 'user@example.com',
  password: 'SecurePassword123',
  username: 'johndoe',              // Optional, auto-generated from email if not provided
  invitation_code: 'INVITE123',     // Optional
  frontend_url: 'https://mygame.com/verify',  // Optional, frontend URL for email verification link, defaults to window.location.href
});

// Check registration result
if (registerResult.success) {
  console.log('Registration successful:', registerResult);
} else if (registerResult.code === 'ACCOUNT_EXISTS') {
  console.log('Account exists, please login directly');
  console.log('Error details:', registerResult.details);
}

// Login
const loginResult = await client.login({
  email: 'user@example.com',
  password: 'SecurePassword123',
});

// Save token
localStorage.setItem('token', loginResult.token);

// ⚠️ Note: Automatic invitation code redirect
// If server returns INVITE_CODE_REQUIRED error and includes redirectUrl,
// SDK will automatically redirect user to that URL (usually invitation code input page)
// No extra handling needed, page will redirect automatically

// Get current user info
const user = await client.getCurrentUser();
console.log('User:', user);
console.log('App ID:', user.app_id);           // Multi-tenant application ID
console.log('Email verified:', user.email_verified);

// Logout
await client.logout();

// Forgot password
await client.forgotPassword({
  email: 'user@example.com',
  frontend_url: 'https://mygame.com/',  // Optional, defaults to window.location.href
});

// Reset password
await client.resetPassword({
  token: 'reset-token-from-email',
  new_password: 'NewSecurePassword123',
});
```

### 4. OAuth Third-Party Login (Backend Proxy Mode)

The SDK uses Backend Proxy Mode where Client Secret is never exposed to the frontend, providing higher security.

**Advantages**:
- ✅ Client Secret is never exposed to frontend
- ✅ Supports any developer domain (no need to configure in OAuth platform)
- ✅ Built-in CSRF protection
- ✅ Zero configuration, works out of the box

**Workflow**:
1. Frontend calls `{provider}Authorize()` to get OAuth URL
2. Frontend redirects user to OAuth provider
3. After user authorization, OAuth provider callbacks to fixed account-hub URL
4. account-hub handles OAuth, creates JWT token
5. account-hub 302 redirects to `return_url?token=xxx`
6. Frontend extracts token from URL and stores it

#### Usage Examples

**Method 1: Use default return_url (current page)**

```typescript
// Google login
const { authorize_url } = await client.googleAuthorize();
window.location.href = authorize_url;

// Discord login
const { authorize_url } = await client.discordAuthorize();
window.location.href = authorize_url;

// GitHub login
const { authorize_url } = await client.githubAuthorize();
window.location.href = authorize_url;
```

**Method 2: Custom return_url**

```typescript
// Redirect to dashboard after login
const { authorize_url } = await client.googleAuthorize({
  return_url: 'https://mygame.com/dashboard'
});
window.location.href = authorize_url;
```

**Extract token in callback page**:

```typescript
// URL: https://mygame.com/?token=eyJhbGc...
const token = new URLSearchParams(window.location.search).get('token');
if (token) {
  localStorage.setItem('token', token);
  // Login successful, redirect or update UI
}
```

#### OAuth Account Unlinking

```typescript
// Unlink Google account
await client.unlinkGoogle();

// Unlink Discord account
await client.unlinkDiscord();

// Unlink GitHub account
await client.unlinkGithub();
```

### 5. Using Login UI Components

The SDK provides beautiful login modal components with dark and light themes:

#### Theme Comparison

| Theme | Design Style | Use Cases |
|------|---------|---------|
| **Dark** 🌙 | Dynamic grid gradient + glassmorphism | Tech products, gaming platforms, dark interface apps |
| **Light** ☀️ | Elevated card design + soft shadows | Business apps, content platforms, light interface apps |

#### Basic Usage

```typescript
import { SeaVerseBackendAPIClient, AuthModal } from '@seaspark/auth-sdk';
import '@seaspark/auth-sdk/auth-modal.css'; // Import styles

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Your application ID
  environment: 'production',
});

// Create login modal
const authModal = new AuthModal({
  client,
  theme: 'dark', // 'dark' | 'light' - defaults to 'dark'

  // Login success callback
  onLoginSuccess: (token, user) => {
    localStorage.setItem('token', token);
    console.log('Login successful:', user);
  },

  // Signup success callback
  onSignupSuccess: (token, user) => {
    localStorage.setItem('token', token);
    console.log('Signup successful:', user);
  },

  // Invitation code required callback (optional)
  onInviteCodeRequired: (userId, email) => {
    console.log('Need invitation code to activate account:', userId, email);
    // AuthModal will automatically show invitation code input interface
  },

  // Apply invite success callback (optional)
  onApplyInviteSuccess: (applicationId, email) => {
    console.log('Invitation code application submitted:', applicationId, email);
    // Can add custom logic here, such as showing notifications
  },

  // Error callback
  onError: (error) => {
    console.error('Authentication error:', error.message);
  },

  // OAuth configuration (optional)
  returnUrl: 'https://mygame.com/',  // URL to return after OAuth login, optional, defaults to window.location.href
  enableOAuth: {
    google: true,   // Enable Google login
    discord: true,  // Enable Discord login
    github: true,   // Enable GitHub login
  },
});

// Show login interface
authModal.show('login');

// Show signup interface
authModal.show('signup');

// Hide modal
authModal.hide();

// Destroy modal
authModal.destroy();
```

**✨ New Feature: Auto Toast Notifications**

AuthModal has built-in modern Toast notification system, no extra configuration needed:

```typescript
// Toast displays automatically, CSS auto-injected
// - Registration success → Green success notification
// - Account exists → Yellow warning notification
// - Login failure → Red error notification
// - Reset password → Blue info notification

// You can also use Toast independently (CSS auto-injected)
import { Toast } from '@seaspark/auth-sdk';

Toast.success('Success', 'Data saved');
Toast.error('Failed', 'Please try again later');
Toast.warning('Notice', 'Account already exists');
Toast.info('Info', 'Email sent');
```

#### Password Reset Flow

AuthModal supports complete password reset flow:

1. **User triggers forgot password**: Click "Forgot Password?" link in login interface
2. **Send reset email**: After entering email, system sends reset link with `reset_token`
3. **Auto trigger reset modal**: When user clicks link in email and returns to website, AuthModal automatically detects `reset_token` parameter in URL and shows reset password form
4. **Set new password**: User enters and confirms new password then submits
5. **Auto cleanup URL**: After successful reset, automatically clears `reset_token` parameter from URL

The entire process requires no extra code, AuthModal handles it automatically:

```typescript
// 1. Initialize AuthModal (only once)
const authModal = new AuthModal({
  client,
  // ... other configuration
});

// 2. After user clicks reset link in email, AuthModal automatically:
//    - Detects ?reset_token=xxx parameter in URL
//    - Shows reset password form
//    - User submits new password
//    - Calls client.resetPassword() API
//    - Cleans reset_token parameter from URL
//    - Shows success message
```

#### OAuth Configuration Instructions

The `enableOAuth` parameter is **completely optional**:

- If **not provided**, no third-party login buttons will be shown
- If **partially configured** (e.g., only enable Google), only enabled buttons will be shown
- If **fully configured** for all platforms, all third-party login buttons will be shown

**Configuration field descriptions**:
- `returnUrl`: **Optional** - URL to return after OAuth login, defaults to `window.location.href` if not provided
- `enableOAuth.google`: Whether to enable Google login
- `enableOAuth.discord`: Whether to enable Discord login
- `enableOAuth.github`: Whether to enable GitHub login

```typescript
// Example 1: No OAuth buttons
const authModal1 = new AuthModal({
  client,
  theme: 'dark',
  // No enableOAuth, no OAuth buttons shown
});

// Example 2: Only show Google login
const authModal2 = new AuthModal({
  client,
  theme: 'light',
  returnUrl: 'https://mygame.com/dashboard',
  enableOAuth: {
    google: true,
    // Discord and GitHub not enabled, won't show these buttons
  },
});

// Example 3: Show all OAuth buttons
const authModal3 = new AuthModal({
  client,
  theme: 'dark',
  enableOAuth: {
    google: true,
    discord: true,
    github: true,
  },
});
```

#### Handling OAuth Callback

In Backend Proxy Mode, after OAuth login it redirects to `returnUrl?token=xxx`. Check and handle token on page load:

```typescript
// Automatically handle OAuth callback on page load
const result = AuthModal.handleOAuthCallback({
  client,
  onLoginSuccess: (token) => {
    localStorage.setItem('token', token);
    console.log('OAuth login successful');

    // Can now call authenticated APIs directly
    // handleOAuthCallback has automatically called client.setToken()
    client.getCurrentUser()
      .then(user => console.log('User info:', user));
  },
});

if (result) {
  console.log('Handled OAuth callback, token:', result.token);
}
```

**Important Note**:
- `handleOAuthCallback()` automatically calls `client.setToken(token)` to update client authentication configuration
- This means after the `onLoginSuccess` callback, all authenticated APIs (such as `getCurrentUser()`, `logout()`, etc.) will automatically include the `Authorization` header

#### Manual Token Setting

If you don't use `AuthModal.handleOAuthCallback()`, you can also set the token manually:

```typescript
// Extract token from URL
const token = new URLSearchParams(window.location.search).get('token');
if (token) {
  // Manually set token
  client.setToken(token);
  localStorage.setItem('token', token);

  // Now can call authenticated APIs
  const user = await client.getCurrentUser();
}
```

### 6. Container Management

```typescript
// List all containers
const containers = await client.listContainers();

// Register new container
const result = await client.registerContainer({
  containerId: 'container-123',
  metadata: {
    version: '1.0.0',
    capabilities: ['skill-execution'],
  },
});

// Get container info
const container = await client.getContainer({
  containerId: 'container-123',
});

// Container heartbeat
await client.containerHeartbeat({
  containerId: 'container-123',
  status: 'healthy',
});

// Unregister container
await client.unregisterContainer({
  containerId: 'container-123',
});

// Get container stats
const stats = await client.getContainerStats();
```

### 7. Skill Marketplace

```typescript
// List marketplace skills
const skills = await client.listMarketplaceSkills({
  category: 'productivity',
  page: 1,
  pageSize: 20,
});

// Get skill details
const skill = await client.getMarketplaceSkill({
  skillId: 'skill-123',
});

// Install skill
await client.installSkill({
  skillId: 'skill-123',
});

// List installed skills
const userSkills = await client.listUserSkills();

// Uninstall skill
await client.uninstallSkill({
  skillId: 'skill-123',
});
```

### 8. Invitation Code Management

```typescript
// Apply for invitation code (when user doesn't have one)
const application = await client.applyInvite({
  email: 'player@example.com',
  reason: 'I want to join this amazing platform to build innovative applications and connect with the community.'
});

if (application.success) {
  console.log('Application submitted:', application.data);
  console.log('Application ID:', application.data.id);
  console.log('Status:', application.data.status); // 'pending'
} else {
  // Handle error
  if (application.code === 'APPLICATION_DUPLICATE') {
    console.log('You have already submitted an application in the past 24 hours');
  } else {
    console.error('Application failed:', application.error);
  }
}

// List my invitation codes
const invites = await client.listInvites({
  status: 'active',
  page: 1,
  page_size: 20,
});
console.log('My invitation codes:', invites.data.invites);

// Get invitation code stats
const stats = await client.getInviteStats();
console.log('Total invitation codes:', stats.data.total_codes);
console.log('Active invitation codes:', stats.data.active_codes);
console.log('Total usage count:', stats.data.total_uses);

// Get invitation code details
const invite = await client.getInvite('inv_abc123');
console.log('Invitation code:', invite.data.code);
console.log('Used count:', invite.data.used_count);
console.log('Max uses:', invite.data.max_uses);

// Get invitation code usage records
const usages = await client.getInviteUsages('inv_abc123', {
  page: 1,
  page_size: 20,
});
console.log('Usage records:', usages.data.usages);
```

### 9. Email Verification and Invitation Code Binding

#### Email Verification (Auto Login)

After registration, users receive a verification email containing a verification link in the format: `frontend_url?verify_token=xxx`

**Method 1: Use AuthModal Auto-Handling (Recommended)**

```typescript
import { AuthModal } from '@seaspark/auth-sdk';

// Create AuthModal instance
const modal = new AuthModal({
  client,
  onLoginSuccess: (token, user) => {
    localStorage.setItem('token', token);
    console.log('Email verified and logged in successfully:', user);
  },
  onError: (error) => {
    console.error('Email verification failed:', error);
  }
});

// AuthModal will automatically detect verify_token parameter in URL
// After detection, it will automatically:
// 1. Call verifyEmail() API to verify email
// 2. Get returned JWT token and auto-login
// 3. Trigger onLoginSuccess callback
// 4. Clean verify_token parameter from URL
// 5. Show success message
```

**Debugging Tips**:

If email verification has issues, check browser console logs:

```javascript
// Normal case should show:
[AuthModal] Detected verify_token, starting email verification...
[AuthModal] Email verification successful: { id: "xxx", email: "user@example.com", ... }

// If verification fails, will show:
[AuthModal] Email verification failed: Error: ...
```

**Method 2: Manual Email Verification Handling**

```typescript
// Verify email (get verify_token from email link)
const urlParams = new URLSearchParams(window.location.search);
const verifyToken = urlParams.get('verify_token');

if (verifyToken) {
  const result = await client.verifyEmail(verifyToken);

  // Auto-login: save returned token
  localStorage.setItem('token', result.data.token);
  localStorage.setItem('refreshToken', result.data.refreshToken);

  console.log('Email verified successfully, auto-logged in:', result.data.user);

  // Redirect to homepage
  window.location.href = '/';
}
```

#### Invitation Code Binding (Account Activation)

When registering with external email or OAuth login without providing invitation code, a temporary account is created and requires subsequent invitation code binding for activation.

**Apply for Invitation Code Feature**:

If user doesn't have an invitation code, AuthModal provides built-in application feature:
1. In invitation code input interface, user can click "Don't have an invitation code? Apply for one" link
2. Automatically jumps to apply for invitation code form
3. User fills in email and application reason (10-500 characters)
4. After submission, calls `applyInvite()` API, after backend approval, invitation code will be sent via email

**Auto Redirect Mechanism**:
- When `login()` returns `INVITE_CODE_REQUIRED` error and includes `redirectUrl`, SDK automatically redirects page to that URL
- redirectUrl usually contains `error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx` parameters
- No need to manually handle redirect logic, SDK completes automatically

For other scenarios (such as redirects after OAuth login), backend directly redirects to `frontend_url?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx`.

**Method 1: Use AuthModal Auto-Handling (Recommended)**

```typescript
import { AuthModal } from '@seaspark/auth-sdk';

// Create AuthModal instance
const modal = new AuthModal({
  client,
  onLoginSuccess: (token, user) => {
    localStorage.setItem('token', token);
    console.log('Login successful:', user);
  },
  onInviteCodeRequired: (userId, email) => {
    // Optional: custom handling when invitation code is needed
    console.log('Need invitation code to activate account:', userId, email);
  }
});

// AuthModal will automatically detect the following parameter combinations in URL:
// - error_code=INVITE_CODE_REQUIRED
// - user_id or temp_user_id (user ID)
// - email (optional, user email)
//
// After detection, it will automatically:
// 1. Show invitation code input interface
// 2. After user inputs invitation code, call bindInviteCode() API
// 3. After successful activation, auto-login (save token)
// 4. Clean parameters from URL
```

**Debugging Tips**:

If invitation code modal doesn't appear, check browser console for the following logs:

```javascript
// Normal case should show:
[AuthModal] Detected INVITE_CODE_REQUIRED: {
  errorCode: "INVITE_CODE_REQUIRED",
  userId: "xxx",
  tempUserId: null,
  email: "user@example.com",
  fullURL: "http://localhost:8001/?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx"
}

// If user_id is missing, will show error:
[AuthModal] Missing user_id in URL parameters.
// And shows alert with full URL for troubleshooting backend redirect issues
```

**Method 2: Manual Invitation Code Binding Handling**

```typescript
// 1. Check if invitation code is needed in URL
const urlParams = new URLSearchParams(window.location.search);
const errorCode = urlParams.get('error_code');
const userId = urlParams.get('user_id');

if (errorCode === 'INVITE_CODE_REQUIRED' && userId) {
  // 2. Show invitation code input interface (custom UI)
  const inviteCode = await showInviteCodeInput(); // Your custom UI

  // 3. Bind invitation code
  const result = await client.bindInviteCode({
    user_id: userId,
    invite_code: inviteCode
  });

  // 4. Activation successful, auto-login
  localStorage.setItem('token', result.data.token);
  localStorage.setItem('refreshToken', result.data.refreshToken);
  console.log('Account activated successfully:', result.data.user);

  // 5. Redirect to homepage
  window.location.href = '/';
}
```

**Method 3: Use Static Method Handling**

```typescript
import { AuthModal } from '@seaspark/auth-sdk';

// AuthModal provides static method to handle invitation code scenario
const inviteCodeInfo = AuthModal.handleInviteCodeRequired(
  { client },
  (userId, email) => {
    // Custom handling logic
    const code = prompt(`Please enter invitation code to activate account (${email}):`);
    if (code) {
      client.bindInviteCode({ user_id: userId, invite_code: code })
        .then(res => {
          localStorage.setItem('token', res.data.token);
          window.location.reload();
        });
    }
  }
);
```

### 10. Other Features

```typescript
// Get API Service Token
const apiToken = await client.getApiServiceToken();

// Get conversation status
const status = await client.getConversationStatus({
  conversationId: 'conv-123',
});

// Get speech token
const speechToken = await client.getSpeechToken();
```

## Advanced Configuration

### Custom Authentication

```typescript
import { AuthFactory } from '@seaspark/auth-sdk';

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Application ID
  environment: 'production',

  // Use JWT authentication
  auth: AuthFactory.create({
    type: 'jwt',
    credentials: {
      type: 'jwt',
      token: localStorage.getItem('token'),
    },
  }),
});
```

### Custom Hooks

```typescript
import { BuiltInHooks } from '@seaspark/auth-sdk';

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Application ID
  environment: 'production',

  hooks: {
    hooks: [
      // Logger Hook
      BuiltInHooks.createLoggerHook({
        logLevel: 'debug',
        logRequestBody: true,
        logResponseBody: true,
      }),

      // Request ID Hook
      BuiltInHooks.createRequestIdHook(),

      // Custom Hook
      {
        type: 'beforeRequest',
        name: 'custom-hook',
        priority: 100,
        handler: async (context) => {
          console.log('Before request:', context.config.url);
        },
      },
    ],
  },
});
```

### Request Retry Configuration

The SDK supports custom HTTP request retry strategies. **By default, retry functionality is disabled (maxRetries: 0)** to ensure request predictability.

#### Default Behavior (Retry Disabled)

```typescript
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  // retryOptions not set, no retry by default
});

// If request fails, will return error immediately, no retry
```

#### Enable Basic Retry

```typescript
import { SeaVerseBackendAPIClient } from '@seaspark/auth-sdk';

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  retryOptions: {
    maxRetries: 3,        // Retry up to 3 times
    retryDelay: 1000,     // Initial delay 1000ms (1 second)
    // Defaults to retry on these status codes: [408, 429, 500, 502, 503, 504]
  },
});
```

**Retry Strategy Explanation**:
- Uses **exponential backoff** algorithm: 1st retry waits 1 second, 2nd waits 2 seconds, 3rd waits 4 seconds
- HTTP status codes that trigger auto-retry:
  - `408` - Request Timeout
  - `429` - Too Many Requests (rate limiting)
  - `500` - Internal Server Error
  - `502` - Bad Gateway
  - `503` - Service Unavailable
  - `504` - Gateway Timeout
- Network errors (no response) will also trigger retry

#### Custom Retry Status Codes

```typescript
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  retryOptions: {
    maxRetries: 5,
    retryDelay: 2000,
    retryStatusCodes: [503, 504],  // Only retry on 503 and 504
  },
});
```

#### Custom Retry Logic

```typescript
import type { RetryOptions } from '@seaspark/auth-sdk';

const retryOptions: RetryOptions = {
  maxRetries: 3,
  retryDelay: 1000,
  // Custom judgment logic: only retry on specific errors
  shouldRetry: (error) => {
    // Only retry on service unavailable errors
    if (error.response?.status === 503) {
      return true;
    }
    // Retry on network errors with no response
    if (!error.response) {
      return true;
    }
    return false;
  },
};

const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',
  retryOptions,
});
```

#### RetryOptions Type Definition

```typescript
interface RetryOptions {
  maxRetries?: number;           // Maximum retry count, default 0 (disabled)
  retryDelay?: number;           // Initial retry delay (milliseconds), default 1000
  retryStatusCodes?: number[];   // List of status codes that trigger retry
  shouldRetry?: (error: AxiosError) => boolean;  // Custom retry judgment function
}
```

**Usage Recommendations**:
- ⚠️ **Recommended to disable retry in production** (default behavior), avoid duplicate requests on business logic errors
- ✅ Only enable retry in unstable network scenarios, such as mobile apps, weak network environments
- ✅ Ensure backend APIs support idempotent operations, avoid side effects from retries
- ✅ For critical business (such as payments), recommended to use custom `shouldRetry` to carefully control retry logic

### Environment Configuration

The SDK supports the following environments:

| Environment | Description | BaseURL |
|------|------|---------|
| `production` | Production environment | `https://seaspark-account-hub.dev.seaspark.ai` (待更新) |
| `staging` | Staging environment | `https://seaspark-account-hub.dev.seaspark.ai` |
| `development` | Development environment | `https://seaspark-account-hub.dev.seaspark.ai` |
| `local` | Local environment | `http://localhost:8001` |

Auto-detection rules:
- `*.seaverse.com` → production
- `*.staging.seaverse.dev` → staging
- `*.dev.seaverse.dev` → development
- `localhost` → local

## API Reference

### Authentication Related

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` | Register new user, frontend_url is frontend URL for email verification link, defaults to window.location.href |
| `login()` | `{ email, password, frontend_url? }` | `LoginResponse` | User login, frontend_url used for sending verification email when email not verified, defaults to https://seaverse.ai/. ⚠️ If returns INVITE_CODE_REQUIRED with redirectUrl, will automatically redirect |
| `getCurrentUser()` | - | `User` | Get current user |
| `logout()` | - | `SuccessResponse` | Logout |
| `verifyEmail()` | `verifyToken: string` | `EmailVerificationResponse` | Verify email and return auto-login token |
| `bindInviteCode()` | `{ user_id, invite_code }` | `BindInviteCodeResponse` | Bind invitation code to activate temporary account and auto-login |
| `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` | Forgot password, frontend_url defaults to window.location.href |
| `resetPassword()` | `{ token, new_password }` | `SuccessResponse` | Reset password |
| `setToken()` | `token: string` | `void` | Set authentication token (used after OAuth login) |
| `isInIframe()` | - | `boolean` | Check if application is running in iframe (supports static method and instance method) |
| `getIframeToken()` | `{ timeout? }` | `Promise<string>` | Get token from parent page (iframe scenario only), timeout defaults to 30000ms |
| `getApiServiceToken()` | - | `ApiServiceTokenResponse` | Get API Token |

#### Registration Flow Description

The `register()` method supports three registration flows, SDK automatically handles based on backend response:

**Flow 1: Immediate Activation (Default)**
```typescript
const response = await client.register({ email, password });
// response.success === true
// response.requiresEmailVerification === undefined (or false)
// response.requiresInvitationCode === undefined (or false)
// → SDK automatically calls login() and triggers onSignupSuccess callback
```

**Flow 2: Requires Email Verification**
```typescript
const response = await client.register({ email, password });
// response.success === true
// response.requiresEmailVerification === true
// → SDK shows Toast prompting user to check email
// → Does not auto-call login(), user needs to click verification link in email
```

**Flow 3: Requires Invitation Code Activation**
```typescript
const response = await client.register({ email, password });
// response.success === true
// response.requiresInvitationCode === true
// response.tempUserId === "temp_xxx"
// → SDK shows invitation code input form
// → Does not auto-call login(), user needs to input invitation code
```

**Important Note**:
- AuthModal automatically handles these three flows, no manual judgment needed
- Only when no verification or activation is needed, will `login()` be automatically called
- This avoids unnecessary login requests when email is not verified or account is not activated

### OAuth Related

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `googleAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Google OAuth authorization URL |
| `discordAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | Discord OAuth authorization URL |
| `githubAuthorize()` | `{ return_url? }` | `OAuthAuthorizeResponse` | GitHub OAuth authorization URL |
| `unlinkGoogle()` | - | `SuccessResponse` | Unlink Google |
| `unlinkDiscord()` | - | `SuccessResponse` | Unlink Discord |
| `unlinkGithub()` | - | `SuccessResponse` | Unlink GitHub |

### Container Management

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `listContainers()` | - | `ContainerListResponse` | List containers |
| `registerContainer()` | `{ containerId, metadata }` | `SuccessResponse` | Register container |
| `getContainer()` | `{ containerId }` | `Container` | Get container info |
| `unregisterContainer()` | `{ containerId }` | `SuccessResponse` | Unregister container |
| `containerHeartbeat()` | `{ containerId, status }` | `SuccessResponse` | Container heartbeat |
| `getContainerStats()` | - | `ContainerStatsResponse` | Container stats |

### Skill Marketplace

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `listMarketplaceSkills()` | `{ category?, page?, pageSize? }` | `MarketplaceSkillsListResponse` | List marketplace skills |
| `getMarketplaceSkill()` | `{ skillId }` | `MarketplaceSkill` | Get skill details |
| `installSkill()` | `{ skillId }` | `SuccessResponse` | Install skill |
| `listUserSkills()` | - | `UserInstalledSkillsListResponse` | List installed skills |
| `uninstallSkill()` | `{ skillId }` | `SuccessResponse` | Uninstall skill |

### Invitation Code Management

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `applyInvite()` | `{ email, reason }` | `ApplyInviteResponse` | Apply for invitation code (reason needs 10-500 characters)|
| `listInvites()` | `{ page?, page_size?, status? }` | `ListInvitesResponse` | List my invitation codes |
| `getInviteStats()` | - | `InviteStatsResponse` | Get invitation code stats |
| `getInvite()` | `inviteId: string` | `InviteCodeDetailResponse` | Get invitation code details |
| `getInviteUsages()` | `inviteId: string, { page?, page_size? }` | `ListInviteUsagesResponse` | Get invitation code usage records |

### Others

| Method | Parameters | Return Value | Description |
|------|------|--------|------|
| `getHealth()` | - | `HealthResponse` | Health check |
| `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` | Get conversation status |
| `getSpeechToken()` | - | `SpeechTokenResponse` | Get speech token |

## Response Format Compatibility

### Auto Response Unwrapping

Starting from v0.2.5, SDK automatically supports two API response formats, no manual handling needed:

**Format 1: Wrapped Format** (Recommended)
```json
{
  "data": {
    "token": "eyJhbGc...",
    "user": { ... }
  },
  "success": true
}
```

**Format 2: Flat Format** (Backward Compatible)
```json
{
  "token": "eyJhbGc...",
  "user": { ... }
}
```

SDK automatically detects response format and extracts correct data:

```typescript
// Regardless of which format backend returns, the following code works
const loginResult = await client.login({
  email: 'user@example.com',
  password: 'password123',
});

console.log(loginResult.token); // ✅ Always gets token correctly
console.log(loginResult.user);  // ✅ Always gets user correctly
```

### Affected Methods

The following methods have implemented auto response unwrapping:

- ✅ `login()` - Login API
- ✅ `register()` - Registration API
- ✅ `getCurrentUser()` - Get current user

### Implementation Principle

SDK internally handles response format through the following logic:

```typescript
const response = await httpClient.request(config);
const responseData = response.data;

// Detect and unwrap
if (responseData.data && typeof responseData.data === 'object') {
  // Wrapped format: extract data field
  return responseData.data;
}
// Flat format: return directly
return responseData;
```

This means:
- 🔄 **Backend format changes don't require frontend modifications** - Backend can freely adjust response format
- 🔧 **Progressive migration** - Different APIs can be migrated to new format gradually
- ✅ **Backward compatible** - Old code continues to work without modifications

## Error Handling

### Error Response Format

All API errors follow a unified response format:

```typescript
interface ApiError {
  success: false;           // Always false on error
  error: string;            // Human-readable error message
  code?: string;            // Machine-readable error code
  details?: any;            // Additional error details
}
```

### Error Codes

SDK provides standard error code enums:

```typescript
import { ErrorCode } from '@seaspark/auth-sdk';

// Account-related errors
ErrorCode.ACCOUNT_EXISTS         // Account already exists
ErrorCode.ACCOUNT_NOT_FOUND      // Account not found
ErrorCode.ACCOUNT_SUSPENDED      // Account suspended
ErrorCode.INVALID_CREDENTIALS    // Invalid login credentials
ErrorCode.EMAIL_NOT_VERIFIED     // Email not verified

// Validation-related errors
ErrorCode.INVALID_EMAIL          // Invalid email address
ErrorCode.INVALID_PASSWORD       // Invalid password
ErrorCode.PASSWORD_TOO_WEAK      // Password too weak

// Invitation code-related errors
ErrorCode.INVALID_INVITATION_CODE // Invalid invitation code
ErrorCode.INVITATION_REQUIRED     // Invitation code required
ErrorCode.INVITE_CODE_REQUIRED    // Invitation code required (will auto-redirect to redirectUrl)

// Token-related errors
ErrorCode.INVALID_TOKEN          // Invalid token
ErrorCode.TOKEN_EXPIRED          // Token expired

// Internal errors
ErrorCode.INTERNAL_ERROR         // Server internal error
```

### Error Handling Best Practices

#### 1. Handle Duplicate Users During Registration

Since backend returns 200 OK when account exists (success response), frontend won't throw exception, but includes error info in response body.

```typescript
import { ErrorCode } from '@seaspark/auth-sdk';

const result = await client.register({
  email: 'user@example.com',
  password: 'password123',
});

// Check success field in response
if (result.success) {
  console.log('Registration successful:', result);
  // Proceed with follow-up actions, such as auto-login
} else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
  // Account exists, prompt user
  const { email, app_id } = result.details;
  console.log(`Account ${email} already exists in application ${app_id}`);

  // Show friendly prompt
  alert('This email is already registered. Please login instead.');

  // Or guide user to login
  showLoginModal();
} else {
  // Handle other errors
  console.error('Registration failed:', result.error);
}
```

#### 2. Handle Various Errors During Login

```typescript
try {
  const result = await client.login({
    email: 'user@example.com',
    password: 'wrong-password',
  });
  console.log('Login successful:', result);
} catch (error) {
  const errorCode = error.response?.data?.code;

  switch (errorCode) {
    case ErrorCode.INVALID_CREDENTIALS:
      showError('Incorrect username or password');
      break;
    case ErrorCode.EMAIL_NOT_VERIFIED:
      showError('Please verify your email first');
      showResendVerificationButton();
      break;
    case ErrorCode.ACCOUNT_SUSPENDED:
      showError('Your account has been suspended, please contact administrator');
      break;
    default:
      showError('Login failed, please try again later');
  }
}
```

#### 3. Generic Error Handling Function

```typescript
import { ErrorCode } from '@seaspark/auth-sdk';

function handleApiError(error: any) {
  const apiError = error.response?.data;

  if (!apiError) {
    // Network error or other unknown error
    return {
      title: 'Network Error',
      message: 'Please check your network connection',
    };
  }

  // Return user-friendly messages based on error code
  const errorMessages: Record<string, { title: string; message: string }> = {
    [ErrorCode.ACCOUNT_EXISTS]: {
      title: 'Account Exists',
      message: 'This email is already registered, please login directly',
    },
    [ErrorCode.INVALID_CREDENTIALS]: {
      title: 'Login Failed',
      message: 'Incorrect username or password',
    },
    [ErrorCode.EMAIL_NOT_VERIFIED]: {
      title: 'Email Not Verified',
      message: 'Please verify your email address first',
    },
    [ErrorCode.INVALID_INVITATION_CODE]: {
      title: 'Invalid Invitation Code',
      message: 'Please check if your invitation code is correct',
    },
    [ErrorCode.TOKEN_EXPIRED]: {
      title: 'Login Expired',
      message: 'Please login again',
    },
  };

  return errorMessages[apiError.code] || {
    title: 'Operation Failed',
    message: apiError.error || 'Unknown error occurred',
  };
}

// Usage example
try {
  await client.register({ email, password });
} catch (error) {
  const { title, message } = handleApiError(error);
  showNotification(title, message);
}
```

#### 4. TypeScript Type-Safe Error Handling

```typescript
import { ErrorCode, models } from '@seaspark/auth-sdk';

async function safeRegister(email: string, password: string) {
  // Register API now returns 200 OK for both success and account exists cases
  const result = await client.register({ email, password });

  if (result.success) {
    return {
      success: true,
      data: result,
    };
  } else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
    // TypeScript knows the type of details
    const details = result.details as models.AccountExistsErrorDetails;
    return {
      success: false,
      error: 'ACCOUNT_EXISTS',
      email: details.email,
      appId: details.app_id,
    };
  } else {
    return {
      success: false,
      error: result.error || 'Unknown error',
    };
  }
}

// Usage
const registerResult = await safeRegister('user@example.com', 'password123');
if (registerResult.success) {
  console.log('Registration successful');
} else if (registerResult.error === 'ACCOUNT_EXISTS') {
  console.log(`Account already exists: ${registerResult.email}`);
}
```

### HTTP Status Code Reference

| HTTP Status Code | Error Code Example | Description |
|-----------|----------|------|
| 200 OK | `ACCOUNT_EXISTS` | Account exists (business error but returns success response) |
| 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` | Invalid request parameters |
| 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` | Authentication failed |
| 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` | Insufficient permissions |
| 500 Internal Server Error | `INTERNAL_ERROR` | Server internal error |

**Note**: For registration API, account exists case returns 200 OK, but in response body `success: false` and `code: "ACCOUNT_EXISTS"`. This design makes it easier for frontend to handle this common business scenario.

## Type Definitions

```typescript
// User info
interface User {
  id?: string;
  app_id?: string | null;           // Application ID (multi-tenant support)
  email?: string;
  username?: string;
  created_at?: number;               // Unix timestamp
  email_verified?: boolean;          // Email verification status
  google_id?: string | null;         // Google account ID
  discord_id?: string | null;        // Discord account ID
  github_id?: string | null;         // GitHub account ID

  // Deprecated fields (backward compatible)
  createdAt?: number;                // @deprecated Use created_at
  emailVerified?: boolean;           // @deprecated Use email_verified
  googleId?: string;                 // @deprecated Use google_id
  discordId?: string;                // @deprecated Use discord_id
  githubId?: string;                 // @deprecated Use github_id
}

// Login response
interface LoginResponse {
  token: string;
  user: User;
}

// Registration response
interface RegisterResponse {
  success: boolean;
  message?: string;
  userId?: string;
  // Email verification related
  requiresEmailVerification?: boolean;  // Whether email verification is required
  // Invitation code activation related
  requiresInvitationCode?: boolean;     // Whether invitation code activation is required
  tempUserId?: string;                  // Temporary user ID (needs activation)
  // Error info
  error?: string;
  code?: string;                        // E.g. 'ACCOUNT_EXISTS'
  details?: Record<string, any>;
}

// AuthModal options
interface AuthModalOptions {
  client: SeaVerseBackendAPIClient;
  theme?: 'dark' | 'light';
  onLoginSuccess?: (token: string, user: any) => void;
  onSignupSuccess?: (token: string, user: any) => void;
  onInviteCodeRequired?: (userId: string, email: string) => void; // Callback when invitation code activation is needed
  onApplyInviteSuccess?: (applicationId: string, email: string) => void; // Callback when invitation code application succeeds
  onError?: (error: Error) => void;
  returnUrl?: string;        // URL to return after OAuth login, optional, defaults to window.location.href
  enableOAuth?: {
    google?: boolean;        // Enable Google login
    discord?: boolean;       // Enable Discord login
    github?: boolean;        // Enable GitHub login
  };
}
```

## Complete Examples

Check [examples/auth-sdk-demo](../../examples/auth-sdk-demo) directory for complete runnable examples.

```bash
# Run example
cd examples/auth-sdk-demo
pnpm install
pnpm dev
```

## Migration from Old Versions

### v0.1.x → v0.2.0 Upgrade Guide

#### 1. Backward Compatibility
Good news! v0.2.0 is fully backward compatible, existing code **works without modifications**.

#### 2. Recommended Migration Steps

While not required, we recommend gradually migrating to new field naming conventions:

```typescript
// Old code (still works)
const user = await client.getCurrentUser();
console.log(user.emailVerified);  // ⚠️ Deprecated but works
console.log(user.createdAt);      // ⚠️ Deprecated but works

// New code (recommended)
const user = await client.getCurrentUser();
console.log(user.email_verified); // ✅ Recommended
console.log(user.created_at);     // ✅ Recommended
console.log(user.app_id);         // ✅ New field: multi-tenant support
```

#### 3. Registration API Update

```typescript
// Old code (still works)
await client.register({
  email: 'user@example.com',
  password: 'password123',
  invitationCode: 'INVITE123',  // ⚠️ Deprecated but works
});

// New code (recommended)
await client.register({
  email: 'user@example.com',
  password: 'password123',
  username: 'myusername',       // ✨ New feature
  invitation_code: 'INVITE123', // ✅ Recommended
});
```

#### 4. Password Reset API Update

```typescript
// Old code (needs update)
await client.resetPassword({
  token: 'reset-token',
  newPassword: 'NewPass123',    // ⚠️ Deprecated
});

// New code (recommended)
await client.resetPassword({
  token: 'reset-token',
  new_password: 'NewPass123',   // ✅ Recommended
});
```

#### 5. TypeScript Type Hints

If you use TypeScript, compiler will automatically hint deprecated fields:

```typescript
const user = await client.getCurrentUser();
user.emailVerified;  // TypeScript will show strikethrough and deprecation warning
user.email_verified; // ✅ No warning
```

#### 6. Multi-Tenant Feature (New)

If your application needs multi-tenant architecture support, you can use the new `app_id` field:

```typescript
const user = await client.getCurrentUser();

console.log('your app id:', user.app_id);
```

## FAQ

### How to Handle Authentication Token?

```typescript
// Save token after successful login
const loginResult = await client.login({ email, password });
localStorage.setItem('token', loginResult.token);

// Create authenticated client
import { AuthFactory } from '@seaspark/auth-sdk';

const authenticatedClient = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Application ID
  environment: 'production',
  auth: AuthFactory.create({
    type: 'jwt',
    credentials: {
      type: 'jwt',
      token: localStorage.getItem('token'),
    },
  }),
});
```

### OAuth redirect_uri_mismatch Error?

Ensure the redirect URI in OAuth application configuration exactly matches the `redirectUri` in code (including protocol, domain, port, path).

### How to Customize Request Timeout?

```typescript
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Application ID
  environment: 'production',
  timeout: 30000,            // 30 seconds
});
```

### How to Connect to Local API in Local Development?

```typescript
const client = new SeaVerseBackendAPIClient({
  appId: 'your app id',         // Required: Application ID
  environment: 'local',      // Or use baseURL: 'http://localhost:8001'
});
```

## Development

```bash
# Install dependencies
pnpm install

# Build
pnpm build

# Watch mode
pnpm dev

# Test
pnpm test
```

## Related Links

- 📦 [NPM Package](https://www.npmjs.com/package/@seaspark/auth-sdk)
- 📖 [Example Project](../../examples/auth-sdk-demo)
- 🐛 [Issues](https://github.com/seaverseai/sv-sdk/issues)
- 💬 [Discussions](https://github.com/seaverseai/sv-sdk/discussions)

## License

MIT © [SeaSpark Team](mailto:support@seaverse.com)

## Changelog

### v0.3.6 (Current Version)
- 🧹 **Code Cleanup**: Remove desktop application OAuth callback related functionality
  - Remove `oauthDesktopURL` configuration option
  - Simplify OAuth flow, unified use of `returnUrl` parameter
  - Clean up related documentation and code comments

### v0.2.5
- 🔄 **Response Format Compatibility**: Automatic compatibility with wrapped and flat API response formats
  - Fix "Invalid response from server" prompt during login
  - `login()`, `register()`, `getCurrentUser()` methods auto-unwrap `data` field
  - Support two formats: `{ data: {...}, success: true }` and `{ ... }`
- 📝 **Documentation Update**: Add response format compatibility section

### v0.2.0
- 🔄 **API Path Update**: All authentication APIs migrated from `/api/auth/*` to `/sdk/v1/auth/*`
- 🏢 **Multi-Tenant Support**: User model adds `app_id` field, supports multi-application isolation
- 🔑 **Important Change**: `appId` is now a **required parameter**, SDK automatically adds `X-App-ID` request header to all requests
- ✨ **New Features**:
  - Registration supports custom `username` (optional)
  - All fields standardized to snake_case (retains camelCase backward compatibility)
- 📝 **Field Updates**:
  - `SeaVerseBackendAPIClientOptions`: Add required field `appId`
  - `User`: Add `app_id`, `created_at`, `email_verified`, `google_id`, `discord_id`, `github_id`
  - `RegisterRequest`: Add `username`, `invitation_code`
  - `ResetPasswordRequest`: `newPassword` → `new_password`
- ⚠️ **Breaking Changes**:
  - Must provide `appId` parameter to initialize client
  - API path changes require backend support
- ✅ **Backward Compatible**: Except for `appId`, all old fields still work

### v0.1.5
- Fix OAuth state management, switch from sessionStorage to localStorage
- Enhance API response handling

### v0.1.4
- Enhance API response handling
- Optimize error handling

### v0.1.2
- Update package name to @seaspark/auth-sdk
- Add multi-environment support

View complete changelog: [CHANGELOG.md](./CHANGELOG.md)