# @scwar/nestjs-pusher

A comprehensive NestJS module for integrating with the Pusher Channels REST API. This package provides a complete wrapper around all Pusher API endpoints with robust error handling, retries, and TypeScript support.

## Features

- 🚀 **Complete API Coverage**: All Pusher Channels REST API endpoints implemented
- 🔄 **Automatic Retries**: Built-in retry mechanism with exponential backoff
- 🛡️ **Robust Error Handling**: Comprehensive error handling with detailed error messages
- 📝 **TypeScript Support**: Full type definitions for all API requests and responses
- 🔐 **End-to-End Encryption**: Support for encrypted channels (requires `tweetnacl` and `tweetnacl-util`)
- 🧪 **Comprehensive Testing**: Extensive test coverage for all endpoints
- ⚡ **Performance**: Uses native fetch API for optimal performance
- 🔧 **Configurable**: Easy configuration through NestJS module options

## Installation

```bash
npm install @scwar/nestjs-pusher
```

For end-to-end encryption support (optional):

```bash
npm install tweetnacl tweetnacl-util
```

## Quick Start

### 1. Import the module

```typescript
import { PusherModule } from '@scwar/nestjs-pusher';

@Module({
  imports: [
    PusherModule.forRoot({
      appId: 'your-app-id',
      key: 'your-pusher-key',
      secret: 'your-pusher-secret',
      cluster: 'us2', // or your cluster
      timeout: 30000,
      retries: 3,
    }),
  ],
})
export class AppModule {}
```

### 2. Inject and use the service

```typescript
import { PusherService } from '@scwar/nestjs-pusher';

@Injectable()
export class NotificationService {
  constructor(private readonly pusherService: PusherService) {}

  async sendNotification(channel: string, event: string, data: any) {
    return this.pusherService.event.trigger(channel, event, data);
  }
}
```

## Configuration Options

```typescript
interface PusherModuleOptions {
  appId: string;
  key: string;
  secret: string;
  cluster?: string; // Default: 'us2'
  encryptionMasterKeyBase64?: string; // Required for private-encrypted-* channels
  baseUrl?: string; // Default: 'https://api.pusherapp.com'
  timeout?: number; // Default: 30000
  retries?: number; // Default: 3
  retryDelay?: number; // Default: 1000
  maxRetryDelay?: number; // Default: 10000
  useTLS?: boolean; // Default: true
}
```

## Available Services

### Event Service

Trigger events on channels:

```typescript
// Single channel
await pusherService.event.trigger('my-channel', 'my-event', { message: 'Hello' });

// Multiple channels
await pusherService.event.trigger(
  ['channel-1', 'channel-2'],
  'my-event',
  { message: 'Hello' }
);

// With socket ID exclusion
await pusherService.event.trigger(
  'my-channel',
  'my-event',
  { message: 'Hello' },
  'socket-id-to-exclude'
);

// Batch events
await pusherService.event.triggerBatch({
  batch: [
    {
      channel: 'channel-1',
      name: 'event-1',
      data: { message: 'Hello' },
    },
    {
      channel: 'channel-2',
      name: 'event-2',
      data: { message: 'World' },
    },
  ],
});
```

### Channel Service

Query channel information:

```typescript
// List all channels
const channels = await pusherService.channel.getChannels({
  filter_by_prefix: 'presence-',
  attributes: 'user_count,subscription_count',
});

// Get channel info
const channelInfo = await pusherService.channel.getChannel('my-channel', {
  attributes: 'user_count',
});

// Get users in presence channel
const users = await pusherService.channel.getChannelUsers('presence-channel');
```

### Auth Service

Authenticate users and authorize channels:

```typescript
// Authenticate user
const userAuth = pusherService.auth.authenticateUser('socket-id', {
  id: 'user-123',
  name: 'John Doe',
  image: 'https://example.com/avatar.jpg',
});

// Authorize private channel
const channelAuth = pusherService.auth.authorizeChannel('socket-id', 'private-channel');

// Authorize presence channel
const presenceAuth = pusherService.auth.authorizeChannel(
  'socket-id',
  'presence-channel',
  {
    user_id: 'user-123',
    user_info: {
      name: 'John Doe',
    },
  }
);

// Terminate user connections
await pusherService.auth.terminateUserConnections('user-123');
```

### Webhook Service

Validate and parse webhooks:

```typescript
// Validate webhook
const isValid = pusherService.webhook.isValid(webhookRequest);

// Get webhook data
const webhookData = pusherService.webhook.getData(webhookRequest);

// Get events from webhook
const events = pusherService.webhook.getEvents(webhookRequest);

// Get webhook timestamp
const timestamp = pusherService.webhook.getTime(webhookRequest);
```

## End-to-End Encryption

To use end-to-end encryption for `private-encrypted-*` channels:

1. Install required dependencies:

```bash
npm install tweetnacl tweetnacl-util
```

2. Configure the encryption master key:

```typescript
PusherModule.forRoot({
  appId: 'your-app-id',
  key: 'your-key',
  secret: 'your-secret',
  encryptionMasterKeyBase64: 'your-base64-encoded-master-key',
})
```

3. Trigger events on encrypted channels:

```typescript
// The data will be automatically encrypted
await pusherService.event.trigger(
  'private-encrypted-channel',
  'my-event',
  { message: 'This will be encrypted' }
);
```

## Error Handling

The package provides comprehensive error handling:

```typescript
try {
  const result = await pusherService.event.trigger('channel', 'event', data);
} catch (error) {
  if (error instanceof PusherError) {
    console.log('Pusher Error:', error.message);
    console.log('Error Code:', error.code);
    console.log('HTTP Status:', error.status);
    console.log('Error Data:', error.data);
  }
}
```

## Retry Mechanism

Automatic retries with exponential backoff for failed requests:

```typescript
// Configure retries in module options
PusherModule.forRoot({
  appId: 'your-app-id',
  key: 'your-key',
  secret: 'your-secret',
  retries: 3,           // Number of retry attempts
  retryDelay: 1000,     // Initial delay in ms
  maxRetryDelay: 10000, // Maximum delay in ms
})
```

## REST API Signatures

Generate signed query strings for manual API requests:

```typescript
const queryString = pusherService.createSignedQueryString({
  method: 'GET',
  path: '/apps/123/channels',
  params: {
    filter_by_prefix: 'presence-',
  },
});
```

## Testing

```bash
# Run tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:cov
```

## Version Management & Releases

This package includes an automated version bumping system that follows semantic versioning and conventional commits.

### Automatic Version Bumping

```bash
# Automatically determine and bump version
npm run version:auto

# Manual version bumps
npm run version:patch  # 1.0.0 → 1.0.1
npm run version:minor  # 1.0.0 → 1.1.0
npm run version:major  # 1.0.0 → 2.0.0
```

### Conventional Commits

All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:

```bash
# Feature commits (minor version bump)
git commit -m "feat: add new event type"

# Bug fix commits (patch version bump)
git commit -m "fix: resolve authentication issue"

# Breaking change commits (major version bump)
git commit -m "feat!: breaking change in API"
```

### Release Process

```bash
# Complete release process with automatic version bump
npm run release:auto

# Manual release with specific version bump
npm run release:patch
npm run release:minor
npm run release:major
```

## Contributing

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

**Important**: All commits must follow the conventional commit format to ensure proper version bumping.

## License

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

## Support

For support, please open an issue on GitHub or contact the maintainers.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for a list of changes and version history.

