diff --git a/admin/spark-standalone.sh b/admin/spark-standalone.sh index 7392ff7305..eb248cd040 100755 --- a/admin/spark-standalone.sh +++ b/admin/spark-standalone.sh @@ -9,7 +9,7 @@ PACKAGES_DIR=`dirname $0`/../packages echo 'Meteor = {};' cat $PACKAGES_DIR/underscore/underscore.js cat $PACKAGES_DIR/logging/logging.js -cat $PACKAGES_DIR/uuid/uuid.js +cat $PACKAGES_DIR/random/random.js cat $PACKAGES_DIR/deps/deps.js cat $PACKAGES_DIR/deps/deps-utils.js cat $PACKAGES_DIR/liverange/liverange.js diff --git a/docs/client/api.html b/docs/client/api.html index 70fd14c291..12d79e84b6 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -1034,25 +1034,6 @@ Example: // After five seconds, stop keeping the count. setTimeout(function () {handle.stop();}, 5000); -{{> api_box id}} - -This returns a random text string such as ``"Jjwjg6gouWLXhMGKW"`` -that is likely to be unique in the whole world. - -Currently Meteor uses this function to generate `_id` fields for new Mongo -documents by default. If your database requires native binary Mongo IDs instead, -pass `"MONGO"` as the `idGeneration` option to -[`Meteor.Collection`](#meteor_collection). - - -{{#note}} - -In the current implementation, the returned string is derived from a -pseudorandom number generator. The IDs generated definitely are not random -enough to be used for cryptographic or security purposes. - -{{/note}} - {{> api_box collection_object_id}} `Meteor.Collection.ObjectID` follows the same API as the [Node MongoDB driver @@ -1500,7 +1481,7 @@ Example: // Support for playing D&D: Roll 3d6 for dexterity Accounts.onCreateUser(function(options, user) { - var d6 = function () { return Math.floor(Math.random() * 6) + 1; }; + var d6 = function () { return Math.floor(Random.fraction() * 6) + 1; }; user.dexterity = d6() + d6() + d6(); // We still want the default hook's 'profile' behavior. if (options.profile) diff --git a/docs/client/api.js b/docs/client/api.js index 00c7dfb5a8..5f2ba48dda 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -684,7 +684,7 @@ Template.api.cursor_observe_changes = { Template.api.id = { id: "meteor_id", - name: "Meteor.id()", + name: "Random.id()", locus: "Anywhere", descr: ["Return a unique identifier."], args: [ ] diff --git a/docs/client/docs.js b/docs/client/docs.js index 4dee50fc1e..6587d7fcd4 100644 --- a/docs/client/docs.js +++ b/docs/client/docs.js @@ -151,7 +151,6 @@ var toc = [ {instance: "cursor", name: "observeChanges", id: "observe_changes"} ], {type: "spacer"}, - "Meteor.id", {name: "Meteor.Collection.ObjectID", id: "collection_object_id"}, {type: "spacer"}, {name: "Selectors", style: "noncode"}, @@ -295,6 +294,7 @@ var toc = [ "force-ssl", "jquery", "less", + "random", "spiderable", "stylus", "showdown", diff --git a/docs/client/packages.html b/docs/client/packages.html index 6cf66ae559..adfd08f2b8 100644 --- a/docs/client/packages.html +++ b/docs/client/packages.html @@ -25,6 +25,7 @@ and removed with: {{> pkg_force_ssl}} {{> pkg_jquery}} {{> pkg_less}} +{{> pkg_random}} {{> pkg_spiderable}} {{> pkg_stylus}} {{> pkg_showdown}} diff --git a/docs/client/packages/random.html b/docs/client/packages/random.html new file mode 100644 index 0000000000..4ad9c75c58 --- /dev/null +++ b/docs/client/packages/random.html @@ -0,0 +1,34 @@ + diff --git a/examples/leaderboard/.meteor/packages b/examples/leaderboard/.meteor/packages index 2ca3c152a4..bd558a2ce0 100644 --- a/examples/leaderboard/.meteor/packages +++ b/examples/leaderboard/.meteor/packages @@ -6,3 +6,4 @@ autopublish insecure preserve-inputs +random diff --git a/examples/leaderboard/leaderboard.js b/examples/leaderboard/leaderboard.js index d75006046c..1013e28189 100644 --- a/examples/leaderboard/leaderboard.js +++ b/examples/leaderboard/leaderboard.js @@ -41,7 +41,7 @@ if (Meteor.isServer) { "Nikola Tesla", "Claude Shannon"]; for (var i = 0; i < names.length; i++) - Players.insert({name: names[i], score: Math.floor(Math.random()*10)*5}); + Players.insert({name: names[i], score: Math.floor(Random.fraction()*10)*5}); } }); } diff --git a/examples/other/quiescence/.meteor/packages b/examples/other/quiescence/.meteor/packages index b205e40610..19052c13e7 100644 --- a/examples/other/quiescence/.meteor/packages +++ b/examples/other/quiescence/.meteor/packages @@ -5,3 +5,4 @@ insecure preserve-inputs +random diff --git a/examples/other/quiescence/quiescence.js b/examples/other/quiescence/quiescence.js index 46a1f6614a..a86cec8fe5 100644 --- a/examples/other/quiescence/quiescence.js +++ b/examples/other/quiescence/quiescence.js @@ -69,7 +69,7 @@ if (Meteor.isServer) { }; Template.updated.events({ 'click #update-button': function () { - var num = Math.round(Math.random()*100); + var num = Math.round(Random.fraction()*100); Meteor.call('setMagic', num); } }); diff --git a/examples/other/template-demo/client/template-demo.js b/examples/other/template-demo/client/template-demo.js index 7959a9ca2c..0407ec425f 100644 --- a/examples/other/template-demo/client/template-demo.js +++ b/examples/other/template-demo/client/template-demo.js @@ -150,12 +150,12 @@ Template.circles.events({ Session.set("selectedCircle:" + this.group, evt.currentTarget.id); }, 'click .add': function () { - Circles.insert({x: Meteor.random(), y: Meteor.random(), - r: Meteor.random() * .1 + .02, + Circles.insert({x: Random.fraction(), y: Random.fraction(), + r: Random.fraction() * .1 + .02, color: { - r: Meteor.random(), - g: Meteor.random(), - b: Meteor.random() + r: Random.fraction(), + g: Random.fraction(), + b: Random.fraction() }, group: this.group }); @@ -171,7 +171,7 @@ Template.circles.events({ Circles.find({group: this.group}).forEach(function (r) { Circles.update(r._id, { $set: { - x: Meteor.random(), y: Meteor.random(), r: Meteor.random() * .1 + .02 + x: Random.fraction(), y: Random.fraction(), r: Random.fraction() * .1 + .02 } }); }); diff --git a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js index 28a86e9d74..eb1b306d39 100644 --- a/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js +++ b/examples/unfinished/accounts-ui-viewer/accounts-ui-viewer.js @@ -26,7 +26,7 @@ if (Meteor.isClient) { } else { // make sure we have a username Meteor.users.update(Meteor.userId(), - { $set: { username: Meteor.uuid() }}); + { $set: { username: Random.id() }}); } } } else if (key === "signupFields") { @@ -116,7 +116,7 @@ if (Meteor.isClient) { var fakeLogin = function (callback) { Accounts.createUser( - {username: Meteor.uuid(), + {username: Random.id(), password: "password", profile: { name: "Joe Schmoe" }}, function () { diff --git a/examples/unfinished/benchmark/.meteor/packages b/examples/unfinished/benchmark/.meteor/packages index 0d8b60823c..233dc66a33 100644 --- a/examples/unfinished/benchmark/.meteor/packages +++ b/examples/unfinished/benchmark/.meteor/packages @@ -6,3 +6,4 @@ insecure preserve-inputs bootstrap +random diff --git a/examples/unfinished/benchmark/benchmark.js b/examples/unfinished/benchmark/benchmark.js index 6f323a5def..db76523e47 100644 --- a/examples/unfinished/benchmark/benchmark.js +++ b/examples/unfinished/benchmark/benchmark.js @@ -16,7 +16,7 @@ if (Meteor.isServer) { ////////////////////////////// var random = function (n) { - return Math.floor(Math.random() * n); + return Math.floor(Random.fraction() * n); }; var randomChars = @@ -25,13 +25,13 @@ var randomString = function (length) { // XXX make more efficient var ret = ''; _.times(length, function () { - ret += randomChars[random(randomChars.length)]; + ret += Random.choice(randomChars); }); return ret; }; var pickCollection = function () { - return Collections[random(Collections.length)]; + return Random.choice(Collections); }; var generateDoc = function () { @@ -123,7 +123,7 @@ if (Meteor.isClient) { Meteor.setInterval(function () { var C = pickCollection(); var docs = C.find({}).fetch(); - var doc = docs[random(docs.length)]; + var doc = Random.choice(docs); if (doc) C.remove(doc._id); }, 1000 / PARAMS.removesPerSecond); @@ -133,7 +133,7 @@ if (Meteor.isClient) { Meteor.setInterval(function () { var C = pickCollection(); var docs = C.find({}).fetch(); - var doc = docs[random(docs.length)]; + var doc = Random.choice(docs); if (doc) { var field = 'Field' + random(PARAMS.documentNumFields); var modifer = {}; diff --git a/examples/wordplay/model.js b/examples/wordplay/model.js index 04b0188abf..7aca086fec 100644 --- a/examples/wordplay/model.js +++ b/examples/wordplay/model.js @@ -44,7 +44,7 @@ var new_board = function () { // pick random letter from each die for (i = 0; i < 16; i += 1) { - board[i] = DICE[i].split('')[Math.floor(Math.random() * 6)]; + board[i] = Random.choice(DICE[i]); } // knuth shuffle diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index d38777c801..3e621979e0 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -57,7 +57,7 @@ // support reconnecting using a meteor login token Accounts._generateStampedLoginToken = function () { - return {token: Meteor.uuid(), when: +(new Date)}; + return {token: Random.id(), when: +(new Date)}; }; Accounts.registerLoginHandler(function(options) { diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 01ba4e1a21..fc0759acf4 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -8,7 +8,7 @@ Tinytest.add('accounts - config validates keys', function (test) { }); Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', function (test) { - var facebookId = Meteor.uuid(); + var facebookId = Random.id(); // create an account with facebook var uid1 = Accounts.updateOrCreateUserFromExternalService( @@ -38,8 +38,8 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', func }); Tinytest.add('accounts - updateOrCreateUserFromExternalService - Weibo', function (test) { - var weiboId1 = Meteor.uuid(); - var weiboId2 = Meteor.uuid(); + var weiboId1 = Random.id(); + var weiboId2 = Random.id(); // users that have different service ids get different users uid1 = Accounts.updateOrCreateUserFromExternalService( @@ -85,7 +85,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Twitter', funct Tinytest.add('accounts - insertUserDoc username', function (test) { var userIn = { - username: Meteor.uuid() + username: Random.id() }; // user does not already exist. create a user object with fields set. @@ -113,9 +113,9 @@ Tinytest.add('accounts - insertUserDoc username', function (test) { }); Tinytest.add('accounts - insertUserDoc email', function (test) { - var email1 = Meteor.uuid(); - var email2 = Meteor.uuid(); - var email3 = Meteor.uuid(); + var email1 = Random.id(); + var email2 = Random.id(); + var email3 = Random.id(); var userIn = { emails: [{address: email1, verified: false}, {address: email2, verified: true}] diff --git a/packages/accounts-base/localstorage_token.js b/packages/accounts-base/localstorage_token.js index 9ee0a61fda..7446b7aa3d 100644 --- a/packages/accounts-base/localstorage_token.js +++ b/packages/accounts-base/localstorage_token.js @@ -7,8 +7,8 @@ // logging in and out, to protect multiple tabs running the same tests // simultaneously from interfering with each others' localStorage. Accounts._isolateLoginTokenForTest = function () { - loginTokenKey = loginTokenKey + Meteor.uuid(); - userIdKey = userIdKey + Meteor.uuid(); + loginTokenKey = loginTokenKey + Random.id(); + userIdKey = userIdKey + Random.id(); }; Accounts._storeLoginToken = function(userId, token) { diff --git a/packages/accounts-facebook/facebook_client.js b/packages/accounts-facebook/facebook_client.js index caca4c0961..42f462276f 100644 --- a/packages/accounts-facebook/facebook_client.js +++ b/packages/accounts-facebook/facebook_client.js @@ -12,7 +12,7 @@ return; } - var state = Meteor.uuid(); + var state = Random.id(); var mobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent); var display = mobile ? 'touch' : 'popup'; diff --git a/packages/accounts-github/github_client.js b/packages/accounts-github/github_client.js index c16db9680d..9943b5689a 100644 --- a/packages/accounts-github/github_client.js +++ b/packages/accounts-github/github_client.js @@ -11,7 +11,7 @@ callback && callback(new Accounts.ConfigError("Service not configured")); return; } - var state = Meteor.uuid(); + var state = Random.id(); var scope = (options && options.requestPermissions) || []; var flatScope = _.map(scope, encodeURIComponent).join('+'); diff --git a/packages/accounts-google/google_client.js b/packages/accounts-google/google_client.js index bfcd83f605..b4c68d6f56 100644 --- a/packages/accounts-google/google_client.js +++ b/packages/accounts-google/google_client.js @@ -12,7 +12,7 @@ return; } - var state = Meteor.uuid(); + var state = Random.id(); // always need this to get user id from google. var requiredScope = ['https://www.googleapis.com/auth/userinfo.profile']; diff --git a/packages/accounts-oauth1-helper/oauth1_binding.js b/packages/accounts-oauth1-helper/oauth1_binding.js index 0497dbe46d..70d24c4b35 100644 --- a/packages/accounts-oauth1-helper/oauth1_binding.js +++ b/packages/accounts-oauth1-helper/oauth1_binding.js @@ -78,7 +78,7 @@ OAuth1Binding.prototype._buildHeader = function(headers) { var self = this; return _.extend({ oauth_consumer_key: self._consumerKey, - oauth_nonce: Meteor.uuid().replace(/\W/g, ''), + oauth_nonce: Random.id().replace(/\W/g, ''), oauth_signature_method: 'HMAC-SHA1', oauth_timestamp: (new Date().valueOf()/1000).toFixed().toString(), oauth_version: '1.0' diff --git a/packages/accounts-oauth1-helper/oauth1_tests.js b/packages/accounts-oauth1-helper/oauth1_tests.js index d0688ebb89..88c5cc90b5 100644 --- a/packages/accounts-oauth1-helper/oauth1_tests.js +++ b/packages/accounts-oauth1-helper/oauth1_tests.js @@ -1,11 +1,11 @@ Tinytest.add("oauth1 - loginResultForState is stored", function (test) { var http = __meteor_bootstrap__.require('http'); - var twitterfooId = Meteor.uuid(); - var twitterfooName = 'nickname' + Meteor.uuid(); - var twitterfooAccessToken = Meteor.uuid(); - var twitterfooAccessTokenSecret = Meteor.uuid(); - var state = Meteor.uuid(); + var twitterfooId = Random.id(); + var twitterfooName = 'nickname' + Random.id(); + var twitterfooAccessToken = Random.id(); + var twitterfooAccessTokenSecret = Random.id(); + var state = Random.id(); OAuth1Binding.prototype.prepareRequestToken = function() {}; OAuth1Binding.prototype.prepareAccessToken = function() { @@ -70,11 +70,11 @@ Tinytest.add("oauth1 - loginResultForState is stored", function (test) { Tinytest.add("oauth1 - error in user creation", function (test) { var http = __meteor_bootstrap__.require('http'); - var state = Meteor.uuid(); - var twitterfailId = Meteor.uuid(); - var twitterfailName = 'nickname' + Meteor.uuid(); - var twitterfailAccessToken = Meteor.uuid(); - var twitterfailAccessTokenSecret = Meteor.uuid(); + var state = Random.id(); + var twitterfailId = Random.id(); + var twitterfailName = 'nickname' + Random.id(); + var twitterfailAccessToken = Random.id(); + var twitterfailAccessTokenSecret = Random.id(); if (!Accounts.loginServiceConfiguration.findOne({service: 'twitterfail'})) Accounts.loginServiceConfiguration.insert({service: 'twitterfail'}); diff --git a/packages/accounts-oauth2-helper/oauth2_tests.js b/packages/accounts-oauth2-helper/oauth2_tests.js index 206e509669..3d679b6a7a 100644 --- a/packages/accounts-oauth2-helper/oauth2_tests.js +++ b/packages/accounts-oauth2-helper/oauth2_tests.js @@ -1,7 +1,7 @@ Tinytest.add("oauth2 - loginResultForState is stored", function (test) { var http = __meteor_bootstrap__.require('http'); - var foobookId = Meteor.uuid(); - var state = Meteor.uuid(); + var foobookId = Random.id(); + var state = Random.id(); if (!Accounts.loginServiceConfiguration.findOne({service: 'foobook'})) Accounts.loginServiceConfiguration.insert({service: 'foobook'}); @@ -42,8 +42,8 @@ Tinytest.add("oauth2 - loginResultForState is stored", function (test) { Tinytest.add("oauth2 - error in user creation", function (test) { var http = __meteor_bootstrap__.require('http'); - var state = Meteor.uuid(); - var failbookId = Meteor.uuid(); + var state = Random.id(); + var failbookId = Random.id(); if (!Accounts.loginServiceConfiguration.findOne({service: 'failbook'})) Accounts.loginServiceConfiguration.insert({service: 'failbook'}); diff --git a/packages/accounts-password/email_tests.js b/packages/accounts-password/email_tests.js index 97d555e749..5798518259 100644 --- a/packages/accounts-password/email_tests.js +++ b/packages/accounts-password/email_tests.js @@ -15,7 +15,7 @@ testAsyncMulti("accounts emails - reset password flow", [ function (test, expect) { - email1 = Meteor.uuid() + "-intercept@example.com"; + email1 = Random.id() + "-intercept@example.com"; Accounts.createUser({email: email1, password: 'foobar'}, expect(function (error) { test.equal(error, undefined); @@ -87,8 +87,8 @@ testAsyncMulti("accounts emails - verify email flow", [ function (test, expect) { - email2 = Meteor.uuid() + "-intercept@example.com"; - email3 = Meteor.uuid() + "-intercept@example.com"; + email2 = Random.id() + "-intercept@example.com"; + email3 = Random.id() + "-intercept@example.com"; Accounts.createUser( {email: email2, password: 'foobar'}, loggedIn(test, expect)); @@ -171,7 +171,7 @@ testAsyncMulti("accounts emails - enroll account flow", [ function (test, expect) { - email4 = Meteor.uuid() + "-intercept@example.com"; + email4 = Random.id() + "-intercept@example.com"; Meteor.call("createUserOnServer", email4, expect(function (error, result) { test.isFalse(error); diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 968df3e5ec..f48e2de906 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -23,8 +23,8 @@ // user: either {username: (username)}, {email: (email)}, or {id: (userId)} // A: hex encoded int. the client's public key for this exchange // @returns {Object} with fields: - // identiy: string uuid - // salt: string uuid + // identity: random string ID + // salt: random string ID // B: hex encoded int. server's public key for this exchange beginPasswordExchange: function (request) { var selector = selectorFromUserQuery(request.user); @@ -191,7 +191,7 @@ if (!email || !_.contains(_.pluck(user.emails || [], 'address'), email)) throw new Error("No such email for user."); - var token = Meteor.uuid(); + var token = Random.id(); var when = +(new Date); Meteor.users.update(userId, {$set: { "services.password.reset": { @@ -233,7 +233,7 @@ var tokenRecord = { - token: Meteor.uuid(), + token: Random.id(), address: address, when: +(new Date)}; Meteor.users.update( @@ -268,7 +268,7 @@ throw new Error("No such email for user."); - var token = Meteor.uuid(); + var token = Random.id(); var when = +(new Date); Meteor.users.update(userId, {$set: { "services.password.reset": { diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index 4f79bb04ab..f5ad41eaf0 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -28,11 +28,11 @@ if (Meteor.isClient) (function () { testAsyncMulti("passwords - long series", [ function (test, expect) { - username = Meteor.uuid(); - username2 = Meteor.uuid(); - username3 = Meteor.uuid(); + username = Random.id(); + username2 = Random.id(); + username3 = Random.id(); // use -intercept so that we don't print to the console - email = Meteor.uuid() + '-intercept@example.com'; + email = Random.id() + '-intercept@example.com'; password = 'password'; password2 = 'password2'; password3 = 'password3'; @@ -227,7 +227,7 @@ if (Meteor.isServer) (function () { Tinytest.add( 'passwords - createUser hooks', function (test) { - var email = Meteor.uuid() + '@example.com'; + var email = Random.id() + '@example.com'; test.throws(function () { // should fail the new user validators Accounts.createUser({email: email, profile: {invalid: true}}); @@ -249,7 +249,7 @@ if (Meteor.isServer) (function () { Tinytest.add( 'passwords - setPassword', function (test) { - var username = Meteor.uuid(); + var username = Random.id(); var userId = Accounts.createUser({username: username}); diff --git a/packages/accounts-twitter/twitter_client.js b/packages/accounts-twitter/twitter_client.js index a9fd891a17..0d96ddcf9b 100644 --- a/packages/accounts-twitter/twitter_client.js +++ b/packages/accounts-twitter/twitter_client.js @@ -13,7 +13,7 @@ return; } - var state = Meteor.uuid(); + var state = Random.id(); // We need to keep state across the next two 'steps' so we're adding // a state parameter to the url and the callback url that we'll be returned // to by oauth provider diff --git a/packages/accounts-weibo/weibo_client.js b/packages/accounts-weibo/weibo_client.js index a4b5258383..f5d221e143 100644 --- a/packages/accounts-weibo/weibo_client.js +++ b/packages/accounts-weibo/weibo_client.js @@ -13,7 +13,7 @@ return; } - var state = Meteor.uuid(); + var state = Random.id(); // XXX need to support configuring access_type and scope var loginUrl = 'https://api.weibo.com/oauth2/authorize' + diff --git a/packages/http/httpcall_tests.js b/packages/http/httpcall_tests.js index 55348f075f..4e5d99cdac 100644 --- a/packages/http/httpcall_tests.js +++ b/packages/http/httpcall_tests.js @@ -98,7 +98,7 @@ testAsyncMulti("httpcall - timeout", [ // Should time out Meteor.http.call( - "GET", url_prefix()+"/slow-"+Meteor.uuid(), + "GET", url_prefix()+"/slow-"+Random.id(), { timeout: 500 }, expect(function(error, result) { test.isTrue(error); @@ -107,7 +107,7 @@ testAsyncMulti("httpcall - timeout", [ // Should not time out Meteor.http.call( - "GET", url_prefix()+"/foo-"+Meteor.uuid(), + "GET", url_prefix()+"/foo-"+Random.id(), { timeout: 2000 }, expect(function(error, result) { test.isFalse(error); @@ -254,7 +254,7 @@ testAsyncMulti("httpcall - http auth", [ // uses cached credentials even if we supply different ones: // https://bugzilla.mozilla.org/show_bug.cgi?id=654348 var password = 'rocks'; - //var password = Meteor.uuid().replace(/[^0-9a-zA-Z]/g, ''); + //var password = Random.id().replace(/[^0-9a-zA-Z]/g, ''); Meteor.http.call( "GET", url_prefix()+"/login?"+password, { auth: "meteor:"+password }, diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index c7b4020b5c..9d62beed59 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -469,7 +469,7 @@ _.extend(Meteor._LivedataConnection.prototype, { } } else { // New sub! Generate an id, save it locally, and send message. - id = Meteor.uuid(); + id = Random.id(); self._subscriptions[id] = { id: id, name: name, diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js index c5e2f571c6..d3ae5187b2 100644 --- a/packages/livedata/livedata_connection_tests.js +++ b/packages/livedata/livedata_connection_tests.js @@ -74,7 +74,7 @@ Tinytest.add("livedata stub - receive data", function (test) { startAndConnect(test, stream); // data comes in for unknown collection. - var coll_name = Meteor.uuid(); + var coll_name = Random.id(); stream.receive({msg: 'added', collection: coll_name, id: '1234', fields: {a: 1}}); // break throught the black box and test internal state @@ -294,7 +294,7 @@ Tinytest.add("livedata stub - methods", function (test) { startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); // setup method @@ -436,7 +436,7 @@ Tinytest.add("livedata stub - methods calling methods", function (test) { startAndConnect(test, stream); - var coll_name = Meteor.uuid(); + var coll_name = Random.id(); var coll = new Meteor.Collection(coll_name, {manager: conn}); // setup methods @@ -521,7 +521,7 @@ Tinytest.add("livedata stub - reconnect", function (test) { startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); var o = observeCursor(test, coll.find()); @@ -646,7 +646,7 @@ Tinytest.add("livedata stub - reconnect method which only got result", function var conn = newConnection(stream); startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); var o = observeCursor(test, coll.find()); @@ -819,7 +819,7 @@ Tinytest.add("livedata stub - reconnect method which only got data", function (t var conn = newConnection(stream); startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); var o = observeCursor(test, coll.find()); @@ -906,7 +906,7 @@ Tinytest.add("livedata stub - multiple stubs same doc", function (test) { var conn = newConnection(stream); startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); var o = observeCursor(test, coll.find()); @@ -991,7 +991,7 @@ Tinytest.add("livedata stub - unsent methods don't block quiescence", function ( var conn = newConnection(stream); startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); conn.methods({ @@ -1067,7 +1067,7 @@ Tinytest.add("livedata connection - two wait methods", function (test) { var conn = newConnection(stream); startAndConnect(test, stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); // setup method @@ -1320,7 +1320,7 @@ Tinytest.add("livedata connection - onReconnect with sent messages", function(te params: ['login'], id: '*'}); // we connect. - stream.receive({msg: 'connected', session: Meteor.uuid()}); + stream.receive({msg: 'connected', session: Random.id()}); test.length(stream.sent, 0); // login got result (but not yet data) @@ -1446,7 +1446,7 @@ Tinytest.add("livedata stub - stubs before connected", function (test) { var stream = new Meteor._StubStream(); var conn = newConnection(stream); - var collName = Meteor.uuid(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); // Start and send "connect", but DON'T get 'connected' quite yet. diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index ab969be6af..4409df1f62 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -201,7 +201,7 @@ _.extend(Meteor._SessionCollectionView.prototype, { Meteor._LivedataSession = function (server, version) { var self = this; - self.id = Meteor.uuid(); + self.id = Random.id(); self.server = server; self.version = version; @@ -772,7 +772,7 @@ Meteor._LivedataSubscription = function ( if (self._subscriptionId) { self._subscriptionHandle = 'N' + self._subscriptionId; } else { - self._subscriptionHandle = 'U' + Meteor.id(); + self._subscriptionHandle = 'U' + Random.id(); } // has _deactivate been called? diff --git a/packages/livedata/livedata_test_service.js b/packages/livedata/livedata_test_service.js index 740af62335..322cc5b504 100644 --- a/packages/livedata/livedata_test_service.js +++ b/packages/livedata/livedata_test_service.js @@ -244,7 +244,7 @@ if (Meteor.isServer) { // First add a random item, which should be cleaned up. We use ready/onReady // to make sure that the second test block is only called after the added is // processed, so that there's any chance of the coll.find().count() failing. - sub.added(collName, Meteor.id(), {foo: 42}); + sub.added(collName, Random.id(), {foo: 42}); sub.ready(); if (options.stopInHandler) { diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js index 62c73bbac6..c10d87646e 100644 --- a/packages/livedata/livedata_tests.js +++ b/packages/livedata/livedata_tests.js @@ -45,7 +45,7 @@ if (Meteor.isServer) { } Tinytest.add("livedata - methods with colliding names", function (test) { - var x = LocalCollection.uuid(); + var x = Random.id(); var m = {}; m[x] = function () {}; Meteor.methods(m); @@ -137,7 +137,7 @@ testAsyncMulti("livedata - basic method invocation", [ function (test, expect) { if (Meteor.isClient) { // For test isolation - var token = Meteor.uuid(); + var token = Random.id(); Meteor.apply( "delayedTrue", [token], {wait: false}, expect(function(err, res) { test.equal(res, false); @@ -149,7 +149,7 @@ testAsyncMulti("livedata - basic method invocation", [ // test that `wait: true` is respected function(test, expect) { if (Meteor.isClient) { - var token = Meteor.uuid(); + var token = Random.id(); Meteor.apply( "delayedTrue", [token], {wait: true}, expect(function(err, res) { test.equal(res, true); @@ -378,7 +378,7 @@ if (Meteor.isClient) { undoEavesdrop(); }); }, function(test, expect) { - var key = Meteor.uuid(); + var key = Random.id(); Meteor.subscribe("recordUserIdOnStop", key); Meteor.apply("setUserId", [100], {wait: true}, expect(function () {})); Meteor.apply("setUserId", [101], {wait: true}, expect(function () {})); @@ -401,7 +401,7 @@ if (Meteor.isClient) { testAsyncMulti("livedata - overlapping universal subs", [ function (test, expect) { var coll = new Meteor.Collection("overlappingUniversalSubs"); - var token = Meteor.uuid(); + var token = Random.id(); test.isFalse(coll.findOne(token)); Meteor.call("testOverlappingSubs", token, expect(function (err) { test.isFalse(err); @@ -413,7 +413,7 @@ if (Meteor.isClient) { testAsyncMulti("livedata - runtime universal sub creation", [ function (test, expect) { var coll = new Meteor.Collection("runtimeSubCreation"); - var token = Meteor.uuid(); + var token = Random.id(); test.isFalse(coll.findOne(token)); Meteor.call("runtimeUniversalSubCreation", token, expect(function (err) { test.isFalse(err); @@ -436,7 +436,7 @@ if (Meteor.isClient) { // conn._subscriptions is empty. var conn = new Meteor._LivedataConnection('/', {reloadWithOutstanding: true}); - var collName = Meteor.id(); + var collName = Random.id(); var coll = new Meteor.Collection(collName, {manager: conn}); var errorFromRerun; var gotErrorFromStopper = false; diff --git a/packages/livedata/package.js b/packages/livedata/package.js index bfa72cc69b..8563defe91 100644 --- a/packages/livedata/package.js +++ b/packages/livedata/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.use(['stream', 'uuid']); + api.use(['stream', 'random']); api.use(['ejson', 'json', 'underscore', 'deps', 'logging'], ['client', 'server']); // livedata_connection.js uses a Minimongo collection internally to diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 7b52f48c0c..6bbd84a331 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -375,7 +375,7 @@ LocalCollection.prototype.insert = function (doc) { // if you really want to use ObjectIDs, set this global. // Meteor.Collection specifies its own ids and does not use this code. doc._id = LocalCollection._useOID ? new LocalCollection._ObjectID() - : LocalCollection.id(); + : Random.id(); } var id = LocalCollection._idStringify(doc._id); diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 32d10cb4e1..0fe9d0b49d 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -1208,7 +1208,7 @@ Tinytest.add("minimongo - observe ordered", function (test) { // test stop handle.stop(); - var idA2 = LocalCollection.id(); + var idA2 = Random.id(); c.insert({_id: idA2, a:2}); test.equal(operations.shift(), undefined); diff --git a/packages/minimongo/objectid.js b/packages/minimongo/objectid.js index 2cd6682e61..90d259dd11 100644 --- a/packages/minimongo/objectid.js +++ b/packages/minimongo/objectid.js @@ -18,7 +18,7 @@ LocalCollection._ObjectID = function (hexString) { // meant to work with _.isEqual(), which relies on structural equality self._str = hexString; } else { - self._str = LocalCollection._randomHexString(24); + self._str = Random.hexString(24); } }; diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index 7b88f59b3c..c7ee043b58 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -8,11 +8,10 @@ Package.on_use(function (api, where) { // It would be sort of nice if minimongo didn't depend on // underscore, so we could ship it separately. - api.use(['underscore', 'json', 'ejson', 'ordered-dict'], where); + api.use(['underscore', 'json', 'ejson', 'ordered-dict', 'random'], where); api.add_files([ 'minimongo.js', 'selector.js', - 'uuid.js', 'modify.js', 'diff.js', 'objectid.js' diff --git a/packages/minimongo/uuid.js b/packages/minimongo/uuid.js deleted file mode 100644 index dab9dd8fe5..0000000000 --- a/packages/minimongo/uuid.js +++ /dev/null @@ -1,171 +0,0 @@ -// XXX dups packages/uuid/uuid.js - -// LocalCollection.random() -- known good PRNG, replaces Math.random() -// LocalCollection.uuid() -- returns RFC 4122 v4 UUID. - -// see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript -// for a full discussion and Alea implementation. - -// Copyright (C) 2010 by Johannes Baagøe -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, -// modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -(function() { - -var HEX_DIGITS = "0123456789abcdef"; -var UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz"; - -LocalCollection._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))); -} - -// instantiate RNG. Heuristically collect entropy from various sources - -// client sources -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) || ""; - -// server sources -var pid = (typeof process !== 'undefined' && process.pid) || 1; - -LocalCollection.random = new LocalCollection._Alea([ - new Date(), height, width, agent, pid, Math.random()]); - -LocalCollection._randomHexString = function (len) { - var digits = []; - for (var i = 0; i < len; i++) { - digits[i] = HEX_DIGITS.substr(Math.floor(LocalCollection.random() * 0x10), - 1); - } - return digits.join(""); -}; - -// RFC 4122 v4 UUID. -LocalCollection.uuid = function () { - var s = []; - for (var i = 0; i < 36; i++) { - s[i] = HEX_DIGITS.substr(Math.floor(LocalCollection.random() * 0x10), 1); - } - s[14] = "4"; - s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = "-"; - - var uuid = s.join(""); - return uuid; -}; - -LocalCollection.id = function() { - var digits = []; - var base = UNMISTAKABLE_CHARS.length; - // Length of 17 preserves around 96 bits of entropy, which is the - // amount of state in our PRNG - for (var i = 0; i < 17; i++) { - digits[i] = UNMISTAKABLE_CHARS.substr(Math.floor(LocalCollection.random() * base), 1); - } - return digits.join(""); -}; - -})(); diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 11c245645e..45ecd1ae62 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -28,7 +28,7 @@ Meteor.Collection = function (name, options) { case 'STRING': default: self._makeNewID = function () { - return LocalCollection.id(); + return Random.id(); }; break; } @@ -201,7 +201,7 @@ Meteor.Collection._rewriteSelector = function (selector) { if (!selector || (('_id' in selector) && !selector._id)) // can't match anything - return {_id: LocalCollection.id()}; + return {_id: Random.id()}; var ret = {}; _.each(selector, function (value, key) { diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 7fdea4728d..097452c71d 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -229,8 +229,8 @@ Tinytest.addAsync("mongo-livedata - fuzz test, " + idGeneration, function(test, // Use non-deterministic randomness so we can have a shorter fuzz // test (fewer iterations). For deterministic (fully seeded) - // randomness, remove the call to Math.random(). - var seededRandom = new SeededRandom("foobard" + Math.random()); + // randomness, remove the call to Random.fraction(). + var seededRandom = new SeededRandom("foobard" + Random.fraction()); // Random integer in [0,n) var rnd = function (n) { return seededRandom.nextIntBetween(0, n-1); @@ -545,7 +545,7 @@ if (Meteor.isServer) { testAsyncMulti('mongo-livedata - rewrite selector, ' + idGeneration, [ function (test, expect) { - var collectionName = Meteor.uuid(); + var collectionName = Random.id(); if (Meteor.isClient) { Meteor.call('createInsecureCollection', collectionName, collectionOptions); Meteor.subscribe('c-' + collectionName); @@ -583,7 +583,7 @@ testAsyncMulti('mongo-livedata - rewrite selector, ' + idGeneration, [ testAsyncMulti('mongo-livedata - empty documents, ' + idGeneration, [ function (test, expect) { - var collectionName = Meteor.uuid(); + var collectionName = Random.id(); if (Meteor.isClient) { Meteor.call('createInsecureCollection', collectionName); Meteor.subscribe('c-' + collectionName); @@ -604,7 +604,7 @@ testAsyncMulti('mongo-livedata - empty documents, ' + idGeneration, [ testAsyncMulti('mongo-livedata - document with a date, ' + idGeneration, [ function (test, expect) { - var collectionName = Meteor.uuid(); + var collectionName = Random.id(); if (Meteor.isClient) { Meteor.call('createInsecureCollection', collectionName, collectionOptions); Meteor.subscribe('c-' + collectionName); @@ -633,7 +633,7 @@ testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ "dCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdl" + "bmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9y" + "dCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4="); - var collectionName = Meteor.uuid(); + var collectionName = Random.id(); if (Meteor.isClient) { Meteor.call('createInsecureCollection', collectionName, collectionOptions); Meteor.subscribe('c-' + collectionName); @@ -658,7 +658,7 @@ testAsyncMulti('mongo-livedata - document with binary data, ' + idGeneration, [ testAsyncMulti('mongo-livedata - specified _id', [ function (test, expect) { - var collectionName = Meteor.uuid(); + var collectionName = Random.id(); if (Meteor.isClient) { Meteor.call('createInsecureCollection', collectionName); Meteor.subscribe('c-' + collectionName); diff --git a/packages/mongo-livedata/observe_changes_tests.js b/packages/mongo-livedata/observe_changes_tests.js index e1fa9eecf1..908da0f180 100644 --- a/packages/mongo-livedata/observe_changes_tests.js +++ b/packages/mongo-livedata/observe_changes_tests.js @@ -2,7 +2,7 @@ var makeCollection = function () { if (Meteor.isServer) - return new Meteor.Collection(Meteor.id()); + return new Meteor.Collection(Random.id()); else return new Meteor.Collection(null); }; diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js index 47f7ad5114..49a36c5614 100644 --- a/packages/mongo-livedata/package.js +++ b/packages/mongo-livedata/package.js @@ -13,7 +13,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.use(['uuid', 'ejson', 'json', 'underscore', 'minimongo', 'logging', 'livedata'], + api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging', 'livedata'], ['client', 'server']); api.add_files('mongo_driver.js', 'server'); diff --git a/packages/past/past.js b/packages/past/past.js index 033fbcce12..45b9b4fb66 100644 --- a/packages/past/past.js +++ b/packages/past/past.js @@ -11,3 +11,19 @@ Meteor.deps.Context.prototype.on_invalidate = // We used to require a special "autosubscribe" call to reactively subscribe to // things. Now, it works with autorun. Meteor.autosubscribe = Meteor.autorun; + +// Instead of the "random" package with Random.id(), we used to have this +// Meteor.uuid() implementing the RFC 4122 v4 UUID. +Meteor.uuid = function () { + var HEX_DIGITS = "0123456789abcdef"; + var s = []; + for (var i = 0; i < 36; i++) { + s[i] = Random.choice(HEX_DIGITS); + } + s[14] = "4"; + s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = "-"; + + var uuid = s.join(""); + return uuid; +}; diff --git a/packages/uuid/package.js b/packages/random/package.js similarity index 57% rename from packages/uuid/package.js rename to packages/random/package.js index 0cfea8b7d4..18f30588a1 100644 --- a/packages/uuid/package.js +++ b/packages/random/package.js @@ -1,9 +1,9 @@ Package.describe({ - summary: "Better random number and UUIDv4 generators", + summary: "Random number generator and utilities", internal: true }); Package.on_use(function (api, where) { where = where || ['client', 'server']; - api.add_files('uuid.js', where); + api.add_files('random.js', where); }); diff --git a/packages/uuid/uuid.js b/packages/random/random.js similarity index 61% rename from packages/uuid/uuid.js rename to packages/random/random.js index 90854020db..b5f1b43abc 100644 --- a/packages/uuid/uuid.js +++ b/packages/random/random.js @@ -1,38 +1,10 @@ -// XXX dups packages/minimongo/uuid.js +(function() { -// Meteor.random() -- known good PRNG, replaces Math.random() -// Meteor.uuid() -- returns RFC 4122 v4 UUID. +Random = {}; // see http://baagoe.org/en/wiki/Better_random_numbers_for_javascript // for a full discussion and Alea implementation. - -// Copyright (C) 2010 by Johannes Baagøe -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, copy, -// modify, merge, publish, distribute, sublicense, and/or sell copies -// of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -(function() { - -var HEX_DIGITS = "0123456789abcdef"; -var UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz"; - -var Alea = Meteor._Alea = function () { +Random._Alea = function () { function Mash() { var n = 0xefc8249d; @@ -103,7 +75,7 @@ var Alea = Meteor._Alea = function () { return random; } (Array.prototype.slice.call(arguments))); -} +}; // instantiate RNG. Heuristically collect entropy from various sources @@ -131,32 +103,38 @@ var agent = (typeof navigator !== 'undefined' && navigator.userAgent) || ""; // server sources var pid = (typeof process !== 'undefined' && process.pid) || 1; -Meteor.random = new Alea([ +// XXX On the server, use the crypto module (OpenSSL) instead of this PRNG. +// (Make Random.fraction be generated from Random.hexString instead of the +// other way around, and generate Random.hexString from crypto.randomBytes.) +Random.fraction = new Random._Alea([ new Date(), height, width, agent, pid, Math.random()]); -// RFC 4122 v4 UUID. -Meteor.uuid = function () { - var s = []; - for (var i = 0; i < 36; i++) { - s[i] = HEX_DIGITS.substr(Math.floor(Meteor.random() * 0x10), 1); - } - s[14] = "4"; - s[19] = HEX_DIGITS.substr((parseInt(s[19],16) & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = "-"; - - var uuid = s.join(""); - return uuid; +Random.choice = function (arrayOrString) { + var index = Math.floor(Random.fraction() * arrayOrString.length); + if (typeof arrayOrString === "string") + return arrayOrString.substr(index, 1); + else + return arrayOrString[index]; }; -Meteor.id = function() { +var UNMISTAKABLE_CHARS = "23456789ABCDEFGHJKLMNPQRSTWXYZabcdefghijkmnopqrstuvwxyz"; +Random.id = function() { var digits = []; - var base = UNMISTAKABLE_CHARS.length; // Length of 17 preserves around 96 bits of entropy, which is the // amount of state in our PRNG for (var i = 0; i < 17; i++) { - digits[i] = UNMISTAKABLE_CHARS.substr(Math.floor(Meteor.random() * base), 1); + digits[i] = Random.choice(UNMISTAKABLE_CHARS); } return digits.join(""); }; +var HEX_DIGITS = "0123456789abcdef"; +Random.hexString = function (digits) { + var hexDigits = []; + for (var i = 0; i < digits; ++i) { + hexDigits.push(Random.choice("0123456789abcdef")); + } + return hexDigits.join(''); +}; + })(); diff --git a/packages/spark/package.js b/packages/spark/package.js index 189ae6e56e..1383dd5ebc 100644 --- a/packages/spark/package.js +++ b/packages/spark/package.js @@ -4,7 +4,7 @@ Package.describe({ }); Package.on_use(function (api) { - api.use(['underscore', 'uuid', 'domutils', 'liverange', 'universal-events'], + api.use(['underscore', 'random', 'domutils', 'liverange', 'universal-events'], 'client'); api.add_files(['spark.js', 'patch.js', 'convenience.js', diff --git a/packages/spark/spark.js b/packages/spark/spark.js index 5a33b5146a..48533e30fc 100644 --- a/packages/spark/spark.js +++ b/packages/spark/spark.js @@ -44,7 +44,7 @@ Spark._currentRenderer = (function () { }; })(); -Spark._TAG = "_spark_" + Meteor.uuid(); +Spark._TAG = "_spark_" + Random.id(); // XXX document contract for each type of annotation? Spark._ANNOTATION_NOTIFY = "notify"; Spark._ANNOTATION_DATA = "data"; @@ -106,16 +106,6 @@ var withEventGuard = function (func) { finally { eventGuardActive = previous; } }; -Spark._createId = function () { - // Chars can't include '-' to be safe inside HTML comments. - var chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_"; - var id = ""; - for (var i = 0; i < 8; i++) - id += chars.substr(Math.floor(Meteor.random() * 64), 1); - return id; -}; - Spark._Renderer = function () { // Map from annotation ID to an annotation function, which is called // at render time and receives (startNode, endNode). @@ -164,7 +154,7 @@ _.extend(Spark._Renderer.prototype, { // unescaped < and > in HTML attribute values, where they are normally // safe. We can't assume that a string like '<1>' came from us // and not arbitrary user-entered data. - var id = (type || '') + ":" + Spark._createId(); + var id = (type || '') + ":" + Random.id(); this.annotations[id] = function (start, end) { if ((! start) || (! type)) { // ! start: materialize called us with no args because this @@ -1100,7 +1090,7 @@ Spark.labelBranch = function (label, htmlFunc) { return htmlFunc(); if (label === Spark.UNIQUE_LABEL) - label = Spark._createId(); + label = Random.id(); renderer.currentBranch.pushLabel(label); var html = htmlFunc(); diff --git a/packages/spark/spark_tests.js b/packages/spark/spark_tests.js index bf78da9dd0..6e3acdeaa2 100644 --- a/packages/spark/spark_tests.js +++ b/packages/spark/spark_tests.js @@ -1639,8 +1639,8 @@ Tinytest.add("spark - landmark patching", function(test) { for(var i=0; i<5; i++) { // Use non-deterministic randomness so we can have a shorter fuzz // test (fewer iterations). For deterministic (fully seeded) - // randomness, remove the call to Math.random(). - rand = new SeededRandom("preserved nodes "+i+" "+Math.random()); + // randomness, remove the call to Random.fraction(). + rand = new SeededRandom("preserved nodes "+i+" "+Random.fraction()); var R = ReactiveVar(false); var structure = randomNodeList(null, 6); @@ -1878,7 +1878,7 @@ Tinytest.add("spark - leaderboard, " + idGeneration, function(test) { })); var idGen; if (idGeneration === 'STRING') - idGen = LocalCollection.id; + idGen = Random.id; else idGen = function () { return new LocalCollection._ObjectID(); }; @@ -2550,7 +2550,7 @@ testAsyncMulti( // This is quite a tricky implementation. var withIframe = function(onReady1, onReady2) { - var frameName = "submitframe"+String(Math.random()).slice(2); + var frameName = "submitframe"+String(Random.fraction()).slice(2); var iframeDiv = OnscreenDiv( Meteor.render(function() { return '