nip11 - Types, requestRelayInfos() and tests

This commit is contained in:
Giacomo Gagliano 2023-07-10 17:24:19 +02:00 committed by fiatjaf_
parent 6a07e7c1cc
commit 9cd4f16e45
2 changed files with 340 additions and 0 deletions

27
nip11.test.ts Normal file
View File

@ -0,0 +1,27 @@
import {Nip11} from './nip11'
const requestRelayInfos = Nip11.requestRelayInfos
describe('requesting Relay infos as for NIP11', () => {
test('testing damus relay', async () => {
const expected_relay_name = 'relay.nostr.nu'
const expected_relay_description =
'A nostr relay build by Edward Hollander.'
const expected_supported_nips = [
1, 2, 4, 9, 11, 12, 15, 16, 20, 22, 26, 28, 33, 40
]
const test_relay = 'https://relay.nostr.nu'
const relay_infos = await requestRelayInfos(test_relay)
const relay_name = relay_infos.name
const relay_description = relay_infos.description
const fees = relay_infos.fees
const admission = fees?.admission
const supported_nips = relay_infos.supported_nips
const admission_condition = Array.isArray(admission)
expect(relay_name).toBe(expected_relay_name)
expect(relay_description).toBe(expected_relay_description)
expect(fees).toBeTruthy()
expect(admission_condition).toBeTruthy()
expect(supported_nips).toMatchObject(expected_supported_nips)
})
})

313
nip11.ts Normal file
View File

