@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
JavaScript
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));
}