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.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +151 -103
  3. package/dist/__mocks__/extendedWsMock.d.ts +35 -0
  4. package/dist/__mocks__/extendedWsMock.js +156 -0
  5. package/dist/__mocks__/logger.d.ts +9 -0
  6. package/dist/__mocks__/logger.js +6 -0
  7. package/dist/__mocks__/mockLogger.d.ts +41 -0
  8. package/dist/__mocks__/mockLogger.js +47 -0
  9. package/dist/__mocks__/mockserver.d.ts +31 -0
  10. package/dist/__mocks__/mockserver.js +39 -0
  11. package/dist/__mocks__/wsMock.d.ts +26 -0
  12. package/dist/__mocks__/wsMock.js +120 -0
  13. package/dist/client.d.ts +105 -0
  14. package/dist/client.js +105 -0
  15. package/dist/core/client.d.ts +94 -0
  16. package/dist/core/client.js +360 -0
  17. package/dist/core/nostr-server.d.ts +27 -0
  18. package/dist/core/nostr-server.js +95 -0
  19. package/dist/core/queue.d.ts +61 -0
  20. package/dist/core/queue.js +108 -0
  21. package/dist/core/server.d.ts +27 -0
  22. package/dist/core/server.js +114 -0
  23. package/dist/crypto/bech32.d.ts +26 -0
  24. package/dist/crypto/bech32.js +163 -0
  25. package/dist/crypto/handlers.d.ts +11 -0
  26. package/dist/crypto/handlers.js +36 -0
  27. package/dist/crypto/index.d.ts +5 -0
  28. package/dist/crypto/index.js +5 -0
  29. package/dist/crypto/schnorr.d.ts +16 -0
  30. package/dist/crypto/schnorr.js +51 -0
  31. package/dist/endpoints/metrics.d.ts +29 -0
  32. package/dist/endpoints/metrics.js +101 -0
  33. package/dist/index.d.ts +11 -6
  34. package/dist/index.js +16 -4
  35. package/dist/nips/index.d.ts +19 -0
  36. package/dist/nips/index.js +34 -0
  37. package/dist/nips/nip-01.d.ts +34 -0
  38. package/dist/nips/nip-01.js +145 -0
  39. package/dist/nips/nip-02.d.ts +83 -0
  40. package/dist/nips/nip-02.js +123 -0
  41. package/dist/nips/nip-04.d.ts +36 -0
  42. package/dist/nips/nip-04.js +105 -0
  43. package/dist/nips/nip-05.d.ts +86 -0
  44. package/dist/nips/nip-05.js +151 -0
  45. package/dist/nips/nip-09.d.ts +92 -0
  46. package/dist/nips/nip-09.js +190 -0
  47. package/dist/nips/nip-11.d.ts +64 -0
  48. package/dist/nips/nip-11.js +154 -0
  49. package/dist/nips/nip-13.d.ts +73 -0
  50. package/dist/nips/nip-13.js +128 -0
  51. package/dist/nips/nip-15.d.ts +83 -0
  52. package/dist/nips/nip-15.js +101 -0
  53. package/dist/nips/nip-16.d.ts +88 -0
  54. package/dist/nips/nip-16.js +150 -0
  55. package/dist/nips/nip-19.d.ts +28 -0
  56. package/dist/nips/nip-19.js +103 -0
  57. package/dist/nips/nip-20.d.ts +59 -0
  58. package/dist/nips/nip-20.js +95 -0
  59. package/dist/nips/nip-22.d.ts +89 -0
  60. package/dist/nips/nip-22.js +142 -0
  61. package/dist/nips/nip-26.d.ts +52 -0
  62. package/dist/nips/nip-26.js +139 -0
  63. package/dist/nips/nip-28.d.ts +103 -0
  64. package/dist/nips/nip-28.js +170 -0
  65. package/dist/nips/nip-33.d.ts +94 -0
  66. package/dist/nips/nip-33.js +133 -0
  67. package/dist/nostr-server.d.ts +23 -0
  68. package/dist/nostr-server.js +44 -0
  69. package/dist/server.d.ts +13 -3
  70. package/dist/server.js +60 -33
  71. package/dist/transport/base.d.ts +54 -0
  72. package/dist/transport/base.js +104 -0
  73. package/dist/transport/websocket.d.ts +22 -0
  74. package/dist/transport/websocket.js +122 -0
  75. package/dist/types/events.d.ts +63 -0
  76. package/dist/types/events.js +5 -0
  77. package/dist/types/filters.d.ts +19 -0
  78. package/dist/types/filters.js +5 -0
  79. package/dist/types/handlers.d.ts +80 -0
  80. package/dist/types/handlers.js +5 -0
  81. package/dist/types/index.d.ts +118 -39
  82. package/dist/types/index.js +21 -1
  83. package/dist/types/logger.d.ts +40 -0
  84. package/dist/types/logger.js +5 -0
  85. package/dist/types/messages.d.ts +135 -0
  86. package/dist/types/messages.js +40 -0
  87. package/dist/types/nostr.d.ts +120 -39
  88. package/dist/types/nostr.js +5 -10
  89. package/dist/types/options.d.ts +154 -0
  90. package/dist/types/options.js +5 -0
  91. package/dist/types/relays.d.ts +26 -0
  92. package/dist/types/relays.js +5 -0
  93. package/dist/types/scoring.d.ts +47 -0
  94. package/dist/types/scoring.js +29 -0
  95. package/dist/types/socket.d.ts +99 -0
  96. package/dist/types/socket.js +5 -0
  97. package/dist/types/transport.d.ts +97 -0
  98. package/dist/types/transport.js +5 -0
  99. package/dist/types/validation.d.ts +50 -0
  100. package/dist/types/validation.js +5 -0
  101. package/dist/types/websocket.d.ts +172 -0
  102. package/dist/types/websocket.js +5 -0
  103. package/dist/utils/http.d.ts +10 -0
  104. package/dist/utils/http.js +24 -0
  105. package/dist/utils/logger.d.ts +11 -2
  106. package/dist/utils/logger.js +18 -13
  107. package/dist/utils/metrics.d.ts +81 -0
  108. package/dist/utils/metrics.js +206 -0
  109. package/dist/utils/rate-limiter.d.ts +85 -0
  110. package/dist/utils/rate-limiter.js +175 -0
  111. package/package.json +18 -21
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @file NIP-28: Public Chat
3
+ * @module nips/nip-28
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/28.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Chat event kinds
10
+ */
11
+ export declare const ChatEventKinds: {
12
+ readonly CHANNEL_CREATION: 40;
13
+ readonly CHANNEL_METADATA: 41;
14
+ readonly CHANNEL_MESSAGE: 42;
15
+ readonly CHANNEL_HIDE_MESSAGE: 43;
16
+ readonly CHANNEL_MUTE_USER: 44;
17
+ readonly USER_MUTE: 45;
18
+ };
19
+ /**
20
+ * Channel metadata structure
21
+ */
22
+ export interface ChannelMetadata {
23
+ name: string;
24
+ about?: string;
25
+ picture?: string;
26
+ rules?: string[];
27
+ moderators?: string[];
28
+ pinned?: string[];
29
+ }
30
+ /**
31
+ * Creates a channel creation message
32
+ * @param metadata - Channel metadata
33
+ * @returns {NostrWSMessage} Channel creation event
34
+ */
35
+ export declare function createChannelCreationEvent(metadata: ChannelMetadata): NostrWSMessage;
36
+ /**
37
+ * Creates a channel message
38
+ * @param channelId - Channel ID
39
+ * @param content - Message content
40
+ * @param replyTo - Optional ID of message being replied to
41
+ * @returns {NostrWSMessage} Channel message event
42
+ */
43
+ export declare function createChannelMessage(channelId: string, content: string, replyTo?: string): NostrWSMessage;
44
+ /**
45
+ * Creates a message moderation event
46
+ * @param channelId - Channel ID
47
+ * @param messageId - Message ID to moderate
48
+ * @param reason - Moderation reason
49
+ * @returns {NostrWSMessage} Hide message event
50
+ */
51
+ export declare function createHideMessageEvent(channelId: string, messageId: string, reason: string): NostrWSMessage;
52
+ /**
53
+ * Chat message handler interface
54
+ */
55
+ export interface ChatMessageHandler {
56
+ /**
57
+ * Handles incoming chat message
58
+ * @param message - Chat message
59
+ * @returns {Promise<void>}
60
+ */
61
+ handleMessage(message: NostrWSMessage): Promise<void>;
62
+ /**
63
+ * Handles message moderation
64
+ * @param message - Moderation message
65
+ * @returns {Promise<void>}
66
+ */
67
+ handleModeration(message: NostrWSMessage): Promise<void>;
68
+ }
69
+ /**
70
+ * Creates a chat message handler
71
+ * @param logger - Logger instance
72
+ * @returns {ChatMessageHandler} Message handler
73
+ */
74
+ export declare function createChatMessageHandler(logger: Logger): ChatMessageHandler;
75
+ /**
76
+ * Channel subscription manager interface
77
+ */
78
+ export interface ChannelSubscriptionManager {
79
+ /**
80
+ * Subscribes to a channel
81
+ * @param channelId - Channel ID
82
+ * @returns {NostrWSMessage} Subscription message
83
+ */
84
+ subscribe(channelId: string): NostrWSMessage;
85
+ /**
86
+ * Unsubscribes from a channel
87
+ * @param channelId - Channel ID
88
+ * @returns {NostrWSMessage} Unsubscribe message
89
+ */
90
+ unsubscribe(channelId: string): NostrWSMessage;
91
+ /**
92
+ * Gets channel metadata
93
+ * @param channelId - Channel ID
94
+ * @returns {Promise<ChannelMetadata | undefined>} Channel metadata
95
+ */
96
+ getMetadata(channelId: string): Promise<ChannelMetadata | undefined>;
97
+ }
98
+ /**
99
+ * Creates a channel subscription manager
100
+ * @param logger - Logger instance
101
+ * @returns {ChannelSubscriptionManager} Subscription manager
102
+ */
103
+ export declare function createChannelSubscriptionManager(logger: Logger): ChannelSubscriptionManager;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @file NIP-28: Public Chat
3
+ * @module nips/nip-28
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/28.md
5
+ */
6
+ /**
7
+ * Chat event kinds
8
+ */
9
+ export const ChatEventKinds = {
10
+ CHANNEL_CREATION: 40,
11
+ CHANNEL_METADATA: 41,
12
+ CHANNEL_MESSAGE: 42,
13
+ CHANNEL_HIDE_MESSAGE: 43,
14
+ CHANNEL_MUTE_USER: 44,
15
+ USER_MUTE: 45
16
+ };
17
+ /**
18
+ * Creates a channel creation message
19
+ * @param metadata - Channel metadata
20
+ * @returns {NostrWSMessage} Channel creation event
21
+ */
22
+ export function createChannelCreationEvent(metadata) {
23
+ return {
24
+ type: 'EVENT',
25
+ data: {
26
+ kind: ChatEventKinds.CHANNEL_CREATION,
27
+ content: JSON.stringify(metadata),
28
+ tags: []
29
+ }
30
+ };
31
+ }
32
+ /**
33
+ * Creates a channel message
34
+ * @param channelId - Channel ID
35
+ * @param content - Message content
36
+ * @param replyTo - Optional ID of message being replied to
37
+ * @returns {NostrWSMessage} Channel message event
38
+ */
39
+ export function createChannelMessage(channelId, content, replyTo) {
40
+ const tags = [['e', channelId, '', 'root']];
41
+ if (replyTo) {
42
+ tags.push(['e', replyTo, '', 'reply']);
43
+ }
44
+ return {
45
+ type: 'EVENT',
46
+ data: {
47
+ kind: ChatEventKinds.CHANNEL_MESSAGE,
48
+ content,
49
+ tags
50
+ }
51
+ };
52
+ }
53
+ /**
54
+ * Creates a message moderation event
55
+ * @param channelId - Channel ID
56
+ * @param messageId - Message ID to moderate
57
+ * @param reason - Moderation reason
58
+ * @returns {NostrWSMessage} Hide message event
59
+ */
60
+ export function createHideMessageEvent(channelId, messageId, reason) {
61
+ return {
62
+ type: 'EVENT',
63
+ data: {
64
+ kind: ChatEventKinds.CHANNEL_HIDE_MESSAGE,
65
+ content: reason,
66
+ tags: [
67
+ ['e', channelId, '', 'root'],
68
+ ['e', messageId, '', 'reply']
69
+ ]
70
+ }
71
+ };
72
+ }
73
+ /**
74
+ * Creates a chat message handler
75
+ * @param logger - Logger instance
76
+ * @returns {ChatMessageHandler} Message handler
77
+ */
78
+ export function createChatMessageHandler(logger) {
79
+ return {
80
+ async handleMessage(message) {
81
+ try {
82
+ if (message.type !== 'EVENT' || !message.data)
83
+ return;
84
+ const event = message.data;
85
+ if (event.kind !== ChatEventKinds.CHANNEL_MESSAGE)
86
+ return;
87
+ // Extract channel ID and reply ID from tags
88
+ const tags = event.tags;
89
+ const channelId = tags.find(tag => tag[0] === 'e' && tag[3] === 'root')?.[1];
90
+ const replyId = tags.find(tag => tag[0] === 'e' && tag[3] === 'reply')?.[1];
91
+ // Process message
92
+ logger.debug('Processing chat message', {
93
+ channelId,
94
+ replyId,
95
+ content: event.content
96
+ });
97
+ // Additional message processing logic here
98
+ }
99
+ catch (error) {
100
+ logger.error('Error handling chat message:', error);
101
+ }
102
+ },
103
+ async handleModeration(message) {
104
+ try {
105
+ if (message.type !== 'EVENT' || !message.data)
106
+ return;
107
+ const event = message.data;
108
+ if (event.kind !== ChatEventKinds.CHANNEL_HIDE_MESSAGE)
109
+ return;
110
+ // Extract channel and message IDs
111
+ const tags = event.tags;
112
+ const channelId = tags.find(tag => tag[0] === 'e' && tag[3] === 'root')?.[1];
113
+ const messageId = tags.find(tag => tag[0] === 'e' && tag[3] === 'reply')?.[1];
114
+ // Process moderation
115
+ logger.debug('Processing message moderation', {
116
+ channelId,
117
+ messageId,
118
+ reason: event.content
119
+ });
120
+ // Additional moderation logic here
121
+ }
122
+ catch (error) {
123
+ logger.error('Error handling message moderation:', error);
124
+ }
125
+ }
126
+ };
127
+ }
128
+ /**
129
+ * Creates a channel subscription manager
130
+ * @param logger - Logger instance
131
+ * @returns {ChannelSubscriptionManager} Subscription manager
132
+ */
133
+ export function createChannelSubscriptionManager(logger) {
134
+ const subscriptions = new Map(); // channelId -> subscriptionId
135
+ const metadata = new Map();
136
+ return {
137
+ subscribe(channelId) {
138
+ const subscriptionId = `chat:${channelId}:${Date.now()}`;
139
+ subscriptions.set(channelId, subscriptionId);
140
+ return {
141
+ type: 'REQ',
142
+ data: {
143
+ subscription_id: subscriptionId,
144
+ filter: {
145
+ kinds: [
146
+ ChatEventKinds.CHANNEL_MESSAGE,
147
+ ChatEventKinds.CHANNEL_HIDE_MESSAGE
148
+ ],
149
+ '#e': [channelId]
150
+ }
151
+ }
152
+ };
153
+ },
154
+ unsubscribe(channelId) {
155
+ const subscriptionId = subscriptions.get(channelId);
156
+ if (!subscriptionId) {
157
+ logger.debug(`No subscription found for channel ${channelId}`);
158
+ return { type: 'CLOSE', data: { subscription_id: '' } };
159
+ }
160
+ subscriptions.delete(channelId);
161
+ return {
162
+ type: 'CLOSE',
163
+ data: { subscription_id: subscriptionId }
164
+ };
165
+ },
166
+ async getMetadata(channelId) {
167
+ return metadata.get(channelId);
168
+ }
169
+ };
170
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @file NIP-33: Parameterized Replaceable Events
3
+ * @module nips/nip-33
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/33.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Parameterized replaceable event kinds (30000-39999)
10
+ */
11
+ export declare const PARAMETERIZED_REPLACEABLE_KINDS: {
12
+ CATEGORIZED_BOOKMARK: number;
13
+ CATEGORIZED_HIGHLIGHT: number;
14
+ CATEGORIZED_PEOPLE_LIST: number;
15
+ PROFILE_BADGES: number;
16
+ BADGE_DEFINITION: number;
17
+ CUSTOM_START: number;
18
+ CUSTOM_END: number;
19
+ };
20
+ /**
21
+ * Creates a parameterized replaceable event
22
+ * @param kind - Event kind
23
+ * @param content - Event content
24
+ * @param identifier - Unique identifier for the parameter
25
+ * @param additionalTags - Additional tags
26
+ * @returns {NostrWSMessage} Parameterized replaceable event
27
+ */
28
+ export declare function createParameterizedEvent(kind: number, content: string, identifier: string, additionalTags?: string[][]): NostrWSMessage;
29
+ /**
30
+ * Validates a parameterized replaceable event
31
+ * @param message - Message to validate
32
+ * @param _logger - Logger instance
33
+ * @returns {boolean} True if valid
34
+ */
35
+ export declare function validateParameterizedEvent(message: NostrWSMessage, _logger: Logger): boolean;
36
+ /**
37
+ * Parameterized event manager interface
38
+ */
39
+ export interface ParameterizedEventManager {
40
+ /**
41
+ * Creates a new parameterized event
42
+ * @param kind - Event kind
43
+ * @param content - Event content
44
+ * @param identifier - Parameter identifier
45
+ * @param tags - Additional tags
46
+ * @returns {NostrWSMessage} Created event
47
+ */
48
+ createEvent(kind: number, content: string, identifier: string, tags?: string[][]): NostrWSMessage;
49
+ /**
50
+ * Updates an existing parameterized event
51
+ * @param kind - Event kind
52
+ * @param identifier - Parameter identifier
53
+ * @param content - New content
54
+ * @returns {NostrWSMessage} Update event
55
+ */
56
+ updateEvent(kind: number, identifier: string, content: string): NostrWSMessage;
57
+ /**
58
+ * Subscribes to parameterized events
59
+ * @param kinds - Event kinds to subscribe to
60
+ * @param identifiers - Parameter identifiers
61
+ * @returns {NostrWSMessage} Subscription message
62
+ */
63
+ subscribe(kinds: number[], identifiers: string[]): NostrWSMessage;
64
+ }
65
+ /**
66
+ * Creates a parameterized event manager
67
+ * @param _logger - Logger instance
68
+ * @returns {ParameterizedEventManager} Event manager
69
+ */
70
+ export declare function createParameterizedEventManager(_logger: Logger): ParameterizedEventManager;
71
+ /**
72
+ * Event replacement handler interface
73
+ */
74
+ export interface EventReplacementHandler {
75
+ /**
76
+ * Checks if an event should replace another
77
+ * @param newEvent - New event
78
+ * @param existingEvent - Existing event
79
+ * @returns {boolean} True if should replace
80
+ */
81
+ shouldReplace(newEvent: Record<string, unknown>, existingEvent: Record<string, unknown>): boolean;
82
+ /**
83
+ * Gets replacement key for an event
84
+ * @param event - Event to get key for
85
+ * @returns {string} Replacement key
86
+ */
87
+ getReplacementKey(event: Record<string, unknown>): string;
88
+ }
89
+ /**
90
+ * Creates an event replacement handler
91
+ * @param _logger - Logger instance
92
+ * @returns {EventReplacementHandler} Replacement handler
93
+ */
94
+ export declare function createEventReplacementHandler(_logger: Logger): EventReplacementHandler;
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @file NIP-33: Parameterized Replaceable Events
3
+ * @module nips/nip-33
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/33.md
5
+ */
6
+ /**
7
+ * Parameterized replaceable event kinds (30000-39999)
8
+ */
9
+ export const PARAMETERIZED_REPLACEABLE_KINDS = {
10
+ CATEGORIZED_BOOKMARK: 30000,
11
+ CATEGORIZED_HIGHLIGHT: 30001,
12
+ CATEGORIZED_PEOPLE_LIST: 30002,
13
+ PROFILE_BADGES: 30008,
14
+ BADGE_DEFINITION: 30009,
15
+ CUSTOM_START: 31000,
16
+ CUSTOM_END: 39999
17
+ };
18
+ /**
19
+ * Creates a parameterized replaceable event
20
+ * @param kind - Event kind
21
+ * @param content - Event content
22
+ * @param identifier - Unique identifier for the parameter
23
+ * @param additionalTags - Additional tags
24
+ * @returns {NostrWSMessage} Parameterized replaceable event
25
+ */
26
+ export function createParameterizedEvent(kind, content, identifier, additionalTags = []) {
27
+ return {
28
+ type: 'EVENT',
29
+ data: {
30
+ kind,
31
+ content,
32
+ tags: [
33
+ ['d', identifier],
34
+ ...additionalTags
35
+ ]
36
+ }
37
+ };
38
+ }
39
+ /**
40
+ * Validates a parameterized replaceable event
41
+ * @param message - Message to validate
42
+ * @param _logger - Logger instance
43
+ * @returns {boolean} True if valid
44
+ */
45
+ export function validateParameterizedEvent(message, _logger) {
46
+ try {
47
+ if (message.type !== 'EVENT' || !message.data) {
48
+ return false;
49
+ }
50
+ const event = message.data;
51
+ const kind = event.kind;
52
+ // Check if kind is in valid range
53
+ if (kind < 30000 || kind > 39999) {
54
+ _logger.debug('Invalid kind for parameterized replaceable event');
55
+ return false;
56
+ }
57
+ // Must have exactly one 'd' tag
58
+ if (!Array.isArray(event.tags)) {
59
+ _logger.debug('Missing tags array');
60
+ return false;
61
+ }
62
+ const dTags = event.tags.filter(tag => Array.isArray(tag) && tag[0] === 'd' && tag[1]);
63
+ if (dTags.length !== 1) {
64
+ _logger.debug('Must have exactly one d tag');
65
+ return false;
66
+ }
67
+ return true;
68
+ }
69
+ catch (error) {
70
+ _logger.error('Error validating parameterized event:', error);
71
+ return false;
72
+ }
73
+ }
74
+ /**
75
+ * Creates a parameterized event manager
76
+ * @param _logger - Logger instance
77
+ * @returns {ParameterizedEventManager} Event manager
78
+ */
79
+ export function createParameterizedEventManager(_logger) {
80
+ return {
81
+ createEvent(kind, content, identifier, tags = []) {
82
+ return createParameterizedEvent(kind, content, identifier, tags);
83
+ },
84
+ updateEvent(kind, identifier, content) {
85
+ return createParameterizedEvent(kind, content, identifier);
86
+ },
87
+ subscribe(kinds, identifiers) {
88
+ return {
89
+ type: 'REQ',
90
+ data: {
91
+ filter: {
92
+ kinds,
93
+ '#d': identifiers
94
+ }
95
+ }
96
+ };
97
+ }
98
+ };
99
+ }
100
+ /**
101
+ * Creates an event replacement handler
102
+ * @param _logger - Logger instance
103
+ * @returns {EventReplacementHandler} Replacement handler
104
+ */
105
+ export function createEventReplacementHandler(_logger) {
106
+ return {
107
+ shouldReplace(newEvent, existingEvent) {
108
+ try {
109
+ // Must be same kind
110
+ if (newEvent.kind !== existingEvent.kind)
111
+ return false;
112
+ // Must have same 'd' tag value
113
+ const newTags = newEvent.tags;
114
+ const existingTags = existingEvent.tags;
115
+ const newDTag = newTags?.find(tag => tag[0] === 'd')?.[1];
116
+ const existingDTag = existingTags?.find(tag => tag[0] === 'd')?.[1];
117
+ if (newDTag !== existingDTag)
118
+ return false;
119
+ // Replace if newer
120
+ return newEvent.created_at > existingEvent.created_at;
121
+ }
122
+ catch (error) {
123
+ _logger.error('Error checking event replacement:', error);
124
+ return false;
125
+ }
126
+ },
127
+ getReplacementKey(event) {
128
+ const kind = event.kind;
129
+ const dTag = event.tags?.find(tag => tag[0] === 'd')?.[1];
130
+ return `${kind}:${dTag}`;
131
+ }
132
+ };
133
+ }
@@ -1,8 +1,31 @@
1
1
  import { NostrWSServerOptions } from './types/nostr';
2
+ /**
3
+ * Represents a Nostr WebSocket server
4
+ */
2
5
  export declare class NostrWSServer {
6
+ /**
7
+ * The underlying WebSocket server instance
8
+ */
3
9
  private server;
10
+ /**
11
+ * Logger instance for this server
12
+ */
4
13
  private logger;
14
+ /**
15
+ * Creates a new Nostr WebSocket server instance
16
+ *
17
+ * @param {NostrWSServerOptions} options - Server configuration options
18
+ */
5
19
  constructor(options: NostrWSServerOptions);
20
+ /**
21
+ * Closes the WebSocket server
22
+ */
6
23
  close(): void;
7
24
  }
25
+ /**
26
+ * Creates a new Nostr WebSocket server instance
27
+ *
28
+ * @param {NostrWSServerOptions} options - Server configuration options
29
+ * @returns {NostrWSServer} The created server instance
30
+ */
8
31
  export declare function createWSServer(options: NostrWSServerOptions): NostrWSServer;
@@ -1,15 +1,36 @@
1
1
  import { Server as WebSocketServer } from 'ws';
2
2
  import { getLogger } from './utils/logger';
3
+ /**
4
+ * Represents a Nostr WebSocket server
5
+ */
3
6
  export class NostrWSServer {
7
+ /**
8
+ * Creates a new Nostr WebSocket server instance
9
+ *
10
+ * @param {NostrWSServerOptions} options - Server configuration options
11
+ */
4
12
  constructor(options) {
13
+ /**
14
+ * Logger instance for this server
15
+ */
5
16
  this.logger = getLogger('NostrWSServer');
6
17
  this.server = new WebSocketServer({ port: options.port });
18
+ /**
19
+ * Handles incoming WebSocket connections
20
+ *
21
+ * @param {NostrWSSocket} socket - The connected WebSocket client
22
+ */
7
23
  this.server.on('connection', (socket) => {
8
24
  socket.subscriptions = new Set();
9
25
  socket.isAlive = true;
10
26
  if (options.onConnection) {
11
27
  options.onConnection(socket);
12
28
  }
29
+ /**
30
+ * Handles incoming messages from the client
31
+ *
32
+ * @param {Buffer | ArrayBuffer | Buffer[]} data - The incoming message data
33
+ */
13
34
  const handleMessage = async (data) => {
14
35
  try {
15
36
  const message = JSON.parse(data.toString());
@@ -24,11 +45,19 @@ export class NostrWSServer {
24
45
  }
25
46
  };
26
47
  socket.on('message', handleMessage);
48
+ /**
49
+ * Handles client disconnections
50
+ */
27
51
  socket.on('close', () => {
28
52
  if (options.handlers?.close) {
29
53
  options.handlers.close(socket);
30
54
  }
31
55
  });
56
+ /**
57
+ * Handles WebSocket errors
58
+ *
59
+ * @param {Error} error - The error that occurred
60
+ */
32
61
  socket.on('error', (error) => {
33
62
  if (options.handlers?.error) {
34
63
  options.handlers.error(socket, error);
@@ -45,19 +74,34 @@ export class NostrWSServer {
45
74
  socket.isAlive = false;
46
75
  socket.ping();
47
76
  }, options.heartbeatInterval);
77
+ /**
78
+ * Handles WebSocket pong responses
79
+ */
48
80
  socket.on('pong', () => {
49
81
  socket.isAlive = true;
50
82
  });
83
+ /**
84
+ * Handles client disconnections (again, to clear the interval)
85
+ */
51
86
  socket.on('close', () => {
52
87
  clearInterval(interval);
53
88
  });
54
89
  }
55
90
  });
56
91
  }
92
+ /**
93
+ * Closes the WebSocket server
94
+ */
57
95
  close() {
58
96
  this.server.close();
59
97
  }
60
98
  }
99
+ /**
100
+ * Creates a new Nostr WebSocket server instance
101
+ *
102
+ * @param {NostrWSServerOptions} options - Server configuration options
103
+ * @returns {NostrWSServer} The created server instance
104
+ */
61
105
  export function createWSServer(options) {
62
106
  return new NostrWSServer(options);
63
107
  }
package/dist/server.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  /// <reference types="node" />
2
- import { EventEmitter } from 'events';
3
2
  import { WebSocketServer } from 'ws';
4
- import type { NostrWSOptions, NostrWSMessage, ExtendedWebSocket } from './types/index.js';
3
+ import { EventEmitter } from 'events';
4
+ import type { NostrWSOptions, NostrWSMessage } from './types/index.js';
5
+ /**
6
+ * WebSocket server implementation for Nostr protocol
7
+ * Extends EventEmitter to provide event-based message handling
8
+ */
5
9
  export declare class NostrWSServer extends EventEmitter {
6
10
  private wss;
7
11
  private options;
12
+ private clients;
8
13
  private heartbeatInterval;
9
- clients: Map<string, ExtendedWebSocket>;
10
14
  constructor(wss: WebSocketServer, options?: Partial<NostrWSOptions>);
11
15
  private setupServer;
12
16
  private handleConnection;
@@ -14,4 +18,10 @@ export declare class NostrWSServer extends EventEmitter {
14
18
  broadcast(message: NostrWSMessage): void;
15
19
  broadcastToChannel(channel: string, message: NostrWSMessage): void;
16
20
  close(): void;
21
+ /**
22
+ * Check if a client with the given ID exists
23
+ * @param clientId - The ID of the client to check
24
+ * @returns boolean indicating if the client exists
25
+ */
26
+ hasClient(clientId: string): boolean;
17
27
  }