Skip to content

Browser Support Test — TypeScript source

Audit your real browser against 30 modern web capabilities - WebGPU, WASM, WebAuthn, Service Workers and more. Get a weighted readiness score, copy-paste detection snippets, and a shareable link.

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

/**
 * Browser Support Test - pure logic (feature probes, weighted score, share codec).
 *
 * Language: TypeScript
 * CosmoDev polyglot showcase port of the `browser-support-test` tool.
 * Ported from src/tools/BrowserSupportTest.tsx (+ browser-support/* modules) -
 * display source, part of CosmoDev's polyglot tool pages.
 *
 * Probes a normalized {nav, win, doc} env for web capabilities, reduces the
 * verdicts to a weighted readiness score (0-100) + tier, and packs them into a
 * compact base64url share token. Detection is pure API-presence probing
 * (`'gpu' in navigator`, `canvas.getContext('webgl')`, `typeof WebAssembly`…);
 * a throwing probe degrades to 'unknown'. This is browser-only logic - the
 * Go/Rust/PHP/Python ports in this set show the analogous pattern for their
 * own runtimes. Catalog below is a representative subset (live tool ships ~30).
 */

export type CheckStatus = 'supported' | 'limited' | 'needs-https' | 'unsupported' | 'unknown';

export interface FeatureResult { id: string; status: CheckStatus; detail?: string; }
export interface BrowserEnv { nav: any; win: any; doc: any; }
export interface FeatureCheck {
  id: string; title: string; category: string; weight: number;
  explainer: string; codeSnippet: string;
  detect: (env: BrowserEnv) => CheckStatus | { status: CheckStatus; detail?: string };
}
export interface ResultReport { v: 1; s: string; score: number; browser?: string; }

// Status → contribution (0..1) × weight. needs-https scores below "limited":
// the capability exists but is unusable in the current context.
const CONTRIBUTION: Record<CheckStatus, number> = {
  supported: 1, limited: 0.5, 'needs-https': 0.3, unsupported: 0, unknown: 0,
};

/** Weighted readiness 0-100; ids absent from `checks` are ignored. */
export function computeScore(results: FeatureResult[], checks: FeatureCheck[]): number {
  const wm = new Map(checks.map((c) => [c.id, c.weight]));
  let earned = 0, possible = 0;
  for (const r of results) {
    const w = wm.get(r.id);
    if (w === undefined) continue;
    earned += CONTRIBUTION[r.status] * w;
    possible += w;
  }
  return possible === 0 ? 0 : Math.round((earned / possible) * 100);
}

// One char per check, in catalog order → fits a full audit in a URL.
const TO_CHAR: Record<CheckStatus, string> = {
  supported: 's', limited: 'l', 'needs-https': 'h', unsupported: 'x', unknown: '?',
};
const FROM_CHAR: Record<string, CheckStatus> = {
  s: 'supported', l: 'limited', h: 'needs-https', x: 'unsupported', '?': 'unknown',
};
export const packStatuses = (r: FeatureResult[]): string => r.map((x) => TO_CHAR[x.status] ?? '?').join('');
export const unpackStatuses = (s: string): CheckStatus[] => [...s].map((c) => FROM_CHAR[c] ?? 'unknown');

// base64url of UTF-8 bytes (btoa/atob + TextEncoder are global in browsers + Node ≥ 20).
function toBase64Url(str: string): string {
  const bytes = new TextEncoder().encode(str);
  const bin = Array.from(bytes, (b) => String.fromCharCode(b)).join('');
  return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
function fromBase64Url(b64url: string): string {
  const bin = atob(b64url.replace(/-/g, '+').replace(/_/g, '/'));
  return new TextDecoder().decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));
}
export function encodeResults(r: ResultReport): string { return toBase64Url(JSON.stringify(r)); }
export function decodeResults(enc: string): ResultReport | null {
  if (!enc) return null;
  try {
    const p = JSON.parse(fromBase64Url(enc));
    if (p && p.v === 1 && typeof p.s === 'string' && typeof p.score === 'number') return p;
    return null;
  } catch { return null; }
}

/** Readiness tier from a 0-100 score. */
export function tierFor(score: number): { label: string; tone: string } {
  if (score >= 90) return { label: 'Fully modern', tone: 'text-success' };
  if (score >= 75) return { label: 'Very capable', tone: 'text-success' };
  if (score >= 55) return { label: 'Capable, minor gaps', tone: 'text-accent' };
  if (score >= 35) return { label: 'Showing its age', tone: 'text-fg-muted' };
  return { label: 'Legacy browser', tone: 'text-danger' };
}

