nostr-websocket-utils 0.2.4 → 0.3.0
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/LICENSE +1 -1
- package/README.md +151 -103
- package/dist/__mocks__/extendedWsMock.d.ts +35 -0
- package/dist/__mocks__/extendedWsMock.js +156 -0
- package/dist/__mocks__/logger.d.ts +9 -0
- package/dist/__mocks__/logger.js +6 -0
- package/dist/__mocks__/mockLogger.d.ts +41 -0
- package/dist/__mocks__/mockLogger.js +47 -0
- package/dist/__mocks__/mockserver.d.ts +31 -0
- package/dist/__mocks__/mockserver.js +39 -0
- package/dist/__mocks__/wsMock.d.ts +26 -0
- package/dist/__mocks__/wsMock.js +120 -0
- package/dist/client.d.ts +105 -0
- package/dist/client.js +105 -0
- package/dist/core/client.d.ts +94 -0
- package/dist/core/client.js +360 -0
- package/dist/core/nostr-server.d.ts +27 -0
- package/dist/core/nostr-server.js +95 -0
- package/dist/core/queue.d.ts +61 -0
- package/dist/core/queue.js +108 -0
- package/dist/core/server.d.ts +27 -0
- package/dist/core/server.js +114 -0
- package/dist/crypto/bech32.d.ts +26 -0
- package/dist/crypto/bech32.js +163 -0
- package/dist/crypto/handlers.d.ts +11 -0
- package/dist/crypto/handlers.js +36 -0
- package/dist/crypto/index.d.ts +5 -0
- package/dist/crypto/index.js +5 -0
- package/dist/crypto/schnorr.d.ts +16 -0
- package/dist/crypto/schnorr.js +51 -0
- package/dist/endpoints/metrics.d.ts +29 -0
- package/dist/endpoints/metrics.js +101 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +16 -4
- package/dist/nips/index.d.ts +19 -0
- package/dist/nips/index.js +34 -0
- package/dist/nips/nip-01.d.ts +34 -0
- package/dist/nips/nip-01.js +145 -0
- package/dist/nips/nip-02.d.ts +83 -0
- package/dist/nips/nip-02.js +123 -0
- package/dist/nips/nip-04.d.ts +36 -0
- package/dist/nips/nip-04.js +105 -0
- package/dist/nips/nip-05.d.ts +86 -0
- package/dist/nips/nip-05.js +151 -0
- package/dist/nips/nip-09.d.ts +92 -0
- package/dist/nips/nip-09.js +190 -0
- package/dist/nips/nip-11.d.ts +64 -0
- package/dist/nips/nip-11.js +154 -0
- package/dist/nips/nip-13.d.ts +73 -0
- package/dist/nips/nip-13.js +128 -0
- package/dist/nips/nip-15.d.ts +83 -0
- package/dist/nips/nip-15.js +101 -0
- package/dist/nips/nip-16.d.ts +88 -0
- package/dist/nips/nip-16.js +150 -0
- package/dist/nips/nip-19.d.ts +28 -0
- package/dist/nips/nip-19.js +103 -0
- package/dist/nips/nip-20.d.ts +59 -0
- package/dist/nips/nip-20.js +95 -0
- package/dist/nips/nip-22.d.ts +89 -0
- package/dist/nips/nip-22.js +142 -0
- package/dist/nips/nip-26.d.ts +52 -0
- package/dist/nips/nip-26.js +139 -0
- package/dist/nips/nip-28.d.ts +103 -0
- package/dist/nips/nip-28.js +170 -0
- package/dist/nips/nip-33.d.ts +94 -0
- package/dist/nips/nip-33.js +133 -0
- package/dist/nostr-server.d.ts +23 -0
- package/dist/nostr-server.js +44 -0
- package/dist/server.d.ts +13 -3
- package/dist/server.js +60 -33
- package/dist/transport/base.d.ts +54 -0
- package/dist/transport/base.js +104 -0
- package/dist/transport/websocket.d.ts +22 -0
- package/dist/transport/websocket.js +122 -0
- package/dist/types/events.d.ts +63 -0
- package/dist/types/events.js +5 -0
- package/dist/types/filters.d.ts +19 -0
- package/dist/types/filters.js +5 -0
- package/dist/types/handlers.d.ts +80 -0
- package/dist/types/handlers.js +5 -0
- package/dist/types/index.d.ts +118 -39
- package/dist/types/index.js +21 -1
- package/dist/types/logger.d.ts +40 -0
- package/dist/types/logger.js +5 -0
- package/dist/types/messages.d.ts +135 -0
- package/dist/types/messages.js +40 -0
- package/dist/types/nostr.d.ts +120 -39
- package/dist/types/nostr.js +5 -10
- package/dist/types/options.d.ts +154 -0
- package/dist/types/options.js +5 -0
- package/dist/types/relays.d.ts +26 -0
- package/dist/types/relays.js +5 -0
- package/dist/types/scoring.d.ts +47 -0
- package/dist/types/scoring.js +29 -0
- package/dist/types/socket.d.ts +99 -0
- package/dist/types/socket.js +5 -0
- package/dist/types/transport.d.ts +97 -0
- package/dist/types/transport.js +5 -0
- package/dist/types/validation.d.ts +50 -0
- package/dist/types/validation.js +5 -0
- package/dist/types/websocket.d.ts +172 -0
- package/dist/types/websocket.js +5 -0
- package/dist/utils/http.d.ts +10 -0
- package/dist/utils/http.js +24 -0
- package/dist/utils/logger.d.ts +11 -2
- package/dist/utils/logger.js +18 -13
- package/dist/utils/metrics.d.ts +81 -0
- package/dist/utils/metrics.js +206 -0
- package/dist/utils/rate-limiter.d.ts +85 -0
- package/dist/utils/rate-limiter.js +175 -0
- package/package.json +18 -21
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/// <reference types="node" />
|
|
3
|
+
declare class MockWebSocket {
|
|
4
|
+
static OPEN: number;
|
|
5
|
+
static CLOSED: number;
|
|
6
|
+
static CONNECTING: number;
|
|
7
|
+
static CLOSING: number;
|
|
8
|
+
url: string;
|
|
9
|
+
readyState: number;
|
|
10
|
+
onopen: ((event: Event) => void) | null;
|
|
11
|
+
onclose: ((event: CloseEvent) => void) | null;
|
|
12
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
13
|
+
eventListeners: {
|
|
14
|
+
[type: string]: ((data: Buffer) => void)[];
|
|
15
|
+
};
|
|
16
|
+
constructor(url: string | URL, _protocols?: string | string[]);
|
|
17
|
+
send(_data: unknown): void;
|
|
18
|
+
close(): void;
|
|
19
|
+
simulateOpen(): void;
|
|
20
|
+
simulateMessage(data: unknown): void;
|
|
21
|
+
simulateClose(): void;
|
|
22
|
+
addEventListener(type: string, listener: (data: Buffer) => void): void;
|
|
23
|
+
dispatchEvent(event: Event | CloseEvent | MessageEvent): boolean;
|
|
24
|
+
}
|
|
25
|
+
declare const mockWebSocket: MockWebSocket;
|
|
26
|
+
export default mockWebSocket;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
class MockWebSocket {
|
|
2
|
+
constructor(url, _protocols) {
|
|
3
|
+
this.onopen = null;
|
|
4
|
+
this.onclose = null;
|
|
5
|
+
this.onmessage = null;
|
|
6
|
+
this.eventListeners = {};
|
|
7
|
+
this.url = typeof url === 'string' ? url : url.toString();
|
|
8
|
+
this.readyState = MockWebSocket.CONNECTING;
|
|
9
|
+
}
|
|
10
|
+
send(_data) {
|
|
11
|
+
// Implementation not needed for these tests
|
|
12
|
+
}
|
|
13
|
+
close() {
|
|
14
|
+
this.readyState = MockWebSocket.CLOSING;
|
|
15
|
+
this.simulateClose();
|
|
16
|
+
}
|
|
17
|
+
simulateOpen() {
|
|
18
|
+
this.readyState = MockWebSocket.OPEN;
|
|
19
|
+
const event = new MockEvent('open');
|
|
20
|
+
if (this.onopen)
|
|
21
|
+
this.onopen(event);
|
|
22
|
+
}
|
|
23
|
+
simulateMessage(data) {
|
|
24
|
+
const messageData = Buffer.from(JSON.stringify(data));
|
|
25
|
+
const listeners = this.eventListeners['message'] || [];
|
|
26
|
+
listeners.forEach(listener => listener(messageData));
|
|
27
|
+
}
|
|
28
|
+
simulateClose() {
|
|
29
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
30
|
+
const event = new MockCloseEvent('close');
|
|
31
|
+
if (this.onclose)
|
|
32
|
+
this.onclose(event);
|
|
33
|
+
}
|
|
34
|
+
addEventListener(type, listener) {
|
|
35
|
+
if (!this.eventListeners[type]) {
|
|
36
|
+
this.eventListeners[type] = [];
|
|
37
|
+
}
|
|
38
|
+
this.eventListeners[type].push(listener);
|
|
39
|
+
}
|
|
40
|
+
dispatchEvent(event) {
|
|
41
|
+
if (event.type === 'close' && this.onclose) {
|
|
42
|
+
this.onclose(event);
|
|
43
|
+
}
|
|
44
|
+
else if (event.type === 'open' && this.onopen) {
|
|
45
|
+
this.onopen(event);
|
|
46
|
+
}
|
|
47
|
+
else if (event.type === 'message' && this.onmessage) {
|
|
48
|
+
this.onmessage(event);
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
MockWebSocket.OPEN = 1;
|
|
54
|
+
MockWebSocket.CLOSED = 3;
|
|
55
|
+
MockWebSocket.CONNECTING = 0;
|
|
56
|
+
MockWebSocket.CLOSING = 2;
|
|
57
|
+
// Set up the global WebSocket with proper typing
|
|
58
|
+
global.WebSocket = MockWebSocket;
|
|
59
|
+
class MockEvent {
|
|
60
|
+
constructor(type) {
|
|
61
|
+
this.NONE = 0;
|
|
62
|
+
this.CAPTURING_PHASE = 1;
|
|
63
|
+
this.AT_TARGET = 2;
|
|
64
|
+
this.BUBBLING_PHASE = 3;
|
|
65
|
+
this.target = null;
|
|
66
|
+
this.currentTarget = null;
|
|
67
|
+
this.eventPhase = 0;
|
|
68
|
+
this.bubbles = false;
|
|
69
|
+
this.cancelable = false;
|
|
70
|
+
this.defaultPrevented = false;
|
|
71
|
+
this.composed = false;
|
|
72
|
+
this.timeStamp = Date.now();
|
|
73
|
+
this.srcElement = null;
|
|
74
|
+
this.returnValue = true;
|
|
75
|
+
this.cancelBubble = false;
|
|
76
|
+
this.isTrusted = true;
|
|
77
|
+
this.type = type;
|
|
78
|
+
}
|
|
79
|
+
preventDefault() { }
|
|
80
|
+
stopPropagation() { }
|
|
81
|
+
stopImmediatePropagation() { }
|
|
82
|
+
composedPath() { return []; }
|
|
83
|
+
initEvent(_type, _bubbles, _cancelable) { }
|
|
84
|
+
}
|
|
85
|
+
class MockCloseEvent extends MockEvent {
|
|
86
|
+
constructor(type) {
|
|
87
|
+
super(type);
|
|
88
|
+
this.code = 1000;
|
|
89
|
+
this.reason = '';
|
|
90
|
+
this.wasClean = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/*
|
|
94
|
+
class MockMessageEvent extends MockEvent implements MessageEvent {
|
|
95
|
+
readonly data: unknown;
|
|
96
|
+
readonly origin: string = '';
|
|
97
|
+
readonly lastEventId: string = '';
|
|
98
|
+
readonly source: Window | MessagePort | ServiceWorker | null = null;
|
|
99
|
+
readonly ports: ReadonlyArray<MessagePort> = [];
|
|
100
|
+
|
|
101
|
+
constructor(data: unknown) {
|
|
102
|
+
super('message');
|
|
103
|
+
this.data = data;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
initMessageEvent(
|
|
107
|
+
_type: string,
|
|
108
|
+
_bubbles?: boolean,
|
|
109
|
+
_cancelable?: boolean,
|
|
110
|
+
_data: unknown = null,
|
|
111
|
+
_origin: string = '',
|
|
112
|
+
_lastEventId: string = '',
|
|
113
|
+
_source: Window | MessagePort | ServiceWorker | null = null,
|
|
114
|
+
_ports: MessagePort[] = []
|
|
115
|
+
): void {}
|
|
116
|
+
}
|
|
117
|
+
*/
|
|
118
|
+
// Export an instance of MockWebSocket
|
|
119
|
+
const mockWebSocket = new MockWebSocket('ws://test.com');
|
|
120
|
+
export default mockWebSocket;
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client implementation for Nostr protocol communication
|
|
6
|
+
* Extends EventEmitter to provide event-based message handling
|
|
7
|
+
*
|
|
8
|
+
* @extends EventEmitter
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const client = new NostrWSClient('wss://relay.example.com', {
|
|
12
|
+
* logger: console,
|
|
13
|
+
* heartbeatInterval: 30000,
|
|
14
|
+
* handlers: {
|
|
15
|
+
* message: async (msg) => console.log('Received:', msg),
|
|
16
|
+
* error: (err) => console.error('Error:', err),
|
|
17
|
+
* close: () => console.log('Connection closed')
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await client.connect();
|
|
22
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
4
25
|
export declare class NostrWSClient extends EventEmitter {
|
|
5
26
|
private url;
|
|
6
27
|
private ws;
|
|
@@ -10,15 +31,99 @@ export declare class NostrWSClient extends EventEmitter {
|
|
|
10
31
|
private reconnectAttempts;
|
|
11
32
|
private messageQueue;
|
|
12
33
|
private clientId;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new NostrWSClient instance
|
|
36
|
+
*
|
|
37
|
+
* @param {string} url - The WebSocket server URL to connect to
|
|
38
|
+
* @param {Partial<NostrWSOptions>} options - Configuration options
|
|
39
|
+
* @param {number} [options.heartbeatInterval=30000] - Interval for sending heartbeat messages in milliseconds
|
|
40
|
+
* @param {object} options.logger - Logger instance (required)
|
|
41
|
+
* @param {Function} [options.WebSocketImpl=WebSocket] - WebSocket implementation to use
|
|
42
|
+
* @param {object} [options.handlers] - Event handlers
|
|
43
|
+
* @param {Function} [options.handlers.message] - Message handler function
|
|
44
|
+
* @param {Function} [options.handlers.error] - Error handler function
|
|
45
|
+
* @param {Function} [options.handlers.close] - Connection close handler function
|
|
46
|
+
* @throws {Error} If logger is not provided
|
|
47
|
+
*/
|
|
13
48
|
constructor(url: string, options?: Partial<NostrWSOptions>);
|
|
49
|
+
/**
|
|
50
|
+
* Establishes a connection to the WebSocket server
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* client.connect();
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
14
57
|
connect(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Sets up event handlers for the WebSocket connection
|
|
60
|
+
*
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
15
63
|
private setupEventHandlers;
|
|
64
|
+
/**
|
|
65
|
+
* Starts sending heartbeat messages at the specified interval
|
|
66
|
+
*
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
16
69
|
private startHeartbeat;
|
|
70
|
+
/**
|
|
71
|
+
* Stops sending heartbeat messages
|
|
72
|
+
*
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
17
75
|
private stopHeartbeat;
|
|
76
|
+
/**
|
|
77
|
+
* Handles reconnecting to the WebSocket server after a disconnection
|
|
78
|
+
*
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
18
81
|
private handleReconnect;
|
|
82
|
+
/**
|
|
83
|
+
* Subscribes to a channel with optional filter
|
|
84
|
+
*
|
|
85
|
+
* @param {string} channel - Channel name
|
|
86
|
+
* @param {unknown} [filter] - Filter data
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* client.subscribe('channel-name', { ...filterData });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
19
92
|
subscribe(channel: string, filter?: unknown): void;
|
|
93
|
+
/**
|
|
94
|
+
* Unsubscribes from a channel
|
|
95
|
+
*
|
|
96
|
+
* @param {string} channel - Channel name
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* client.unsubscribe('channel-name');
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
20
102
|
unsubscribe(channel: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Flushes the message queue by sending pending messages
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
21
108
|
private flushMessageQueue;
|
|
109
|
+
/**
|
|
110
|
+
* Sends a message to the WebSocket server
|
|
111
|
+
*
|
|
112
|
+
* @param {NostrWSMessage} message - Message to send
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
22
119
|
send(message: NostrWSMessage): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Closes the WebSocket connection
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* client.close();
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
23
128
|
close(): void;
|
|
24
129
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from 'uuid';
|
|
2
2
|
import WebSocket from 'ws';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket client implementation for Nostr protocol communication
|
|
6
|
+
* Extends EventEmitter to provide event-based message handling
|
|
7
|
+
*
|
|
8
|
+
* @extends EventEmitter
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const client = new NostrWSClient('wss://relay.example.com', {
|
|
12
|
+
* logger: console,
|
|
13
|
+
* heartbeatInterval: 30000,
|
|
14
|
+
* handlers: {
|
|
15
|
+
* message: async (msg) => console.log('Received:', msg),
|
|
16
|
+
* error: (err) => console.error('Error:', err),
|
|
17
|
+
* close: () => console.log('Connection closed')
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* await client.connect();
|
|
22
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
4
25
|
export class NostrWSClient extends EventEmitter {
|
|
26
|
+
/**
|
|
27
|
+
* Creates a new NostrWSClient instance
|
|
28
|
+
*
|
|
29
|
+
* @param {string} url - The WebSocket server URL to connect to
|
|
30
|
+
* @param {Partial<NostrWSOptions>} options - Configuration options
|
|
31
|
+
* @param {number} [options.heartbeatInterval=30000] - Interval for sending heartbeat messages in milliseconds
|
|
32
|
+
* @param {object} options.logger - Logger instance (required)
|
|
33
|
+
* @param {Function} [options.WebSocketImpl=WebSocket] - WebSocket implementation to use
|
|
34
|
+
* @param {object} [options.handlers] - Event handlers
|
|
35
|
+
* @param {Function} [options.handlers.message] - Message handler function
|
|
36
|
+
* @param {Function} [options.handlers.error] - Error handler function
|
|
37
|
+
* @param {Function} [options.handlers.close] - Connection close handler function
|
|
38
|
+
* @throws {Error} If logger is not provided
|
|
39
|
+
*/
|
|
5
40
|
constructor(url, options = {}) {
|
|
6
41
|
super();
|
|
7
42
|
this.url = url;
|
|
@@ -25,6 +60,14 @@ export class NostrWSClient extends EventEmitter {
|
|
|
25
60
|
}
|
|
26
61
|
};
|
|
27
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Establishes a connection to the WebSocket server
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* client.connect();
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
28
71
|
connect() {
|
|
29
72
|
if (this.ws) {
|
|
30
73
|
this.options.logger.info('WebSocket connection already exists');
|
|
@@ -41,6 +84,11 @@ export class NostrWSClient extends EventEmitter {
|
|
|
41
84
|
this.handleReconnect();
|
|
42
85
|
}
|
|
43
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Sets up event handlers for the WebSocket connection
|
|
89
|
+
*
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
44
92
|
setupEventHandlers() {
|
|
45
93
|
if (!this.ws)
|
|
46
94
|
return;
|
|
@@ -81,6 +129,11 @@ export class NostrWSClient extends EventEmitter {
|
|
|
81
129
|
}
|
|
82
130
|
});
|
|
83
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Starts sending heartbeat messages at the specified interval
|
|
134
|
+
*
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
84
137
|
startHeartbeat() {
|
|
85
138
|
if (this.heartbeatInterval)
|
|
86
139
|
return;
|
|
@@ -90,12 +143,22 @@ export class NostrWSClient extends EventEmitter {
|
|
|
90
143
|
}
|
|
91
144
|
}, this.options.heartbeatInterval || 30000);
|
|
92
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Stops sending heartbeat messages
|
|
148
|
+
*
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
93
151
|
stopHeartbeat() {
|
|
94
152
|
if (this.heartbeatInterval) {
|
|
95
153
|
clearInterval(this.heartbeatInterval);
|
|
96
154
|
this.heartbeatInterval = null;
|
|
97
155
|
}
|
|
98
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Handles reconnecting to the WebSocket server after a disconnection
|
|
159
|
+
*
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
99
162
|
handleReconnect() {
|
|
100
163
|
if (this.reconnectTimeout)
|
|
101
164
|
return;
|
|
@@ -106,18 +169,42 @@ export class NostrWSClient extends EventEmitter {
|
|
|
106
169
|
this.emit('reconnect');
|
|
107
170
|
}, 5000);
|
|
108
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Subscribes to a channel with optional filter
|
|
174
|
+
*
|
|
175
|
+
* @param {string} channel - Channel name
|
|
176
|
+
* @param {unknown} [filter] - Filter data
|
|
177
|
+
* @example
|
|
178
|
+
* ```typescript
|
|
179
|
+
* client.subscribe('channel-name', { ...filterData });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
109
182
|
subscribe(channel, filter) {
|
|
110
183
|
this.send({
|
|
111
184
|
type: 'subscribe',
|
|
112
185
|
data: { channel, filter }
|
|
113
186
|
});
|
|
114
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Unsubscribes from a channel
|
|
190
|
+
*
|
|
191
|
+
* @param {string} channel - Channel name
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* client.unsubscribe('channel-name');
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
115
197
|
unsubscribe(channel) {
|
|
116
198
|
this.send({
|
|
117
199
|
type: 'unsubscribe',
|
|
118
200
|
data: { channel }
|
|
119
201
|
});
|
|
120
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Flushes the message queue by sending pending messages
|
|
205
|
+
*
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
121
208
|
flushMessageQueue() {
|
|
122
209
|
while (this.messageQueue.length > 0 && this.ws?.readyState === WebSocket.OPEN) {
|
|
123
210
|
const message = this.messageQueue.shift();
|
|
@@ -126,6 +213,16 @@ export class NostrWSClient extends EventEmitter {
|
|
|
126
213
|
}
|
|
127
214
|
}
|
|
128
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Sends a message to the WebSocket server
|
|
218
|
+
*
|
|
219
|
+
* @param {NostrWSMessage} message - Message to send
|
|
220
|
+
* @returns {Promise<void>}
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* client.send({ type: 'EVENT', data: { ... } });
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
129
226
|
async send(message) {
|
|
130
227
|
message.id = message.id || uuidv4();
|
|
131
228
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
@@ -140,6 +237,14 @@ export class NostrWSClient extends EventEmitter {
|
|
|
140
237
|
this.handleReconnect();
|
|
141
238
|
}
|
|
142
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Closes the WebSocket connection
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* client.close();
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
143
248
|
close() {
|
|
144
249
|
this.stopHeartbeat();
|
|
145
250
|
if (this.reconnectTimeout) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file WebSocket client implementation
|
|
3
|
+
* @module core/client
|
|
4
|
+
*/
|
|
5
|
+
/// <reference types="node" />
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { NostrWSMessage, NostrWSOptions, ConnectionState } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* WebSocket client implementation for Nostr protocol communication
|
|
10
|
+
* Extends EventEmitter to provide event-based message handling
|
|
11
|
+
*/
|
|
12
|
+
export declare class NostrWSClient extends EventEmitter {
|
|
13
|
+
private readonly url;
|
|
14
|
+
private ws;
|
|
15
|
+
private state;
|
|
16
|
+
private messageQueue;
|
|
17
|
+
private reconnectTimeout;
|
|
18
|
+
private heartbeatInterval;
|
|
19
|
+
private heartbeatTimeout;
|
|
20
|
+
private missedHeartbeats;
|
|
21
|
+
private reconnectAttempts;
|
|
22
|
+
private subscriptions;
|
|
23
|
+
private readonly clientId;
|
|
24
|
+
private readonly options;
|
|
25
|
+
constructor(url: string, options?: Partial<NostrWSOptions>);
|
|
26
|
+
/**
|
|
27
|
+
* Gets the current connection state
|
|
28
|
+
*/
|
|
29
|
+
get connectionState(): ConnectionState;
|
|
30
|
+
/**
|
|
31
|
+
* Updates the connection state and notifies handlers
|
|
32
|
+
*/
|
|
33
|
+
private setState;
|
|
34
|
+
/**
|
|
35
|
+
* Establishes a connection to the WebSocket server
|
|
36
|
+
*/
|
|
37
|
+
connect(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Sets up event handlers for the WebSocket connection
|
|
40
|
+
*/
|
|
41
|
+
private setupEventHandlers;
|
|
42
|
+
/**
|
|
43
|
+
* Starts the heartbeat mechanism
|
|
44
|
+
*/
|
|
45
|
+
private startHeartbeat;
|
|
46
|
+
/**
|
|
47
|
+
* Handles heartbeat responses
|
|
48
|
+
*/
|
|
49
|
+
private handleHeartbeatResponse;
|
|
50
|
+
/**
|
|
51
|
+
* Stops the heartbeat mechanism
|
|
52
|
+
*/
|
|
53
|
+
private stopHeartbeat;
|
|
54
|
+
/**
|
|
55
|
+
* Handles connection errors
|
|
56
|
+
*/
|
|
57
|
+
private handleConnectionError;
|
|
58
|
+
/**
|
|
59
|
+
* Handles disconnection and cleanup
|
|
60
|
+
*/
|
|
61
|
+
private handleDisconnection;
|
|
62
|
+
/**
|
|
63
|
+
* Initiates reconnection with exponential backoff
|
|
64
|
+
*/
|
|
65
|
+
private reconnect;
|
|
66
|
+
/**
|
|
67
|
+
* Subscribes to a channel with optional filter
|
|
68
|
+
*/
|
|
69
|
+
subscribe(channel: string, filter?: unknown): void;
|
|
70
|
+
/**
|
|
71
|
+
* Resubscribes to all active subscriptions
|
|
72
|
+
*/
|
|
73
|
+
private resubscribeAll;
|
|
74
|
+
/**
|
|
75
|
+
* Unsubscribes from a channel
|
|
76
|
+
*/
|
|
77
|
+
unsubscribe(channel: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Flushes the message queue by sending pending messages
|
|
80
|
+
*/
|
|
81
|
+
private flushMessageQueue;
|
|
82
|
+
/**
|
|
83
|
+
* Sends a message immediately without queueing
|
|
84
|
+
*/
|
|
85
|
+
private sendImmediate;
|
|
86
|
+
/**
|
|
87
|
+
* Sends a message to the WebSocket server
|
|
88
|
+
*/
|
|
89
|
+
send(message: NostrWSMessage): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Closes the WebSocket connection
|
|
92
|
+
*/
|
|
93
|
+
close(): void;
|
|
94
|
+
}
|