npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pocketping/sdk-node

v1.9.0

Published

Node.js SDK for implementing PocketPing protocol

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-node

Quick 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-For

User-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: googlebot matches 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 replyTo is set and the Telegram message ID is available.
  • Discord: uses native replies via message_reference when 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 setWebhook API
  • 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