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 →