# echofi-client

A TypeScript/Node.js client library for EchoFi services with **Level 3 Ultimate API**, gRPC-Web, and WebSocket clients for music streaming, user management, activity tracking, and real-time communication.

## 🚀 New in v2.4.0: Level 3 Ultimate API

**67% code reduction** with the new simplified JSON parameter syntax:

```typescript
// Before (Level 2)
const request = Helpers.Music.createListArtistsRequest({ offset: 0, limit: 20 });
const response = await client.music.listArtists(request);

// After (Level 3) ✨
const response = await client.listArtists({ offset: 0, limit: 20 });
```

**34 enhanced methods** across all services with smart type detection and full backward compatibility!

## 📦 Installation

```bash
npm install echofi-client
```

## 🚀 Proto-Generated Client

This client uses generated code directly from your proto files, ensuring it's always in sync with your service definitions. Simply run `npm run update` after updating your proto files!

## Features

- 🎵 **Music Service**: Artists, albums, tracks, and genres management
- 👤 **User Service**: User management and operations
- 📊 **Activity Service**: Listening history, favorites, playlists, and point rates
- 📈 **Stats Service**: User statistics, points, and boost management
- 🔌 **WebSocket Client**: Real-time listening sessions and point updates
- 🤖 **gRPC-Web Client**: Browser-compatible with automatic buffer conversion
- 🛡️ **Error Handling**: Comprehensive error handling and logging
- 📝 **TypeScript Ready**: Full TypeScript support with generated types
- 🌐 **Browser Ready**: Works in all modern browsers with automatic protobuf serialization
- 🚀 **Simple Setup**: Unified client for all environments

## Installation

```bash
# Clone the repository
git clone <repository-url>
cd client

# Install dependencies
npm install
```

## 🔧 Quick Setup

### Database and Backend Setup

1. **Initialize the database** (from the backend directory):
```bash
cd backend
make init-db
make load-sample-data
```

2. **Start the backend server**:
```bash
cd backend
make run
# Or with environment variables:
DB_HOST=localhost DB_PORT=5432 DB_USER=testing DB_PASSWORD=suckupyourmouth DB_NAME=echofi2 ./server
```

The backend will start:
- **gRPC Server**: `localhost:50051` (for Node.js clients)
- **gRPC-Web Server**: `localhost:8080` (for browser clients)
- **WebSocket Server**: `localhost:8080/ws` (for real-time updates)

### Client Setup

1. **Install dependencies:**
   ```bash
   npm install
   ```

2. **Update proto files (if needed):**
   ```bash
   npm run update
   ```

3. **Test the client:**
   ```bash
   npm test
   ```

4. **Run examples:**
   ```bash
   # Basic usage
   npm run example

   # Comprehensive demo
   npm run example:comprehensive
   ```

## 🤖 Proto Update Workflow

When you update your proto files, simply run:

```bash
npm run update
```

This will:
- 📄 Generate new JavaScript code from protos
- 🧪 Run tests to verify everything works

**Available update commands:**
```bash
npm run update           # Generate proto + test
npm run update-proto     # Force proto regeneration
npm run generate-proto   # Generate proto files with change detection
```

## Quick Start

### 🤖 gRPC Client

```javascript
const { GrpcClient } = require('echofi-client');

async function main() {
  // Create and initialize gRPC client
  const client = new GrpcClient({
    host: 'localhost',
    port: 50051
  });

  await client.initialize();

  // Use the services with proto method names
  const user = await client.user.callAsync('getUser', { id: 'user123' });
  const artists = await client.music.callAsync('listArtists', { offset: 0, limit: 10 });
  const stats = await client.stats.callAsync('getUserStatistics', { user_id: 'user123' });

  // Always close when done
  client.close();
}

main().catch(console.error);
```

### 🔌 WebSocket Client

```javascript
const { WebSocketClient } = require('echofi-client');

async function main() {
  // Create WebSocket client for real-time features
  const wsClient = new WebSocketClient({
    url: 'ws://localhost:8080/ws',
    autoReconnect: true
  });

  // Setup event listeners
  wsClient.on('point_update', (pointData) => {
    console.log(`Earned ${pointData.points_earned} points!`);
  });

  wsClient.on('session_update', (sessionData) => {
    console.log('Session updated:', sessionData);
  });

  // Connect with authentication
  await wsClient.connect('your-auth-token');

  // Start listening session
  await wsClient.startListeningSession(123);

  // Always disconnect when done
  wsClient.disconnect();
}

main().catch(console.error);
```

