UNPKG

@datadayrepos/js-id-web

Version:

Utils for generating identifiers in javascript browser environment. Using web crypto engine for random number generation.

146 lines (145 loc) 4.36 kB
import { getEnvironmentMachineID, getProcessID, randInt3, TextDecoderPolyfill, TextEncoderPolyfill } from '../crypto/index.js'; import { computeSHA256String } from '../hash/index.js'; async function machineId() { const rawMid = getEnvironmentMachineID(); const mid = await computeSHA256String(rawMid, 3); return mid; } async function processId() { const pid = await getProcessID(); return pid; } export async function generateXID() { const mid = await machineId(); const pid = await processId(); let seq = randInt3(); let time = Math.floor(Date.now() / 1000); function next() { const now = Math.floor(Date.now() / 1000); if (time !== now) { time = now; seq = randInt3(); } const c = seq & 0xFFFFFF; seq += 1; const buff = new Uint8Array(12); buff[0] = (time >> 24) & 0xFF; buff[1] = (time >> 16) & 0xFF; buff[2] = (time >> 8) & 0xFF; buff[3] = time & 0xFF; buff[4] = mid[0]; buff[5] = mid[1]; buff[6] = mid[2]; buff[7] = (pid >> 8) & 0xFF; buff[8] = pid & 0xFF; buff[9] = (c >> 16) & 0xFF; buff[10] = (c >> 8) & 0xFF; buff[11] = c & 0xFF; const encodedFull = encodeString(buff); return encodedFull.substring(0, 20); } return { next, }; } const crockford = '0123456789abcdefghjkmnpqrstvwxyz'; const padding = '='.charCodeAt(0); const codebook = Array.from({ length: 32 }); for (let i = 0; i < 32; i += 1) codebook[i] = crockford.charCodeAt(i); function pad(buff, ptr, trailing) { let len = 8; buff[ptr + 7] = padding; if (trailing < 4) { buff[ptr + 6] = padding; buff[ptr + 5] = padding; len -= 2; if (trailing < 3) { buff[ptr + 4] = padding; len -= 1; if (trailing < 2) { buff[ptr + 3] = padding; buff[ptr + 2] = padding; len -= 2; } } } return len; } function encodeLength(n) { const len = n / 5; const precise = len | 0; if (len === precise) return len * 8; return (precise + 1) * 8; } function wrap(input) { const encoder = new TextEncoderPolyfill(); if (typeof input === 'string') { return encoder.encode(input).buffer; } else if (Array.isArray(input)) { const concatenatedString = input.join(''); return encoder.encode(concatenatedString).buffer; } else { return input; } } function encode(input) { const src = new Uint8Array(wrap(input)); const len = src.length; if (len === 0) return new Uint8Array(); const dst = new Uint8Array(encodeLength(len)); let offset = 0; let written = 0; while (offset < len) { let b0 = 0; let b1 = 0; let b2 = 0; let b3 = 0; let b4 = 0; let b5 = 0; let b6 = 0; let b7 = 0; const remain = len - offset; switch (remain) { default: b7 = src[offset + 4] & 0x1F; b6 = src[offset + 3] >> 5; case 4: b6 |= (src[offset + 3] << 3) & 0x1F; b5 = (src[offset + 3] >> 2) & 0x1F; b4 = src[offset + 3] >> 7; case 3: b4 |= (src[offset + 2] << 1) & 0x1F; b3 = (src[offset + 2] >> 4) & 0x1F; case 2: b3 |= (src[offset + 1] << 4) & 0x1F; b2 = (src[offset + 1] >> 1) & 0x1F; b1 = (src[offset + 1] >> 6) & 0x1F; case 1: b1 |= (src[offset] << 2) & 0x1F; b0 = src[offset] >> 3; } dst[written] = codebook[b0]; dst[written + 1] = codebook[b1]; dst[written + 2] = codebook[b2]; dst[written + 3] = codebook[b3]; dst[written + 4] = codebook[b4]; dst[written + 5] = codebook[b5]; dst[written + 6] = codebook[b6]; dst[written + 7] = codebook[b7]; if (remain < 5) { written += pad(dst, written, remain); break; } written += 8; offset += 5; } return dst.slice(0, written); } function encodeString(input) { return new TextDecoderPolyfill().decode(encode(input)); }