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

@nhtio/rocketchat-bot-sdk

v1.20260406.2

Published

An SDK for integrating bot users with RocketChat

Readme

@nhtio/rocketchat-bot-sdk

A TypeScript SDK for running Rocket.Chat bot accounts as sidecar Node.js processes. Connects to Rocket.Chat via both DDP WebSocket (real-time events) and the REST API, with built-in support for end-to-end encryption (E2EE), a typed event emitter, and a lifecycle hook system.


Requirements

  • Node.js 18 or later
  • Rocket.Chat 8.2.0 or later

Installation

npm install @nhtio/rocketchat-bot-sdk
# or
pnpm add @nhtio/rocketchat-bot-sdk

Quick Start

import { RocketChatBot } from '@nhtio/rocketchat-bot-sdk'

const bot = new RocketChatBot({
  serverUrl: 'https://chat.example.com',
  credentials: {
    username: 'my-bot',
    userId: 'botUserId',
    password: 'botPassword',
    personalAccessToken: 'botPersonalAccessToken',
  },
})

// Listen for messages before connecting
bot.on('message', (msg) => {
  if (msg.isOwn) return  // ignore messages sent by this bot

  console.log(`[${msg.rid}] ${msg.u.username}: ${msg.msg}`)

  if (msg.msg === '!ping') {
    bot.sendMessage({ rid: msg.rid, msg: 'pong!' })
  }
})

await bot.connect()

// Subscribe to a room to receive its messages
await bot.subscribeToRoom('GENERAL')

Configuration

BotConfig

interface BotConfig {
  serverUrl: string        // e.g. "https://chat.example.com"
  credentials: BotCredentials
  e2ee?: E2EEConfig        // omit to disable E2EE
  reconnect?: ReconnectConfig
}

BotCredentials

interface BotCredentials {
  username: string
  userId: string
  password: string
  personalAccessToken: string
  totpSecret?: string      // Base-32 TOTP secret for MFA accounts
}

E2EEConfig

interface E2EEConfig {
  passphrase: string           // used to protect the bot's RSA private key
  storage: E2EEStorageContract // your backend for key persistence
}

E2EEStorageContract is an interface you implement to control where E2EE keys are stored (filesystem, database, KMS, etc.):

interface E2EEStorageContract {
  getPublicKey(): Promise<string | null>
  setPublicKey(key: string): Promise<void>
  getPrivateKey(): Promise<string | null>
  setPrivateKey(key: string): Promise<void>
  getRoomKey(roomId: string, keyId: string): Promise<string | null>
  setRoomKey(roomId: string, keyId: string, key: string): Promise<void>
  getOldRoomKeys(roomId: string): Promise<Array<{ keyId: string; key: string }>>
  setOldRoomKeys(roomId: string, keys: Array<{ keyId: string; key: string }>): Promise<void>
}

ReconnectConfig

interface ReconnectConfig {
  enabled?: boolean    // default: true
  maxDelay?: number    // milliseconds, default: 30000
}

Events

Subscribe to events using the standard .on() / .once() / .off() API.

bot.on('message', (msg) => { /* ReceivedMessage */ })

Connection events

| Event | Arguments | Description | | -------------- | ---------------------------------- | -------------------------------------------------- | | connected | — | DDP session established and bot authenticated | | disconnected | reason: string | Connection lost or intentionally closed | | reconnecting | attempt: number, delayMs: number | About to attempt reconnection | | logged_in | userId: string | Bot successfully authenticated | | state:change | state, previous: ConnectionState | Internal state machine transition | | error | error: Error | Unrecoverable error (e.g. reconnect limit reached) | | ddp:changed | collection, id, fields | Raw DDP changed notification |

Message events

All message events receive a ReceivedMessage object, which extends RCMessage with an isOwn: boolean flag. isOwn is true when the message was sent by the bot itself.

| Event | Arguments | Description | | ----------------- | ---------------------- | -------------------------------- | | message | msg: ReceivedMessage | New message in a subscribed room | | message:updated | msg: ReceivedMessage | An existing message was edited | | message:deleted | { _id, rid } | A message was deleted |

