This document provides comprehensive technical specifications for our Nostr relay implementation, covering both core infrastructure and protocol-specific features.
The connection handling architecture is split across several components:
ConnectionManagerService: Central service for managing WebSocket connectionsNostrRelayService: Handles Nostr-specific message processing and relay operationsEnhancedWebSocket: Extended WebSocket interface with Nostr-specific properties
The ConnectionManagerService maintains a Map of all active WebSocket connections. Each connection is stored with:
- A unique identifier
- Authentication status
- Connection timestamp
- Associated public key (if authenticated)
Map<string, EnhancedWebSocket>Each WebSocket connection is enhanced with additional properties:
id: Unique identifier for the connectionauthenticated: Boolean flag indicating authentication statuspubkey: Public key of the authenticated user (if any)challenge: Authentication challenge stringconnectedAt: Timestamp of connection establishment
The relay implements request throttling using @nostr-relay/throttler:
- Default rate limit: 100 requests per minute
- Configurable through
throttlerConfig.EVENT.limitandthrottlerConfig.EVENT.ttl - Applied globally across all connections
-
Connection Establishment
- New WebSocket connection received
- Assigned unique ID
- Added to ConnectionManager map
- Initial properties set (unauthenticated)
-
Authentication (Optional)
- Challenge issued to client
- Client signs challenge
- Verification of signature
- Connection marked as authenticated
- Public key associated with connection
-
Connection Termination
- Connection removed from ConnectionManager map
- Resources cleaned up
- Stats updated
-
Connection Management
- Always use ConnectionManagerService for connection operations
- Maintain accurate connection state
- Clean up resources on connection close
-
Error Handling
- Catch and log WebSocket errors
- Send appropriate NOTICE messages to clients
- Maintain connection state consistency
-
Type Safety
- Use EnhancedWebSocket interface consistently
- Handle type mismatches with external libraries carefully
- Document any necessary type assertions
NIP-29 extends Nostr's capabilities to support group chats through specific event kinds and tags.
41: Group Creation42: Group Metadata43: Group Members/Admins44: Group Message
{
"kind": 41,
"content": "",
"tags": [
["g", "group-id"],
["name", "Group Name"],
["about", "Group Description"],
["picture", "https://example.com/group.jpg"]
]
}{
"kind": 42,
"content": "",
"tags": [
["g", "group-id"],
["name", "Updated Group Name"],
["about", "Updated Description"]
]
}{
"kind": 43,
"content": "",
"tags": [
["g", "group-id"],
["p", "member-pubkey-1", "admin"],
["p", "member-pubkey-2", "member"]
]
}{
"kind": 44,
"content": "Hello group!",
"tags": [
["g", "group-id"],
["e", "reply-to-event-id"],
["p", "mentioned-pubkey"]
]
}const event = {
kind: 41,
created_at: Math.floor(Date.now() / 1000),
tags: [
['g', 'test-group-id'],
['name', 'Test Group'],
['about', 'Test group for NIP-29']
],
content: '',
pubkey: publicKey,
};
const signedEvent = nostrTools.finalizeEvent(event, privateKey);
ws.send(JSON.stringify(['EVENT', signedEvent]));const event = {
kind: 44,
created_at: Math.floor(Date.now() / 1000),
tags: [
['g', 'test-group-id'],
['e', 'reply-to-event-id'],
],
content: 'Hello group!',
pubkey: publicKey,
};
const signedEvent = nostrTools.finalizeEvent(event, privateKey);
ws.send(JSON.stringify(['EVENT', signedEvent]));[Additional NIP implementations will be documented here...]