hashids.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. (function (global, factory) {
  2. if (typeof define === "function" && define.amd) {
  3. define("Hashids", ["exports"], factory);
  4. } else if (typeof exports !== "undefined") {
  5. factory(exports);
  6. } else {
  7. var mod = {
  8. exports: {}
  9. };
  10. factory(mod.exports);
  11. global.Hashids = mod.exports;
  12. }
  13. })(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports) {
  14. "use strict";
  15. _exports.__esModule = true;
  16. _exports.onlyChars = _exports.withoutChars = _exports.keepUnique = _exports.default = void 0;
  17. function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
  18. function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
  19. function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
  20. function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
  21. var Hashids =
  22. /*#__PURE__*/
  23. function () {
  24. function Hashids(salt, minLength, alphabet, seps) {
  25. if (salt === void 0) {
  26. salt = '';
  27. }
  28. if (minLength === void 0) {
  29. minLength = 0;
  30. }
  31. if (alphabet === void 0) {
  32. alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
  33. }
  34. if (seps === void 0) {
  35. seps = 'cfhistuCFHISTU';
  36. }
  37. this.minLength = minLength;
  38. if (typeof minLength !== 'number') {
  39. throw new TypeError("Hashids: Provided 'minLength' has to be a number (is " + typeof minLength + ")");
  40. }
  41. if (typeof salt !== 'string') {
  42. throw new TypeError("Hashids: Provided 'salt' has to be a string (is " + typeof salt + ")");
  43. }
  44. if (typeof alphabet !== 'string') {
  45. throw new TypeError("Hashids: Provided alphabet has to be a string (is " + typeof alphabet + ")");
  46. }
  47. var saltChars = Array.from(salt);
  48. var alphabetChars = Array.from(alphabet);
  49. var sepsChars = Array.from(seps);
  50. this.salt = saltChars;
  51. var uniqueAlphabet = keepUnique(alphabetChars);
  52. if (uniqueAlphabet.length < minAlphabetLength) {
  53. throw new Error("Hashids: alphabet must contain at least " + minAlphabetLength + " unique characters, provided: " + uniqueAlphabet);
  54. }
  55. /** `alphabet` should not contains `seps` */
  56. this.alphabet = withoutChars(uniqueAlphabet, sepsChars);
  57. /** `seps` should contain only characters present in `alphabet` */
  58. var filteredSeps = onlyChars(sepsChars, uniqueAlphabet);
  59. this.seps = shuffle(filteredSeps, saltChars);
  60. var sepsLength;
  61. var diff;
  62. if (this.seps.length === 0 || this.alphabet.length / this.seps.length > sepDiv) {
  63. sepsLength = Math.ceil(this.alphabet.length / sepDiv);
  64. if (sepsLength > this.seps.length) {
  65. var _this$seps;
  66. diff = sepsLength - this.seps.length;
  67. (_this$seps = this.seps).push.apply(_this$seps, _toConsumableArray(this.alphabet.slice(0, diff)));
  68. this.alphabet = this.alphabet.slice(diff);
  69. }
  70. }
  71. this.alphabet = shuffle(this.alphabet, saltChars);
  72. var guardCount = Math.ceil(this.alphabet.length / guardDiv);
  73. if (this.alphabet.length < 3) {
  74. this.guards = this.seps.slice(0, guardCount);
  75. this.seps = this.seps.slice(guardCount);
  76. } else {
  77. this.guards = this.alphabet.slice(0, guardCount);
  78. this.alphabet = this.alphabet.slice(guardCount);
  79. }
  80. this.guardsRegExp = makeAnyOfCharsRegExp(this.guards);
  81. this.sepsRegExp = makeAnyOfCharsRegExp(this.seps);
  82. this.allowedCharsRegExp = makeAtLeastSomeCharRegExp([].concat(_toConsumableArray(this.alphabet), _toConsumableArray(this.guards), _toConsumableArray(this.seps)));
  83. }
  84. var _proto = Hashids.prototype;
  85. _proto.encode = function encode(first) {
  86. for (var _len = arguments.length, numbers = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  87. numbers[_key - 1] = arguments[_key];
  88. }
  89. var ret = '';
  90. if (Array.isArray(first)) {
  91. numbers = first;
  92. } else {
  93. // eslint-disable-next-line eqeqeq
  94. numbers = [].concat(_toConsumableArray(first != null ? [first] : []), _toConsumableArray(numbers));
  95. }
  96. if (!numbers.length) {
  97. return ret;
  98. }
  99. if (!numbers.every(isIntegerNumber)) {
  100. numbers = numbers.map(function (n) {
  101. return typeof n === 'bigint' || typeof n === 'number' ? n : safeParseInt10(String(n));
  102. });
  103. }
  104. if (!numbers.every(isPositiveAndFinite)) {
  105. return ret;
  106. }
  107. return this._encode(numbers).join('');
  108. };
  109. _proto.decode = function decode(id) {
  110. if (!id || typeof id !== 'string' || id.length === 0) return [];
  111. return this._decode(id);
  112. }
  113. /**
  114. * @description Splits a hex string into groups of 12-digit hexadecimal numbers,
  115. * then prefixes each with '1' and encodes the resulting array of numbers
  116. *
  117. * Encoding '00000000000f00000000000f000f' would be the equivalent of:
  118. * Hashids.encode([0x100000000000f, 0x100000000000f, 0x1000f])
  119. *
  120. * This means that if your environment supports BigInts,
  121. * you will get different (shorter) results if you provide
  122. * a BigInt representation of your hex and use `encode` directly, e.g.:
  123. * Hashids.encode(BigInt(`0x${hex}`))
  124. *
  125. * To decode such a representation back to a hex string, use the following snippet:
  126. * Hashids.decode(id)[0].toString(16)
  127. */
  128. ;
  129. _proto.encodeHex = function encodeHex(hex) {
  130. switch (typeof hex) {
  131. case 'bigint':
  132. hex = hex.toString(16);
  133. break;
  134. case 'string':
  135. if (!/^[0-9a-fA-F]+$/.test(hex)) return '';
  136. break;
  137. default:
  138. throw new Error("Hashids: The provided value is neither a string, nor a BigInt (got: " + typeof hex + ")");
  139. }
  140. var numbers = splitAtIntervalAndMap(hex, 12, function (part) {
  141. return parseInt("1" + part, 16);
  142. });
  143. return this.encode(numbers);
  144. };
  145. _proto.decodeHex = function decodeHex(id) {
  146. return this.decode(id).map(function (number) {
  147. return number.toString(16).slice(1);
  148. }).join('');
  149. };
  150. _proto._encode = function _encode(numbers) {
  151. var _this = this;
  152. var alphabet = this.alphabet;
  153. var numbersIdInt = numbers.reduce(function (last, number, i) {
  154. return last + (typeof number === 'bigint' ? Number(number % BigInt(i + 100)) : number % (i + 100));
  155. }, 0);
  156. var ret = [alphabet[numbersIdInt % alphabet.length]];
  157. var lottery = ret.slice();
  158. var seps = this.seps;
  159. var guards = this.guards;
  160. numbers.forEach(function (number, i) {
  161. var _ret;
  162. var buffer = lottery.concat(_this.salt, alphabet);
  163. alphabet = shuffle(alphabet, buffer);
  164. var last = toAlphabet(number, alphabet);
  165. (_ret = ret).push.apply(_ret, _toConsumableArray(last));
  166. if (i + 1 < numbers.length) {
  167. var charCode = last[0].codePointAt(0) + i;
  168. var extraNumber = typeof number === 'bigint' ? Number(number % BigInt(charCode)) : number % charCode;
  169. ret.push(seps[extraNumber % seps.length]);
  170. }
  171. });
  172. if (ret.length < this.minLength) {
  173. var prefixGuardIndex = (numbersIdInt + ret[0].codePointAt(0)) % guards.length;
  174. ret.unshift(guards[prefixGuardIndex]);
  175. if (ret.length < this.minLength) {
  176. var suffixGuardIndex = (numbersIdInt + ret[2].codePointAt(0)) % guards.length;
  177. ret.push(guards[suffixGuardIndex]);
  178. }
  179. }
  180. var halfLength = Math.floor(alphabet.length / 2);
  181. while (ret.length < this.minLength) {
  182. var _ret2, _ret3;
  183. alphabet = shuffle(alphabet, alphabet);
  184. (_ret2 = ret).unshift.apply(_ret2, _toConsumableArray(alphabet.slice(halfLength)));
  185. (_ret3 = ret).push.apply(_ret3, _toConsumableArray(alphabet.slice(0, halfLength)));
  186. var excess = ret.length - this.minLength;
  187. if (excess > 0) {
  188. var halfOfExcess = excess / 2;
  189. ret = ret.slice(halfOfExcess, halfOfExcess + this.minLength);
  190. }
  191. }
  192. return ret;
  193. };
  194. _proto.isValidId = function isValidId(id) {
  195. return this.allowedCharsRegExp.test(id);
  196. };
  197. _proto._decode = function _decode(id) {
  198. if (!this.isValidId(id)) {
  199. throw new Error("The provided ID (" + id + ") is invalid, as it contains characters that do not exist in the alphabet (" + this.guards.join('') + this.seps.join('') + this.alphabet.join('') + ")");
  200. }
  201. var idGuardsArray = id.split(this.guardsRegExp);
  202. var splitIndex = idGuardsArray.length === 3 || idGuardsArray.length === 2 ? 1 : 0;
  203. var idBreakdown = idGuardsArray[splitIndex];
  204. if (idBreakdown.length === 0) return [];
  205. var lotteryChar = idBreakdown[Symbol.iterator]().next().value;
  206. var idArray = idBreakdown.slice(lotteryChar.length).split(this.sepsRegExp);
  207. var lastAlphabet = this.alphabet;
  208. var result = [];
  209. for (var _iterator = idArray, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
  210. var _ref;
  211. if (_isArray) {
  212. if (_i >= _iterator.length) break;
  213. _ref = _iterator[_i++];
  214. } else {
  215. _i = _iterator.next();
  216. if (_i.done) break;
  217. _ref = _i.value;
  218. }
  219. var subId = _ref;
  220. var buffer = [lotteryChar].concat(_toConsumableArray(this.salt), _toConsumableArray(lastAlphabet));
  221. var nextAlphabet = shuffle(lastAlphabet, buffer.slice(0, lastAlphabet.length));
  222. result.push(fromAlphabet(Array.from(subId), nextAlphabet));
  223. lastAlphabet = nextAlphabet;
  224. } // if the result is different from what we'd expect, we return an empty result (malformed input):
  225. if (this._encode(result).join('') !== id) return [];
  226. return result;
  227. };
  228. return Hashids;
  229. }();
  230. _exports.default = Hashids;
  231. var minAlphabetLength = 16;
  232. var sepDiv = 3.5;
  233. var guardDiv = 12;
  234. var keepUnique = function keepUnique(content) {
  235. return Array.from(new Set(content));
  236. };
  237. _exports.keepUnique = keepUnique;
  238. var withoutChars = function withoutChars(chars, _withoutChars) {
  239. return chars.filter(function (char) {
  240. return !_withoutChars.includes(char);
  241. });
  242. };
  243. _exports.withoutChars = withoutChars;
  244. var onlyChars = function onlyChars(chars, keepChars) {
  245. return chars.filter(function (char) {
  246. return keepChars.includes(char);
  247. });
  248. };
  249. _exports.onlyChars = onlyChars;
  250. var isIntegerNumber = function isIntegerNumber(n) {
  251. return typeof n === 'bigint' || !Number.isNaN(Number(n)) && Math.floor(Number(n)) === n;
  252. };
  253. var isPositiveAndFinite = function isPositiveAndFinite(n) {
  254. return typeof n === 'bigint' || n >= 0 && Number.isSafeInteger(n);
  255. };
  256. function shuffle(alphabetChars, saltChars) {
  257. if (saltChars.length === 0) {
  258. return alphabetChars;
  259. }
  260. var integer;
  261. var transformed = alphabetChars.slice();
  262. for (var i = transformed.length - 1, v = 0, p = 0; i > 0; i--, v++) {
  263. v %= saltChars.length;
  264. p += integer = saltChars[v].codePointAt(0);
  265. var j = (integer + v + p) % i; // swap characters at positions i and j
  266. var a = transformed[i];
  267. var b = transformed[j];
  268. transformed[j] = a;
  269. transformed[i] = b;
  270. }
  271. return transformed;
  272. }
  273. var toAlphabet = function toAlphabet(input, alphabetChars) {
  274. var id = [];
  275. if (typeof input === 'bigint') {
  276. var alphabetLength = BigInt(alphabetChars.length);
  277. do {
  278. id.unshift(alphabetChars[Number(input % alphabetLength)]);
  279. input = input / alphabetLength;
  280. } while (input > BigInt(0));
  281. } else {
  282. do {
  283. id.unshift(alphabetChars[input % alphabetChars.length]);
  284. input = Math.floor(input / alphabetChars.length);
  285. } while (input > 0);
  286. }
  287. return id;
  288. };
  289. var fromAlphabet = function fromAlphabet(inputChars, alphabetChars) {
  290. return inputChars.reduce(function (carry, item) {
  291. var index = alphabetChars.indexOf(item);
  292. if (index === -1) {
  293. throw new Error("The provided ID (" + inputChars.join('') + ") is invalid, as it contains characters that do not exist in the alphabet (" + alphabetChars.join('') + ")");
  294. }
  295. if (typeof carry === 'bigint') {
  296. return carry * BigInt(alphabetChars.length) + BigInt(index);
  297. }
  298. var value = carry * alphabetChars.length + index;
  299. var isSafeValue = Number.isSafeInteger(value);
  300. if (isSafeValue) {
  301. return value;
  302. } else {
  303. if (typeof BigInt === 'function') {
  304. return BigInt(carry) * BigInt(alphabetChars.length) + BigInt(index);
  305. } else {
  306. // we do not have support for BigInt:
  307. throw new Error("Unable to decode the provided string, due to lack of support for BigInt numbers in the current environment");
  308. }
  309. }
  310. }, 0);
  311. };
  312. var safeToParseNumberRegExp = /^\+?[0-9]+$/;
  313. var safeParseInt10 = function safeParseInt10(str) {
  314. return safeToParseNumberRegExp.test(str) ? parseInt(str, 10) : NaN;
  315. };
  316. var splitAtIntervalAndMap = function splitAtIntervalAndMap(str, nth, map) {
  317. return Array.from({
  318. length: Math.ceil(str.length / nth)
  319. }, function (_, index) {
  320. return map(str.slice(index * nth, (index + 1) * nth));
  321. });
  322. };
  323. var makeAnyOfCharsRegExp = function makeAnyOfCharsRegExp(chars) {
  324. return new RegExp(chars.map(function (char) {
  325. return escapeRegExp(char);
  326. }) // we need to sort these from longest to shortest,
  327. // as they may contain multibyte unicode characters (these should come first)
  328. .sort(function (a, b) {
  329. return b.length - a.length;
  330. }).join('|'));
  331. };
  332. var makeAtLeastSomeCharRegExp = function makeAtLeastSomeCharRegExp(chars) {
  333. return new RegExp("^[" + chars.map(function (char) {
  334. return escapeRegExp(char);
  335. }) // we need to sort these from longest to shortest,
  336. // as they may contain multibyte unicode characters (these should come first)
  337. .sort(function (a, b) {
  338. return b.length - a.length;
  339. }).join('') + "]+$");
  340. };
  341. var escapeRegExp = function escapeRegExp(text) {
  342. return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  343. };
  344. });
  345. //# sourceMappingURL=hashids.js.map