src/auth/tag.ts
v0.1.0 · 1.7 KB
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'),
});
},
};
}