/** Parse a UA string into "Chrome 130 · macOS" (display label only). */
export function browserLabel(ua: string): string {
  if (!ua) return 'Your browser';
  let name = '', ver = '';
  const pick = (re: RegExp, n: string): boolean => {
    const m = re.exec(ua);
    if (m) { name = n; ver = m[1]; return true; }
    return false;
  };
  pick(/(?:firefox|fxios)\/(\d+)/i, 'Firefox') ||
    pick(/(?:edg|edge)\/(\d+)/i, 'Edge') ||
    pick(/(?:opr|opera)\/(\d+)/i, 'Opera') ||
    pick(/(?:chrome|crios)\/(\d+)/i, 'Chrome') ||
    pick(/version\/(\d+)[.\d]* safari/i, 'Safari') ||
    pick(/safari\/(\d+)/i, 'Safari');
  const os =
    /mac os x/i.test(ua) ? 'macOS' : /windows nt/i.test(ua) ? 'Windows'
    : /android/i.test(ua) ? 'Android' : /(iphone|ipad|cpu os)/i.test(ua) ? 'iOS'
    : /linux/i.test(ua) ? 'Linux' : '';
  const main = name ? `${name}${ver ? ' ' + ver : ''}` : 'Your browser';
  return os ? `${main} · ${os}` : main;
}

export const createEnv = (): BrowserEnv => {
  const g = globalThis as any;
  return { nav: g.navigator, win: g, doc: g.document };
};

const S = 'supported' as CheckStatus, X = 'unsupported' as CheckStatus;
function canvasCtx(env: BrowserEnv, type: string): boolean {
  try {
    const c = env.doc.createElement('canvas');
    return !!c && typeof c.getContext === 'function' && !!c.getContext(type);
  } catch { return false; }
}
export const FEATURE_CHECKS: FeatureCheck[] = [
  { id: 'canvas-2d', title: 'Canvas 2D', category: 'Graphics', weight: 2, explainer: '2D drawing surface.', codeSnippet: `!!document.createElement('canvas').getContext('2d')`, detect: (e) => (canvasCtx(e, '2d') ? S : X) },
  { id: 'webgpu', title: 'WebGPU', category: 'Graphics', weight: 3, explainer: 'Next-gen GPU + compute API.', codeSnippet: `'gpu' in navigator`, detect: (e) => ('gpu' in e.nav ? S : X) },
  { id: 'webassembly', title: 'WebAssembly', category: 'Compute', weight: 3, explainer: 'Native-speed compiled code.', codeSnippet: `typeof WebAssembly === 'object'`, detect: (e) => (typeof e.win.WebAssembly === 'object' ? S : X) },
  { id: 'indexeddb', title: 'IndexedDB', category: 'Storage', weight: 3, explainer: 'Client-side database.', codeSnippet: `'indexedDB' in window`, detect: (e) => ('indexedDB' in e.win ? S : X) },
  { id: 'service-worker', title: 'Service Worker', category: 'Connectivity', weight: 3, explainer: 'Offline background scripts.', codeSnippet: `'serviceWorker' in navigator`, detect: (e) => ('serviceWorker' in e.nav ? S : X) },
  { id: 'crypto-subtle', title: 'WebCrypto (subtle)', category: 'Security', weight: 3, explainer: 'Native hashing/signing.', codeSnippet: `!!window.crypto?.subtle`, detect: (e) => (e.win.crypto?.subtle ? S : X) },
  { id: 'secure-context', title: 'Secure Context (HTTPS)', category: 'Security', weight: 2, explainer: 'Unlocks camera, SW, crypto.', codeSnippet: `window.isSecureContext`, detect: (e) => (e.win.isSecureContext === true ? S : { status: 'needs-https' }) },
  { id: 'css-has', title: ':has() Selector', category: 'Platform', weight: 2, explainer: 'Parent selector.', codeSnippet: `CSS.supports('selector', ':has(*)')`, detect: (e) => (typeof e.win.CSS?.supports === 'function' && e.win.CSS.supports('selector', ':has(*)') ? S : X) },
];

/** Run probes; a throw degrades to 'unknown' so one failure can't abort the audit. */
function resolve(id: string, out: CheckStatus | { status: CheckStatus; detail?: string }): FeatureResult {
  return typeof out === 'string' ? { id, status: out } : { id, status: out.status, ...(out.detail ? { detail: out.detail } : {}) };
}
export function runChecks(checks: FeatureCheck[], env: BrowserEnv): FeatureResult[] {
  return checks.map((c) => {
    try { return resolve(c.id, c.detect(env)); } catch { return { id: c.id, status: 'unknown' }; }
  });
}
export const runAllChecks = (env: BrowserEnv): FeatureResult[] => runChecks(FEATURE_CHECKS, env);

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 →