/ src / services / nodeService.js
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  }