nostr-websocket-utils 0.2.2 โ 0.2.4
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 +86 -103
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/nostr-server.d.ts +8 -0
- package/dist/nostr-server.js +63 -0
- package/dist/types/nostr.d.ts +52 -0
- package/dist/types/nostr.js +10 -0
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -11,13 +11,15 @@ A TypeScript library providing WebSocket utilities for Nostr applications, focus
|
|
|
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
|
-
- ๐งช
|
|
18
|
+
- ๐ Built-in logging with Winston integration
|
|
19
|
+
- ๐ก๏ธ Comprehensive error handling and validation
|
|
20
|
+
- ๐งช 100% test coverage with Jest
|
|
21
|
+
- ๐ฆ Zero DOM dependencies
|
|
22
|
+
- ๐ Full TypeScript type safety
|
|
21
23
|
|
|
22
24
|
## Installation
|
|
23
25
|
|
|
@@ -25,137 +27,118 @@ A TypeScript library providing WebSocket utilities for Nostr applications, focus
|
|
|
25
27
|
npm install nostr-websocket-utils
|
|
26
28
|
```
|
|
27
29
|
|
|
28
|
-
## Breaking Changes in v0.2.
|
|
30
|
+
## Breaking Changes in v0.2.4
|
|
29
31
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
32
|
+
- ๐ Added dedicated Nostr WebSocket server implementation
|
|
33
|
+
- ๐ Enhanced TypeScript type definitions for Nostr messages
|
|
34
|
+
- ๐ Improved message handling with strict type checking
|
|
35
|
+
- ๐งช Added comprehensive test suite for Nostr server
|
|
36
|
+
- ๐ก๏ธ Strengthened type safety with `unknown` types
|
|
37
|
+
- ๐ Added support for Nostr EVENT and REQ messages
|
|
35
38
|
|
|
36
39
|
## Usage
|
|
37
40
|
|
|
38
|
-
### Server Example
|
|
41
|
+
### Nostr Server Example
|
|
39
42
|
|
|
40
43
|
```typescript
|
|
41
|
-
import
|
|
42
|
-
import {
|
|
43
|
-
import { NostrWSServer } from 'nostr-websocket-utils';
|
|
44
|
-
import winston from 'winston';
|
|
45
|
-
|
|
46
|
-
const app = express();
|
|
47
|
-
const server = createServer(app);
|
|
48
|
-
|
|
49
|
-
// Create a logger
|
|
50
|
-
const logger = winston.createLogger({
|
|
51
|
-
level: 'info',
|
|
52
|
-
format: winston.format.json(),
|
|
53
|
-
transports: [new winston.transports.Console()]
|
|
54
|
-
});
|
|
44
|
+
import { NostrWSServer, createWSServer } from 'nostr-websocket-utils';
|
|
45
|
+
import { NostrWSMessageType, NostrWSEvent } from 'nostr-websocket-utils/types/nostr';
|
|
55
46
|
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
|
|
47
|
+
// Create Nostr WebSocket server
|
|
48
|
+
const server = createWSServer({
|
|
49
|
+
port: 8080,
|
|
50
|
+
heartbeatInterval: 30000, // Optional: 30 seconds
|
|
59
51
|
handlers: {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
52
|
+
// Required: Handle incoming messages
|
|
53
|
+
message: async (socket, message) => {
|
|
54
|
+
switch (message[0]) {
|
|
55
|
+
case NostrWSMessageType.EVENT:
|
|
56
|
+
const event = message[1] as NostrWSEvent;
|
|
57
|
+
// Handle Nostr event
|
|
58
|
+
break;
|
|
59
|
+
case NostrWSMessageType.REQ:
|
|
60
|
+
const [_type, subscriptionId, filter] = message;
|
|
61
|
+
// Handle subscription request
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
63
64
|
},
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
// Optional: Handle errors
|
|
66
|
+
error: (socket, error) => {
|
|
67
|
+
console.error('WebSocket error:', error);
|
|
66
68
|
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
// Optional: Handle client disconnection
|
|
70
|
+
close: (socket) => {
|
|
71
|
+
console.info('Client disconnected');
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
// Start listening
|
|
77
|
+
server.listen();
|
|
74
78
|
```
|
|
75
79
|
|
|
76
|
-
###
|
|
80
|
+
### Types
|
|
77
81
|
|
|
78
82
|
```typescript
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
logger,
|
|
90
|
-
WebSocketImpl: CustomWebSocketImplementation // Optional custom WebSocket implementation
|
|
91
|
-
});
|
|
83
|
+
// Nostr Event Type
|
|
84
|
+
interface NostrWSEvent {
|
|
85
|
+
id: string;
|
|
86
|
+
pubkey: string;
|
|
87
|
+
created_at: number;
|
|
88
|
+
kind: number;
|
|
89
|
+
tags: string[][];
|
|
90
|
+
content: string;
|
|
91
|
+
sig: string;
|
|
92
|
+
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// Nostr Filter Type
|
|
95
|
+
interface NostrWSFilter {
|
|
96
|
+
ids?: string[];
|
|
97
|
+
authors?: string[];
|
|
98
|
+
kinds?: number[];
|
|
99
|
+
'#e'?: string[];
|
|
100
|
+
'#p'?: string[];
|
|
101
|
+
since?: number;
|
|
102
|
+
until?: number;
|
|
103
|
+
limit?: number;
|
|
104
|
+
}
|
|
96
105
|
|
|
97
|
-
|
|
106
|
+
// Message Types
|
|
107
|
+
enum NostrWSMessageType {
|
|
108
|
+
EVENT = 'EVENT',
|
|
109
|
+
REQ = 'REQ',
|
|
110
|
+
CLOSE = 'CLOSE',
|
|
111
|
+
NOTICE = 'NOTICE',
|
|
112
|
+
AUTH = 'AUTH',
|
|
113
|
+
EOSE = 'EOSE'
|
|
114
|
+
}
|
|
98
115
|
```
|
|
99
116
|
|
|
100
|
-
##
|
|
117
|
+
## Advanced Configuration
|
|
101
118
|
|
|
102
|
-
###
|
|
119
|
+
### Server Options
|
|
103
120
|
|
|
104
121
|
```typescript
|
|
105
|
-
interface
|
|
106
|
-
|
|
122
|
+
interface NostrWSServerOptions {
|
|
123
|
+
port: number;
|
|
107
124
|
heartbeatInterval?: number;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
logger: Logger;
|
|
114
|
-
// Required handlers object
|
|
125
|
+
maxPayloadSize?: number;
|
|
126
|
+
cors?: {
|
|
127
|
+
origin?: string | string[];
|
|
128
|
+
methods?: string[];
|
|
129
|
+
};
|
|
115
130
|
handlers: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
error?: (ws: WebSocket, error: Error) => void;
|
|
120
|
-
// Optional close handler
|
|
121
|
-
close?: (ws: WebSocket) => void;
|
|
131
|
+
message: MessageHandler;
|
|
132
|
+
error?: ErrorHandler;
|
|
133
|
+
close?: CloseHandler;
|
|
122
134
|
};
|
|
123
|
-
// Optional custom WebSocket implementation
|
|
124
|
-
WebSocketImpl?: WebSocketImpl;
|
|
125
135
|
}
|
|
126
136
|
```
|
|
127
137
|
|
|
128
|
-
## Why This Library is Perfect for Nostr Development
|
|
129
|
-
|
|
130
|
-
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:
|
|
131
|
-
|
|
132
|
-
### 1. Nostr-Specific Message Types
|
|
133
|
-
- Built-in support for core Nostr protocol message types (`subscribe`, `unsubscribe`, `event`, `request/response`)
|
|
134
|
-
- Perfectly aligned with Nostr's pub/sub model
|
|
135
|
-
- Type-safe message handling for all Nostr events
|
|
136
|
-
|
|
137
|
-
### 2. Advanced WebSocket Management
|
|
138
|
-
- Zero-config connection maintenance with automatic reconnection
|
|
139
|
-
- Built-in heartbeat mechanism prevents stale connections
|
|
140
|
-
- Smart message queuing ensures no events are lost during disconnections
|
|
141
|
-
- Comprehensive connection state tracking
|
|
142
|
-
|
|
143
|
-
### 3. Efficient Event Distribution
|
|
144
|
-
- Channel-based broadcasting for targeted event distribution
|
|
145
|
-
- Support for filtered subscriptions (crucial for Nostr event filtering)
|
|
146
|
-
- Memory-efficient subscription tracking
|
|
147
|
-
- Optimized message routing to relevant subscribers
|
|
148
|
-
|
|
149
|
-
### 4. Developer Experience
|
|
150
|
-
- Full TypeScript support with comprehensive type definitions
|
|
151
|
-
- Event-driven architecture matching Nostr's event-centric nature
|
|
152
|
-
- Clear, consistent error handling and validation
|
|
153
|
-
- Required handlers pattern ensures type safety
|
|
154
|
-
|
|
155
138
|
## Contributing
|
|
156
139
|
|
|
157
|
-
|
|
140
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
158
141
|
|
|
159
142
|
## License
|
|
160
143
|
|
|
161
|
-
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
144
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { NostrWSClient } from './client.js';
|
|
2
2
|
export { NostrWSServer } from './server.js';
|
|
3
|
+
export { NostrWSServer as NostrServer } from './nostr-server.js';
|
|
4
|
+
export { createWSServer as createServer } from './nostr-server.js';
|
|
3
5
|
export { getLogger } from './utils/logger.js';
|
|
4
6
|
export type { NostrWSOptions, NostrWSMessage, NostrWSSubscription, NostrWSClientEvents, NostrWSServerEvents, ExtendedWebSocket, NostrWSValidationResult, NostrWSConnectionState } from './types/index.js';
|
|
7
|
+
export type { NostrWSEvent as NostrEvent, NostrWSFilter as NostrFilter, NostrWSSocket as NostrSocket, NostrWSServerOptions, NostrWSServerMessage, NostrWSMessageType } from './types/nostr.js';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { NostrWSClient } from './client.js';
|
|
2
2
|
export { NostrWSServer } from './server.js';
|
|
3
|
+
export { NostrWSServer as NostrServer } from './nostr-server.js';
|
|
4
|
+
export { createWSServer as createServer } from './nostr-server.js';
|
|
3
5
|
export { getLogger } from './utils/logger.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NostrWSServerOptions } from './types/nostr';
|
|
2
|
+
export declare class NostrWSServer {
|
|
3
|
+
private server;
|
|
4
|
+
private logger;
|
|
5
|
+
constructor(options: NostrWSServerOptions);
|
|
6
|
+
close(): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createWSServer(options: NostrWSServerOptions): NostrWSServer;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Server as WebSocketServer } from 'ws';
|
|
2
|
+
import { getLogger } from './utils/logger';
|
|
3
|
+
export class NostrWSServer {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
this.logger = getLogger('NostrWSServer');
|
|
6
|
+
this.server = new WebSocketServer({ port: options.port });
|
|
7
|
+
this.server.on('connection', (socket) => {
|
|
8
|
+
socket.subscriptions = new Set();
|
|
9
|
+
socket.isAlive = true;
|
|
10
|
+
if (options.onConnection) {
|
|
11
|
+
options.onConnection(socket);
|
|
12
|
+
}
|
|
13
|
+
const handleMessage = async (data) => {
|
|
14
|
+
try {
|
|
15
|
+
const message = JSON.parse(data.toString());
|
|
16
|
+
if (options.handlers?.message) {
|
|
17
|
+
await options.handlers.message(socket, message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (options.handlers?.error) {
|
|
22
|
+
options.handlers.error(socket, error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
socket.on('message', handleMessage);
|
|
27
|
+
socket.on('close', () => {
|
|
28
|
+
if (options.handlers?.close) {
|
|
29
|
+
options.handlers.close(socket);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
socket.on('error', (error) => {
|
|
33
|
+
if (options.handlers?.error) {
|
|
34
|
+
options.handlers.error(socket, error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
// Setup heartbeat
|
|
38
|
+
if (options.heartbeatInterval) {
|
|
39
|
+
const interval = setInterval(() => {
|
|
40
|
+
if (!socket.isAlive) {
|
|
41
|
+
socket.terminate();
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
socket.isAlive = false;
|
|
46
|
+
socket.ping();
|
|
47
|
+
}, options.heartbeatInterval);
|
|
48
|
+
socket.on('pong', () => {
|
|
49
|
+
socket.isAlive = true;
|
|
50
|
+
});
|
|
51
|
+
socket.on('close', () => {
|
|
52
|
+
clearInterval(interval);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
close() {
|
|
58
|
+
this.server.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function createWSServer(options) {
|
|
62
|
+
return new NostrWSServer(options);
|
|
63
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ExtendedWebSocket } from './index';
|
|
2
|
+
export interface NostrWSEvent {
|
|
3
|
+
id: string;
|
|
4
|
+
pubkey: string;
|
|
5
|
+
created_at: number;
|
|
6
|
+
kind: number;
|
|
7
|
+
tags: string[][];
|
|
8
|
+
content: string;
|
|
9
|
+
sig: string;
|
|
10
|
+
}
|
|
11
|
+
export interface NostrWSFilter {
|
|
12
|
+
ids?: string[];
|
|
13
|
+
authors?: string[];
|
|
14
|
+
kinds?: number[];
|
|
15
|
+
'#e'?: string[];
|
|
16
|
+
'#p'?: string[];
|
|
17
|
+
since?: number;
|
|
18
|
+
until?: number;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface NostrWSSocket extends ExtendedWebSocket {
|
|
22
|
+
subscriptions?: Set<string>;
|
|
23
|
+
authenticated?: boolean;
|
|
24
|
+
pubkey?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare enum NostrWSMessageType {
|
|
27
|
+
EVENT = "EVENT",
|
|
28
|
+
REQ = "REQ",
|
|
29
|
+
CLOSE = "CLOSE",
|
|
30
|
+
NOTICE = "NOTICE",
|
|
31
|
+
OK = "OK",
|
|
32
|
+
AUTH = "AUTH",
|
|
33
|
+
EOSE = "EOSE"
|
|
34
|
+
}
|
|
35
|
+
export type NostrWSServerMessage = [NostrWSMessageType, ...unknown[]];
|
|
36
|
+
export interface NostrWSServerOptions {
|
|
37
|
+
port: number;
|
|
38
|
+
heartbeatInterval?: number;
|
|
39
|
+
maxPayloadSize?: number;
|
|
40
|
+
cors?: {
|
|
41
|
+
origin: string;
|
|
42
|
+
methods: string[];
|
|
43
|
+
};
|
|
44
|
+
onConnection?: (socket: NostrWSSocket) => void;
|
|
45
|
+
onDisconnect?: (socket: NostrWSSocket) => void;
|
|
46
|
+
onError?: (error: Error, socket?: NostrWSSocket) => void;
|
|
47
|
+
handlers?: {
|
|
48
|
+
message: (socket: NostrWSSocket, message: NostrWSServerMessage) => void | Promise<void>;
|
|
49
|
+
error?: (socket: NostrWSSocket, error: Error) => void;
|
|
50
|
+
close?: (socket: NostrWSSocket) => void;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export var NostrWSMessageType;
|
|
2
|
+
(function (NostrWSMessageType) {
|
|
3
|
+
NostrWSMessageType["EVENT"] = "EVENT";
|
|
4
|
+
NostrWSMessageType["REQ"] = "REQ";
|
|
5
|
+
NostrWSMessageType["CLOSE"] = "CLOSE";
|
|
6
|
+
NostrWSMessageType["NOTICE"] = "NOTICE";
|
|
7
|
+
NostrWSMessageType["OK"] = "OK";
|
|
8
|
+
NostrWSMessageType["AUTH"] = "AUTH";
|
|
9
|
+
NostrWSMessageType["EOSE"] = "EOSE";
|
|
10
|
+
})(NostrWSMessageType || (NostrWSMessageType = {}));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nostr-websocket-utils",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "WebSocket utilities for Nostr applications",
|
|
3
|
+
"version": "0.2.4",
|
|
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,7 +17,15 @@
|
|
|
17
17
|
"keywords": [
|
|
18
18
|
"nostr",
|
|
19
19
|
"websocket",
|
|
20
|
-
"
|
|
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
31
|
"author": "vveerrgg",
|
|
@@ -43,6 +51,7 @@
|
|
|
43
51
|
"nostr-tools": "^2.1.4"
|
|
44
52
|
},
|
|
45
53
|
"devDependencies": {
|
|
54
|
+
"@jest/globals": "^29.7.0",
|
|
46
55
|
"@types/jest": "^29.5.14",
|
|
47
56
|
"@types/node": "^20.11.0",
|
|
48
57
|
"@types/ws": "^8.5.10",
|