pradt2 / always-online-stun

A list of publicly available STUN servers, refreshed every hour.
MIT License
573 stars 49 forks source link

Add client-side/JS fetch() use case example to readme maybe #3

Closed kyr0 closed 1 year ago

kyr0 commented 2 years ago

Hey :)

as up-to-date STUN servers lists are often needed to be known in-browser, and you might not want to hard-code them, you can actually use this code in-browser to fetch them fresh from GitHub, and Microsoft will pay for the traffic + takes care for DDoS protection:

const stunServerList = (await (await fetch(
      'https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_ipv4s.txt'
)).text()).split('\n')
Bildschirmfoto 2022-03-04 um 23 49 26
pradt2 commented 2 years ago

Hey, yep we can try to add a dedicated readme with small code snippets for different languages.

Idk about doing a fetch from the browser, wouldn't CORS be an issue?

If not then maybe we can even start thinking about shipping small code libs that would make fetching the latest hosts/ips list a one-liner.

tarwich commented 2 years ago

Can we play code golf? I used to do this:

const all = fetch(
  "https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts.txt"
)
  .then((res) => res.text())
  .then((text) => text.trim().split("\n"));
const mapped = all.then((ipv4s) => ipv4s.map((ip) => `stun:${ip}`));

However, I found a couple problems:

Now I have this, but I feel it's way overboard, for an example:

/**
 * All these urls are known to have CORS header Access-Control-Allow-Origin: *
 */
const KnownUrls = {
  geoIpCache:
    "https://raw.githubusercontent.com/pradt2/always-online-stun/master/geoip_cache.txt",
  geolocation: "https://geolocation-db.com/json/",
};

/**
 * Get the latitude/longitude for the browser
 *
 * @return {Promise<{latitude: number, longitude: number}>}
 */
function getLocation() {
  let result;

  result = result || fetch(KnownUrls.geolocation).then((res) => res.json());

  return result;
}

/**
 * @return {Promise<Record<string, [latitude: number, longitude: number]>>}
 */
function getGeoIpCache() {
  let result;

  result = result || fetch(KnownUrls.geoIpCache).then((res) => res.json());

  return result;
}

/**
 * @param {[number, number]} pointA
 * @param {[number, number]} pointB
 */
function pointDistance(pointA, pointB) {
  return Math.sqrt(
    Math.pow(pointA[0] - pointB[0], 2) + Math.pow(pointA[1] - pointB[1], 2)
  );
}

/**
 * Return a list of STUN servers sorted by distance to <latitude, longitude>
 *
 * @param {number} latitude
 * @param {number} longitude
 */
async function getStunServersByDistance(latitude, longitude) {
  const geoIpCache = await getGeoIpCache();

  const geoIpCacheArray = Object.entries(geoIpCache).map(
    ([ip, [latitude, longitude]]) => ({
      ip,
      latitude,
      longitude,
    })
  );

  // Sort the geoIpCacheArray by distance to myLocation
  const geoIpCacheArraySorted = geoIpCacheArray.sort((a, b) => {
    const distanceA = pointDistance(
      [latitude, longitude],
      [a.latitude, a.longitude]
    );
    const distanceB = pointDistance(
      [latitude, longitude],
      [b.latitude, b.longitude]
    );

    return distanceA - distanceB;
  });

  return geoIpCacheArraySorted;
}

/**
 * @param {string} ip
 */
function isIpv4(ip) {
  return ip.split(".").length === 4;
}

async function getIpv4sByDistance(latitude, longitude) {
  return getStunServersByDistance(latitude, longitude).then((stunServers) =>
    stunServers.filter((stunServer) => isIpv4(stunServer.ip))
  );
}

async function getIpv6sByDistance(latitude, longitude) {
  return getStunServersByDistance(latitude, longitude).then((stunServers) =>
    stunServers.filter((stunServer) => !isIpv4(stunServer.ip))
  );
}

async function getTopClosestIpv4s(n = 5) {
  const location = await getLocation();

  return getIpv4sByDistance(location.latitude, location.longitude).then(
    (ipv4s) => ipv4s.slice(0, n)
  );
}

I think we could get this optimized, cached, sorted, and still under 20 LOC with input from others. Let me know what y'all think.

pradt2 commented 2 years ago

Hi @tarwich, seems like you're trying to find the stun server closest to the caller. We already have a discussion about this in #4. I'll post my code there