### 🌐 Using Both Clients Together

```javascript
const { GrpcClient, WebSocketClient } = require('echofi-client');

async function main() {
  // Create both clients
  const grpcClient = new GrpcClient({ host: 'localhost', port: 50051 });
  const wsClient = new WebSocketClient({ url: 'ws://localhost:8080/ws' });

  // Initialize gRPC client
  await grpcClient.initialize();

  // Connect WebSocket for real-time updates
  await wsClient.connect('your-auth-token');

  // Use gRPC for data operations
  const user = await grpcClient.user.callAsync('getUser', { id: 'user123' });

  // Use WebSocket for real-time features
  await wsClient.startListeningSession(123);

  // Cleanup
  grpcClient.close();
  wsClient.disconnect();
}

main().catch(console.error);
```

## Configuration

### gRPC Client Configuration

```javascript
const { GrpcClient } = require('echofi-client');

// Basic configuration
const client = new GrpcClient({
  host: 'localhost',        // Server host
  port: 50051,             // Server port
});

// Advanced configuration with SSL
const grpc = require('@grpc/grpc-js');

const client = new GrpcClient({
  host: 'your-server.com',
  port: 443,
  credentials: grpc.credentials.createSsl(), // Use SSL in production
  options: {
    'grpc.keepalive_time_ms': 30000,
    'grpc.keepalive_timeout_ms': 5000,
    'grpc.keepalive_permit_without_calls': true
  }
});
```

### WebSocket Client Configuration

```javascript
const { WebSocketClient } = require('echofi-client');

// Basic configuration
const wsClient = new WebSocketClient({
  url: 'ws://localhost:8080/ws',
  autoReconnect: true,
  reconnectInterval: 5000,
  maxReconnectAttempts: 10
});

// Advanced configuration
const wsClient = new WebSocketClient({
  url: 'wss://your-server.com/ws',  // Use WSS in production
  autoReconnect: true,
  reconnectInterval: 3000,
  maxReconnectAttempts: 5,
  heartbeatInterval: 30000
});
```

## Services Overview

### User Service

```javascript
// Get user by ID
const user = await client.user.callAsync('getUser', { id: 'user123' });

// List users with pagination
const users = await client.user.callAsync('listUsers', {
  offset: 0,
  limit: 10
});
```

### Music Service

```javascript
// Get artist information
const artist = await client.music.callAsync('getArtist', { artist_id: 1 });

// List tracks with filters
const tracks = await client.music.callAsync('listTracks', {
  offset: 0,
  limit: 20,
  artist_id: 1,
  genre_id: 5
});

// List artists
const artists = await client.music.callAsync('listArtists', { offset: 0, limit: 10 });
```

### Activity Service

```javascript
// Get user's listening history
const history = await client.activity.callAsync('listListeningHistory', {
  offset: 0,
  limit: 50,
  user_id: 'user123'
});

// Get user favorites
const favorites = await client.activity.callAsync('listUserFavorites', {
  offset: 0,
  limit: 10,
  user_id: 'user123'
});

// Get user playlists
const playlists = await client.activity.callAsync('listPlaylists', {
  offset: 0,
  limit: 10,
  created_by: 'user123',
  is_public: true
});

// Get playlist tracks
const playlistTracks = await client.activity.callAsync('listPlaylistTracks', {
  playlist_id: 1,
  offset: 0,
  limit: 10
});
```

### Stats Service

```javascript
// Get user statistics
const stats = await client.stats.callAsync('getUserStatistics', { user_id: 'user123' });

// Get user points
const points = await client.stats.callAsync('getUserPoints', { user_id: 'user123' });

// Get point boosts
const boosts = await client.stats.callAsync('listPointBoosts', {
  offset: 0,
  limit: 10,
  user_id: 'user123',
  is_active: true
});
```

