echofi-client
Version:
A TypeScript/Node.js client library for EchoFi services with Level 3 Ultimate API and gRPC-Web clients for music streaming, user management, activity tracking, and authentication.
677 lines (529 loc) โข 17.6 kB
Markdown
# 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.