Package Exports
- @samthomson/nostr-messaging/core
- @samthomson/nostr-messaging/package.json
- @samthomson/nostr-messaging/ui
Readme
@samthomson/nostr-messaging
Reusable Nostr messaging system with NIP-04 and NIP-17 support, IndexedDB caching, and React context for state management.
Built to work with MKStacks.
Installation
npm install @samthomson/nostr-messagingQuick Start
1. Wrap your app with DMProvider
The package provides a headless React context that manages all messaging state. You need to provide it with dependencies from your app:
import { DMProvider, type DMProviderDeps } from '@samthomson/nostr-messaging/core';
import { useNostr } from '@nostrify/react';
import { useCurrentUser } from '@/hooks/useCurrentUser';
// ... other hooks
function App() {
const { nostr } = useNostr();
const { user } = useCurrentUser();
// ... gather other dependencies
const deps: DMProviderDeps = {
nostr, // NPool instance from @nostrify/react
user, // { pubkey: string, signer: Signer } | null
discoveryRelays: [...], // Array of relay URLs for NIP-17
relayMode: 'hybrid', // 'discovery' | 'hybrid' | 'strict_outbox'
updateConfig: (fn) => {}, // Function to update app config
isOnline: true, // Network status
wasOffline: false, // Was offline flag
toast: ({ title, description, variant }) => {}, // Toast function
getDisplayName: (pubkey, metadata) => string, // Format user names
fetchAuthorsBatch: useAuthorsBatch, // Required: your app's batch-fetch hook (see below)
publishEvent: async (event) => {}, // Publish events
};
return (
<DMProvider deps={deps}>
{/* Your app */}
</DMProvider>
);
}Required: fetchAuthorsBatch
The provider needs profile metadata to show display names in the conversation list and chat. You must pass your app's batch-fetch hook (e.g. useAuthorsBatch) as fetchAuthorsBatch. The provider will call it with the relevant pubkeys; your hook should return { data?: Map } where the map is pubkey -> NostrMetadata or pubkey -> { metadata?: NostrMetadata }. If you use Vite, add resolve.dedupe: ["react", "react-dom"] so the package and app share one React instance; otherwise hook updates may not re-render the provider and names can stay unresolved.
2. Use the messaging hooks
import { useDMContext, useConversationMessages } from '@samthomson/nostr-messaging/core';
function MessagingComponent() {
const {
messagingState,
conversations,
sendMessage,
searchMessages,
// ... many other methods
} = useDMContext();
// Get messages for a specific conversation
const {
messages,
loadMore,
hasMore,
isLoading,
} = useConversationMessages('conversationId');
// Send a message
await sendMessage({
conversationId: 'pubkey',
content: 'Hello!',
protocol: 'nip17', // or 'nip04'
});
return (
<div>
{/* Build your UI */}
</div>
);
}Dependencies (peer vs dev)
The package declares @nostrify/nostrify and @nostrify/react in peerDependencies (so the consuming app installs and provides them; the package does not bundle them) and again in devDependencies (so we can build and test the package in isolation). That is intentional: peers are required at runtime from the app; devDeps are only for local npm install and npm run build in the package repo.
What's Included
Core (@samthomson/nostr-messaging/core)
Everything you need for messaging:
- Pure Functions: Message encryption, conversation management, protocol handling
- Storage: IndexedDB caching for messages and media
- React Context:
DMProviderfor state management - React Hooks:
useDMContext,useConversationMessages - Types: Full TypeScript support
- Constants:
DM_PHASES,MESSAGE_PROTOCOL,PROTOCOL_MODE, etc.
Features
- ✅ NIP-04 (legacy DMs) and NIP-17 (private messages) support
- ✅ Encrypted file attachments
- ✅ IndexedDB caching for offline support
- ✅ Message search (full-text and conversation search)
- ✅ Unread message tracking
- ✅ Real-time message sync
- ✅ Gap filling for message history
- ✅ Protocol auto-detection and migration
UI export: what the app must provide
If you use @samthomson/nostr-messaging/ui, the package does not bundle its own primitives. Your app must provide them so the package's imports resolve:
Bundler config
Your app must resolve@/to your app source (e.g.@/→src/). The package's UI code imports from@/components/ui/...and@/lib/utils; those are resolved at build time by your app's bundler.Components and util
The package expects these to exist at the paths below. An MKStack/shadcn app already has them; otherwise add them or point@/so they exist.
| Path | Exports used |
|---|---|
@/lib/utils |
cn (class-name merge, e.g. clsx + tailwind-merge) |
@/components/ui/avatar |
Avatar, AvatarFallback, AvatarImage |
@/components/ui/badge |
Badge |
@/components/ui/button |
Button |
@/components/ui/card |
Card, CardContent |
@/components/ui/dialog |
Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger |
@/components/ui/popover |
Popover, PopoverContent, PopoverTrigger |
@/components/ui/scroll-area |
ScrollArea |
@/components/ui/select |
Select, SelectContent, SelectItem, SelectTrigger, SelectValue |
@/components/ui/separator |
Separator |
@/components/ui/skeleton |
Skeleton |
@/components/ui/tabs |
Tabs, TabsContent, TabsList, TabsTrigger |
@/components/ui/textarea |
Textarea |
@/components/ui/tooltip |
Tooltip, TooltipContent, TooltipProvider, TooltipTrigger |
Install the peer dependencies listed in package.json (React, Radix UI packages, clsx, tailwind-merge, lucide-react, etc.) so your app and the package agree on versions.
DMProviderDeps Interface
interface DMProviderDeps {
nostr: NPool; // Nostr relay pool
user: { pubkey: string; signer: Signer } | null;
discoveryRelays: string[]; // Relays for NIP-17 discovery
relayMode: 'discovery' | 'hybrid' | 'strict_outbox';
updateConfig: (updater: (config: any) => any) => void;
isOnline: boolean;
wasOffline: boolean;
toast: (options: {
title?: string;
description?: string;
variant?: 'default' | 'destructive';
}) => void;
getDisplayName: (pubkey: string, metadata?: NostrMetadata) => string;
/** Required. Pass your app's useAuthorsBatch (or equivalent). See "Required: fetchAuthorsBatch" above. */
fetchAuthorsBatch: (pubkeys: string[]) => { data?: Map<string, NostrMetadata | { metadata?: NostrMetadata }> };
publishEvent: (event: any) => Promise<void>;
}useDMContext API
The main hook provides:
const {
// State
messagingState, // Current loading phase and status
conversations, // Map of all conversations
// Actions
sendMessage, // Send a message
deleteMessage, // Delete a message
markAsRead, // Mark conversation as read
// Search
searchMessages, // Full-text message search
searchConversations, // Search conversations
// File handling
uploadAndSendFile, // Upload and send file
// Sync
forceSync, // Force sync messages
// ... many more
} = useDMContext();Development
Local Development with Another Project
If you're working on both the package and a consuming app:
# In the consuming app's parent directory
git clone https://github.com/yourusername/nostr-messaging.git
# In the consuming app
npm install
# The postinstall script will automatically create a symlink
# if it detects ../nostr-messaging existsBuild
npm install
npm run buildWatch Mode
npm run dev # Rebuilds on changesPublishing
Apps depend on this package being on the registry (e.g. ^0.3.0). After version bumps, publish so npm i in consuming apps succeeds.
# Log in if needed (token expired / new machine)
npm login
# From this repo: build and publish (scoped package needs public access)
npm run build
npm publish --access public
# If you use 2FA: npm publish --access public --otp=YOUR_CODE
# Verify
npm view @samthomson/nostr-messagingLocal symlink (Silent, Pathos, etc.): Those apps have a postinstall that, when ../nostr-messaging exists, symlinks it into node_modules/@samthomson/nostr-messaging and runs npm run build in the linked package so dist/ and types are up to date. That only runs after npm i succeeds, so the version in the app’s package.json (e.g. ^0.3.0) must exist on the registry first. Publish this package, then in the app run npm i; the symlink will replace the installed copy and use your local build.
Migration from 0.1.x to 0.2.x
Version 0.2.0 consolidated all exports into /core:
// Before (0.1.x)
import { DMProvider } from '@samthomson/nostr-messaging/react';
import { writeMessagesToDB } from '@samthomson/nostr-messaging/storage';
// After (0.2.x)
import {
DMProvider,
writeMessagesToDB
} from '@samthomson/nostr-messaging/core';License
MIT