@openclaw/buzz 2026.8.1-beta.2 → 2026.9.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter.runtime-D0uEmTwf.js → adapter.runtime-DA6guOQ_.js} +5 -5
- package/dist/api.js +2 -2
- package/dist/{channel-CCmyE6Yq.js → channel-BulZqShF.js} +234 -270
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{directory-config-BXa3Vqzu.js → directory-config-BesCse0q.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/qa-runner-api.js +2 -2
- package/dist/{room-membership-CNBlHjTj.js → room-membership-DSAkyE0a.js} +55 -5
- package/dist/setup-plugin-api.js +2 -2
- package/dist/{setup-surface-DCxlD2oY.js → setup-surface-BvQgEJ7O.js} +20 -42
- package/dist/{types-CPFsIZ5Z.js → types-BN_HOsnq.js} +18 -5
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js +7 -5
- package/node_modules/nostr-tools/lib/cjs/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js +6 -4
- package/node_modules/nostr-tools/lib/cjs/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/core.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/core.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/index.js +17 -11
- package/node_modules/nostr-tools/lib/cjs/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/kinds.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/kinds.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip04.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip10.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip10.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip13.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip17.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip17.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip18.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip18.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip19.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip21.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip22.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip22.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip25.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip25.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip27.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip28.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip28.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip29.js +288 -12
- package/node_modules/nostr-tools/lib/cjs/nip29.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip44.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/nip45.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip45.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip46.js +9 -5
- package/node_modules/nostr-tools/lib/cjs/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip47.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip47.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip57.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip57.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip59.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip59.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nip98.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nip98.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nipb0.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/nipb7.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/nipb7.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/pool.js +9 -5
- package/node_modules/nostr-tools/lib/cjs/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/pure.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/pure.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/references.js.map +1 -1
- package/node_modules/nostr-tools/lib/cjs/relay.js +8 -4
- package/node_modules/nostr-tools/lib/cjs/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/signer.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/signer.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/utils.js +2 -0
- package/node_modules/nostr-tools/lib/cjs/utils.js.map +2 -2
- package/node_modules/nostr-tools/lib/cjs/wasm.js +10 -6
- package/node_modules/nostr-tools/lib/cjs/wasm.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js +7 -5
- package/node_modules/nostr-tools/lib/esm/abstract-pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js +6 -4
- package/node_modules/nostr-tools/lib/esm/abstract-relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/core.js +10 -6
- package/node_modules/nostr-tools/lib/esm/core.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/index.js +17 -11
- package/node_modules/nostr-tools/lib/esm/index.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/kinds.js +2 -0
- package/node_modules/nostr-tools/lib/esm/kinds.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip04.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip10.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip10.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip13.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip17.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip17.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip18.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip18.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip19.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip21.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip22.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip22.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip25.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip25.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip27.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip28.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip28.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip29.js +288 -12
- package/node_modules/nostr-tools/lib/esm/nip29.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip44.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/nip45.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip45.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip46.js +9 -5
- package/node_modules/nostr-tools/lib/esm/nip46.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip47.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip47.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip57.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip57.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip59.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip59.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nip98.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nip98.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nipb0.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nipb0.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/nipb7.js +2 -0
- package/node_modules/nostr-tools/lib/esm/nipb7.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/pool.js +9 -5
- package/node_modules/nostr-tools/lib/esm/pool.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/pure.js +10 -6
- package/node_modules/nostr-tools/lib/esm/pure.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/references.js.map +1 -1
- package/node_modules/nostr-tools/lib/esm/relay.js +8 -4
- package/node_modules/nostr-tools/lib/esm/relay.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/signer.js +2 -0
- package/node_modules/nostr-tools/lib/esm/signer.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/utils.js +2 -0
- package/node_modules/nostr-tools/lib/esm/utils.js.map +2 -2
- package/node_modules/nostr-tools/lib/esm/wasm.js +10 -6
- package/node_modules/nostr-tools/lib/esm/wasm.js.map +2 -2
- package/node_modules/nostr-tools/lib/nostr.bundle.js +17 -11
- package/node_modules/nostr-tools/lib/nostr.bundle.js.map +2 -2
- package/node_modules/nostr-tools/lib/types/core.d.ts +5 -0
- package/node_modules/nostr-tools/lib/types/nip29.d.ts +243 -3
- package/node_modules/nostr-tools/lib/types/nip29.test.d.ts +1 -0
- package/node_modules/nostr-tools/package.json +1 -1
- package/openclaw.plugin.json +24 -0
- package/package.json +5 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../nip29.ts", "../../nip11.ts", "../../nip19.ts", "../../utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { AbstractSimplePool } from './abstract-pool.ts'\nimport { Subscription } from './abstract-relay.ts'\nimport type { Event, EventTemplate } from './core.ts'\nimport { fetchRelayInformation, RelayInformation } from './nip11.ts'\nimport { decode, NostrTypeGuard } from './nip19.ts'\nimport { normalizeURL } from './utils.ts'\n\n/**\n * Represents a NIP29 group.\n */\nexport type Group = {\n relay: string\n metadata: GroupMetadata\n admins?: GroupAdmin[]\n members?: GroupMember[]\n reference: GroupReference\n}\n\n/**\n * Represents the metadata for a NIP29 group.\n */\nexport type GroupMetadata = {\n id: string\n pubkey: string\n name?: string\n picture?: string\n about?: string\n isPublic?: boolean\n isOpen?: boolean\n}\n\n/**\n * Represents a NIP29 group reference.\n */\nexport type GroupReference = {\n id: string\n host: string\n}\n\n/**\n * Represents a NIP29 group member.\n */\nexport type GroupMember = {\n pubkey: string\n label?: string\n}\n\n/**\n * Represents a NIP29 group admin.\n */\nexport type GroupAdmin = {\n pubkey: string\n label?: string\n permissions: GroupAdminPermission[]\n}\n\n/**\n * Represents the permissions that a NIP29 group admin can have.\n */\nexport enum GroupAdminPermission {\n /** @deprecated use PutUser instead */\n AddUser = 'add-user',\n EditMetadata = 'edit-metadata',\n DeleteEvent = 'delete-event',\n RemoveUser = 'remove-user',\n /** @deprecated removed from NIP */\n AddPermission = 'add-permission',\n /** @deprecated removed from NIP */\n RemovePermission = 'remove-permission',\n /** @deprecated removed from NIP */\n EditGroupStatus = 'edit-group-status',\n PutUser = 'put-user',\n CreateGroup = 'create-group',\n DeleteGroup = 'delete-group',\n CreateInvite = 'create-invite',\n}\n\n/**\n * Generates a group metadata event template.\n *\n * @param group - The group object.\n * @returns An event template with the generated group metadata that can be signed later.\n */\nexport function generateGroupMetadataEventTemplate(group: Group): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n group.metadata.name && tags.push(['name', group.metadata.name])\n group.metadata.picture && tags.push(['picture', group.metadata.picture])\n group.metadata.about && tags.push(['about', group.metadata.about])\n group.metadata.isPublic && tags.push(['public'])\n group.metadata.isOpen && tags.push(['open'])\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39000,\n tags,\n }\n}\n\n/**\n * Validates a group metadata event.\n *\n * @param event - The event to validate.\n * @returns A boolean indicating whether the event is valid.\n */\nexport function validateGroupMetadataEvent(event: Event): boolean {\n if (event.kind !== 39000) return false\n\n if (!event.pubkey) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Generates an event template for group admins.\n *\n * @param group - The group object.\n * @param admins - An array of group admins.\n * @returns The generated event template with the group admins that can be signed later.\n */\nexport function generateGroupAdminsEventTemplate(group: Group, admins: GroupAdmin[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n for (const admin of admins) {\n tags.push(['p', admin.pubkey, admin.label || '', ...admin.permissions])\n }\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39001,\n tags,\n }\n}\n\n/**\n * Validates a group admins event.\n *\n * @param event - The event to validate.\n * @returns True if the event is valid, false otherwise.\n */\nexport function validateGroupAdminsEvent(event: Event): boolean {\n if (event.kind !== 39001) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n // validate permissions\n for (const [tag, _value, _label, ...permissions] of event.tags) {\n if (tag !== 'p') continue\n\n for (let i = 0; i < permissions.length; i += 1) {\n if (typeof permissions[i] !== 'string') return false\n\n // validate permission name from the GroupAdminPermission enum\n if (!Object.values(GroupAdminPermission).includes(permissions[i] as GroupAdminPermission)) return false\n }\n }\n\n return true\n}\n\n/**\n * Generates an event template for a group with its members.\n *\n * @param group - The group object.\n * @param members - An array of group members.\n * @returns The generated event template with the group members that can be signed later.\n */\nexport function generateGroupMembersEventTemplate(group: Group, members: GroupMember[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n for (const member of members) {\n tags.push(['p', member.pubkey, member.label || ''])\n }\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39002,\n tags,\n }\n}\n\n/**\n * Validates a group members event.\n *\n * @param event - The event to validate.\n * @returns Returns `true` if the event is a valid group members event, `false` otherwise.\n */\nexport function validateGroupMembersEvent(event: Event): boolean {\n if (event.kind !== 39002) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Returns the normalized relay URL based on the provided group reference.\n *\n * @param groupReference - The group reference object containing the host.\n * @returns The normalized relay URL.\n */\nexport function getNormalizedRelayURLByGroupReference(groupReference: GroupReference): string {\n return normalizeURL(groupReference.host)\n}\n\n/**\n * Fetches relay information by group reference.\n *\n * @param groupReference The group reference.\n * @returns A promise that resolves to the relay information.\n */\nexport async function fetchRelayInformationByGroupReference(groupReference: GroupReference): Promise<RelayInformation> {\n const normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n\n return fetchRelayInformation(normalizedRelayURL)\n}\n\n/**\n * Fetches the group metadata event from the specified pool.\n * If the normalizedRelayURL is not provided, it will be obtained using the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool to fetch the group metadata event from.\n * @param {GroupReference} options.groupReference - The reference to the group.\n * @param {string} [options.normalizedRelayURL] - The normalized URL of the relay.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group metadata event that can be parsed later to get the group metadata object.\n * @throws {Error} If the group is not found on the specified relay.\n */\nexport async function fetchGroupMetadataEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupMetadataEvent = await pool.get([normalizedRelayURL], {\n kinds: [39000],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupMetadataEvent) throw new Error(`group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupMetadataEvent\n}\n\n/**\n * Parses a group metadata event and returns the corresponding GroupMetadata object.\n *\n * @param event - The event to parse.\n * @returns The parsed GroupMetadata object.\n * @throws An error if the group metadata event is invalid.\n */\nexport function parseGroupMetadataEvent(event: Event): GroupMetadata {\n if (!validateGroupMetadataEvent(event)) throw new Error('invalid group metadata event')\n\n const metadata: GroupMetadata = {\n id: '',\n pubkey: event.pubkey,\n }\n\n for (const [tag, value] of event.tags) {\n switch (tag) {\n case 'd':\n metadata.id = value\n break\n case 'name':\n metadata.name = value\n break\n case 'picture':\n metadata.picture = value\n break\n case 'about':\n metadata.about = value\n break\n case 'public':\n metadata.isPublic = true\n break\n case 'open':\n metadata.isOpen = true\n break\n }\n }\n\n return metadata\n}\n\n/**\n * Fetches the group admins event from the specified pool.\n * If the normalizedRelayURL is not provided, it will be obtained from the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool to fetch the group admins event from.\n * @param {GroupReference} options.groupReference - The reference to the group.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information.\n * @returns {Promise<Event>} The group admins event that can be parsed later to get the group admins object.\n * @throws {Error} If the group admins event is not found on the specified relay.\n */\nexport async function fetchGroupAdminsEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupAdminsEvent = await pool.get([normalizedRelayURL], {\n kinds: [39001],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupAdminsEvent) throw new Error(`admins for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupAdminsEvent\n}\n\n/**\n * Parses a group admins event and returns an array of GroupAdmin objects.\n *\n * @param event - The event to parse.\n * @returns An array of GroupAdmin objects.\n * @throws Throws an error if the group admins event is invalid.\n */\nexport function parseGroupAdminsEvent(event: Event): GroupAdmin[] {\n if (!validateGroupAdminsEvent(event)) throw new Error('invalid group admins event')\n\n const admins: GroupAdmin[] = []\n\n for (const [tag, value, label, ...permissions] of event.tags) {\n if (tag !== 'p') continue\n\n admins.push({\n pubkey: value,\n label,\n permissions: permissions as GroupAdminPermission[],\n })\n }\n\n return admins\n}\n\n/**\n * Fetches the group members event from the specified relay.\n * If the normalizedRelayURL is not provided, it will be obtained using the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool object.\n * @param {GroupReference} options.groupReference - The group reference object.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group members event that can be parsed later to get the group members object.\n * @throws {Error} If the group members event is not found.\n */\nexport async function fetchGroupMembersEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupMembersEvent = await pool.get([normalizedRelayURL], {\n kinds: [39002],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupMembersEvent) throw new Error(`members for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupMembersEvent\n}\n\n/**\n * Parses a group members event and returns an array of GroupMember objects.\n * @param event - The event to parse.\n * @returns An array of GroupMember objects.\n * @throws Throws an error if the group members event is invalid.\n */\nexport function parseGroupMembersEvent(event: Event): GroupMember[] {\n if (!validateGroupMembersEvent(event)) throw new Error('invalid group members event')\n\n const members: GroupMember[] = []\n\n for (const [tag, value, label] of event.tags) {\n if (tag !== 'p') continue\n\n members.push({\n pubkey: value,\n label,\n })\n }\n\n return members\n}\n\n/**\n * Fetches and parses the group metadata event, group admins event, and group members event from the specified pool.\n * If the normalized relay URL is not provided, it will be obtained using the group reference.\n * If the relay information is not provided, it will be fetched using the normalized relay URL.\n *\n * @param {Object} options - The options for loading the group.\n * @param {AbstractSimplePool} options.pool - The pool to load the group from.\n * @param {GroupReference} options.groupReference - The reference of the group to load.\n * @param {string} [options.normalizedRelayURL] - The normalized URL of the relay to use.\n * @param {RelayInformation} [options.relayInformation] - The relay information to use.\n * @returns {Promise<Group>} A promise that resolves to the loaded group.\n */\nexport async function loadGroup({\n pool,\n groupReference,\n normalizedRelayURL,\n relayInformation,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Group> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const metadataEvent = await fetchGroupMetadataEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const metadata = parseGroupMetadataEvent(metadataEvent)\n\n const adminsEvent = await fetchGroupAdminsEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const admins = parseGroupAdminsEvent(adminsEvent)\n\n const membersEvent = await fetchGroupMembersEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const members = parseGroupMembersEvent(membersEvent)\n\n const group: Group = {\n relay: normalizedRelayURL,\n metadata,\n admins,\n members,\n reference: groupReference,\n }\n\n return group\n}\n\n/**\n * Loads a group from the specified pool using the provided group code.\n *\n * @param {AbstractSimplePool} pool - The pool to load the group from.\n * @param {string} code - The code representing the group.\n * @returns {Promise<Group>} - A promise that resolves to the loaded group.\n * @throws {Error} - If the group code is invalid.\n */\nexport async function loadGroupFromCode(pool: AbstractSimplePool, code: string): Promise<Group> {\n const groupReference = parseGroupCode(code)\n\n if (!groupReference) throw new Error('invalid group code')\n\n return loadGroup({ pool, groupReference })\n}\n\n/**\n * Parses a group code and returns a GroupReference object.\n *\n * @param code The group code to parse.\n * @returns A GroupReference object if the code is valid, otherwise null.\n */\nexport function parseGroupCode(code: string): null | GroupReference {\n if (NostrTypeGuard.isNAddr(code)) {\n try {\n let { data } = decode(code)\n\n let { relays, identifier } = data\n if (!relays || relays.length === 0) return null\n\n let host = relays![0]\n if (host.startsWith('wss://')) {\n host = host.slice(6)\n }\n return { host, id: identifier }\n } catch (err) {\n return null\n }\n } else if (code.split(\"'\").length === 2) {\n let spl = code.split(\"'\")\n return { host: spl[0], id: spl[1] }\n }\n\n return null\n}\n\n/**\n * Encodes a group reference into a string.\n *\n * @param gr - The group reference to encode.\n * @returns The encoded group reference as a string.\n */\nexport function encodeGroupReference(gr: GroupReference): string {\n const { host, id } = gr\n const normalizedHost = host.replace(/^(https?:\\/\\/|wss?:\\/\\/)/, '')\n\n return `${normalizedHost}'${id}`\n}\n\n/**\n * Subscribes to relay groups metadata events and calls the provided event handler function\n * when an event is received.\n *\n * @param {Object} options - The options for subscribing to relay groups metadata events.\n * @param {AbstractSimplePool} options.pool - The pool to subscribe to.\n * @param {string} options.relayURL - The URL of the relay.\n * @param {Function} options.onError - The error handler function.\n * @param {Function} options.onEvent - The event handler function.\n * @param {Function} [options.onConnect] - The connect handler function.\n * @returns {Function} - A function to close the subscription\n */\nexport function subscribeRelayGroupsMetadataEvents({\n pool,\n relayURL,\n onError,\n onEvent,\n onConnect,\n}: {\n pool: AbstractSimplePool\n relayURL: string\n onError: (err: Error) => void\n onEvent: (event: Event) => void\n onConnect?: () => void\n}): () => void {\n let sub: Subscription\n\n const normalizedRelayURL = normalizeURL(relayURL)\n\n fetchRelayInformation(normalizedRelayURL)\n .then(async info => {\n const abstractedRelay = await pool.ensureRelay(normalizedRelayURL)\n\n onConnect?.()\n\n sub = abstractedRelay.prepareSubscription(\n [\n {\n kinds: [39000],\n limit: 50,\n authors: [info.pubkey],\n },\n ],\n {\n onevent(event: Event) {\n onEvent(event)\n },\n },\n )\n })\n .catch(err => {\n sub.close()\n\n onError(err)\n })\n\n return () => sub.close()\n}\n", "var _fetch: any\n\ntry {\n _fetch = fetch\n} catch {}\n\nexport function useFetchImplementation(fetchImplementation: any): void {\n _fetch = fetchImplementation\n}\n\nexport async function fetchRelayInformation(url: string): Promise<RelayInformation> {\n return (await (\n await fetch(url.replace('ws://', 'http://').replace('wss://', 'https://'), {\n headers: { Accept: 'application/nostr+json' },\n })\n ).json()) as RelayInformation\n}\n\n/**\n * ## Relay Information Document\n\n * Relays may provide server metadata to clients to inform\n * them of capabilities, administrative contacts, and\n * various server attributes. This is made available as a\n * JSON document over HTTP, on the same URI as the relay's\n * websocket.\n\n * Any field may be omitted, and clients MUST ignore any\n * additional fields they do not understand. Relays MUST\n * accept CORS requests by sending\n * `Access-Control-Allow-Origin`,\n * `Access-Control-Allow-Headers`, and\n * `Access-Control-Allow-Methods` headers.\n * @param name string identifying relay\n * @param description string with detailed information\n * @param pubkey administrative contact pubkey\n * @param contact: administrative alternate contact\n * @param supported_nips a list of NIP numbers supported by\n * the relay\n * @param software identifying relay software URL\n * @param version string version identifier\n */\nexport interface BasicRelayInformation {\n // string identifying relay\n name: string\n description: string\n pubkey: string\n contact: string\n supported_nips: number[]\n software: string\n version: string\n // limitation?: Limitations<A, P>\n}\n\n/**\n * * ## Extra Fields\n\n * * ### Server Limitations\n\n * These are limitations imposed by the relay on clients.\n * Your client should expect that requests which exceed\n * these practical_ limitations are rejected or fail immediately.\n * @param max_message_length this is the maximum number of\n * bytes for incoming JSON that the relay will attempt to\n * decode and act upon. When you send large subscriptions,\n * you will be limited by this value. It also effectively\n * limits the maximum size of any event. Value is calculated\n * from `[` to `]` and is after UTF-8 serialization (so some\n * unicode characters will cost 2-3 bytes). It is equal to\n * the maximum size of the WebSocket message frame.\n * @param max_subscriptions total number of subscriptions\n * that may be active on a single websocket connection to\n * this relay. It's possible that authenticated clients with\n * a (paid) relationship to the relay may have higher limits.\n * @param max_filters maximum number of filter values in\n * each subscription. Must be one or higher.\n * @param max_limit the relay server will clamp each\n * filter's `limit` value to this number.\n * This means the client won't be able to get more than this\n * number of events from a single subscription filter. This\n * clamping is typically done silently by the relay, but\n * with this number, you can know that there are additional\n * results if you narrowed your filter's time range or other\n * parameters.\n * @param max_subid_length maximum length of subscription id as a\n * string.\n * @param min_prefix for `authors` and `ids` filters which\n * are to match against a hex prefix, you must provide at\n * least this many hex digits in the prefix.\n * @param max_event_tags in any event, this is the maximum\n * number of elements in the `tags` list.\n * @param max_content_length maximum number of characters in\n * the `content` field of any event. This is a count of\n * unicode characters. After serializing into JSON it may be\n * larger (in bytes), and is still subject to the\n * max_message_length`, if defined.\n * @param min_pow_difficulty new events will require at\n * least this difficulty of PoW, based on [NIP-13](13.md),\n * or they will be rejected by this server.\n * @param auth_required this relay requires [NIP-42](42.md)\n * authentication to happen before a new connection may\n * perform any other action. Even if set to False,\n * authentication may be required for specific actions.\n * @param restricted_writes: this relay requires some kind\n * of condition to be fulfilled in order to accept events\n * (not necessarily, but including\n * @param payment_required this relay requires payment\n * before a new connection may perform any action.\n * @param created_at_lower_limit: 'created_at' lower limit\n * @param created_at_upper_limit: 'created_at' upper limit\n */\nexport interface Limitations {\n max_message_length: number\n max_subscriptions: number\n max_filters: number\n max_limit: number\n max_subid_length: number\n min_prefix: number\n max_event_tags: number\n max_content_length: number\n min_pow_difficulty: number\n auth_required: boolean\n payment_required: boolean\n created_at_lower_limit: number\n created_at_upper_limit: number\n restricted_writes: boolean\n}\n\nexport interface RetentionDetails {\n kinds: (number | number[])[]\n time?: number | null\n count?: number | null\n}\ntype AnyRetentionDetails = RetentionDetails\n/**\n * ### Event Retention\n\n * There may be a cost associated with storing data forever,\n * so relays may wish to state retention times. The values\n * stated here are defaults for unauthenticated users and\n * visitors. Paid users would likely have other policies.\n\n * Retention times are given in seconds, with `null`\n * indicating infinity. If zero is provided, this means the\n * event will not be stored at all, and preferably an error\n * will be provided when those are received.\n * ```json\n{\n...\n \"retention\": [\n { \"kinds\": [0, 1, [5, 7], [40, 49]], \"time\": 3600 },\n { \"kinds\": [[40000, 49999]], \"time\": 100 },\n { \"kinds\": [[30000, 39999]], \"count\": 1000 },\n { \"time\": 3600, \"count\": 10000 }\n ]\n...\n}\n```\n * @param retention is a list of specifications: each will\n * apply to either all kinds, or a subset of kinds. Ranges\n * may be specified for the kind field as a tuple of\n * inclusive start and end values. Events of indicated kind\n * (or all) are then limited to a `count` and/or time\n * period.\n\n * It is possible to effectively blacklist Nostr-based\n * protocols that rely on a specific `kind` number, by\n * giving a retention time of zero for those `kind` values.\n * While that is unfortunate, it does allow clients to\n * discover servers that will support their protocol quickly\n * via a single HTTP fetch.\n\n * There is no need to specify retention times for\n * _ephemeral events_ as defined in [NIP-16](16.md) since\n * they are not retained.\n */\nexport interface Retention {\n retention: AnyRetentionDetails[]\n}\n\n/**\n * Some relays may be governed by the arbitrary laws of a\n * nation state. This may limit what content can be stored\n * in cleartext on those relays. All clients are encouraged\n * to use encryption to work around this limitation.\n\n * It is not possible to describe the limitations of each\n * country's laws and policies which themselves are\n * typically vague and constantly shifting.\n\n * Therefore, this field allows the relay operator to\n * indicate which countries' laws might end up being\n * enforced on them, and then indirectly on their users'\n * content.\n\n * Users should be able to avoid relays in countries they\n * don't like, and/or select relays in more favourable\n * zones. Exposing this flexibility is up to the client\n * software.\n\n * @param relay_countries a list of two-level ISO country\n * codes (ISO 3166-1 alpha-2) whose laws and policies may\n * affect this relay. `EU` may be used for European Union\n * countries.\n\n * Remember that a relay may be hosted in a country which is\n * not the country of the legal entities who own the relay,\n * so it's very likely a number of countries are involved.\n */\nexport interface ContentLimitations {\n relay_countries: string[]\n}\n\n/**\n * ### Community Preferences\n\n * For public text notes at least, a relay may try to foster\n * a local community. This would encourage users to follow\n * the global feed on that relay, in addition to their usual\n * individual follows. To support this goal, relays MAY\n * specify some of the following values.\n\n * @param language_tags is an ordered list of [IETF\n * language\n * tags](https://en.wikipedia.org/wiki/IETF_language_tag\n * indicating the major languages spoken on the relay.\n * @param tags is a list of limitations on the topics to be\n * discussed. For example `sfw-only` indicates that only\n * \"Safe For Work\" content is encouraged on this relay. This\n * relies on assumptions of what the \"work\" \"community\"\n * feels \"safe\" talking about. In time, a common set of tags\n * may emerge that allow users to find relays that suit\n * their needs, and client software will be able to parse\n * these tags easily. The `bitcoin-only` tag indicates that\n * any _altcoin_, _\"crypto\"_ or _blockchain_ comments will\n * be ridiculed without mercy.\n * @param posting_policy is a link to a human-readable page\n * which specifies the community policies for the relay. In\n * cases where `sfw-only` is True, it's important to link to\n * a page which gets into the specifics of your posting\n * policy.\n\n * The `description` field should be used to describe your\n * community goals and values, in brief. The\n * `posting_policy` is for additional detail and legal\n * terms. Use the `tags` field to signify limitations on\n * content, or topics to be discussed, which could be\n * machine processed by appropriate client software.\n */\nexport interface CommunityPreferences {\n language_tags: string[]\n tags: string[]\n posting_policy: string\n}\n\nexport interface Amount {\n amount: number\n unit: 'msat'\n}\nexport interface PublicationAmount extends Amount {\n kinds: number[]\n}\nexport interface Subscription extends Amount {\n period: number\n}\nexport interface Fees {\n admission: Amount[]\n subscription: Subscription[]\n publication: PublicationAmount[]\n}\n/**\n * Relays that require payments may want to expose their fee\n * schedules.\n */\nexport interface PayToRelay {\n payments_url: string\n fees: Fees\n}\n\n/**\n * A URL pointing to an image to be used as an icon for the\n * relay. Recommended to be squared in shape.\n */\nexport interface Icon {\n icon: string\n}\n\nexport type RelayInformation = BasicRelayInformation &\n Partial<Retention> & {\n limitation?: Partial<Limitations>\n } & Partial<ContentLimitations> &\n Partial<CommunityPreferences> &\n Partial<PayToRelay> &\n Partial<Icon>\n", "import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils.js'\nimport { bech32 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nexport type NProfile = `nprofile1${string}`\nexport type NEvent = `nevent1${string}`\nexport type NAddr = `naddr1${string}`\nexport type NSec = `nsec1${string}`\nexport type NPub = `npub1${string}`\nexport type Note = `note1${string}`\nexport type Ncryptsec = `ncryptsec1${string}`\n\nexport const NostrTypeGuard = {\n isNProfile: (value?: string | null): value is NProfile => /^nprofile1[a-z\\d]+$/.test(value || ''),\n isNEvent: (value?: string | null): value is NEvent => /^nevent1[a-z\\d]+$/.test(value || ''),\n isNAddr: (value?: string | null): value is NAddr => /^naddr1[a-z\\d]+$/.test(value || ''),\n isNSec: (value?: string | null): value is NSec => /^nsec1[a-z\\d]{58}$/.test(value || ''),\n isNPub: (value?: string | null): value is NPub => /^npub1[a-z\\d]{58}$/.test(value || ''),\n isNote: (value?: string | null): value is Note => /^note1[a-z\\d]+$/.test(value || ''),\n isNcryptsec: (value?: string | null): value is Ncryptsec => /^ncryptsec1[a-z\\d]+$/.test(value || ''),\n}\n\nexport const Bech32MaxSize = 5000\n\n/**\n * Bech32 regex.\n * @see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32\n */\nexport const BECH32_REGEX = /[\\x21-\\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/\n\nfunction integerToUint8Array(number: number) {\n // Create a Uint8Array with enough space to hold a 32-bit integer (4 bytes).\n const uint8Array = new Uint8Array(4)\n\n // Use bitwise operations to extract the bytes.\n uint8Array[0] = (number >> 24) & 0xff // Most significant byte (MSB)\n uint8Array[1] = (number >> 16) & 0xff\n uint8Array[2] = (number >> 8) & 0xff\n uint8Array[3] = number & 0xff // Least significant byte (LSB)\n\n return uint8Array\n}\n\nexport type ProfilePointer = {\n pubkey: string // hex\n relays?: string[]\n}\n\nexport type EventPointer = {\n id: string // hex\n relays?: string[]\n author?: string\n kind?: number\n}\n\nexport type AddressPointer = {\n identifier: string\n pubkey: string\n kind: number\n relays?: string[]\n}\n\nexport function decodeNostrURI(nip19code: string): ReturnType<typeof decode> | { type: 'invalid'; data: null } {\n try {\n if (nip19code.startsWith('nostr:')) nip19code = nip19code.substring(6)\n return decode(nip19code)\n } catch (_err) {\n return { type: 'invalid', data: null }\n }\n}\n\nexport type DecodedNevent = {\n type: 'nevent'\n data: EventPointer\n}\n\nexport type DecodedNprofile = {\n type: 'nprofile'\n data: ProfilePointer\n}\n\nexport type DecodedNaddr = {\n type: 'naddr'\n data: AddressPointer\n}\n\nexport type DecodedNsec = {\n type: 'nsec'\n data: Uint8Array\n}\n\nexport type DecodedNpub = {\n type: 'npub'\n data: string\n}\n\nexport type DecodedNote = {\n type: 'note'\n data: string\n}\n\nexport type DecodedResult = DecodedNevent | DecodedNprofile | DecodedNaddr | DecodedNpub | DecodedNsec | DecodedNote\n\nexport function decode(nip19: NEvent): DecodedNevent\nexport function decode(nip19: NProfile): DecodedNprofile\nexport function decode(nip19: NAddr): DecodedNaddr\nexport function decode(nip19: NSec): DecodedNsec\nexport function decode(nip19: NPub): DecodedNpub\nexport function decode(nip19: Note): DecodedNote\nexport function decode(code: string): DecodedResult\nexport function decode(code: string): DecodedResult {\n let { prefix, words } = bech32.decode(code as `${string}1${string}`, Bech32MaxSize)\n let data = new Uint8Array(bech32.fromWords(words))\n\n switch (prefix) {\n case 'nprofile': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')\n if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')\n\n return {\n type: 'nprofile',\n data: {\n pubkey: bytesToHex(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n },\n }\n }\n case 'nevent': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nevent')\n if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')\n if (tlv[2] && tlv[2][0].length !== 32) throw new Error('TLV 2 should be 32 bytes')\n if (tlv[3] && tlv[3][0].length !== 4) throw new Error('TLV 3 should be 4 bytes')\n\n return {\n type: 'nevent',\n data: {\n id: bytesToHex(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n author: tlv[2]?.[0] ? bytesToHex(tlv[2][0]) : undefined,\n kind: tlv[3]?.[0] ? parseInt(bytesToHex(tlv[3][0]), 16) : undefined,\n },\n }\n }\n\n case 'naddr': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for naddr')\n if (!tlv[2]?.[0]) throw new Error('missing TLV 2 for naddr')\n if (tlv[2][0].length !== 32) throw new Error('TLV 2 should be 32 bytes')\n if (!tlv[3]?.[0]) throw new Error('missing TLV 3 for naddr')\n if (tlv[3][0].length !== 4) throw new Error('TLV 3 should be 4 bytes')\n\n return {\n type: 'naddr',\n data: {\n identifier: utf8Decoder.decode(tlv[0][0]),\n pubkey: bytesToHex(tlv[2][0]),\n kind: parseInt(bytesToHex(tlv[3][0]), 16),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n },\n }\n }\n\n case 'nsec':\n return { type: prefix, data }\n\n case 'npub':\n case 'note':\n return { type: prefix, data: bytesToHex(data) }\n\n default:\n throw new Error(`unknown prefix ${prefix}`)\n }\n}\n\ntype TLV = { [t: number]: Uint8Array[] }\n\nfunction parseTLV(data: Uint8Array): TLV {\n let result: TLV = {}\n let rest = data\n while (rest.length > 0) {\n if (rest.length < 2) throw new Error('not enough data to read TLV')\n let t = rest[0]\n let l = rest[1]\n let v = rest.slice(2, 2 + l)\n rest = rest.slice(2 + l)\n if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)\n result[t] = result[t] || []\n result[t].push(v)\n }\n return result\n}\n\nexport function nsecEncode(key: Uint8Array): NSec {\n return encodeBytes('nsec', key)\n}\n\nexport function npubEncode(hex: string): NPub {\n return encodeBytes('npub', hexToBytes(hex))\n}\n\nexport function noteEncode(hex: string): Note {\n return encodeBytes('note', hexToBytes(hex))\n}\n\nfunction encodeBech32<Prefix extends string>(prefix: Prefix, data: Uint8Array): `${Prefix}1${string}` {\n let words = bech32.toWords(data)\n return bech32.encode(prefix, words, Bech32MaxSize) as `${Prefix}1${string}`\n}\n\nexport function encodeBytes<Prefix extends string>(prefix: Prefix, bytes: Uint8Array): `${Prefix}1${string}` {\n return encodeBech32(prefix, bytes)\n}\n\nexport function nprofileEncode(profile: ProfilePointer): NProfile {\n let data = encodeTLV({\n 0: [hexToBytes(profile.pubkey)],\n 1: (profile.relays || []).map(url => utf8Encoder.encode(url)),\n })\n return encodeBech32('nprofile', data)\n}\n\nexport function neventEncode(event: EventPointer): NEvent {\n let kindArray\n if (event.kind !== undefined) {\n kindArray = integerToUint8Array(event.kind)\n }\n\n let data = encodeTLV({\n 0: [hexToBytes(event.id)],\n 1: (event.relays || []).map(url => utf8Encoder.encode(url)),\n 2: event.author ? [hexToBytes(event.author)] : [],\n 3: kindArray ? [new Uint8Array(kindArray)] : [],\n })\n\n return encodeBech32('nevent', data)\n}\n\nexport function naddrEncode(addr: AddressPointer): NAddr {\n let kind = new ArrayBuffer(4)\n new DataView(kind).setUint32(0, addr.kind, false)\n\n let data = encodeTLV({\n 0: [utf8Encoder.encode(addr.identifier)],\n 1: (addr.relays || []).map(url => utf8Encoder.encode(url)),\n 2: [hexToBytes(addr.pubkey)],\n 3: [new Uint8Array(kind)],\n })\n return encodeBech32('naddr', data)\n}\n\nfunction encodeTLV(tlv: TLV): Uint8Array {\n let entries: Uint8Array[] = []\n\n Object.entries(tlv)\n .reverse()\n .forEach(([t, vs]) => {\n vs.forEach(v => {\n let entry = new Uint8Array(v.length + 2)\n entry.set([parseInt(t)], 0)\n entry.set([v.length], 1)\n entry.set(v, 2)\n entries.push(entry)\n })\n })\n\n return concatBytes(...entries)\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAI;AAEJ,IAAI;AACF,WAAS;AACX,QAAE;AAAO;AAMT,eAAsB,sBAAsB,KAAwC;AAClF,SAAQ,OACN,MAAM,MAAM,IAAI,QAAQ,SAAS,SAAS,EAAE,QAAQ,UAAU,UAAU,GAAG;AAAA,IACzE,SAAS,EAAE,QAAQ,yBAAyB;AAAA,EAC9C,CAAC,GACD,KAAK;AACT;;;AChBA,IAAAA,gBAAoD;AACpD,kBAAuB;;;ACIvB,mBAAuC;AAHhC,IAAM,cAA2B,IAAI,YAAY,OAAO;AACxD,IAAM,cAA2B,IAAI,YAAY;AAIjD,SAAS,aAAa,KAAqB;AAChD,MAAI;AACF,QAAI,IAAI,QAAQ,KAAK,MAAM;AAAI,YAAM,WAAW;AAChD,QAAI,IAAI,IAAI,IAAI,GAAG;AACnB,QAAI,EAAE,aAAa;AAAS,QAAE,WAAW;AAAA,aAChC,EAAE,aAAa;AAAU,QAAE,WAAW;AAC/C,MAAE,WAAW,EAAE,SAAS,QAAQ,QAAQ,GAAG;AAC3C,QAAI,EAAE,SAAS,SAAS,GAAG;AAAG,QAAE,WAAW,EAAE,SAAS,MAAM,GAAG,EAAE;AACjE,QAAK,EAAE,SAAS,QAAQ,EAAE,aAAa,SAAW,EAAE,SAAS,SAAS,EAAE,aAAa;AAAS,QAAE,OAAO;AACvG,MAAE,aAAa,KAAK;AACpB,MAAE,OAAO;AACT,WAAO,EAAE,SAAS;AAAA,EACpB,SAAS,GAAP;AACA,UAAM,IAAI,MAAM,gBAAgB,KAAK;AAAA,EACvC;AACF;;;ADTO,IAAM,iBAAiB;AAAA,EAC5B,YAAY,CAAC,UAA6C,sBAAsB,KAAK,SAAS,EAAE;AAAA,EAChG,UAAU,CAAC,UAA2C,oBAAoB,KAAK,SAAS,EAAE;AAAA,EAC1F,SAAS,CAAC,UAA0C,mBAAmB,KAAK,SAAS,EAAE;AAAA,EACvF,QAAQ,CAAC,UAAyC,qBAAqB,KAAK,SAAS,EAAE;AAAA,EACvF,QAAQ,CAAC,UAAyC,qBAAqB,KAAK,SAAS,EAAE;AAAA,EACvF,QAAQ,CAAC,UAAyC,kBAAkB,KAAK,SAAS,EAAE;AAAA,EACpF,aAAa,CAAC,UAA8C,uBAAuB,KAAK,SAAS,EAAE;AACrG;AAEO,IAAM,gBAAgB;AAwFtB,SAAS,OAAO,MAA6B;AAClD,MAAI,EAAE,QAAQ,MAAM,IAAI,mBAAO,OAAO,MAA+B,aAAa;AAClF,MAAI,OAAO,IAAI,WAAW,mBAAO,UAAU,KAAK,CAAC;AAEjD,UAAQ,QAAQ;AAAA,IACd,KAAK,YAAY;AACf,UAAI,MAAM,SAAS,IAAI;AACvB,UAAI,CAAC,IAAI,KAAK;AAAI,cAAM,IAAI,MAAM,4BAA4B;AAC9D,UAAI,IAAI,GAAG,GAAG,WAAW;AAAI,cAAM,IAAI,MAAM,0BAA0B;AAEvE,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,YAAQ,0BAAW,IAAI,GAAG,EAAE;AAAA,UAC5B,QAAQ,IAAI,KAAK,IAAI,GAAG,IAAI,OAAK,YAAY,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,IACA,KAAK,UAAU;AACb,UAAI,MAAM,SAAS,IAAI;AACvB,UAAI,CAAC,IAAI,KAAK;AAAI,cAAM,IAAI,MAAM,0BAA0B;AAC5D,UAAI,IAAI,GAAG,GAAG,WAAW;AAAI,cAAM,IAAI,MAAM,0BAA0B;AACvE,UAAI,IAAI,MAAM,IAAI,GAAG,GAAG,WAAW;AAAI,cAAM,IAAI,MAAM,0BAA0B;AACjF,UAAI,IAAI,MAAM,IAAI,GAAG,GAAG,WAAW;AAAG,cAAM,IAAI,MAAM,yBAAyB;AAE/E,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,QAAI,0BAAW,IAAI,GAAG,EAAE;AAAA,UACxB,QAAQ,IAAI,KAAK,IAAI,GAAG,IAAI,OAAK,YAAY,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,UAC3D,QAAQ,IAAI,KAAK,SAAK,0BAAW,IAAI,GAAG,EAAE,IAAI;AAAA,UAC9C,MAAM,IAAI,KAAK,KAAK,aAAS,0BAAW,IAAI,GAAG,EAAE,GAAG,EAAE,IAAI;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AAAA,IAEA,KAAK,SAAS;AACZ,UAAI,MAAM,SAAS,IAAI;AACvB,UAAI,CAAC,IAAI,KAAK;AAAI,cAAM,IAAI,MAAM,yBAAyB;AAC3D,UAAI,CAAC,IAAI,KAAK;AAAI,cAAM,IAAI,MAAM,yBAAyB;AAC3D,UAAI,IAAI,GAAG,GAAG,WAAW;AAAI,cAAM,IAAI,MAAM,0BAA0B;AACvE,UAAI,CAAC,IAAI,KAAK;AAAI,cAAM,IAAI,MAAM,yBAAyB;AAC3D,UAAI,IAAI,GAAG,GAAG,WAAW;AAAG,cAAM,IAAI,MAAM,yBAAyB;AAErE,aAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,YAAY,YAAY,OAAO,IAAI,GAAG,EAAE;AAAA,UACxC,YAAQ,0BAAW,IAAI,GAAG,EAAE;AAAA,UAC5B,MAAM,aAAS,0BAAW,IAAI,GAAG,EAAE,GAAG,EAAE;AAAA,UACxC,QAAQ,IAAI,KAAK,IAAI,GAAG,IAAI,OAAK,YAAY,OAAO,CAAC,CAAC,IAAI,CAAC;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AAAA,IAEA,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,KAAK;AAAA,IAE9B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,MAAM,QAAQ,UAAM,0BAAW,IAAI,EAAE;AAAA,IAEhD;AACE,YAAM,IAAI,MAAM,kBAAkB,QAAQ;AAAA,EAC9C;AACF;AAIA,SAAS,SAAS,MAAuB;AACvC,MAAI,SAAc,CAAC;AACnB,MAAI,OAAO;AACX,SAAO,KAAK,SAAS,GAAG;AACtB,QAAI,KAAK,SAAS;AAAG,YAAM,IAAI,MAAM,6BAA6B;AAClE,QAAI,IAAI,KAAK;AACb,QAAI,IAAI,KAAK;AACb,QAAI,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;AAC3B,WAAO,KAAK,MAAM,IAAI,CAAC;AACvB,QAAI,EAAE,SAAS;AAAG,YAAM,IAAI,MAAM,kCAAkC,GAAG;AACvE,WAAO,KAAK,OAAO,MAAM,CAAC;AAC1B,WAAO,GAAG,KAAK,CAAC;AAAA,EAClB;AACA,SAAO;AACT;;;AFvIO,IAAK,uBAAL,kBAAKC,0BAAL;AAEL,EAAAA,sBAAA,aAAU;AACV,EAAAA,sBAAA,kBAAe;AACf,EAAAA,sBAAA,iBAAc;AACd,EAAAA,sBAAA,gBAAa;AAEb,EAAAA,sBAAA,mBAAgB;AAEhB,EAAAA,sBAAA,sBAAmB;AAEnB,EAAAA,sBAAA,qBAAkB;AAClB,EAAAA,sBAAA,aAAU;AACV,EAAAA,sBAAA,iBAAc;AACd,EAAAA,sBAAA,iBAAc;AACd,EAAAA,sBAAA,kBAAe;AAfL,SAAAA;AAAA,GAAA;AAwBL,SAAS,mCAAmC,OAA6B;AAC9E,QAAM,OAAmB,CAAC,CAAC,KAAK,MAAM,SAAS,EAAE,CAAC;AAClD,QAAM,SAAS,QAAQ,KAAK,KAAK,CAAC,QAAQ,MAAM,SAAS,IAAI,CAAC;AAC9D,QAAM,SAAS,WAAW,KAAK,KAAK,CAAC,WAAW,MAAM,SAAS,OAAO,CAAC;AACvE,QAAM,SAAS,SAAS,KAAK,KAAK,CAAC,SAAS,MAAM,SAAS,KAAK,CAAC;AACjE,QAAM,SAAS,YAAY,KAAK,KAAK,CAAC,QAAQ,CAAC;AAC/C,QAAM,SAAS,UAAU,KAAK,KAAK,CAAC,MAAM,CAAC;AAE3C,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACxC,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAQO,SAAS,2BAA2B,OAAuB;AAChE,MAAI,MAAM,SAAS;AAAO,WAAO;AAEjC,MAAI,CAAC,MAAM;AAAQ,WAAO;AAE1B,QAAM,eAAe,CAAC,GAAG;AACzB,aAAW,OAAO,cAAc;AAC9B,QAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG;AAAG,aAAO;AAAA,EAClD;AAEA,SAAO;AACT;AASO,SAAS,iCAAiC,OAAc,QAAqC;AAClG,QAAM,OAAmB,CAAC,CAAC,KAAK,MAAM,SAAS,EAAE,CAAC;AAClD,aAAW,SAAS,QAAQ;AAC1B,SAAK,KAAK,CAAC,KAAK,MAAM,QAAQ,MAAM,SAAS,IAAI,GAAG,MAAM,WAAW,CAAC;AAAA,EACxE;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACxC,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAQO,SAAS,yBAAyB,OAAuB;AAC9D,MAAI,MAAM,SAAS;AAAO,WAAO;AAEjC,QAAM,eAAe,CAAC,GAAG;AACzB,aAAW,OAAO,cAAc;AAC9B,QAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG;AAAG,aAAO;AAAA,EAClD;AAGA,aAAW,CAAC,KAAK,QAAQ,WAAW,WAAW,KAAK,MAAM,MAAM;AAC9D,QAAI,QAAQ;AAAK;AAEjB,aAAS,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK,GAAG;AAC9C,UAAI,OAAO,YAAY,OAAO;AAAU,eAAO;AAG/C,UAAI,CAAC,OAAO,OAAO,oBAAoB,EAAE,SAAS,YAAY,EAA0B;AAAG,eAAO;AAAA,IACpG;AAAA,EACF;AAEA,SAAO;AACT;AASO,SAAS,kCAAkC,OAAc,SAAuC;AACrG,QAAM,OAAmB,CAAC,CAAC,KAAK,MAAM,SAAS,EAAE,CAAC;AAClD,aAAW,UAAU,SAAS;AAC5B,SAAK,KAAK,CAAC,KAAK,OAAO,QAAQ,OAAO,SAAS,EAAE,CAAC;AAAA,EACpD;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;AAAA,IACxC,MAAM;AAAA,IACN;AAAA,EACF;AACF;AAQO,SAAS,0BAA0B,OAAuB;AAC/D,MAAI,MAAM,SAAS;AAAO,WAAO;AAEjC,QAAM,eAAe,CAAC,GAAG;AACzB,aAAW,OAAO,cAAc;AAC9B,QAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG;AAAG,aAAO;AAAA,EAClD;AAEA,SAAO;AACT;AAQO,SAAS,sCAAsC,gBAAwC;AAC5F,SAAO,aAAa,eAAe,IAAI;AACzC;AAQA,eAAsB,sCAAsC,gBAA2D;AACrH,QAAM,qBAAqB,sCAAsC,cAAc;AAE/E,SAAO,sBAAsB,kBAAkB;AACjD;AAeA,eAAsB,wBAAwB;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKmB;AACjB,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,sCAAsC,cAAc;AAAA,EAC3E;AAEA,MAAI,CAAC,kBAAkB;AACrB,uBAAmB,MAAM,sBAAsB,kBAAkB;AAAA,EACnE;AAEA,QAAM,qBAAqB,MAAM,KAAK,IAAI,CAAC,kBAAkB,GAAG;AAAA,IAC9D,OAAO,CAAC,IAAK;AAAA,IACb,SAAS,CAAC,iBAAiB,MAAM;AAAA,IACjC,MAAM,CAAC,eAAe,EAAE;AAAA,EAC1B,CAAC;AAED,MAAI,CAAC;AAAoB,UAAM,IAAI,MAAM,UAAU,eAAe,oBAAoB,oBAAoB;AAE1G,SAAO;AACT;AASO,SAAS,wBAAwB,OAA6B;AACnE,MAAI,CAAC,2BAA2B,KAAK;AAAG,UAAM,IAAI,MAAM,8BAA8B;AAEtF,QAAM,WAA0B;AAAA,IAC9B,IAAI;AAAA,IACJ,QAAQ,MAAM;AAAA,EAChB;AAEA,aAAW,CAAC,KAAK,KAAK,KAAK,MAAM,MAAM;AACrC,YAAQ,KAAK;AAAA,MACX,KAAK;AACH,iBAAS,KAAK;AACd;AAAA,MACF,KAAK;AACH,iBAAS,OAAO;AAChB;AAAA,MACF,KAAK;AACH,iBAAS,UAAU;AACnB;AAAA,MACF,KAAK;AACH,iBAAS,QAAQ;AACjB;AAAA,MACF,KAAK;AACH,iBAAS,WAAW;AACpB;AAAA,MACF,KAAK;AACH,iBAAS,SAAS;AAClB;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;AAeA,eAAsB,sBAAsB;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKmB;AACjB,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,sCAAsC,cAAc;AAAA,EAC3E;AAEA,MAAI,CAAC,kBAAkB;AACrB,uBAAmB,MAAM,sBAAsB,kBAAkB;AAAA,EACnE;AAEA,QAAM,mBAAmB,MAAM,KAAK,IAAI,CAAC,kBAAkB,GAAG;AAAA,IAC5D,OAAO,CAAC,KAAK;AAAA,IACb,SAAS,CAAC,iBAAiB,MAAM;AAAA,IACjC,MAAM,CAAC,eAAe,EAAE;AAAA,EAC1B,CAAC;AAED,MAAI,CAAC;AAAkB,UAAM,IAAI,MAAM,qBAAqB,eAAe,oBAAoB,oBAAoB;AAEnH,SAAO;AACT;AASO,SAAS,sBAAsB,OAA4B;AAChE,MAAI,CAAC,yBAAyB,KAAK;AAAG,UAAM,IAAI,MAAM,4BAA4B;AAElF,QAAM,SAAuB,CAAC;AAE9B,aAAW,CAAC,KAAK,OAAO,UAAU,WAAW,KAAK,MAAM,MAAM;AAC5D,QAAI,QAAQ;AAAK;AAEjB,WAAO,KAAK;AAAA,MACV,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAeA,eAAsB,uBAAuB;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKmB;AACjB,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,sCAAsC,cAAc;AAAA,EAC3E;AAEA,MAAI,CAAC,kBAAkB;AACrB,uBAAmB,MAAM,sBAAsB,kBAAkB;AAAA,EACnE;AAEA,QAAM,oBAAoB,MAAM,KAAK,IAAI,CAAC,kBAAkB,GAAG;AAAA,IAC7D,OAAO,CAAC,KAAK;AAAA,IACb,SAAS,CAAC,iBAAiB,MAAM;AAAA,IACjC,MAAM,CAAC,eAAe,EAAE;AAAA,EAC1B,CAAC;AAED,MAAI,CAAC;AAAmB,UAAM,IAAI,MAAM,sBAAsB,eAAe,oBAAoB,oBAAoB;AAErH,SAAO;AACT;AAQO,SAAS,uBAAuB,OAA6B;AAClE,MAAI,CAAC,0BAA0B,KAAK;AAAG,UAAM,IAAI,MAAM,6BAA6B;AAEpF,QAAM,UAAyB,CAAC;AAEhC,aAAW,CAAC,KAAK,OAAO,KAAK,KAAK,MAAM,MAAM;AAC5C,QAAI,QAAQ;AAAK;AAEjB,YAAQ,KAAK;AAAA,MACX,QAAQ;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAcA,eAAsB,UAAU;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKmB;AACjB,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,sCAAsC,cAAc;AAAA,EAC3E;AAEA,MAAI,CAAC,kBAAkB;AACrB,uBAAmB,MAAM,sBAAsB,kBAAkB;AAAA,EACnE;AAEA,QAAM,gBAAgB,MAAM,wBAAwB,EAAE,MAAM,gBAAgB,oBAAoB,iBAAiB,CAAC;AAClH,QAAM,WAAW,wBAAwB,aAAa;AAEtD,QAAM,cAAc,MAAM,sBAAsB,EAAE,MAAM,gBAAgB,oBAAoB,iBAAiB,CAAC;AAC9G,QAAM,SAAS,sBAAsB,WAAW;AAEhD,QAAM,eAAe,MAAM,uBAAuB,EAAE,MAAM,gBAAgB,oBAAoB,iBAAiB,CAAC;AAChH,QAAM,UAAU,uBAAuB,YAAY;AAEnD,QAAM,QAAe;AAAA,IACnB,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb;AAEA,SAAO;AACT;AAUA,eAAsB,kBAAkB,MAA0B,MAA8B;AAC9F,QAAM,iBAAiB,eAAe,IAAI;AAE1C,MAAI,CAAC;AAAgB,UAAM,IAAI,MAAM,oBAAoB;AAEzD,SAAO,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C;AAQO,SAAS,eAAe,MAAqC;AAClE,MAAI,eAAe,QAAQ,IAAI,GAAG;AAChC,QAAI;AACF,UAAI,EAAE,KAAK,IAAI,OAAO,IAAI;AAE1B,UAAI,EAAE,QAAQ,WAAW,IAAI;AAC7B,UAAI,CAAC,UAAU,OAAO,WAAW;AAAG,eAAO;AAE3C,UAAI,OAAO,OAAQ;AACnB,UAAI,KAAK,WAAW,QAAQ,GAAG;AAC7B,eAAO,KAAK,MAAM,CAAC;AAAA,MACrB;AACA,aAAO,EAAE,MAAM,IAAI,WAAW;AAAA,IAChC,SAAS,KAAP;AACA,aAAO;AAAA,IACT;AAAA,EACF,WAAW,KAAK,MAAM,GAAG,EAAE,WAAW,GAAG;AACvC,QAAI,MAAM,KAAK,MAAM,GAAG;AACxB,WAAO,EAAE,MAAM,IAAI,IAAI,IAAI,IAAI,GAAG;AAAA,EACpC;AAEA,SAAO;AACT;AAQO,SAAS,qBAAqB,IAA4B;AAC/D,QAAM,EAAE,MAAM,GAAG,IAAI;AACrB,QAAM,iBAAiB,KAAK,QAAQ,4BAA4B,EAAE;AAElE,SAAO,GAAG,kBAAkB;AAC9B;AAcO,SAAS,mCAAmC;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMe;AACb,MAAI;AAEJ,QAAM,qBAAqB,aAAa,QAAQ;AAEhD,wBAAsB,kBAAkB,EACrC,KAAK,OAAM,SAAQ;AAClB,UAAM,kBAAkB,MAAM,KAAK,YAAY,kBAAkB;AAEjE,gBAAY;AAEZ,UAAM,gBAAgB;AAAA,MACpB;AAAA,QACE;AAAA,UACE,OAAO,CAAC,IAAK;AAAA,UACb,OAAO;AAAA,UACP,SAAS,CAAC,KAAK,MAAM;AAAA,QACvB;AAAA,MACF;AAAA,MACA;AAAA,QACE,QAAQ,OAAc;AACpB,kBAAQ,KAAK;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC,EACA,MAAM,SAAO;AACZ,QAAI,MAAM;AAEV,YAAQ,GAAG;AAAA,EACb,CAAC;AAEH,SAAO,MAAM,IAAI,MAAM;AACzB;",
|
|
4
|
+
"sourcesContent": ["import { AbstractSimplePool } from './abstract-pool.ts'\nimport { Subscription } from './abstract-relay.ts'\nimport type { Event, EventTemplate } from './core.ts'\nimport { fetchRelayInformation, RelayInformation } from './nip11.ts'\nimport { decode, NostrTypeGuard } from './nip19.ts'\nimport { normalizeURL } from './utils.ts'\n\n/**\n * Represents a NIP29 group.\n */\nexport type Group = {\n relay: string\n metadata: GroupMetadata\n admins?: GroupAdmin[]\n members?: GroupMember[]\n reference: GroupReference\n}\n\n/**\n * Represents the metadata for a NIP29 group.\n */\nexport type GroupMetadata = {\n id: string\n pubkey: string\n name?: string\n picture?: string\n banner?: string\n about?: string\n isPrivate?: boolean\n isRestricted?: boolean\n isHidden?: boolean\n isClosed?: boolean\n hasLiveKit?: boolean\n supportedKinds?: string[]\n parent?: string\n children?: string[]\n}\n\n/**\n * Represents a NIP29 group reference.\n */\nexport type GroupReference = {\n id: string\n host: string\n}\n\n/**\n * Represents a NIP29 group member.\n */\nexport type GroupMember = {\n pubkey: string\n label?: string\n}\n\n/**\n * Represents a NIP29 group admin.\n */\nexport type GroupAdmin = {\n pubkey: string\n label?: string\n permissions: GroupAdminPermission[]\n}\n\n/**\n * Represents the permissions that a NIP29 group admin can have.\n */\nexport enum GroupAdminPermission {\n /** @deprecated use PutUser instead */\n AddUser = 'add-user',\n EditMetadata = 'edit-metadata',\n DeleteEvent = 'delete-event',\n RemoveUser = 'remove-user',\n /** @deprecated removed from NIP */\n AddPermission = 'add-permission',\n /** @deprecated removed from NIP */\n RemovePermission = 'remove-permission',\n /** @deprecated removed from NIP */\n EditGroupStatus = 'edit-group-status',\n PutUser = 'put-user',\n CreateGroup = 'create-group',\n DeleteGroup = 'delete-group',\n CreateInvite = 'create-invite',\n UpdatePinList = 'update-pin-list',\n}\n\nfunction buildGroupMetadataTags(metadata: GroupMetadata): string[][] {\n const tags: string[][] = []\n metadata.name && tags.push(['name', metadata.name])\n metadata.picture && tags.push(['picture', metadata.picture])\n metadata.banner && tags.push(['banner', metadata.banner])\n metadata.about && tags.push(['about', metadata.about])\n metadata.isPrivate && tags.push(['private'])\n metadata.isRestricted && tags.push(['restricted'])\n metadata.isHidden && tags.push(['hidden'])\n metadata.isClosed && tags.push(['closed'])\n metadata.hasLiveKit && tags.push(['livekit'])\n metadata.supportedKinds &&\n metadata.supportedKinds.length > 0 &&\n tags.push(['supported_kinds', ...metadata.supportedKinds])\n metadata.parent && tags.push(['parent', metadata.parent])\n metadata.children &&\n metadata.children.forEach(child => {\n tags.push(['child', child])\n })\n\n return tags\n}\n\n/**\n * Generates a group metadata event template.\n *\n * @param group - The group object.\n * @returns An event template with the generated group metadata that can be signed later.\n */\nexport function generateGroupMetadataEventTemplate(group: Group): EventTemplate {\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39000,\n tags: [['d', group.metadata.id], ...buildGroupMetadataTags(group.metadata)],\n }\n}\n\n/**\n * Validates a group metadata event.\n *\n * @param event - The event to validate.\n * @returns A boolean indicating whether the event is valid.\n */\nexport function validateGroupMetadataEvent(event: Event): boolean {\n if (event.kind !== 39000) return false\n\n if (!event.pubkey) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Generates an event template for group admins.\n *\n * @param group - The group object.\n * @param admins - An array of group admins.\n * @returns The generated event template with the group admins that can be signed later.\n */\nexport function generateGroupAdminsEventTemplate(group: Group, admins: GroupAdmin[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n for (const admin of admins) {\n tags.push(['p', admin.pubkey, admin.label || '', ...admin.permissions])\n }\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39001,\n tags,\n }\n}\n\n/**\n * Validates a group admins event.\n *\n * @param event - The event to validate.\n * @returns True if the event is valid, false otherwise.\n */\nexport function validateGroupAdminsEvent(event: Event): boolean {\n if (event.kind !== 39001) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n // validate permissions\n for (const [tag, _value, _label, ...permissions] of event.tags) {\n if (tag !== 'p') continue\n\n for (let i = 0; i < permissions.length; i += 1) {\n if (typeof permissions[i] !== 'string') return false\n\n // validate permission name from the GroupAdminPermission enum\n if (!Object.values(GroupAdminPermission).includes(permissions[i] as GroupAdminPermission)) return false\n }\n }\n\n return true\n}\n\n/**\n * Generates an event template for a group with its members.\n *\n * @param group - The group object.\n * @param members - An array of group members.\n * @returns The generated event template with the group members that can be signed later.\n */\nexport function generateGroupMembersEventTemplate(group: Group, members: GroupMember[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n for (const member of members) {\n tags.push(['p', member.pubkey, member.label || ''])\n }\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39002,\n tags,\n }\n}\n\n/**\n * Validates a group members event.\n *\n * @param event - The event to validate.\n * @returns Returns `true` if the event is a valid group members event, `false` otherwise.\n */\nexport function validateGroupMembersEvent(event: Event): boolean {\n if (event.kind !== 39002) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Returns the normalized relay URL based on the provided group reference.\n *\n * @param groupReference - The group reference object containing the host.\n * @returns The normalized relay URL.\n */\nexport function getNormalizedRelayURLByGroupReference(groupReference: GroupReference): string {\n return normalizeURL(groupReference.host)\n}\n\n/**\n * Fetches relay information by group reference.\n *\n * @param groupReference The group reference.\n * @returns A promise that resolves to the relay information.\n */\nexport async function fetchRelayInformationByGroupReference(groupReference: GroupReference): Promise<RelayInformation> {\n const normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n\n return fetchRelayInformation(normalizedRelayURL)\n}\n\n/**\n * Fetches the group metadata event from the specified pool.\n * If the normalizedRelayURL is not provided, it will be obtained using the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool to fetch the group metadata event from.\n * @param {GroupReference} options.groupReference - The reference to the group.\n * @param {string} [options.normalizedRelayURL] - The normalized URL of the relay.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group metadata event that can be parsed later to get the group metadata object.\n * @throws {Error} If the group is not found on the specified relay.\n */\nexport async function fetchGroupMetadataEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupMetadataEvent = await pool.get([normalizedRelayURL], {\n kinds: [39000],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupMetadataEvent) throw new Error(`group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupMetadataEvent\n}\n\n/**\n * Parses a group metadata event and returns the corresponding GroupMetadata object.\n *\n * @param event - The event to parse.\n * @returns The parsed GroupMetadata object.\n * @throws An error if the group metadata event is invalid.\n */\nexport function parseGroupMetadataEvent(event: Event): GroupMetadata {\n if (!validateGroupMetadataEvent(event)) throw new Error('invalid group metadata event')\n\n const metadata: GroupMetadata = {\n id: '',\n pubkey: event.pubkey,\n }\n\n for (const [tag, value] of event.tags) {\n switch (tag) {\n case 'd':\n metadata.id = value\n break\n case 'name':\n metadata.name = value\n break\n case 'picture':\n metadata.picture = value\n break\n case 'banner':\n metadata.banner = value\n break\n case 'about':\n metadata.about = value\n break\n case 'private':\n metadata.isPrivate = true\n break\n case 'restricted':\n metadata.isRestricted = true\n break\n case 'hidden':\n metadata.isHidden = true\n break\n case 'closed':\n metadata.isClosed = true\n break\n case 'livekit':\n metadata.hasLiveKit = true\n break\n case 'parent':\n metadata.parent = value\n break\n }\n }\n\n const supportedKinds = event.tags.filter(([tag]) => tag === 'supported_kinds').flatMap(([, ...values]) => values)\n if (supportedKinds.length > 0) metadata.supportedKinds = supportedKinds\n\n const children = event.tags.filter(([tag]) => tag === 'child').map(([, value]) => value)\n if (children.length > 0) metadata.children = children\n\n return metadata\n}\n\n/**\n * Fetches the group admins event from the specified pool.\n * If the normalizedRelayURL is not provided, it will be obtained from the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool to fetch the group admins event from.\n * @param {GroupReference} options.groupReference - The reference to the group.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information.\n * @returns {Promise<Event>} The group admins event that can be parsed later to get the group admins object.\n * @throws {Error} If the group admins event is not found on the specified relay.\n */\nexport async function fetchGroupAdminsEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupAdminsEvent = await pool.get([normalizedRelayURL], {\n kinds: [39001],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupAdminsEvent) throw new Error(`admins for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupAdminsEvent\n}\n\n/**\n * Parses a group admins event and returns an array of GroupAdmin objects.\n *\n * @param event - The event to parse.\n * @returns An array of GroupAdmin objects.\n * @throws Throws an error if the group admins event is invalid.\n */\nexport function parseGroupAdminsEvent(event: Event): GroupAdmin[] {\n if (!validateGroupAdminsEvent(event)) throw new Error('invalid group admins event')\n\n const admins: GroupAdmin[] = []\n\n for (const [tag, value, label, ...permissions] of event.tags) {\n if (tag !== 'p') continue\n\n admins.push({\n pubkey: value,\n label,\n permissions: permissions as GroupAdminPermission[],\n })\n }\n\n return admins\n}\n\n/**\n * Fetches the group members event from the specified relay.\n * If the normalizedRelayURL is not provided, it will be obtained using the groupReference.\n * If the relayInformation is not provided, it will be fetched using the normalizedRelayURL.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool object.\n * @param {GroupReference} options.groupReference - The group reference object.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group members event that can be parsed later to get the group members object.\n * @throws {Error} If the group members event is not found.\n */\nexport async function fetchGroupMembersEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupMembersEvent = await pool.get([normalizedRelayURL], {\n kinds: [39002],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupMembersEvent) throw new Error(`members for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupMembersEvent\n}\n\n/**\n * Fetches the group roles event from the specified pool.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool object.\n * @param {GroupReference} options.groupReference - The group reference object.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group roles event that can be parsed later to get the group roles object.\n * @throws {Error} If the group roles event is not found.\n */\nexport async function fetchGroupRolesEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupRolesEvent = await pool.get([normalizedRelayURL], {\n kinds: [39003],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupRolesEvent) throw new Error(`roles for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupRolesEvent\n}\n\n/**\n * Fetches the group livekit participants event from the specified pool.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool object.\n * @param {GroupReference} options.groupReference - The group reference object.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group livekit participants event that can be parsed later to get the participants.\n * @throws {Error} If the group livekit participants event is not found.\n */\nexport async function fetchGroupLivekitParticipantsEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupLivekitParticipantsEvent = await pool.get([normalizedRelayURL], {\n kinds: [39004],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupLivekitParticipantsEvent)\n throw new Error(`livekit participants for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupLivekitParticipantsEvent\n}\n\n/**\n * Fetches the group pinned events event from the specified pool.\n *\n * @param {Object} options - The options object.\n * @param {AbstractSimplePool} options.pool - The pool object.\n * @param {GroupReference} options.groupReference - The group reference object.\n * @param {string} [options.normalizedRelayURL] - The normalized relay URL.\n * @param {RelayInformation} [options.relayInformation] - The relay information object.\n * @returns {Promise<Event>} The group pinned events event that can be parsed later to get the pinned events.\n * @throws {Error} If the group pinned events event is not found.\n */\nexport async function fetchGroupPinnedEventsEvent({\n pool,\n groupReference,\n relayInformation,\n normalizedRelayURL,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Event> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const groupPinnedEventsEvent = await pool.get([normalizedRelayURL], {\n kinds: [39005],\n authors: [relayInformation.pubkey],\n '#d': [groupReference.id],\n })\n\n if (!groupPinnedEventsEvent)\n throw new Error(`pinned events for group '${groupReference.id}' not found on ${normalizedRelayURL}`)\n\n return groupPinnedEventsEvent\n}\n\n/**\n * Parses a group members event and returns an array of GroupMember objects.\n * @param event - The event to parse.\n * @returns An array of GroupMember objects.\n * @throws Throws an error if the group members event is invalid.\n */\nexport function parseGroupMembersEvent(event: Event): GroupMember[] {\n if (!validateGroupMembersEvent(event)) throw new Error('invalid group members event')\n\n const members: GroupMember[] = []\n\n for (const [tag, value, label] of event.tags) {\n if (tag !== 'p') continue\n\n members.push({\n pubkey: value,\n label,\n })\n }\n\n return members\n}\n\n/**\n * Represents a NIP29 group role.\n */\nexport type GroupRole = {\n name: string\n description?: string\n}\n\n/**\n * Generates an event template for the roles supported by a group.\n *\n * @param group - The group object.\n * @param roles - An array of group roles.\n * @returns The generated event template with the group roles that can be signed later.\n */\nexport function generateGroupRolesEventTemplate(group: Group, roles: GroupRole[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n for (const role of roles) {\n const tag = ['role', role.name]\n role.description && tag.push(role.description)\n tags.push(tag)\n }\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39003,\n tags,\n }\n}\n\n/**\n * Validates a group roles event.\n *\n * @param event - The event to validate.\n * @returns True if the event is a valid group roles event, false otherwise.\n */\nexport function validateGroupRolesEvent(event: Event): boolean {\n if (event.kind !== 39003) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Parses a group roles event and returns an array of GroupRole objects.\n *\n * @param event - The event to parse.\n * @returns An array of GroupRole objects.\n * @throws Throws an error if the group roles event is invalid.\n */\nexport function parseGroupRolesEvent(event: Event): GroupRole[] {\n if (!validateGroupRolesEvent(event)) throw new Error('invalid group roles event')\n\n const roles: GroupRole[] = []\n\n for (const [tag, name, description] of event.tags) {\n if (tag !== 'role') continue\n\n roles.push({ name, description })\n }\n\n return roles\n}\n\n/**\n * Generates an event template for the livekit participants of a group.\n *\n * @param group - The group object.\n * @param participants - An array of pubkeys currently live in the group's AV rooms.\n * @returns The generated event template with the livekit participants that can be signed later.\n */\nexport function generateGroupLivekitParticipantsEventTemplate(group: Group, participants: string[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n participants.forEach(pubkey => {\n tags.push(['participant', pubkey])\n })\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39004,\n tags,\n }\n}\n\n/**\n * Validates a group livekit participants event.\n *\n * @param event - The event to validate.\n * @returns True if the event is a valid group livekit participants event, false otherwise.\n */\nexport function validateGroupLivekitParticipantsEvent(event: Event): boolean {\n if (event.kind !== 39004) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Parses a group livekit participants event and returns an array of participant pubkeys.\n *\n * @param event - The event to parse.\n * @returns An array of participant pubkeys.\n * @throws Throws an error if the group livekit participants event is invalid.\n */\nexport function parseGroupLivekitParticipantsEvent(event: Event): string[] {\n if (!validateGroupLivekitParticipantsEvent(event)) throw new Error('invalid group livekit participants event')\n\n return event.tags.filter(([tag]) => tag === 'participant').map(([, pubkey]) => pubkey)\n}\n\n/**\n * Represents a reference to a pinned event in a NIP29 group.\n */\nexport type GroupPinnedEvent = {\n type: 'e' | 'a'\n value: string\n}\n\n/**\n * Generates an event template for the events pinned in a group.\n *\n * @param group - The group object.\n * @param pinnedEvents - An array of references to pinned events, in display order.\n * @returns The generated event template with the pinned events that can be signed later.\n */\nexport function generateGroupPinnedEventsEventTemplate(group: Group, pinnedEvents: GroupPinnedEvent[]): EventTemplate {\n const tags: string[][] = [['d', group.metadata.id]]\n pinnedEvents.forEach(pinnedEvent => {\n tags.push([pinnedEvent.type, pinnedEvent.value])\n })\n\n return {\n content: '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 39005,\n tags,\n }\n}\n\n/**\n * Validates a group pinned events event.\n *\n * @param event - The event to validate.\n * @returns True if the event is a valid group pinned events event, false otherwise.\n */\nexport function validateGroupPinnedEventsEvent(event: Event): boolean {\n if (event.kind !== 39005) return false\n\n const requiredTags = ['d'] as const\n for (const tag of requiredTags) {\n if (!event.tags.find(([t]) => t == tag)) return false\n }\n\n return true\n}\n\n/**\n * Parses a group pinned events event and returns an array of GroupPinnedEvent objects.\n *\n * @param event - The event to parse.\n * @returns An array of GroupPinnedEvent objects.\n * @throws Throws an error if the group pinned events event is invalid.\n */\nexport function parseGroupPinnedEventsEvent(event: Event): GroupPinnedEvent[] {\n if (!validateGroupPinnedEventsEvent(event)) throw new Error('invalid group pinned events event')\n\n const pinnedEvents: GroupPinnedEvent[] = []\n\n for (const [tag, value] of event.tags) {\n if (tag !== 'e' && tag !== 'a') continue\n\n pinnedEvents.push({ type: tag, value })\n }\n\n return pinnedEvents\n}\n\n/**\n * Generates a group moderation event template. These events require the `h` tag and\n * may optionally carry timeline references in `previous` tags.\n */\nfunction generateGroupModerationEventTemplate(\n kind: number,\n groupId: string,\n content: string,\n tags: string[][],\n previous?: string[],\n): EventTemplate {\n const allTags: string[][] = [['h', groupId], ...tags]\n previous && previous.length > 0 && allTags.push(['previous', ...previous])\n\n return {\n content,\n created_at: Math.floor(Date.now() / 1000),\n kind,\n tags: allTags,\n }\n}\n\n/**\n * Generates a `put-user` (kind:9000) moderation event template.\n *\n * @param groupId - The id of the group.\n * @param pubkey - The pubkey of the user to add or update.\n * @param roles - Optional roles to assign to the user.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generatePutUserEventTemplate(\n groupId: string,\n pubkey: string,\n roles?: string[],\n reason?: string,\n previous?: string[],\n): EventTemplate {\n return generateGroupModerationEventTemplate(9000, groupId, reason || '', [['p', pubkey, ...(roles || [])]], previous)\n}\n\n/**\n * Generates a `remove-user` (kind:9001) moderation event template.\n *\n * @param groupId - The id of the group.\n * @param pubkey - The pubkey of the user to remove.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateRemoveUserEventTemplate(\n groupId: string,\n pubkey: string,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n return generateGroupModerationEventTemplate(9001, groupId, reason || '', [['p', pubkey]], previous)\n}\n\n/**\n * Generates an `edit-metadata` (kind:9002) moderation event template carrying\n * all the metadata fields of the group.\n *\n * @param group - The group object with the updated metadata.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateEditGroupMetadataEventTemplate(\n group: Group,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n return generateGroupModerationEventTemplate(\n 9002,\n group.metadata.id,\n reason || '',\n buildGroupMetadataTags(group.metadata),\n previous,\n )\n}\n\n/**\n * Generates a `delete-event` (kind:9005) moderation event template.\n *\n * @param groupId - The id of the group.\n * @param eventId - The id of the event to delete.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateDeleteEventEventTemplate(\n groupId: string,\n eventId: string,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n return generateGroupModerationEventTemplate(9005, groupId, reason || '', [['e', eventId]], previous)\n}\n\n/**\n * Generates a `create-group` (kind:9007) moderation event template.\n *\n * @param groupId - The id of the group to create.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateCreateGroupEventTemplate(groupId: string, reason?: string, previous?: string[]): EventTemplate {\n return generateGroupModerationEventTemplate(9007, groupId, reason || '', [], previous)\n}\n\n/**\n * Generates a `delete-group` (kind:9008) moderation event template.\n *\n * @param groupId - The id of the group to delete.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateDeleteGroupEventTemplate(groupId: string, reason?: string, previous?: string[]): EventTemplate {\n return generateGroupModerationEventTemplate(9008, groupId, reason || '', [], previous)\n}\n\n/**\n * Generates a `create-invite` (kind:9009) moderation event template.\n *\n * @param groupId - The id of the group.\n * @param code - An arbitrary invite code.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateCreateInviteEventTemplate(\n groupId: string,\n code: string,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n return generateGroupModerationEventTemplate(9009, groupId, reason || '', [['code', code]], previous)\n}\n\n/**\n * Generates an `update-pin-list` (kind:9010) moderation event template.\n *\n * @param groupId - The id of the group.\n * @param pinnedEvents - The full ordered list of pinned events.\n * @param reason - Optional reason for the action.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateUpdatePinListEventTemplate(\n groupId: string,\n pinnedEvents: GroupPinnedEvent[],\n reason?: string,\n previous?: string[],\n): EventTemplate {\n const tags = pinnedEvents.map(pinnedEvent => [pinnedEvent.type, pinnedEvent.value])\n return generateGroupModerationEventTemplate(9010, groupId, reason || '', tags, previous)\n}\n\n/**\n * Generates a group join request (kind:9021) event template.\n *\n * @param groupId - The id of the group.\n * @param inviteCode - Optional invite code to be preauthorized by the relay.\n * @param reason - Optional reason for the request.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateGroupJoinRequestEventTemplate(\n groupId: string,\n inviteCode?: string,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n const tags: string[][] = [['h', groupId]]\n inviteCode && tags.push(['code', inviteCode])\n previous && previous.length > 0 && tags.push(['previous', ...previous])\n\n return {\n content: reason || '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 9021,\n tags,\n }\n}\n\n/**\n * Generates a group leave request (kind:9022) event template.\n *\n * @param groupId - The id of the group.\n * @param reason - Optional reason for the request.\n * @param previous - Optional timeline references.\n * @returns The generated event template that can be signed later.\n */\nexport function generateGroupLeaveRequestEventTemplate(\n groupId: string,\n reason?: string,\n previous?: string[],\n): EventTemplate {\n const tags: string[][] = [['h', groupId]]\n previous && previous.length > 0 && tags.push(['previous', ...previous])\n\n return {\n content: reason || '',\n created_at: Math.floor(Date.now() / 1000),\n kind: 9022,\n tags,\n }\n}\n\n/**\n * Fetches and parses the group metadata event, group admins event, and group members event from the specified pool.\n * If the normalized relay URL is not provided, it will be obtained using the group reference.\n * If the relay information is not provided, it will be fetched using the normalized relay URL.\n *\n * @param {Object} options - The options for loading the group.\n * @param {AbstractSimplePool} options.pool - The pool to load the group from.\n * @param {GroupReference} options.groupReference - The reference of the group to load.\n * @param {string} [options.normalizedRelayURL] - The normalized URL of the relay to use.\n * @param {RelayInformation} [options.relayInformation] - The relay information to use.\n * @returns {Promise<Group>} A promise that resolves to the loaded group.\n */\nexport async function loadGroup({\n pool,\n groupReference,\n normalizedRelayURL,\n relayInformation,\n}: {\n pool: AbstractSimplePool\n groupReference: GroupReference\n normalizedRelayURL?: string\n relayInformation?: RelayInformation\n}): Promise<Group> {\n if (!normalizedRelayURL) {\n normalizedRelayURL = getNormalizedRelayURLByGroupReference(groupReference)\n }\n\n if (!relayInformation) {\n relayInformation = await fetchRelayInformation(normalizedRelayURL)\n }\n\n const metadataEvent = await fetchGroupMetadataEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const metadata = parseGroupMetadataEvent(metadataEvent)\n\n const adminsEvent = await fetchGroupAdminsEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const admins = parseGroupAdminsEvent(adminsEvent)\n\n const membersEvent = await fetchGroupMembersEvent({ pool, groupReference, normalizedRelayURL, relayInformation })\n const members = parseGroupMembersEvent(membersEvent)\n\n const group: Group = {\n relay: normalizedRelayURL,\n metadata,\n admins,\n members,\n reference: groupReference,\n }\n\n return group\n}\n\n/**\n * Loads a group from the specified pool using the provided group code.\n *\n * @param {AbstractSimplePool} pool - The pool to load the group from.\n * @param {string} code - The code representing the group.\n * @returns {Promise<Group>} - A promise that resolves to the loaded group.\n * @throws {Error} - If the group code is invalid.\n */\nexport async function loadGroupFromCode(pool: AbstractSimplePool, code: string): Promise<Group> {\n const groupReference = parseGroupCode(code)\n\n if (!groupReference) throw new Error('invalid group code')\n\n return loadGroup({ pool, groupReference })\n}\n\n/**\n * Parses a group code and returns a GroupReference object.\n *\n * @param code The group code to parse.\n * @returns A GroupReference object if the code is valid, otherwise null.\n */\nexport function parseGroupCode(code: string): null | GroupReference {\n if (NostrTypeGuard.isNAddr(code)) {\n try {\n let { data } = decode(code)\n\n let { relays, identifier } = data\n if (!relays || relays.length === 0) return null\n\n let host = relays![0]\n if (host.startsWith('wss://')) {\n host = host.slice(6)\n }\n return { host, id: identifier }\n } catch (err) {\n return null\n }\n } else if (code.split(\"'\").length === 2) {\n let spl = code.split(\"'\")\n return { host: spl[0], id: spl[1] }\n }\n\n return null\n}\n\n/**\n * Encodes a group reference into a string.\n *\n * @param gr - The group reference to encode.\n * @returns The encoded group reference as a string.\n */\nexport function encodeGroupReference(gr: GroupReference): string {\n const { host, id } = gr\n const normalizedHost = host.replace(/^(https?:\\/\\/|wss?:\\/\\/)/, '')\n\n return `${normalizedHost}'${id}`\n}\n\n/**\n * Subscribes to relay groups metadata events and calls the provided event handler function\n * when an event is received.\n *\n * @param {Object} options - The options for subscribing to relay groups metadata events.\n * @param {AbstractSimplePool} options.pool - The pool to subscribe to.\n * @param {string} options.relayURL - The URL of the relay.\n * @param {Function} options.onError - The error handler function.\n * @param {Function} options.onEvent - The event handler function.\n * @param {Function} [options.onConnect] - The connect handler function.\n * @returns {Function} - A function to close the subscription\n */\nexport function subscribeRelayGroupsMetadataEvents({\n pool,\n relayURL,\n onError,\n onEvent,\n onConnect,\n}: {\n pool: AbstractSimplePool\n relayURL: string\n onError: (err: Error) => void\n onEvent: (event: Event) => void\n onConnect?: () => void\n}): () => void {\n let sub: Subscription\n\n const normalizedRelayURL = normalizeURL(relayURL)\n\n fetchRelayInformation(normalizedRelayURL)\n .then(async info => {\n const abstractedRelay = await pool.ensureRelay(normalizedRelayURL)\n\n onConnect?.()\n\n sub = abstractedRelay.prepareSubscription(\n [\n {\n kinds: [39000],\n limit: 50,\n authors: [info.pubkey],\n },\n ],\n {\n onevent(event: Event) {\n onEvent(event)\n },\n },\n )\n })\n .catch(err => {\n sub.close()\n\n onError(err)\n })\n\n return () => sub.close()\n}\n", "var _fetch: any\n\ntry {\n _fetch = fetch\n} catch {}\n\nexport function useFetchImplementation(fetchImplementation: any): void {\n _fetch = fetchImplementation\n}\n\nexport async function fetchRelayInformation(url: string): Promise<RelayInformation> {\n return (await (\n await fetch(url.replace('ws://', 'http://').replace('wss://', 'https://'), {\n headers: { Accept: 'application/nostr+json' },\n })\n ).json()) as RelayInformation\n}\n\n/**\n * ## Relay Information Document\n\n * Relays may provide server metadata to clients to inform\n * them of capabilities, administrative contacts, and\n * various server attributes. This is made available as a\n * JSON document over HTTP, on the same URI as the relay's\n * websocket.\n\n * Any field may be omitted, and clients MUST ignore any\n * additional fields they do not understand. Relays MUST\n * accept CORS requests by sending\n * `Access-Control-Allow-Origin`,\n * `Access-Control-Allow-Headers`, and\n * `Access-Control-Allow-Methods` headers.\n * @param name string identifying relay\n * @param description string with detailed information\n * @param pubkey administrative contact pubkey\n * @param contact: administrative alternate contact\n * @param supported_nips a list of NIP numbers supported by\n * the relay\n * @param software identifying relay software URL\n * @param version string version identifier\n */\nexport interface BasicRelayInformation {\n // string identifying relay\n name: string\n description: string\n pubkey: string\n contact: string\n supported_nips: number[]\n software: string\n version: string\n // limitation?: Limitations<A, P>\n}\n\n/**\n * * ## Extra Fields\n\n * * ### Server Limitations\n\n * These are limitations imposed by the relay on clients.\n * Your client should expect that requests which exceed\n * these practical_ limitations are rejected or fail immediately.\n * @param max_message_length this is the maximum number of\n * bytes for incoming JSON that the relay will attempt to\n * decode and act upon. When you send large subscriptions,\n * you will be limited by this value. It also effectively\n * limits the maximum size of any event. Value is calculated\n * from `[` to `]` and is after UTF-8 serialization (so some\n * unicode characters will cost 2-3 bytes). It is equal to\n * the maximum size of the WebSocket message frame.\n * @param max_subscriptions total number of subscriptions\n * that may be active on a single websocket connection to\n * this relay. It's possible that authenticated clients with\n * a (paid) relationship to the relay may have higher limits.\n * @param max_filters maximum number of filter values in\n * each subscription. Must be one or higher.\n * @param max_limit the relay server will clamp each\n * filter's `limit` value to this number.\n * This means the client won't be able to get more than this\n * number of events from a single subscription filter. This\n * clamping is typically done silently by the relay, but\n * with this number, you can know that there are additional\n * results if you narrowed your filter's time range or other\n * parameters.\n * @param max_subid_length maximum length of subscription id as a\n * string.\n * @param min_prefix for `authors` and `ids` filters which\n * are to match against a hex prefix, you must provide at\n * least this many hex digits in the prefix.\n * @param max_event_tags in any event, this is the maximum\n * number of elements in the `tags` list.\n * @param max_content_length maximum number of characters in\n * the `content` field of any event. This is a count of\n * unicode characters. After serializing into JSON it may be\n * larger (in bytes), and is still subject to the\n * max_message_length`, if defined.\n * @param min_pow_difficulty new events will require at\n * least this difficulty of PoW, based on [NIP-13](13.md),\n * or they will be rejected by this server.\n * @param auth_required this relay requires [NIP-42](42.md)\n * authentication to happen before a new connection may\n * perform any other action. Even if set to False,\n * authentication may be required for specific actions.\n * @param restricted_writes: this relay requires some kind\n * of condition to be fulfilled in order to accept events\n * (not necessarily, but including\n * @param payment_required this relay requires payment\n * before a new connection may perform any action.\n * @param created_at_lower_limit: 'created_at' lower limit\n * @param created_at_upper_limit: 'created_at' upper limit\n */\nexport interface Limitations {\n max_message_length: number\n max_subscriptions: number\n max_filters: number\n max_limit: number\n max_subid_length: number\n min_prefix: number\n max_event_tags: number\n max_content_length: number\n min_pow_difficulty: number\n auth_required: boolean\n payment_required: boolean\n created_at_lower_limit: number\n created_at_upper_limit: number\n restricted_writes: boolean\n}\n\nexport interface RetentionDetails {\n kinds: (number | number[])[]\n time?: number | null\n count?: number | null\n}\ntype AnyRetentionDetails = RetentionDetails\n/**\n * ### Event Retention\n\n * There may be a cost associated with storing data forever,\n * so relays may wish to state retention times. The values\n * stated here are defaults for unauthenticated users and\n * visitors. Paid users would likely have other policies.\n\n * Retention times are given in seconds, with `null`\n * indicating infinity. If zero is provided, this means the\n * event will not be stored at all, and preferably an error\n * will be provided when those are received.\n * ```json\n{\n...\n \"retention\": [\n { \"kinds\": [0, 1, [5, 7], [40, 49]], \"time\": 3600 },\n { \"kinds\": [[40000, 49999]], \"time\": 100 },\n { \"kinds\": [[30000, 39999]], \"count\": 1000 },\n { \"time\": 3600, \"count\": 10000 }\n ]\n...\n}\n```\n * @param retention is a list of specifications: each will\n * apply to either all kinds, or a subset of kinds. Ranges\n * may be specified for the kind field as a tuple of\n * inclusive start and end values. Events of indicated kind\n * (or all) are then limited to a `count` and/or time\n * period.\n\n * It is possible to effectively blacklist Nostr-based\n * protocols that rely on a specific `kind` number, by\n * giving a retention time of zero for those `kind` values.\n * While that is unfortunate, it does allow clients to\n * discover servers that will support their protocol quickly\n * via a single HTTP fetch.\n\n * There is no need to specify retention times for\n * _ephemeral events_ as defined in [NIP-16](16.md) since\n * they are not retained.\n */\nexport interface Retention {\n retention: AnyRetentionDetails[]\n}\n\n/**\n * Some relays may be governed by the arbitrary laws of a\n * nation state. This may limit what content can be stored\n * in cleartext on those relays. All clients are encouraged\n * to use encryption to work around this limitation.\n\n * It is not possible to describe the limitations of each\n * country's laws and policies which themselves are\n * typically vague and constantly shifting.\n\n * Therefore, this field allows the relay operator to\n * indicate which countries' laws might end up being\n * enforced on them, and then indirectly on their users'\n * content.\n\n * Users should be able to avoid relays in countries they\n * don't like, and/or select relays in more favourable\n * zones. Exposing this flexibility is up to the client\n * software.\n\n * @param relay_countries a list of two-level ISO country\n * codes (ISO 3166-1 alpha-2) whose laws and policies may\n * affect this relay. `EU` may be used for European Union\n * countries.\n\n * Remember that a relay may be hosted in a country which is\n * not the country of the legal entities who own the relay,\n * so it's very likely a number of countries are involved.\n */\nexport interface ContentLimitations {\n relay_countries: string[]\n}\n\n/**\n * ### Community Preferences\n\n * For public text notes at least, a relay may try to foster\n * a local community. This would encourage users to follow\n * the global feed on that relay, in addition to their usual\n * individual follows. To support this goal, relays MAY\n * specify some of the following values.\n\n * @param language_tags is an ordered list of [IETF\n * language\n * tags](https://en.wikipedia.org/wiki/IETF_language_tag\n * indicating the major languages spoken on the relay.\n * @param tags is a list of limitations on the topics to be\n * discussed. For example `sfw-only` indicates that only\n * \"Safe For Work\" content is encouraged on this relay. This\n * relies on assumptions of what the \"work\" \"community\"\n * feels \"safe\" talking about. In time, a common set of tags\n * may emerge that allow users to find relays that suit\n * their needs, and client software will be able to parse\n * these tags easily. The `bitcoin-only` tag indicates that\n * any _altcoin_, _\"crypto\"_ or _blockchain_ comments will\n * be ridiculed without mercy.\n * @param posting_policy is a link to a human-readable page\n * which specifies the community policies for the relay. In\n * cases where `sfw-only` is True, it's important to link to\n * a page which gets into the specifics of your posting\n * policy.\n\n * The `description` field should be used to describe your\n * community goals and values, in brief. The\n * `posting_policy` is for additional detail and legal\n * terms. Use the `tags` field to signify limitations on\n * content, or topics to be discussed, which could be\n * machine processed by appropriate client software.\n */\nexport interface CommunityPreferences {\n language_tags: string[]\n tags: string[]\n posting_policy: string\n}\n\nexport interface Amount {\n amount: number\n unit: 'msat'\n}\nexport interface PublicationAmount extends Amount {\n kinds: number[]\n}\nexport interface Subscription extends Amount {\n period: number\n}\nexport interface Fees {\n admission: Amount[]\n subscription: Subscription[]\n publication: PublicationAmount[]\n}\n/**\n * Relays that require payments may want to expose their fee\n * schedules.\n */\nexport interface PayToRelay {\n payments_url: string\n fees: Fees\n}\n\n/**\n * A URL pointing to an image to be used as an icon for the\n * relay. Recommended to be squared in shape.\n */\nexport interface Icon {\n icon: string\n}\n\nexport type RelayInformation = BasicRelayInformation &\n Partial<Retention> & {\n limitation?: Partial<Limitations>\n } & Partial<ContentLimitations> &\n Partial<CommunityPreferences> &\n Partial<PayToRelay> &\n Partial<Icon>\n", "import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils.js'\nimport { bech32 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nexport type NProfile = `nprofile1${string}`\nexport type NEvent = `nevent1${string}`\nexport type NAddr = `naddr1${string}`\nexport type NSec = `nsec1${string}`\nexport type NPub = `npub1${string}`\nexport type Note = `note1${string}`\nexport type Ncryptsec = `ncryptsec1${string}`\n\nexport const NostrTypeGuard = {\n isNProfile: (value?: string | null): value is NProfile => /^nprofile1[a-z\\d]+$/.test(value || ''),\n isNEvent: (value?: string | null): value is NEvent => /^nevent1[a-z\\d]+$/.test(value || ''),\n isNAddr: (value?: string | null): value is NAddr => /^naddr1[a-z\\d]+$/.test(value || ''),\n isNSec: (value?: string | null): value is NSec => /^nsec1[a-z\\d]{58}$/.test(value || ''),\n isNPub: (value?: string | null): value is NPub => /^npub1[a-z\\d]{58}$/.test(value || ''),\n isNote: (value?: string | null): value is Note => /^note1[a-z\\d]+$/.test(value || ''),\n isNcryptsec: (value?: string | null): value is Ncryptsec => /^ncryptsec1[a-z\\d]+$/.test(value || ''),\n}\n\nexport const Bech32MaxSize = 5000\n\n/**\n * Bech32 regex.\n * @see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32\n */\nexport const BECH32_REGEX = /[\\x21-\\x7E]{1,83}1[023456789acdefghjklmnpqrstuvwxyz]{6,}/\n\nfunction integerToUint8Array(number: number) {\n // Create a Uint8Array with enough space to hold a 32-bit integer (4 bytes).\n const uint8Array = new Uint8Array(4)\n\n // Use bitwise operations to extract the bytes.\n uint8Array[0] = (number >> 24) & 0xff // Most significant byte (MSB)\n uint8Array[1] = (number >> 16) & 0xff\n uint8Array[2] = (number >> 8) & 0xff\n uint8Array[3] = number & 0xff // Least significant byte (LSB)\n\n return uint8Array\n}\n\nexport type ProfilePointer = {\n pubkey: string // hex\n relays?: string[]\n}\n\nexport type EventPointer = {\n id: string // hex\n relays?: string[]\n author?: string\n kind?: number\n}\n\nexport type AddressPointer = {\n identifier: string\n pubkey: string\n kind: number\n relays?: string[]\n}\n\nexport function decodeNostrURI(nip19code: string): ReturnType<typeof decode> | { type: 'invalid'; data: null } {\n try {\n if (nip19code.startsWith('nostr:')) nip19code = nip19code.substring(6)\n return decode(nip19code)\n } catch (_err) {\n return { type: 'invalid', data: null }\n }\n}\n\nexport type DecodedNevent = {\n type: 'nevent'\n data: EventPointer\n}\n\nexport type DecodedNprofile = {\n type: 'nprofile'\n data: ProfilePointer\n}\n\nexport type DecodedNaddr = {\n type: 'naddr'\n data: AddressPointer\n}\n\nexport type DecodedNsec = {\n type: 'nsec'\n data: Uint8Array\n}\n\nexport type DecodedNpub = {\n type: 'npub'\n data: string\n}\n\nexport type DecodedNote = {\n type: 'note'\n data: string\n}\n\nexport type DecodedResult = DecodedNevent | DecodedNprofile | DecodedNaddr | DecodedNpub | DecodedNsec | DecodedNote\n\nexport function decode(nip19: NEvent): DecodedNevent\nexport function decode(nip19: NProfile): DecodedNprofile\nexport function decode(nip19: NAddr): DecodedNaddr\nexport function decode(nip19: NSec): DecodedNsec\nexport function decode(nip19: NPub): DecodedNpub\nexport function decode(nip19: Note): DecodedNote\nexport function decode(code: string): DecodedResult\nexport function decode(code: string): DecodedResult {\n let { prefix, words } = bech32.decode(code as `${string}1${string}`, Bech32MaxSize)\n let data = new Uint8Array(bech32.fromWords(words))\n\n switch (prefix) {\n case 'nprofile': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')\n if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')\n\n return {\n type: 'nprofile',\n data: {\n pubkey: bytesToHex(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n },\n }\n }\n case 'nevent': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nevent')\n if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')\n if (tlv[2] && tlv[2][0].length !== 32) throw new Error('TLV 2 should be 32 bytes')\n if (tlv[3] && tlv[3][0].length !== 4) throw new Error('TLV 3 should be 4 bytes')\n\n return {\n type: 'nevent',\n data: {\n id: bytesToHex(tlv[0][0]),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n author: tlv[2]?.[0] ? bytesToHex(tlv[2][0]) : undefined,\n kind: tlv[3]?.[0] ? parseInt(bytesToHex(tlv[3][0]), 16) : undefined,\n },\n }\n }\n\n case 'naddr': {\n let tlv = parseTLV(data)\n if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for naddr')\n if (!tlv[2]?.[0]) throw new Error('missing TLV 2 for naddr')\n if (tlv[2][0].length !== 32) throw new Error('TLV 2 should be 32 bytes')\n if (!tlv[3]?.[0]) throw new Error('missing TLV 3 for naddr')\n if (tlv[3][0].length !== 4) throw new Error('TLV 3 should be 4 bytes')\n\n return {\n type: 'naddr',\n data: {\n identifier: utf8Decoder.decode(tlv[0][0]),\n pubkey: bytesToHex(tlv[2][0]),\n kind: parseInt(bytesToHex(tlv[3][0]), 16),\n relays: tlv[1] ? tlv[1].map(d => utf8Decoder.decode(d)) : [],\n },\n }\n }\n\n case 'nsec':\n return { type: prefix, data }\n\n case 'npub':\n case 'note':\n return { type: prefix, data: bytesToHex(data) }\n\n default:\n throw new Error(`unknown prefix ${prefix}`)\n }\n}\n\ntype TLV = { [t: number]: Uint8Array[] }\n\nfunction parseTLV(data: Uint8Array): TLV {\n let result: TLV = {}\n let rest = data\n while (rest.length > 0) {\n if (rest.length < 2) throw new Error('not enough data to read TLV')\n let t = rest[0]\n let l = rest[1]\n let v = rest.slice(2, 2 + l)\n rest = rest.slice(2 + l)\n if (v.length < l) throw new Error(`not enough data to read on TLV ${t}`)\n result[t] = result[t] || []\n result[t].push(v)\n }\n return result\n}\n\nexport function nsecEncode(key: Uint8Array): NSec {\n return encodeBytes('nsec', key)\n}\n\nexport function npubEncode(hex: string): NPub {\n return encodeBytes('npub', hexToBytes(hex))\n}\n\nexport function noteEncode(hex: string): Note {\n return encodeBytes('note', hexToBytes(hex))\n}\n\nfunction encodeBech32<Prefix extends string>(prefix: Prefix, data: Uint8Array): `${Prefix}1${string}` {\n let words = bech32.toWords(data)\n return bech32.encode(prefix, words, Bech32MaxSize) as `${Prefix}1${string}`\n}\n\nexport function encodeBytes<Prefix extends string>(prefix: Prefix, bytes: Uint8Array): `${Prefix}1${string}` {\n return encodeBech32(prefix, bytes)\n}\n\nexport function nprofileEncode(profile: ProfilePointer): NProfile {\n let data = encodeTLV({\n 0: [hexToBytes(profile.pubkey)],\n 1: (profile.relays || []).map(url => utf8Encoder.encode(url)),\n })\n return encodeBech32('nprofile', data)\n}\n\nexport function neventEncode(event: EventPointer): NEvent {\n let kindArray\n if (event.kind !== undefined) {\n kindArray = integerToUint8Array(event.kind)\n }\n\n let data = encodeTLV({\n 0: [hexToBytes(event.id)],\n 1: (event.relays || []).map(url => utf8Encoder.encode(url)),\n 2: event.author ? [hexToBytes(event.author)] : [],\n 3: kindArray ? [new Uint8Array(kindArray)] : [],\n })\n\n return encodeBech32('nevent', data)\n}\n\nexport function naddrEncode(addr: AddressPointer): NAddr {\n let kind = new ArrayBuffer(4)\n new DataView(kind).setUint32(0, addr.kind, false)\n\n let data = encodeTLV({\n 0: [utf8Encoder.encode(addr.identifier)],\n 1: (addr.relays || []).map(url => utf8Encoder.encode(url)),\n 2: [hexToBytes(addr.pubkey)],\n 3: [new Uint8Array(kind)],\n })\n return encodeBech32('naddr', data)\n}\n\nfunction encodeTLV(tlv: TLV): Uint8Array {\n let entries: Uint8Array[] = []\n\n Object.entries(tlv)\n .reverse()\n .forEach(([t, vs]) => {\n vs.forEach(v => {\n let entry = new Uint8Array(v.length + 2)\n entry.set([parseInt(t)], 0)\n entry.set([v.length], 1)\n entry.set(v, 2)\n entries.push(entry)\n })\n })\n\n return concatBytes(...entries)\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n if (input.length !== 64) return false\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_utils", "GroupAdminPermission"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../nip44.ts", "../../utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { chacha20 } from '@noble/ciphers/chacha.js'\nimport { equalBytes } from '@noble/ciphers/utils.js'\nimport { secp256k1 } from '@noble/curves/secp256k1.js'\nimport { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf.js'\nimport { hmac } from '@noble/hashes/hmac.js'\nimport { sha256 } from '@noble/hashes/sha2.js'\nimport { concatBytes, hexToBytes, randomBytes } from '@noble/hashes/utils.js'\nimport { base64 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nconst minPlaintextSize = 0x0001 // 1b msg => padded to 32b\nconst maxPlaintextSize = 0xffffffff // 4294967295 (2^32-1)\nconst extendedPrefixThreshold = 0x10000 // 65536: lengths below use 2-byte u16 prefix, at or above use 6-byte prefix\n\nexport function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array {\n const sharedX = secp256k1.getSharedSecret(privkeyA, hexToBytes('02' + pubkeyB)).subarray(1, 33)\n return hkdf_extract(sha256, sharedX, utf8Encoder.encode('nip44-v2'))\n}\n\nfunction getMessageKeys(\n conversationKey: Uint8Array,\n nonce: Uint8Array,\n): { chacha_key: Uint8Array; chacha_nonce: Uint8Array; hmac_key: Uint8Array } {\n const keys = hkdf_expand(sha256, conversationKey, nonce, 76)\n return {\n chacha_key: keys.subarray(0, 32),\n chacha_nonce: keys.subarray(32, 44),\n hmac_key: keys.subarray(44, 76),\n }\n}\n\nfunction calcPaddedLen(len: number): number {\n if (!Number.isSafeInteger(len) || len < 1) throw new Error('expected positive integer')\n if (len <= 32) return 32\n const nextPower = 2 ** (Math.floor(Math.log2(len - 1)) + 1)\n const chunk = nextPower <= 256 ? 32 : nextPower / 8\n return chunk * (Math.floor((len - 1) / chunk) + 1)\n}\n\nfunction writeU16BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > 0xffff)\n throw new Error('invalid plaintext size: must be between 1 and 65535 bytes')\n const arr = new Uint8Array(2)\n new DataView(arr.buffer).setUint16(0, num, false)\n return arr\n}\n\nfunction writeU32BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < extendedPrefixThreshold || num > maxPlaintextSize)\n throw new Error('invalid plaintext size: must be between 65536 and 4294967295 bytes')\n const arr = new Uint8Array(4)\n new DataView(arr.buffer).setUint32(0, num, false)\n return arr\n}\n\nfunction pad(plaintext: string): Uint8Array {\n const unpadded = utf8Encoder.encode(plaintext)\n const unpaddedLen = unpadded.length\n if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize)\n throw new Error('invalid plaintext size: must be between 1 and 4294967295 bytes')\n const prefix =\n unpaddedLen >= extendedPrefixThreshold\n ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen)) // 6 bytes\n : writeU16BE(unpaddedLen) // 2 bytes\n const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen)\n return concatBytes(prefix, unpadded, suffix)\n}\n\nfunction unpad(padded: Uint8Array): string {\n const dv = new DataView(padded.buffer, padded.byteOffset, padded.byteLength)\n const firstTwo = dv.getUint16(0)\n let unpaddedLen: number\n let prefixLen: number\n if (firstTwo === 0) {\n // Extended format: 2 zero bytes + 4-byte u32 length\n unpaddedLen = dv.getUint32(2)\n if (unpaddedLen < extendedPrefixThreshold) throw new Error('invalid padding')\n prefixLen = 6\n } else {\n unpaddedLen = firstTwo\n prefixLen = 2\n }\n const unpadded = padded.subarray(prefixLen, prefixLen + unpaddedLen)\n if (\n unpaddedLen < minPlaintextSize ||\n unpaddedLen > maxPlaintextSize ||\n unpadded.length !== unpaddedLen ||\n padded.length !== prefixLen + calcPaddedLen(unpaddedLen)\n )\n throw new Error('invalid padding')\n return utf8Decoder.decode(unpadded)\n}\n\nfunction hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {\n if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')\n const combined = concatBytes(aad, message)\n return hmac(sha256, key, combined)\n}\n\n// metadata: always 65b (version: 1b, nonce: 32b, mac: 32b)\n// plaintext: 1b to 0xffffffff\n// padded plaintext (small, <65536): 32b to 0x10000, with 2b prefix -> 34b to 0x10000+2\n// padded plaintext (large, >=65536): 0x10000 to 0x100000000, with 6b prefix -> 0x10006 to 0x100000000+6\n// ciphertext: same as padded plaintext (chacha20 doesn't change length)\n// raw payload (small): 99 (65+34) to 65603 (65+0x10000+2)\n// raw payload (large): 65607 (65+0x10006) to 4294967367 (65+0x100000000+6)\nfunction decodePayload(payload: string): { nonce: Uint8Array; ciphertext: Uint8Array; mac: Uint8Array } {\n if (typeof payload !== 'string') throw new Error('payload must be a valid string')\n const plen = payload.length\n if (plen < 132) throw new Error('invalid payload length: ' + plen)\n if (payload[0] === '#') throw new Error('unknown encryption version')\n let data: Uint8Array\n try {\n data = base64.decode(payload)\n } catch (error) {\n throw new Error('invalid base64: ' + (error as any).message)\n }\n const dlen = data.length\n if (dlen < 99) throw new Error('invalid data length: ' + dlen)\n const vers = data[0]\n if (vers !== 2) throw new Error('unknown encryption version ' + vers)\n return {\n nonce: data.subarray(1, 33),\n ciphertext: data.subarray(33, -32),\n mac: data.subarray(-32),\n }\n}\n\nexport function encrypt(plaintext: string, conversationKey: Uint8Array, nonce: Uint8Array = randomBytes(32)): string {\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)\n const padded = pad(plaintext)\n const ciphertext = chacha20(chacha_key, chacha_nonce, padded)\n const mac = hmacAad(hmac_key, ciphertext, nonce)\n return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac))\n}\n\n/** Callers should validate payload size before calling to prevent DoS from oversized inputs. */\nexport function decrypt(payload: string, conversationKey: Uint8Array): string {\n const { nonce, ciphertext, mac } = decodePayload(payload)\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)\n const calculatedMac = hmacAad(hmac_key, ciphertext, nonce)\n if (!equalBytes(calculatedMac, mac)) throw new Error('invalid MAC')\n const padded = chacha20(chacha_key, chacha_nonce, ciphertext)\n return unpad(padded)\n}\n\nexport const v2 = {\n utils: {\n getConversationKey,\n calcPaddedLen,\n pad,\n unpad,\n },\n encrypt,\n decrypt,\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
4
|
+
"sourcesContent": ["import { chacha20 } from '@noble/ciphers/chacha.js'\nimport { equalBytes } from '@noble/ciphers/utils.js'\nimport { secp256k1 } from '@noble/curves/secp256k1.js'\nimport { extract as hkdf_extract, expand as hkdf_expand } from '@noble/hashes/hkdf.js'\nimport { hmac } from '@noble/hashes/hmac.js'\nimport { sha256 } from '@noble/hashes/sha2.js'\nimport { concatBytes, hexToBytes, randomBytes } from '@noble/hashes/utils.js'\nimport { base64 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nconst minPlaintextSize = 0x0001 // 1b msg => padded to 32b\nconst maxPlaintextSize = 0xffffffff // 4294967295 (2^32-1)\nconst extendedPrefixThreshold = 0x10000 // 65536: lengths below use 2-byte u16 prefix, at or above use 6-byte prefix\n\nexport function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array {\n const sharedX = secp256k1.getSharedSecret(privkeyA, hexToBytes('02' + pubkeyB)).subarray(1, 33)\n return hkdf_extract(sha256, sharedX, utf8Encoder.encode('nip44-v2'))\n}\n\nfunction getMessageKeys(\n conversationKey: Uint8Array,\n nonce: Uint8Array,\n): { chacha_key: Uint8Array; chacha_nonce: Uint8Array; hmac_key: Uint8Array } {\n const keys = hkdf_expand(sha256, conversationKey, nonce, 76)\n return {\n chacha_key: keys.subarray(0, 32),\n chacha_nonce: keys.subarray(32, 44),\n hmac_key: keys.subarray(44, 76),\n }\n}\n\nfunction calcPaddedLen(len: number): number {\n if (!Number.isSafeInteger(len) || len < 1) throw new Error('expected positive integer')\n if (len <= 32) return 32\n const nextPower = 2 ** (Math.floor(Math.log2(len - 1)) + 1)\n const chunk = nextPower <= 256 ? 32 : nextPower / 8\n return chunk * (Math.floor((len - 1) / chunk) + 1)\n}\n\nfunction writeU16BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > 0xffff)\n throw new Error('invalid plaintext size: must be between 1 and 65535 bytes')\n const arr = new Uint8Array(2)\n new DataView(arr.buffer).setUint16(0, num, false)\n return arr\n}\n\nfunction writeU32BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < extendedPrefixThreshold || num > maxPlaintextSize)\n throw new Error('invalid plaintext size: must be between 65536 and 4294967295 bytes')\n const arr = new Uint8Array(4)\n new DataView(arr.buffer).setUint32(0, num, false)\n return arr\n}\n\nfunction pad(plaintext: string): Uint8Array {\n const unpadded = utf8Encoder.encode(plaintext)\n const unpaddedLen = unpadded.length\n if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize)\n throw new Error('invalid plaintext size: must be between 1 and 4294967295 bytes')\n const prefix =\n unpaddedLen >= extendedPrefixThreshold\n ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen)) // 6 bytes\n : writeU16BE(unpaddedLen) // 2 bytes\n const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen)\n return concatBytes(prefix, unpadded, suffix)\n}\n\nfunction unpad(padded: Uint8Array): string {\n const dv = new DataView(padded.buffer, padded.byteOffset, padded.byteLength)\n const firstTwo = dv.getUint16(0)\n let unpaddedLen: number\n let prefixLen: number\n if (firstTwo === 0) {\n // Extended format: 2 zero bytes + 4-byte u32 length\n unpaddedLen = dv.getUint32(2)\n if (unpaddedLen < extendedPrefixThreshold) throw new Error('invalid padding')\n prefixLen = 6\n } else {\n unpaddedLen = firstTwo\n prefixLen = 2\n }\n const unpadded = padded.subarray(prefixLen, prefixLen + unpaddedLen)\n if (\n unpaddedLen < minPlaintextSize ||\n unpaddedLen > maxPlaintextSize ||\n unpadded.length !== unpaddedLen ||\n padded.length !== prefixLen + calcPaddedLen(unpaddedLen)\n )\n throw new Error('invalid padding')\n return utf8Decoder.decode(unpadded)\n}\n\nfunction hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {\n if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')\n const combined = concatBytes(aad, message)\n return hmac(sha256, key, combined)\n}\n\n// metadata: always 65b (version: 1b, nonce: 32b, mac: 32b)\n// plaintext: 1b to 0xffffffff\n// padded plaintext (small, <65536): 32b to 0x10000, with 2b prefix -> 34b to 0x10000+2\n// padded plaintext (large, >=65536): 0x10000 to 0x100000000, with 6b prefix -> 0x10006 to 0x100000000+6\n// ciphertext: same as padded plaintext (chacha20 doesn't change length)\n// raw payload (small): 99 (65+34) to 65603 (65+0x10000+2)\n// raw payload (large): 65607 (65+0x10006) to 4294967367 (65+0x100000000+6)\nfunction decodePayload(payload: string): { nonce: Uint8Array; ciphertext: Uint8Array; mac: Uint8Array } {\n if (typeof payload !== 'string') throw new Error('payload must be a valid string')\n const plen = payload.length\n if (plen < 132) throw new Error('invalid payload length: ' + plen)\n if (payload[0] === '#') throw new Error('unknown encryption version')\n let data: Uint8Array\n try {\n data = base64.decode(payload)\n } catch (error) {\n throw new Error('invalid base64: ' + (error as any).message)\n }\n const dlen = data.length\n if (dlen < 99) throw new Error('invalid data length: ' + dlen)\n const vers = data[0]\n if (vers !== 2) throw new Error('unknown encryption version ' + vers)\n return {\n nonce: data.subarray(1, 33),\n ciphertext: data.subarray(33, -32),\n mac: data.subarray(-32),\n }\n}\n\nexport function encrypt(plaintext: string, conversationKey: Uint8Array, nonce: Uint8Array = randomBytes(32)): string {\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)\n const padded = pad(plaintext)\n const ciphertext = chacha20(chacha_key, chacha_nonce, padded)\n const mac = hmacAad(hmac_key, ciphertext, nonce)\n return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac))\n}\n\n/** Callers should validate payload size before calling to prevent DoS from oversized inputs. */\nexport function decrypt(payload: string, conversationKey: Uint8Array): string {\n const { nonce, ciphertext, mac } = decodePayload(payload)\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce)\n const calculatedMac = hmacAad(hmac_key, ciphertext, nonce)\n if (!equalBytes(calculatedMac, mac)) throw new Error('invalid MAC')\n const padded = chacha20(chacha_key, chacha_nonce, ciphertext)\n return unpad(padded)\n}\n\nexport const v2 = {\n utils: {\n getConversationKey,\n calcPaddedLen,\n pad,\n unpad,\n },\n encrypt,\n decrypt,\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n if (input.length !== 64) return false\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["import_utils", "hkdf_extract", "hkdf_expand"]
|
|
7
7
|
}
|
|
@@ -40,6 +40,8 @@ var import_utils = require("@noble/hashes/utils.js");
|
|
|
40
40
|
var utf8Decoder = new TextDecoder("utf-8");
|
|
41
41
|
var utf8Encoder = new TextEncoder();
|
|
42
42
|
function isHex32(input) {
|
|
43
|
+
if (input.length !== 64)
|
|
44
|
+
return false;
|
|
43
45
|
for (let i = 0; i < 64; i++) {
|
|
44
46
|
let cc = input.charCodeAt(i);
|
|
45
47
|
if (isNaN(cc) || cc < 48 || cc > 102 || cc > 57 && cc < 97) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../nip45.ts", "../../utils.ts"],
|
|
4
|
-
"sourcesContent": ["import { sha256 } from '@noble/hashes/sha2.js'\nimport { bytesToHex } from '@noble/hashes/utils.js'\n\nimport type { Event } from './core.ts'\nimport type { Filter } from './filter.ts'\nimport { isHex32 } from './utils.ts'\n\nconst M = 256\nconst HLL_HEX_LENGTH = M * 2\nconst utf8Encoder = new TextEncoder()\n\nexport type CountManyDirective = 'reactions' | 'reposts' | 'quotes' | 'replies' | 'comments' | 'followers'\n\nexport function getCountManyFilter(target: string, directive: CountManyDirective): Filter {\n switch (directive) {\n case 'reactions':\n return { '#e': [target], kinds: [7] }\n case 'reposts':\n return { '#e': [target], kinds: [6] }\n case 'quotes':\n return { '#q': [target], kinds: [1, 1111] }\n case 'replies':\n return { '#e': [target], kinds: [1] }\n case 'comments':\n return { '#E': [target], kinds: [1111] }\n case 'followers':\n return { '#p': [target], kinds: [3] }\n }\n}\n\nexport function newHll(): Uint8Array {\n return new Uint8Array(M)\n}\n\nexport function hllDecode(hex: string): Uint8Array | undefined {\n if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-f]+$/.test(hex)) return undefined\n\n const registers = new Uint8Array(M)\n for (let i = 0; i < M; i++) {\n registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return registers\n}\n\nexport function hllEncode(registers: Uint8Array): string {\n if (registers.length !== M) throw new Error(`invalid number of registers ${registers.length}`)\n\n let hex = ''\n for (let i = 0; i < M; i++) {\n hex += registers[i].toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function computeOffset(filterFirstTagValue: string): number {\n let hex = filterFirstTagValue\n\n if (!isHex32(hex)) {\n const parts = hex.split(':')\n if (parts.length === 3 && isHex32(parts[1])) {\n hex = parts[1]\n } else {\n hex = bytesToHex(sha256(utf8Encoder.encode(filterFirstTagValue)))\n }\n }\n\n return parseInt(hex[32], 16) + 8\n}\n\nexport function getFilterFirstTagValue(filter: Filter): string | undefined {\n for (const key in filter) {\n if (key[0] !== '#') continue\n\n const values = filter[key as `#${string}`]\n if (Array.isArray(values) && typeof values[0] === 'string') return values[0]\n }\n\n return undefined\n}\n\nexport function feedPubkey(hll: Uint8Array, pubkey: string, offset: number): Uint8Array {\n if (offset < 0 || offset > 24) throw new Error(`invalid offset ${offset}`)\n if (!isHex32(pubkey)) throw new Error('pubkey must be 32-byte hex')\n\n if (hll.length === 0) hll = newHll()\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const ri = parseInt(pubkey.slice(offset * 2, offset * 2 + 2), 16)\n const value = countLeadingZeroBitsAfterOffset(pubkey, offset) + 1\n if (value > hll[ri]) hll[ri] = value\n\n return hll\n}\n\nexport function feedEvent(hll: Uint8Array, event: Event, offset: number): Uint8Array {\n return feedPubkey(hll, event.pubkey, offset)\n}\n\nexport function mergeHll(target: Uint8Array, source: Uint8Array): Uint8Array {\n if (target.length === 0) target = newHll()\n if (target.length !== M) throw new Error(`invalid number of registers ${target.length}`)\n if (source.length !== M) throw new Error(`invalid number of registers ${source.length}`)\n\n for (let i = 0; i < M; i++) {\n if (source[i] > target[i]) target[i] = source[i]\n }\n\n return target\n}\n\nexport function estimateCount(hll: Uint8Array): number {\n if (hll.length === 0) return 0\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const v = countZeros(hll)\n if (v !== 0) {\n const lc = linearCounting(M, v)\n if (lc <= 220) return Math.floor(lc)\n }\n\n const estimate = calculateEstimate(hll)\n if (estimate <= M * 3 && v !== 0) return Math.floor(linearCounting(M, v))\n\n return Math.floor(estimate)\n}\n\nfunction countLeadingZeroBitsAfterOffset(pubkey: string, offset: number): number {\n let zeroBits = 0\n for (let i = offset + 1; i < offset + 8; i++) {\n const byte = parseInt(pubkey.slice(i * 2, i * 2 + 2), 16)\n if (byte === 0) {\n zeroBits += 8\n continue\n }\n\n let mask = 0x80\n while ((byte & mask) === 0) {\n zeroBits++\n mask >>= 1\n }\n break\n }\n return zeroBits\n}\n\nfunction countZeros(registers: Uint8Array): number {\n let count = 0\n for (let i = 0; i < M; i++) {\n if (registers[i] === 0) count++\n }\n return count\n}\n\nfunction linearCounting(m: number, v: number): number {\n return m * Math.log(m / v)\n}\n\nfunction calculateEstimate(registers: Uint8Array): number {\n let sum = 0\n for (let i = 0; i < M; i++) {\n sum += 1 / 2 ** registers[i]\n }\n return (0.7182725932495458 * M * M) / sum\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAuB;AACvB,IAAAA,gBAA2B;;;ACI3B,mBAAuC;AAHhC,IAAM,cAA2B,IAAI,YAAY,OAAO;AACxD,IAAM,cAA2B,IAAI,YAAY;AA8HjD,SAAS,QAAQ,OAAwB;AAC9C,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,KAAK,MAAM,WAAW,CAAC;AAC3B,QAAI,MAAM,EAAE,KAAK,KAAK,MAAM,KAAK,OAAQ,KAAK,MAAM,KAAK,IAAK;AAC5D,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;;;
|
|
4
|
+
"sourcesContent": ["import { sha256 } from '@noble/hashes/sha2.js'\nimport { bytesToHex } from '@noble/hashes/utils.js'\n\nimport type { Event } from './core.ts'\nimport type { Filter } from './filter.ts'\nimport { isHex32 } from './utils.ts'\n\nconst M = 256\nconst HLL_HEX_LENGTH = M * 2\nconst utf8Encoder = new TextEncoder()\n\nexport type CountManyDirective = 'reactions' | 'reposts' | 'quotes' | 'replies' | 'comments' | 'followers'\n\nexport function getCountManyFilter(target: string, directive: CountManyDirective): Filter {\n switch (directive) {\n case 'reactions':\n return { '#e': [target], kinds: [7] }\n case 'reposts':\n return { '#e': [target], kinds: [6] }\n case 'quotes':\n return { '#q': [target], kinds: [1, 1111] }\n case 'replies':\n return { '#e': [target], kinds: [1] }\n case 'comments':\n return { '#E': [target], kinds: [1111] }\n case 'followers':\n return { '#p': [target], kinds: [3] }\n }\n}\n\nexport function newHll(): Uint8Array {\n return new Uint8Array(M)\n}\n\nexport function hllDecode(hex: string): Uint8Array | undefined {\n if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-f]+$/.test(hex)) return undefined\n\n const registers = new Uint8Array(M)\n for (let i = 0; i < M; i++) {\n registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return registers\n}\n\nexport function hllEncode(registers: Uint8Array): string {\n if (registers.length !== M) throw new Error(`invalid number of registers ${registers.length}`)\n\n let hex = ''\n for (let i = 0; i < M; i++) {\n hex += registers[i].toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function computeOffset(filterFirstTagValue: string): number {\n let hex = filterFirstTagValue\n\n if (!isHex32(hex)) {\n const parts = hex.split(':')\n if (parts.length === 3 && isHex32(parts[1])) {\n hex = parts[1]\n } else {\n hex = bytesToHex(sha256(utf8Encoder.encode(filterFirstTagValue)))\n }\n }\n\n return parseInt(hex[32], 16) + 8\n}\n\nexport function getFilterFirstTagValue(filter: Filter): string | undefined {\n for (const key in filter) {\n if (key[0] !== '#') continue\n\n const values = filter[key as `#${string}`]\n if (Array.isArray(values) && typeof values[0] === 'string') return values[0]\n }\n\n return undefined\n}\n\nexport function feedPubkey(hll: Uint8Array, pubkey: string, offset: number): Uint8Array {\n if (offset < 0 || offset > 24) throw new Error(`invalid offset ${offset}`)\n if (!isHex32(pubkey)) throw new Error('pubkey must be 32-byte hex')\n\n if (hll.length === 0) hll = newHll()\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const ri = parseInt(pubkey.slice(offset * 2, offset * 2 + 2), 16)\n const value = countLeadingZeroBitsAfterOffset(pubkey, offset) + 1\n if (value > hll[ri]) hll[ri] = value\n\n return hll\n}\n\nexport function feedEvent(hll: Uint8Array, event: Event, offset: number): Uint8Array {\n return feedPubkey(hll, event.pubkey, offset)\n}\n\nexport function mergeHll(target: Uint8Array, source: Uint8Array): Uint8Array {\n if (target.length === 0) target = newHll()\n if (target.length !== M) throw new Error(`invalid number of registers ${target.length}`)\n if (source.length !== M) throw new Error(`invalid number of registers ${source.length}`)\n\n for (let i = 0; i < M; i++) {\n if (source[i] > target[i]) target[i] = source[i]\n }\n\n return target\n}\n\nexport function estimateCount(hll: Uint8Array): number {\n if (hll.length === 0) return 0\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const v = countZeros(hll)\n if (v !== 0) {\n const lc = linearCounting(M, v)\n if (lc <= 220) return Math.floor(lc)\n }\n\n const estimate = calculateEstimate(hll)\n if (estimate <= M * 3 && v !== 0) return Math.floor(linearCounting(M, v))\n\n return Math.floor(estimate)\n}\n\nfunction countLeadingZeroBitsAfterOffset(pubkey: string, offset: number): number {\n let zeroBits = 0\n for (let i = offset + 1; i < offset + 8; i++) {\n const byte = parseInt(pubkey.slice(i * 2, i * 2 + 2), 16)\n if (byte === 0) {\n zeroBits += 8\n continue\n }\n\n let mask = 0x80\n while ((byte & mask) === 0) {\n zeroBits++\n mask >>= 1\n }\n break\n }\n return zeroBits\n}\n\nfunction countZeros(registers: Uint8Array): number {\n let count = 0\n for (let i = 0; i < M; i++) {\n if (registers[i] === 0) count++\n }\n return count\n}\n\nfunction linearCounting(m: number, v: number): number {\n return m * Math.log(m / v)\n}\n\nfunction calculateEstimate(registers: Uint8Array): number {\n let sum = 0\n for (let i = 0; i < M; i++) {\n sum += 1 / 2 ** registers[i]\n }\n return (0.7182725932495458 * M * M) / sum\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n if (input.length !== 64) return false\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_utils", "utf8Encoder"]
|
|
7
7
|
}
|
|
@@ -62,6 +62,8 @@ function normalizeURL(url) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
function isHex32(input) {
|
|
65
|
+
if (input.length !== 64)
|
|
66
|
+
return false;
|
|
65
67
|
for (let i2 = 0; i2 < 64; i2++) {
|
|
66
68
|
let cc = input.charCodeAt(i2);
|
|
67
69
|
if (isNaN(cc) || cc < 48 || cc > 102 || cc > 57 && cc < 97) {
|
|
@@ -886,10 +888,12 @@ var Subscription = class {
|
|
|
886
888
|
this.closed = true;
|
|
887
889
|
}
|
|
888
890
|
this.relay.openSubs.delete(this.id);
|
|
889
|
-
this.
|
|
890
|
-
|
|
891
|
-
this.relay.
|
|
892
|
-
|
|
891
|
+
if (!this.id.startsWith("<forced-ping>")) {
|
|
892
|
+
this.relay.ongoingOperations--;
|
|
893
|
+
if (this.relay.ongoingOperations === 0) {
|
|
894
|
+
this.relay.idleSince = Date.now();
|
|
895
|
+
this.relay.scheduleIdleClose();
|
|
896
|
+
}
|
|
893
897
|
}
|
|
894
898
|
this.onclose?.(reason);
|
|
895
899
|
}
|
|
@@ -1240,7 +1244,7 @@ var AbstractSimplePool = class {
|
|
|
1240
1244
|
});
|
|
1241
1245
|
} catch (err) {
|
|
1242
1246
|
this.onRelayConnectionFailure?.(url);
|
|
1243
|
-
return
|
|
1247
|
+
return Promise.reject("connection failure: " + String(err));
|
|
1244
1248
|
}
|
|
1245
1249
|
return r.publish(event).catch(async (err) => {
|
|
1246
1250
|
if (err instanceof Error && err.message.startsWith("auth-required: ") && params?.onauth) {
|