nostr-websocket-utils 0.2.1 โ†’ 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,187 +1,199 @@
1
- Nostr Websocket Utils
1
+ # Nostr Websocket Utils
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@humanjavaenterprises/nostr-websocket-utils.svg)](https://www.npmjs.com/package/@humanjavaenterprises/nostr-websocket-utils)
4
- [![License](https://img.shields.io/npm/l/@humanjavaenterprises/nostr-websocket-utils.svg)](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/blob/main/LICENSE)
3
+ [![npm version](https://img.shields.io/npm/v/nostr-websocket-utils.svg)](https://www.npmjs.com/package/nostr-websocket-utils)
4
+ [![License](https://img.shields.io/npm/l/nostr-websocket-utils.svg)](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/blob/main/LICENSE)
5
5
  [![Build Status](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/workflows/CI/badge.svg)](https://github.com/HumanjavaEnterprises/nostr-websocket-utils/actions)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org)
7
7
  [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)
8
8
 
9
- A TypeScript library providing WebSocket utilities for Nostr applications, with robust connection handling, automatic reconnection, and channel-based messaging.
9
+ A TypeScript library providing WebSocket utilities for Nostr applications, focusing on robust connection handling, automatic reconnection, and channel-based messaging. This library has been streamlined to remove any DOM-related code, enhancing performance and maintainability.
10
10
 
11
11
  ## Features
12
12
 
13
13
  - ๐Ÿ”„ Automatic reconnection with configurable attempts
14
- - ๐Ÿ’“ Heartbeat monitoring
14
+ - ๐Ÿ’“ Heartbeat monitoring with configurable intervals
15
15
  - ๐Ÿ“จ Message queueing during disconnections
16
- - ๐Ÿ“ข Channel-based broadcasting
16
+ - ๐Ÿ“ข Channel-based broadcasting with filtered subscriptions
17
17
  - ๐Ÿ”’ Type-safe message handling with required handlers
18
- - ๐Ÿ“ Built-in logging
19
- - ๐Ÿ›ก๏ธ Comprehensive error handling
20
- - ๐Ÿงช Full test coverage
18
+ - ๐Ÿ“ Built-in logging with Winston integration
19
+ - ๐Ÿ›ก๏ธ Comprehensive error handling and validation
20
+ - ๐Ÿงช 100% test coverage with Jest
21
+ - ๐Ÿ“ฆ Zero DOM dependencies
21
22
 
22
23
  ## Installation
23
24
 
24
25
  ```bash
25
- npm install @humanjavaenterprises/nostr-websocket-utils
26
+ npm install nostr-websocket-utils
26
27
  ```
27
28
 
28
- ## Breaking Changes in v0.2.0
29
+ ## Breaking Changes in v0.2.2
29
30
 
30
- - Introduced required handlers pattern for better type safety
31
- - Removed individual event handler properties (onMessage, onError, onClose)
32
- - Message handler is now required in server options
33
- - Client updated to match server interface
31
+ - ๐Ÿ”ฅ Removed all DOM-related code for better server-side compatibility
32
+ - ๐Ÿ†” Added UUID support for message tracking and correlation
33
+ - ๐Ÿ”Œ Introduced `WebSocketImpl` option for custom WebSocket implementations
34
+ - ๐Ÿ›ก๏ธ Enhanced TypeScript type safety and validation
35
+ - ๐Ÿ”„ Improved reconnection and error handling logic
36
+ - ๐Ÿ“ Added comprehensive logging support
34
37
 
35
38
  ## Usage
36
39
 
37
40
  ### Server Example
38
41
 
39
42
  ```typescript
40
- import express from 'express';
41
- import { createServer } from 'http';
42
- import { NostrWSServer } from '@humanjavaenterprises/nostr-websocket-utils';
43
- import winston from 'winston';
44
-
45
- const app = express();
46
- const server = createServer(app);
47
-
48
- // Create a logger
49
- const logger = winston.createLogger({
50
- level: 'info',
51
- format: winston.format.json(),
52
- transports: [new winston.transports.Console()]
53
- });
43
+ import { NostrWSServer } from 'nostr-websocket-utils';
44
+ import { WebSocketServer } from 'ws';
45
+ import { getLogger } from './utils/logger';
54
46
 
55
- // Initialize WebSocket server with required handlers
56
- const wss = new NostrWSServer(server, {
57
- heartbeatInterval: 30000,
58
- logger,
47
+ // Create WebSocket server
48
+ const wss = new WebSocketServer({ port: 8080 });
49
+
50
+ // Initialize NostrWSServer with handlers
51
+ const server = new NostrWSServer(wss, {
52
+ logger: getLogger('nostr-server'),
53
+ heartbeatInterval: 30000, // Optional: 30 seconds
59
54
  handlers: {
60
- // Required message handler
55
+ // Required: Handle incoming messages
61
56
  message: async (ws, message) => {
62
- logger.info('Received message:', message);
63
-
64
- // Example: Handle different message types
65
57
  switch (message.type) {
66
58
  case 'subscribe':
67
59
  // Handle subscription
60
+ ws.subscriptions?.add(message.data.channel);
68
61
  break;
69
62
  case 'event':
70
- // Broadcast to specific channel
71
- wss.broadcastToChannel('my-channel', {
72
- type: 'event',
73
- data: { content: 'Hello channel!' }
74
- });
63
+ // Broadcast to relevant subscribers
64
+ server.broadcastToChannel(message.data.channel, message);
75
65
  break;
76
66
  }
77
67
  },
78
- // Optional error handler
68
+ // Optional: Handle errors
79
69
  error: (ws, error) => {
80
70
  logger.error('WebSocket error:', error);
81
71
  },
82
- // Optional close handler
72
+ // Optional: Handle client disconnection
83
73
  close: (ws) => {
84
- logger.info('Client disconnected');
74
+ logger.info(`Client ${ws.clientId} disconnected`);
85
75
  }
86
76
  }
87
77
  });
88
-
89
- server.listen(3000);
90
78
  ```
91
79
 
92
80
  ### Client Example
93
81
 
94
82
  ```typescript
95
- import { NostrWSClient } from '@humanjavaenterprises/nostr-websocket-utils';
96
- import winston from 'winston';
97
-
98
- // Create a logger
99
- const logger = winston.createLogger({
100
- level: 'info',
101
- format: winston.format.json(),
102
- transports: [new winston.transports.Console()]
103
- });
83
+ import { NostrWSClient } from 'nostr-websocket-utils';
84
+ import { getLogger } from './utils/logger';
104
85
 
105
- const client = new NostrWSClient('wss://your-server.com', {
86
+ const client = new NostrWSClient('ws://localhost:8080', {
87
+ logger: getLogger('nostr-client'),
106
88
  heartbeatInterval: 30000,
107
- reconnectInterval: 5000,
108
- maxReconnectAttempts: 5,
109
- logger,
110
89
  handlers: {
90
+ // Required: Handle incoming messages
111
91
  message: async (ws, message) => {
112
- logger.info('Received message:', message);
113
- // Handle message
114
- },
115
- error: (ws, error) => {
116
- logger.error('Connection error:', error);
117
- },
118
- close: (ws) => {
119
- logger.info('Connection closed');
92
+ console.log('Received:', message);
120
93
  }
121
94
  }
122
95
  });
123
96
 
124
- // Listen to events
97
+ // Listen for connection events
125
98
  client.on('connect', () => {
126
- logger.info('Connected!');
99
+ console.log('Connected to server');
100
+
101
+ // Subscribe to a channel
127
102
  client.subscribe('my-channel');
103
+
104
+ // Send an event
105
+ client.send({
106
+ type: 'event',
107
+ data: {
108
+ channel: 'my-channel',
109
+ content: 'Hello, Nostr!'
110
+ }
111
+ });
128
112
  });
129
113
 
114
+ // Connect to server
130
115
  client.connect();
131
116
  ```
132
117
 
133
- ## Interface Reference
118
+ ## API Reference
134
119
 
135
- ### NostrWSOptions
120
+ ### NostrWSServer
121
+
122
+ The server-side WebSocket handler with support for channels and broadcasting.
136
123
 
137
124
  ```typescript
138
- interface NostrWSOptions {
139
- // Interval for sending heartbeat pings (ms)
140
- heartbeatInterval?: number;
141
- // Interval between reconnection attempts (ms)
142
- reconnectInterval?: number;
143
- // Maximum number of reconnection attempts
144
- maxReconnectAttempts?: number;
145
- // Required logger instance
146
- logger: Logger;
147
- // Required handlers object
148
- handlers: {
149
- // Required message handler
150
- message: (ws: ExtendedWebSocket, message: NostrWSMessage) => Promise<void> | void;
151
- // Optional error handler
152
- error?: (ws: WebSocket, error: Error) => void;
153
- // Optional close handler
154
- close?: (ws: WebSocket) => void;
155
- };
125
+ class NostrWSServer {
126
+ constructor(wss: WebSocketServer, options: NostrWSOptions);
127
+
128
+ // Broadcast to all connected clients
129
+ broadcast(message: NostrWSMessage): void;
130
+
131
+ // Broadcast to specific channel subscribers
132
+ broadcastToChannel(channel: string, message: NostrWSMessage): void;
133
+
134
+ // Close the server and all connections
135
+ close(): void;
156
136
  }
157
137
  ```
158
138
 
159
- ## Why This Library is Perfect for Nostr Development
139
+ ### NostrWSClient
140
+
141
+ The client-side WebSocket handler with automatic reconnection and message queueing.
142
+
143
+ ```typescript
144
+ class NostrWSClient {
145
+ constructor(url: string, options: NostrWSOptions);
146
+
147
+ // Connect to the server
148
+ connect(): void;
149
+
150
+ // Subscribe to a channel
151
+ subscribe(channel: string, filter?: unknown): void;
152
+
153
+ // Unsubscribe from a channel
154
+ unsubscribe(channel: string): void;
155
+
156
+ // Send a message to the server
157
+ send(message: NostrWSMessage): Promise<void>;
158
+
159
+ // Close the connection
160
+ close(): void;
161
+ }
162
+ ```
163
+
164
+ ### NostrWSMessage
165
+
166
+ The standard message format for communication.
167
+
168
+ ```typescript
169
+ interface NostrWSMessage {
170
+ id?: string; // Auto-generated UUID if not provided
171
+ type: string; // Message type (e.g., 'subscribe', 'event')
172
+ data: {
173
+ channel?: string; // Target channel for subscription/broadcast
174
+ [key: string]: any; // Additional message data
175
+ };
176
+ }
177
+ ```
160
178
 
161
- This library is specifically designed to accelerate Nostr application development by providing a robust foundation for WebSocket communication. Here's what makes it particularly valuable:
179
+ ## Why Choose This Library
162
180
 
163
- ### 1. Nostr-Specific Message Types
164
- - Built-in support for core Nostr protocol message types (`subscribe`, `unsubscribe`, `event`, `request/response`)
165
- - Perfectly aligned with Nostr's pub/sub model
181
+ ### 1. Nostr-Optimized
182
+ - Built specifically for Nostr protocol requirements
183
+ - Efficient pub/sub model with filtered subscriptions
166
184
  - Type-safe message handling for all Nostr events
167
185
 
168
- ### 2. Advanced WebSocket Management
169
- - Zero-config connection maintenance with automatic reconnection
170
- - Built-in heartbeat mechanism prevents stale connections
171
- - Smart message queuing ensures no events are lost during disconnections
172
- - Comprehensive connection state tracking
173
-
174
- ### 3. Efficient Event Distribution
175
- - Channel-based broadcasting for targeted event distribution
176
- - Support for filtered subscriptions (crucial for Nostr event filtering)
177
- - Memory-efficient subscription tracking
178
- - Optimized message routing to relevant subscribers
179
-
180
- ### 4. Developer Experience
181
- - Full TypeScript support with comprehensive type definitions
182
- - Event-driven architecture matching Nostr's event-centric nature
183
- - Clear, consistent error handling and validation
184
- - Required handlers pattern ensures type safety
186
+ ### 2. Production-Ready
187
+ - Comprehensive error handling and recovery
188
+ - Memory-efficient subscription management
189
+ - Built-in logging and monitoring
190
+ - Extensive test coverage
191
+
192
+ ### 3. Developer-Friendly
193
+ - Clear TypeScript definitions
194
+ - Flexible configuration options
195
+ - Detailed documentation
196
+ - Active maintenance
185
197
 
186
198
  ## Contributing
187
199
 
package/dist/client.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import { EventEmitter } from 'events';
2
3
  import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
3
4
  export declare class NostrWSClient extends EventEmitter {
@@ -8,6 +9,7 @@ export declare class NostrWSClient extends EventEmitter {
8
9
  private heartbeatInterval;
9
10
  private reconnectAttempts;
10
11
  private messageQueue;
12
+ private clientId;
11
13
  constructor(url: string, options?: Partial<NostrWSOptions>);
12
14
  connect(): void;
13
15
  private setupEventHandlers;
package/dist/client.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { v4 as uuidv4 } from 'uuid';
1
2
  import WebSocket from 'ws';
2
3
  import { EventEmitter } from 'events';
3
4
  export class NostrWSClient extends EventEmitter {
@@ -12,9 +13,11 @@ export class NostrWSClient extends EventEmitter {
12
13
  if (!options.logger) {
13
14
  throw new Error('Logger is required');
14
15
  }
16
+ this.clientId = uuidv4();
15
17
  this.options = {
16
18
  heartbeatInterval: options.heartbeatInterval || 30000,
17
19
  logger: options.logger,
20
+ WebSocketImpl: options.WebSocketImpl || WebSocket,
18
21
  handlers: {
19
22
  message: options.handlers?.message || (async () => { }),
20
23
  error: options.handlers?.error || (() => { }),
@@ -28,7 +31,9 @@ export class NostrWSClient extends EventEmitter {
28
31
  return;
29
32
  }
30
33
  try {
31
- this.ws = new WebSocket(this.url);
34
+ this.options.logger.debug('Creating new WebSocket connection');
35
+ this.ws = new this.options.WebSocketImpl(this.url);
36
+ this.options.logger.debug('WebSocket created successfully');
32
37
  this.setupEventHandlers();
33
38
  }
34
39
  catch (error) {
@@ -122,6 +127,7 @@ export class NostrWSClient extends EventEmitter {
122
127
  }
123
128
  }
124
129
  async send(message) {
130
+ message.id = message.id || uuidv4();
125
131
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
126
132
  this.messageQueue.push(message);
127
133
  return;
package/dist/server.d.ts CHANGED
@@ -1,12 +1,15 @@
1
+ /// <reference types="node" />
1
2
  import { EventEmitter } from 'events';
2
- import { Server as HttpServer } from 'http';
3
- import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
3
+ import { WebSocketServer } from 'ws';
4
+ import type { NostrWSOptions, NostrWSMessage, ExtendedWebSocket } from './types/index.js';
4
5
  export declare class NostrWSServer extends EventEmitter {
5
6
  private wss;
6
7
  private options;
7
8
  private heartbeatInterval;
8
- constructor(server: HttpServer, options?: Partial<NostrWSOptions>);
9
+ clients: Map<string, ExtendedWebSocket>;
10
+ constructor(wss: WebSocketServer, options?: Partial<NostrWSOptions>);
9
11
  private setupServer;
12
+ private handleConnection;
10
13
  private startHeartbeat;
11
14
  broadcast(message: NostrWSMessage): void;
12
15
  broadcastToChannel(channel: string, message: NostrWSMessage): void;
package/dist/server.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { EventEmitter } from 'events';
2
- import { WebSocketServer, WebSocket } from 'ws';
2
+ import { WebSocket } from 'ws';
3
+ import { v4 as uuidv4 } from 'uuid';
3
4
  export class NostrWSServer extends EventEmitter {
4
- constructor(server, options = {}) {
5
+ constructor(wss, options = {}) {
5
6
  super();
6
7
  this.heartbeatInterval = null;
8
+ this.clients = new Map();
7
9
  if (!options.logger) {
8
10
  throw new Error('Logger is required');
9
11
  }
@@ -13,47 +15,57 @@ export class NostrWSServer extends EventEmitter {
13
15
  this.options = {
14
16
  heartbeatInterval: options.heartbeatInterval || 30000,
15
17
  logger: options.logger,
18
+ WebSocketImpl: options.WebSocketImpl || WebSocket,
16
19
  handlers: {
17
20
  message: options.handlers.message,
18
21
  error: options.handlers.error || (() => { }),
19
22
  close: options.handlers.close || (() => { })
20
23
  }
21
24
  };
22
- this.wss = new WebSocketServer({ server });
25
+ this.wss = wss;
23
26
  this.setupServer();
24
27
  }
25
28
  setupServer() {
26
29
  this.wss.on('connection', (ws) => {
27
- const extWs = ws;
28
- extWs.subscriptions = new Set();
29
- extWs.isAlive = true;
30
- ws.on('message', async (data) => {
31
- try {
32
- const message = JSON.parse(data.toString());
33
- await this.options.handlers.message(extWs, message);
34
- }
35
- catch (error) {
36
- if (this.options.handlers.error) {
37
- this.options.handlers.error(ws, error);
38
- }
39
- }
40
- });
41
- ws.on('close', () => {
42
- extWs.isAlive = false;
43
- if (this.options.handlers.close) {
44
- this.options.handlers.close(ws);
45
- }
46
- });
47
- ws.on('error', (error) => {
48
- if (this.options.handlers.error) {
49
- this.options.handlers.error(ws, error);
50
- }
51
- });
30
+ this.handleConnection(ws);
52
31
  });
53
32
  if (this.options.heartbeatInterval && this.options.heartbeatInterval > 0) {
54
33
  this.startHeartbeat();
55
34
  }
56
35
  }
36
+ handleConnection(ws) {
37
+ ws.isAlive = true;
38
+ ws.subscriptions = new Set();
39
+ ws.clientId = ws.clientId || uuidv4();
40
+ this.clients.set(ws.clientId, ws);
41
+ ws.on('message', async (data) => {
42
+ try {
43
+ const message = JSON.parse(data.toString());
44
+ await this.options.handlers.message(ws, message);
45
+ }
46
+ catch (error) {
47
+ if (this.options.handlers.error) {
48
+ this.options.handlers.error(ws, error);
49
+ }
50
+ }
51
+ });
52
+ ws.on('close', () => {
53
+ if (ws.clientId) {
54
+ this.clients.delete(ws.clientId);
55
+ }
56
+ if (this.options.handlers.close) {
57
+ this.options.handlers.close(ws);
58
+ }
59
+ });
60
+ ws.on('error', (error) => {
61
+ if (ws.clientId) {
62
+ this.clients.delete(ws.clientId);
63
+ }
64
+ if (this.options.handlers.error) {
65
+ this.options.handlers.error(ws, error);
66
+ }
67
+ });
68
+ }
57
69
  startHeartbeat() {
58
70
  this.heartbeatInterval = setInterval(() => {
59
71
  this.wss.clients.forEach((ws) => {
@@ -5,6 +5,7 @@ export interface NostrWSOptions {
5
5
  reconnectInterval?: number;
6
6
  maxReconnectAttempts?: number;
7
7
  logger: Logger;
8
+ WebSocketImpl: typeof WebSocket;
8
9
  handlers: {
9
10
  message: (ws: ExtendedWebSocket, message: NostrWSMessage) => Promise<void> | void;
10
11
  error?: (ws: WebSocket, error: Error) => void;
@@ -35,6 +36,7 @@ export interface NostrWSServerEvents {
35
36
  export interface ExtendedWebSocket extends WebSocket {
36
37
  isAlive?: boolean;
37
38
  subscriptions?: Set<string>;
39
+ clientId?: string;
38
40
  messageQueue?: NostrWSMessage[];
39
41
  lastPing?: number;
40
42
  reconnectAttempts?: number;
@@ -51,5 +53,6 @@ export interface NostrWSConnectionState {
51
53
  export interface Logger {
52
54
  debug: (message: string, ...args: unknown[]) => void;
53
55
  info: (message: string, ...args: unknown[]) => void;
56
+ warn: (message: string, ...args: unknown[]) => void;
54
57
  error: (message: string, ...args: unknown[]) => void;
55
58
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nostr-websocket-utils",
3
- "version": "0.2.1",
4
- "description": "WebSocket utilities for Nostr applications",
3
+ "version": "0.2.3",
4
+ "description": "Robust WebSocket utilities for Nostr applications with automatic reconnection, channel-based messaging, and type-safe handlers. Features heartbeat monitoring, message queueing, and comprehensive error handling.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
@@ -17,23 +17,41 @@
17
17
  "keywords": [
18
18
  "nostr",
19
19
  "websocket",
20
- "utils",
20
+ "typescript",
21
+ "ws",
22
+ "realtime",
23
+ "pubsub",
24
+ "channel-based",
25
+ "reconnection",
26
+ "heartbeat",
27
+ "message-queue",
28
+ "type-safe",
21
29
  "maiqr"
22
30
  ],
23
- "author": "Human Java Enterprises",
31
+ "author": "vveerrgg",
24
32
  "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/HumanjavaEnterprises/nostr-websocket-utils.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/HumanjavaEnterprises/nostr-websocket-utils/issues"
39
+ },
40
+ "homepage": "https://github.com/HumanjavaEnterprises/nostr-websocket-utils#readme",
25
41
  "dependencies": {
26
- "ws": "^8.16.0",
27
- "winston": "^3.11.0",
28
42
  "@noble/curves": "^1.3.0",
29
43
  "@noble/hashes": "^1.3.3",
30
- "nostr-tools": "^2.1.4"
44
+ "@types/uuid": "^10.0.0",
45
+ "nostr-tools": "^2.1.4",
46
+ "uuid": "^11.0.3",
47
+ "winston": "^3.11.0",
48
+ "ws": "^8.16.0"
31
49
  },
32
50
  "peerDependencies": {
33
51
  "nostr-tools": "^2.1.4"
34
52
  },
35
53
  "devDependencies": {
36
- "@types/jest": "^29.5.11",
54
+ "@types/jest": "^29.5.14",
37
55
  "@types/node": "^20.11.0",
38
56
  "@types/ws": "^8.5.10",
39
57
  "@typescript-eslint/eslint-plugin": "^6.18.1",