Skip to content

Random Port Generator — JavaScript source

Generate one or many random TCP/UDP port numbers across registered, ephemeral, or the full range - optionally unique. Runs entirely in your browser with crypto-grade randomness.

This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.

/**
 * random-port-generator - random/dynamic port picker over well-known ranges.
 *
 * Language:   JavaScript (ES2020+, runs unmodified in Node 16+ and modern browsers)
 * Source:     CosmoDev polyglot showcase port of the Random Port Generator tool,
 *             ported from src/lib/random-port.ts (the canonical TypeScript
 *             implementation) and kept in lock-step with cli/random-port-generator
 *             (the Go twin). Pure + deterministic via an injectable RNG; invalid
 *             custom ranges throw (mirroring the TS throw).
 * License:    display source - part of CosmoDev's polyglot tool pages.
 *
 * Design goals:
 *   - Pure + deterministic; never throws on valid input.
 *   - Functionally equivalent to the TS reference and the Go twin: same named
 *     ranges, same validation, same Fisher-Yates partial-shuffle unique pick.
 *   - Self-contained: stdlib only (Node's `crypto` for the default RNG; no npm
 *     dependencies).
 *
 * The injectable `rng` is the key that makes a *random* tool deterministic and
 * therefore testable: every showcase assertion under `require.main === module`
 * uses a fixed closure and reproduces a vector from src/lib/random-port.test.ts
 * exactly.
 */

'use strict';

const { randomBytes } = require('crypto');

/**
 * Range preset. `null`/`undefined` -> 'registered' (the default).
 * @typedef {('any' | 'registered' | 'ephemeral' | 'custom' | null | undefined)} PortRange
 */

/**
 * Options shape. Keys are all optional.
 * @typedef {Object} PortOptions
 * @property {PortRange} [range]   Range preset. Defaults to 'registered'.
 * @property {number} [min]        Custom-range lower bound (default 1).
 * @property {number} [max]        Custom-range upper bound (default 65535).
 * @property {number} [count]      How many ports randomPorts returns. Defaults to 1.
 * @property {boolean} [unique]    Dedupe via Fisher-Yates partial shuffle.
 * @property {() => number} [rng]  Injectable random in [0, 1). Default = CSPRNG.
 */

// Named ranges mirror RANGES in random-port.ts (and the Go bounds() switch).
const RANGES = {
  any: [1, 65535],
  registered: [1024, 49151],
  ephemeral: [49152, 65535],
};

/**
 * Default RNG: a CSPRNG float in [0, 1) - mirrors the TS `crypto.getRandomValues`
 * default (read a 32-bit unsigned int, divide by 2**32).
 */
function defaultRng() {
  return randomBytes(4).readUInt32BE(0) / 2 ** 32;
}

/**
 * Resolve an options object to its `{ lo, hi }` bounds.
 *
 * @param {PortOptions} opts
 * @returns {{ lo: number, hi: number }}
 */
function resolveRange(opts) {
  if (opts.range === 'custom') {
    return { lo: opts.min ?? 1, hi: opts.max ?? 65535 };
  }
  const [lo, hi] = RANGES[opts.range ?? 'registered'];
  return { lo, hi };
}

/**
 * Validate a resolved range. Mirrors the TS `assertRange`.
 */
function assertRange(lo, hi) {
  if (!Number.isInteger(lo) || !Number.isInteger(hi) || lo < 0 || lo > 65535 || hi > 65535 || lo > hi) {
    throw new Error(`Invalid port range ${lo}-${hi}`);
  }
}

/**
 * Return a single random port within the resolved range.
 *
 * @param {PortOptions} [options={}]
 * @returns {number}
 */
function randomPort(options = {}) {
  const opts = { range: 'registered', count: 1, unique: false, ...options };
  const rng = opts.rng ?? defaultRng;
  const { lo, hi } = resolveRange(opts);
  assertRange(lo, hi);
  return lo + Math.floor(rng() * (hi - lo + 1));
}

/**
 * Return `count` ports. When `unique`, a Fisher-Yates partial shuffle over the
 * range yields distinct values (capped at range capacity).
 *
 * @param {PortOptions} [options={}]
 * @returns {number[]}
 */
function randomPorts(options = {}) {
  const opts = { range: 'registered', count: 1, unique: false, ...options };
  const count = Math.max(1, opts.count ?? 1);
  const { lo, hi } = resolveRange(opts);
  assertRange(lo, hi);
  if (!opts.unique) {
    return Array.from({ length: count }, () => randomPort(opts));
  }

  // Fisher-Yates partial shuffle over the range to pick `n` unique ports.
  const capacity = hi - lo + 1;
  const n = Math.min(count, capacity);
  const rng = opts.rng ?? defaultRng;
  const pool = Array.from({ length: capacity }, (_, i) => lo + i);
  for (let i = 0; i < n; i++) {
    const j = i + Math.floor(rng() * (capacity - i));
    [pool[i], pool[j]] = [pool[j], pool[i]];
  }
  return pool.slice(0, n);
}

// CommonJS export so the file is consumable from Node without a build step,
// while staying dependency-free and framework-agnostic.
module.exports = { randomPort, randomPorts, resolveRange, assertRange, RANGES };

if (require.main === module) {
  // Showcase vectors mirror the deterministic cases in src/lib/random-port.test.ts.
  const assert = require('assert');
  assert.strictEqual(randomPort({ rng: () => 0 }), 1024);
  assert.strictEqual(randomPort({ range: 'any', rng: () => 0 }), 1);
  assert.strictEqual(randomPort({ range: 'ephemeral', rng: () => 0 }), 49152);
  assert.deepStrictEqual(resolveRange({ range: 'registered' }), { lo: 1024, hi: 49151 });
  assert.strictEqual(randomPort({ range: 'custom', min: 8000, max: 8000, rng: () => 0.9 }), 8000);
  assert.deepStrictEqual(resolveRange({ range: 'custom' }), { lo: 1, hi: 65535 });
  assert.deepStrictEqual(randomPorts({ count: 5, rng: () => 0 }), [1024, 1024, 1024, 1024, 1024]);
  const uniq = randomPorts({ count: 5, unique: true, range: 'custom', min: 1, max: 3, rng: () => 0.5 });
  assert.strictEqual(uniq.length, 3);
  assert.deepStrictEqual([...uniq].sort((a, b) => a - b), [1, 2, 3]);
  assert.throws(() => randomPort({ range: 'custom', min: 100, max: 50 }));
  console.log('ok');
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →