MCPcopy Create free account
hub / github.com/cameri/nostream / CountMessageHandler

Class CountMessageHandler

src/handlers/count-message-handler.ts:15–67  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

13const debug = createLogger('count-message-handler')
14
15export class CountMessageHandler implements IMessageHandler {
16 public constructor(
17 private readonly webSocket: IWebSocketAdapter,
18 private readonly eventRepository: IEventRepository,
19 private readonly settings: () => Settings,
20 ) {}
21
22 public async handleMessage(message: CountMessage): Promise<void> {
23 const queryId = message[1]
24 const countEnabled = this.settings().nip45?.enabled ?? true
25 if (!countEnabled) {
26 this.webSocket.emit(WebSocketAdapterEvent.Message, createClosedMessage(queryId, 'COUNT is disabled by relay configuration'))
27 return
28 }
29
30 // Some clients send the same filter more than once.
31 // We remove duplicates so we do less DB work.
32 const filters = uniqWith(equals, message.slice(2)) as SubscriptionFilter[]
33
34 const reason = this.canCount(queryId, filters)
35 if (reason) {
36 debug('count request %s with %o rejected: %s', queryId, filters, reason)
37 // NIP-45 says we should close rejected COUNT requests with a reason.
38 this.webSocket.emit(WebSocketAdapterEvent.Message, createClosedMessage(queryId, reason))
39 return
40 }
41
42 try {
43 const count = await this.eventRepository.countByFilters(filters)
44 this.webSocket.emit(WebSocketAdapterEvent.Message, createCountResultMessage(queryId, { count }))
45 } catch (error) {
46 debug('count request %s failed: %o', queryId, error)
47 // Keep this message generic so internal errors are not leaked to clients.
48 this.webSocket.emit(WebSocketAdapterEvent.Message, createClosedMessage(queryId, 'error: unable to count events'))
49 }
50 }
51
52 private canCount(queryId: SubscriptionId, filters: SubscriptionFilter[]): string | undefined {
53 const subscriptionLimits = this.settings().limits?.client?.subscription
54 const maxFilters = subscriptionLimits?.maxFilters ?? 0
55
56 if (maxFilters > 0 && filters.length > maxFilters) {
57 return `Too many filters: Number of filters per count query must be less than or equal to ${maxFilters}`
58 }
59
60 if (
61 typeof subscriptionLimits?.maxSubscriptionIdLength === 'number' &&
62 queryId.length > subscriptionLimits.maxSubscriptionIdLength
63 ) {
64 return `Query ID too long: Query ID must be less than or equal to ${subscriptionLimits.maxSubscriptionIdLength}`
65 }
66 }
67}

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected