@pocketping/sdk-node
v1.9.0
Published
Node.js SDK for implementing PocketPing protocol
Maintainers
Readme
PocketPing Node.js SDK
Node.js SDK for PocketPing - real-time customer chat with mobile notifications.
Tip: Use the CLI for guided bridge setup:
npx @pocketping/cli init
Installation
npm install @pocketping/sdk-node
# Or with pnpm
pnpm add @pocketping/sdk-node
# Or with yarn
yarn add @pocketping/sdk-nodeQuick Start with Express
import express from 'express';
import { createServer } from 'http';
import { PocketPing } from '@pocketping/sdk-node';
const app = express();
const server = createServer(app);
app.use(express.json());
// Initialize PocketPing
const pp = new PocketPing({
welcomeMessage: 'Hi! How can we help you today?',
onNewSession: (session) => {
console.log(`New session: ${session.id}`);
},
onMessage: (message, session) => {
console.log(`Message from ${message.sender}: ${message.content}`);
},
});
// Mount PocketPing routes
app.use('/pocketping', pp.middleware());
// Attach WebSocket for real-time communication
pp.attachWebSocket(server);
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});Configuration Options
const pp = new PocketPing({
// Welcome message shown to new visitors
welcomeMessage: 'Hi! How can we help you?',
// Callbacks
onNewSession: (session) => { /* ... */ },
onMessage: (message, session) => { /* ... */ },
onEvent: (event, session) => { /* ... */ },
// Custom storage (default: in-memory)
storage: new MemoryStorage(),
// Bridge server for notifications (Telegram, Discord, Slack)
bridgeServerUrl: 'http://localhost:3001',
// Protocol version settings
protocolVersion: '1.0',
minSupportedVersion: '0.1',
// IP filtering (see IP Filtering section below)
ipFilter: {
enabled: true,
mode: 'blocklist',
blocklist: ['203.0.113.0/24'],
},
});IP Filtering
Block or allow specific IP addresses or CIDR ranges:
const pp = new PocketPing({
ipFilter: {
enabled: true,
mode: 'blocklist', // 'allowlist' | 'blocklist' | 'both'
blocklist: [
'203.0.113.0/24', // CIDR range
'198.51.100.50', // Single IP
],
allowlist: [
'10.0.0.0/8', // Internal network
],
logBlocked: true, // Log blocked requests (default: true)
blockedStatusCode: 403,
blockedMessage: 'Forbidden',
},
});
// Or with a custom filter function
const pp = new PocketPing({
ipFilter: {
enabled: true,
mode: 'blocklist',
customFilter: (ip, request) => {
// Return true to allow, false to block, null to defer to list-based filtering
if (ip.startsWith('192.168.')) return true; // Always allow local
return null; // Use blocklist/allowlist
},
},
});Modes
| Mode | Behavior |
|------|----------|
| blocklist | Block IPs in blocklist, allow all others (default) |
| allowlist | Only allow IPs in allowlist, block all others |
| both | Allowlist takes precedence, then blocklist is applied |
CIDR Support
The SDK supports CIDR notation for IP ranges:
- Single IP:
192.168.1.1(treated as/32) - Class C:
192.168.1.0/24(256 addresses) - Class B:
172.16.0.0/16(65,536 addresses) - Class A:
10.0.0.0/8(16M addresses)
Manual IP Check
// Check IP manually
const result = pp.checkIpFilter('192.168.1.50');
// result: { allowed: boolean, reason: string, matchedRule?: string }
// Get client IP from request headers
const clientIp = pp.getClientIp(request.headers);
// Checks: CF-Connecting-IP, X-Real-IP, X-Forwarded-ForUser-Agent Filtering
Block bots and automated requests from creating chat sessions:
const pp = new PocketPing({
uaFilter: {
enabled: true,
mode: 'blocklist', // 'blocklist' | 'allowlist' | 'both'
useDefaultBots: true, // Include ~50 default bot patterns
blocklist: ['my-custom-scraper', '/spam-\\d+/'], // Custom patterns
allowlist: ['my-monitoring-bot'], // Always allow these
logBlocked: true,
},
});Filter Modes
| Mode | Behavior |
|------|----------|
| blocklist | Block matching UAs, allow all others |
| allowlist | Only allow matching UAs, block all others |
| both | Allowlist takes precedence, then blocklist is applied |
Pattern Matching
- Substring:
googlebotmatches any UA containing "googlebot" (case-insensitive) - Regex:
/bot-\d+/- wrap pattern in/for regex matching
Default Bot Patterns (~50)
When useDefaultBots: true, these are blocked automatically:
- Search crawlers: GoogleBot, BingBot, DuckDuckBot, etc.
- SEO tools: SEMrush, Ahrefs, Screaming Frog
- HTTP libraries: curl, wget, Python-requests, axios
- AI crawlers: GPTBot, ChatGPT-User, Anthropic-AI
Manual UA Check
import { checkUaFilter, isBot, DEFAULT_BOT_PATTERNS } from '@pocketping/sdk-node';
// Quick bot check
if (isBot(req.headers['user-agent'])) {
return res.status(403).json({ error: 'Bots not allowed' });
}
// Full filter check
const result = checkUaFilter(req.headers['user-agent'], {
enabled: true,
useDefaultBots: true,
});
// result: { allowed: boolean, reason: string, matchedPattern?: string }Built-in Bridges
The SDK includes built-in bridges for Telegram, Discord, and Slack. No external libraries required - all communication uses HTTP APIs directly.
Telegram Bridge
import { PocketPing, TelegramBridge } from '@pocketping/sdk-node';
const pp = new PocketPing({
welcomeMessage: 'Hello!',
});
pp.addBridge(new TelegramBridge({
botToken: process.env.TELEGRAM_BOT_TOKEN!,
chatIds: process.env.TELEGRAM_CHAT_ID!,
showUrl: true, // Show page URL in notifications
}));Discord Bridge
import { PocketPing, DiscordBridge } from '@pocketping/sdk-node';
// Webhook mode (simple, send-only)
pp.addBridge(DiscordBridge.withWebhook({
webhookUrl: process.env.DISCORD_WEBHOOK_URL!,
}));
// Or Bot mode (bidirectional)
pp.addBridge(DiscordBridge.withBot({
botToken: process.env.DISCORD_BOT_TOKEN!,
channelId: process.env.DISCORD_CHANNEL_ID!,
}));Slack Bridge
import { PocketPing, SlackBridge } from '@pocketping/sdk-node';
// Webhook mode (simple, send-only)
pp.addBridge(SlackBridge.withWebhook({
webhookUrl: process.env.SLACK_WEBHOOK_URL!,
}));
// Or Bot mode (bidirectional)
pp.addBridge(SlackBridge.withBot({
botToken: process.env.SLACK_BOT_TOKEN!,
channelId: process.env.SLACK_CHANNEL_ID!,
}));Multiple Bridges
You can add multiple bridges to receive notifications on all platforms simultaneously:
const pp = new PocketPing({ welcomeMessage: 'Hello!' });
pp.addBridge(new TelegramBridge({ ... }));
pp.addBridge(DiscordBridge.withBot({ ... }));
pp.addBridge(SlackBridge.withWebhook({ ... }));Reply Behavior
- Telegram: uses native replies when
replyTois set and the Telegram message ID is available. - Discord: uses native replies via
message_referencewhen the Discord message ID is available. - Slack: renders a quoted block (left bar) in the thread.
Architecture Options
1. Embedded Mode with Built-in Bridges (Simple)
SDK handles everything directly including notifications - best for single server deployments:
import { PocketPing, TelegramBridge } from '@pocketping/sdk-node';
const pp = new PocketPing({
welcomeMessage: 'Hello!',
});
pp.addBridge(new TelegramBridge({
botToken: process.env.TELEGRAM_BOT_TOKEN!,
chatIds: process.env.TELEGRAM_CHAT_ID!,
}));
app.use('/pocketping', pp.middleware());
pp.attachWebSocket(server);2. Bridge Server Mode (Production)
SDK connects to a dedicated bridge server (written in Go) for notifications:
const pp = new PocketPing({
welcomeMessage: 'Hello!',
bridgeServerUrl: process.env.BRIDGE_SERVER_URL,
});The bridge server handles Telegram, Discord, and Slack integrations via HTTP APIs, keeping your main server lightweight.
Receiving Operator Messages (WebhookHandler)
To receive replies from operators via Telegram, Discord, or Slack, use the WebhookHandler:
import { WebhookHandler } from '@pocketping/sdk-node';
const webhookHandler = new WebhookHandler({
telegramBotToken: process.env.TELEGRAM_BOT_TOKEN,
slackBotToken: process.env.SLACK_BOT_TOKEN,
discordBotToken: process.env.DISCORD_BOT_TOKEN,
allowedBotIds: process.env.BRIDGE_TEST_BOT_IDS?.split(',').map((id) => id.trim()).filter(Boolean),
onOperatorMessage: async (sessionId, content, operatorName, source, attachments) => {
console.log(`Message from ${operatorName} via ${source}: ${content}`);
// Forward to widget
await pp.sendOperatorMessage(sessionId, content, source, operatorName);
// Handle attachments
for (const att of attachments) {
console.log(`Attachment: ${att.filename} (${att.size} bytes)`);
// att.data contains the file bytes
}
},
onOperatorMessageEdit: async (sessionId, bridgeMessageId, content, source) => {
console.log(`Edited message ${bridgeMessageId} via ${source}: ${content}`);
},
onOperatorMessageDelete: async (sessionId, bridgeMessageId, source) => {
console.log(`Deleted message ${bridgeMessageId} via ${source}`);
},
});
// Mount webhook routes
app.post('/webhooks/telegram', async (req, res) => {
const result = await webhookHandler.handleTelegramWebhook(req.body);
res.json(result);
});
app.post('/webhooks/slack', async (req, res) => {
const result = await webhookHandler.handleSlackWebhook(req.body);
res.json(result);
});
app.post('/webhooks/discord', async (req, res) => {
const result = await webhookHandler.handleDiscordWebhook(req.body);
res.json(result);
});Configure webhooks on each platform to point to your server:
- Telegram: Use
setWebhookAPI - Slack: Configure Event Subscriptions
- Discord: Set up Interactions Endpoint
Custom Storage
Implement the Storage interface for persistence:
import { Storage, Session, Message } from '@pocketping/sdk-node';
class PostgresStorage implements Storage {
async createSession(session: Session): Promise<void> {
// Your implementation
}
async getSession(sessionId: string): Promise<Session | null> {
// Your implementation
}
async saveMessage(message: Message): Promise<void> {
// Your implementation
}
async getMessages(sessionId: string, options?: { after?: string; limit?: number }): Promise<Message[]> {
// Your implementation
}
// ... implement other methods
}
const pp = new PocketPing({
storage: new PostgresStorage(),
});Events / Callbacks
const pp = new PocketPing({
onNewSession: (session) => {
console.log(`New session: ${session.id}`);
// Notify your team, log to analytics, etc.
},
onMessage: (message, session) => {
console.log(`Message from ${message.sender}: ${message.content}`);
},
onEvent: (event, session) => {
console.log(`Custom event: ${event.name}`, event.data);
},
});Custom Events
PocketPing supports bidirectional custom events between your website and backend.
Listening for Events (Widget -> Backend)
// Using callback in config
const pp = new PocketPing({
onEvent: (event, session) => {
console.log(`Event ${event.name} from session ${session.id}`);
console.log(`Data:`, event.data);
},
});
// Or using subscription
pp.onEvent('clicked_pricing', (event, session) => {
console.log(`User interested in: ${event.data?.plan}`);
});
// Subscribe to all events
pp.onEvent('*', (event, session) => {
console.log(`Event: ${event.name}`, event.data);
});Sending Events (Backend -> Widget)
// Send to a specific session
await pp.emitEvent('session-123', 'show_offer', {
discount: 20,
code: 'SAVE20',
});
// Broadcast to all connected sessions
await pp.broadcastEvent('announcement', {
message: 'New feature launched!',
});User Identification
Track and identify users across sessions:
// On the frontend (widget)
PocketPing.identify({
userId: 'user_123',
email: '[email protected]',
name: 'John Doe',
plan: 'pro',
});
// Get current identity
const identity = PocketPing.getIdentity();
// Reset identity (e.g., on logout)
PocketPing.reset();User identity is automatically included in session metadata and forwarded to bridges.
Operator Presence
Control operator online status:
// Set operator as online
pp.setOperatorOnline(true);
// Set operator as offline
pp.setOperatorOnline(false);When using the bridge server, presence is managed automatically via Telegram/Discord/Slack commands.
API Reference
PocketPing Class
| Method | Description |
|--------|-------------|
| middleware() | Returns Express middleware for HTTP routes |
| attachWebSocket(server) | Attaches WebSocket handler for real-time communication |
| setOperatorOnline(online) | Sets operator online/offline status |
| onEvent(name, callback) | Subscribe to custom events |
| offEvent(name, callback) | Unsubscribe from custom events |
| emitEvent(sessionId, name, data) | Send event to specific session |
| broadcastEvent(name, data) | Broadcast event to all sessions |
| getSession(sessionId) | Get session by ID |
| getMessages(sessionId, options) | Get messages for a session |
Types
interface Session {
id: string;
visitorId: string;
metadata: SessionMetadata;
createdAt: Date;
lastActivity: Date;
status: 'active' | 'closed';
}
interface Message {
id: string;
sessionId: string;
sender: 'visitor' | 'operator' | 'system' | 'ai';
content: string;
timestamp: Date;
metadata?: Record<string, unknown>;
}
interface CustomEvent {
name: string;
data?: Record<string, unknown>;
timestamp: Date;
sessionId?: string;
}Widget Integration
Add the widget to your website:
<script src="https://unpkg.com/@pocketping/widget"></script>
<script>
PocketPing.init({
endpoint: '/pocketping',
theme: 'light', // or 'dark'
primaryColor: '#667eea',
});
</script>Or via npm:
import { init } from '@pocketping/widget';
init({
endpoint: '/pocketping',
theme: 'dark',
primaryColor: '#667eea',
});License
MIT
