How do I create a GUID / UUID?

Viewed 2619948

How do I create GUIDs (globally-unique identifiers) in JavaScript? The GUID / UUID should be at least 32 characters and should stay in the ASCII range to avoid trouble when passing them around.

I'm not sure what routines are available on all browsers, how "random" and seeded the built-in random number generator is, etc.

69 Answers

[Edited 2021-10-16 to reflect latest best-practices for producing RFC4122-compliant UUIDs]

Most readers here will want to use the uuid module. It is well-tested and supported.

The crypto.randomUUID() function is an emerging standard that is supported in Node.js and an increasing number of browsers.

If neither of those work for you, there is this method (based on the original answer to this question):

function uuidv4() {
  return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );
}

console.log(uuidv4());

Note: The use of any UUID generator that relies on Math.random() is strongly discouraged (including snippets featured in previous versions of this answer) for reasons best-explained here. TL;DR: Math.random()-based solutions do not provide good uniqueness guarantees.

UUIDs (Universally Unique IDentifier), also known as GUIDs (Globally Unique IDentifier), according to RFC 4122, are identifiers designed to provide certain uniqueness guarantees.

While it is possible to implement RFC-compliant UUIDs in a few lines of JavaScript code (e.g., see @broofa's answer, below) there are several common pitfalls:

  • Invalid id format (UUIDs must be of the form "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx", where x is one of [0-9, a-f] M is one of [1-5], and N is [8, 9, a, or b]
  • Use of a low-quality source of randomness (such as Math.random)

Thus, developers writing code for production environments are encouraged to use a rigorous, well-maintained implementation such as the uuid module.

Here's some code based on RFC 4122, section 4.4 (Algorithms for Creating a UUID from Truly Random or Pseudo-Random Number).

function createUUID() {
    // http://www.ietf.org/rfc/rfc4122.txt
    var s = [];
    var hexDigits = "0123456789abcdef";
    for (var i = 0; i < 36; i++) {
        s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
    }
    s[14] = "4";  // bits 12-15 of the time_hi_and_version field to 0010
    s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);  // bits 6-7 of the clock_seq_hi_and_reserved to 01
    s[8] = s[13] = s[18] = s[23] = "-";

    var uuid = s.join("");
    return uuid;
}

From sagi shkedy's technical blog:

function generateGuid() {
  var result, i, j;
  result = '';
  for(j=0; j<32; j++) {
    if( j == 8 || j == 12 || j == 16 || j == 20)
      result = result + '-';
    i = Math.floor(Math.random()*16).toString(16).toUpperCase();
    result = result + i;
  }
  return result;
}

There are other methods that involve using an ActiveX control, but stay away from these!

I thought it was worth pointing out that no GUID generator can guarantee unique keys (check the Wikipedia article). There is always a chance of collisions. A GUID simply offers a large enough universe of keys to reduce the change of collisions to almost nil.

One line solution using Blobs.

window.URL.createObjectURL(new Blob([])).substring(31);

The value at the end (31) depends on the length of the URL.


EDIT:

A more compact and universal solution, as suggested by rinogo:

URL.createObjectURL(new Blob([])).substr(-36);

Added in: v15.6.0, v14.17.0 there is a built-in crypto.randomUUID() function.

import * as crypto from "crypto";

const uuid = crypto.randomUUID();

In the browser, crypto.randomUUID() is currently supported in Chromium 92+ and Firefox 95+.

The native URL.createObjectURL is generating an UUID. You can take advantage of this.

function uuid() {
  const url = URL.createObjectURL(new Blob())
  const [id] = url.toString().split('/').reverse()
  URL.revokeObjectURL(url)
  return id
}

I couldn't find any answer that uses a single 16-octet TypedArray and a DataView, so I think the following solution for generating a version 4 UUID per the RFC will stand on its own here:

const uuid4 = () => {
    const ho = (n, p) => n.toString(16).padStart(p, 0); /// Return the hexadecimal text representation of number `n`, padded with zeroes to be of length `p`
    const data = crypto.getRandomValues(new Uint8Array(16)); /// Fill the buffer with random data
    data[6] = (data[6] & 0xf) | 0x40; /// Patch the 6th byte to reflect a version 4 UUID
    data[8] = (data[8] & 0x3f) | 0x80; /// Patch the 8th byte to reflect a variant 1 UUID (version 4 UUIDs are)
    const view = new DataView(data.buffer); /// Create a view backed by a 16-byte buffer
    return `${ho(view.getUint32(0), 8)}-${ho(view.getUint16(4), 4)}-${ho(view.getUint16(6), 4)}-${ho(view.getUint16(8), 4)}-${ho(view.getUint32(10), 8)}${ho(view.getUint16(14), 4)}`; /// Compile the canonical textual form from the array data
};

I prefer it because it only relies on functions available to the standard ECMAScript platform, where possible -- which is all but one procedure.

At the time of writing this, getRandomValues is not something implemented for the crypto object in Node.js. However, it has the equivalent randomBytes function which may be used instead.

Just another more readable variant with just two mutations.

function uuid4()
{
  function hex (s, b)
  {
    return s +
      (b >>> 4   ).toString (16) +  // high nibble
      (b & 0b1111).toString (16);   // low nibble
  }

  let r = crypto.getRandomValues (new Uint8Array (16));

  r[6] = r[6] >>> 4 | 0b01000000; // Set type 4: 0100
  r[8] = r[8] >>> 3 | 0b10000000; // Set variant: 100

  return r.slice ( 0,  4).reduce (hex, '' ) +
         r.slice ( 4,  6).reduce (hex, '-') +
         r.slice ( 6,  8).reduce (hex, '-') +
         r.slice ( 8, 10).reduce (hex, '-') +
         r.slice (10, 16).reduce (hex, '-');
}

OK, using the uuid package, and its support for version 1, 3, 4 and 5 UUIDs, do:

yarn add uuid

And then:

const uuidv1 = require('uuid/v1');
uuidv1(); // ⇨ '45745c60-7b1a-11e8-9c9c-2d42b21b1a3e'

You can also do it with fully-specified options:

const v1options = {
  node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
  clockseq: 0x1234,
  msecs: new Date('2011-11-01').getTime(),
  nsecs: 5678
};
uuidv1(v1options); // ⇨ '710b962e-041c-11e1-9234-0123456789ab'

For more information, visit the npm page here.

Inspired by broofa's answer I had my own take on it:

Here's the cryptographically stronger version using crypto.getRandomValues.

function uuidv4() {
    const a = crypto.getRandomValues(new Uint16Array(8));
    let i = 0;
    return '00-0-4-1-000'.replace(/[^-]/g, 
            s => (a[i++] + s * 0x10000 >> s).toString(16).padStart(4, '0')
    );
}

console.log(uuidv4());

and here's the faster version using Math.random using almost the same principle:

function uuidv4() {
    return '00-0-4-1-000'.replace(/[^-]/g,
            s => ((Math.random() + ~~s) * 0x10000 >> s).toString(16).padStart(4, '0')
    );
}

console.log(uuidv4());

It is important to use well-tested code that is maintained by more than one contributor instead of whipping your own stuff for this.

This is one of the places where you probably want to prefer the most stable code than the shortest possible clever version that works in X browser, but doesn't take in to account idiosyncrasies of Y which would often lead to very-hard-to-investigate bugs than manifests only randomly for some users. Personally I use uuid-js at https://github.com/aurigadl/uuid-js which is Bower enabled so I can take updates easily.

The most simple function to do this:

function createGuid(){  
   let S4 = () => Math.floor((1+Math.random())*0x10000).toString(16).substring(1); 
   let guid = `${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
   
   return guid.toLowerCase();  
}

A TypeScript version of broofa's update from 2017-06-28, based on crypto API:

function genUUID() {
    // Reference: https://stackoverflow.com/a/2117523/709884
    return ("10000000-1000-4000-8000-100000000000").replace(/[018]/g, s => {
        const c = Number.parseInt(s, 10)
        return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
    })
}

Reasons:

  • Use of + between number[] and number isn't valid
  • The conversion from string to number has to be explicit

This works for Node.js too, if you replace let buffer = new Uint8Array(); crypto.getRandomValues with let buffer = crypto.randomBytes(16)

It should beat most regular expression solutions in performance.

const hex = '0123456789ABCDEF'

let generateToken = function() {
    let buffer = new Uint8Array(16)

    crypto.getRandomValues(buffer)

    buffer[6] = 0x40 | (buffer[6] & 0xF)
    buffer[8] = 0x80 | (buffer[8] & 0xF)

    let segments = []

    for (let i = 0; i < 16; ++i) {
        segments.push(hex[(buffer[i] >> 4 & 0xF)])
        segments.push(hex[(buffer[i] >> 0 & 0xF)])

        if (i == 3 || i == 5 || i == 7 || i == 9) {
            segments.push('-')
        }
    }

    return segments.join('')
}

for (let i = 0; i < 100; ++i) {
  console.log(generateToken())
}

Performance charts (everybody loves them): jsbench

There are many correct answers here, but sadly, included code samples are quite cryptic and difficult to understand. This is how I create version 4 (random) UUIDs.

Note that following pieces of code make use of binary literals for improved readability, thus require ECMAScript 6.

Node.js version

function uuid4() {
  let array = new Uint8Array(16)
  crypto.randomFillSync(array)

  // Manipulate the 9th byte
  array[8] &= 0b00111111 // Clear the first two bits
  array[8] |= 0b10000000 // Set the first two bits to 10

  // Manipulate the 7th byte
  array[6] &= 0b00001111 // Clear the first four bits
  array[6] |= 0b01000000 // Set the first four bits to 0100

  const pattern = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  let idx = 0

  return pattern.replace(
    /XX/g,
    () => array[idx++].toString(16).padStart(2, "0"), // padStart ensures a leading zero, if needed
  )
}

Browser version

Only the second line is different.

function uuid4() {
  let array = new Uint8Array(16)
  crypto.getRandomValues(array)

  // Manipulate the 9th byte
  array[8] &= 0b00111111 // Clear the first two bits
  array[8] |= 0b10000000 // Set the first two bits to 10

  // Manipulate the 7th byte
  array[6] &= 0b00001111 // Clear the first four bits
  array[6] |= 0b01000000 // Set the first four bits to 0100

  const pattern = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
  let idx = 0

  return pattern.replace(
    /XX/g,
    () => array[idx++].toString(16).padStart(2, "0"), // padStart ensures a leading zero, if needed
  )
}

Tests

And finally, corresponding tests (Jasmine).

describe(".uuid4()", function() {
  it("returns a UUIDv4 string", function() {
    const uuidPattern = "XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX"
    const uuidPatternRx = new RegExp(uuidPattern.
      replaceAll("X", "[0-9a-f]").
      replaceAll("Y", "[89ab]"))

    for (let attempt = 0; attempt < 1000; attempt++) {
      let retval = uuid4()
      expect(retval.length).toEqual(36)
      expect(retval).toMatch(uuidPatternRx)
    }
  })
})

UUID v4 explained

A very good explanation of UUID version 4 is here: Generate a UUID compliant with RFC 4122.

Final notes

Also, there are plenty of third-party packages. However, as long as you have just basic needs, I don't recommend them. Really, there is not much to win and pretty much to lose. Authors may pursue for tiniest bits of performance, "fix" things which aren't supposed to be fixed, and when it comes to security, it is a risky idea. Similarly, they may introduce other bugs or incompatibilities. Careful updates require time.

Here you can find a very small function that generates UUIDs.

One of the final versions is:

function b(
  a                  // Placeholder
){
  var cryptoObj = window.crypto || window.msCrypto; // For Internet Explorer 11
  return a           // If the placeholder was passed, return
    ? (              // a random number from 0 to 15
      a ^            // unless b is 8,
      cryptoObj.getRandomValues(new Uint8Array(1))[0]  // in which case
      % 16           // a random number from
      >> a/4         // 8 to 11
      ).toString(16) // in hexadecimal
    : (              // or otherwise a concatenated string:
      [1e7] +        // 10000000 +
      -1e3 +         // -1000 +
      -4e3 +         // -4000 +
      -8e3 +         // -80000000 +
      -1e11          // -100000000000,
      ).replace(     // Replacing
        /[018]/g,    // zeroes, ones, and eights with
        b            // random hex digits
      )
}

Based on the work of broofa, I've added some more randomness by adding the timestamp to math.random():

function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = parseFloat('0.' + Math.random().toString().replace('0.', '') + new Date().getTime()) * 16 | 0,
            v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

The UUID currently has a proposal for addition to the standard library and can be supported here ECMAScript proposal: JavaScript standard library UUID

The proposal encompasses having UUID as the following:

// We're not yet certain as to how the API will be accessed (whether it's in the global, or a
// future built-in module), and this will be part of the investigative process as we continue
// working on the proposal.
uuid(); // "52e6953d-edbe-4953-be2e-65ed3836b2f0"

This implementation follows the same layout as the V4 random UUID generation found here: https://www.npmjs.com/package/uuid

const uuidv4 = require('uuid/v4');
uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'

I think it's noteworthy to understand how much bandwidth could be saved by this having an official implementation in the standard library. The authors of the proposal have also noted:

The 12  kB uuid module is downloaded from npm > 62,000,000 times a month (June 2019); making it available in the standard library eventually saves TBs of bandwidth globally. If we continue to address user needs, such as uuid, with the standard library, bandwidth savings add up.

I've built on everything mentioned here to produce something twice as fast, portable all environments, including node, and upgraded from Math.random() to crypto-strength randomness. You might not think UUID needs crypto strength, but what that means is even less chance of a collision, which is the entire point of a UUID.

function random() {
    const
        fourBytesOn = 0xffffffff, // 4 bytes, all 32 bits on: 4294967295
        c = typeof crypto === "object"
            ? crypto // Node.js or most browsers
            : typeof msCrypto === "object" // Stinky non-standard Internet Explorer
                ? msCrypto // eslint-disable-line no-undef
                : null; // What old or bad environment are we running in?
        return c
            ? c.randomBytes
                ? parseInt(c.randomBytes(4).toString("hex"), 16) / (fourBytesOn + 1) - Number.EPSILON // Node.js
                : c.getRandomValues(new Uint32Array(1))[0] / (fourBytesOn + 1) - Number.EPSILON // Browsers
            : Math.random();
}

function uuidV4() { // eslint-disable-line complexity
    // If possible, generate a single random value, 128 bits (16 bytes)
    // in length. In an environment where that is not possible, generate
    // and make use of four 32-bit (4-byte) random values.
    // Use crypto-grade randomness when available, else Math.random()
    const
        c = typeof crypto === "object"
            ? crypto // Node.js or most browsers
            : typeof msCrypto === "object" // Stinky non-standard Internet Explorer
                ? msCrypto // eslint-disable-line no-undef
            : null; // What old or bad environment are we running in?
    let
        byteArray = c
            ? c.randomBytes
                ? c.randomBytes(16) // Node.js
                : c.getRandomValues(new Uint8Array(16)) // Browsers
            : null,
        uuid = [ ];

    /* eslint-disable no-bitwise */
    if ( ! byteArray) { // No support for generating 16 random bytes
                        // in one shot -- this will be slower
        const
            int = [
                random() * 0xffffffff | 0,
                random() * 0xffffffff | 0,
                random() * 0xffffffff | 0,
                random() * 0xffffffff | 0
            ];
        byteArray = [ ];
        for (let i = 0; i < 256; i++) {
            byteArray[i] = int[i < 4 ? 0 : i < 8 ? 1 : i < 12 ? 2 : 3] >> i % 4 * 8 & 0xff;
        }
    }
    byteArray[6] = byteArray[6] & 0x0f | 0x40; // Always 4, per RFC, indicating the version
    byteArray[8] = byteArray[8] & 0x3f | 0x80; // Constrained to [89ab], per RFC for version 4
    for (let i = 0; i < 16; ++i) {
        uuid[i] = (byteArray[i] < 16 ? "0" : "") + byteArray[i].toString(16);
    }
    uuid =
        uuid[ 0] + uuid[ 1] + uuid[ 2] + uuid[ 3] + "-" +
        uuid[ 4] + uuid[ 5]                       + "-" +
        uuid[ 6] + uuid[ 7]                       + "-" +
        uuid[ 8] + uuid[ 9]                       + "-" +
        uuid[10] + uuid[11] + uuid[12] + uuid[13] + uuid[14] + uuid[15];
    return uuid;
    /* eslint-enable no-bitwise */
}

Here is a function that generates a static UUID from a string or a random UUID if no string supplied:

function stringToUUID (str)
{
  if (str === undefined || !str.length)
    str = "" + Math.random() * new Date().getTime() + Math.random();

  let c = 0,
      r = "";

  for (let i = 0; i < str.length; i++)
    c = (c + (str.charCodeAt(i) * (i + 1) - 1)) & 0xfffffffffffff;

  str = str.substr(str.length / 2) + c.toString(16) + str.substr(0, str.length / 2);
  for(let i = 0, p = c + str.length; i < 32; i++)
  {
    if (i == 8 || i == 12 || i == 16 || i == 20)
      r += "-";

    c = p = (str[(i ** i + p + 1) % str.length]).charCodeAt(0) + p + i;
    if (i == 12)
      c = (c % 5) + 1; //1-5
    else if (i == 16)
      c = (c % 4) + 8; //8-B
    else
      c %= 16; //0-F

    r += c.toString(16);
  }
  return r;
}

console.log("Random       :", stringToUUID());
console.log("Static [1234]:", stringToUUID("1234")); //29c2c73b-52de-4344-9cf6-e6da61cb8656
console.log("Static [test]:", stringToUUID("test")); //e39092c6-1dbb-3ce0-ad3a-2a41db98778c

jsfiddle

We can use replace and crypto.getRandomValues to get an output like this:

xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx

Enter image description here

If we are looking for an opti solution, we have to replace crypto.getRandomValues(new Uint8Array(1))[0] by an array(32).

const uuidv4 = () =>
  ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
    (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  );

console.log(uuidv4());

To get this code:

function uuidv4() {
  let bytes = window.crypto.getRandomValues(new Uint8Array(32));
  const randomBytes = () => (bytes = bytes.slice(1)) && bytes[0];

  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
      (c ^ randomBytes() & 15 >> c / 4).toString(16)
    );
}


for (var i = 0; i < 10; i++)
  console.log(uuidv4());

Collision:

We can do like google analytics and add a timestamp with : uuidv4() + "." + (+new Date()).

The following uuid implementation offers a different ES6 2020 solution using BigInt and focuses on "Use case intent for a uuid design pattern"; especially for use with indexedDb primaryKey scenarios where unifying sequencing in time and collation are valuable.

So, noting that this post has over 30 answers, here goes...

This post has:

  1. a "TL;DR" code section w/self-contained es6 class Xuid
  2. a use-case and motivations discussion section regarding the es6 class Xuid provided code.

TL;DR class Xuid solution for generic v4 uuid using a monotonic clock

The code-below is extracted from Smallscript's EdgeS web-client library that I wrote and own and is provided here, freely MIT licensed. A GitHub version will be available once EdgeS web-client toolset is released.

Usage example:

eval: console.log(Xuid.v4New)
emits: {1eb4a659-8bdc-4ce0-c002-b1d505d38ea8}

class Xuid {
  //@ edges.sm.st, ess.dev: MIT license Smallscript/David Simmons 2020
  //! Can't use `static const field = const` xbrowser (thus, const's duped)
  static get v4New() {
    const ns7Now = this.ns7Now, xnode48 = this.xnode48; let clock_seq13
    // monotonic `clock_seq` guarantee (13-bits/time-quantum)
    if(ns7Now <= this.ns7Now_prevSeq && this.ns7Now_prevSeq)
      clock_seq13 = ((this.ns7Now_prevSeq += 1n) - ns7Now) & 0b1_1111_1111_1111n
    else
      clock_seq13 = 0n, this.ns7Now_prevSeq = ns7Now
    const time60 = ((ns7Now << 4n) & 0xFFFF_FFFF_FFFF_0000n) |
                           (ns7Now & 0x0000_0000_0000_0FFFn),
              v4 = 0x1_00000000_0000_0000_0000_000000000000n |
      (time60 << 64n) | (0x00000000_0000_4000_0000_000000000000n) | // M: V4
      (0b110n << 61n) | (clock_seq13 << 48n) | // N: Variant-2 time-seq collation
      xnode48, s = v4.toString(16)//.substr(1)
    return `{${s.substr(1,8)}-${s.substr(9,4)}-${s.substr(13,4)}-${
      s.substr(17,4)}-${s.substr(21,12)}}`
  }
  static get xnode48()/*:<BigInt#48>*/{
    if(this.xnode48_) return this.xnode48_
    let clockSeqNode; if(typeof URL !== 'undefined' && URL.createObjectURL) {
      const url = URL.createObjectURL(new Blob())
      const id = (url.toString().split('/').reverse()[0]).split('-')
      URL.revokeObjectURL(url)
      clockSeqNode = BigInt('0x'+id[3]+id[4])
    }
    else {
      const a4 = this.a4; this.getRandomValues(this.a4);
      clockSeqNode = (BigInt(a4[2]) << 32n) | BigInt(a4[3])
    }
    // simulate the 48-bit node-id and 13-bit clock-seq
    // to combine with 3-bit uuid-variant
    return this.xnode48_ = clockSeqNode & 0xFFFF_FFFF_FFFFn;
  }
  static get jdNow()/*:<double#ns7>*/{
    // return 2440587.5+Date.now()/864e5 // <- Date-quantum-ms form (7ns form below)
    return this.jdFromNs7(this.ns7Now)
  }
  static get ns7Now()/*:<BigInt#60>*/{
    if(typeof performance !== 'undefined' && performance.now)
      Reflect.defineProperty(this, 'ns7Now',
        Reflect.getOwnPropertyDescriptor(this,'ns7Now_performance'))
    else
      Reflect.defineProperty(this, 'ns7Now',
        Reflect.getOwnPropertyDescriptor(this, 'ns7Now_Date'))
    return this.ns7Now
  }
  static get ns7Now_Date()/*:<BigInt#60>*/{
    // const epoch1582Ns7_bias = 0x1b2_1dd2_1381_4000  // V1 1582 Oct 15
    // const epoch1601Ns7_bias = 0x19d_b1de_d53e_8000n // FILETIME base
    const epoch1970Ns7 = BigInt(Date.now() * 1000_0.0)
    return epoch1970Ns7 + 0x1b2_1dd2_1381_4000n
  }
  static get ns7Now_performance()/*:<BigInt#60>*/{
    const epochPgNs7 = BigInt(performance.now()*/*15*/1000_0.0|/*17*/0)
    if(!this.epoch1970PgNs7) // performance.timing.navigationStart
      this.epoch1970PgNs7 = this.ns7Now_Date - epochPgNs7
    return epochPgNs7 + this.epoch1970PgNs7
  }
  static dateFromJd(jd) {return new Date((jd - 2440587.5) * 864e5)}
  static dateFromNs7(ns7) {
    return new Date(Number(ns7 - 0x1b2_1dd2_1381_4000n) / 1000_0.0)}
  static jdFromNs7(ns7) {   // atomic-clock leap-seconds (ignored)
    return 2440587.5 + (Number(ns7 - 0x1b2_1dd2_1381_4000n) / 864e9)
  }
  static ns7FromJd(jd) {
    return BigInt((jd - 2440587.5) * 864e9) + 0x1b2_1dd2_1381_4000n
  }
  static getRandomValues(va/*:<Uint32Array>*/) {
    if(typeof crypto !== 'undefined' && crypto.getRandomValues)
      crypto.getRandomValues(va)
    else for(let i = 0, n = va.length; i < n; i += 1)
      va[i] = Math.random() * 0x1_0000_0000 >>> 0
  }
  static get a4() {return this.a4_ || (this.a4_ = new Uint32Array(4))}
  static ntohl(v)/*:<BigInt>*/{
    let r = '0x', sign = 1n, s = BigInt(v).toString(16)
    if(s[0] == '-') s = s.substr(1), sign = -1n
    for(let i = s.length; i > 0; i -= 2)
      r += (i == 1) ? ('0' + s[i-1]) : s[i-2] + s[i-1]
    return sign*BigInt(r)
  }
  static ntohl32(v)/*:<Number>*/{return Number(this.ntohl(v))}
}

Motivation

While v4 uuid defines a basically random uuid, it is desirable to have a uuid implementation that can support some additional characteristics.

  • creates new uuid values quickly and efficiently (using BigInt)
  • implemented as stand-alone code with a nominal 80 loc readable class w/comments
  • incorporates uuid uniqueness using monotonic time within a context
  • stringifies such that the string form:
    • collates based on time and then context (using uuid Variant-2)
    • converts back to a binary form that correctly identifies and recovers the time
  • incorporates JavaScript micro-second clock accuracy where available
  • supports cross-environment quantum of 100 nano-second units based on julian-day epoch year 1582 Oct 15, V1 compatibility. Choices that enable unified time behavior across a spectrum of environments and use cases consistent with EdgeS and ESS language model.

    Especially suited for database use with facilities like SQLite.

  • uses es6 class design to simplify extensibility for nominal work to extend it to provide other uuid variants
  • for this posting, unified and incorporated basic time and related eswc library APIs.
    • Julian Day API
    • ns7 (100 nano-second quantum) API
    • ntohl API for endian convenience re-ordering BigInt string representations
  • derived from QKS Smalltalk 1991, AOS® [Agile Object System;Agents Object System] engine family technology for language, framework and runtimes it preserves use case compatibility across a wide range of current and historical host OS models.
    • specifically where the Xuid curly-brace quoted scalar string format supports guid, uuid, and uid (git, fossil, SqLite repo-id) representations, FILETIME, etc.

      as in: {1eb4a659-8bdc-4ce0-c002-b1d505d38ea8}

  • last, but not least, it provides a desirable solution to working with indexedDb object stores where using a uuid as the primaryKey becomes desireable.
    • enabling auto-sequencing capabilities
    • natural string collation
      • note the subtle use of uuid Variant-2 to reverse time value of the LHS in its stringified form.
    • natural and simple put updating
    • natural pattern for efs (EdgeS virtual file-system auto-names)
    • service-worker and cloud-server sync and replicate actions

Summary

Although terse, hopefully that is sufficient explanation for now; try it.

And, please feel free to comment, submit feedback or suggestions.

When released as part of the EdgeS web-client eswc library on GitHub the indexedDb usage patterns with efs will serve as examples of its design intentions which include addressing efficiencies and usability with indexedDb and related PWA sync and replicate scenarios.

Related

Benchmarking uuids/sec

const start = Xuid.ns7Now
for(let i = 100000; i; i -=1)
  Xuid.v4New
const end = Xuid.ns7Now
console.log(`Delta 7ns: ${(end-start)/100000n}`)

Resulted in: values of 16..20 => ~2 micro-seconds => 500,000 uuids/sec

This is just a concept, which most certainly can be improved in many ways, but isn't that slow as I thought it would be.

In general, this code includes hex encoded timestamp in milliseconds (with some hacking it gives 12 digits, so the code will work even after 2527-06-24, but not after 5138-11-16), which means it's sortable. It's not that random, it uses the MAC address for last 12 digits. 13th letter is hard coded 1, to keep it sortable.

After that, next 6 digits come from semi-random string, where first digits come from count of records generated on that millisecond, and other digits are randomly generated. That 6-digit portion contains a dash, and hard coded letter 'a', to keep records sortable.

I know this could be shortened, and performance improved, but I'm happy with results (except the MAC address).

currentNanoseconds = () => {
  return nodeMode ? process.hrtime.bigint() : BigInt(Date.now() * 1000000);
}

nodeFindMacAddress = () => {
  // Extract MAC address
  const interfaces = require('os').networkInterfaces();
  let result = null;
  for (index in interfaces) {
    let entry = interfaces[index];
    entry.forEach(item => {
      if (item.mac !== '00:00:00:00:00:00') {
        result = '-' + item.mac.replace(/:/g, '');
      }
    });
  }
  return result;
}

const nodeMode = typeof(process) !== 'undefined';
let macAddress = nodeMode ? nodeFindMacAddress() : '-a52e99ef5efc';
let startTime = currentNanoseconds();


let uuids = []; // Array for storing generated UUIDs, useful for testing
let currentTime = null; // Holds the last value of Date.now(), used as a base for generating the UUID
let timePart = null; // Part of the UUID generated from Date.now()
let counter = 0; // Used for counting records created at certain millisecond
let lastTime = null; // Used for resetting the record counter

const limit = 1000000;

for (let testCounter = 0; testCounter < limit; testCounter++) {
  let uuid = testMe();

  if (nodeMode || testCounter <= 50) {
    uuids.push(uuid);
  }
}

const timePassed = Number(currentNanoseconds() - startTime);

if (nodeMode) {
  const fs = require('fs');
  fs.writeFileSync('temp.txt', JSON.stringify(uuids).replace(/,/g, ',\n'));
} else {
  console.log(uuids);
}

console.log({
  operationsPerSecond: (1000 * limit / timePassed).toString() + 'm',
  nanosecondsPerCycle: timePassed / limit,
  milliSecondsPassed: timePassed / 1000000,
  microSecondsPassed: timePassed / 1000,
  nanosecondsPassed: timePassed
});

function testMe() {
  currentTime = Date.now();
  let uuid = null; // Function result

  if (currentTime !== lastTime) {
    // Added a 9 before timestamp, so that the hex-encoded timestamp is 12 digits long. Currently, it is 11 digits long, and it will be until 2527-06-24
    // console.log(Date.parse("2527-06-24").toString(16).length)
    // Code will stop working on 5138-11-17, because the timestamp will be 15 digits long, and the code only handles up to 14 digit timestamps
    // console.log((Date.parse("5138-11-17")).toString().length)
    timePart = parseInt(('99999999999999' + currentTime).substr(-14)).toString(16);
    timePart = timePart.substr(0, 8) + '-' + timePart.substr(8, 4) + '-1';
    counter = 0;
  }

  randomPart = ('000000' + Math.floor(10 * (counter + Math.random()))).slice(-6);
  randomPart = randomPart.substr(0, 3) + '-a' + randomPart.substr(3, 3);
  uuid = timePart + randomPart + macAddress;

  counter++;

  lastTime = currentTime;

  return uuid;
}

this offering returns 5 groups of 8 digits from a-z,0-9 most of it is random, however incorprates time of day, and has a randomly incrementing counter. you can specify any base you like (hex, decimal, 36), by default chooses a random base for each group of 8, in the range of base 16 to 36

function newId(base) {
return[
 Math.random,
 function (){ return (newId.last ? windowId.last + Math.random() : Math.random() ) },
 Math.random,
 Date.now,
 Math.random
].map(function(fn){
    return fn().toString(base||(16+(Math.random()*20))).substr(-8);
}).join('-');
}

var demo = function(base){
    document.getElementById('uuid').textContent = newId(base);
}
demo(16);
#uuid { font-family: monospace; font-size: 1.5em; }
<p id="uuid"></p>
<button onclick="demo(16);">Hex (base 16)</button>
<button onclick="demo(36);">Base 36</button>
<button onclick="demo(10);">Decimal (base 10)</button>
<button onclick="demo();">Random base</button>

Don't use Math.random in any case since it generates a non-cryptographic source of random numbers.

The solution below using crypto.getRandomValues

function uuidv4() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
    // tslint:disable-next-line: no-bitwise
    const r =
      (window.crypto.getRandomValues(new Uint32Array(1))[0] *
        Math.pow(2, -32) * 16) |
      0;
    // tslint:disable-next-line: no-bitwise
    const v = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

This link helps you to understand the insecure randomness thrown by Fortify Scanner.

UUID with timestamp built in (emitter/parser)

This is my simple approach to generating a valid UUID v4 with very strong uniqueness and fast runtime.

The basic idea is not new, but the approach is different. I use a timestamp in milliseconds from the date.now() (in the Node.js library, which I'll point later, I use nanoseconds timestamp from process.hrtime.bigint()), and then add a random 5 digit number (10000-90000) to the end of the timestamp string.

After merging the strings, I just form a valid UUID from digits and a pair of special characters, so that my UUID consists only of digits and a few non-numeric characters. Please check it out below:

/*
 * uuid-timestamp (emitter)
 * UUID v4 based on timestamp
 *
 * Created by tarkh
 * tarkh.com (C) 2020
 */
const uuidEmit = () => {
  // Get now time
  const n = Date.now();
  // Generate random
  const r = Math.random();
  // Stringify now time and generate additional random number
  const s = String(n) + String(~~(r*9e4)+1e4);
  // Form UUID and return it
  return `${s.slice(0,8)}-${s.slice(8,12)}-4${s.slice(12,15)}-${[8,9,'a','b'][~~(r*3)]}${s.slice(15,18)}-${s.slice(s.length-12)}`;
};

// Generate 5 UUIDs
console.log(`${uuidEmit()}
${uuidEmit()}
${uuidEmit()}
${uuidEmit()}
${uuidEmit()}`);

Looking at the results, you obviously see that the first part of UUIDs is the same, and then comes randomness. This is because I inserted the timestamp into the UUID linearly. The code will produce a new UUID every millisecond (nanosecond in Node.js library) + add a random 5-digit number to the end, so we end up with very approximate collision probability around 1 in 10 million per second. If we use Node.js library, our very approximate collision probability goes to 1 in 10 billion per second.

Timestamp built into the UUID

Since we insert a timestamp into the UUID linearly, we get a feature (good or bad - depends on the task) - ability to easily extract this timestamp back from the UUID. This way we can understand when UUID was released:

/*
 * uuid-timestamp (parser)
 * UUID v4 based on timestamp
 *
 * Created by tarkh
 * tarkh.com (C) 2020
 */
const uuidParse = (uuid) => {
  // Get current timestamp string length
  let tl = String(Date.now()).length;
  // Strip out timestamp from UUID
  let ts = '';
  let i = -1;
  while(tl--) {
    i++;
    if(i===8||i===13||i===14||i===18||i===19||i===23) {
      tl++;
      continue;
    }
    ts += uuid[i];
  }
  return Number(ts);
};

// Get the timestamp when UUID was emitted
const time = uuidParse('15970688-7109-4530-8114-887109530114');

// Covert timestamp to date and print it
console.log(new Date(time).toUTCString());

Node.js

The NPM version of my code above available as a Node.js module. This version is even more powerful in generating unique values, because instead of millisecond timestamp it uses nanoseconds from combination of system time and process.hrtime.bigint() diff.

Benchmarks

At the end of my post, I want to do some performance tests based on some of the answers from this topic. Of course, my decision is not the fastest, but it certainly takes the top positions.

Check jsBench here

Effectively, a GUID, or UUID as it is called in non-Microsoft-circles, is just a 128-Bit cryptographic random number, with the UUID version number (1-5) being at a fixed location byte.

So when you just generate a bunch of random numbers between 0 and 65535 and hex-encode them, like this:

function guid()
{
    function s4()
    {
        return Math.floor(Math.random() * 65536).toString(16).padStart(4, '0')
    } // End Function s4

    return s4() + s4() + '-' + s4() + '-' + "4" + s4().substr(1) + '-' + s4() + '-' + s4() + s4() + s4();
} // End Function guid

You get a valid GUID, but due to the random-implementation, it's not cryptographically secure.

To generate a cryptographically secure GUID, you need to use window.crypto (or window.msCrypto for Internet Explorer).

That goes like this:

function cryptGuid()
{
    var array = new Uint16Array(8);
    (window.crypto || window.msCrypto).getRandomValues(array);
    var dataView = new DataView(array.buffer);

    var parts = [];

    for(var i = 0; i < array.length; ++i)
    {
        // 0&1,2,3,4,5-7 dataView.getUint16(0-7)
        if(i>1 && i<6) parts.push("-");
        parts.push(dataView.getUint16(i).toString(16).padStart(4, '0'));
    }

    parts[5] = "4" + parts[5].substr(1);
    // console.log(parts);
    return parts.join('').toUpperCase();// .toLowerCase();
}

cryptGuid();

Plus you have to decide, if you return the number as lower-or upper-case character string. Certain software require lowercase characters (e.g., Reporting Service), while others generate uppercase characters (SQL Server).

The coolest way:

function uuid(){
    var u = URL.createObjectURL(new Blob([""]))
    URL.revokeObjectURL(u);
    return u.split("/").slice(-1)[0]
}

It's probably not fast, efficient, or supported in IE2 but it sure is cool

For situations where you already have created a URL for some resource through utilizing the URL.createObjectURL method, you probably won't do faster or shorter than the following:

const uuid = url => url.substr(-36);

The above will work with any compliant implementation of createObjectURL, since the specification explicitly mandates a UUID be added to the end of the URL returned by the former. So you are guaranteed the last 36 characters are the UUID part of the generated URL.

To be clear, sometimes -- heck, perhaps most of the time, everything considered -- you want to generate a UUID for something else than resources you create URLs for with createObjectURL. In those cases, calling the latter method on some new Blob() is going to absolutely tank the performance (and leak memory unless you clean up after yourself using the corresponding revokeObjectURL). It is still quite a "one-liner" though.

I do not recommend you use the above method just for generating UUIDs unless you already have URLs obtained through createObjectURL or something that has a UUID at the end.

I just wanted to mention the above variant for completeness.

Easy to do with a simple uuid package https://www.npmjs.com/package/uuid

const { v4: uuidv4 } = require('uuid');
uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'

The following is not v4 compliant, but it could easily be altered to be. It's just an example of extending the Uint8Array type, and using crypto.getRandomValues() to generate the UUID byte values.

class uuid extends Uint8Array {
    constructor() {
        super(16)
        /* Not v4, just some random bytes */
        window.crypto.getRandomValues(this)
    }
    toString() {
        let id = new String()
        for (let i = 0; i < this.length; i++) {
            /* Convert uint8 to hex string */
            let hex = this[i].toString(16).toUpperCase()

            /* Add zero padding */
            while (hex.length < 2) {
                hex = String(0).concat(hex)
            }
            id += hex

            /* Add dashes */
            if (i == 4 || i == 6 || i == 8 || i == 10 || i == 16) {
                id += '-'
            }
        }
        return id
    }
}
var guid = createMyGuid();

function createMyGuid()  
{  
   return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {  
      var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);  
      return v.toString(16);  
   });  
}

I use this version. It is safe and simple. It is not to generate formatted uids, it is just to generate random strings of chars you need.

export function makeId(length) {
  let result = '';
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const charactersLength = characters.length;

  for (let i = 0; i < length; i++) {
    let letterPos = crypto.getRandomValues(new Uint8Array(1))[0] / 255 * charactersLength - 1
    result += characters[letterPos]
  }
  return result;
}

Here's a method that generates RFC4122 using true random via random.org. If the fetch fails it falls back to the browser's inbuilt crypto library which should almost be just as good. And finally, if the user's browser in question doesn't support that, it uses Math.random().

async function UUID() {
    //get 31 random hex characters
    return (await (async () => {
        let output;
        try {
            //try from random.org
            output = (await (
                await fetch('https://www.random.org/integers/?num=31&min=0&max=15&col=31&base=16&format=plain&rnd=new')
            ).text())
                //get rid of whitespace
                .replace(/[^0-9a-fA-F]+/g, '')
            ;
            if (output.length != 31)
                throw '';
        }
        catch {
            output = '';
            try {
                //failing that, try getting 16 8-bit digits from crypto
                for (let num of crypto.getRandomValues(new Uint8Array(16)))
                    //interpret as 32 4-bit hex numbers
                    output += (num >> 4).toString(16) + (num & 15).toString(16);
                //we only want 31
                output = output.substr(1);
            }
            catch {
                //failing THAT, use Math.random
                while (output.length < 31)
                    output += (0 | Math.random() * 16).toString(16);
            }
        }
        return output;
    })())
        //split into appropriate sections, and set the 15th character to 4
        .replace(/^(.{8})(.{4})(.{3})(.{4})/, '$1-$2-4$3-$4-')
        //force character 20 to the correct range
        .replace(/(?<=-)[^89abAB](?=[^-]+-[^-]+$)/, (num) => (
            (parseInt(num, 16) % 4 + 8).toString(16)
        ))
    ;
}

enter image description here

Related