Skip to content

Commit cd3eba7

Browse files
committed
test: add NIP-11 parity coverage and update support docs
1 parent 60e5cba commit cd3eba7

9 files changed

Lines changed: 64 additions & 11 deletions

File tree

CONFIGURATION.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,15 @@ The settings below are listed in alphabetical order by name. Please keep this ta
127127

128128
| Name | Description |
129129
|---------------------------------------------|-------------------------------------------------------------------------------|
130+
| info.banner | Public banner image URL for the relay information document. |
130131
| info.contact | Relay operator's contact. (e.g. mailto:operator@relay-your-domain.com) |
131132
| info.description | Public description of your relay. (e.g. Toronto Bitcoin Group Public Relay) |
133+
| info.icon | Public icon image URL for the relay information document. |
132134
| info.name | Public name of your relay. (e.g. TBG's Public Relay) |
133135
| info.pubkey | Relay operator's Nostr pubkey in hex format. |
134136
| info.relay_url | Public-facing URL of your relay. (e.g. wss://relay.your-domain.com) |
137+
| info.self | Relay pubkey in hex format for the relay information document `self` field. |
138+
| info.terms_of_service | Public URL to relay terms of service. |
135139
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
136140
| limits.admissionCheck.rateLimits[].period | Rate limit period in milliseconds. |
137141
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ NIPs with a relay-specific implementation are listed here.
4949
- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
5050
- [x] NIP-09: Event deletion
5151
- [x] NIP-11: Relay information document
52-
- [x] NIP-11a: Relay Information Document Extensions
5352
- [x] NIP-12: Generic tag queries
5453
- [x] NIP-13: Proof of Work
5554
- [x] NIP-15: End of Stored Events Notice

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
44,
2222
45
2323
],
24-
"supportedNipExtensions": [
25-
"11a"
26-
],
24+
"supportedNipExtensions": [],
2725
"main": "src/index.ts",
2826
"scripts": {
2927
"dev": "node --env-file-if-exists=.env -r ts-node/register src/index.ts",

src/constants/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export enum EventTags {
5555
}
5656

5757
export const ALL_RELAYS = 'ALL_RELAYS'
58+
export const DEFAULT_FILTER_LIMIT = 500
5859

5960
export enum PaymentsProcessors {
6061
LNURL = 'lnurl',

src/handlers/request-handlers/root-request-handler.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { path, pathEq } from 'ramda'
44
import { createSettings } from '../../factories/settings-factory'
55
import { escapeHtml } from '../../utils/html'
66
import { FeeSchedule } from '../../@types/settings'
7+
import { DEFAULT_FILTER_LIMIT } from '../../constants/base'
78
import { fromBech32 } from '../../utils/transform'
89
import { getTemplate } from '../../utils/template-cache'
910
import packageJson from '../../../package.json'
@@ -41,16 +42,16 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
4142
const relayInformationDocument = {
4243
name,
4344
description,
44-
banner,
45-
icon,
45+
...(banner !== undefined ? { banner } : {}),
46+
...(icon !== undefined ? { icon } : {}),
4647
pubkey,
47-
self,
48+
...(self !== undefined ? { self } : {}),
4849
contact,
4950
supported_nips: packageJson.supportedNips,
5051
supported_nip_extensions: packageJson.supportedNipExtensions,
5152
software: packageJson.repository.url,
5253
version: packageJson.version,
53-
terms_of_service,
54+
...(terms_of_service !== undefined ? { terms_of_service } : {}),
5455
limitation: {
5556
max_message_length: settings.network.maxPayloadSize,
5657
max_subscriptions: settings.limits?.client?.subscription?.maxSubscriptions,
@@ -67,7 +68,7 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
6768
payment_required: settings.payments?.enabled,
6869
created_at_lower_limit: createdAtLimits?.maxNegativeDelta,
6970
created_at_upper_limit: createdAtLimits?.maxPositiveDelta,
70-
default_limit: 500,
71+
default_limit: DEFAULT_FILTER_LIMIT,
7172
restricted_writes: hasWriteRestriction,
7273
},
7374
payments_url: paymentsUrl.toString(),

src/repositories/event-repository.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030

3131
import {
3232
ContextMetadataKey,
33+
DEFAULT_FILTER_LIMIT,
3334
EventDeduplicationMetadataKey,
3435
EventExpirationTimeMetadataKey,
3536
EventKinds,
@@ -76,7 +77,7 @@ export class EventRepository implements IEventRepository {
7677
if (typeof currentFilter.limit === 'number') {
7778
builder.limit(currentFilter.limit).orderBy('event_created_at', 'DESC').orderBy('event_id', 'asc')
7879
} else {
79-
builder.limit(500).orderBy('event_created_at', 'asc').orderBy('event_id', 'asc')
80+
builder.limit(DEFAULT_FILTER_LIMIT).orderBy('event_created_at', 'asc').orderBy('event_id', 'asc')
8081
}
8182

8283
if (isTagQuery) {

test/integration/features/nip-11/nip-11.feature

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ Feature: NIP-11
99
When a client requests the relay information document
1010
Then the supported_nips field matches the NIPs declared in package.json
1111

12+
Scenario: Relay information response includes required CORS headers
13+
When a client requests the relay information document
14+
Then the relay information response includes required NIP-11 CORS headers
15+
16+
Scenario: Relay information document includes NIP-11 limitation parity fields
17+
When a client requests the relay information document
18+
Then the limitation object contains NIP-11 parity fields and values
19+
1220
Scenario: Relay does not return information document for a non-NIP-11 Accept header
1321
When a client requests the root path with Accept header "text/html"
1422
Then the response Content-Type does not include "application/nostr+json"

test/integration/features/nip-11/nip-11.feature.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import axios, { AxiosResponse } from 'axios'
33
import chai from 'chai'
44

55
import packageJson from '../../../../package.json'
6+
import { DEFAULT_FILTER_LIMIT } from '../../../../src/constants/base'
67
import { createSettings } from '../../../../src/factories/settings-factory'
78

89
chai.use(require('sinon-chai'))
@@ -70,3 +71,32 @@ Then('the limitation object contains a max_filters field', function(this: World<
7071
const expectedMaxFilters = createSettings().limits?.client?.subscription?.maxFilters
7172
expect(doc.limitation.max_filters).to.equal(expectedMaxFilters)
7273
})
74+
75+
Then('the relay information response includes required NIP-11 CORS headers', function(
76+
this: World<Record<string, any>>,
77+
) {
78+
const headers = this.parameters.httpResponse.headers
79+
expect(headers['access-control-allow-origin']).to.equal('*')
80+
expect(headers['access-control-allow-headers']).to.equal('*')
81+
expect(headers['access-control-allow-methods']).to.equal('GET, OPTIONS')
82+
})
83+
84+
Then('the limitation object contains NIP-11 parity fields and values', function(this: World<Record<string, any>>) {
85+
const doc = this.parameters.httpResponse.data
86+
const settings = createSettings()
87+
const eventLimits = settings.limits?.event
88+
89+
const expectedRestrictedWrites =
90+
Boolean(settings.payments?.enabled && settings.payments?.feeSchedules?.admission?.some((fee) => fee.enabled)) ||
91+
(eventLimits?.eventId?.minLeadingZeroBits ?? 0) > 0 ||
92+
(eventLimits?.pubkey?.minLeadingZeroBits ?? 0) > 0 ||
93+
(eventLimits?.pubkey?.whitelist?.length ?? 0) > 0 ||
94+
(eventLimits?.pubkey?.blacklist?.length ?? 0) > 0 ||
95+
(eventLimits?.kind?.whitelist?.length ?? 0) > 0 ||
96+
(eventLimits?.kind?.blacklist?.length ?? 0) > 0
97+
98+
expect(doc.limitation.created_at_lower_limit).to.equal(eventLimits?.createdAt?.maxNegativeDelta)
99+
expect(doc.limitation.created_at_upper_limit).to.equal(eventLimits?.createdAt?.maxPositiveDelta)
100+
expect(doc.limitation.default_limit).to.equal(DEFAULT_FILTER_LIMIT)
101+
expect(doc.limitation.restricted_writes).to.equal(expectedRestrictedWrites)
102+
})

test/unit/handlers/request-handlers/root-request-handler.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { expect } = chai
77

88
import * as settingsFactory from '../../../../src/factories/settings-factory'
99
import * as templateCache from '../../../../src/utils/template-cache'
10+
import { DEFAULT_FILTER_LIMIT } from '../../../../src/constants/base'
1011
import { rootRequestHandler } from '../../../../src/handlers/request-handlers/root-request-handler'
1112

1213
const baseSettings = {
@@ -125,6 +126,16 @@ describe('rootRequestHandler', () => {
125126
expect(doc.terms_of_service).to.equal('https://relay.example.com/terms')
126127
})
127128

129+
it('does not include optional NIP-11 fields when not configured', () => {
130+
rootRequestHandler(req, res, next)
131+
132+
const doc = res.send.firstCall.args[0]
133+
expect(doc).to.not.have.property('banner')
134+
expect(doc).to.not.have.property('icon')
135+
expect(doc).to.not.have.property('self')
136+
expect(doc).to.not.have.property('terms_of_service')
137+
})
138+
128139
it('includes NIP-11 limitation created_at and default_limit fields', () => {
129140
createSettingsStub.returns({
130141
...baseSettings,
@@ -145,7 +156,7 @@ describe('rootRequestHandler', () => {
145156
const doc = res.send.firstCall.args[0]
146157
expect(doc.limitation.created_at_lower_limit).to.equal(86400)
147158
expect(doc.limitation.created_at_upper_limit).to.equal(300)
148-
expect(doc.limitation.default_limit).to.equal(500)
159+
expect(doc.limitation.default_limit).to.equal(DEFAULT_FILTER_LIMIT)
149160
})
150161

151162
it('sets limitation.restricted_writes based on active write restrictions', () => {

0 commit comments

Comments
 (0)