@pushflodev/sdk
v1.0.9
Published
Official TypeScript SDK for PushFlo real-time messaging service
Maintainers
Readme
@pushflodev/sdk
Official TypeScript SDK for PushFlo real-time messaging service.
Features
- Zero runtime dependencies - Built entirely on native Web APIs
- Full TypeScript support - Complete type definitions included
- Three entry points - Browser client, server client, and React hooks
- Auto-reconnection - Automatic reconnection with exponential backoff
- Lightweight - Minimal bundle size
Installation
# npm
npm install @pushflodev/sdk
# yarn
yarn add @pushflodev/sdk
# pnpm
pnpm add @pushflodev/sdkQuick Start - Browser (60 seconds)
import { PushFloClient } from '@pushflodev/sdk';
// 1. Create client
const client = new PushFloClient({
publishKey: 'pub_xxxxxxxxxxxxx', // Get from console.pushflo.dev
});
// 2. Connect
await client.connect();
// 3. Subscribe to channel
const subscription = client.subscribe('notifications', {
onMessage: (message) => {
console.log('Received:', message.content);
console.log('Event type:', message.eventType);
console.log('Timestamp:', new Date(message.timestamp));
},
});
// 4. Cleanup when done
subscription.unsubscribe();
client.disconnect();
// 5. Clean up all resources (optional - for complete teardown)
client.destroy();Quick Start - Server (60 seconds)
import { PushFloServer } from '@pushflodev/sdk/server';
// 1. Create server client
const pushflo = new PushFloServer({
secretKey: 'sec_xxxxxxxxxxxxx', // Get from console.pushflo.dev
});
// 2. Publish a message
const result = await pushflo.publish('notifications', {
title: 'New Order',
orderId: '12345',
amount: 99.99,
});
console.log('Message ID:', result.id);
console.log('Delivered to:', result.delivered, 'subscribers');Quick Start - React (60 seconds)
// App.tsx - Wrap your app with provider
import { PushFloProvider } from '@pushflodev/sdk/react';
function App() {
return (
<PushFloProvider publishKey="pub_xxxxxxxxxxxxx">
<NotificationList />
</PushFloProvider>
);
}
// NotificationList.tsx - Subscribe to channel
import { useChannel } from '@pushflodev/sdk/react';
function NotificationList() {
const { messages, connectionState } = useChannel('notifications');
if (connectionState === 'connecting') {
return <div>Connecting...</div>;
}
return (
<ul>
{messages.map((msg) => (
<li key={msg.id}>
<strong>{msg.content.title}</strong>
<span>{new Date(msg.timestamp).toLocaleString()}</span>
</li>
))}
</ul>
);
}Browser Client
Connection State Handling
import { PushFloClient, ConnectionState } from '@pushflodev/sdk';
const client = new PushFloClient({ publishKey: 'pub_xxx' });
// Listen to connection changes
client.onConnectionChange((state: ConnectionState) => {
switch (state) {
case 'disconnected':
console.log('Disconnected from PushFlo');
break;
case 'connecting':
console.log('Connecting to PushFlo...');
break;
case 'connected':
console.log('Connected to PushFlo!');
break;
case 'error':
console.log('Connection error');
break;
}
});
await client.connect();Client Options
const client = new PushFloClient({
// Required
publishKey: 'pub_xxxxxxxxxxxxx',
// Optional
baseUrl: 'https://api.pushflo.dev', // Custom API URL
autoConnect: false, // Auto-connect on creation
debug: false, // Enable debug logging
connectionTimeout: 30000, // Connection timeout (ms)
heartbeatInterval: 25000, // Heartbeat interval (ms)
autoReconnect: true, // Auto-reconnect on disconnect
maxReconnectAttempts: 0, // Max reconnect attempts (0 = infinite)
reconnectDelay: 1000, // Initial reconnect delay (ms)
maxReconnectDelay: 30000, // Max reconnect delay (ms)
});Subscription Options
const subscription = client.subscribe('notifications', {
onMessage: (message) => {
console.log('Received:', message);
},
onError: (error) => {
console.error('Subscription error:', error);
},
onSubscribed: () => {
console.log('Successfully subscribed');
},
onUnsubscribed: () => {
console.log('Unsubscribed');
},
});
// Later: unsubscribe
subscription.unsubscribe();Event Listeners
// Listen for all messages
client.on('message', (message) => {
console.log('Message on', message.channel, ':', message.content);
});
// Listen for errors
client.on('error', (error) => {
console.error('Error:', error);
});
// Listen for connection events
client.on('connected', (info) => {
console.log('Connected with client ID:', info.clientId);
});
client.on('disconnected', (reason) => {
console.log('Disconnected:', reason);
});Server Client
Channel Management
import { PushFloServer } from '@pushflodev/sdk/server';
const pushflo = new PushFloServer({ secretKey: 'sec_xxx' });
// List all channels
const { channels, pagination } = await pushflo.listChannels({
page: 1,
pageSize: 25,
});
console.log('Channels:', channels);
console.log('Total:', pagination.total);
// Get a specific channel
const channel = await pushflo.getChannel('notifications');
console.log('Channel:', channel.name, '- Messages:', channel.messageCount);
// Create a new channel
const newChannel = await pushflo.createChannel({
name: 'Order Updates',
slug: 'order-updates',
description: 'Real-time order status updates',
isPrivate: false,
});
console.log('Created:', newChannel.slug);
// Update a channel
const updated = await pushflo.updateChannel('order-updates', {
description: 'Updated description',
});
// Delete a channel
await pushflo.deleteChannel('order-updates');
console.log('Channel deleted');Message History
import { PushFloServer } from '@pushflodev/sdk/server';
const pushflo = new PushFloServer({ secretKey: 'sec_xxx' });
// Get message history
const { messages, pagination } = await pushflo.getMessageHistory('notifications', {
page: 1,
pageSize: 50,
});
messages.forEach((msg) => {
console.log(`[${msg.eventType}] ${JSON.stringify(msg.content)}`);
});
console.log(`Page ${pagination.page} of ${pagination.totalPages}`);Publishing with Event Types
import { PushFloServer } from '@pushflodev/sdk/server';
const pushflo = new PushFloServer({ secretKey: 'sec_xxx' });
// Publish with custom event type
await pushflo.publish('orders',
{ orderId: '123', status: 'shipped' },
{ eventType: 'order.shipped' }
);
// Subscribe and filter by event type (browser client)
client.subscribe('orders', {
onMessage: (message) => {
if (message.eventType === 'order.shipped') {
showShippingNotification(message.content);
} else if (message.eventType === 'order.delivered') {
showDeliveryNotification(message.content);
}
},
});Server Options
const pushflo = new PushFloServer({
// Required
secretKey: 'sec_xxxxxxxxxxxxx',
// Optional
baseUrl: 'https://api.pushflo.dev', // Custom API URL
timeout: 30000, // Request timeout (ms)
debug: false, // Enable debug logging
retryAttempts: 3, // Retry failed requests
});React Integration
Provider Options
import { PushFloProvider } from '@pushflodev/sdk/react';
function App() {
return (
<PushFloProvider
publishKey={process.env.NEXT_PUBLIC_PUSHFLO_PUBLISH_KEY!}
baseUrl={process.env.NEXT_PUBLIC_PUSHFLO_BASE_URL}
autoConnect={true}
debug={process.env.NODE_ENV === 'development'}
>
<Dashboard />
</PushFloProvider>
);
}usePushFlo Hook
import { usePushFlo } from '@pushflodev/sdk/react';
function Dashboard() {
const { connectionState, isConnected, connect, disconnect } = usePushFlo();
return (
<div>
<p>Status: {connectionState}</p>
<button onClick={connect} disabled={isConnected}>Connect</button>
<button onClick={disconnect} disabled={!isConnected}>Disconnect</button>
</div>
);
}useChannel Hook
import { useChannel } from '@pushflodev/sdk/react';
function NotificationBell() {
const { messages, lastMessage, clearMessages, isSubscribed } = useChannel('notifications', {
onMessage: (msg) => {
// Play sound, show toast, etc.
playNotificationSound();
},
maxMessages: 100, // Limit stored messages
});
return (
<div>
<span>({messages.length} new)</span>
{lastMessage && <p>Latest: {lastMessage.content.title}</p>}
<button onClick={clearMessages}>Clear</button>
</div>
);
}Error Handling
import {
PushFloClient,
PushFloError,
ConnectionError,
AuthenticationError
} from '@pushflodev/sdk';
const client = new PushFloClient({ publishKey: 'pub_xxx' });
try {
await client.connect();
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key:', error.message);
} else if (error instanceof ConnectionError) {
console.error('Connection failed:', error.message);
if (error.retryable) {
console.log('Will auto-retry...');
}
} else if (error instanceof PushFloError) {
console.error('PushFlo error:', error.code, error.message);
} else {
throw error;
}
}
// Listen for runtime errors
client.on('error', (error) => {
console.error('Runtime error:', error);
});Error Types
| Error Class | Description | Retryable |
|-------------|-------------|-----------|
| PushFloError | Base error class | Varies |
| ConnectionError | WebSocket connection issues | Yes |
| AuthenticationError | Invalid/missing API key | No |
| NetworkError | HTTP request failures | Varies |
| ValidationError | Invalid input (e.g., channel slug) | No |
Channel Naming Rules
Channel slugs must follow these rules:
| Rule | Valid | Invalid |
|------|-------|---------|
| Lowercase only | my-channel | My-Channel |
| Letters, numbers, hyphens | channel-123 | channel:123 |
| No special characters | my-channel | my_channel, my.channel |
| No leading/trailing hyphens | my-channel | -channel, channel- |
| No consecutive hyphens | my-channel | my--channel |
| 1-128 characters | a to 128 chars | empty or >128 chars |
Validation Utilities
import {
isValidChannelSlug,
toChannelSlug,
validateChannelSlug,
} from '@pushflodev/sdk';
// Check if valid
isValidChannelSlug('my-channel'); // true
isValidChannelSlug('my:channel'); // false
// Convert to valid slug
toChannelSlug('My Channel!'); // 'my-channel'
toChannelSlug('user:123:messages'); // 'user-123-messages'
// Detailed validation with suggestions
const result = validateChannelSlug('My:Channel');
// {
// valid: false,
// error: "Channel slug contains invalid characters: :. Only lowercase letters, numbers, and hyphens are allowed.",
// suggestion: "my-channel"
// }Auto-Creating Channels
If you need to auto-create channels with dynamic names, use toChannelSlug() to ensure valid slugs:
import { PushFloServer, toChannelSlug } from '@pushflodev/sdk/server';
const server = new PushFloServer({ secretKey: 'sec_xxx' });
// Convert dynamic names to valid slugs
const orgId = 'org_abc123';
const brandId = 'brand_xyz789';
const channelSlug = toChannelSlug(`${orgId}-${brandId}-notifications`);
// Result: 'org-abc123-brand-xyz789-notifications'
await server.createChannel({
name: 'Brand Notifications',
slug: channelSlug,
});Environment Variables
# .env.local (Next.js)
NEXT_PUBLIC_PUSHFLO_PUBLISH_KEY=pub_xxxxxxxxxxxxx
NEXT_PUBLIC_PUSHFLO_BASE_URL=https://api.pushflo.dev
PUSHFLO_SECRET_KEY=sec_xxxxxxxxxxxxx
# .env (Vite)
VITE_PUSHFLO_PUBLISH_KEY=pub_xxxxxxxxxxxxx
VITE_PUSHFLO_BASE_URL=https://api.pushflo.dev
# .env (Node.js server)
PUSHFLO_SECRET_KEY=sec_xxxxxxxxxxxxx
PUSHFLO_BASE_URL=https://api.pushflo.devTypeScript Types
import type {
// Connection
ConnectionState,
ClientOptions,
ServerOptions,
// Channels
Channel,
ChannelInput,
// Messages
Message,
PublishOptions,
PublishResult,
// API
Pagination,
// Subscriptions
Subscription,
SubscriptionOptions,
} from '@pushflodev/sdk';API Keys
| Key Prefix | Permissions | Use Case |
|------------|-------------|----------|
| pub_xxx | Read/Subscribe | Browser clients |
| sec_xxx | Read/Write/Publish | Server-side code |
| mgmt_xxx | Full access | Channel management |
Troubleshooting
Connection Issues
// Enable debug logging
const client = new PushFloClient({
publishKey: 'pub_xxx',
debug: true, // Logs all WebSocket activity
});CORS Errors
CORS is configured on the PushFlo servers. If you see CORS errors, ensure:
- You're using the correct API endpoint
- Your domain is registered in the PushFlo console
React Strict Mode
The SDK handles React Strict Mode correctly. The client is cleaned up and recreated as needed during development.
Server-Side Rendering (SSR)
The browser client requires WebSocket which is not available on the server. Use the SDK only in client components or with dynamic imports:
// Next.js App Router
'use client';
import { PushFloProvider } from '@pushflodev/sdk/react';License
MIT