Room events

| Event | Arguments | Description | | ---------------------- | ---------------------------- | ------------------------------------------ | | room:changed | room: RCRoom | Room metadata was updated | | subscription:changed | sub: RCSubscription | The bot's room subscription record changed | | typing | roomId, username, isTyping | A user started or stopped typing |

User events

| Event | Arguments | Description | | ------------- | -------------------------- | -------------------------------------------------------------------- | | user:status | userId, username, status | A user's presence changed — requires subscribeToUserStatus() first |

E2EE events

| Event | Arguments | Description | | ---------------- | ----------------------------------------- | ---------------------------------------------- | | e2ee:ready | — | E2EE keys loaded, encryption layer operational | | e2ee:decrypted | msg: ReceivedMessage, plaintext: string | An encrypted message was decrypted | | e2ee:error | error: Error, context: string | An E2EE operation failed |


Hooks

Hooks let you intercept operations before and after they execute. Hooks run on a per-instance basis and support async middleware with error recovery.

import { RocketChatBot } from '@nhtio/rocketchat-bot-sdk'

const bot = new RocketChatBot(config)

// Log every outgoing message
bot.hooks.hook('beforeSendMessage', async ([message]) => {
  console.log('Sending:', message.msg)
})

// Prevent messages containing a blocked word
bot.hooks.hook('beforeSendMessage', async ([message]) => {
  if (message.msg.includes('badword')) {
    throw new Error('Message blocked')
  }
})

Available hooks

| Hook | Before args | After args | | ---------------------------------------- | ----------------- | ---------------------------------- | | beforeConnect / afterConnect | — | error \| null | | beforeDisconnect / afterDisconnect | — | error \| null | | beforeSendMessage / afterSendMessage | OutgoingMessage | error \| null, RCMessage \| null | | beforeJoinRoom / afterJoinRoom | roomId: string | error \| null | | beforeLeaveRoom / afterLeaveRoom | roomId: string | error \| null |


End-to-End Encryption (E2EE)

When E2EE is configured, the SDK automatically encrypts outgoing messages and decrypts incoming ones for rooms that have an active E2EE key.

Setup

import { RocketChatBot, type E2EEStorageContract } from '@nhtio/rocketchat-bot-sdk'
import { readFile, writeFile } from 'node:fs/promises'

// Minimal filesystem-backed key store
const storage: E2EEStorageContract = {
  async getPublicKey() { return readFile('.keys/pub', 'utf8').catch(() => null) },
  async setPublicKey(k) { await writeFile('.keys/pub', k) },
  async getPrivateKey() { return readFile('.keys/priv', 'utf8').catch(() => null) },
  async setPrivateKey(k) { await writeFile('.keys/priv', k) },
  async getRoomKey(rid, kid) {
    return readFile(`.keys/${rid}-${kid}`, 'utf8').catch(() => null)
  },
  async setRoomKey(rid, kid, k) { await writeFile(`.keys/${rid}-${kid}`, k) },
  async getOldRoomKeys(rid) {
    const raw = await readFile(`.keys/${rid}-old.json`, 'utf8').catch(() => '[]')
    return JSON.parse(raw)
  },
  async setOldRoomKeys(rid, keys) {
    await writeFile(`.keys/${rid}-old.json`, JSON.stringify(keys))
  },
}

const bot = new RocketChatBot({
  serverUrl: 'https://chat.example.com',
  credentials: { /* ... */ },
  e2ee: {
    passphrase: process.env.BOT_E2EE_PASSPHRASE!,
    storage,
  },
})

bot.on('e2ee:ready', () => console.log('E2EE ready'))

// Encrypted messages arrive already decrypted via the 'message' event.
// The e2ee:decrypted event fires alongside it for inspection.
bot.on('e2ee:decrypted', (msg, plaintext) => {
  console.log('Decrypted:', plaintext)
})

await bot.connect()

Creating a room key

If you create a new encrypted room, generate its key before sending messages:

await bot.createRoomKey(roomId)

Manual decryption

For messages fetched via getMessage() rather than the stream:

