npm package: @harmoni-org/sdk
Description: Harmoni SDK - API abstraction layer with utilities and helpers
Author: Osama Younis
Latest version: 0.0.3

# 🎵 Harmoni SDK

> A powerful, type-safe SDK for seamless backend integration with comprehensive utilities and tools.

[![npm version](https://img.shields.io/npm/v/@harmoni-org/sdk.svg)](https://www.npmjs.com/package/@harmoni-org/sdk)
[![npm downloads](https://img.shields.io/npm/dm/@harmoni-org/sdk.svg)](https://www.npmjs.com/package/@harmoni-org/sdk)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![CI](https://github.com/HarmOni-Official/harmoni-sdk/workflows/CI/badge.svg)](https://github.com/HarmOni-Official/harmoni-sdk/actions)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)

## ✨ Features

- 🚀 **Modern Architecture** - Built with TypeScript, supports ESM & CJS
- 🔐 **Real Token Refresh** - Auto-refresh with proper request retry on 401
- 💾 **Session Persistence** - Automatic token storage & restoration across app reloads
- 📱 **Cross-Platform** - Works in browsers, React Native, Expo, Node.js, and more
- 🔄 **Smart Retry** - Exponential backoff that doesn't break POST requests
- 📤 **Upload Support** - Automatic FormData handling with progress tracking
- 🌐 **SSR-Safe** - Works in Node.js, browsers, and edge runtimes
- 🎯 **Type Safety** - Full TypeScript support with comprehensive types
- 🧩 **Modular Design** - Easy to extend with new modules
- 🎬 **Watch Together** - Real-time synchronized video playback with WebSocket
- 💬 **Real-time Chat** - Built-in chat for Watch Together rooms
- 🎮 **Player Integration** - Abstract interfaces for any video player (HTML5, VLC, custom)
- 🛠️ **Rich Utilities** - String, date, object, validation helpers included
- 📦 **Tree-shakeable** - Only bundle what you use (~8KB core, ~15KB with utils)
- ✅ **Well Tested** - Comprehensive test coverage
- 📚 **Production Ready** - Battle-tested patterns, see [PRODUCTION_READY.md](docs/guides/PRODUCTION_READY.md)

## 📦 Installation

```bash
npm install @harmoni-org/sdk
```

```bash
yarn add @harmoni-org/sdk
```

```bash
pnpm add @harmoni-org/sdk
```

## 🎮 Try the Demo

Want to see it in action? Check out the demo project that uses the published npm package:

```bash
cd demo
npm install
npm run demo           # Basic usage demo
npm run demo:watch     # Watch Together demo
npm run demo:player    # Player integration demo
```

[See demo folder](./demo) for complete examples using `@harmoni-org/sdk` from npm!

## 🚀 Quick Start

### Basic Usage

```typescript
import { HarmoniSDK } from '@harmoni-org/sdk';

// Initialize the SDK
const sdk = new HarmoniSDK({
  baseURL: 'https://api.yourbackend.com',
  timeout: 30000,
  autoRefreshToken: true, // Automatically refresh tokens on 401
  // autoRestoreToken: true (default) - Token persists across reloads
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
});

// Login - token automatically saved to localStorage
const user = await sdk.auth.login({
  emailOrUsername: 'user@example.com',
  password: 'secure_password',
});
console.log('Logged in as:', user.username);

// 🔄 User closes and reopens app...
// ✅ Token is automatically restored! No extra code needed.

// Update user profile
await sdk.user.updateProfile({
  username: 'newusername',
});

// Watch Together - Synchronized playback
await sdk.watchTogether.connect();
const room = await sdk.watchTogether.createRoom({ roomName: 'Movie Night' });

sdk.watchTogether.onRoomUpdate((update) => {
  if (update.metadata.action === 'play') player.play();
});

sdk.watchTogether.play(); // Syncs to all users
await sdk.watchTogether.sendMessage('Hello! 👋');
```

> **Note:** The SDK is fully SSR-safe and works in Node.js, browsers, and edge runtimes.

### Advanced Configuration

```typescript
import { HarmoniSDK } from '@harmoni-org/sdk';

const sdk = new HarmoniSDK({
  baseURL: 'https://api.yourbackend.com',
  timeout: 30000,
  headers: {
    'X-Custom-Header': 'value',
  },
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000,
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
  autoRefreshToken: true,
});

// Set auth token manually if you have it stored
sdk.setAuthToken('your-access-token');
```

## 📖 API Documentation

### Authentication Module

```typescript
// Check if username is available
const isAvailable = await sdk.auth.isUsernameUnique('johndoe');

// Check if email is available
const emailAvailable = await sdk.auth.isEmailUnique('john@example.com');

// Register a new user
const user = await sdk.auth.register({
  username: 'johndoe',
  password: 'secure_password',
  email: 'john@example.com', // optional
});
// Returns: { id, username, email, token, refreshToken }

// Login with email or username
const user = await sdk.auth.login({
  emailOrUsername: 'johndoe', // can be email or username
  password: 'password',
});
// Returns: { id, username, email, token, refreshToken }

// Verify current token
const verified = await sdk.auth.verifyToken();
// Returns: { user: { id, username, email, createdAt } }

// Refresh access token
const newToken = await sdk.auth.refreshAccessToken();
// Returns: string (new access token)

// Logout
await sdk.auth.logout();

// Password reset flow
await sdk.auth.requestPasswordReset('user@example.com');
await sdk.auth.resetPassword('reset-token', 'new-password');

// Change password (authenticated)
await sdk.auth.changePassword('old-password', 'new-password');

// Email verification
await sdk.auth.verifyEmail('verification-token');
await sdk.auth.resendVerificationEmail('user@example.com');
```

### Watch Together Module

```typescript
// Connect to Watch Together service
await sdk.watchTogether.connect();

// Create a room
const room = await sdk.watchTogether.createRoom({
  roomName: 'Movie Night',
});
console.log('Room ID:', room.roomId);

// Or join existing room
const room = await sdk.watchTogether.joinRoom({
  roomId: 'abc123',
});

// Listen for events
sdk.watchTogether.onRoomUpdate((update) => {
  if (update.metadata.userId !== myUserId) {
    switch (update.metadata.action) {
      case 'play':
        player.play();
        break;
      case 'pause':
        player.pause();
        break;
      case 'seek':
        player.seek(update.roomUpdates.syncState?.time);
        break;
    }
  }
});

// Sync check (automatic every 30s, or manual)
sdk.watchTogether.onSyncState((syncState) => {
  const diff = Math.abs(player.getCurrentTime() - syncState.time);
  if (diff > 1) player.seek(syncState.time);
});

// Control playback (synced to all users)
sdk.watchTogether.play();
sdk.watchTogether.pause();
sdk.watchTogether.seek(125.5);

// Send chat messages
await sdk.watchTogether.sendMessage('Hello everyone!');

sdk.watchTogether.onChatMessage((message) => {
  console.log(`${message.username}: ${message.content}`);
});

// Update file info
sdk.watchTogether.updateFileInfo({
  fileId: 'video-001',
  name: 'movie.mkv',
  fullTime: 7200, // 2 hours
  hash: 'abc123',
});

// Leave room
sdk.watchTogether.leaveRoom();

// Disconnect
sdk.watchTogether.disconnect();
```

See [Watch Together Module Documentation](src/modules/watchTogether/README.md) for complete API reference.

### Video Player Integration

The SDK provides abstract interfaces for integrating any video player with Watch Together:

```typescript
import { HTML5VideoController, SyncedPlayer } from '@harmoni-org/sdk';

// Create player (HTML5, VLC, YouTube, custom, etc.)
const player = new HTML5VideoController('video-element');
await player.start();

// Connect player to Watch Together
const syncedPlayer = new SyncedPlayer(player, sdk.watchTogether, {
  getCurrentUserId: () => currentUser.id,
  onPlayerStopped: () => sdk.watchTogether.leaveRoom(),
});

// Load and play - automatically syncs to all users!
await player.loadMedia('https://example.com/video.mp4');
await player.play();

// When user seeks, pauses, or plays - all users follow
// When other users control their player - yours follows
```

**Key Features:**

- ✅ Abstract interfaces for any player (HTML5, VLC, YouTube, custom)
- ✅ Automatic user action detection (play, pause, seek)
- ✅ Smart sync (distinguishes user actions from sync commands)
- ✅ No feedback loops
- ✅ Built-in HTML5 implementation
- ✅ Create custom controllers for any player

**Create Custom Player:**

```typescript
import { VideoPlayerController } from '@harmoni-org/sdk';

class MyCustomPlayer implements VideoPlayerController {
  async play(): Promise<void> {
    // Your play logic
  }

  async getStatus(): Promise<IPlayerState> {
    return {
      isPlaying: this.myPlayer.isPlaying(),
      currentTime: this.myPlayer.getCurrentTime(),
      // ... map your player's state
    };
  }

  // Implement other methods...
}

// Use with Watch Together
const syncedPlayer = new SyncedPlayer(new MyCustomPlayer(), sdk.watchTogether, options);
```

See [Player Integration Guide](docs/features/PLAYER_INTEGRATION.md) for complete documentation.

### User Module

```typescript
// Get user by ID
const user = await sdk.user.getById('user-id-123');

// Get current user profile
const profile = await sdk.user.getCurrentUser();

// Update profile
await sdk.user.updateProfile({
  username: 'johndoe_new',
  email: 'newemail@example.com',
});

// List users with pagination
const users = await sdk.user.list({
  page: 1,
  limit: 20,
});

// Search users
const results = await sdk.user.search('john', {
  page: 1,
  limit: 10,
});

// Upload avatar
const file = new File(['...'], 'avatar.jpg');
const { url } = await sdk.user.uploadAvatar(file);

// Delete avatar
await sdk.user.deleteAvatar();

// Delete account
await sdk.user.deleteAccount();
```

## 🛠️ Utilities

### String Utilities

```typescript
import { stringUtils } from '@harmoni-org/sdk';

stringUtils.capitalize('hello'); // 'Hello'
stringUtils.titleCase('hello world'); // 'Hello World'
stringUtils.slugify('Hello World!'); // 'hello-world'
stringUtils.truncate('Long text...', 10); // 'Long te...'
stringUtils.isValidEmail('test@example.com'); // true
stringUtils.camelToSnake('myVariable'); // 'my_variable'
stringUtils.snakeToCamel('my_variable'); // 'myVariable'
stringUtils.randomString(16); // 'aB3dEf5gH7jK9mN0'
```

### Date Utilities

```typescript
import { dateUtils } from '@harmoni-org/sdk';

dateUtils.formatDate(new Date(), 'YYYY-MM-DD'); // '2024-01-15'
dateUtils.timeAgo(new Date('2024-01-01')); // '2 weeks ago'
dateUtils.addDays(new Date(), 7); // Date 7 days from now
dateUtils.addHours(new Date(), 3); // Date 3 hours from now
dateUtils.isToday(new Date()); // true
dateUtils.isPast(new Date('2023-01-01')); // true
dateUtils.isFuture(new Date('2025-01-01')); // true
```

### Object Utilities

```typescript
import { objectUtils } from '@harmoni-org/sdk';

const original = { a: 1, b: { c: 2 } };
const cloned = objectUtils.deepClone(original);

const merged = objectUtils.deepMerge({ a: 1, b: 2 }, { b: 3, c: 4 }); // { a: 1, b: 3, c: 4 }

const picked = objectUtils.pick({ a: 1, b: 2, c: 3 }, ['a', 'c']); // { a: 1, c: 3 }
const omitted = objectUtils.omit({ a: 1, b: 2, c: 3 }, ['b']); // { a: 1, c: 3 }

objectUtils.isEmpty({}); // true
objectUtils.isEmpty({ a: 1 }); // false

const value = objectUtils.getNestedValue({ a: { b: { c: 1 } } }, 'a.b.c'); // 1
```

### Validation Utilities

```typescript
import { validationUtils } from '@harmoni-org/sdk';

// Email validation
const emailResult = validationUtils.validateEmail('test@example.com');
// { valid: true }

// Password validation
const passwordResult = validationUtils.validatePassword('MyP@ssw0rd', {
  minLength: 8,
  requireUppercase: true,
  requireLowercase: true,
  requireNumbers: true,
  requireSpecialChars: true,
});
// { valid: true }

// URL validation
const urlResult = validationUtils.validateUrl('https://example.com');
// { valid: true }

// Phone validation
const phoneResult = validationUtils.validatePhone('+1234567890');
// { valid: true }

// Required field
const requiredResult = validationUtils.required('value', 'Username');
// { valid: true }

// Length validation
validationUtils.minLength('test', 3, 'Username'); // { valid: true }
validationUtils.maxLength('test', 10, 'Username'); // { valid: true }
```

### Storage Utilities

```typescript
import { localStorage, sessionStorage } from '@harmoni-org/sdk';

// Local storage (SSR-safe - uses memory fallback in Node.js)
localStorage.set('user', { id: 1, name: 'John' });
const user = localStorage.get<{ id: number; name: string }>('user');
localStorage.remove('user');
localStorage.clear();

// Check if browser storage is available
if (localStorage.isAvailable()) {
  console.log('Using browser localStorage');
} else {
  console.log('Using in-memory storage (SSR/Node)');
}

// Session storage
sessionStorage.set('token', 'abc123');
const token = sessionStorage.get<string>('token');
```

## 🔌 Advanced Usage

### File Uploads

The SDK automatically handles `FormData` for file uploads:

```typescript
// Upload user avatar
const file = document.querySelector('input[type="file"]').files[0];
const result = await sdk.user.uploadAvatar(file);
console.log('Avatar URL:', result.url);

// Upload with progress tracking
const formData = new FormData();
formData.append('file', file);

await sdk.getHttpClient().post('/uploads', formData, {
  onUploadProgress: (progressEvent) => {
    const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
    console.log(`Upload progress: ${progress}%`);
  },
});
```

See [examples/upload-example.ts](examples/upload-example.ts) for more upload patterns.

### Token Refresh with Request Retry

The SDK automatically:

1. Detects 401 errors
2. Calls refresh token endpoint
3. Retries the original request with new token

```typescript
const sdk = new HarmoniSDK({
  baseURL: 'https://api.example.com',
  autoRefreshToken: true, // Enable auto-refresh
});

// This request will automatically retry if token expires
const data = await sdk.user.getCurrentUser();
// If 401 → refreshes token → retries request → returns data
```

### Smart Retry Logic

The SDK only retries safe operations (GET, HEAD, OPTIONS) by default:

```typescript
const sdk = new HarmoniSDK({
  baseURL: 'https://api.example.com',
  retryConfig: {
    maxRetries: 3,
    retryDelay: 1000, // Base delay (uses exponential backoff)
    retryableStatuses: [408, 429, 500, 502, 503, 504],
  },
});

// GET requests will retry on network errors or 5xx errors
await sdk.user.list(); // ✅ Will retry

// POST/PATCH/DELETE won't retry (not idempotent)
await sdk.auth.login(creds); // ❌ Won't retry (POST)
```

### Custom Interceptors

```typescript
const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });

// Add request interceptor
sdk.getHttpClient().addRequestInterceptor({
  onFulfilled: (config) => {
    console.log('Request:', config.url);
    return config;
  },
  onRejected: (error) => {
    console.error('Request error:', error);
    return Promise.reject(error);
  },
});

// Add response interceptor
sdk.getHttpClient().addResponseInterceptor({
  onFulfilled: (response) => {
    console.log('Response:', response.status);
    return response;
  },
  onRejected: (error) => {
    console.error('Response error:', error);
    return Promise.reject(error);
  },
});
```

### Custom Error Handling

```typescript
import { ApiError } from '@harmoni-org/sdk';

try {
  await sdk.auth.login(credentials);
} catch (error) {
  if (ApiError.isApiError(error)) {
    console.error('API Error:', error.status, error.message);
    console.error('Error code:', error.code);
    console.error('Details:', error.details);
  } else {
    console.error('Unknown error:', error);
  }
}
```

### Creating Custom Modules

```typescript
import { HttpClient } from '@harmoni-org/sdk';

class CustomModule {
  constructor(private http: HttpClient) {}

  async customEndpoint() {
    return this.http.get('/custom/endpoint');
  }
}

// Extend the SDK
const sdk = new HarmoniSDK({ baseURL: 'https://api.example.com' });
const customModule = new CustomModule(sdk.getHttpClient());
```

## 🏗️ Project Structure

```
harmoni-sdk/
├── src/
│   ├── core/              # Core HTTP client & error handling
│   │   ├── http/
│   │   └── errors/
│   ├── modules/           # Feature modules (auth, user, etc.)
│   │   ├── auth/
│   │   └── user/
│   ├── utils/             # Utility functions
│   ├── types/             # TypeScript types
│   ├── sdk/               # Main SDK class
│   └── index.ts           # Main entry point
├── tests/                 # Test files
├── .github/workflows/     # CI/CD pipelines
└── dist/                  # Build output (ESM + CJS)
```

## 🧪 Testing

```bash
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Generate coverage report
npm run test -- --coverage
```

## 🔨 Development

```bash
# Install dependencies
npm install

# Start development mode (watch)
npm run dev

# Build the package
npm run build

# Run linter
npm run lint

# Format code
npm run format

# Type check
npm run typecheck
```

## 📝 Creating a Release

This project uses [Changesets](https://github.com/changesets/changesets) for version management.

1. Create a changeset:

```bash
npm run changeset
```

2. Commit the changeset files
3. Push to GitHub
4. A "Version Packages" PR will be created automatically
5. Merge the PR to publish to npm

## 🤝 Contributing

Contributions are welcome! Please follow these steps:

1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Add tests for your changes
5. Run tests and linting (`npm test && npm run lint`)
6. Commit your changes (`git commit -m 'feat: add amazing feature'`)
7. Push to the branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request

## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## 📚 Additional Resources

### Documentation

- 📚 [Documentation](docs/README.md) - Complete documentation index
- 📖 [Production Ready Features](docs/guides/PRODUCTION_READY.md) - Why this SDK is production-ready
- 🚀 [Quick Start Guide](docs/guides/QUICK_START.md) - Get started in 5 minutes
- 🔐 [Authentication Guide](docs/guides/AUTHENTICATION.md) - Complete auth documentation
- 💾 [Session Persistence Guide](docs/guides/SESSION_PERSISTENCE.md) - Session management
- 📱 [Cross-Platform Guide](docs/guides/CROSS_PLATFORM.md) - Platform-specific implementations
- 🔄 [Migration Guide](docs/guides/MIGRATION.md) - Migrate from direct API calls
- 🛠️ [Setup Guide](docs/guides/SETUP.md) - Development and publishing
- 🤝 [Contributing](CONTRIBUTING.md) - Contribution guidelines

### Examples

- [Basic Usage](examples/basic-usage.ts) - Common authentication patterns
- [Advanced Usage](examples/advanced-usage.ts) - Interceptors, error handling, utilities
- [File Uploads](examples/upload-example.ts) - File upload with progress tracking
- [Watch Together](examples/watch-together-example.ts) - Real-time synchronized playback

### External Links

- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
- [Vitest Documentation](https://vitest.dev/)
- [Changesets Documentation](https://github.com/changesets/changesets)

## 🙏 Acknowledgments

- Built with [TypeScript](https://www.typescriptlang.org/)
- Bundled with [tsup](https://tsup.egoist.dev/)
- Tested with [Vitest](https://vitest.dev/)
- HTTP client powered by [Axios](https://axios-http.com/)

## 📞 Support

- 📧 Email: support@harmoni.dev
- 🐛 Issues: [GitHub Issues](https://github.com/yourusername/harmoni-sdk/issues)
- 💬 Discussions: [GitHub Discussions](https://github.com/yourusername/harmoni-sdk/discussions)

---

Made with ❤️ by the Harmoni Team