## 🔌 WebSocket Real-time Features

The WebSocket client provides real-time communication for listening sessions and point updates:

```javascript
const { WebSocketClient } = require('echofi-client');

const wsClient = new WebSocketClient({
  url: 'ws://localhost:8080/ws',
  autoReconnect: true
});

// Setup event listeners
wsClient.on('point_update', (pointData) => {
  console.log(`Earned ${pointData.points_earned} points!`);
  updatePointsDisplay(pointData.total_points);
});

wsClient.on('session_update', (sessionData) => {
  console.log('Session status:', sessionData.status);
  updateSessionDisplay(sessionData);
});

wsClient.on('connected', () => {
  console.log('WebSocket connected');
});

wsClient.on('disconnected', (data) => {
  console.log('WebSocket disconnected:', data);
});

// Connect and start session
await wsClient.connect('your-auth-token');
await wsClient.startListeningSession(trackId);
```

## 🔐 Authentication

The EchoFi backend uses NextAuth.js session tokens for authentication. Here's how to integrate with your frontend:

### Getting Session Tokens

```javascript
// In your Next.js API route or server component
import { getServerSession } from "next-auth/next"
import { authOptions } from "@/lib/auth"

const session = await getServerSession(authOptions)
const sessionToken = session?.sessionToken
```

### Making Authenticated Requests

```javascript
const { GrpcClient } = require('echofi-client');

const client = new GrpcClient();
await client.initialize();

// Pass session token in metadata for protected endpoints
const user = await client.user.callAsync('GetUser',
  { id: '' }, // Empty ID returns authenticated user's data
  { authorization: sessionToken }
);

// Public endpoints don't require authentication
const artists = await client.music.callAsync('ListArtists', {
  offset: 0,
  limit: 10
});
```

### Authentication Helper Class

```javascript
class EchoFiAuthenticatedClient {
  constructor(config = {}) {
    this.client = new GrpcClient(config);
    this.sessionToken = null;
  }

  async initialize() {
    await this.client.initialize();
  }

  setSessionToken(token) {
    this.sessionToken = token;
  }

  // Helper for authenticated requests
  async authenticatedCall(service, method, request = {}) {
    if (!this.sessionToken) {
      throw new Error('No session token provided');
    }
    return await this.client[service].callAsync(method, request, {
      authorization: this.sessionToken
    });
  }

  // Helper for public requests
  async publicCall(service, method, request = {}) {
    return await this.client[service].callAsync(method, request);
  }
}
```

### Error Handling

```javascript
try {
  const user = await client.user.callAsync('GetUser',
    { id: '' },
    { authorization: sessionToken }
  );
} catch (error) {
  if (error.code === 16) { // UNAUTHENTICATED
    // Redirect to login or refresh session
    console.log('Authentication required');
  } else {
    console.error('Request failed:', error.message);
  }
}
```

### Protected vs Public Endpoints

**Protected Endpoints (require authentication):**
- `user.UserService/GetUser`
- `activity.ActivityService/*` (listening sessions, favorites, playlists)
- `stats.StatsService/*` (user statistics, points)

**Public Endpoints (no authentication required):**
- `music.MusicService/*` (artists, albums, tracks, genres)

## Examples

### Available Examples
```bash
npm run example             # Basic gRPC usage
npm run example:frontend    # Using both gRPC and WebSocket clients
npm run example:websocket   # WebSocket-only example
npm test                    # Test gRPC client
```

### Example Files
- `examples/basic-usage.js` - Basic gRPC client usage
- `examples/frontend-usage.js` - Using both clients together
- `examples/websocket-example.js` - WebSocket client features
- `examples/test-client.js` - Client testing and validation

### Testing
```bash
npm test
```

## API Reference

### GrpcClient

#### Constructor
```javascript
new GrpcClient(config)
```

**Parameters:**
- `config.host` (string): Server hostname (default: 'localhost')
- `config.port` (number): Server port (default: 8080)
- `config.credentials` (grpc.Credentials): gRPC credentials (default: insecure)
- `config.options` (object): Additional gRPC options

#### Methods
- `initialize()`: Initialize all service connections
- `close()`: Close all connections
- `isInitialized()`: Check if client is initialized

