Page MenuHomePhabricator

D4731.id15299.diff
No OneTemporary

D4731.id15299.diff

diff --git a/web/utils/text-utils.js b/web/utils/text-utils.js
--- a/web/utils/text-utils.js
+++ b/web/utils/text-utils.js
@@ -1,4 +1,6 @@
// @flow
+import crypto from 'crypto';
+import invariant from 'invariant';
let canvas;
@@ -16,4 +18,44 @@
return Math.max(...widths);
}
-export { calculateMaxTextWidth };
+const numberOfPossibleByteValues = 256;
+
+function generateRandomString(length: number, availableSigns: string): string {
+ invariant(length >= 0, 'length must be non-negative');
+ invariant(
+ availableSigns !== '' || length === 0,
+ "cannot create a random string of non-zero length from availableSigns = ''",
+ );
+ invariant(
+ numberOfPossibleByteValues >= availableSigns.length,
+ 'The number of availabe signs must not exceed 256',
+ );
+
+ const validByteUpperBound =
+ availableSigns.length *
+ Math.floor(numberOfPossibleByteValues / availableSigns.length);
+ // Generating more bytes than the required length,
+ // proportionally to how many values will be omitted
+ // due to uniformness requirement,
+ // to lower the chances of having to draw again
+ const drawBytes =
+ length *
+ (1 +
+ 2 * (1 - Math.floor(validByteUpperBound / numberOfPossibleByteValues)));
+
+ let str = '';
+
+ while (str.length < length) {
+ const rand = crypto.randomBytes(drawBytes);
+
+ for (let i = 0; str.length < length && i < drawBytes; i++) {
+ if (rand[i] < validByteUpperBound) {
+ const index = rand[i] % availableSigns.length;
+ str += availableSigns.charAt(index);
+ }
+ }
+ }
+ return str;
+}
+
+export { calculateMaxTextWidth, generateRandomString };
diff --git a/web/utils/text-utils.test.js b/web/utils/text-utils.test.js
new file mode 100644
--- /dev/null
+++ b/web/utils/text-utils.test.js
@@ -0,0 +1,50 @@
+// @flow
+
+import { generateRandomString } from './text-utils';
+
+describe('generateRandomString', () => {
+ it('should return an empty string when passed length = 0', () => {
+ expect(generateRandomString(0, 'abcde')).toMatch(/^$/);
+ expect(generateRandomString(0, '')).toMatch(/^$/);
+ });
+
+ it('should return a random string of length equal to length, containig only characters given in availableSigns', () => {
+ const length1 = 100;
+ const availableSigns1 = 'abcde';
+ expect(generateRandomString(length1, availableSigns1)).toMatch(
+ new RegExp(`^[${availableSigns1}]{${length1.toString()}}$`),
+ );
+
+ const length2 = 10;
+ const availableSigns2 = 'abcde0123456789!@#$%^&*';
+ expect(generateRandomString(length2, availableSigns2)).toMatch(
+ new RegExp(`^[${availableSigns2}]{${length2.toString()}}$`),
+ );
+
+ const length3 = 10;
+ const availableSigns3 = 'a';
+ expect(generateRandomString(length3, availableSigns3)).toMatch(
+ new RegExp(`^[${availableSigns3}]{${length3.toString()}}$`),
+ );
+ });
+
+ it('should throw an error when length is negative', () => {
+ expect(() => generateRandomString(-1, 'abc')).toThrow();
+ expect(() => generateRandomString(-1, '')).toThrow();
+ expect(() => generateRandomString(-123, 'abc')).toThrow();
+ expect(() => generateRandomString(-123, '')).toThrow();
+ });
+
+ it('should throw an error when availableSigns is an empty string, and length is positive', () => {
+ expect(() => generateRandomString(1, '')).toThrow();
+ expect(() => generateRandomString(10, '')).toThrow();
+ });
+
+ it('should throw an error when avaliableSigns length exceeds 256', () => {
+ const longString =
+ '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
+ '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789' +
+ '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789';
+ expect(() => generateRandomString(1, longString)).toThrow();
+ });
+});

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 27, 11:43 PM (21 h, 18 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2592113
Default Alt Text
D4731.id15299.diff (3 KB)

Event Timeline