nodeService.js
1 import axios from "axios"; 2 import { runCommand } from "../utils/command.js"; 3 import { 4 showErrorMessage, 5 showInfoMessage, 6 showSuccessMessage, 7 } from "../utils/messages.js"; 8 import os from "os"; 9 import { getCodexVersion } from "../handlers/installationHandlers.js"; 10 11 const platform = os.platform(); 12 13 // Add a variable to store wallet address in memory 14 let currentWallet = null; 15 16 export async function setWalletAddress(wallet) { 17 // Basic ERC20 address validation 18 if (wallet && !/^0x[a-fA-F0-9]{40}$/.test(wallet)) { 19 throw new Error("Invalid ERC20 wallet address format"); 20 } 21 currentWallet = wallet; 22 } 23 24 export async function getWalletAddress() { 25 return currentWallet; 26 } 27 28 export async function isNodeRunning(config) { 29 try { 30 const response = await axios.get( 31 `http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`, 32 ); 33 return response.status === 200; 34 } catch (error) { 35 return false; 36 } 37 } 38 39 export async function isCodexInstalled(config) { 40 try { 41 const version = await getCodexVersion(config); 42 return version.length > 0; 43 } catch (error) { 44 return false; 45 } 46 } 47 48 export async function logToSupabase( 49 nodeData, 50 retryCount = 3, 51 retryDelay = 1000, 52 ) { 53 const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 54 55 for (let attempt = 1; attempt <= retryCount; attempt++) { 56 try { 57 const peerCount = nodeData.table.nodes 58 ? nodeData.table.nodes.length 59 : "0"; 60 const payload = { 61 nodeId: nodeData.table.localNode.nodeId, 62 peerId: nodeData.table.localNode.peerId, 63 publicIp: nodeData.announceAddresses[0].split("/")[2], 64 version: nodeData.codex.version, 65 peerCount: peerCount == 0 ? "0" : peerCount, 66 port: nodeData.announceAddresses[0].split("/")[4], 67 listeningAddress: nodeData.table.localNode.address, 68 timestamp: new Date().toISOString(), 69 wallet: currentWallet, 70 }; 71 72 const response = await axios.post( 73 "https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/codexnodes", 74 payload, 75 { 76 headers: { 77 "Content-Type": "application/json", 78 }, 79 timeout: 5000, 80 }, 81 ); 82 83 return response.status === 200; 84 } catch (error) { 85 const isLastAttempt = attempt === retryCount; 86 const isNetworkError = 87 error.code === "ENOTFOUND" || 88 error.code === "ETIMEDOUT" || 89 error.code === "ECONNREFUSED"; 90 91 if (isLastAttempt || !isNetworkError) { 92 console.error( 93 `Failed to log node data (attempt ${attempt}/${retryCount}):`, 94 error.message, 95 ); 96 if (error.response) { 97 console.error("Error response:", { 98 status: error.response.status, 99 data: error.response.data, 100 }); 101 } 102 if (isLastAttempt) return false; 103 } else { 104 // Only log retry attempts for network errors 105 console.log( 106 `Retrying to log data (attempt ${attempt}/${retryCount})...`, 107 ); 108 await delay(retryDelay); 109 } 110 } 111 } 112 return false; 113 } 114 115 export async function checkDependencies() { 116 if (platform === "linux") { 117 try { 118 await runCommand("ldconfig -p | grep libgomp"); 119 return true; 120 } catch (error) { 121 console.log( 122 showErrorMessage("Required dependency libgomp1 is not installed."), 123 ); 124 console.log( 125 showInfoMessage( 126 "For Debian-based Linux systems, please install it manually using:\n\n" + 127 "sudo apt update && sudo apt install libgomp1", 128 ), 129 ); 130 return false; 131 } 132 } 133 return true; 134 } 135 136 export async function startPeriodicLogging(config) { 137 const FIFTEEN_MINUTES = 15 * 60 * 1000; // 15 minutes in milliseconds 138 139 const logNodeInfo = async () => { 140 try { 141 const response = await axios.get( 142 `http://localhost:${config.ports.apiPort}/api/codex/v1/debug/info`, 143 ); 144 if (response.status === 200) { 145 await logToSupabase(response.data); 146 } 147 } catch (error) { 148 // Silently handle any logging errors to not disrupt the node operation 149 console.error("Failed to log node data:", error.message); 150 } 151 }; 152 153 // Initial log 154 await logNodeInfo(); 155 156 // Set up periodic logging 157 const intervalId = setInterval(logNodeInfo, FIFTEEN_MINUTES); 158 159 // Return cleanup function 160 return () => clearInterval(intervalId); 161 } 162 163 export async function updateWalletAddress(nodeId, wallet) { 164 // Basic ERC20 address validation 165 if (!/^0x[a-fA-F0-9]{40}$/.test(wallet)) { 166 throw new Error("Invalid ERC20 wallet address format"); 167 } 168 169 try { 170 const response = await axios.post( 171 "https://vfcnsjxahocmzefhckfz.supabase.co/functions/v1/wallet", 172 { 173 nodeId, 174 wallet, 175 }, 176 { 177 headers: { 178 "Content-Type": "application/json", 179 }, 180 timeout: 5000, 181 }, 182 ); 183 return response.status === 200; 184 } catch (error) { 185 console.error("Failed to update wallet address:", error.message); 186 throw error; 187 } 188 }