Skip to content

Commit 32a1ec5

Browse files
authored
feat: complete NIP-11 relay info parity (#557)
* feat: add missing relay information fields to settings model * feat: add NIP-11 root response fields and CORS headers * feat: add nip11 limitation fields for created_at, default_limit, and restricted_writes * test: add NIP-11 parity coverage and update support docs * docs(changeset): update NIP-11 relay info fields and CORS, with test and docs updates
1 parent 56652f3 commit 32a1ec5

12 files changed

Lines changed: 163 additions & 7 deletions

File tree

.changeset/loose-jeans-lead.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": patch
3+
---
4+
5+
update NIP-11 relay info fields and CORS, with test and docs updates

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",

resources/default-settings.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ info:
22
relay_url: wss://nostream.your-domain.com
33
name: nostream.your-domain.com
44
description: A nostr relay written in Typescript.
5+
banner: https://nostream.your-domain.com/banner.png
6+
icon: https://nostream.your-domain.com/icon.png
57
pubkey: replace-with-your-pubkey-in-hex
8+
self: replace-with-your-relay-pubkey-in-hex
69
contact: mailto:operator@your-domain.com
10+
terms_of_service: https://nostream.your-domain.com/terms
711
payments:
812
enabled: false
913
processor: zebedee

src/@types/settings.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ export interface Info {
88
name: string
99
description: string
1010
pubkey: string
11+
banner?: string
12+
icon?: string
13+
self?: string
1114
contact: string
15+
terms_of_service?: string
1216
}
1317

1418
export interface Network {

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: 27 additions & 2 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'
@@ -13,26 +14,44 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
1314

1415
if (accepts(request).type(['application/nostr+json'])) {
1516
const {
16-
info: { name, description, pubkey: rawPubkey, contact, relay_url },
17+
info: { name, description, banner, icon, pubkey: rawPubkey, self: rawSelf, contact, relay_url, terms_of_service },
1718
} = settings
1819

1920
const paymentsUrl = new URL(relay_url)
2021
paymentsUrl.protocol = paymentsUrl.protocol === 'wss:' ? 'https:' : 'http:'
2122
paymentsUrl.pathname = '/invoices'
2223

2324
const content = settings.limits?.event?.content
25+
const eventLimits = settings.limits?.event
26+
const createdAtLimits = eventLimits?.createdAt
27+
const hasAdmissionRestriction =
28+
settings.payments?.enabled === true &&
29+
Boolean(settings.payments?.feeSchedules?.admission?.some((feeSchedule) => feeSchedule.enabled))
30+
const hasWriteRestriction =
31+
hasAdmissionRestriction ||
32+
(eventLimits?.eventId?.minLeadingZeroBits ?? 0) > 0 ||
33+
(eventLimits?.pubkey?.minLeadingZeroBits ?? 0) > 0 ||
34+
(eventLimits?.pubkey?.whitelist?.length ?? 0) > 0 ||
35+
(eventLimits?.pubkey?.blacklist?.length ?? 0) > 0 ||
36+
(eventLimits?.kind?.whitelist?.length ?? 0) > 0 ||
37+
(eventLimits?.kind?.blacklist?.length ?? 0) > 0
2438

2539
const pubkey = rawPubkey.startsWith('npub1') ? fromBech32(rawPubkey) : rawPubkey
40+
const self = rawSelf?.startsWith('npub1') ? fromBech32(rawSelf) : rawSelf
2641

2742
const relayInformationDocument = {
2843
name,
2944
description,
45+
...(banner !== undefined ? { banner } : {}),
46+
...(icon !== undefined ? { icon } : {}),
3047
pubkey,
48+
...(self !== undefined ? { self } : {}),
3149
contact,
3250
supported_nips: packageJson.supportedNips,
3351
supported_nip_extensions: packageJson.supportedNipExtensions,
3452
software: packageJson.repository.url,
3553
version: packageJson.version,
54+
...(terms_of_service !== undefined ? { terms_of_service } : {}),
3655
limitation: {
3756
max_message_length: settings.network.maxPayloadSize,
3857
max_subscriptions: settings.limits?.client?.subscription?.maxSubscriptions,
@@ -44,9 +63,13 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
4463
max_content_length: Array.isArray(content)
4564
? content[0].maxLength // best guess since we have per-kind limits
4665
: content?.maxLength,
47-
min_pow_difficulty: settings.limits?.event?.eventId?.minLeadingZeroBits,
66+
min_pow_difficulty: eventLimits?.eventId?.minLeadingZeroBits,
4867
auth_required: false,
4968
payment_required: settings.payments?.enabled,
69+
created_at_lower_limit: createdAtLimits?.maxNegativeDelta,
70+
created_at_upper_limit: createdAtLimits?.maxPositiveDelta,
71+
default_limit: DEFAULT_FILTER_LIMIT,
72+
restricted_writes: hasWriteRestriction,
5073
},
5174
payments_url: paymentsUrl.toString(),
5275
fees: Object.getOwnPropertyNames(settings.payments.feeSchedules).reduce(
@@ -68,6 +91,8 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
6891
response
6992
.setHeader('content-type', 'application/nostr+json')
7093
.setHeader('access-control-allow-origin', '*')
94+
.setHeader('access-control-allow-headers', '*')
95+
.setHeader('access-control-allow-methods', 'GET, OPTIONS')
7196
.status(200)
7297
.send(relayInformationDocument)
7398

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"

0 commit comments

Comments
 (0)