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,73 @@
1
+ /**
2
+ * @file NIP-13: Proof of Work
3
+ * @module nips/nip-13
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/13.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Calculates the number of leading zero bits in a hex string
10
+ * @param hex - Hex string to check
11
+ * @returns {number} Number of leading zero bits
12
+ */
13
+ export declare function countLeadingZeroBits(hex: string): number;
14
+ /**
15
+ * Calculates event ID with proof of work
16
+ * @param event - Event object without ID
17
+ * @param targetDifficulty - Target number of leading zero bits
18
+ * @param maxAttempts - Maximum number of attempts
19
+ * @returns {Promise<string>} Event ID with sufficient proof of work
20
+ */
21
+ export declare function calculatePowEventId(event: Record<string, unknown>, targetDifficulty: number, maxAttempts?: number): Promise<string>;
22
+ /**
23
+ * Validates proof of work for an event
24
+ * @param message - Message containing event
25
+ * @param minDifficulty - Minimum required difficulty
26
+ * @param logger - Logger instance
27
+ * @returns {boolean} True if proof of work is valid
28
+ */
29
+ export declare function validateEventPoW(message: NostrWSMessage, minDifficulty: number, logger: Logger): boolean;
30
+ /**
31
+ * Dynamic difficulty calculator based on event type and content
32
+ */
33
+ export interface DifficultyCalculator {
34
+ /**
35
+ * Calculates required difficulty for an event
36
+ * @param event - Event to check
37
+ * @returns {number} Required number of leading zero bits
38
+ */
39
+ calculateRequiredDifficulty(event: Record<string, unknown>): number;
40
+ }
41
+ /**
42
+ * Creates a default difficulty calculator
43
+ * @param baseDifficulty - Base difficulty for all events
44
+ * @param contentMultiplier - Multiplier based on content length
45
+ * @returns {DifficultyCalculator} Difficulty calculator
46
+ */
47
+ export declare function createDifficultyCalculator(baseDifficulty?: number, contentMultiplier?: number): DifficultyCalculator;
48
+ /**
49
+ * Rate limiter interface for proof of work
50
+ */
51
+ export interface PowRateLimiter {
52
+ /**
53
+ * Checks if an event should be rate limited
54
+ * @param pubkey - Publisher's public key
55
+ * @param currentTime - Current timestamp
56
+ * @returns {boolean} True if should be rate limited
57
+ */
58
+ shouldRateLimit(pubkey: string, currentTime: number): boolean;
59
+ /**
60
+ * Records an event for rate limiting
61
+ * @param pubkey - Publisher's public key
62
+ * @param difficulty - Event difficulty
63
+ * @param currentTime - Current timestamp
64
+ */
65
+ recordEvent(pubkey: string, difficulty: number, currentTime: number): void;
66
+ }
67
+ /**
68
+ * Creates a default PoW rate limiter
69
+ * @param windowSeconds - Time window for rate limiting
70
+ * @param maxDifficulty - Maximum cumulative difficulty per window
71
+ * @returns {PowRateLimiter} Rate limiter
72
+ */
73
+ export declare function createPowRateLimiter(windowSeconds?: number, maxDifficulty?: number): PowRateLimiter;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * @file NIP-13: Proof of Work
3
+ * @module nips/nip-13
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/13.md
5
+ */
6
+ import { createHash } from 'crypto';
7
+ /**
8
+ * Calculates the number of leading zero bits in a hex string
9
+ * @param hex - Hex string to check
10
+ * @returns {number} Number of leading zero bits
11
+ */
12
+ export function countLeadingZeroBits(hex) {
13
+ let count = 0;
14
+ for (const char of hex) {
15
+ const nibble = parseInt(char, 16);
16
+ if (nibble === 0) {
17
+ count += 4;
18
+ }
19
+ else {
20
+ count += Math.clz32(nibble) - 28;
21
+ break;
22
+ }
23
+ }
24
+ return count;
25
+ }
26
+ /**
27
+ * Calculates event ID with proof of work
28
+ * @param event - Event object without ID
29
+ * @param targetDifficulty - Target number of leading zero bits
30
+ * @param maxAttempts - Maximum number of attempts
31
+ * @returns {Promise<string>} Event ID with sufficient proof of work
32
+ */
33
+ export async function calculatePowEventId(event, targetDifficulty, maxAttempts = 1000000) {
34
+ let nonce = 0;
35
+ const eventCopy = { ...event };
36
+ while (nonce < maxAttempts) {
37
+ eventCopy.nonce = nonce.toString();
38
+ const serialized = JSON.stringify([
39
+ 0,
40
+ eventCopy.pubkey,
41
+ eventCopy.created_at,
42
+ eventCopy.kind,
43
+ eventCopy.tags,
44
+ eventCopy.content,
45
+ eventCopy.nonce
46
+ ]);
47
+ const hash = createHash('sha256').update(serialized).digest('hex');
48
+ const difficulty = countLeadingZeroBits(hash);
49
+ if (difficulty >= targetDifficulty) {
50
+ return hash;
51
+ }
52
+ nonce++;
53
+ }
54
+ throw new Error('Failed to find proof of work within maximum attempts');
55
+ }
56
+ /**
57
+ * Validates proof of work for an event
58
+ * @param message - Message containing event
59
+ * @param minDifficulty - Minimum required difficulty
60
+ * @param logger - Logger instance
61
+ * @returns {boolean} True if proof of work is valid
62
+ */
63
+ export function validateEventPoW(message, minDifficulty, logger) {
64
+ try {
65
+ if (message.type !== 'EVENT' || !message.data) {
66
+ return true; // Not an event message
67
+ }
68
+ const event = message.data;
69
+ if (!event.id || typeof event.id !== 'string') {
70
+ logger.debug('Missing event ID');
71
+ return false;
72
+ }
73
+ // Calculate difficulty
74
+ const difficulty = countLeadingZeroBits(event.id);
75
+ return difficulty >= minDifficulty;
76
+ }
77
+ catch (error) {
78
+ logger.error('Error validating proof of work:', error);
79
+ return false;
80
+ }
81
+ }
82
+ /**
83
+ * Creates a default difficulty calculator
84
+ * @param baseDifficulty - Base difficulty for all events
85
+ * @param contentMultiplier - Multiplier based on content length
86
+ * @returns {DifficultyCalculator} Difficulty calculator
87
+ */
88
+ export function createDifficultyCalculator(baseDifficulty = 8, contentMultiplier = 0.001) {
89
+ return {
90
+ calculateRequiredDifficulty(event) {
91
+ let difficulty = baseDifficulty;
92
+ // Increase difficulty for larger content
93
+ if (typeof event.content === 'string') {
94
+ difficulty += Math.floor(event.content.length * contentMultiplier);
95
+ }
96
+ // Increase difficulty for certain event kinds
97
+ const kind = event.kind;
98
+ if (kind >= 1000) {
99
+ difficulty += 4; // Higher difficulty for application-specific events
100
+ }
101
+ return difficulty;
102
+ }
103
+ };
104
+ }
105
+ /**
106
+ * Creates a default PoW rate limiter
107
+ * @param windowSeconds - Time window for rate limiting
108
+ * @param maxDifficulty - Maximum cumulative difficulty per window
109
+ * @returns {PowRateLimiter} Rate limiter
110
+ */
111
+ export function createPowRateLimiter(windowSeconds = 3600, maxDifficulty = 100) {
112
+ const difficulties = new Map();
113
+ return {
114
+ shouldRateLimit(pubkey, currentTime) {
115
+ const events = difficulties.get(pubkey) || [];
116
+ // Remove old events
117
+ const validEvents = events.filter(([time]) => currentTime - time < windowSeconds);
118
+ // Calculate cumulative difficulty
119
+ const totalDifficulty = validEvents.reduce((sum, [, diff]) => sum + diff, 0);
120
+ return totalDifficulty >= maxDifficulty;
121
+ },
122
+ recordEvent(pubkey, difficulty, currentTime) {
123
+ const events = difficulties.get(pubkey) || [];
124
+ events.push([currentTime, difficulty]);
125
+ difficulties.set(pubkey, events);
126
+ }
127
+ };
128
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @file NIP-15: End of Stored Events Notice
3
+ * @module nips/nip-15
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/15.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Creates an EOSE (End of Stored Events) message
10
+ * @param subscriptionId - Subscription ID
11
+ * @returns {NostrWSMessage} EOSE message
12
+ */
13
+ export declare function createEOSEMessage(subscriptionId: string): NostrWSMessage;
14
+ /**
15
+ * Subscription state manager interface
16
+ */
17
+ export interface SubscriptionStateManager {
18
+ /**
19
+ * Registers a new subscription
20
+ * @param subscriptionId - Subscription ID
21
+ * @param filter - Subscription filter
22
+ */
23
+ registerSubscription(subscriptionId: string, filter: Record<string, unknown>): void;
24
+ /**
25
+ * Marks a subscription as complete (EOSE sent)
26
+ * @param subscriptionId - Subscription ID
27
+ */
28
+ markComplete(subscriptionId: string): void;
29
+ /**
30
+ * Checks if a subscription is complete
31
+ * @param subscriptionId - Subscription ID
32
+ * @returns {boolean} True if EOSE has been sent
33
+ */
34
+ isComplete(subscriptionId: string): boolean;
35
+ /**
36
+ * Gets subscription filter
37
+ * @param subscriptionId - Subscription ID
38
+ * @returns {Record<string, unknown> | undefined} Subscription filter
39
+ */
40
+ getFilter(subscriptionId: string): Record<string, unknown> | undefined;
41
+ /**
42
+ * Removes a subscription
43
+ * @param subscriptionId - Subscription ID
44
+ */
45
+ removeSubscription(subscriptionId: string): void;
46
+ }
47
+ /**
48
+ * Creates a subscription state manager
49
+ * @param logger - Logger instance
50
+ * @returns {SubscriptionStateManager} Subscription state manager
51
+ */
52
+ export declare function createSubscriptionStateManager(logger: Logger): SubscriptionStateManager;
53
+ /**
54
+ * Pagination handler interface
55
+ */
56
+ export interface PaginationHandler {
57
+ /**
58
+ * Gets next page of events
59
+ * @param subscriptionId - Subscription ID
60
+ * @param pageSize - Number of events per page
61
+ * @returns {Promise<NostrWSMessage[]>} Next page of events
62
+ */
63
+ getNextPage(subscriptionId: string, pageSize: number): Promise<NostrWSMessage[]>;
64
+ /**
65
+ * Checks if more events are available
66
+ * @param subscriptionId - Subscription ID
67
+ * @returns {boolean} True if more events exist
68
+ */
69
+ hasMoreEvents(subscriptionId: string): boolean;
70
+ /**
71
+ * Updates pagination state with new events
72
+ * @param subscriptionId - Subscription ID
73
+ * @param events - New events
74
+ */
75
+ updateState(subscriptionId: string, events: NostrWSMessage[]): void;
76
+ }
77
+ /**
78
+ * Creates a pagination handler
79
+ * @param stateManager - Subscription state manager
80
+ * @param logger - Logger instance
81
+ * @returns {PaginationHandler} Pagination handler
82
+ */
83
+ export declare function createPaginationHandler(stateManager: SubscriptionStateManager, logger: Logger): PaginationHandler;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @file NIP-15: End of Stored Events Notice
3
+ * @module nips/nip-15
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/15.md
5
+ */
6
+ /**
7
+ * Creates an EOSE (End of Stored Events) message
8
+ * @param subscriptionId - Subscription ID
9
+ * @returns {NostrWSMessage} EOSE message
10
+ */
11
+ export function createEOSEMessage(subscriptionId) {
12
+ return {
13
+ type: 'EOSE',
14
+ data: {
15
+ subscription_id: subscriptionId
16
+ }
17
+ };
18
+ }
19
+ /**
20
+ * Creates a subscription state manager
21
+ * @param logger - Logger instance
22
+ * @returns {SubscriptionStateManager} Subscription state manager
23
+ */
24
+ export function createSubscriptionStateManager(logger) {
25
+ const subscriptions = new Map();
26
+ return {
27
+ registerSubscription(subscriptionId, filter) {
28
+ subscriptions.set(subscriptionId, {
29
+ filter,
30
+ complete: false,
31
+ timestamp: Date.now()
32
+ });
33
+ },
34
+ markComplete(subscriptionId) {
35
+ const state = subscriptions.get(subscriptionId);
36
+ if (state) {
37
+ state.complete = true;
38
+ }
39
+ else {
40
+ logger.debug(`Attempting to mark unknown subscription ${subscriptionId} as complete`);
41
+ }
42
+ },
43
+ isComplete(subscriptionId) {
44
+ return subscriptions.get(subscriptionId)?.complete || false;
45
+ },
46
+ getFilter(subscriptionId) {
47
+ return subscriptions.get(subscriptionId)?.filter;
48
+ },
49
+ removeSubscription(subscriptionId) {
50
+ subscriptions.delete(subscriptionId);
51
+ }
52
+ };
53
+ }
54
+ /**
55
+ * Creates a pagination handler
56
+ * @param stateManager - Subscription state manager
57
+ * @param logger - Logger instance
58
+ * @returns {PaginationHandler} Pagination handler
59
+ */
60
+ export function createPaginationHandler(stateManager, logger) {
61
+ const paginationStates = new Map();
62
+ return {
63
+ async getNextPage(subscriptionId, pageSize) {
64
+ const state = paginationStates.get(subscriptionId);
65
+ if (!state) {
66
+ logger.debug(`No pagination state for subscription ${subscriptionId}`);
67
+ return [];
68
+ }
69
+ const start = state.currentIndex;
70
+ const end = Math.min(start + pageSize, state.events.length);
71
+ state.currentIndex = end;
72
+ state.hasMore = end < state.events.length;
73
+ // If this is the last page, send EOSE
74
+ if (!state.hasMore && !stateManager.isComplete(subscriptionId)) {
75
+ stateManager.markComplete(subscriptionId);
76
+ return [
77
+ ...state.events.slice(start, end),
78
+ createEOSEMessage(subscriptionId)
79
+ ];
80
+ }
81
+ return state.events.slice(start, end);
82
+ },
83
+ hasMoreEvents(subscriptionId) {
84
+ return paginationStates.get(subscriptionId)?.hasMore || false;
85
+ },
86
+ updateState(subscriptionId, events) {
87
+ const existing = paginationStates.get(subscriptionId);
88
+ if (existing) {
89
+ existing.events.push(...events);
90
+ existing.hasMore = true;
91
+ }
92
+ else {
93
+ paginationStates.set(subscriptionId, {
94
+ events,
95
+ currentIndex: 0,
96
+ hasMore: true
97
+ });
98
+ }
99
+ }
100
+ };
101
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @file NIP-16: Event Treatment
3
+ * @module nips/nip-16
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/16.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Event treatment types
10
+ */
11
+ export declare const EventTreatment: {
12
+ readonly EPHEMERAL: "ephemeral";
13
+ readonly REPLACEABLE: "replaceable";
14
+ readonly PERSISTENT: "persistent";
15
+ };
16
+ export type EventTreatmentType = typeof EventTreatment[keyof typeof EventTreatment];
17
+ /**
18
+ * Replaceable event kinds (10000-19999)
19
+ */
20
+ export declare const REPLACEABLE_EVENT_KINDS: {
21
+ METADATA: number;
22
+ CONTACT_LIST: number;
23
+ CHANNEL_METADATA: number;
24
+ CHANNEL_MESSAGE: number;
25
+ USER_STATUS: number;
26
+ USER_PROFILE: number;
27
+ RELAY_LIST: number;
28
+ BOOKMARKS: number;
29
+ MUTE_LIST: number;
30
+ PIN_LIST: number;
31
+ RELAY_METADATA: number;
32
+ CLIENT_PREFERENCES: number;
33
+ CUSTOM_START: number;
34
+ CUSTOM_END: number;
35
+ };
36
+ /**
37
+ * Ephemeral event kinds (20000-29999)
38
+ */
39
+ export declare const EPHEMERAL_EVENT_KINDS: {
40
+ TYPING_INDICATOR: number;
41
+ ONLINE_STATUS: number;
42
+ USER_PRESENCE: number;
43
+ CUSTOM_START: number;
44
+ CUSTOM_END: number;
45
+ };
46
+ /**
47
+ * Determines event treatment type
48
+ * @param eventKind - Event kind number
49
+ * @returns {EventTreatmentType} Event treatment type
50
+ */
51
+ export declare function getEventTreatment(eventKind: number): EventTreatmentType;
52
+ /**
53
+ * Validates event treatment rules
54
+ * @param message - Message to validate
55
+ * @param logger - Logger instance
56
+ * @returns {boolean} True if event follows treatment rules
57
+ */
58
+ export declare function validateEventTreatment(message: NostrWSMessage, logger: Logger): boolean;
59
+ /**
60
+ * Storage management interface for different event types
61
+ */
62
+ export interface EventStorageManager {
63
+ /**
64
+ * Determines if an event should be stored
65
+ * @param event - Event to check
66
+ * @returns {boolean} True if event should be stored
67
+ */
68
+ shouldStore(event: Record<string, unknown>): boolean;
69
+ /**
70
+ * Gets storage duration for an event
71
+ * @param event - Event to check
72
+ * @returns {number} Storage duration in seconds (0 for permanent)
73
+ */
74
+ getStorageDuration(event: Record<string, unknown>): number;
75
+ /**
76
+ * Checks if an event should replace another
77
+ * @param newEvent - New event
78
+ * @param existingEvent - Existing event
79
+ * @returns {boolean} True if new event should replace existing
80
+ */
81
+ shouldReplace(newEvent: Record<string, unknown>, existingEvent: Record<string, unknown>): boolean;
82
+ }
83
+ /**
84
+ * Creates default event storage manager
85
+ * @param logger - Logger instance
86
+ * @returns {EventStorageManager} Storage manager
87
+ */
88
+ export declare function createEventStorageManager(logger: Logger): EventStorageManager;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @file NIP-16: Event Treatment
3
+ * @module nips/nip-16
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/16.md
5
+ */
6
+ /**
7
+ * Event treatment types
8
+ */
9
+ export const EventTreatment = {
10
+ EPHEMERAL: 'ephemeral',
11
+ REPLACEABLE: 'replaceable',
12
+ PERSISTENT: 'persistent'
13
+ };
14
+ /**
15
+ * Replaceable event kinds (10000-19999)
16
+ */
17
+ export const REPLACEABLE_EVENT_KINDS = {
18
+ METADATA: 0,
19
+ CONTACT_LIST: 3,
20
+ CHANNEL_METADATA: 41,
21
+ CHANNEL_MESSAGE: 42,
22
+ USER_STATUS: 10000,
23
+ USER_PROFILE: 10001,
24
+ RELAY_LIST: 10002,
25
+ BOOKMARKS: 10003,
26
+ MUTE_LIST: 10004,
27
+ PIN_LIST: 10005,
28
+ RELAY_METADATA: 10010,
29
+ CLIENT_PREFERENCES: 10015,
30
+ CUSTOM_START: 11000,
31
+ CUSTOM_END: 19999
32
+ };
33
+ /**
34
+ * Ephemeral event kinds (20000-29999)
35
+ */
36
+ export const EPHEMERAL_EVENT_KINDS = {
37
+ TYPING_INDICATOR: 20001,
38
+ ONLINE_STATUS: 20002,
39
+ USER_PRESENCE: 20003,
40
+ CUSTOM_START: 21000,
41
+ CUSTOM_END: 29999
42
+ };
43
+ /**
44
+ * Determines event treatment type
45
+ * @param eventKind - Event kind number
46
+ * @returns {EventTreatmentType} Event treatment type
47
+ */
48
+ export function getEventTreatment(eventKind) {
49
+ if (eventKind >= 20000 && eventKind < 30000) {
50
+ return EventTreatment.EPHEMERAL;
51
+ }
52
+ if (eventKind >= 10000 && eventKind < 20000 || [0, 3, 41, 42].includes(eventKind)) {
53
+ return EventTreatment.REPLACEABLE;
54
+ }
55
+ return EventTreatment.PERSISTENT;
56
+ }
57
+ /**
58
+ * Validates event treatment rules
59
+ * @param message - Message to validate
60
+ * @param logger - Logger instance
61
+ * @returns {boolean} True if event follows treatment rules
62
+ */
63
+ export function validateEventTreatment(message, logger) {
64
+ try {
65
+ if (message.type !== 'EVENT' || !message.data) {
66
+ return true; // Not an event message
67
+ }
68
+ const event = message.data;
69
+ const kind = event.kind;
70
+ const treatment = getEventTreatment(kind);
71
+ // Validate based on treatment type
72
+ switch (treatment) {
73
+ case EventTreatment.EPHEMERAL:
74
+ // Ephemeral events shouldn't have large content
75
+ if (typeof event.content === 'string' && event.content.length > 1000) {
76
+ logger.debug('Ephemeral event content too large');
77
+ return false;
78
+ }
79
+ break;
80
+ case EventTreatment.REPLACEABLE:
81
+ // Replaceable events must have proper 'd' tag for uniqueness
82
+ if (Array.isArray(event.tags)) {
83
+ const dTags = event.tags.filter(tag => Array.isArray(tag) && tag[0] === 'd');
84
+ if (dTags.length > 1) {
85
+ logger.debug('Multiple d tags in replaceable event');
86
+ return false;
87
+ }
88
+ }
89
+ break;
90
+ }
91
+ return true;
92
+ }
93
+ catch (error) {
94
+ logger.error('Error validating event treatment:', error);
95
+ return false;
96
+ }
97
+ }
98
+ /**
99
+ * Creates default event storage manager
100
+ * @param logger - Logger instance
101
+ * @returns {EventStorageManager} Storage manager
102
+ */
103
+ export function createEventStorageManager(logger) {
104
+ return {
105
+ shouldStore(event) {
106
+ const kind = event.kind;
107
+ const treatment = getEventTreatment(kind);
108
+ // Don't store ephemeral events by default
109
+ if (treatment === EventTreatment.EPHEMERAL) {
110
+ return false;
111
+ }
112
+ return true;
113
+ },
114
+ getStorageDuration(event) {
115
+ const kind = event.kind;
116
+ const treatment = getEventTreatment(kind);
117
+ switch (treatment) {
118
+ case EventTreatment.EPHEMERAL:
119
+ return 300; // 5 minutes
120
+ case EventTreatment.REPLACEABLE:
121
+ return 0; // Permanent until replaced
122
+ default:
123
+ return 0; // Permanent
124
+ }
125
+ },
126
+ shouldReplace(newEvent, existingEvent) {
127
+ try {
128
+ const kind = newEvent.kind;
129
+ if (kind !== existingEvent.kind)
130
+ return false;
131
+ const treatment = getEventTreatment(kind);
132
+ if (treatment !== EventTreatment.REPLACEABLE)
133
+ return false;
134
+ // Check 'd' tag for parameterized replaceable events
135
+ if (Array.isArray(newEvent.tags) && Array.isArray(existingEvent.tags)) {
136
+ const newDTag = newEvent.tags.find(tag => Array.isArray(tag) && tag[0] === 'd')?.[1];
137
+ const existingDTag = existingEvent.tags.find(tag => Array.isArray(tag) && tag[0] === 'd')?.[1];
138
+ if (newDTag !== existingDTag)
139
+ return false;
140
+ }
141
+ // Replace if new event is newer
142
+ return newEvent.created_at > existingEvent.created_at;
143
+ }
144
+ catch (error) {
145
+ logger.error('Error checking event replacement:', error);
146
+ return false;
147
+ }
148
+ }
149
+ };
150
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @file NIP-19: bech32-encoded entities
3
+ * @module nips/nip-19
4
+ */
5
+ /**
6
+ * Encode a public key to bech32 npub format
7
+ */
8
+ export declare function encodePubkey(pubkey: string): string;
9
+ /**
10
+ * Encode a private key to bech32 nsec format
11
+ */
12
+ export declare function encodePrivkey(privkey: string): string;
13
+ /**
14
+ * Decode a bech32 npub to hex pubkey
15
+ */
16
+ export declare function decodePubkey(npub: string): string;
17
+ /**
18
+ * Decode a bech32 nsec to hex privkey
19
+ */
20
+ export declare function decodePrivkey(nsec: string): string;
21
+ /**
22
+ * Process tags containing bech32-encoded entities
23
+ */
24
+ export declare function processBech32Tags(tags: string[][]): string[][];
25
+ /**
26
+ * Encode tags containing hex pubkeys to bech32
27
+ */
28
+ export declare function encodeBech32Tags(tags: string[][]): string[][];