5 /* global Uint32Array */
10 * Get the current user's groups or rights
13 * @return {jQuery.Promise}
15 function getUserInfo() {
16 if ( !userInfoPromise
) {
17 userInfoPromise
= new mw
.Api().getUserInfo();
19 return userInfoPromise
;
22 // mw.user with the properties options and tokens gets defined in mediawiki.js.
26 * Generate a random user session ID.
28 * This information would potentially be stored in a cookie to identify a user during a
29 * session or series of sessions. Its uniqueness should not be depended on unless the
30 * browser supports the crypto API.
32 * Known problems with Math.random():
33 * Using the Math.random function we have seen sets
34 * with 1% of non uniques among 200,000 values with Safari providing most of these.
35 * Given the prevalence of Safari in mobile the percentage of duplicates in
36 * mobile usages of this code is probably higher.
39 * We need about 64 bits to make sure that probability of collision
40 * on 500 million (5*10^8) is <= 1%
41 * See https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
43 * @return {string} 64 bit integer in hex format, padded
45 generateRandomSessionId: function () {
47 hexRnds
= new Array( 2 ),
49 crypto
= window
.crypto
|| window
.msCrypto
;
51 if ( crypto
&& crypto
.getRandomValues
) {
52 // Fill an array with 2 random values, each of which is 32 bits.
53 // Note that Uint32Array is array-like but does not implement Array.
54 rnds
= new Uint32Array( 2 );
55 crypto
.getRandomValues( rnds
);
58 Math
.floor( Math
.random() * 0x100000000 ),
59 Math
.floor( Math
.random() * 0x100000000 )
62 // Convert number to a string with 16 hex characters
63 for ( i
= 0; i
< 2; i
++ ) {
64 // Add 0x100000000 before converting to hex and strip the extra character
65 // after converting to keep the leading zeros.
66 hexRnds
[ i
] = ( rnds
[ i
] + 0x100000000 ).toString( 16 ).slice( 1 );
69 // Concatenation of two random integers with entropy n and m
70 // returns a string with entropy n+m if those strings are independent
71 return hexRnds
.join( '' );
75 * Get the current user's database id
77 * Not to be confused with #id.
79 * @return {number} Current user's id, or 0 if user is anonymous
82 return mw
.config
.get( 'wgUserId' ) || 0;
86 * Get the current user's name
88 * @return {string|null} User name string or null if user is anonymous
90 getName: function () {
91 return mw
.config
.get( 'wgUserName' );
95 * Get date user registered, if available
97 * @return {boolean|null|Date} False for anonymous users, null if data is
98 * unavailable, or Date for when the user registered.
100 getRegistration: function () {
102 if ( mw
.user
.isAnon() ) {
105 registration
= mw
.config
.get( 'wgUserRegistration' );
106 // Registration may be unavailable if the user signed up before MediaWiki
107 // began tracking this.
108 return !registration
? null : new Date( registration
);
112 * Whether the current user is anonymous
116 isAnon: function () {
117 return mw
.user
.getName() === null;
121 * Get an automatically generated random ID (stored in a session cookie)
123 * This ID is ephemeral for everyone, staying in their browser only until they close
126 * @return {string} Random session ID
128 sessionId: function () {
129 var sessionId
= mw
.cookie
.get( 'mwuser-sessionId' );
130 if ( sessionId
=== null ) {
131 sessionId
= mw
.user
.generateRandomSessionId();
132 mw
.cookie
.set( 'mwuser-sessionId', sessionId
, { expires
: null } );
138 * Get the current user's name or the session ID
140 * Not to be confused with #getId.
142 * @return {string} User name or random session ID
145 return mw
.user
.getName() || mw
.user
.sessionId();
149 * Get the user's bucket (place them in one if not done already)
151 * mw.user.bucket( 'test', {
152 * buckets: { ignored: 50, control: 25, test: 25 },
157 * @deprecated since 1.23
158 * @param {string} key Name of bucket
159 * @param {Object} options Bucket configuration options
160 * @param {Object} options.buckets List of bucket-name/relative-probability pairs (required,
161 * must have at least one pair)
162 * @param {number} [options.version=0] Version of bucket test, changing this forces
164 * @param {number} [options.expires=30] Length of time (in days) until the user gets
166 * @return {string} Bucket name - the randomly chosen key of the `options.buckets` object
168 bucket: function ( key
, options
) {
169 var cookie
, parts
, version
, bucket
,
170 range
, k
, rand
, total
;
172 options
= $.extend( {
178 cookie
= mw
.cookie
.get( 'mwuser-bucket:' + key
);
180 // Bucket information is stored as 2 integers, together as version:bucket like: "1:2"
181 if ( typeof cookie
=== 'string' && cookie
.length
> 2 && cookie
.indexOf( ':' ) !== -1 ) {
182 parts
= cookie
.split( ':' );
183 if ( parts
.length
> 1 && Number( parts
[ 0 ] ) === options
.version
) {
184 version
= Number( parts
[ 0 ] );
185 bucket
= String( parts
[ 1 ] );
189 if ( bucket
=== undefined ) {
190 if ( !$.isPlainObject( options
.buckets
) ) {
191 throw new Error( 'Invalid bucket. Object expected for options.buckets.' );
194 version
= Number( options
.version
);
198 for ( k
in options
.buckets
) {
199 range
+= options
.buckets
[ k
];
202 // Select random value within range
203 rand
= Math
.random() * range
;
205 // Determine which bucket the value landed in
207 for ( k
in options
.buckets
) {
209 total
+= options
.buckets
[ k
];
210 if ( total
>= rand
) {
216 'mwuser-bucket:' + key
,
217 version
+ ':' + bucket
,
218 { expires
: Number( options
.expires
) * 86400 }
226 * Get the current user's groups
228 * @param {Function} [callback]
229 * @return {jQuery.Promise}
231 getGroups: function ( callback
) {
232 var userGroups
= mw
.config
.get( 'wgUserGroups', [] );
234 // Uses promise for backwards compatibility
235 return $.Deferred().resolve( userGroups
).done( callback
);
239 * Get the current user's rights
241 * @param {Function} [callback]
242 * @return {jQuery.Promise}
244 getRights: function ( callback
) {
245 return getUserInfo().then(
246 function ( userInfo
) { return userInfo
.rights
; },
247 function () { return []; }
252 }( mediaWiki
, jQuery
) );