Implements real-time features with Pusher Channels for WebSocket-based pub/sub messaging. Use when adding live updates, notifications, chat, presence indicators, or collaborative features.
Adds real-time WebSocket pub/sub messaging using Pusher Channels. Use when building live notifications, chat, presence indicators, or collaborative features that need instant updates.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Real-time WebSocket infrastructure for pub/sub messaging. Supports public, private, presence, and encrypted channels.
npm install pusher-js
import Pusher from 'pusher-js';
const pusher = new Pusher('YOUR_APP_KEY', {
cluster: 'us2', // your cluster
});
// Subscribe to a channel
const channel = pusher.subscribe('my-channel');
// Bind to an event
channel.bind('my-event', (data) => {
console.log('Received:', data);
});
npm install pusher
import Pusher from 'pusher';
const pusher = new Pusher({
appId: 'YOUR_APP_ID',
key: 'YOUR_APP_KEY',
secret: 'YOUR_APP_SECRET',
cluster: 'us2',
useTLS: true
});
// Trigger an event
await pusher.trigger('my-channel', 'my-event', {
message: 'Hello from server!'
});
Anyone can subscribe. No authentication required.
// Client
const channel = pusher.subscribe('news-updates');
channel.bind('new-article', (data) => {
console.log('New article:', data.title);
});
Require authentication. Prefix with private-.
// Client
const privateChannel = pusher.subscribe('private-user-123');
privateChannel.bind('notification', (data) => {
console.log('Private notification:', data);
});
// Server - Auth endpoint (e.g., /pusher/auth)
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
// Verify user has access to this channel
if (!userCanAccess(req.user, channel)) {
return res.status(403).json({ error: 'Forbidden' });
}
const auth = pusher.authorizeChannel(socketId, channel);
res.json(auth);
});
Track who's online. Prefix with presence-.
// Client
const presenceChannel = pusher.subscribe('presence-room-1');
// Current members
presenceChannel.bind('pusher:subscription_succeeded', (members) => {
console.log('Member count:', members.count);
members.each((member) => {
console.log('Member:', member.id, member.info);
});
});
// New member joined
presenceChannel.bind('pusher:member_added', (member) => {
console.log('Joined:', member.info.name);
});
// Member left
presenceChannel.bind('pusher:member_removed', (member) => {
console.log('Left:', member.info.name);
});
// Server - Auth endpoint for presence
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
const presenceData = {
user_id: req.user.id,
user_info: {
name: req.user.name,
avatar: req.user.avatar
}
};
const auth = pusher.authorizeChannel(socketId, channel, presenceData);
res.json(auth);
});
End-to-end encryption. Prefix with private-encrypted-.
// Client - need to enable encryption
const pusher = new Pusher('APP_KEY', {
cluster: 'us2',
channelAuthorization: {
endpoint: '/pusher/auth'
}
});
const encrypted = pusher.subscribe('private-encrypted-secret');
encrypted.bind('message', (data) => {
// Data is automatically decrypted
console.log('Decrypted:', data);
});
const pusher = new Pusher('APP_KEY', {
cluster: 'us2',
// Authentication
channelAuthorization: {
endpoint: '/pusher/auth',
transport: 'ajax', // or 'jsonp'
headers: {
'Authorization': 'Bearer ' + token
}
},
// Connection options
forceTLS: true,
enabledTransports: ['ws', 'wss'],
// Auto-reconnect (enabled by default)
activityTimeout: 120000,
pongTimeout: 30000
});
pusher.connection.bind('connected', () => {
console.log('Connected! Socket ID:', pusher.connection.socket_id);
});
pusher.connection.bind('disconnected', () => {
console.log('Disconnected');
});
pusher.connection.bind('error', (err) => {
console.error('Connection error:', err);
});
pusher.connection.bind('state_change', (states) => {
console.log('State changed from', states.previous, 'to', states.current);
});
// States: initialized, connecting, connected, unavailable, failed, disconnected
Trigger events from client (private/presence channels only).
// Enable on dashboard first
const channel = pusher.subscribe('private-chat-room');
// Trigger from client (prefix with 'client-')
channel.trigger('client-typing', {
user: 'Alice',
typing: true
});
// Listen for client events
channel.bind('client-typing', (data) => {
console.log(data.user, 'is typing...');
});
// Single channel
await pusher.trigger('my-channel', 'event-name', { data: 'value' });
// Multiple channels (max 100)
await pusher.trigger(['channel-1', 'channel-2'], 'event-name', { data: 'value' });
// Exclude a socket (don't send to sender)
await pusher.trigger('my-channel', 'event-name', { data: 'value' }, {
socket_id: 'exclude-socket-id'
});
await pusher.triggerBatch([
{ channel: 'channel-1', name: 'event-1', data: { msg: 'Hello' } },
{ channel: 'channel-2', name: 'event-2', data: { msg: 'World' } }
]);
// Get channel info
const info = await pusher.get({ path: '/channels/presence-room-1' });
// Get users in presence channel
const users = await pusher.get({
path: '/channels/presence-room-1/users'
});
// Get all channels
const channels = await pusher.get({ path: '/channels' });
import Pusher from 'pusher-js';
import { useEffect, useState, createContext, useContext } from 'react';
// Create context
const PusherContext = createContext(null);
export function PusherProvider({ children }) {
const [pusher] = useState(() =>
new Pusher('APP_KEY', { cluster: 'us2' })
);
useEffect(() => {
return () => pusher.disconnect();
}, []);
return (
<PusherContext.Provider value={pusher}>
{children}
</PusherContext.Provider>
);
}
// Hook to subscribe to channel
export function useChannel(channelName) {
const pusher = useContext(PusherContext);
const [channel, setChannel] = useState(null);
useEffect(() => {
const ch = pusher.subscribe(channelName);
setChannel(ch);
return () => {
pusher.unsubscribe(channelName);
};
}, [channelName]);
return channel;
}
// Hook to bind to events
export function useEvent(channel, eventName, callback) {
useEffect(() => {
if (!channel) return;
channel.bind(eventName, callback);
return () => channel.unbind(eventName, callback);
}, [channel, eventName, callback]);
}
// Usage
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
const channel = useChannel(`private-room-${roomId}`);
useEvent(channel, 'new-message', (data) => {
setMessages((prev) => [...prev, data]);
});
return (
<div>
{messages.map((msg, i) => (
<div key={i}>{msg.text}</div>
))}
</div>
);
}
// app/api/pusher/auth/route.ts
import Pusher from 'pusher';
import { getServerSession } from 'next-auth';
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID!,
key: process.env.NEXT_PUBLIC_PUSHER_KEY!,
secret: process.env.PUSHER_SECRET!,
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
useTLS: true
});
export async function POST(req: Request) {
const session = await getServerSession();
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
const data = await req.formData();
const socketId = data.get('socket_id') as string;
const channel = data.get('channel_name') as string;
// For presence channels
if (channel.startsWith('presence-')) {
const presenceData = {
user_id: session.user.id,
user_info: {
name: session.user.name,
image: session.user.image
}
};
const auth = pusher.authorizeChannel(socketId, channel, presenceData);
return Response.json(auth);
}
// For private channels
const auth = pusher.authorizeChannel(socketId, channel);
return Response.json(auth);
}
// Client
const userChannel = pusher.subscribe(`private-user-${userId}`);
userChannel.bind('notification', (notification) => {
showToast(notification.message);
updateNotificationCount();
});
// Server
async function sendNotification(userId, notification) {
await pusher.trigger(`private-user-${userId}`, 'notification', {
id: notification.id,
message: notification.message,
createdAt: new Date().toISOString()
});
}
// Client
let typingTimeout;
function handleInput() {
channel.trigger('client-typing', { userId: myUserId });
clearTimeout(typingTimeout);
typingTimeout = setTimeout(() => {
channel.trigger('client-stopped-typing', { userId: myUserId });
}, 1000);
}
channel.bind('client-typing', ({ userId }) => {
showTypingIndicator(userId);
});
channel.bind('client-stopped-typing', ({ userId }) => {
hideTypingIndicator(userId);
});
const presenceChannel = pusher.subscribe('presence-app');
const [onlineUsers, setOnlineUsers] = useState([]);
presenceChannel.bind('pusher:subscription_succeeded', (members) => {
const users = [];
members.each((member) => users.push(member.info));
setOnlineUsers(users);
});
presenceChannel.bind('pusher:member_added', (member) => {
setOnlineUsers((prev) => [...prev, member.info]);
});
presenceChannel.bind('pusher:member_removed', (member) => {
setOnlineUsers((prev) => prev.filter((u) => u.id !== member.id));
});
Pusher.logToConsole = true; // Enable in development
const pusher = new Pusher('APP_KEY', {
cluster: 'us2'
});
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.