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,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NIP-05: DNS-Based Verification
|
|
3
|
+
* @module nips/nip-05
|
|
4
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/05.md
|
|
5
|
+
*/
|
|
6
|
+
import { fetchJson } from '../utils/http';
|
|
7
|
+
/**
|
|
8
|
+
* Verifies a NIP-05 identifier
|
|
9
|
+
* @param identifier - Internet identifier (user@domain.com)
|
|
10
|
+
* @param pubkey - Public key to verify
|
|
11
|
+
* @param logger - Logger instance
|
|
12
|
+
* @returns {Promise<NIP05VerificationResult>} Verification result
|
|
13
|
+
*/
|
|
14
|
+
export async function verifyNIP05Identifier(identifier, pubkey, logger) {
|
|
15
|
+
try {
|
|
16
|
+
// Parse identifier
|
|
17
|
+
const [name, domain] = identifier.split('@');
|
|
18
|
+
if (!name || !domain) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
error: 'Invalid identifier format'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Fetch well-known URL
|
|
25
|
+
const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
|
|
26
|
+
const response = await fetchJson(url);
|
|
27
|
+
if (!response || !response.names || !response.names[name]) {
|
|
28
|
+
return {
|
|
29
|
+
valid: false,
|
|
30
|
+
error: 'Name not found in well-known file'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Verify name matches pubkey
|
|
34
|
+
const verifiedPubkey = response.names[name];
|
|
35
|
+
if (!verifiedPubkey) {
|
|
36
|
+
return {
|
|
37
|
+
valid: false,
|
|
38
|
+
error: 'Name not found'
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (verifiedPubkey !== pubkey) {
|
|
42
|
+
return {
|
|
43
|
+
valid: false,
|
|
44
|
+
error: 'Public key mismatch'
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Get associated relays if available
|
|
48
|
+
const relays = response.relays?.[verifiedPubkey];
|
|
49
|
+
return {
|
|
50
|
+
valid: true,
|
|
51
|
+
pubkey: verifiedPubkey,
|
|
52
|
+
relays
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
57
|
+
logger.error(`NIP-05 verification failed: ${errorMessage}`);
|
|
58
|
+
return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: errorMessage
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Creates metadata event with NIP-05 identifier
|
|
66
|
+
* @param identifier - Internet identifier
|
|
67
|
+
* @param metadata - Additional metadata
|
|
68
|
+
* @returns {Record<string, unknown>} Metadata event content
|
|
69
|
+
*/
|
|
70
|
+
export function createNIP05Metadata(identifier, metadata = {}) {
|
|
71
|
+
return {
|
|
72
|
+
...metadata,
|
|
73
|
+
nip05: identifier
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a NIP-05 verification cache
|
|
78
|
+
* @param defaultTTL - Default TTL in seconds
|
|
79
|
+
* @returns {NIP05VerificationCache} Verification cache
|
|
80
|
+
*/
|
|
81
|
+
export function createNIP05VerificationCache(defaultTTL = 3600) {
|
|
82
|
+
const cache = new Map();
|
|
83
|
+
function getCacheKey(identifier, pubkey) {
|
|
84
|
+
return `${identifier}:${pubkey}`;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
get(identifier, pubkey) {
|
|
88
|
+
const key = getCacheKey(identifier, pubkey);
|
|
89
|
+
const entry = cache.get(key);
|
|
90
|
+
if (!entry)
|
|
91
|
+
return undefined;
|
|
92
|
+
if (Date.now() > entry.expiresAt) {
|
|
93
|
+
cache.delete(key);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return entry.result;
|
|
97
|
+
},
|
|
98
|
+
set(identifier, pubkey, result, ttl = defaultTTL) {
|
|
99
|
+
const key = getCacheKey(identifier, pubkey);
|
|
100
|
+
cache.set(key, {
|
|
101
|
+
result,
|
|
102
|
+
expiresAt: Date.now() + (ttl * 1000)
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
cleanup() {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
for (const [key, entry] of cache.entries()) {
|
|
108
|
+
if (now > entry.expiresAt) {
|
|
109
|
+
cache.delete(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Creates a NIP-05 batch verifier
|
|
117
|
+
* @param logger - Logger instance
|
|
118
|
+
* @param cache - Optional verification cache
|
|
119
|
+
* @returns {NIP05BatchVerifier} Batch verifier
|
|
120
|
+
*/
|
|
121
|
+
export function createNIP05BatchVerifier(logger, cache) {
|
|
122
|
+
const queue = new Map();
|
|
123
|
+
return {
|
|
124
|
+
addToQueue(identifier, pubkey) {
|
|
125
|
+
queue.set(identifier, pubkey);
|
|
126
|
+
},
|
|
127
|
+
async verifyAll() {
|
|
128
|
+
const results = new Map();
|
|
129
|
+
for (const [identifier, pubkey] of queue.entries()) {
|
|
130
|
+
// Check cache first
|
|
131
|
+
if (cache) {
|
|
132
|
+
const cached = cache.get(identifier, pubkey);
|
|
133
|
+
if (cached) {
|
|
134
|
+
results.set(identifier, cached);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Verify and cache result
|
|
139
|
+
const result = await verifyNIP05Identifier(identifier, pubkey, logger);
|
|
140
|
+
results.set(identifier, result);
|
|
141
|
+
if (cache) {
|
|
142
|
+
cache.set(identifier, pubkey, result, 3600);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return results;
|
|
146
|
+
},
|
|
147
|
+
clearQueue() {
|
|
148
|
+
queue.clear();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NIP-09: Event Deletion
|
|
3
|
+
* @module nips/nip-09
|
|
4
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/09.md
|
|
5
|
+
*/
|
|
6
|
+
import type { NostrEvent, NostrSubscriptionEvent } from '../types/events';
|
|
7
|
+
/**
|
|
8
|
+
* Represents the result of a deletion operation
|
|
9
|
+
* @interface DeletionResult
|
|
10
|
+
* @property {boolean} success - Whether the deletion operation was successful
|
|
11
|
+
* @property {string} [error] - Error message if the operation failed
|
|
12
|
+
* @property {string[]} [deletedIds] - Array of successfully deleted event IDs
|
|
13
|
+
*/
|
|
14
|
+
interface DeletionResult {
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
deletedIds?: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Event deletion kind
|
|
21
|
+
*/
|
|
22
|
+
export declare const EVENT_DELETION_KIND = 5;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a deletion event
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDeletionEvent(eventIds: string[], reason?: string): Omit<NostrEvent, 'id' | 'sig'>;
|
|
27
|
+
/**
|
|
28
|
+
* Validates a deletion event
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateDeletionEvent(event: NostrEvent): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Processes a deletion event
|
|
33
|
+
*/
|
|
34
|
+
export declare function processDeletionEvent(event: NostrEvent, _logger: any, deleteEvent: (id: string) => Promise<boolean>): Promise<DeletionResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Event deletion manager interface
|
|
37
|
+
*/
|
|
38
|
+
export interface EventDeletionManager {
|
|
39
|
+
/**
|
|
40
|
+
* Processes a deletion event
|
|
41
|
+
* @param message - Deletion message
|
|
42
|
+
* @returns {Promise<string[]>} Deleted event IDs
|
|
43
|
+
*/
|
|
44
|
+
processDeletion(message: NostrEvent): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Checks if an event has been deleted
|
|
47
|
+
* @param eventId - Event ID to check
|
|
48
|
+
* @returns {boolean} True if event is deleted
|
|
49
|
+
*/
|
|
50
|
+
isDeleted(eventId: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Gets deletion reason for an event
|
|
53
|
+
* @param eventId - Event ID
|
|
54
|
+
* @returns {string | undefined} Deletion reason if available
|
|
55
|
+
*/
|
|
56
|
+
getDeletionReason(eventId: string): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Gets all deleted events
|
|
59
|
+
* @returns {Map<string, string>} Map of event IDs to deletion reasons
|
|
60
|
+
*/
|
|
61
|
+
getDeletedEvents(): Map<string, string>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Creates an event deletion manager
|
|
65
|
+
* @param _logger - Logger instance
|
|
66
|
+
* @returns {EventDeletionManager} Deletion manager
|
|
67
|
+
*/
|
|
68
|
+
export declare function createEventDeletionManager(_logger: any): EventDeletionManager;
|
|
69
|
+
/**
|
|
70
|
+
* Event deletion subscription manager interface
|
|
71
|
+
*/
|
|
72
|
+
export interface DeletionSubscriptionManager {
|
|
73
|
+
/**
|
|
74
|
+
* Creates a subscription for deletion events
|
|
75
|
+
* @param eventIds - Event IDs to monitor
|
|
76
|
+
* @returns {NostrSubscriptionEvent} Subscription message
|
|
77
|
+
*/
|
|
78
|
+
subscribeToDeletions(eventIds: string[]): NostrSubscriptionEvent;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a subscription for all deletions by a user
|
|
81
|
+
* @param pubkey - Public key of user
|
|
82
|
+
* @returns {NostrSubscriptionEvent} Subscription message
|
|
83
|
+
*/
|
|
84
|
+
subscribeToUserDeletions(pubkey: string): NostrSubscriptionEvent;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Creates a deletion subscription manager
|
|
88
|
+
* @param _logger - Logger instance
|
|
89
|
+
* @returns {DeletionSubscriptionManager} Subscription manager
|
|
90
|
+
*/
|
|
91
|
+
export declare function createDeletionSubscriptionManager(_logger: any): DeletionSubscriptionManager;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NIP-09: Event Deletion
|
|
3
|
+
* @module nips/nip-09
|
|
4
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/09.md
|
|
5
|
+
*/
|
|
6
|
+
import { getLogger } from '../utils/logger';
|
|
7
|
+
const logger = getLogger('NIP-09');
|
|
8
|
+
/**
|
|
9
|
+
* Event deletion kind
|
|
10
|
+
*/
|
|
11
|
+
export const EVENT_DELETION_KIND = 5;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a deletion event
|
|
14
|
+
*/
|
|
15
|
+
export function createDeletionEvent(eventIds, reason) {
|
|
16
|
+
return {
|
|
17
|
+
kind: EVENT_DELETION_KIND,
|
|
18
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
19
|
+
tags: eventIds.map(id => ['e', id]),
|
|
20
|
+
content: reason || '',
|
|
21
|
+
pubkey: '', // To be filled by the caller
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validates a deletion event
|
|
26
|
+
*/
|
|
27
|
+
export function validateDeletionEvent(event) {
|
|
28
|
+
try {
|
|
29
|
+
// Must be kind 5
|
|
30
|
+
if (event.kind !== EVENT_DELETION_KIND) {
|
|
31
|
+
logger.debug('Invalid event kind for deletion');
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// Must have at least one e tag
|
|
35
|
+
const eventIds = event.tags
|
|
36
|
+
.filter((tag) => Array.isArray(tag) && tag[0] === 'e' && typeof tag[1] === 'string')
|
|
37
|
+
.map(tag => tag[1]);
|
|
38
|
+
if (eventIds.length === 0) {
|
|
39
|
+
logger.debug('No event IDs to delete');
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
// Validate each event ID format
|
|
43
|
+
const validHexString = /^[0-9a-f]{64}$/;
|
|
44
|
+
const invalidIds = eventIds.filter(id => !validHexString.test(id));
|
|
45
|
+
if (invalidIds.length > 0) {
|
|
46
|
+
logger.debug(`Invalid event IDs: ${invalidIds.join(', ')}`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
53
|
+
logger.error(`Error validating deletion event: ${errorMessage}`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Processes a deletion event
|
|
59
|
+
*/
|
|
60
|
+
export async function processDeletionEvent(event, _logger, deleteEvent) {
|
|
61
|
+
try {
|
|
62
|
+
if (!validateDeletionEvent(event)) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: 'Invalid deletion event'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const eventIds = event.tags
|
|
69
|
+
.filter((tag) => tag[0] === 'e')
|
|
70
|
+
.map(tag => tag[1]);
|
|
71
|
+
const deletedIds = [];
|
|
72
|
+
const failedIds = [];
|
|
73
|
+
for (const eventId of eventIds) {
|
|
74
|
+
try {
|
|
75
|
+
const success = await deleteEvent(eventId);
|
|
76
|
+
if (success) {
|
|
77
|
+
deletedIds.push(eventId);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
failedIds.push(eventId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
85
|
+
logger.error(`Failed to delete event ${eventId}: ${errorMessage}`);
|
|
86
|
+
failedIds.push(eventId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (failedIds.length > 0) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: `Failed to delete events: ${failedIds.join(', ')}`,
|
|
93
|
+
deletedIds
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
success: true,
|
|
98
|
+
deletedIds
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
103
|
+
logger.error(`Error processing deletion event: ${errorMessage}`);
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: errorMessage
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates an event deletion manager
|
|
112
|
+
* @param _logger - Logger instance
|
|
113
|
+
* @returns {EventDeletionManager} Deletion manager
|
|
114
|
+
*/
|
|
115
|
+
export function createEventDeletionManager(_logger) {
|
|
116
|
+
// Map of deleted event IDs to deletion reasons
|
|
117
|
+
const deletedEvents = new Map();
|
|
118
|
+
// Map of deleted event IDs to deletion event IDs
|
|
119
|
+
const deletionEvents = new Map();
|
|
120
|
+
return {
|
|
121
|
+
async processDeletion(message) {
|
|
122
|
+
try {
|
|
123
|
+
if (!validateDeletionEvent(message)) {
|
|
124
|
+
throw new Error('Invalid deletion event');
|
|
125
|
+
}
|
|
126
|
+
const event = message;
|
|
127
|
+
const eventIds = event.tags
|
|
128
|
+
.filter(tag => tag[0] === 'e')
|
|
129
|
+
.map(tag => tag[1]);
|
|
130
|
+
const reason = event.content;
|
|
131
|
+
const deletionId = event.id;
|
|
132
|
+
// Process each event ID
|
|
133
|
+
eventIds.forEach(eventId => {
|
|
134
|
+
// Only delete if not already deleted or if this deletion is newer
|
|
135
|
+
const existingDeletionId = deletionEvents.get(eventId);
|
|
136
|
+
if (!existingDeletionId || deletionId > existingDeletionId) {
|
|
137
|
+
deletedEvents.set(eventId, reason);
|
|
138
|
+
deletionEvents.set(eventId, deletionId);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
logger.debug('Processed deletion event', {
|
|
142
|
+
deletionId,
|
|
143
|
+
eventIds,
|
|
144
|
+
reason
|
|
145
|
+
});
|
|
146
|
+
return eventIds;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
logger.error('Error processing deletion event:', error);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
isDeleted(eventId) {
|
|
154
|
+
return deletedEvents.has(eventId);
|
|
155
|
+
},
|
|
156
|
+
getDeletionReason(eventId) {
|
|
157
|
+
return deletedEvents.get(eventId);
|
|
158
|
+
},
|
|
159
|
+
getDeletedEvents() {
|
|
160
|
+
return new Map(deletedEvents);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Creates a deletion subscription manager
|
|
166
|
+
* @param _logger - Logger instance
|
|
167
|
+
* @returns {DeletionSubscriptionManager} Subscription manager
|
|
168
|
+
*/
|
|
169
|
+
export function createDeletionSubscriptionManager(_logger) {
|
|
170
|
+
return {
|
|
171
|
+
subscribeToDeletions(eventIds) {
|
|
172
|
+
return {
|
|
173
|
+
subscriptionId: `deletion_${eventIds.join('_')}`,
|
|
174
|
+
filters: [{
|
|
175
|
+
kinds: [EVENT_DELETION_KIND],
|
|
176
|
+
'#e': eventIds
|
|
177
|
+
}]
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
subscribeToUserDeletions(pubkey) {
|
|
181
|
+
return {
|
|
182
|
+
subscriptionId: `user_deletion_${pubkey}`,
|
|
183
|
+
filters: [{
|
|
184
|
+
kinds: [EVENT_DELETION_KIND],
|
|
185
|
+
authors: [pubkey]
|
|
186
|
+
}]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NIP-11: Relay Information Document
|
|
3
|
+
* @module nips/nip-11
|
|
4
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/11.md
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Relay information document structure
|
|
8
|
+
*/
|
|
9
|
+
export interface RelayInformation {
|
|
10
|
+
name?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
pubkey?: string;
|
|
13
|
+
contact?: string;
|
|
14
|
+
supported_nips?: number[];
|
|
15
|
+
software?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
limitation?: {
|
|
18
|
+
max_message_length?: number;
|
|
19
|
+
max_subscriptions?: number;
|
|
20
|
+
max_filters?: number;
|
|
21
|
+
max_limit?: number;
|
|
22
|
+
max_subid_length?: number;
|
|
23
|
+
min_prefix?: number;
|
|
24
|
+
max_event_tags?: number;
|
|
25
|
+
max_content_length?: number;
|
|
26
|
+
min_pow_difficulty?: number;
|
|
27
|
+
auth_required?: boolean;
|
|
28
|
+
payment_required?: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fetches relay information document
|
|
33
|
+
* @param url - Relay URL (ws:// or wss://)
|
|
34
|
+
* @returns {Promise<RelayInformation>} Relay information
|
|
35
|
+
*/
|
|
36
|
+
export declare function getRelayInformation(url: string): Promise<RelayInformation>;
|
|
37
|
+
/**
|
|
38
|
+
* Checks if relay meets requirements
|
|
39
|
+
* @param relay - Relay information
|
|
40
|
+
* @param requirements - Required relay features
|
|
41
|
+
* @returns {boolean} True if relay meets all requirements
|
|
42
|
+
*/
|
|
43
|
+
export declare function checkRelayRequirements(relay: RelayInformation, requirements: Partial<RelayInformation>): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Validates relay capabilities against required features
|
|
46
|
+
* @param info - Relay information
|
|
47
|
+
* @param requiredNips - Required NIPs
|
|
48
|
+
* @param requiredFeatures - Required relay features
|
|
49
|
+
* @returns {boolean} True if relay supports all required features
|
|
50
|
+
*/
|
|
51
|
+
export declare function validateRelayCapabilities(info: RelayInformation, requiredNips?: number[], requiredFeatures?: Partial<RelayInformation['limitation']>): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Creates a relay selection score based on capabilities
|
|
54
|
+
* @param info - Relay information
|
|
55
|
+
* @param preferences - Scoring preferences
|
|
56
|
+
* @returns {number} Relay score (higher is better)
|
|
57
|
+
*/
|
|
58
|
+
export declare function scoreRelayCapabilities(info: RelayInformation, preferences?: {
|
|
59
|
+
preferredNips?: number[];
|
|
60
|
+
minMessageLength?: number;
|
|
61
|
+
minSubscriptions?: number;
|
|
62
|
+
requireAuth?: boolean;
|
|
63
|
+
requirePayment?: boolean;
|
|
64
|
+
}): number;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file NIP-11: Relay Information Document
|
|
3
|
+
* @module nips/nip-11
|
|
4
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/11.md
|
|
5
|
+
*/
|
|
6
|
+
import { getLogger } from '../utils/logger';
|
|
7
|
+
import { fetchJson } from '../utils/http';
|
|
8
|
+
const logger = getLogger('NIP-11');
|
|
9
|
+
/**
|
|
10
|
+
* Fetches relay information document
|
|
11
|
+
* @param url - Relay URL (ws:// or wss://)
|
|
12
|
+
* @returns {Promise<RelayInformation>} Relay information
|
|
13
|
+
*/
|
|
14
|
+
export async function getRelayInformation(url) {
|
|
15
|
+
try {
|
|
16
|
+
const relayUrl = url.replace('ws://', 'http://').replace('wss://', 'https://');
|
|
17
|
+
const response = await fetchJson(`${relayUrl}/.well-known/nostr.json`);
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
22
|
+
logger.error(`Failed to fetch relay information: ${errorMessage}`);
|
|
23
|
+
throw new Error(`Failed to fetch relay information: ${errorMessage}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks if relay meets requirements
|
|
28
|
+
* @param relay - Relay information
|
|
29
|
+
* @param requirements - Required relay features
|
|
30
|
+
* @returns {boolean} True if relay meets all requirements
|
|
31
|
+
*/
|
|
32
|
+
export function checkRelayRequirements(relay, requirements) {
|
|
33
|
+
try {
|
|
34
|
+
// Check supported NIPs
|
|
35
|
+
if (requirements.supported_nips && relay.supported_nips) {
|
|
36
|
+
const missingNips = requirements.supported_nips.filter(nip => !relay.supported_nips?.includes(nip));
|
|
37
|
+
if (missingNips.length > 0) {
|
|
38
|
+
logger.debug(`Missing required NIPs: ${missingNips.join(', ')}`);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check limitations
|
|
43
|
+
if (requirements.limitation && relay.limitation) {
|
|
44
|
+
for (const [key, value] of Object.entries(requirements.limitation)) {
|
|
45
|
+
const relayValue = relay.limitation[key];
|
|
46
|
+
// Skip if relay doesn't specify this limitation
|
|
47
|
+
if (relayValue === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
// For maximum values, relay should support at least the required value
|
|
50
|
+
if (key.startsWith('max_') && typeof relayValue === 'number' && typeof value === 'number') {
|
|
51
|
+
if (relayValue < value) {
|
|
52
|
+
logger.debug(`Relay ${key} too low: ${relayValue} < ${value}`);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// For minimum values, relay should not require more than specified
|
|
57
|
+
if (key.startsWith('min_') && typeof relayValue === 'number' && typeof value === 'number') {
|
|
58
|
+
if (relayValue > value) {
|
|
59
|
+
logger.debug(`Relay ${key} too high: ${relayValue} > ${value}`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// For boolean flags, values must match if specified
|
|
64
|
+
if (typeof relayValue === 'boolean' && typeof value === 'boolean') {
|
|
65
|
+
if (relayValue !== value) {
|
|
66
|
+
logger.debug(`Relay ${key} mismatch: ${relayValue} !== ${value}`);
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
76
|
+
logger.error(`Error checking relay requirements: ${errorMessage}`);
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validates relay capabilities against required features
|
|
82
|
+
* @param info - Relay information
|
|
83
|
+
* @param requiredNips - Required NIPs
|
|
84
|
+
* @param requiredFeatures - Required relay features
|
|
85
|
+
* @returns {boolean} True if relay supports all required features
|
|
86
|
+
*/
|
|
87
|
+
export function validateRelayCapabilities(info, requiredNips = [], requiredFeatures = {}) {
|
|
88
|
+
// Check NIP support
|
|
89
|
+
if (requiredNips.length > 0) {
|
|
90
|
+
if (!info.supported_nips)
|
|
91
|
+
return false;
|
|
92
|
+
if (!requiredNips.every(nip => info.supported_nips?.includes(nip))) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Check limitations
|
|
97
|
+
if (info.limitation) {
|
|
98
|
+
for (const [key, value] of Object.entries(requiredFeatures)) {
|
|
99
|
+
const relayValue = info.limitation[key];
|
|
100
|
+
if (relayValue === undefined)
|
|
101
|
+
return false;
|
|
102
|
+
// For maximum values, relay limit should be higher
|
|
103
|
+
if (key.startsWith('max_') && typeof relayValue === 'number' && typeof value === 'number') {
|
|
104
|
+
if (relayValue < value)
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
// For minimum values, relay limit should be lower
|
|
108
|
+
if (key.startsWith('min_') && typeof relayValue === 'number' && typeof value === 'number') {
|
|
109
|
+
if (relayValue > value)
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
// For boolean flags, must match exactly
|
|
113
|
+
if (typeof relayValue === 'boolean' && typeof value === 'boolean') {
|
|
114
|
+
if (relayValue !== value)
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Creates a relay selection score based on capabilities
|
|
123
|
+
* @param info - Relay information
|
|
124
|
+
* @param preferences - Scoring preferences
|
|
125
|
+
* @returns {number} Relay score (higher is better)
|
|
126
|
+
*/
|
|
127
|
+
export function scoreRelayCapabilities(info, preferences = {}) {
|
|
128
|
+
let score = 0;
|
|
129
|
+
// Score NIP support
|
|
130
|
+
if (info.supported_nips && preferences.preferredNips) {
|
|
131
|
+
score += preferences.preferredNips.filter(nip => info.supported_nips?.includes(nip)).length * 10;
|
|
132
|
+
}
|
|
133
|
+
// Score limitations
|
|
134
|
+
if (info.limitation) {
|
|
135
|
+
const limits = info.limitation;
|
|
136
|
+
// Message length
|
|
137
|
+
if (limits.max_message_length && preferences.minMessageLength) {
|
|
138
|
+
score += limits.max_message_length >= preferences.minMessageLength ? 5 : -5;
|
|
139
|
+
}
|
|
140
|
+
// Subscriptions
|
|
141
|
+
if (limits.max_subscriptions && preferences.minSubscriptions) {
|
|
142
|
+
score += limits.max_subscriptions >= preferences.minSubscriptions ? 5 : -5;
|
|
143
|
+
}
|
|
144
|
+
// Auth requirements
|
|
145
|
+
if (preferences.requireAuth !== undefined) {
|
|
146
|
+
score += (limits.auth_required === preferences.requireAuth) ? 5 : -10;
|
|
147
|
+
}
|
|
148
|
+
// Payment requirements
|
|
149
|
+
if (preferences.requirePayment !== undefined) {
|
|
150
|
+
score += (limits.payment_required === preferences.requirePayment) ? 5 : -10;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return score;
|
|
154
|
+
}
|