@ -0,0 +1,313 @@
// #85 I created an implementation of each of the different
// types described in the NIP11.
import fetch from 'node-fetch'
export namespace Nip11 {
export interface requestRelayInfos<
N extends string[],
A extends boolean,
P extends boolean
> {
(relay_addr: string): Promise<RelayInfos<N, A, P>>
}
// I wanted to use an enum, but eslint is giving me
// problems!
// export enum headers_accept {
// nostr_json = 'application/nostr+json'
// }
export const requestRelayInfos: requestRelayInfos<any, any, any> = (
relay_addr: string
) => {
return new Promise(async (res, rej) => {
try {
const accept = 'application/nostr+json'
const init = {headers: {accept}}
const response = await fetch(relay_addr, init)
const relayInfos: RelayInfosTemplate<any> =
(await response.json()) as RelayInfosTemplate<any>
res(relayInfos)
} catch (error) {
rej(error)
}
})
}
}
/**
* ## Relay Information Document
* Relays may provide server metadata to clients to inform
* them of capabilities, administrative contacts, and
* various server attributes. This is made available as a
* JSON document over HTTP, on the same URI as the relay's
* websocket.
* Any field may be omitted, and clients MUST ignore any
* additional fields they do not understand. Relays MUST
* accept CORS requests by sending
* `Access-Control-Allow-Origin`,
* `Access-Control-Allow-Headers`, and
* `Access-Control-Allow-Methods` headers.
* @param name string identifying relay
* @param description string with detailed information
* @param pubkey administrative contact pubkey
* @param contact: administrative alternate contact
* @param supported_nips a list of NIP numbers supported by
* the relay
* @param software identifying relay software URL
* @param version string version identifier
*/
export interface RelayInfosTemplate<N extends string[]> {
// string identifying relay
name: string
description: string
pubkey: string
contact: string
supported_nips: N
software: string
version: string
// limitation?: Limitations<A, P>
}
/**
* * ## Extra Fields
* * ### Server Limitations
* These are limitations imposed by the relay on clients.
* Your client should expect that requests which exceed
* these practical_ limitations are rejected or fail immediately.
* @param max_message_length this is the maximum number of
* bytes for incoming JSON that the relay will attempt to
* decode and act upon. When you send large subscriptions,
* you will be limited by this value. It also effectively
* limits the maximum size of any event. Value is calculated
* from `[` to `]` and is after UTF-8 serialization (so some
* unicode characters will cost 2-3 bytes). It is equal to
* the maximum size of the WebSocket message frame.
* @param max_subscription total number of subscriptions
* that may be active on a single websocket connection to
* this relay. It's possible that authenticated clients with
* a (paid) relationship to the relay may have higher limits.
* @param max_filters maximum number of filter values in
* each subscription. Must be one or higher.
* @param max_limit the relay server will clamp each
* filter's `limit` value to this number.
* This means the client won't be able to get more than this
* number of events from a single subscription filter. This
* clamping is typically done silently by the relay, but
* with this number, you can know that there are additional
* results if you narrowed your filter's time range or other
* parameters.
* @param max_subid_length maximum length of subscription id as a
* string.
* @param min_prefix for `authors` and `ids` filters which
* are to match against a hex prefix, you must provide at
* least this many hex digits in the prefix.
* @param max_event_tags in any event, this is the maximum
* number of elements in the `tags` list.
* @param max_content_length maximum number of characters in
* the `content` field of any event. This is a count of
* unicode characters. After serializing into JSON it may be
* larger (in bytes), and is still subject to the
* max_message_length`, if defined.
* @param min_pow_difficulty new events will require at
* least this difficulty of PoW, based on [NIP-13](13.md),
* or they will be rejected by this server.
* @param auth_required this relay requires [NIP-42](42.md)
* authentication to happen before a new connection may
* perform any other action. Even if set to False,
* authentication may be required for specific actions.
* @param payment_required this relay requires payment
* before a new connection may perform any action.
*/
export interface Limitations<A extends boolean, P extends boolean> {
max_message_length: number
max_subscription: number
max_filters: number
max_limit: number
max_subid_length: number
min_prefix: number
max_event_tags: number
max_content_length: number
min_pow_difficulty: number
auth_required: A
payment_required: P
}
type range<L extends number, H extends number> = [L, H]
type anyRange = range<any, any>
type genericKinds = (number | anyRange)[]
interface RetentionDetails<K extends genericKinds> {
kinds: K
time?: number | null
count?: number | null
}
type AnyRetentionDetails = RetentionDetails<any>
/**
* ### Event Retention
* There may be a cost associated with storing data forever,
* so relays may wish to state retention times. The values
* stated here are defaults for unauthenticated users and
* visitors. Paid users would likely have other policies.
* Retention times are given in seconds, with `null`
* indicating infinity. If zero is provided, this means the
* event will not be stored at all, and preferably an error
* will be provided when those are received.
* ```json
{
...
"retention": [
{ "kinds": [0, 1, [5, 7], [40, 49]], "time": 3600 },
{ "kinds": [[40000, 49999]], "time": 100 },
{ "kinds": [[30000, 39999]], "count": 1000 },
{ "time": 3600, "count": 10000 }
]
...
}
```
* @param retention is a list of specifications: each will
* apply to either all kinds, or a subset of kinds. Ranges
* may be specified for the kind field as a tuple of
* inclusive start and end values. Events of indicated kind
* (or all) are then limited to a `count` and/or time
* period.
* It is possible to effectively blacklist Nostr-based
* protocols that rely on a specific `kind` number, by
* giving a retention time of zero for those `kind` values.
* While that is unfortunate, it does allow clients to
* discover servers that will support their protocol quickly
* via a single HTTP fetch.
* There is no need to specify retention times for
* _ephemeral events_ as defined in [NIP-16](16.md) since
* they are not retained.
*/
export interface Retention {
retention: AnyRetentionDetails[]
}
/**
* Some relays may be governed by the arbitrary laws of a
* nation state. This may limit what content can be stored
* in cleartext on those relays. All clients are encouraged
* to use encryption to work around this limitation.
* It is not possible to describe the limitations of each
* country's laws and policies which themselves are
* typically vague and constantly shifting.
* Therefore, this field allows the relay operator to
* indicate which countries' laws might end up being
* enforced on them, and then indirectly on their users'
* content.
* Users should be able to avoid relays in countries they
* don't like, and/or select relays in more favourable
* zones. Exposing this flexibility is up to the client
* software.
* @param relay_countries a list of two-level ISO country
* codes (ISO 3166-1 alpha-2) whose laws and policies may
* affect this relay. `EU` may be used for European Union
* countries.
* Remember that a relay may be hosted in a country which is
* not the country of the legal entities who own the relay,
* so it's very likely a number of countries are involved.
*/
export interface ContentLimitations {
relay_countries: string[]
}
/**
* ### Community Preferences
* For public text notes at least, a relay may try to foster
* a local community. This would encourage users to follow
* the global feed on that relay, in addition to their usual
* individual follows. To support this goal, relays MAY
* specify some of the following values.
* @param language_tags is an ordered list of [IETF
* language
* tags](https://en.wikipedia.org/wiki/IETF_language_tag
* indicating the major languages spoken on the relay.
* @param tags is a list of limitations on the topics to be
* discussed. For example `sfw-only` indicates that only
* "Safe For Work" content is encouraged on this relay. This
* relies on assumptions of what the "work" "community"
* feels "safe" talking about. In time, a common set of tags
* may emerge that allow users to find relays that suit
* their needs, and client software will be able to parse
* these tags easily. The `bitcoin-only` tag indicates that
* any _altcoin_, _"crypto"_ or _blockchain_ comments will
* be ridiculed without mercy.
* @param posting_policy is a link to a human-readable page
* which specifies the community policies for the relay. In
* cases where `sfw-only` is True, it's important to link to
* a page which gets into the specifics of your posting
* policy.
* The `description` field should be used to describe your
* community goals and values, in brief. The
* `posting_policy` is for additional detail and legal
* terms. Use the `tags` field to signify limitations on
* content, or topics to be discussed, which could be
* machine processed by appropriate client software.
*/
export interface CommunityPreferences {
language_tags: string[]
tags: string[]
posting_policy: string
}
export interface Amount {
amount: number
unit: 'msat'
}
export interface PublicationAmount extends Amount {
kinds: number[]
}
export interface Subscription extends Amount {
period: number
}
export interface Fees {
admission: Amount[]
subscription: Subscription[]
publication: PublicationAmount[]
}
/**
* Relays that require payments may want to expose their fee
* schedules.
*/
export interface PayToRelay {
payments_url: string
fees: Fees
}
/**
* A URL pointing to an image to be used as an icon for the
* relay. Recommended to be squared in shape.
*/
export interface Icon {
icon: string
}
export type RelayInfos<
N extends string[],
A extends boolean,
P extends boolean
> = RelayInfosTemplate<N> &
Partial<Retention> & {
limitation?: Partial<Limitations<A, P>>
} & Partial<ContentLimitations> &
Partial<CommunityPreferences> &
Partial<PayToRelay> &
Partial<Icon>