npm package: @postacksolutions/flux-relay-sdk
Description: Official JavaScript/TypeScript SDK for Flux Relay - Real-time messaging platform
Author: Flux Relay
Latest version: 1.2.0

# Flux Relay SDK

Official JavaScript/TypeScript SDK for Flux Relay - Real-time messaging platform.

[![npm version](https://img.shields.io/npm/v/@postacksolutions/flux-relay-sdk.svg)](https://www.npmjs.com/package/@postacksolutions/flux-relay-sdk)
[![License](https://img.shields.io/npm/l/@postacksolutions/flux-relay-sdk.svg)](LICENSE)

## Installation

```bash
npm install @postacksolutions/flux-relay-sdk
# or
yarn add @postacksolutions/flux-relay-sdk
# or
bun add @postacksolutions/flux-relay-sdk
```

## Quick Start

```typescript
import { FluxRelay } from '@postacksolutions/flux-relay-sdk';

// Initialize the client
const client = new FluxRelay({
  apiUrl: 'https://flux.postacksolutions.com',
  serverId: 'YOUR_SERVER_ID',
  apiKey: 'YOUR_API_KEY',
});

// Authenticate a user
const auth = await client.authenticateUser({
  externalUserId: 'user_123', // Your app's user ID
  username: 'john_doe',
  metadata: {
    email: 'john@example.com',
  },
});

// Store the access token
client.setAccessToken(auth.accessToken);

// Now you can use the SDK
const conversations = await client.getConversations();
```

## Configuration

### Basic Configuration

```typescript
const client = new FluxRelay({
  apiUrl: 'https://flux.postacksolutions.com', // Required
  serverId: 'YOUR_SERVER_ID',                  // Optional when using access tokens
  apiKey: 'YOUR_API_KEY',                      // Optional when using access tokens
  timeout: 30000,                              // Optional: Request timeout in ms (default: 30000)
  enforceHttps: true,                          // Optional: Enforce HTTPS in production (default: true)
});
```

### Using Access Tokens

Once you have an access token (from `authenticateUser`), you can use it for all subsequent requests:

```typescript
// After authentication
client.setAccessToken(auth.accessToken);

// Now serverId and apiKey are optional
// The SDK will use the access token for authentication
```

## API Reference

### Authentication

#### `authenticateUser(params)`

Authenticate or create a user. If the user doesn't exist, it will be created automatically.

```typescript
const auth = await client.authenticateUser({
  externalUserId: 'user_123',        // Required: Your app's unique user ID
  username: 'john_doe',               // Required: Display name
  metadata: {                         // Optional: Additional user data
    email: 'john@example.com',
    phoneNumber: '+1234567890',
    avatar: 'https://example.com/avatar.jpg',
  },
});

// Returns:
// {
//   userId: string;
//   accessToken: string;
//   refreshToken: string;
//   user: {
//     id: string;
//     username: string;
//     metadata?: Record<string, any>;
//   };
// }
```

### Contacts

#### `getContacts()`

Get all contacts (users in the same project/server).

```typescript
const contacts = await client.getContacts();

// Returns: Contact[]
// [
//   {
//     userId: string;
//     username: string;
//     externalUserId: string;
//     metadata?: Record<string, any>;
//     createdAt: string;
//   },
//   ...
// ]
```

### Conversations

#### `getConversations()`

List all conversations for the authenticated user.

```typescript
const conversations = await client.getConversations();

// Returns: Conversation[]
// [
//   {
//     id: string;
//     name?: string;
//     isGroup: boolean;
//     participantIds: string[];
//     createdAt: string;
//     updatedAt: string;
//     lastMessage?: Message | null;
//     unreadCount?: number;
//   },
//   ...
// ]
```

#### `createConversation(params)`

Create a new conversation.

```typescript
const conversation = await client.createConversation({
  participantIds: ['user_456'],  // Required: Array of user IDs
  name: 'Chat with Jane',        // Optional: Conversation name (for groups)
  isGroup: false,                // Optional: Whether it's a group chat (default: false)
});

// Returns: Conversation
```

#### `getConversationDetails(conversationId)`

Get details of a specific conversation.

```typescript
const conversation = await client.getConversationDetails('conv_123');

// Returns: Conversation
```

#### `updateConversationName(conversationId, name)`

Update the name of a conversation (typically for group chats).

```typescript
const conversation = await client.updateConversationName('conv_123', 'New Group Name');

// Returns: Conversation
```

#### `deleteConversation(conversationId)`

Delete a conversation.

```typescript
await client.deleteConversation('conv_123');
```

### Messages

#### `getMessages(conversationId, options?)`

Get messages from a conversation.

```typescript
const messages = await client.getMessages('conv_123', {
  limit: 50,                      // Optional: Number of messages (1-100, default: 50)
  before: '2024-01-01T00:00:00Z', // Optional: Get messages before this timestamp
});

// Returns: Message[]
// [
//   {
//     id: string;
//     conversationId: string;
//     senderId: string;
//     content: string;
//     type: string;
//     attachments?: Array<{
//       id: string;
//       filename: string;
//       mimeType: string;
//       size: number;
//     }>;
//     createdAt: string;
//   },
//   ...
// ]
```

#### `sendMessage(conversationId, params)`

Send a message to a conversation.

```typescript
const message = await client.sendMessage('conv_123', {
  content: 'Hello, world!',       // Required: Message content
  type: 'text',                    // Optional: Message type (default: 'text')
  attachments: [                   // Optional: File attachments
    {
      id: 'file_123',
      filename: 'document.pdf',
      mimeType: 'application/pdf',
      size: 1024,
    },
  ],
});

// Returns: Message
```

### File Uploads

#### `uploadFile(file)`

Upload a file and get a file ID for use in messages.

```typescript
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];

const uploadResult = await client.uploadFile(file);

// Returns: { fileId: string; url: string }

// Use the fileId in message attachments
await client.sendMessage('conv_123', {
  content: 'Check out this file!',
  type: 'text',
  attachments: [{
    id: uploadResult.fileId,
    filename: file.name,
    mimeType: file.type,
    size: file.size,
  }],
});
```

**File Upload Limits:**
- Maximum file size: 10MB
- Allowed types: Images, documents, videos, audio files

#### `getFile(fileId)`

Retrieve file data by file ID. Returns the file with base64-encoded data.

```typescript
const file = await client.getFile('file_123');

// Returns:
// {
//   id: string;
//   filename: string;
//   mimeType: string;
//   size: number;
//   data: string; // Base64 encoded
//   uploadedAt: string;
// }

// Example: Download file
const file = await client.getFile('file_123');
const byteCharacters = atob(file.data);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
  byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], { type: file.mimeType });

// Create download link
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = file.filename;
a.click();
URL.revokeObjectURL(url);
```

### Token Management

#### `setAccessToken(token)`

Set the access token manually (useful for token refresh or restoring sessions).

```typescript
client.setAccessToken('your-access-token');
```

#### `getAccessToken()`

Get the current access token.

```typescript
const token = client.getAccessToken();
// Returns: string | null
```

⚠️ **Security Warning**: Only use this for token refresh or secure server-side operations. Never expose tokens in client-side code, logs, or error messages.

#### `clearAccessToken()`

Clear the access token (useful for logout).

```typescript
client.clearAccessToken();
```

## Error Handling

The SDK throws `FluxRelayError` for all errors. Always wrap SDK calls in try-catch blocks:

```typescript
import { FluxRelay, FluxRelayError } from '@postacksolutions/flux-relay-sdk';

try {
  const conversations = await client.getConversations();
} catch (error) {
  if (error instanceof FluxRelayError) {
    console.error('Flux Relay error:', error.message);
    console.error('Status code:', error.statusCode);
    console.error('Error code:', error.errorCode);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Common Error Codes

- `MISSING_API_URL` - API URL is required
- `MISSING_CREDENTIALS` - Server ID and API Key are required
- `INVALID_TOKEN` - Invalid access token format
- `NOT_AUTHENTICATED` - User is not authenticated
- `INVALID_ID` - Invalid ID format
- `REQUEST_TIMEOUT` - Request timed out
- `HTTPS_REQUIRED` - HTTPS is required in production

## Real-time Updates with WebSocket

For real-time message delivery, use Socket.io alongside the SDK:

```typescript
import { io } from 'socket.io-client';

// Connect to WebSocket
const socket = io('https://flux.postacksolutions.com', {
  auth: {
    token: auth.accessToken, // From authenticateUser
  },
  path: '/socket.io',
});

// Listen for new messages
socket.on('new_message', (message) => {
  console.log('New message:', message);
  // Update your UI
});

// Join a conversation room
socket.emit('join_conversation', {
  conversationId: 'conv_123',
});

// Send message via WebSocket (optional, can also use REST API)
socket.emit('send_message', {
  conversationId: 'conv_123',
  content: 'Hello!',
  type: 'text',
});
```

**Install Socket.io client:**

```bash
npm install socket.io-client
```

## TypeScript Support

The SDK is written in TypeScript and includes full type definitions:

```typescript
import { FluxRelay, User, Conversation, Message, Contact } from '@postacksolutions/flux-relay-sdk';

const client: FluxRelay = new FluxRelay({ ... });
const user: User = auth.user;
const conversations: Conversation[] = await client.getConversations();
```

## Security Features

- ✅ HTTPS enforcement in production
- ✅ Input validation and sanitization
- ✅ Path injection prevention
- ✅ Request timeouts
- ✅ Secure token handling
- ✅ XSS prevention

## Examples

### Complete Integration Example

```typescript
import { FluxRelay } from '@postacksolutions/flux-relay-sdk';
import { io } from 'socket.io-client';

// Initialize client
const client = new FluxRelay({
  apiUrl: 'https://flux.postacksolutions.com',
  serverId: 'YOUR_SERVER_ID',
  apiKey: 'YOUR_API_KEY',
});

// Authenticate user
async function initializeMessaging(userId: string, username: string) {
  // Authenticate
  const auth = await client.authenticateUser({
    externalUserId: userId,
    username: username,
  });

  // Set access token
  client.setAccessToken(auth.accessToken);

  // Connect WebSocket
  const socket = io('https://flux.postacksolutions.com', {
    auth: { token: auth.accessToken },
    path: '/socket.io',
  });

  // Load conversations
  const conversations = await client.getConversations();

  // Listen for new messages
  socket.on('new_message', (message) => {
    console.log('New message:', message);
  });

  return { client, socket, conversations };
}

// Usage
const { client, socket, conversations } = await initializeMessaging(
  'user_123',
  'john_doe'
);
```

### React Hook Example

```typescript
import { useState, useEffect } from 'react';
import { FluxRelay } from '@postacksolutions/flux-relay-sdk';

function useFluxRelay(serverId: string, apiKey: string) {
  const [client, setClient] = useState<FluxRelay | null>(null);
  const [conversations, setConversations] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fluxClient = new FluxRelay({
      apiUrl: 'https://flux.postacksolutions.com',
      serverId,
      apiKey,
    });
    setClient(fluxClient);
    setLoading(false);
  }, [serverId, apiKey]);

  const authenticate = async (userId: string, username: string) => {
    if (!client) return;
    const auth = await client.authenticateUser({ externalUserId: userId, username });
    client.setAccessToken(auth.accessToken);
    const convs = await client.getConversations();
    setConversations(convs);
    return auth;
  };

  return { client, conversations, authenticate, loading };
}
```

## Browser Support

- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)

## Node.js Support

- Node.js 16.0.0 or higher

## Resources

- 📚 [Documentation](https://flux.postacksolutions.com/getting-started)
- 🔌 [Integration Guides](https://flux.postacksolutions.com/integrations)
- 📖 [API Reference](https://flux.postacksolutions.com/api-reference)
- 💬 [Support](mailto:support@postacksolutions.com)

## License

MIT

## Support

For support, email support@postacksolutions.com or visit [flux.postacksolutions.com](https://flux.postacksolutions.com).