Skip to main content

Check speedtest health

Inspect the latest speedtest average published for a gateway via GET /v0/gateways/{address}. The script evaluates validity codes, reward multiplier, and the age of the measurement.

Prerequisites

  • Node.js 18 or newer.
  • An API key exported as HELIUMGEEK_API_KEY.
  • A gateway address provided via GATEWAY_ADDRESS or the first CLI argument.

Run the example

  1. Download mobile-speedtest-check.js and save it locally.

  2. Export the environment variables and run:

    export HELIUMGEEK_API_KEY="your-api-key"
    export GATEWAY_ADDRESS="your-gateway-address"
    node mobile-speedtest-check.js

    Or provide the address inline:

    HELIUMGEEK_API_KEY="your-api-key" node mobile-speedtest-check.js <gateway-address>

Script

mobile-speedtest-check.js
#!/usr/bin/env node

const BASE_URL = (process.env.HELIUMGEEK_API_BASE_URL ?? 'https://api.heliumgeek.com/v0').replace(/\/$/, '');
const API_KEY = process.env.HELIUMGEEK_API_KEY;
const GATEWAY_ADDRESS =
(process.argv[2] ?? process.env.GATEWAY_ADDRESS ?? '').trim();

if (!API_KEY) {
console.error('Set HELIUMGEEK_API_KEY before running this script.');
process.exit(1);
}

if (!GATEWAY_ADDRESS) {
console.error(
'Provide the gateway address as an argument or set GATEWAY_ADDRESS.'
);
process.exit(1);
}

const MBPS_FACTOR = 8 / 1_000_000;
const byteFormatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});

async function main() {
const response = await fetch(
buildUrl(`/gateways/${encodeURIComponent(GATEWAY_ADDRESS)}`),
{
headers: {
'x-api-key': API_KEY,
},
}
);

if (!response.ok) {
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}

const gateway = await response.json();
const speedtest = gateway?.recent?.speedtestAverage;

if (!speedtest) {
console.log(
`Gateway ${GATEWAY_ADDRESS} has no recorded speedtest averages.`
);
return;
}

const uploadMbps = speedtest.upload * MBPS_FACTOR;
const downloadMbps = speedtest.download * MBPS_FACTOR;
const latencyMs = speedtest.latency ?? 0;

const issues = [];

if (speedtest.validity !== 0) {
const reason = speedtest.validityString || `code ${speedtest.validity}`;
issues.push(`Speedtest flagged: ${reason}`);
}

if (typeof speedtest.rewardMultiplier === 'number' && speedtest.rewardMultiplier < 1) {
issues.push(`Reward multiplier below 1 (${speedtest.rewardMultiplier})`);
}

const lastTimestamp = speedtest.lastSpeedtestTimestamp ?? speedtest.timestamp;
if (lastTimestamp) {
const ageHours = (Date.now() / 1000 - lastTimestamp) / 3600;
if (ageHours > 24) {
issues.push(`Last speedtest is ${ageHours.toFixed(1)} hours old (expected every 12-hour block)`);
}
} else {
issues.push('Speedtest timestamp missing');
}

console.log(`Speedtest summary for ${GATEWAY_ADDRESS}`);
console.log(` Upload: ${formatMbps(uploadMbps)} Mbps`);
console.log(` Download: ${formatMbps(downloadMbps)} Mbps`);
console.log(` Latency: ${latencyMs} ms`);
console.log(
` Validity: ${speedtest.validityString ?? speedtest.validity} | Reward multiplier: ${speedtest.rewardMultiplier ?? 'n/a'}`
);

if (issues.length === 0) {
console.log('Status: OK — the latest speedtest looks healthy.');
} else {
console.log('Status: Attention required');
for (const issue of issues) {
console.log(` - ${issue}`);
}
}
}

function buildUrl(path, query = {}) {
const url = new URL(path.replace(/^\//, ''), `${BASE_URL}/`);
Object.entries(query).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
url.searchParams.set(key, String(value));
}
});
return url.toString();
}

function formatMbps(mbps) {
return byteFormatter.format(mbps);
}

main().catch((error) => {
console.error(error.message);
process.exit(1);
});