mirror of
https://github.com/Cameri/nostream.git
synced 2025-07-16 08:42:21 +02:00
feat: support nip-111 (#168)
* feat: support nip-111 * test: update schemas
This commit is contained in:
committed by
GitHub
parent
67ad1eb1d1
commit
a7169b3569
@ -59,6 +59,7 @@ NIPs with a relay-specific implementation are listed here.
|
|||||||
- [x] NIP-26: Delegated Event Signing
|
- [x] NIP-26: Delegated Event Signing
|
||||||
- [x] NIP-28: Public Chat
|
- [x] NIP-28: Public Chat
|
||||||
- [x] NIP-33: Parameterized Replaceable Events
|
- [x] NIP-33: Parameterized Replaceable Events
|
||||||
|
- [x] NIP-111: Relay Information Document Extensions
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
26,
|
26,
|
||||||
28,
|
28,
|
||||||
33,
|
33,
|
||||||
40
|
40,
|
||||||
|
111
|
||||||
],
|
],
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from 'express'
|
|||||||
import { path } from 'ramda'
|
import { path } from 'ramda'
|
||||||
|
|
||||||
import { createSettings } from '../../factories/settings-factory'
|
import { createSettings } from '../../factories/settings-factory'
|
||||||
|
import { FeeSchedule } from '../../@types/settings'
|
||||||
import packageJson from '../../../package.json'
|
import packageJson from '../../../package.json'
|
||||||
|
|
||||||
export const rootRequestHandler = (request: Request, response: Response, next: NextFunction) => {
|
export const rootRequestHandler = (request: Request, response: Response, next: NextFunction) => {
|
||||||
@ -9,9 +10,13 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
|
|||||||
|
|
||||||
if (request.header('accept') === 'application/nostr+json') {
|
if (request.header('accept') === 'application/nostr+json') {
|
||||||
const {
|
const {
|
||||||
info: { name, description, pubkey, contact },
|
info: { name, description, pubkey, contact, relay_url },
|
||||||
} = settings
|
} = settings
|
||||||
|
|
||||||
|
const paymentsUrl = new URL(relay_url)
|
||||||
|
paymentsUrl.protocol = paymentsUrl.protocol === 'wss:' ? 'https:' : 'http:'
|
||||||
|
paymentsUrl.pathname = '/invoices'
|
||||||
|
|
||||||
const relayInformationDocument = {
|
const relayInformationDocument = {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@ -20,6 +25,33 @@ export const rootRequestHandler = (request: Request, response: Response, next: N
|
|||||||
supported_nips: packageJson.supportedNips,
|
supported_nips: packageJson.supportedNips,
|
||||||
software: packageJson.repository.url,
|
software: packageJson.repository.url,
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
|
limitation: {
|
||||||
|
max_message_length: settings.network.maxPayloadSize,
|
||||||
|
max_subscriptions: settings.limits.client.subscription.maxSubscriptions,
|
||||||
|
max_filters: settings.limits.client.subscription.maxFilters,
|
||||||
|
max_limit: 5000,
|
||||||
|
max_subid_length: 256,
|
||||||
|
min_prefix: 4,
|
||||||
|
max_event_tags: 2500,
|
||||||
|
max_content_length: 102400,
|
||||||
|
min_pow_difficulty: settings.limits.event.eventId.minLeadingZeroBits,
|
||||||
|
auth_required: false,
|
||||||
|
payment_required: settings.payments.enabled,
|
||||||
|
},
|
||||||
|
payments_url: paymentsUrl.toString(),
|
||||||
|
fees: Object
|
||||||
|
.getOwnPropertyNames(settings.payments.feeSchedules)
|
||||||
|
.reduce((prev, feeName) => {
|
||||||
|
const feeSchedules = settings.payments.feeSchedules[feeName] as FeeSchedule[]
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[feeName]: feeSchedules.reduce((fees, fee) => (fee.enabled)
|
||||||
|
? [...fees, { amount: fee.amount, unit: 'msats' }]
|
||||||
|
: fees, []),
|
||||||
|
}
|
||||||
|
|
||||||
|
}, {} as Record<string, { amount: number, unit: string }>),
|
||||||
}
|
}
|
||||||
|
|
||||||
response
|
response
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Schema from 'joi'
|
import Schema from 'joi'
|
||||||
|
|
||||||
export const prefixSchema = Schema.string().case('lower').hex().min(1).max(64).label('prefix')
|
export const prefixSchema = Schema.string().case('lower').hex().min(4).max(64).label('prefix')
|
||||||
|
|
||||||
export const idSchema = Schema.string().case('lower').hex().length(64).label('id')
|
export const idSchema = Schema.string().case('lower').hex().length(64).label('id')
|
||||||
|
|
||||||
|
@ -31,9 +31,10 @@ export const eventSchema = Schema.object({
|
|||||||
pubkey: pubkeySchema.required(),
|
pubkey: pubkeySchema.required(),
|
||||||
created_at: createdAtSchema.required(),
|
created_at: createdAtSchema.required(),
|
||||||
kind: kindSchema.required(),
|
kind: kindSchema.required(),
|
||||||
tags: Schema.array().items(tagSchema).required(),
|
tags: Schema.array().items(tagSchema).max(2500).required(),
|
||||||
content: Schema.string()
|
content: Schema.string()
|
||||||
.allow('')
|
.allow('')
|
||||||
|
.max(100 * 1024) // 100 kB
|
||||||
.required(),
|
.required(),
|
||||||
sig: signatureSchema.required(),
|
sig: signatureSchema.required(),
|
||||||
}).unknown(false)
|
}).unknown(false)
|
||||||
|
@ -8,5 +8,5 @@ export const filterSchema = Schema.object({
|
|||||||
kinds: Schema.array().items(kindSchema).max(20),
|
kinds: Schema.array().items(kindSchema).max(20),
|
||||||
since: createdAtSchema,
|
since: createdAtSchema,
|
||||||
until: createdAtSchema,
|
until: createdAtSchema,
|
||||||
limit: Schema.number().min(0).multiple(1).max(10000),
|
limit: Schema.number().min(0).multiple(1).max(5000),
|
||||||
}).pattern(/^#[a-z]$/, Schema.array().items(Schema.string().max(1024)).max(256))
|
}).pattern(/^#[a-z]$/, Schema.array().items(Schema.string().max(1024)).max(256))
|
||||||
|
@ -12,7 +12,7 @@ export const eventMessageSchema = Schema.array().ordered(
|
|||||||
.label('EVENT message')
|
.label('EVENT message')
|
||||||
|
|
||||||
export const reqMessageSchema = Schema.array()
|
export const reqMessageSchema = Schema.array()
|
||||||
.ordered(Schema.string().valid('REQ').required(), Schema.string().required().label('subscriptionId'))
|
.ordered(Schema.string().valid('REQ').required(), Schema.string().max(256).required().label('subscriptionId'))
|
||||||
.items(filterSchema.required().label('filter')).max(12)
|
.items(filterSchema.required().label('filter')).max(12)
|
||||||
.label('REQ message')
|
.label('REQ message')
|
||||||
|
|
||||||
|
@ -10,8 +10,8 @@ describe('NIP-01', () => {
|
|||||||
describe('validate filter schema', () => {
|
describe('validate filter schema', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
filter = {
|
filter = {
|
||||||
ids: ['aa', 'bb', 'cc'],
|
ids: ['aaaa', 'bbbb', 'cccc'],
|
||||||
authors: ['aa', 'bb', 'cc'],
|
authors: ['aaaa', 'bbbb', 'cccc'],
|
||||||
kinds: [0, 1, 2, 3],
|
kinds: [0, 1, 2, 3],
|
||||||
since: 1000,
|
since: 1000,
|
||||||
until: 1000,
|
until: 1000,
|
||||||
@ -32,7 +32,7 @@ describe('NIP-01', () => {
|
|||||||
const cases = {
|
const cases = {
|
||||||
ids: [
|
ids: [
|
||||||
{ message: 'must be an array', transform: assocPath(['ids'], null) },
|
{ message: 'must be an array', transform: assocPath(['ids'], null) },
|
||||||
{ message: 'must contain less than or equal to 1000 items', transform: assocPath(['ids'], range(0, 1001).map(() => 'f')) },
|
{ message: 'must contain less than or equal to 1000 items', transform: assocPath(['ids'], range(0, 1001).map(() => 'ffff')) },
|
||||||
],
|
],
|
||||||
prefixOrId: [
|
prefixOrId: [
|
||||||
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['ids', 0], 'f'.repeat(65)) },
|
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['ids', 0], 'f'.repeat(65)) },
|
||||||
@ -41,7 +41,7 @@ describe('NIP-01', () => {
|
|||||||
],
|
],
|
||||||
authors: [
|
authors: [
|
||||||
{ message: 'must be an array', transform: assocPath(['authors'], null) },
|
{ message: 'must be an array', transform: assocPath(['authors'], null) },
|
||||||
{ message: 'must contain less than or equal to 1000 items', transform: assocPath(['authors'], range(0, 1001).map(() => 'f')) },
|
{ message: 'must contain less than or equal to 1000 items', transform: assocPath(['authors'], range(0, 1001).map(() => 'ffff')) },
|
||||||
],
|
],
|
||||||
prefixOrAuthor: [
|
prefixOrAuthor: [
|
||||||
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['authors', 0], 'f'.repeat(65)) },
|
{ message: 'length must be less than or equal to 64 characters long', transform: assocPath(['authors', 0], 'f'.repeat(65)) },
|
||||||
@ -73,7 +73,7 @@ describe('NIP-01', () => {
|
|||||||
{ message: 'must be a number', transform: assocPath(['limit'], null) },
|
{ message: 'must be a number', transform: assocPath(['limit'], null) },
|
||||||
{ message: 'must be greater than or equal to 0', transform: assocPath(['limit'], -1) },
|
{ message: 'must be greater than or equal to 0', transform: assocPath(['limit'], -1) },
|
||||||
{ message: 'must be a multiple of 1', transform: assocPath(['limit'], Math.PI) },
|
{ message: 'must be a multiple of 1', transform: assocPath(['limit'], Math.PI) },
|
||||||
{ message: 'must be less than or equal to 10000', transform: assocPath(['limit'], 10001) },
|
{ message: 'must be less than or equal to 5000', transform: assocPath(['limit'], 5001) },
|
||||||
],
|
],
|
||||||
'#e': [
|
'#e': [
|
||||||
{ message: 'must be an array', transform: assocPath(['#e'], null) },
|
{ message: 'must be an array', transform: assocPath(['#e'], null) },
|
||||||
|
@ -25,6 +25,8 @@ describe('NIP-01', () => {
|
|||||||
|
|
||||||
const result = validateSchema(messageSchema)(message)
|
const result = validateSchema(messageSchema)(message)
|
||||||
|
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
expect(result).not.to.have.property('error')
|
expect(result).not.to.have.property('error')
|
||||||
expect(result).to.have.deep.property('value', message)
|
expect(result).to.have.deep.property('value', message)
|
||||||
})
|
})
|
||||||
@ -56,8 +58,8 @@ describe('NIP-01', () => {
|
|||||||
'REQ',
|
'REQ',
|
||||||
'id',
|
'id',
|
||||||
{
|
{
|
||||||
ids: ['aa', 'bb', 'cc'],
|
ids: ['aaaa', 'bbbb', 'cccc'],
|
||||||
authors: ['aa', 'bb', 'cc'],
|
authors: ['aaaa', 'bbbb', 'cccc'],
|
||||||
kinds: [0, 1, 2, 3],
|
kinds: [0, 1, 2, 3],
|
||||||
since: 1000,
|
since: 1000,
|
||||||
until: 1000,
|
until: 1000,
|
||||||
@ -67,8 +69,8 @@ describe('NIP-01', () => {
|
|||||||
'#r': ['00', '11', '22'],
|
'#r': ['00', '11', '22'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ids: ['aa', 'bb', 'cc'],
|
ids: ['aaaa', 'bbbb', 'cccc'],
|
||||||
authors: ['aa', 'bb', 'cc'],
|
authors: ['aaaa', 'bbbb', 'cccc'],
|
||||||
kinds: [0, 1, 2, 3],
|
kinds: [0, 1, 2, 3],
|
||||||
since: 1000,
|
since: 1000,
|
||||||
until: 1000,
|
until: 1000,
|
||||||
@ -82,7 +84,7 @@ describe('NIP-01', () => {
|
|||||||
|
|
||||||
it('returns same message if valid', () => {
|
it('returns same message if valid', () => {
|
||||||
const result = validateSchema(messageSchema)(message)
|
const result = validateSchema(messageSchema)(message)
|
||||||
|
console.log('result', result)
|
||||||
expect(result).not.to.have.property('error')
|
expect(result).not.to.have.property('error')
|
||||||
expect(result).to.have.deep.property('value', message)
|
expect(result).to.have.deep.property('value', message)
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user