123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- export default class Hashids {
- constructor(salt = '', minLength = 0, alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') {
- const minAlphabetLength = 16;
- const sepDiv = 3.5;
- const guardDiv = 12;
- const errorAlphabetLength = 'error: alphabet must contain at least X unique characters';
- const errorAlphabetSpace = 'error: alphabet cannot contain spaces';
- let uniqueAlphabet = '', sepsLength, diff;
- /* funcs */
- this.escapeRegExp = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
- this.parseInt = (v, radix) => (/^(-|\+)?([0-9]+|Infinity)$/.test(v)) ? parseInt(v, radix) : NaN;
- /* alphabet vars */
- this.seps = 'cfhistuCFHISTU';
- this.minLength = parseInt(minLength, 10) > 0 ? minLength : 0;
- this.salt = (typeof salt === 'string') ? salt : '';
- if (typeof alphabet === 'string') {
- this.alphabet = alphabet;
- }
- for (let i = 0; i !== this.alphabet.length; i++) {
- if (uniqueAlphabet.indexOf(this.alphabet.charAt(i)) === -1) {
- uniqueAlphabet += this.alphabet.charAt(i);
- }
- }
- this.alphabet = uniqueAlphabet;
- if (this.alphabet.length < minAlphabetLength) {
- throw errorAlphabetLength.replace('X', minAlphabetLength);
- }
- if (this.alphabet.search(' ') !== -1) {
- throw errorAlphabetSpace;
- }
- /*
- `this.seps` should contain only characters present in `this.alphabet`
- `this.alphabet` should not contains `this.seps`
- */
- for (let i = 0; i !== this.seps.length; i++) {
- const j = this.alphabet.indexOf(this.seps.charAt(i));
- if (j === -1) {
- this.seps = this.seps.substr(0, i) + ' ' + this.seps.substr(i + 1);
- } else {
- this.alphabet = this.alphabet.substr(0, j) + ' ' + this.alphabet.substr(j + 1);
- }
- }
- this.alphabet = this.alphabet.replace(/ /g, '');
- this.seps = this.seps.replace(/ /g, '');
- this.seps = this._shuffle(this.seps, this.salt);
- if (!this.seps.length || (this.alphabet.length / this.seps.length) > sepDiv) {
- sepsLength = Math.ceil(this.alphabet.length / sepDiv);
- if (sepsLength > this.seps.length) {
- diff = sepsLength - this.seps.length;
- this.seps += this.alphabet.substr(0, diff);
- this.alphabet = this.alphabet.substr(diff);
- }
- }
- this.alphabet = this._shuffle(this.alphabet, this.salt);
- const guardCount = Math.ceil(this.alphabet.length / guardDiv);
- if (this.alphabet.length < 3) {
- this.guards = this.seps.substr(0, guardCount);
- this.seps = this.seps.substr(guardCount);
- } else {
- this.guards = this.alphabet.substr(0, guardCount);
- this.alphabet = this.alphabet.substr(guardCount);
- }
- }
- encode(...numbers) {
- const ret = '';
- if (!numbers.length) {
- return ret;
- }
- if (numbers[0] && numbers[0].constructor === Array) {
- numbers = numbers[0];
- if (!numbers.length) {
- return ret;
- }
- }
- for (let i = 0; i !== numbers.length; i++) {
- numbers[i] = this.parseInt(numbers[i], 10);
- if (numbers[i] >= 0) {
- continue;
- } else {
- return ret;
- }
- }
- return this._encode(numbers);
- }
- decode(id) {
- const ret = [];
- if (!id || !id.length || typeof id !== 'string') {
- return ret;
- }
- return this._decode(id, this.alphabet);
- }
- encodeHex(hex) {
- hex = hex.toString();
- if (!/^[0-9a-fA-F]+$/.test(hex)) {
- return '';
- }
- const numbers = hex.match(/[\w\W]{1,12}/g);
- for (let i = 0; i !== numbers.length; i++) {
- numbers[i] = parseInt('1' + numbers[i], 16);
- }
- return this.encode.apply(this, numbers);
- }
- decodeHex(id) {
- let ret = [];
- const numbers = this.decode(id);
- for (let i = 0; i !== numbers.length; i++) {
- ret += (numbers[i]).toString(16).substr(1);
- }
- return ret;
- }
- _encode(numbers) {
- let ret,
- alphabet = this.alphabet,
- numbersIdInt = 0;
- for (let i = 0; i !== numbers.length; i++) {
- numbersIdInt += (numbers[i] % (i + 100));
- }
- ret = alphabet.charAt(numbersIdInt % alphabet.length);
- const lottery = ret;
- for (let i = 0; i !== numbers.length; i++) {
- let number = numbers[i];
- const buffer = lottery + this.salt + alphabet;
- alphabet = this._shuffle(alphabet, buffer.substr(0, alphabet.length));
- const last = this._toAlphabet(number, alphabet);
- ret += last;
- if (i + 1 < numbers.length) {
- number %= (last.charCodeAt(0) + i);
- const sepsIndex = number % this.seps.length;
- ret += this.seps.charAt(sepsIndex);
- }
- }
- if (ret.length < this.minLength) {
- let guardIndex = (numbersIdInt + ret[0].charCodeAt(0)) % this.guards.length;
- let guard = this.guards[guardIndex];
- ret = guard + ret;
- if (ret.length < this.minLength) {
- guardIndex = (numbersIdInt + ret[2].charCodeAt(0)) % this.guards.length;
- guard = this.guards[guardIndex];
- ret += guard;
- }
- }
- const halfLength = parseInt(alphabet.length / 2, 10);
- while (ret.length < this.minLength) {
- alphabet = this._shuffle(alphabet, alphabet);
- ret = alphabet.substr(halfLength) + ret + alphabet.substr(0, halfLength);
- const excess = ret.length - this.minLength;
- if (excess > 0) {
- ret = ret.substr(excess / 2, this.minLength);
- }
- }
- return ret;
- }
- _decode(id, alphabet) {
- let ret = [], i = 0,
- r = new RegExp(`[${this.escapeRegExp(this.guards)}]`, 'g'),
- idBreakdown = id.replace(r, ' '),
- idArray = idBreakdown.split(' ');
- if (idArray.length === 3 || idArray.length === 2) {
- i = 1;
- }
- idBreakdown = idArray[i];
- if (typeof idBreakdown[0] !== 'undefined') {
- const lottery = idBreakdown[0];
- idBreakdown = idBreakdown.substr(1);
- r = new RegExp(`[${this.escapeRegExp(this.seps)}]`, 'g');
- idBreakdown = idBreakdown.replace(r, ' ');
- idArray = idBreakdown.split(' ');
- for (let j = 0; j !== idArray.length; j++) {
- const subId = idArray[j];
- const buffer = lottery + this.salt + alphabet;
- alphabet = this._shuffle(alphabet, buffer.substr(0, alphabet.length));
- ret.push(this._fromAlphabet(subId, alphabet));
- }
- if (this.encode(ret) !== id) {
- ret = [];
- }
- }
- return ret;
- }
- _shuffle(alphabet, salt) {
- let integer;
- if (!salt.length) {
- return alphabet;
- }
- alphabet = alphabet.split("");
- for (let i = alphabet.length - 1, v = 0, p = 0, j = 0; i > 0; i--, v++) {
- v %= salt.length;
- p += integer = salt.charCodeAt(v);
- j = (integer + v + p) % i;
- const tmp = alphabet[j];
- alphabet[j] = alphabet[i];
- alphabet[i] = tmp;
- }
- alphabet = alphabet.join("");
- return alphabet;
- }
- _toAlphabet(input, alphabet) {
- let id = '';
- do {
- id = alphabet.charAt(input % alphabet.length) + id;
- input = parseInt(input / alphabet.length, 10);
- } while (input);
- return id;
- }
- _fromAlphabet(input, alphabet) {
- return input.split("").map(
- (item) => alphabet.indexOf(item)
- ).reduce(
- (carry, item) => carry * alphabet.length + item,
- 0
- );
- }
- }
|