### Install Golang on Ubuntu Source: https://docs.mysterium.network/for-developers/environment-setup Manual installation of the Go toolchain by downloading and extracting the tarball. ```bash sudo apt-get install -y build-essential sudo rm -rf /usr/local/go wget https://golang.org/dl/go1.15.4.linux-amd64.tar.gz tar -C /usr/local -xzf go1.15.4.linux-amd64.tar.gz ``` -------------------------------- ### Install Docker Compose on Ubuntu Source: https://docs.mysterium.network/for-developers/environment-setup Manual installation of the latest Docker Compose binary from GitHub. ```bash sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose ``` -------------------------------- ### Build output example Source: https://docs.mysterium.network/for-developers/node-development Expected console output after running the build command. ```text 2020-11-12T17:29:07.178 INF ci/packages/build.go:83 > Building cmd/mysterium_node/mysterium_node.go -> myst linux/amd64 2020-11-12T17:29:07.178 ??? github.com/rs/zerolog@v1.17.2/log.go:403 > exec: go build -ldflags=-w -s -o /home/tomas/go/src/github.com/mysteriumnetwork/node/build/myst/myst cmd/mysterium_node/mysterium_node.go 2020-11-12T17:29:09.604 INF ci/packages/build.go:83 > Building cmd/supervisor/supervisor.go -> myst_supervisor linux/amd64 2020-11-12T17:29:09.604 ??? github.com/rs/zerolog@v1.17.2/log.go:403 > exec: go build -ldflags=-w -s -o /home/tomas/go/src/github.com/mysteriumnetwork/node/build/myst_supervisor/myst_supervisor cmd/supervisor/supervisor.go ``` -------------------------------- ### Install Mage Task Runner Source: https://docs.mysterium.network/for-developers/environment-setup Build and install the Mage automation tool from source. ```bash git clone https://github.com/magefile/mage cd mage go run bootstrap.go ``` -------------------------------- ### Run the node Source: https://docs.mysterium.network/for-developers/node-development Commands to start the node in daemon or service mode. ```bash mage daemon ``` ```bash build/myst/myst daemon ``` ```bash build/myst/myst service ``` -------------------------------- ### Start Mysterium nodes Source: https://docs.mysterium.network/developers Commands to initialize the node fleet using Docker or Yarn. ```bash docker-compose up fleet ``` ```bash yarn fleet-start ``` -------------------------------- ### Install Docker CE on Ubuntu Source: https://docs.mysterium.network/for-developers/environment-setup Automated script to install Docker and configure user permissions for non-root execution. ```bash curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $(whoami) docker login ``` -------------------------------- ### Proxy Package Scripts Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Example scripts to add to package.json for running the proxy server. ```json "scripts": { "build": "tsc", "start": "node ./index.js", "dev": "ts-node ./index.ts" } ``` -------------------------------- ### Register IPC channels and handle application lifecycle Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Setup IPC communication and ensure proper cleanup of Node and Supervisor processes on exit. ```typescript app.on("ready", async () => { mainWindow = await createWindow(); supervisor.registerIPC() mysteriumNode.registerIPC(getMainWindow) }); .... app.on("before-quit", async () => { await mysteriumNode.stop(); await supervisor.disconnect(); }); ``` -------------------------------- ### Manage Node Services with Tequilapi Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Functions to start and stop node services, handling TequilapiError exceptions for already running or missing services. ```typescript async function startNode(address: string) { if (nodes.has(address)) { let addressSplit = address.split(':') try { await nodes.get(address)!.api.serviceStart({ providerId: nodes.get(address)!.idenities[0].id, type: "wireguard" }) addOrUpdateNode(addressSplit[0], addressSplit[1]) } catch (e) { // User doesn't have the updated status of the node, so we update it if (e instanceof TequilapiError && e.message.startsWith("Service already running")) { addOrUpdateNode(addressSplit[0], addressSplit[1]) } else throw e } } } async function stopNode(address: string) { if (nodes.has(address)) { let addressSplit = address.split(':') try { await nodes.get(address)!.api.serviceStop(nodes.get(address)!.services[0].id) addOrUpdateNode(addressSplit[0], addressSplit[1]) } catch (e) { // User doesn't have the updated status of the node, so we update it if (e instanceof TequilapiError && e.message.startsWith("Service not found")) { addOrUpdateNode(addressSplit[0], addressSplit[1]) } else throw e } } } ``` -------------------------------- ### Supervisor IPC and Service Management Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Defines the Supervisor class responsible for IPC communication, service installation, and connection management. ```typescript function mystSockPath(): string { if (isWin) { return "\\\\.\\pipe\\mystpipe" } return "/var/run/myst.sock" } const supervisorBin = (): string => { let supervisorBinaryName = "bin/myst_supervisor" if (isWin) { supervisorBinaryName += ".exe" } return staticAssetPath(supervisorBinaryName) } export class Supervisor { conn?: Socket registerIPC(): void { ipcMain.handle(MainIpcListenChannels.SupervisorConnect, () => this.connect()) ipcMain.handle(MainIpcListenChannels.SupervisorInstall, () => this.install()) ipcMain.handle(MainIpcListenChannels.SupervisorUpgrade, () => this.upgrade()) ipcMain.handle(MainIpcListenChannels.SupervisorDisconnect, () => this.disconnect()) } async connect(): Promise { log.info("Connecting to the supervisor...") const mystSock = mystSockPath() return await new Promise((resolve, reject) => { this.conn = net .createConnection(mystSock) .on("connect", () => { log.info("Connected to: ", mystSock) return resolve() }) .on("data", (data: Buffer) => { log.info("Server:", data.toString()) }) .on("error", function (data) { return reject(data) }) }) } //..... async upgrade(): Promise { const bundledVersion = packageJson.dependencies["@mysteriumnetwork/node"] let runningVersion = "" try { runningVersion = await this.runningVersion() } catch (err) { log.error("Error checking running version", err) } log.info("Supervisor version bundled:", bundledVersion, "running:", runningVersion) if (runningVersion == bundledVersion) { log.info("Running supervisor version matches, skipping the upgrade") return } if (!semver.valid(runningVersion) || !semver.valid(bundledVersion)) { log.info("Exotic versions of supervisor found, proceeding to upgrade") } else if (semver.gte(runningVersion, bundledVersion)) { log.info("Running supervisor version is compatible, skipping the upgrade") return } log.info(`Upgrading supervisor ${runningVersion} → ${bundledVersion}`) await supervisor.install() } /** * Installing supervisor as system service **/ async install(): Promise { return await new Promise((resolve) => { sudoExec(`"${supervisorBin()}" -install -uid ${uid()}`) const waitUntilConnected = (): void => { this.connect() .then(() => resolve()) .catch(() => setTimeout(waitUntilConnected, 500)) } setTimeout(waitUntilConnected, 500) })Mysterium Network } disconnect(): void { if (this.conn) { this.conn.destroy() } } } export const supervisor = new Supervisor() ``` -------------------------------- ### Clone and initialize the demo project Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Commands to set up the local development environment for the tutorial. ```bash git clone git@github.com:mysteriumnetwork/tequilapi-webapp-tutorial.git yarn install yarn dev - to see how it looks like ``` -------------------------------- ### Build the project Source: https://docs.mysterium.network/for-developers/node-development Commands to compile the project using Mage or standard Go tools. ```bash cd node mage build ``` ```bash go build -o myst cmd/mysterium_node/mysterium_node.go ``` -------------------------------- ### Initialize HTTP Server Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Sets up a basic Node.js HTTP server to listen for incoming requests. ```typescript import { createServer, IncomingMessage, request, ServerResponse } from 'http'; createServer(onRequest).listen(5000); function onRequest(req: IncomingMessage, res: ServerResponse) { } ``` -------------------------------- ### Initialize Proxy Server Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Sets up a basic HTTP server using Node.js to listen for incoming requests. ```typescript import { createServer, IncomingMessage, request, ServerResponse } from 'http'; createServer(onRequest).listen(5000); function onRequest(req: IncomingMessage, res: ServerResponse) { } ``` -------------------------------- ### Initialize Electron BrowserWindow Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Configuration for the main application window, including webPreferences settings for Tequila API access. ```typescript const win = new BrowserWindow({ width: 650, height: 600, title: "Mysterium dVPN Tutorial", resizable: false, show: true, frame: true, backgroundColor: "#282c34", webPreferences: { webSecurity:false, // We need to disable CORS check to be able to connect to Tequila API nodeIntegration: true, // We can use Node.js in renderer process experimentalFeatures: true, nativeWindowOpen: true, contextIsolation: false, // We can use Electron context in renderer process, so no need for contextBridge }, }); if (isDev) { win.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`); } else { win.loadURL( url.format({ pathname: path.join(__dirname, "index.html"), protocol: "file:", slashes: true, }) ); } ``` -------------------------------- ### Configure Package Scripts Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Add these scripts to package.json to simplify building and running the proxy. ```json "scripts": { "build": "tsc", "start": "node ./index.js", "dev": "ts-node ./index.ts" } ``` -------------------------------- ### Initialize Tequilapi and State Management Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Configures the Tequilapi client factory and defines the global application state interface. ```typescript let tequilapi = new TequilapiClientFactory( `http://127.0.0.1:44050`, 100000 // 100sec timeout. Some requests could take a while ).build(); type StateInterface = { currentIdentity: string, connectionInfo: ConnectionInfo | undefined, identities: IdentityRef[], loaded: boolean, connectionSucces: CallableFunction, }; export var State: StateInterface = { currentIdentity: "", connectionInfo: undefined, identities: [], loaded: false, connectionSucces: () => {}, }; function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } export async function getIdentity( id: string, password: string, create?: boolean ): Promise { let consumerId = ""; if (create) { let res = await tequilapi.identityCreate(password); await tequilapi.identityUnlock(res.id, password); await tequilapi.identityRegister(res.id); consumerId = res.id; State.identities.push(res); } else { consumerId = id; await tequilapi.identityUnlock(consumerId, password); } log.info("Got unclocked consumer identity"); State.currentIdentity = consumerId; } export async function Startup() { try { await supervisorIPC.connect(); } catch (err) { log.info("Failed to connect to the supervisor, installing", err); await supervisorIPC.install(); await supervisorIPC.connect(); } await nodeIPC.killGhosts(); await nodeIPC.start(); while (true) { try { await tequilapi.connectionStatus(); break; } catch { await sleep(500); } } log.info("Node & Supervisor started"); } export async function Register(res: IdentityResponse) { await getIdentity(res.id, res.password, res.create); } export async function Disconnect(): Promise { let status = await tequilapi.connectionStatus(); if (status.status == "Connected") { await tequilapi.connectionCancel(); State.connectionInfo = undefined; log.info(`Disconnected from ${status.proposal?.providerId}`); } } export async function Preload(): Promise { State.identities = await tequilapi.identityList(); let status = await tequilapi.connectionStatus(); State.loaded = true; if (status.status == "Connected") { State.connectionInfo = status; State.currentIdentity = status.consumerId || ""; return true; } else { return false; } } export async function Back(): Promise { await Disconnect(); State.currentIdentity = ""; State.connectionInfo = undefined; } export async function ConnectRandom(consumerId: string): Promise { try { let status = await tequilapi.connectionStatus(); if (status.status == "Connected") { await tequilapi.connectionCancel(); } let proposal = { // There are different types of NAT networks for home internet. Some of them cant work with each other. natCompatibility: "auto", // openvpn and noop used only for testing serviceType: "wireguard", }; let proposals = await tequilapi.findProposals(proposal); let num = Math.round(Math.random() * proposals.length); let prop = proposals[num]; let request = { consumerId: consumerId, providerId: prop.providerId, serviceType: prop.serviceType, }; let res = await tequilapi.connectionCreate(request); if (res.status == "Connected") { State.connectionInfo = res; State.connectionSucces(); log.info(`Connected to ${res.proposal?.providerId}`); } } catch { State.connectionSucces(); } } ``` -------------------------------- ### Initialize Node State and Helper Functions Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Sets up React state for node data and provides helper functions for updating or removing nodes from the map. ```typescript const [nodes, setNodes] = useState>(new Map()) const [nodesKeys, setNodesKeys] = useState([]) const updateNode = (k: string, v: nodeData) => { setNodes(new Map(nodes.set(k,v))); } const removeNode = (k: string) => { nodes.delete(k) setNodes(new Map(nodes)); setNodesKeys((oldArray) => oldArray.filter(x => x != k)) } ``` -------------------------------- ### Configure Shell Environment Variables Source: https://docs.mysterium.network/for-developers/environment-setup Export GOPATH and update PATH to include Go binaries for Ubuntu and macOS. ```bash export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin export PATH=$PATH:/usr/local/go/bin ``` ```bash export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin ``` -------------------------------- ### Configure package.json for electron-webpack Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Scripts and electronWebpack configuration for building and running the application. ```json "scripts": { "postinstall": "shx rm -rf static/bin && shx mkdir -p static/bin && shx cp -r node_modules/@mysteriumnetwork/node/bin/* static/bin", "dev": "electron-webpack dev", "clean": "shx rm -rf dist", "build": "electron-webpack", "bundle": "yarn build && electron-builder" }, "electronWebpack": { "main": { "sourceDirectory": "src/electron" }, "renderer": { "sourceDirectory": "src/renderer", "template": "src/renderer/index.html", "webpackConfig": "webpack.renderer.additions.js" } } ``` -------------------------------- ### Run tests and checks Source: https://docs.mysterium.network/for-developers/node-development Commands to execute unit tests, e2e tests, and linting checks. ```bash mage test ``` ```bash mage teste2ebasic ``` ```bash mage teste2enat ``` ```bash mage checks ``` -------------------------------- ### Clone the node repository Source: https://docs.mysterium.network/for-developers/node-development Initial step to obtain the source code after forking the repository. ```bash git clone https://github.com/YOUR_GITHUB_USERNAME/node.git ``` -------------------------------- ### Code utility commands Source: https://docs.mysterium.network/developers Command to format the codebase. ```bash yarn fix ``` -------------------------------- ### Package Scripts Configuration Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Adds a development script to package.json to run both proxy and frontend concurrently. ```json "dev": "yarn --cwd ../proxy dev & yarn start" ``` -------------------------------- ### Generate documentation Source: https://docs.mysterium.network/for-developers/node-development Command to update documentation and generated files after API changes. ```bash mage generate ``` -------------------------------- ### Project directory structure Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial The recommended file organization for the Electron and TypeScript project. ```text |static // static files |---bin // place where bin from @mysteriumnetwork/node will be copied |src // our code |---electron // electron part with main process |---|---node // IPC Host code |---renderer // app part |---|---api // api methods to work with Tequila API |---|---components // UI components |---shared // shared code, libraries, utils |---|---ipc // IPC clients ``` -------------------------------- ### Recreate Node API Client Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Re-initializes the Tequilapi client if it was lost during persistence or session refresh. ```typescript  async function checkNodeAPI(nodeApi: TequilapiClient | null, ip: string, port: string, token: string): Promise {    // If it was retrived from localStorage and has no client we re-create it    if (!(nodeApi instanceof HttpTequilapiClient)) {      nodeApi = new TequilapiClientFactory('http://localhost:5000/proxy/' + ip + '/' + port + '/tequilapi').build()      nodeApi.authSetToken(token)    }    return nodeApi  } ``` ```javascript  // If no password we assume the token is already in the api and saved  if (nodes.has(address)) {    token = nodes.get(address)!.token    nodeApi = nodes.get(address)!.api  } ``` -------------------------------- ### Execute scraping tasks Source: https://docs.mysterium.network/developers Commands to initiate scraping operations using single or multiple nodes. ```bash yarn booking ``` ```bash yarn booking-fleet ``` ```bash yarn google-fleet ``` ```bash yarn hotels-fleet ``` -------------------------------- ### Manage node fleet connections and info Source: https://docs.mysterium.network/developers Commands to connect nodes to specific countries and check their MYST balances. ```bash yarn fleet-connect ``` ```bash yarn fleet-info ``` -------------------------------- ### Fleet management commands Source: https://docs.mysterium.network/developers Utility commands for controlling the lifecycle and configuration of the node fleet. ```bash yarn fleet-start ``` ```bash yarn fleet-stop ``` ```bash yarn fleet-restart ``` ```bash yarn fleet-info ``` ```bash yarn fleet-connect ``` ```bash yarn fleet-disconnect ``` -------------------------------- ### Implement Input Fields and Add Button Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Provides the UI components for node configuration inputs and the trigger button for adding a node. ```jsx setIpField(event.target.value)}> setPortField(event.target.value)} style={{marginTop: '0.5em'}}> setPasswordField(event.target.value)} style={{marginTop: '0.5em'}}> addOrUpdateNode(ipField, portField, passwordField)} style={{marginTop: '2em'}}>Add Node ``` -------------------------------- ### Retrieve Node Token and API Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Checks for an existing node token in the local storage and initializes the node API if available. ```typescript // If no password we assume the token is already in the api and saved if (nodes.has(address)) { token = nodes.get(address)!.token nodeApi = await checkNodeAPI(nodes.get(address)!.api, ip, port, token) } ``` -------------------------------- ### Access Node Volumes Source: https://docs.mysterium.network/for-developers/web-scraping-workshop Command to change ownership of node volumes for access. ```bash chown -R myst-nodes-* ``` -------------------------------- ### Initialize Node State Management Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Sets up React state hooks for storing node data and helper functions for updating or removing nodes from the local map. ```typescript const [nodes, setNodes] = useState>(new Map()) const [nodesKeys, setNodesKeys] = useState([]) const updateNode = (k: string, v: nodeData) => { setNodes(new Map(nodes.set(k,v))); } const removeNode = (k: string) => { nodes.delete(k) setNodes(new Map(nodes)); setNodesKeys((oldArray) => oldArray.filter(x => x != k)) } ``` -------------------------------- ### Render Node Input Fields Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial HTML structure for input fields and the trigger button for adding nodes. ```html setIpField(event.target.value)}> setPortField(event.target.value)} style={{marginTop: '0.5em'}}> setPasswordField(event.target.value)} style={{marginTop: '0.5em'}}> addOrUpdateNode(ipField, portField, passwordField)} style={{marginTop: '2em'}}>Add Node ``` -------------------------------- ### Interact with TequilaAPI Source: https://docs.mysterium.network/for-developers/node-development Commands to access the CLI or perform health checks via the REST API. ```bash mage cli ``` ```bash build/myst/myst cli ``` ```bash curl http://localhost:4050/healthcheck { "uptime": "47m55.616006453s", "process": 5490, "version": "0.0.0-dev", "buildInfo": { "commit": "99d37edc952b736e3fad069f56e7d968276634a8", "branch": "master", "buildNumber": "4755" } } ``` -------------------------------- ### Render Node List in React Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Maps over node keys to generate table rows with node statistics and control buttons. ```javascript const nodesList = nodesKeys.map((key, i) => { if (nodes.has(key)) { let sumTokens = nodes.get(key)!.stats.stats.sumTokens/(10**18) let apiLoaded = nodes.get(key)!.api instanceof HttpTequilapiClient return ( {key} {nodes.get(key)!.services.length > 0 ? {nodes.get(key)!.services[0].status} : Stopped} {nodes.get(key)!.health.uptime} {nodes.get(key)!.health.version} {nodes.get(key)!.stats.stats.count} {sumTokens.toFixed(2)} {apiLoaded ? ( {nodes.get(key)!.services.length == 0 && startNode(key)}> } {nodes.get(key)!.services.length > 0 && stopNode(key)}> } ) : (No API loaded)} ) } else return }) ``` -------------------------------- ### Node Table Structure Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial HTML structure for displaying the list of managed nodes. ```html Address Service status Uptime Version Sessions Total Earnings (MYSTT) {nodesList.length > 0 && } {nodesList} ``` -------------------------------- ### Define Node Input State Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Initializes state variables for capturing IP, port, and password input fields. ```typescript const [ipField, setIpField] = useState('') const [portField, setPortField] = useState('') const [passwordField, setPasswordField] = useState('') ``` -------------------------------- ### Add Android SDK Dependency Source: https://docs.mysterium.network/for-developers/mobile-sdk Configure the Maven repository and add the mobile-node dependency to your build.gradle file. ```gradle repositories { maven { url "https://maven.mysterium.network/releases" } } dependencies { implementation 'network.mysterium:mobile-node:1.4.7' } ``` -------------------------------- ### Add Android SDK Dependency Source: https://docs.mysterium.network/apis-sdks/mobile-sdk Configure the Maven repository and add the mobile-node dependency to your build.gradle file. ```gradle repositories { maven { url "https://maven.mysterium.network/releases" } } dependencies { implementation 'network.mysterium:mobile-node:1.4.7' } ``` -------------------------------- ### Re-initialize Node API Client Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Checks if the API client exists and recreates it if missing after page reload. ```javascript async function checkNodeAPI(nodeApi: TequilapiClient | null, ip: string, port: string, token: string): Promise { // If it was retrived from localStorage and has no client we re-create it if (!(nodeApi instanceof HttpTequilapiClient)) { nodeApi = new TequilapiClientFactory('http://localhost:5000/proxy/' + ip + '/' + port + '/tequilapi').build() nodeApi.authSetToken(token) } return nodeApi } ``` -------------------------------- ### Handle CORS Preflight Requests Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Responds to OPTIONS requests to satisfy CORS preflight requirements. ```typescript if (req.method === 'OPTIONS') { // Pre-flight request. Reply successfully: res.writeHead(200, cors_headers); res.end(); return; } ``` -------------------------------- ### Implement Node Addition and Data Retrieval Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Functions to handle node authentication, data fetching, and state updates. ```typescript function addOrUpdateNode(ip: string, port: string, password?: string) { // Get data and save the result let address = ip + ':' + port getNodeData(ip, port, password).then(result => { if (password != undefined) { // Reset fields setIpField('') setPortField('') setPasswordField('') } updateNode(address, result) let index = nodesKeys.indexOf(address) if (index == -1) { setNodesKeys(oldArray => [...oldArray, address]) } }).catch((e: any) => { // Need to request token again but we don't store the password, so we need to re-add node if (e instanceof TequilapiError && e.isUnauthorizedError) { alert("Request unauthorized, you need to add your node again.") removeNode(address) } else { if (e.originalResponseData) alert(e.originalResponseData) else alert(e.message) } }) } async function getNodeData(ip: string, port: string, password?: string) : Promise { let address = ip + ':' + port let nodeApi: TequilapiClient | null = null let token: string | null = null if (password == undefined) { // If no password we assume the token is already in the api and saved if (nodes.has(address)) { token = nodes.get(address)!.token nodeApi = nodes.get(address)!.api } } else { // Create node client nodeApi = new TequilapiClientFactory('http://localhost:5000/proxy/' + ip + '/' + port + '/tequilapi').build() // Retrieve token let response = await nodeApi.authAuthenticate({ username: "myst", password: password }, true) token = response.token } if (nodeApi != null && token != null) { let health = await nodeApi.healthCheck() let stats = await nodeApi.sessionStatsAggregated() let idenities = await nodeApi.identityList() let services = await nodeApi.serviceList() return { api: nodeApi, health: health, stats: stats, idenities: idenities, services: services, token: token } } throw Error("Something went wrong when calling the node API") } ``` -------------------------------- ### Implement React App Component Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Uses React hooks to manage application flow and UI updates based on the centralized state. ```typescript import { State, Startup, Preload, ConnectRandom, Register, Disconnect, Back, } from "./api"; function App() { // using state just to refresh App cpmponent const [step, setStep] = useState(""); // running once on App init useEffect(() => { async function run() { await Startup(); let res = await Preload(); if (res) { setStep("connectionInfo"); } else { setStep("select"); } State.connectionSucces = () => { setStep("connectionInfo"); }; } run(); }, []); return ( {!State.currentIdentity && !State.loaded && ( } /> )} {!State.currentIdentity && State.loaded && ( identities={State.identities} onSubmit={async (res: IdentityResponse) => { await Register(res); setStep("connectionMake"); }} /> )} {State.currentIdentity && ( connect={async () => { setStep("Connection"); ConnectRandom(State.currentIdentity); }} disconnect={async () => { await Disconnect(); setStep("connectionMake"); }} back={async () => { await Back(); setStep("select"); }} connection={State.connectionInfo} connecting={step == "Connection"} /> )} ); } export default App; ``` -------------------------------- ### Persist Node Data with Hooks Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Uses useEffect to synchronize node state with browser localStorage. ```javascript // Retrieve data from local storage useEffect(() => { if(localStorage.getItem('nodes') && localStorage.getItem('nodesKeys')) { setNodes(new Map(JSON.parse(localStorage.getItem('nodes')!))) setNodesKeys(JSON.parse(localStorage.getItem('nodesKeys')!) as string[]) } }, []); // Save node data to local storage useEffect(() => { // If none have API initialized call refreshAll if (nodesKeys.map(key => { if (nodes.has(key) && !(nodes.get(key)!.api instanceof HttpTequilapiClient)) return false else return true }).every(x => x == false)) refreshAll() localStorage.setItem('nodes', JSON.stringify(Array.from(nodes.entries()))); }, [nodes, nodesKeys]); useEffect(() => { localStorage.setItem('nodesKeys', JSON.stringify(nodesKeys)); }, [nodesKeys]); ``` -------------------------------- ### Add or Update Node Logic Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Handles the logic for adding or updating nodes, including authentication and error handling for unauthorized requests. ```typescript function addOrUpdateNode(ip: string, port: string, password?: string) { // Get data and save the result let address = ip + ':' + port getNodeData(ip, port, password).then(result => { if (password != undefined) { // Reset fields setIpField('') setPortField('') setPasswordField('') } updateNode(address, result) let index = nodesKeys.indexOf(address) if (index == -1) { setNodesKeys(oldArray => [...oldArray, address]) } }).catch((e: any) => { // Need to request token again but we don't store the password, so we need to re-add node if (e instanceof TequilapiError && e.isUnauthorizedError) { alert("Request unauthorized, you need to add your node again.") removeNode(address) } else { if (e.originalResponseData) alert(e.originalResponseData) else alert(e.message) } }) } async function getNodeData(ip: string, port: string, password?: string) : Promise { let address = ip + ':' + port let nodeApi: TequilapiClient | null = null let token: string | null = null if (password == undefined) { // If no password we assume the token is already in the api and saved if (nodes.has(address)) { token = nodes.get(address)!.token nodeApi = nodes.get(address)!.api } } else { // Create node client nodeApi = new TequilapiClientFactory('http://localhost:5000/proxy/' + ip + '/' + port + '/tequilapi').build() // Retrieve token let response = await nodeApi.authAuthenticate({ username: "myst", password: password }, true) token = response.token } if (nodeApi != null && token != null) { let health = await nodeApi.healthCheck() let stats = await nodeApi.sessionStatsAggregated() let idenities = await nodeApi.identityList() let services = await nodeApi.serviceList() return { api: nodeApi, health: health, stats: stats, idenities: idenities, services: services, token: token } } throw Error("Something went wrong when calling the node API") } ``` -------------------------------- ### Implement MysteriumNode IPC management Source: https://docs.mysterium.network/for-developers/dvpn-desktop-client-tutorial Defines the MysteriumNode class to handle IPC communication for node lifecycle management, including binary path resolution and process control. ```typescript const mystBin = (): string => { let mystBinaryName = "bin/myst" if (platform() === "win32") { mystBinaryName += ".exe" } return staticAssetPath(mystBinaryName) } export class MysteriumNode { port?: number proc?: ChildProcess registerIPC(getMainWindow: () => BrowserWindow | null): void { ipcMain.handle(MainIpcListenChannels.StartNode, () => { return this.start() }) ipcMain.handle(MainIpcListenChannels.StopNode, () => { return this.stop() }) ipcMain.handle(MainIpcListenChannels.KillGhosts, async () => { if (!isDev) { await Promise.all([this.killGhost(4050), this.killGhost(44050)]) } }) } // Myst process is not started from supervisor as supervisor runs as root user // which complicates starting myst process as non root user. start(port = TEQUILAPI_PORT): Promise { this.port = port const mystProcess = spawnProcess( mystBin(), [ "--ui.enable=false", "--usermode", "--consumer", `--tequilapi.port=${port}`, "--discovery.type=api", "daemon", ], { stdio: "ignore", // Needed for unref to work correctly. }, ) mystProcess.stdout?.on("data", (d) => { log.info(d) }) this.proc = mystProcess mystProcess.on("close", (code) => { log.info(`myst process exited with code ${code}`) }) return Promise.resolve() } // Used to kill old processes before start a new one async killGhost(port: number): Promise { const api = new TequilapiClientFactory(`http://127.0.0.1:${port}`, 3_000).build() let hc: NodeHealthcheck | undefined try { hc = await api.healthCheck(100) } catch (err) { log.info("No ghosts found on port", port) } if (!hc?.process) { return } log.info("Found a ghost node on port", port, "PID", hc.process) log.info("Attempting to shutdown gracefully") try { await api.stop() return } catch (err) { log.error("Could not stop node on port " + port +": "+err) } log.info("Attempting to kill process", hc.process) try { process.kill(hc.process) } catch (err) { log.error("Could not kill process PID " + hc.process +": "+err) } } async stop(): Promise { log.info("Stopping myst") if (this.port) { log.info("Shutting down node gracefully on port", this.port) const api = new TequilapiClientFactory(`http://127.0.0.1:${this.port}`, 3_000).build() try { await api.stop() return } catch (err) { log.error("Could not shutdown Mysterium node gracefully: "+err) } } if (this.proc) { log.info("Killing node process", this.proc.pid) try { this.proc.kill() } catch (err) { log.error("Could not kill node process: "+err) } } } } export const mysteriumNode = new MysteriumNode() ``` -------------------------------- ### Forward Request and Set CORS Headers Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Configures CORS headers and forwards the request to the target node, piping the response back to the client. ```typescript let cors_headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'OPTIONS, POST, GET, PUT, DELETE', 'Content-Type': '*/*', 'Access-Control-Allow-Headers': '*' }; var options = { hostname: target_ip, port: target_port, path: target_path, method: req.method, headers: req.headers }; var proxy = request(options, function (target_res: IncomingMessage) { res.writeHead(target_res.statusCode!, cors_headers); target_res.pipe(res, { end: true }); }); req.pipe(proxy, { end: true }); ``` -------------------------------- ### Parse Target URL Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Extracts target IP, port, and path from the incoming request URL for proxying. ```typescript let url = req.url?.split('/') //validity checks if (url!.length < 6) { res.write('url path too short'); res.end(); return } let target_ip = url![2] let target_port = url![3] let target_path = '/' + url!.slice(4).join('/') ``` -------------------------------- ### Define Node Data Interface Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Defines the structure for node data objects stored in the application state. ```typescript interface nodeData { token: string, api: TequilapiClient, health: NodeHealthcheck, stats: SessionStatsAggregatedResponse, idenities: IdentityRef[], services: ServiceInfo[] } ``` -------------------------------- ### Update Node API Logic Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Refactors existing node retrieval to use the checkNodeAPI function. ```javascript // If no password we assume the token is already in the api and saved if (nodes.has(address)) { token = nodes.get(address)!.token nodeApi = nodes.get(address)!.api } ``` ```javascript // If no password we assume the token is already in the api and saved if (nodes.has(address)) { token = nodes.get(address)!.token nodeApi = await checkNodeAPI(nodes.get(address)!.api, ip, port, token) } ``` -------------------------------- ### Refresh Node Data Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Iterates through node keys to update individual node information. ```javascript function refreshAll() { for (let nodeKey of nodesKeys) { let addressSplit = nodeKey.split(':') addOrUpdateNode(addressSplit[0], addressSplit[1]) } } ``` -------------------------------- ### Refresh Node Data Source: https://docs.mysterium.network/for-developers/node-management-with-tequilapi-tutorial Iterates through node keys to update the status of all nodes. ```javascript function refreshAll() { for (let nodeKey of nodesKeys) { let addressSplit = nodeKey.split(':') addOrUpdateNode(addressSplit[0], addressSplit[1]) } } ``` -------------------------------- ### Handle Proxy Request Errors Source: https://docs.mysterium.network/apis-sdks/tequilapitutorial Catches and logs errors during the proxy request process, returning a 500 status code to the client. ```typescript proxy.on('error', function(err) { console.log("Request failed") res.writeHead(500, cors_headers); res.write("Request to node failed") res.end() }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.