Generación de UUID v4 en el navegador: técnicas y utilidad en JavaScript

La opción más sencilla, disponible en contextos seguros (HTTPS o localhost), es crypto.randomUUID():

const crearUUID = () => crypto.randomUUID();

console.log(crearUUID()); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"

Si se necesita compatibilidad con navegadores que no exponen randomUUID, se puede recurrir directamente a crypto.getRandomValues y ensamblar manualmente los 16 bytes, respetando la versión 4 y la variante definida por el RFC 4122:

function uuidDesdeBytes() {
  const bytes = new Uint8Array(16);
  crypto.getRandomValues(bytes);

  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;

  const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');

  return [
    hex.substring(0, 8),
    hex.substring(8, 12),
    hex.substring(12, 16),
    hex.substring(16, 20),
    hex.substring(20, 32)
  ].join('-');
}

El byte en la posición 6 se fija con el nibble alto igual a 4 para indicar la versión, mientras que el byte en la posición 8 adopta la variante 10 en sus dos bits más significativos. El resto de bytes se obtienen de forma aleatoria criptográfica. En producción se recomienda preferir siempre crypto.getRandomValues o crypto.randomUUID en lugar de Math.random, ya que este último no garanitza aleatoriedad criptográfica.

Para escenarios que requieren manipular, comparar o formatear GUIDs con distintas representaciones (N, D, B, P), resulta útil contar con una pequeña utilidda orientada a objetos:

class IdentificadorUnico {
  constructor(valor = null) {
    this._valido = true;
    this._caracteres = [];

    if (typeof valor === 'string') {
      this._parsearCadena(valor);
    } else {
      this._inicializarNulo();
    }
  }

  _parsearCadena(texto) {
    const limpio = texto.toLowerCase().replace(/[{}()\-\s]/g, '');

    if (limpio.length !== 32 || /[^0-9a-f]/.test(limpio)) {
      this._inicializarNulo();
    } else {
      this._caracteres = Array.from(limpio);
    }
  }

  _inicializarNulo() {
    this._caracteres = Array(32).fill('0');
  }

  static crear() {
    const datos = new Uint8Array(16);
    crypto.getRandomValues(datos);

    datos[6] = (datos[6] & 0x0f) | 0x40;
    datos[8] = (datos[8] & 0x3f) | 0x80;

    const hexadecimal = Array.from(datos, b => b.toString(16).padStart(2, '0')).join('');
    return new IdentificadorUnico(hexadecimal);
  }

  static nulo() {
    return new IdentificadorUnico();
  }

  esIgual(otro) {
    if (!otro || !(otro instanceof IdentificadorUnico) || !otro._valido) {
      return false;
    }
    return this.aTexto() === otro.aTexto();
  }

  aTexto(formato = 'D') {
    const formatosPermitidos = new Set(['N', 'D', 'B', 'P']);
    const tipo = String(formato).toUpperCase();
    const seleccion = formatosPermitidos.has(tipo) ? tipo : 'D';

    const base = this._caracteres.join('');

    if (seleccion === 'N') return base;

    const conGuiones = [
      base.substring(0, 8),
      base.substring(8, 12),
      base.substring(12, 16),
      base.substring(16, 20),
      base.substring(20, 32)
    ].join('-');

    switch (seleccion) {
      case 'B': return `{${conGuiones}}`;
      case 'P': return `(${conGuiones})`;
      default:  return conGuiones;
    }
  }
}

La clase normaliza la entrada elliminando llaves, paréntesis, guiones y espacios; si la cadena no cumple el patrón hexadecimal de 32 caracteres, se genera un identificador nulo compuesto por ceros. Los formatos disponibles siguen la convención de .NET: N sin separadores, D con guiones, B entre llaves y P entre paréntesis.

const id = IdentificadorUnico.crear();
console.log(id.aTexto('D'));
console.log(id.aTexto('B'));

Etiquetas: uuid guid web-crypto-api randomuuid ES6

Publicado el 7-25 23:21