const raw = await bot.getMessage(messageId)
if (raw.content) {
  const plaintext = await bot.decryptMessage(raw.rid, raw.content)
}

Files

uploadFile follows Rocket.Chat's two-step media protocol. When E2EE is active for the target room the file bytes are automatically encrypted before upload.

import { readFile } from 'node:fs/promises'

const content = await readFile('./report.pdf')

const msg = await bot.uploadFile({
  rid: roomId,
  file: content,
  filename: 'report.pdf',
  mimetype: 'application/pdf',
  msg: 'Here is the weekly report.',   // optional message text
  description: 'Weekly report',        // optional attachment description
  tmid: threadParentId,                // optional: post into a thread
})

console.log('Uploaded, message ID:', msg._id)

downloadFile fetches the attachment from a received message. For E2EE rooms the raw bytes are decrypted automatically using the per-file AES-CTR key stored in the message envelope.

bot.on('message', async (msg) => {
  if (msg.attachments?.length) {
    const { buffer, filename } = await bot.downloadFile(msg)
    // buffer is already decrypted when the room uses E2EE
    await writeFile(filename ?? 'download', buffer)
  }
})

API Reference

Connection

bot.connect(): Promise<void>
bot.disconnect(): Promise<void>
bot.state: ConnectionState

Streams

bot.subscribeToRoom(roomId: string): Promise<void>
bot.unsubscribeFromRoom(roomId: string): Promise<void>

Messages

bot.sendMessage(message: OutgoingMessage): Promise<RCMessage>
bot.postMessage(params: PostMessageParams): Promise<RCMessage>
bot.updateMessage(msgId: string, roomId: string, text: string): Promise<RCMessage>
bot.deleteMessage(msgId: string, roomId: string): Promise<void>
bot.getMessage(msgId: string): Promise<RCMessage>
bot.getThreadMessages(tmid: string, count?: number, offset?: number): Promise<{ messages: RCMessage[]; total: number }>
bot.getHistory(roomId: string, opts?: HistoryOptions): Promise<{ messages: RCMessage[] }>
bot.syncMessages(roomId: string, lastUpdate: Date | string): Promise<{ updated: RCMessage[]; deleted: { _id: string }[] }>
bot.getMentionedMessages(roomId: string, count?: number, offset?: number): Promise<{ messages: RCMessage[]; total: number }>
bot.getThreadsList(roomId: string, opts?: ThreadListOptions): Promise<{ threads: RCMessage[]; total: number }>
bot.getPinnedMessages(roomId: string, count?: number, offset?: number): Promise<{ messages: RCMessage[]; total: number }>
bot.getStarredMessages(roomId: string, count?: number, offset?: number): Promise<{ messages: RCMessage[]; total: number }>
bot.getDeletedMessages(roomId: string, since: Date | string): Promise<{ messages: { _id: string }[]; total: number }>
bot.getDiscussions(roomId: string, opts?: DiscussionListOptions): Promise<{ messages: RCMessage[]; total: number }>
bot.markAsRead(rid: string): Promise<void>
bot.reactToMessage(messageId: string, emoji: string, shouldReact?: boolean): Promise<void>
bot.pinMessage(messageId: string): Promise<void>
bot.unpinMessage(messageId: string): Promise<void>
bot.starMessage(messageId: string): Promise<void>
bot.unstarMessage(messageId: string): Promise<void>
bot.followMessage(mid: string): Promise<void>
bot.unfollowMessage(mid: string): Promise<void>
bot.reportMessage(messageId: string, description: string): Promise<void>

Files

bot.uploadFile(params: FileUploadParams): Promise<RCMessage>
bot.downloadFile(message: RCMessage): Promise<{ buffer: Uint8Array; filename?: string }>

interface FileUploadParams {
  rid: string
  file: Buffer | Uint8Array
  filename: string
  mimetype?: string
  description?: string
  tmid?: string
  msg?: string
}

Rooms

