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

@snapie/chat-client

v0.2.0

Published

Hive-authenticated chat client SDK for Snapie — DMs, channels, groups, and real-time subscriptions

Readme

@snapie/chat-client

Hive-authenticated chat SDK for Snapie. Supports DMs, channels, groups, real-time subscriptions (polling + FCM), and typing indicators.

Works in any JavaScript/TypeScript environment. React hooks available as a separate entry point.


Installation

npm install @snapie/chat-client
# or
pnpm add @snapie/chat-client

Quick start

import { ChatClient } from '@snapie/chat-client';

const client = new ChatClient({ baseUrl: 'https://snapie.io' });

// Authenticate with a Hive account (posting key challenge/response)
await client.authenticate(username, async (challenge) => {
  // Use Hive Keychain, Aioha, or any signing library
  return await keychain.signBuffer(username, challenge, 'Posting');
});

// Get all conversations
const conversations = await client.getConversations();

// Subscribe to live messages (polls every 15s, merges new ones)
const unsub = client.subscribeToMessages(conversationId, 'dm', (messages) => {
  console.log(messages);
});

// Send a message
await client.sendMessage(conversationId, 'dm', 'Hello from 3speak!');

// Stop subscribing
unsub();

// Clean up everything when done
client.destroy();

Configuration

const client = new ChatClient({
  baseUrl: 'https://snapie.io',   // required — Snapie instance URL
  pollInterval: 15000,             // optional — ms between polls (default: 15000)
  storage: customStorage,          // optional — custom StorageAdapter (see below)
});

Custom storage (React Native / Node)

By default the client uses localStorage. Override it with any object that implements getItem, setItem, removeItem:

import AsyncStorage from '@react-native-async-storage/async-storage';

const client = new ChatClient({
  baseUrl: 'https://snapie.io',
  storage: {
    getItem: (key) => AsyncStorage.getItem(key),      // note: sync wrapper needed
    setItem: (key, val) => AsyncStorage.setItem(key, val),
    removeItem: (key) => AsyncStorage.removeItem(key),
  },
});

Authentication

The auth flow uses Hive's posting key signature — no passwords, no OAuth.

// 1. Authenticate
await client.authenticate(username, async (challenge) => {
  return await signingLibrary.sign(challenge);
});

// 2. Check status
client.isAuthenticated(); // boolean
client.getUsername();     // string | null

// 3. Logout
client.logout();

Conversations

// List all conversations (DMs + channels + groups)
const conversations = await client.getConversations();
// Conversation { _id, name, type: 'dm'|'channel'|'group', lastMessage, unread, ... }

// Open or resume a DM with another Hive user
const conv = await client.openDm('alice');

Messages

// Fetch messages (one-time)
const { messages } = await client.getMessages(conversationId, 'dm');
const { messages } = await client.getMessages(channelId, 'channel', { limit: 50 });

// Send
const { message } = await client.sendMessage(conversationId, 'dm', 'Hey!');

// Reply
const { message } = await client.sendMessage(conversationId, 'dm', 'Got it', replyToMessageId);

// Edit
const updated = await client.editMessage(conversationId, 'dm', messageId, 'Edited text');

Real-time subscriptions

All subscribe* methods return an unsubscribe function. They fire immediately with current data, then refresh on every poll tick.

// Messages
const unsub = client.subscribeToMessages(conv._id, conv.type, (messages) => {
  setMessages(messages); // always the full ordered list
});

// Conversations list
const unsub = client.subscribeToConversations((conversations) => {
  setConversations(conversations);
});

// Unread badge count
const unsub = client.subscribeToUnreadCount((count) => {
  setBadge(count);
});

// Stop any subscription
unsub();

FCM foreground integration

When a Firebase Cloud Messaging push arrives while the app is open, trigger an immediate refresh instead of waiting for the next poll tick:

import { onMessage } from 'firebase/messaging';

onMessage(messaging, () => {
  client.onForegroundPush();
});

Channels & groups

// List public channels
const channels = await client.getChannels();

// Join / leave
await client.joinChannel(channelId);
await client.leaveChannel(channelId);

// Create a group
const group = await client.createGroup({
  name: 'My Group',
  description: 'Optional',
  isPublic: false,
  members: ['alice', 'bob'],
});

await client.addGroupMember(group._id, 'charlie');
await client.removeGroupMember(group._id, 'charlie');

Typing indicators

// Signal that the current user is typing
await client.setTyping(conversationId, true);
await client.setTyping(conversationId, false);

// Poll who else is typing
const { users } = await client.getTyping(conversationId);
// users: string[] — list of usernames currently typing

Preferences

await client.muteUser('spammer');
await client.unmuteUser('spammer');
await client.blockUser('troll');
await client.unblockUser('troll');

const prefs = await client.getPreferences();
// { mutedUsers: string[], blockedUsers: string[] }

Image uploads & rendering

Uploading an image

// Upload a File and get back the public URL
const url = await client.uploadImage(file, username, async (challenge) => {
  return await keychain.signBuffer(username, challenge, 'Posting');
});

// Embed the URL in a message
await client.sendMessage(conversationId, 'dm', url);
// or append to text:
await client.sendMessage(conversationId, 'dm', `Check this out\n${url}`);

Rendering image messages

Snapie embeds image URLs as plain text inside message.content. Use extractImageUrls to detect and render them:

import { extractImageUrls } from '@snapie/chat-client';

function MessageBubble({ message }) {
  const images = extractImageUrls(message.content);
  return (
    <div>
      <p>{message.content}</p>
      {images.map(url => <img key={url} src={url} alt="" />)}
    </div>
  );
}

isImageUrl(url) is also exported if you need to check a single URL.


Push notifications (FCM)

// Register a device token so this user receives push notifications
await client.registerDevice(fcmToken);

React hooks

import { ChatProvider, useConversations, useChatMessages, useUnreadCount, useTyping } from '@snapie/chat-client/react';

// Wrap once at the top level
<ChatProvider client={client}>
  <App />
</ChatProvider>

useConversations

function ConversationList() {
  const { conversations, loading } = useConversations();
  if (loading) return <Spinner />;
  return conversations.map(conv => <ConvRow key={conv._id} conv={conv} />);
}

useChatMessages

function MessageView({ conv }) {
  const { messages, loading, sendMessage, editMessage } = useChatMessages(conv._id, conv.type);

  return (
    <>
      {messages.map(m => <Bubble key={m._id} message={m} />)}
      <Input onSend={sendMessage} />
    </>
  );
}

useUnreadCount

function ChatButton() {
  const { unreadCount } = useUnreadCount();
  return <Button badge={unreadCount}>Chat</Button>;
}

useTyping

function TypingIndicator({ convId }) {
  const { typingUsers, setTyping } = useTyping(convId);
  // setTyping(true) when user starts typing — auto-clears after 5s
  return typingUsers.length > 0 ? <Text>{typingUsers.join(', ')} is typing...</Text> : null;
}

TypeScript types

All types are exported from the main entry point:

import type {
  ChatClient,
  ChatClientOptions,
  Conversation,
  Message,
  Channel,
  MessagesResult,
  DmDeliveryInfo,
  DmStatusInfo,
  TypingStatusInfo,
  ChatPreferences,
  StorageAdapter,
} from '@snapie/chat-client';

Backend

The SDK communicates with the Snapie chat API at {baseUrl}/api/chat/*. The backend is hosted at https://snapie.io. Auth tokens are JWTs issued after Hive posting-key signature verification and are stored in the configured storage adapter.