marsdb
Version:
MarsDB is a lightweight client-side MongoDB-like database, Promise based, written in ES6
243 lines (215 loc) • 6.52 kB
JavaScript
import _try from 'fast.js/function/try';
import invariant from 'invariant';
// Intarnals
let _defaultRandomGenerator;
const RANDOM_GENERATOR_TYPE = {
NODE_CRYPTO: 'NODE_CRYPTO',
BROWSER_CRYPTO: 'BROWSER_CRYPTO',
ALEA: 'ALEA',
};
const UNMISTAKABLE_CHARS = '23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz';
const BASE64_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'0123456789-_';
// see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript
// for a full discussion and Alea implementation.
const Alea = function() {
function Mash() {
var n = 0xefc8249d;
var mash = function(data) {
data = data.toString();
for (var i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
var h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
mash.version = 'Mash 0.9';
return mash;
}
return (function(args) {
var s0 = 0;
var s1 = 0;
var s2 = 0;
var c = 1;
if (args.length == 0) {
args = [+new Date()];
}
var mash = Mash();
s0 = mash(' ');
s1 = mash(' ');
s2 = mash(' ');
for (var i = 0; i < args.length; i++) {
s0 -= mash(args[i]);
if (s0 < 0) {
s0 += 1;
}
s1 -= mash(args[i]);
if (s1 < 0) {
s1 += 1;
}
s2 -= mash(args[i]);
if (s2 < 0) {
s2 += 1;
}
}
mash = null;
var random = function() {
var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
s0 = s1;
s1 = s2;
return s2 = t - (c = t | 0);
};
random.uint32 = function() {
return random() * 0x100000000; // 2^32
};
random.fract53 = function() {
return random() +
(random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
};
random.version = 'Alea 0.9';
random.args = args;
return random;
}(Array.prototype.slice.call(arguments)));
};
/**
* Create seeds array for a browser based on window sizes,
* Date and some random number.
* @return {Arrat}
*/
export function _getBrowserSeeds() {
var height = (typeof window !== 'undefined' && window.innerHeight) ||
(typeof document !== 'undefined'
&& document.documentElement
&& document.documentElement.clientHeight) ||
(typeof document !== 'undefined'
&& document.body
&& document.body.clientHeight) ||
1;
var width = (typeof window !== 'undefined' && window.innerWidth) ||
(typeof document !== 'undefined'
&& document.documentElement
&& document.documentElement.clientWidth) ||
(typeof document !== 'undefined'
&& document.body
&& document.body.clientWidth) ||
1;
var agent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
return [new Date(), height, width, agent, Math.random()];
}
/**
* Random string generator copied from Meteor
* with minor modifications and refactoring.
*/
export default class Random {
constructor(type, options = {}) {
this.type = type;
invariant(
RANDOM_GENERATOR_TYPE[type],
'Random(...): no generator type %s',
type
);
if (type === RANDOM_GENERATOR_TYPE.ALEA) {
invariant(
options.seeds,
'Random(...): seed is not provided for ALEA seeded generator'
);
this.alea = Alea.apply(null, options.seeds);
}
}
fraction() {
if (this.type === RANDOM_GENERATOR_TYPE.ALEA) {
return this.alea();
} else if (this.type === RANDOM_GENERATOR_TYPE.NODE_CRYPTO) {
const numerator = parseInt(this.hexString(8), 16);
return numerator * 2.3283064365386963e-10; // 2^-32
} else if (this.type === RANDOM_GENERATOR_TYPE.BROWSER_CRYPTO) {
const array = new Uint32Array(1);
window.crypto.getRandomValues(array);
return array[0] * 2.3283064365386963e-10; // 2^-32
} else {
throw new Error('Unknown random generator type: ' + this.type);
}
}
hexString(digits) {
if (this.type === RANDOM_GENERATOR_TYPE.NODE_CRYPTO) {
const nodeCrypto = require('crypto');
const numBytes = Math.ceil(digits / 2);
// Try to get cryptographically strong randomness. Fall back to
// non-cryptographically strong if not available.
let bytes = _try(() => nodeCrypto.randomBytes(numBytes));
if (bytes instanceof Error) {
bytes = nodeCrypto.pseudoRandomBytes(numBytes);
}
const result = bytes.toString('hex');
// If the number of digits is odd, we'll have generated an extra 4 bits
// of randomness, so we need to trim the last digit.
return result.substring(0, digits);
} else {
return this._randomString(digits, '0123456789abcdef');
}
}
_randomString(charsCount, alphabet) {
const digits = [];
for (let i = 0; i < charsCount; i++) {
digits[i] = this.choice(alphabet);
}
return digits.join('');
}
id(charsCount) {
// 17 characters is around 96 bits of entropy, which is the amount of
// state in the Alea PRNG.
if (charsCount === undefined) {
charsCount = 17;
}
return this._randomString(charsCount, UNMISTAKABLE_CHARS);
}
secret(charsCount) {
// Default to 256 bits of entropy, or 43 characters at 6 bits per
// character.
if (charsCount === undefined) {
charsCount = 43;
}
return this._randomString(charsCount, BASE64_CHARS);
}
choice(arrayOrString) {
const index = Math.floor(this.fraction() * arrayOrString.length);
if (typeof arrayOrString === 'string') {
return arrayOrString.substr(index, 1);
} else {
return arrayOrString[index];
}
}
static default() {
if (!_defaultRandomGenerator) {
if (typeof window !== 'undefined') {
if (window.crypto && window.crypto.getRandomValues) {
return new Random(RANDOM_GENERATOR_TYPE.BROWSER_CRYPTO);
} else {
return new Random(
RANDOM_GENERATOR_TYPE.ALEA,
{ seeds: _getBrowserSeeds() }
);
}
} else {
return new Random(RANDOM_GENERATOR_TYPE.NODE_CRYPTO);
}
}
return _defaultRandomGenerator;
}
static createWithSeeds() {
invariant(
arguments.length,
'Random.createWithSeeds(...): no seeds were provided'
);
return new Random(
RANDOM_GENERATOR_TYPE.ALEA,
{ seeds: arguments }
);
}
}