bot.createChannel(params: CreateChannelParams): Promise<RCRoom>
bot.createGroup(params: CreateGroupParams): Promise<RCRoom>
bot.createDM(params: CreateDMParams): Promise<RCRoom>
bot.createDiscussion(params: CreateDiscussionParams): Promise<RCRoom>
bot.joinChannel(roomId: string): Promise<void>
bot.leaveRoom(roomId: string): Promise<void>
bot.inviteToChannel(roomId: string, userId: string): Promise<void>
bot.inviteToGroup(roomId: string, userId: string): Promise<void>
bot.kickFromRoom(roomId: string, userId: string): Promise<void>
bot.muteUser(roomId: string, user: { userId: string } | { username: string }): Promise<void>
bot.unmuteUser(roomId: string, user: { userId: string } | { username: string }): Promise<void>
bot.getRoomInfo(roomId: string): Promise<RCRoom>
bot.getRoomMembers(roomId: string, count?: number, offset?: number): Promise<{ members: RCUser[]; total: number }>
bot.getRoomRoles(roomId: string): Promise<RCRoomRole[]>
bot.isMember(roomId: string, user: { userId: string } | { username: string }): Promise<boolean>
bot.getSubscriptions(): Promise<RCSubscription[]>
bot.getRooms(): Promise<RCRoom[]>
bot.setRoomTopic(roomId: string, topic: string): Promise<void>
bot.setRoomAnnouncement(roomId: string, announcement: string): Promise<void>
bot.setRoomDescription(roomId: string, description: string): Promise<void>
bot.renameRoom(roomId: string, name: string): Promise<void>
bot.setRoomReadOnly(roomId: string, readOnly: boolean): Promise<void>
bot.setRoomType(roomId: string, type: 'c' | 'p'): Promise<void>
bot.archiveRoom(roomId: string): Promise<void>
bot.unarchiveRoom(roomId: string): Promise<void>
bot.favoriteRoom(roomId: string, favorite: boolean): Promise<void>
bot.hideRoom(roomId: string): Promise<void>
bot.runSlashCommand(command: string, roomId: string, params?: string, tmid?: string): Promise<void>

Users

bot.getUserInfo(query: { userId: string } | { username: string }): Promise<RCUser>
bot.setStatus(options: SetStatusParams): Promise<void>
bot.getStatus(query?: { userId: string } | { username: string }): Promise<{ status: UserStatus; message: string; connectionStatus: string }>

Presence & Typing

bot.setAway(): Promise<void>
bot.sendTyping(roomId: string, isTyping: boolean): Promise<void>
bot.subscribeToUserStatus(): Promise<void>  // then listen for 'user:status' events

E2EE

bot.e2eeReady: boolean
bot.hasRoomKey(roomId: string): boolean
bot.createRoomKey(roomId: string): Promise<void>
bot.decryptMessage(roomId: string, encryptedContent: string | { algorithm: string; kid: string; iv: string; ciphertext: string }): Promise<string | null>

DDP passthrough

For advanced use cases that require direct access to the DDP connection:

bot.callMethod<T>(method: string, params?: unknown[]): Promise<T>
bot.subscribe(name: string, params?: unknown[]): Promise<string>
bot.unsubscribe(id: string): Promise<void>
bot.rest: RESTClient   // direct REST client access

Exported Types

import type {
  BotConfig,
  BotCredentials,
  E2EEConfig,
  E2EEStorageContract,
  ReconnectConfig,
  BotEventMap,
  BotHookEvents,
  ConnectionState,
  RCUser,
  RCRoom,
  RCRoomRole,
  RCSubscription,
  RCMessage,
  ReceivedMessage,
  RCAttachment,
  EJSONDate,
  UserStatus,
  OutgoingMessage,
  PostMessageParams,
  FileUploadParams,
  CreateChannelParams,
  CreateGroupParams,
  CreateDMParams,
  CreateDiscussionParams,
  HistoryOptions,
  ThreadListOptions,
  DiscussionListOptions,
  SetStatusParams,
} from '@nhtio/rocketchat-bot-sdk'

License

MIT — Copyright © 2025-present New Horizon Technology LTD (nht.io)

This package is published for public use under the MIT License. The source repository is private as this library is a work-product of NHT. Bug reports and questions can be directed to the package maintainer.