add and improve helpers for nip29
This commit is contained in:
parent
9c009ac543
commit
6f334f31a7
684
nip29.ts
684
nip29.ts
|
@ -1,80 +1,514 @@
|
|||
import { AbstractSimplePool } from './abstract-pool.ts'
|
||||
import { Subscription } from './abstract-relay.ts'
|
||||
import { decode } from './nip19.ts'
|
||||
import type { Event } from './core.ts'
|
||||
import { fetchRelayInformation } from './nip11.ts'
|
||||
import type { Event, EventTemplate } from './core.ts'
|
||||
import { fetchRelayInformation, RelayInformation } from './nip11.ts'
|
||||
import { AddressPointer, decode } from './nip19.ts'
|
||||
import { normalizeURL } from './utils.ts'
|
||||
import { AddressPointer } from './nip19.ts'
|
||||
|
||||
export function subscribeRelayGroups(
|
||||
pool: AbstractSimplePool,
|
||||
url: string,
|
||||
params: {
|
||||
ongroups: (_: Group[]) => void
|
||||
onerror: (_: Error) => void
|
||||
onconnect?: () => void
|
||||
},
|
||||
): () => void {
|
||||
let normalized = normalizeURL(url)
|
||||
let sub: Subscription
|
||||
let groups: Group[] = []
|
||||
|
||||
fetchRelayInformation(normalized)
|
||||
.then(async info => {
|
||||
let rl = await pool.ensureRelay(normalized)
|
||||
params.onconnect?.()
|
||||
sub = rl.prepareSubscription(
|
||||
[
|
||||
{
|
||||
kinds: [39000],
|
||||
limit: 50,
|
||||
authors: [info.pubkey],
|
||||
},
|
||||
],
|
||||
{
|
||||
onevent(event: Event) {
|
||||
groups.push(parseGroup(event, normalized))
|
||||
},
|
||||
oneose() {
|
||||
params.ongroups(groups)
|
||||
sub.onevent = (event: Event) => {
|
||||
groups.push(parseGroup(event, normalized))
|
||||
params.ongroups(groups)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
sub.fire()
|
||||
})
|
||||
.catch(params.onerror)
|
||||
|
||||
return () => sub.close()
|
||||
/**
|
||||
* Represents a NIP29 group.
|
||||
*/
|
||||
export type Group = {
|
||||
relay: string
|
||||
metadata: GroupMetadata
|
||||
admins?: GroupAdmin[]
|
||||
members?: GroupMember[]
|
||||
reference: GroupReference
|
||||
}
|
||||
|
||||
export async function loadGroup(pool: AbstractSimplePool, gr: GroupReference): Promise<Group> {
|
||||
let normalized = normalizeURL(gr.host)
|
||||
|
||||
let info = await fetchRelayInformation(normalized)
|
||||
let event = await pool.get([normalized], {
|
||||
kinds: [39000],
|
||||
authors: [info.pubkey],
|
||||
'#d': [gr.id],
|
||||
})
|
||||
if (!event) throw new Error(`group '${gr.id}' not found on ${gr.host}`)
|
||||
return parseGroup(event, normalized)
|
||||
}
|
||||
|
||||
export async function loadGroupFromCode(pool: AbstractSimplePool, code: string): Promise<Group> {
|
||||
let gr = parseGroupCode(code)
|
||||
if (!gr) throw new Error(`code "${code}" does not identify a group`)
|
||||
return loadGroup(pool, gr)
|
||||
/**
|
||||
* Represents the metadata for a NIP29 group.
|
||||
*/
|
||||
export type GroupMetadata = {
|
||||
id: string
|
||||
pubkey: string
|
||||
name?: string
|
||||
picture?: string
|
||||
about?: string
|
||||
isPublic?: boolean
|
||||
isOpen?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a NIP29 group reference.
|
||||
*/
|
||||
export type GroupReference = {
|
||||
id: string
|
||||
host: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a NIP29 group member.
|
||||
*/
|
||||
export type GroupMember = {
|
||||
pubkey: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a NIP29 group admin.
|
||||
*/
|
||||
export type GroupAdmin = {
|
||||
pubkey: string
|
||||
label?: string
|
||||
permissions: GroupAdminPermission[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the permissions that a NIP29 group admin can have.
|
||||
*/
|
||||
export enum GroupAdminPermission {
|
||||
AddUser = 'add-user',
|
||||
EditMetadata = 'edit-metadata',
|
||||
DeleteEvent = 'delete-event',
|
||||
RemoveUser = 'remove-user',
|
||||
AddPermission = 'add-permission',
|
||||
RemovePermission = 'remove-permission',
|
||||
EditGroupStatus = 'edit-group-status',
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a group metadata event template.
|
||||
*
|
||||
* @param group - The group object.
|
||||
* @returns An event template with the generated group metadata that can be signed later.
|
||||
*/
|
||||
export function generateGroupMetadataEventTemplate(group: Group): EventTemplate {
|
||||
const tags: string[][] = [['d', group.metadata.id]]
|
||||
group.metadata.name && tags.push(['name', group.metadata.name])
|
||||
group.metadata.picture && tags.push(['picture', group.metadata.picture])
|
||||
group.metadata.about && tags.push(['about', group.metadata.about])
|
||||
group.metadata.isPublic && tags.push(['public'])
|
||||
group.metadata.isOpen && tags.push(['open'])
|
||||
|
||||
return {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 39000,
|
||||
tags,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a group metadata event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns A boolean indicating whether the event is valid.
|
||||
*/
|
||||
export function validateGroupMetadataEvent(event: Event): boolean {
|
||||
if (event.kind !== 39000) return false
|
||||
|
||||
if (!event.pubkey) return false
|
||||
|
||||
const requiredTags = ['d'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an event template for group admins.
|
||||
*
|
||||
* @param group - The group object.
|
||||
* @param admins - An array of group admins.
|
||||
* @returns The generated event template with the group admins that can be signed later.
|
||||
*/
|
||||
export function generateGroupAdminsEventTemplate(group: Group, admins: GroupAdmin[]): EventTemplate {
|
||||
const tags: string[][] = [['d', group.metadata.id]]
|
||||
for (const admin of admins) {
|
||||
tags.push(['p', admin.pubkey, admin.label || '', ...admin.permissions])
|
||||
}
|
||||
|
||||
return {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 39001,
|
||||
tags,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a group admins event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns True if the event is valid, false otherwise.
|
||||
*/
|
||||
export function validateGroupAdminsEvent(event: Event): boolean {
|
||||
if (event.kind !== 39001) return false
|
||||
|
||||
const requiredTags = ['d'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
// validate permissions
|
||||
for (const [tag, value, label, ...permissions] of event.tags) {
|
||||
if (tag !== 'p') continue
|
||||
|
||||
for (let i = 0; i < permissions.length; i += 1) {
|
||||
if (typeof permissions[i] !== 'string') return false
|
||||
|
||||
// validate permission name from the GroupAdminPermission enum
|
||||
if (!Object.values(GroupAdminPermission).includes(permissions[i] as GroupAdminPermission)) return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an event template for a group with its members.
|
||||
*
|
||||
* @param group - The group object.
|
||||
* @param members - An array of group members.
|
||||
* @returns The generated event template with the group members that can be signed later.
|
||||
*/
|
||||
export function generateGroupMembersEventTemplate(group: Group, members: GroupMember[]): EventTemplate {
|
||||
const tags: string[][] = [['d', group.metadata.id]]
|
||||
for (const member of members) {
|
||||
tags.push(['p', member.pubkey, member.label || ''])
|
||||
}
|
||||
|
||||
return {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 39002,
|
||||
tags,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a group members event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns Returns `true` if the event is a valid group members event, `false` otherwise.
|
||||
*/
|
||||
export function validateGroupMembersEvent(event: Event): boolean {
|
||||
if (event.kind !== 39002) return false
|
||||
|
||||
const requiredTags = ['d'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized relay URL based on the provided group reference.
|
||||
*
|
||||
* @param groupReference - The group reference object containing the host.
|
||||
* @returns The normalized relay URL.
|
||||
*/
|
||||
export function getNormalizedRelayURLByGroupReference(groupReference: GroupReference): string {
|
||||
return normalizeURL(groupReference.host)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches relay information by group reference.
|
||||
*
|
||||
* @param groupReference The group reference.
|
||||
* @returns A promise that resolves to the relay information.
|
||||
*/
|
||||
export async function fetchRelayInformationByGroupReference(groupReference: GroupReference): Promise<RelayInformation> {
|
||||
const normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)
|
||||
|
||||
return fetchRelayInformation(normalizedRelayURL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the group metadata event from the specified pool.
|
||||
* If the normalizedRelayURL is not provided, it will be obtained using the groupReference.
|
||||
* If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.
|
||||
*
|
||||
* @param {Object} options - The options object.
|
||||
* @param {AbstractSimplePool} options.pool - The pool to fetch the group metadata event from.
|
||||
* @param {GroupReference} options.groupReference - The reference to the group.
|
||||
* @param {string} [options.normalizedRelayURL] - The normalized URL of the relay.
|
||||
* @param {RelayInformation} [options.relayInformation] - The relay information object.
|
||||
* @returns {Promise<Event>} The group metadata event that can be parsed later to get the group metadata object.
|
||||
* @throws {Error} If the group is not found on the specified relay.
|
||||
*/
|
||||
export async function fetchGroupMetadataEvent({
|
||||
pool,
|
||||
groupReference,
|
||||
relayInformation,
|
||||
normalizedRelayURL,
|
||||
}: {
|
||||
pool: AbstractSimplePool
|
||||
groupReference: GroupReference
|
||||
normalizedRelayURL?: string
|
||||
relayInformation?: RelayInformation
|
||||
}): Promise<Event> {
|
||||
if (!normalizedRelayURL) {
|
||||
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)
|
||||
}
|
||||
|
||||
if (!relayInformation) {
|
||||
relayInformation = await fetchRelayInformation(normalizedRelayURL)
|
||||
}
|
||||
|
||||
const groupMetadataEvent = await pool.get([normalizedRelayURL], {
|
||||
kinds: [39000],
|
||||
authors: [relayInformation.pubkey],
|
||||
'#d': [groupReference.id],
|
||||
})
|
||||
|
||||
if (!groupMetadataEvent) throw new Error(`group '${groupReference.id}' not found on ${normalizedRelayURL}`)
|
||||
|
||||
return groupMetadataEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a group metadata event and returns the corresponding GroupMetadata object.
|
||||
*
|
||||
* @param event - The event to parse.
|
||||
* @returns The parsed GroupMetadata object.
|
||||
* @throws An error if the group metadata event is invalid.
|
||||
*/
|
||||
export function parseGroupMetadataEvent(event: Event): GroupMetadata {
|
||||
if (!validateGroupMetadataEvent(event)) throw new Error('invalid group metadata event')
|
||||
|
||||
const metadata: GroupMetadata = {
|
||||
id: '',
|
||||
pubkey: event.pubkey,
|
||||
}
|
||||
|
||||
for (const [tag, value] of event.tags) {
|
||||
switch (tag) {
|
||||
case 'd':
|
||||
metadata.id = value
|
||||
break
|
||||
case 'name':
|
||||
metadata.name = value
|
||||
break
|
||||
case 'picture':
|
||||
metadata.picture = value
|
||||
break
|
||||
case 'about':
|
||||
metadata.about = value
|
||||
break
|
||||
case 'public':
|
||||
metadata.isPublic = true
|
||||
break
|
||||
case 'open':
|
||||
metadata.isOpen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the group admins event from the specified pool.
|
||||
* If the normalizedRelayURL is not provided, it will be obtained from the groupReference.
|
||||
* If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.
|
||||
*
|
||||
* @param {Object} options - The options object.
|
||||
* @param {AbstractSimplePool} options.pool - The pool to fetch the group admins event from.
|
||||
* @param {GroupReference} options.groupReference - The reference to the group.
|
||||
* @param {string} [options.normalizedRelayURL] - The normalized relay URL.
|
||||
* @param {RelayInformation} [options.relayInformation] - The relay information.
|
||||
* @returns {Promise<Event>} The group admins event that can be parsed later to get the group admins object.
|
||||
* @throws {Error} If the group admins event is not found on the specified relay.
|
||||
*/
|
||||
export async function fetchGroupAdminsEvent({
|
||||
pool,
|
||||
groupReference,
|
||||
relayInformation,
|
||||
normalizedRelayURL,
|
||||
}: {
|
||||
pool: AbstractSimplePool
|
||||
groupReference: GroupReference
|
||||
normalizedRelayURL?: string
|
||||
relayInformation?: RelayInformation
|
||||
}): Promise<Event> {
|
||||
if (!normalizedRelayURL) {
|
||||
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)
|
||||
}
|
||||
|
||||
if (!relayInformation) {
|
||||
relayInformation = await fetchRelayInformation(normalizedRelayURL)
|
||||
}
|
||||
|
||||
const groupAdminsEvent = await pool.get([normalizedRelayURL], {
|
||||
kinds: [39001],
|
||||
authors: [relayInformation.pubkey],
|
||||
'#d': [groupReference.id],
|
||||
})
|
||||
|
||||
if (!groupAdminsEvent) throw new Error(`admins for group '${groupReference.id}' not found on ${normalizedRelayURL}`)
|
||||
|
||||
return groupAdminsEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a group admins event and returns an array of GroupAdmin objects.
|
||||
*
|
||||
* @param event - The event to parse.
|
||||
* @returns An array of GroupAdmin objects.
|
||||
* @throws Throws an error if the group admins event is invalid.
|
||||
*/
|
||||
export function parseGroupAdminsEvent(event: Event): GroupAdmin[] {
|
||||
if (!validateGroupAdminsEvent(event)) throw new Error('invalid group admins event')
|
||||
|
||||
const admins: GroupAdmin[] = []
|
||||
|
||||
for (const [tag, value, label, ...permissions] of event.tags) {
|
||||
if (tag !== 'p') continue
|
||||
|
||||
admins.push({
|
||||
pubkey: value,
|
||||
label,
|
||||
permissions: permissions as GroupAdminPermission[],
|
||||
})
|
||||
}
|
||||
|
||||
return admins
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the group members event from the specified relay.
|
||||
* If the normalizedRelayURL is not provided, it will be obtained using the groupReference.
|
||||
* If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.
|
||||
*
|
||||
* @param {Object} options - The options object.
|
||||
* @param {AbstractSimplePool} options.pool - The pool object.
|
||||
* @param {GroupReference} options.groupReference - The group reference object.
|
||||
* @param {string} [options.normalizedRelayURL] - The normalized relay URL.
|
||||
* @param {RelayInformation} [options.relayInformation] - The relay information object.
|
||||
* @returns {Promise<Event>} The group members event that can be parsed later to get the group members object.
|
||||
* @throws {Error} If the group members event is not found.
|
||||
*/
|
||||
export async function fetchGroupMembersEvent({
|
||||
pool,
|
||||
groupReference,
|
||||
relayInformation,
|
||||
normalizedRelayURL,
|
||||
}: {
|
||||
pool: AbstractSimplePool
|
||||
groupReference: GroupReference
|
||||
normalizedRelayURL?: string
|
||||
relayInformation?: RelayInformation
|
||||
}): Promise<Event> {
|
||||
if (!normalizedRelayURL) {
|
||||
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)
|
||||
}
|
||||
|
||||
if (!relayInformation) {
|
||||
relayInformation = await fetchRelayInformation(normalizedRelayURL)
|
||||
}
|
||||
|
||||
const groupMembersEvent = await pool.get([normalizedRelayURL], {
|
||||
kinds: [39002],
|
||||
authors: [relayInformation.pubkey],
|
||||
'#d': [groupReference.id],
|
||||
})
|
||||
|
||||
if (!groupMembersEvent) throw new Error(`members for group '${groupReference.id}' not found on ${normalizedRelayURL}`)
|
||||
|
||||
return groupMembersEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a group members event and returns an array of GroupMember objects.
|
||||
* @param event - The event to parse.
|
||||
* @returns An array of GroupMember objects.
|
||||
* @throws Throws an error if the group members event is invalid.
|
||||
*/
|
||||
export function parseGroupMembersEvent(event: Event): GroupMember[] {
|
||||
if (!validateGroupMembersEvent(event)) throw new Error('invalid group members event')
|
||||
|
||||
const members: GroupMember[] = []
|
||||
|
||||
for (const [tag, value, label] of event.tags) {
|
||||
if (tag !== 'p') continue
|
||||
|
||||
members.push({
|
||||
pubkey: value,
|
||||
label,
|
||||
})
|
||||
}
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses the group metadata event, group admins event, and group members event from the specified pool.
|
||||
* If the normalized relay URL is not provided, it will be obtained using the group reference.
|
||||
* If the relay information is not provided, it will be fetched using the normalized relay URL.
|
||||
*
|
||||
* @param {Object} options - The options for loading the group.
|
||||
* @param {AbstractSimplePool} options.pool - The pool to load the group from.
|
||||
* @param {GroupReference} options.groupReference - The reference of the group to load.
|
||||
* @param {string} [options.normalizedRelayURL] - The normalized URL of the relay to use.
|
||||
* @param {RelayInformation} [options.relayInformation] - The relay information to use.
|
||||
* @returns {Promise<Group>} A promise that resolves to the loaded group.
|
||||
*/
|
||||
export async function loadGroup({
|
||||
pool,
|
||||
groupReference,
|
||||
normalizedRelayURL,
|
||||
relayInformation,
|
||||
}: {
|
||||
pool: AbstractSimplePool
|
||||
groupReference: GroupReference
|
||||
normalizedRelayURL?: string
|
||||
relayInformation?: RelayInformation
|
||||
}): Promise<Group> {
|
||||
if (!normalizedRelayURL) {
|
||||
normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)
|
||||
}
|
||||
|
||||
if (!relayInformation) {
|
||||
relayInformation = await fetchRelayInformation(normalizedRelayURL)
|
||||
}
|
||||
|
||||
const metadataEvent = await fetchGroupMetadataEvent({ pool, groupReference, normalizedRelayURL, relayInformation })
|
||||
const metadata = parseGroupMetadataEvent(metadataEvent)
|
||||
|
||||
const adminsEvent = await fetchGroupAdminsEvent({ pool, groupReference, normalizedRelayURL, relayInformation })
|
||||
const admins = parseGroupAdminsEvent(adminsEvent)
|
||||
|
||||
const membersEvent = await fetchGroupMembersEvent({ pool, groupReference, normalizedRelayURL, relayInformation })
|
||||
const members = parseGroupMembersEvent(membersEvent)
|
||||
|
||||
const group: Group = {
|
||||
relay: normalizedRelayURL,
|
||||
metadata,
|
||||
admins,
|
||||
members,
|
||||
reference: groupReference,
|
||||
}
|
||||
|
||||
return group
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a group from the specified pool using the provided group code.
|
||||
*
|
||||
* @param {AbstractSimplePool} pool - The pool to load the group from.
|
||||
* @param {string} code - The code representing the group.
|
||||
* @returns {Promise<Group>} - A promise that resolves to the loaded group.
|
||||
* @throws {Error} - If the group code is invalid.
|
||||
*/
|
||||
export async function loadGroupFromCode(pool: AbstractSimplePool, code: string): Promise<Group> {
|
||||
const groupReference = parseGroupCode(code)
|
||||
|
||||
if (!groupReference) throw new Error('invalid group code')
|
||||
|
||||
return loadGroup({ pool, groupReference })
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a group code and returns a GroupReference object.
|
||||
*
|
||||
* @param code The group code to parse.
|
||||
* @returns A GroupReference object if the code is valid, otherwise null.
|
||||
*/
|
||||
export function parseGroupCode(code: string): null | GroupReference {
|
||||
if (code.startsWith('naddr1')) {
|
||||
try {
|
||||
|
@ -99,68 +533,74 @@ export function parseGroupCode(code: string): null | GroupReference {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a group reference into a string.
|
||||
*
|
||||
* @param gr - The group reference to encode.
|
||||
* @returns The encoded group reference as a string.
|
||||
*/
|
||||
export function encodeGroupReference(gr: GroupReference): string {
|
||||
if (gr.host.startsWith('https://')) gr.host = gr.host.slice(8)
|
||||
if (gr.host.startsWith('wss://')) gr.host = gr.host.slice(6)
|
||||
return `${gr.host}'${gr.id}`
|
||||
const { host, id } = gr
|
||||
const normalizedHost = host.replace(/^(https?:\/\/|wss?:\/\/)/, '')
|
||||
|
||||
return `${normalizedHost}'${id}`
|
||||
}
|
||||
|
||||
export type Group = {
|
||||
id: string
|
||||
relay: string
|
||||
pubkey: string
|
||||
name?: string
|
||||
picture?: string
|
||||
about?: string
|
||||
public?: boolean
|
||||
open?: boolean
|
||||
}
|
||||
/**
|
||||
* Subscribes to relay groups metadata events and calls the provided event handler function
|
||||
* when an event is received.
|
||||
*
|
||||
* @param {Object} options - The options for subscribing to relay groups metadata events.
|
||||
* @param {AbstractSimplePool} options.pool - The pool to subscribe to.
|
||||
* @param {string} options.relayURL - The URL of the relay.
|
||||
* @param {Function} options.onError - The error handler function.
|
||||
* @param {Function} options.onEvent - The event handler function.
|
||||
* @param {Function} [options.onConnect] - The connect handler function.
|
||||
* @returns {Function} - A function to close the subscription
|
||||
*/
|
||||
export function subscribeRelayGroupsMetadataEvents({
|
||||
pool,
|
||||
relayURL,
|
||||
onError,
|
||||
onEvent,
|
||||
onConnect,
|
||||
}: {
|
||||
pool: AbstractSimplePool
|
||||
relayURL: string
|
||||
onError: (err: Error) => void
|
||||
onEvent: (event: Event) => void
|
||||
onConnect?: () => void
|
||||
}): () => void {
|
||||
let sub: Subscription
|
||||
|
||||
export function parseGroup(event: Event, relay: string): Group {
|
||||
const group: Partial<Group> = { relay, pubkey: event.pubkey }
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
const tag = event.tags[i]
|
||||
switch (tag[0]) {
|
||||
case 'd':
|
||||
group.id = tag[1] || ''
|
||||
break
|
||||
case 'name':
|
||||
group.name = tag[1] || ''
|
||||
break
|
||||
case 'about':
|
||||
group.about = tag[1] || ''
|
||||
break
|
||||
case 'picture':
|
||||
group.picture = tag[1] || ''
|
||||
break
|
||||
case 'open':
|
||||
group.open = true
|
||||
break
|
||||
case 'public':
|
||||
group.public = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return group as Group
|
||||
}
|
||||
const normalizedRelayURL = normalizeURL(relayURL)
|
||||
|
||||
export type Member = {
|
||||
pubkey: string
|
||||
label?: string
|
||||
permissions: string[]
|
||||
}
|
||||
fetchRelayInformation(normalizedRelayURL)
|
||||
.then(async info => {
|
||||
const abstractedRelay = await pool.ensureRelay(normalizedRelayURL)
|
||||
|
||||
export function parseMembers(event: Event): Member[] {
|
||||
const members = []
|
||||
for (let i = 0; i < event.tags.length; i++) {
|
||||
const tag = event.tags[i]
|
||||
if (tag.length < 2) continue
|
||||
if (tag[0] !== 'p') continue
|
||||
if (!tag[1].match(/^[0-9a-f]{64}$/)) continue
|
||||
const member: Member = { pubkey: tag[1], permissions: [] }
|
||||
if (tag.length > 2) member.label = tag[2]
|
||||
if (tag.length > 3) member.permissions = tag.slice(3)
|
||||
members.push(member)
|
||||
}
|
||||
return members
|
||||
onConnect?.()
|
||||
|
||||
sub = abstractedRelay.prepareSubscription(
|
||||
[
|
||||
{
|
||||
kinds: [39000],
|
||||
limit: 50,
|
||||
authors: [info.pubkey],
|
||||
},
|
||||
],
|
||||
{
|
||||
onevent(event: Event) {
|
||||
onEvent(event)
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
.catch(err => {
|
||||
sub.close()
|
||||
|
||||
onError(err)
|
||||
})
|
||||
|
||||
return () => sub.close()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue