Skip to content
markpaper

src/auth/tag.ts

v0.1.0 · 1.7 KB

Download file
import { randomBytes } from 'node:crypto';

export const CLOID_LENGTH = 32;
export const CLOID_PATTERN = /^[0-9a-f]{32}$/;
export interface CloidFields {
  flag: number;
  sequence: number;
  random: string;
}

/** The caller owns and persists the magic; changing it changes order ownership. */
export function createOrderTag(options: { magic: string; randomBytes?: (length: number) => Buffer }) {
  if (!/^[0-9a-f]{8}$/.test(options?.magic ?? '')) throw new TypeError('Required eight lowercase hex magic');
  let nextSequence = 0;
  function encode(fields: CloidFields): string {
    if (
      !Number.isInteger(fields.flag) ||
      fields.flag < 0 ||
      fields.flag > 255 ||
      !Number.isSafeInteger(fields.sequence) ||
      fields.sequence < 0 ||
      fields.sequence > 0xffffffff ||
      !/^[0-9a-f]{14}$/.test(fields.random)
    )
      throw new TypeError('Invalid order tag fields');
    return (
      options.magic +
      fields.flag.toString(16).padStart(2, '0') +
      fields.sequence.toString(16).padStart(8, '0') +
      fields.random
    );
  }
  function decode(cloid: unknown): CloidFields | null {
    if (typeof cloid !== 'string' || !CLOID_PATTERN.test(cloid) || !cloid.startsWith(options.magic)) return null;
    return {
      flag: Number.parseInt(cloid.slice(8, 10), 16),
      sequence: Number.parseInt(cloid.slice(10, 18), 16),
      random: cloid.slice(18),
    };
  }
  return {
    encode,
    decode,
    isOwn: (cloid: unknown) => decode(cloid) !== null,
    next(flag: number): string {
      if (nextSequence > 0xffffffff) throw new RangeError('Order tag sequence exhausted');
      return encode({
        flag,
        sequence: nextSequence++,
        random: (options.randomBytes ?? randomBytes)(7).toString('hex'),
      });
    },
  };
}
All files