@bernierllc/in-app-chat
v4.0.1
Published
A generic, role-based in-app chat system for Next.js applications
Downloads
116
Readme
@bernierllc/in-app-chat
A drop-in, role-based in-app messaging suite for Next.js applications. Bring your own data store and permission policy; the package supplies the React provider, hooks, and UI components. External platform bridging (Slack, Discord, Teams) and NeverHub slash-command routing are also included.
Note: Roles are fully configurable. You can use any string for roles (e.g.,
'user','admin','moderator') and configure permissions as needed. No roles are hardcoded.
Naming migration — canonical InAppChat* naming
2.9.0 renamed the package-identity public API from Messaging*/MESSAGING_*/messaging.config.*
to canonical InAppChat*/IN_APP_CHAT_*/in-app-chat.config.* to match the package name, keeping
every old name as a working @deprecated alias. 3.0.0 removes those aliases — only the canonical
names below remain. If you are upgrading from 2.9.x or earlier, rename any Messaging* symbol using
this map. (Legacy MESSAGING_* env vars and messaging.config.* files are still read for runtime
config, with a one-time deprecation warning.)
| Surface | Removed in 3.0.0 (was) | Canonical (use this) |
|---|---|---|
| Provider | MessagingProvider | InAppChatProvider |
| Hook | useMessaging | useInAppChat |
| Constants | MESSAGING_CONSTANTS | IN_APP_CHAT_CONSTANTS |
| Config loader | loadMessagingConfiguration, loadMessagingConfigurationWithSources, getMessagingEnvironmentDocumentation, MessagingConfigurationLoader | loadInAppChatConfiguration, loadInAppChatConfigurationWithSources, getInAppChatEnvironmentDocumentation, InAppChatConfigurationLoader |
| Config schema | messagingConfigSchema, validateMessagingConfig | inAppChatConfigSchema, validateInAppChatConfig |
| Config types | MessagingRuntimeConfig, ResolvedMessagingConfig, MessagingConfig, MessagingProviderProps, UseMessagingReturn, MessagingStats, MessagingError, MessagingEvent, MessagingTheme, MessagingPermissions | InAppChatRuntimeConfig, ResolvedInAppChatConfig, InAppChatConfig, InAppChatProviderProps, UseInAppChatReturn, InAppChatStats, InAppChatErrorInfo, InAppChatEvent, InAppChatTheme, InAppChatPermissions |
| Env vars | MESSAGING_* | IN_APP_CHAT_* (legacy MESSAGING_* still read, with a one-time deprecation warning) |
| Config file | messaging.config.{js,json} | in-app-chat.config.{js,cjs,json} (legacy still discovered, with a one-time deprecation warning) |
Domain names that describe chat concepts —
Message,MessageThread,MessagesPage,MessageInput,useConversations,sendMessage, etc. — are not renamed; they remain the canonical API.
3.0.0 — Breaking Changes
The following APIs are removed in 3.0.0. Replace them before upgrading.
| Removed | Why / Migration |
|---|---|
| Deprecated Messaging* / MESSAGING_CONSTANTS / messaging*Config* aliases | The 2.9.0 @deprecated aliases are removed. Rename to the canonical InAppChat* / IN_APP_CHAT_CONSTANTS / inAppChat*Config* symbols using the naming migration map above. |
| encryptMessage / decryptMessage | These were btoa/atob base64 wrappers, never a security guarantee. At-rest and in-transit encryption belong to your MessageStore / RealTimeTransport implementation. Remove calls — there is no replacement in this package. |
| inAppChatApi / createInAppChatApi (and 2.9.0 aliases messagingApi / createMessagingApi) | The api/* entry-point is removed. An HTTP boundary is now a MessageStore whose methods fetch your own routes. See the HTTP-backed store example below. |
| createDatabaseConnection / runMigrations / seedDatabase | The database/* entry-point is removed. Replaced by the MessageStore port — implement it against your DB of choice. InMemoryMessageStore is the reference. |
| InAppChatProviderProps.config | The provider shape changed from { config: { roles, permissions, database, ... } } to { store, policy, currentUser, transport?, children }. Update your provider instantiation. |
| validateMessagePermissions(role, permission) (old positional signature) | Now validateMessagePermissions(policy: PermissionPolicy, role: string, action: ChatPermission): boolean. Pass the injected policy as the first argument. |
Features
- Role-based messaging — Different interfaces and features for different user types
- Real-time updates — Live message delivery via the optional
RealTimeTransportport - File attachments — Support for documents and media sharing
- Message templates — Role-specific pre-written messages
- Bulk messaging — Send messages to multiple recipients
- Search and filtering — Advanced conversation management
- Responsive design — Works on desktop and mobile
- Accessibility — WCAG 2.1 AA compliant
- Customizable — Easy to theme and extend
- External Integrations — Slack, Discord, Teams bridging with user mapping
- Slash Commands — Dynamic command routing via NeverHub service discovery
- Live Configuration — Admin UI toggles for features and integrations
- Service Discovery — Auto-register commands when packages come online
Quick Start
1. Install
npm install @bernierllc/in-app-chat2. Define a permission policy
import type { PermissionPolicy } from '@bernierllc/in-app-chat';
const policy: PermissionPolicy = {
roles: {
admin: ['send', 'delete', 'createTemplate', 'bulkSend'],
user: ['send'],
viewer: [],
},
};3. Create a store
For getting started quickly, use the shipped InMemoryMessageStore:
import { InMemoryMessageStore } from '@bernierllc/in-app-chat';
const store = new InMemoryMessageStore({
conversations: [
{
conversationId: 'conv-1',
participants: [{ id: 'user-1', role: 'admin', name: 'Alice' }],
lastMessage: 'Hello!',
timestamp: new Date().toISOString(),
unread: 0,
},
],
});4. Mount the provider
store and policy come from steps 2–3 above.
// app/layout.tsx
import { InAppChatProvider } from '@bernierllc/in-app-chat';
export default function RootLayout({ children }: { children: React.ReactNode }) {
const currentUser = { id: 'user-1', role: 'admin' }; // from your auth layer
return (
<html lang="en">
<body>
<InAppChatProvider
store={store}
policy={policy}
currentUser={currentUser}
>
{children}
</InAppChatProvider>
</body>
</html>
);
}5. Consume via context
import { useInAppChatContext } from '@bernierllc/in-app-chat';
export function ChatPanel() {
const { conversations, loading, error, sendMessage, selectConversation } = useInAppChatContext();
if (loading) return <p>Loading…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{conversations.map(c => (
<li key={c.conversationId} onClick={() => selectConversation(c.conversationId)}>
{c.lastMessage}
</li>
))}
</ul>
);
}6. Add messaging components to your navigation
import { MessagesIcon, MessagesDropdown } from '@bernierllc/in-app-chat';
export function Navigation() {
return (
<nav>
<MessagesIcon />
<MessagesDropdown />
</nav>
);
}7. Full messaging page
import { MessagesPage } from '@bernierllc/in-app-chat';
export default function Messages() {
return <MessagesPage />;
}Provider Props
interface InAppChatProviderProps {
store: MessageStore; // required — the ONLY data seam
policy: PermissionPolicy; // required — role → permissions data
currentUser: { id: string; role: string }; // required
transport?: RealTimeTransport; // optional — live updates; absent ⇒ no crash
children: React.ReactNode;
}Adapter Ports
MessageStore (required)
The persistence port. Back it with your database, an external API, or InMemoryMessageStore.
interface MessageStore {
getConversations(userId: string, filters?: ConversationFilters): Promise<ConversationListItem[]>;
getThread(conversationId: string): Promise<MessageThread | null>;
sendMessage(input: SendMessageInput): Promise<Message>;
markRead(conversationId: string, userId: string): Promise<void>;
listTemplates(ownerId: string): Promise<MessageTemplate[]>;
createTemplate(input: MessageTemplate): Promise<MessageTemplate>;
updateTemplate(id: string, updates: Partial<Omit<MessageTemplate, 'id'>>): Promise<MessageTemplate>;
deleteTemplate(id: string): Promise<void>;
}
// sendMessage input shape
interface SendMessageInput {
conversationId: string;
senderId: string;
body: string;
attachments?: MessageAttachment[];
}RealTimeTransport (optional)
Pass this to get live message delivery. Omit it and the provider works in poll/refresh mode with no crash.
interface RealTimeTransport {
subscribe(conversationId: string, onMessage: (msg: Message) => void): () => void;
// return value is the unsubscribe function — the provider calls it on cleanup
}PermissionPolicy (required)
Plain data — a role-to-permissions map. The package evaluates it internally.
type ChatPermission = 'send' | 'delete' | 'createTemplate' | 'bulkSend';
interface PermissionPolicy {
roles: Record<string, ChatPermission[]>;
}
// Example
const policy: PermissionPolicy = {
roles: {
admin: ['send', 'delete', 'createTemplate', 'bulkSend'],
user: ['send'],
viewer: [],
},
};Bring Your Own Store
InMemoryMessageStore — reference implementation
InMemoryMessageStore is a real, exported implementation (not a mock). Use it to get running immediately, and read its source as the canonical reference for how to implement a DB-backed store.
import { InMemoryMessageStore } from '@bernierllc/in-app-chat';
const store = new InMemoryMessageStore({
conversations: [...], // seed data, optional
threads: [...], // optional
templates: [...], // optional
});DB-backed store
For production, implement MessageStore against your database. @bernierllc/database-adapter-core is a suggested base.
import type { MessageStore, SendMessageInput } from '@bernierllc/in-app-chat';
export class PostgresMessageStore implements MessageStore {
constructor(private db: YourDbClient) {}
async getConversations(userId, filters) { /* SELECT … */ }
async getThread(conversationId) { /* SELECT … */ }
async sendMessage(input: SendMessageInput) { /* INSERT … */ }
async markRead(conversationId, userId) { /* UPDATE … */ }
// … listTemplates, createTemplate, updateTemplate, deleteTemplate
}HTTP-backed store
There is no shipped HTTP client or route factory. An HTTP boundary is simply a MessageStore whose methods call your own routes:
export class ApiMessageStore implements MessageStore {
constructor(private baseUrl: string) {}
async getConversations(userId) {
const res = await fetch(`${this.baseUrl}/api/chat/conversations?userId=${userId}`);
return res.json();
}
async sendMessage(input: SendMessageInput) {
const res = await fetch(`${this.baseUrl}/api/chat/messages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
return res.json();
}
// … implement the remaining methods against your routes
}useInAppChatContext() — context value
interface InAppChatContextValue {
store: MessageStore;
conversations: ConversationListItem[];
loading: boolean;
error: Error | null;
selectedConversationId: string | null;
selectConversation: (id: string) => void;
sendMessage: (conversationId: string, body: string) => Promise<Message | undefined>;
}External Platform Integrations
Slack Integration
Enable bidirectional messaging with Slack workspaces:
// in-app-chat.config.js
module.exports = {
integrations: {
slack: {
enabled: true,
userMapping: 'external', // Show Slack users as "external" in browser
bidirectional: true, // Messages flow both ways
slashCommands: true, // Enable slash commands in Slack
socketMode: true, // Use Socket Mode for real-time
channelWhitelist: ['#general', '#support']
}
}
};Required Environment Variables:
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secretInstall Integration Package:
npm install @bernierllc/chat-integration-slackDiscord Integration
npm install @bernierllc/chat-integration-discordMicrosoft Teams Integration
npm install @bernierllc/chat-integration-teamsSlash Commands via NeverHub
Dynamic Command Registration
Commands are automatically discovered and registered when their packages come online:
// in-app-chat.config.js
module.exports = {
slashCommands: {
'/task': {
enabled: true,
package: '@bernierllc/slash-commands-tasks',
description: 'Create and manage tasks',
requiresAuth: true,
platforms: ['in-app', 'slack', 'discord'],
aliases: ['/tasks', '/todo']
},
'/schedule': {
enabled: true,
package: '@bernierllc/slash-commands-calendar',
description: 'Schedule meetings and events'
}
}
};Available Slash Command Packages
npm install @bernierllc/slash-commands-tasks
npm install @bernierllc/slash-commands-calendar
npm install @bernierllc/slash-commands-authService Discovery Flow
- in-app-chat registers with NeverHub as command router
- Command packages register their capabilities with NeverHub
- in-app-chat automatically discovers and registers commands
- Commands work across all enabled platforms (in-app, Slack, Discord, Teams)
- When packages go offline, commands are automatically deregistered
Admin UI Integration
- Enable/disable individual slash commands
- Toggle integrations (Slack, Discord, Teams) on/off
- Manage permissions for role-based command access
- View integration status and health monitoring
Components
Core Components
InAppChatProvider— Main provider componentMessagesIcon— Navigation icon with unread countMessagesDropdown— Quick access dropdownMessagesPage— Full messaging interfaceConversationList— List of conversationsMessageThread— Message thread viewMessageInput— Message input componentMessageBubble— Individual message display
Role-Specific Components
RoleBasedFilters— Role-specific filteringBulkMessageModal— Bulk messaging interfaceTemplateSelector— Message template selectorFileUpload— File attachment componentMessageSearch— Search functionality
Customization
Styling
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@bernierllc/in-app-chat/dist/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
colors: {
messaging: {
primary: '#3b82f6',
secondary: '#64748b',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444',
},
},
},
},
};Custom Components
import { MessageBubble, MessageBubbleProps } from '@bernierllc/in-app-chat';
export function CustomMessageBubble(props: MessageBubbleProps) {
return (
<div className="custom-message-bubble">
<MessageBubble {...props} />
</div>
);
}Performance
- Code splitting — Only loads components when needed
- Virtual scrolling — Handles large message lists efficiently
- Optimistic updates — Immediate UI feedback
- Efficient caching — Reduces store calls
- Bundle optimization — Minimal impact on app size
Security
- Access validation — Role-based permissions evaluated via injected
PermissionPolicy - Input sanitization — XSS protection applied by the provider before persistence
- Rate limiting — Prevents abuse
- Audit logging — Track all activities
At-rest / in-transit encryption belongs in your
MessageStoreandRealTimeTransportimplementations, not in this package. The oldencryptMessage/decryptMessagehelpers (base64 only, never a security guarantee) were removed in 3.0.0.
Support
License
MIT License - see LICENSE for details.
Monorepo Workspaces & Dependency Management
This package is part of a monorepo using npm workspaces. All dependencies are hoisted to the root. Always run npm install from the root directory.
React 19 and Testing Library Compatibility
This package uses React 19.1.0. If you see peer dependency warnings with Testing Library, use:
npm install --legacy-peer-depsThis is a temporary workaround until official support is released.