### WebSocketClient

#### Constructor
```javascript
new WebSocketClient(config)
```

**Parameters:**
- `config.url` (string): WebSocket URL (default: 'ws://localhost:8080/ws')
- `config.autoReconnect` (boolean): Enable auto-reconnection (default: true)
- `config.reconnectInterval` (number): Reconnection interval in ms (default: 5000)
- `config.maxReconnectAttempts` (number): Max reconnection attempts (default: 10)
- `config.heartbeatInterval` (number): Heartbeat interval in ms (default: 30000)

#### Methods
- `connect(token)`: Connect to WebSocket server with auth token
- `disconnect()`: Disconnect from server
- `startListeningSession(trackId)`: Start a listening session
- `pauseListeningSession()`: Pause current session
- `endListeningSession()`: End current session
- `sendHeartbeat()`: Send manual heartbeat
- `getConnectionStatus()`: Get current connection status

#### Events
- `connected`: WebSocket connected
- `disconnected`: WebSocket disconnected
- `error`: Connection or protocol error
- `point_update`: Real-time point updates
- `session_update`: Listening session updates
- `heartbeat`: Heartbeat received
- `server_error`: Server-side error

### Service Methods

All service methods return Promises and support the following patterns:

#### Pagination
Most list methods support pagination:
```javascript
{
  offset: 0,    // Starting position
  limit: 10     // Number of items to return
}
```

#### Filtering
Many methods support filtering options:
```javascript
// Activity service
client.activity.callAsync('listListeningHistory', {
  user_id: 'user123',
  offset: 0,
  limit: 50
});

// Music service
client.music.callAsync('listTracks', {
  artist_id: 1,
  album_id: 2,
  genre_id: 3,
  offset: 0,
  limit: 20
});
```

## Error Handling

The client includes comprehensive error handling:

```javascript
try {
  const user = await client.user.callAsync('getUser', { id: 'nonexistent' });
} catch (error) {
  console.error('Error:', error.message);
  // Handle specific gRPC errors
  if (error.code === grpc.status.NOT_FOUND) {
    console.log('User not found');
  }
}
```

## Development

### Generate Proto Files
```bash
npm run generate-proto
```

### Available Scripts

```bash
# Proto update commands
npm run update           # Generate proto + test
npm run update-proto     # Force regenerate proto files
npm run generate-proto   # Generate proto files with change detection

# Example commands
npm run example          # Basic gRPC usage example
npm run example:frontend # Frontend client with WebSocket
npm run example:websocket # WebSocket-only example
npm run example:advanced # Advanced workflows
npm run example:comprehensive # All features demo
npm run example:errors        # Error handling patterns

# Testing
npm test                 # Test client functionality
```

### Project Structure
```
├── package.json           # Project configuration
├── README.md             # This file
├── LICENSE               # MIT License
├── .gitignore           # Git ignore rules
├── src/
│   ├── client.js         # Main gRPC client class
│   ├── frontend-client.js # Enhanced frontend client with WebSocket
│   ├── websocket-client.js # WebSocket-only client
│   ├── react-hooks.js    # React-like hooks for frontend integration
│   ├── index.js          # Main exports
│   └── generated/        # Auto-generated proto files
├── examples/
│   ├── basic-usage.js           # Basic gRPC usage examples
│   ├── frontend-usage.js        # Frontend client examples
│   ├── websocket-example.js     # WebSocket-only examples
│   ├── advanced-usage.js        # Advanced workflows
│   ├── comprehensive-example.js # All features demo
│   ├── error-handling.js        # Error handling patterns
│   └── test-client.js           # Client testing
├── docs/
│   ├── FRONTEND_CLIENT_GUIDE.md # Complete frontend integration guide
│   └── [other backend docs]     # Backend documentation
├── proto/                       # Protocol buffer definitions
│   ├── activity/               # Activity service protos
│   ├── music/                  # Music service protos
│   ├── stats/                  # Stats service protos
│   └── user/                   # User service protos
└── scripts/
    └── generate-proto.js        # Proto generation with change detection
```

## Contributing

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

## License

This project is licensed under the MIT License - see the LICENSE file for details.
