2024-04-08 12:45:51 +00:00
|
|
|
import { nip19, verifyEvent } from 'nostr-tools'
|
2024-02-28 16:49:44 +00:00
|
|
|
import { SignedEvent } from '../types'
|
2024-03-05 10:08:33 +00:00
|
|
|
import axios from 'axios'
|
2024-05-27 09:32:24 +00:00
|
|
|
import { NIP05_REGEX } from '../constants'
|
2024-02-28 16:49:44 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param hexKey hex private or public key
|
|
|
|
* @returns whether or not is key valid
|
|
|
|
*/
|
|
|
|
const validateHex = (hexKey: string) => {
|
|
|
|
return hexKey.match(/^[a-f0-9]{64}$/)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* NPUB provided - it will convert NPUB to HEX
|
|
|
|
* HEX provided - it will return HEX
|
|
|
|
*
|
2024-04-08 12:45:51 +00:00
|
|
|
* @param pubKey in NPUB, HEX format
|
2024-02-28 16:49:44 +00:00
|
|
|
* @returns HEX format
|
|
|
|
*/
|
2024-05-17 08:34:56 +00:00
|
|
|
export const npubToHex = (pubKey: string): string | null => {
|
2024-02-28 16:49:44 +00:00
|
|
|
// If key is NPUB
|
2024-05-17 08:34:56 +00:00
|
|
|
if (pubKey.startsWith('npub1')) {
|
2024-02-28 16:49:44 +00:00
|
|
|
try {
|
|
|
|
return nip19.decode(pubKey).data as string
|
|
|
|
} catch (error) {
|
2024-05-17 08:34:56 +00:00
|
|
|
return null
|
2024-02-28 16:49:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// valid hex key
|
2024-05-17 08:34:56 +00:00
|
|
|
if (validateHex(pubKey)) return pubKey
|
2024-02-28 16:49:44 +00:00
|
|
|
|
|
|
|
// Not a valid hex key
|
2024-05-17 08:34:56 +00:00
|
|
|
return null
|
2024-02-28 16:49:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If NSEC key is provided function will convert it to HEX
|
|
|
|
* If HEX key Is provided function will validate the HEX format and return
|
|
|
|
*
|
|
|
|
* @param nsec or private key in HEX format
|
|
|
|
*/
|
|
|
|
export const nsecToHex = (nsec: string): string | null => {
|
|
|
|
// If key is NSEC
|
|
|
|
if (nsec.startsWith('nsec')) {
|
|
|
|
try {
|
|
|
|
return nip19.decode(nsec).data as string
|
|
|
|
} catch (error) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// since it's not NSEC key we check if it's a valid hex key
|
|
|
|
if (validateHex(nsec)) return nsec
|
|
|
|
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2024-05-17 08:34:56 +00:00
|
|
|
export const hexToNpub = (hexPubkey: string): `npub1${string}` => {
|
|
|
|
if (hexPubkey.startsWith('npub1')) return hexPubkey as `npub1${string}`
|
2024-03-01 10:16:35 +00:00
|
|
|
|
|
|
|
return nip19.npubEncode(hexPubkey)
|
|
|
|
}
|
|
|
|
|
2024-02-28 16:49:44 +00:00
|
|
|
export const verifySignedEvent = (event: SignedEvent) => {
|
|
|
|
const isGood = verifyEvent(event)
|
|
|
|
|
|
|
|
if (!isGood) {
|
|
|
|
throw new Error(
|
|
|
|
'Signed event did not pass verification. Check sig, id and pubkey.'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2024-03-05 10:08:33 +00:00
|
|
|
|
2024-05-27 09:32:24 +00:00
|
|
|
/**
|
|
|
|
* Function to query NIP-05 data and return the public key and relays.
|
|
|
|
*
|
|
|
|
* @param {string} nip05 - The NIP-05 identifier in the format "name@domain".
|
|
|
|
* @returns {Promise<{ pubkey: string, relays: string[] }>} - The public key and an array of relay URLs.
|
|
|
|
* @throws Will throw an error if the NIP-05 identifier is invalid or if there is an issue with the network request.
|
|
|
|
*/
|
2024-03-05 10:08:33 +00:00
|
|
|
export const queryNip05 = async (
|
|
|
|
nip05: string
|
|
|
|
): Promise<{
|
|
|
|
pubkey: string
|
|
|
|
relays: string[]
|
|
|
|
}> => {
|
|
|
|
const match = nip05.match(NIP05_REGEX)
|
2024-05-27 09:32:24 +00:00
|
|
|
|
|
|
|
// Throw an error if the NIP-05 identifier is invalid
|
2024-03-05 10:08:33 +00:00
|
|
|
if (!match) throw new Error('Invalid nip05')
|
|
|
|
|
2024-05-27 09:32:24 +00:00
|
|
|
// Destructure the match result, assigning default value '_' to name if not provided
|
2024-03-05 10:08:33 +00:00
|
|
|
const [_, name = '_', domain] = match
|
2024-05-27 09:32:24 +00:00
|
|
|
|
|
|
|
// Construct the URL to query the NIP-05 data
|
2024-03-05 10:08:33 +00:00
|
|
|
const url = `https://${domain}/.well-known/nostr.json?name=${name}`
|
2024-05-27 09:32:24 +00:00
|
|
|
|
|
|
|
// Perform the network request to get the NIP-05 data
|
2024-03-05 10:08:33 +00:00
|
|
|
const res = await axios(url)
|
|
|
|
.then((res) => {
|
|
|
|
return res.data
|
|
|
|
})
|
|
|
|
.catch((err) => {
|
|
|
|
console.log('err :>> ', err)
|
|
|
|
throw err
|
|
|
|
})
|
|
|
|
|
2024-05-27 09:32:24 +00:00
|
|
|
// Extract the public key from the response data
|
2024-03-05 10:08:33 +00:00
|
|
|
const pubkey = res.names[name]
|
|
|
|
const relays: string[] = []
|
|
|
|
|
2024-05-27 09:32:24 +00:00
|
|
|
// If a public key is found
|
2024-03-05 10:08:33 +00:00
|
|
|
if (pubkey) {
|
2024-05-27 09:32:24 +00:00
|
|
|
// Function to add relays if they exist and are not empty
|
|
|
|
const addRelays = (relayList?: string[]) => {
|
|
|
|
if (relayList && relayList.length > 0) {
|
|
|
|
relays.push(...relayList)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for user-specific relays in the NIP-46 section of the response data
|
|
|
|
if (res.nip46) {
|
|
|
|
addRelays(res.nip46[pubkey] as string[])
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for user-specific relays in the relays section of the response data if not found in NIP-46
|
|
|
|
if (relays.length === 0 && res.relays) {
|
|
|
|
addRelays(res.relays[pubkey] as string[])
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no user-specific relays are found, check for root user relays
|
|
|
|
if (relays.length === 0) {
|
2024-03-05 10:08:33 +00:00
|
|
|
const root = res.names['_']
|
|
|
|
if (root) {
|
2024-05-27 09:32:24 +00:00
|
|
|
// Check for root user relays in both NIP-46 and relays sections
|
|
|
|
addRelays(res.nip46?.[root] as string[])
|
|
|
|
addRelays(res.relays?.[root] as string[])
|
2024-03-05 10:08:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-27 09:32:24 +00:00
|
|
|
// Return the public key and the array of relays
|
2024-03-05 10:08:33 +00:00
|
|
|
return {
|
|
|
|
pubkey,
|
|
|
|
relays
|
|
|
|
}
|
|
|
|
}
|
2024-03-19 10:27:18 +00:00
|
|
|
|
|
|
|
export const base64EncodeSignedEvent = (event: SignedEvent) => {
|
|
|
|
try {
|
|
|
|
const authEventSerialized = JSON.stringify(event)
|
|
|
|
const token = btoa(authEventSerialized)
|
|
|
|
return token
|
|
|
|
} catch (error) {
|
|
|
|
throw new Error('An error occurred in JSON.stringify of signedAuthEvent')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const base64DecodeAuthToken = (authToken: string): SignedEvent => {
|
|
|
|
const decodedToken = atob(authToken)
|
|
|
|
|
|
|
|
try {
|
|
|
|
const signedEvent = JSON.parse(decodedToken)
|
|
|
|
return signedEvent
|
|
|
|
} catch (error) {
|
|
|
|
throw new Error('An error occurred in JSON.parse of the auth token')
|
|
|
|
}
|
|
|
|
}
|
2024-05-15 11:57:54 +00:00
|
|
|
|
2024-05-16 06:55:24 +00:00
|
|
|
/**
|
2024-05-17 08:34:56 +00:00
|
|
|
* @param pubkey in hex or npub format
|
2024-05-16 06:55:24 +00:00
|
|
|
* @returns robohash.org url for the avatar
|
|
|
|
*/
|
2024-05-17 11:35:37 +00:00
|
|
|
export const getRoboHashPicture = (
|
|
|
|
pubkey?: string,
|
|
|
|
set: number = 1
|
|
|
|
): string => {
|
2024-05-17 11:33:01 +00:00
|
|
|
if (!pubkey) return ''
|
2024-05-16 06:55:24 +00:00
|
|
|
const npub = hexToNpub(pubkey)
|
2024-05-17 07:37:30 +00:00
|
|
|
return `https://robohash.org/${npub}.png?set=set${set}`
|
2024-05-15 11:57:54 +00:00
|
|
|
}
|