import { useEffect, useState } from "react" import { MdDeleteOutline } from "react-icons/md" import * as environment from "../environment" import "./Bootnodes.css" import { Title, Switch } from "." import { helper } from "@substrate/light-client-extension-helpers/extension-page" import { wellKnownGenesisHashByChainId } from "../constants" import { Select, SelectValue, SelectTrigger, SelectContent, SelectGroup, SelectItem, } from "@/components/ui/select" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" interface BootnodesType { checked: boolean bootnode: string } const getBootNodes = async (chainId: string) => (await helper.getChains()).find( ({ genesisHash }) => genesisHash === wellKnownGenesisHashByChainId[chainId], )?.bootNodes ?? [] const setBootNodes = async (chainId: string, bootNodes: string[]) => helper.setBootNodes(wellKnownGenesisHashByChainId[chainId], bootNodes) // Add to localstorage the given bootnode for the given chain const saveToLocalStorage = async ( chainName: string, bootnode: string, add: boolean, def: string[], ) => { if (def.length === 0) throw new Error("Default Bootnodes should exist.") let res: string[] const chainBootnodes = await getBootNodes(chainName) res = chainBootnodes && Object.keys(chainBootnodes).length > 0 ? [...chainBootnodes] : [...def] add ? res.push(bootnode) : res.splice(res.indexOf(bootnode), 1) await setBootNodes(chainName, res) } export const Bootnodes = () => { const [selectedChain, setSelectedChain] = useState("casper_staging_testnet") const [defaultBn, setDefaultBn] = useState([]) const [customBn, setCustomBn] = useState([]) const [customBnInput, setCustomBnInput] = useState("") const [selectedChainDefaultBn, setSelectedChainDefaultBn] = useState([]) const [addMessage, setAddMessage] = useState(undefined) const [bootnodeMsgClass, setBootnodeMsgClass] = useState() useEffect(() => { if (addMessage && !addMessage?.error) { setBootnodeMsgClass("pb-2 text-accent") setCustomBnInput("") } else { setBootnodeMsgClass("pb-2 text-destructive") } }, [addMessage]) useEffect(() => { Promise.all([ getBootNodes(selectedChain), environment.getDefaultBootnodes(selectedChain), ]).then(([bootnodes, defaultBootnodes]) => { console.assert(defaultBootnodes, `Invalid chain name: ${selectedChain}`) defaultBootnodes ??= [] setSelectedChainDefaultBn(defaultBootnodes) const tmpDef: BootnodesType[] = [] const tmpCust: BootnodesType[] = [] // When bootnodes do not exist assign and save the local ones if (!bootnodes?.length) { setBootNodes(selectedChain, defaultBootnodes) defaultBootnodes.forEach((b) => { tmpDef.push({ bootnode: b, checked: true }) }) } else { bootnodes?.forEach((b) => { defaultBootnodes?.length && defaultBootnodes?.includes(b) ? tmpDef.push({ bootnode: b, checked: true }) : tmpCust.push({ bootnode: b, checked: true }) }) } setDefaultBn(tmpDef) setCustomBn(tmpCust) }) }, [selectedChain]) const checkMultiAddr = (addr: string) => { const ws = /\/(ip4|ip6|dns4|dns6|dns)\/([a-zA-Z0-9.-]+)\/tcp\/[0-9]{1,5}(\/(ws|wss|tls\/ws))?\/p2p\/[a-zA-Z1-9^Il0O]+/i const webrtc = /\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)\/p2p\/[a-zA-Z1-9^Il0O]+/i if (!ws.test(addr) && !webrtc.test(addr)) throw new Error("Provided multiaddress is not correct.") } const alterBootnodes = async ( bootnode: string, add: boolean, defaultBootnode: boolean, ) => { // if bootnode belongs to the list (default) then it does not need to be validated as it // comes from the chainspecs. It can be saved to the local storage at once. try { if (!defaultBootnode) { // verify bootnode validity checkMultiAddr(customBnInput) } // Check if bootnode already exists in the default and custom lists if ( selectedChainDefaultBn?.includes(customBnInput) || customBn.map((c) => c.bootnode).includes(customBnInput) ) { setAddMessage({ error: true, message: "Bootnode already exists in the list." }) } else { await saveToLocalStorage( selectedChain, bootnode, add, selectedChainDefaultBn, ) } const tmp = defaultBootnode ? [...defaultBn] : [...customBn] const i = tmp.findIndex((b) => b.bootnode === bootnode) if (i !== -1) { tmp[i].checked = add } else { tmp.push({ bootnode, checked: true }) } defaultBootnode ? setDefaultBn(tmp) : setCustomBn(tmp) setCustomBnInput("") } catch (err) { setAddMessage({ error: true, message: (err as Error).message.replace(/^\w/, (c) => c.toUpperCase()) }) } } return (

Networks

Bootnodes Default
{selectedChainDefaultBn?.map((bn) => (
{bn}
d.bootnode).includes(bn)} />
))}
Custom
{customBn.map((c) => (
{c.bootnode}
))}
Add custom Bootnode
{ addMessage && setAddMessage(undefined) setCustomBnInput(v.target.value) }} />

{addMessage && Object.keys(addMessage) ? addMessage.message : ""}

) }