Merge "ApiSandbox: Move labels outside progress bars"
[lhc/web/wiklou.git] / resources / src / mediawiki.user.js
1 /**
2 * @class mw.user
3 * @singleton
4 */
5 /* global Uint16Array */
6 ( function () {
7 var userInfoPromise, pageviewRandomId;
8
9 /**
10 * Get the current user's groups or rights
11 *
12 * @private
13 * @return {jQuery.Promise}
14 */
15 function getUserInfo() {
16 if ( !userInfoPromise ) {
17 userInfoPromise = new mw.Api().getUserInfo();
18 }
19 return userInfoPromise;
20 }
21
22 // mw.user with the properties options and tokens gets defined in mediawiki.js.
23 $.extend( mw.user, {
24
25 /**
26 * Generate a random user session ID.
27 *
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.
31 *
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.
37 *
38 * Rationale:
39 * We need about 80 bits to make sure that probability of collision
40 * on 155 billion is <= 1%
41 *
42 * See https://en.wikipedia.org/wiki/Birthday_attack#Mathematics
43 * n(p;H) = n(0.01,2^80)= sqrt (2 * 2^80 * ln(1/(1-0.01)))
44
45 * @return {string} 80 bit integer in hex format, padded
46 */
47 generateRandomSessionId: function () {
48 var rnds, i,
49 // Support: IE 11
50 crypto = window.crypto || window.msCrypto;
51
52 if ( crypto && crypto.getRandomValues && typeof Uint16Array === 'function' ) {
53 // Fill an array with 5 random values, each of which is 16 bits.
54 // Note that Uint16Array is array-like but does not implement Array.
55 rnds = new Uint16Array( 5 );
56 crypto.getRandomValues( rnds );
57 } else {
58 rnds = new Array( 5 );
59 // 0x10000 is 2^16 so the operation below will return a number
60 // between 2^16 and zero
61 for ( i = 0; i < 5; i++ ) {
62 rnds[ i ] = Math.floor( Math.random() * 0x10000 );
63 }
64 }
65
66 // Convert the 5 16bit-numbers into 20 characters (4 hex per 16 bits).
67 // Concatenation of two random integers with entropy n and m
68 // returns a string with entropy n+m if those strings are independent.
69 // Tested that below code is faster than array + loop + join.
70 return ( rnds[ 0 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
71 ( rnds[ 1 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
72 ( rnds[ 2 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
73 ( rnds[ 3 ] + 0x10000 ).toString( 16 ).slice( 1 ) +
74 ( rnds[ 4 ] + 0x10000 ).toString( 16 ).slice( 1 );
75 },
76
77 /**
78 * A sticky generateRandomSessionId for the current JS execution context,
79 * cached within this class (also known as a page view token).
80 *
81 * @since 1.32
82 * @return {string} 80 bit integer in hex format, padded
83 */
84 getPageviewToken: function () {
85 if ( !pageviewRandomId ) {
86 pageviewRandomId = mw.user.generateRandomSessionId();
87 }
88
89 return pageviewRandomId;
90 },
91
92 /**
93 * Get the current user's database id
94 *
95 * Not to be confused with #id.
96 *
97 * @return {number} Current user's id, or 0 if user is anonymous
98 */
99 getId: function () {
100 return mw.config.get( 'wgUserId' ) || 0;
101 },
102
103 /**
104 * Get the current user's name
105 *
106 * @return {string|null} User name string or null if user is anonymous
107 */
108 getName: function () {
109 return mw.config.get( 'wgUserName' );
110 },
111
112 /**
113 * Get date user registered, if available
114 *
115 * @return {boolean|null|Date} False for anonymous users, null if data is
116 * unavailable, or Date for when the user registered.
117 */
118 getRegistration: function () {
119 var registration;
120 if ( mw.user.isAnon() ) {
121 return false;
122 }
123 registration = mw.config.get( 'wgUserRegistration' );
124 // Registration may be unavailable if the user signed up before MediaWiki
125 // began tracking this.
126 return !registration ? null : new Date( registration );
127 },
128
129 /**
130 * Whether the current user is anonymous
131 *
132 * @return {boolean}
133 */
134 isAnon: function () {
135 return mw.user.getName() === null;
136 },
137
138 /**
139 * Retrieve a random ID persisted in sessionStorage, generating it if needed
140 *
141 * This ID is stored in sessionStorage and persists within a single tab,
142 * including between page views through links and form submissions,
143 * and when going forwards/backwards in browser history, and when restoring
144 * a closed tab, or restoring a closed browser session.
145 *
146 * This is different from session cookies, because it is not shared between
147 * tabs of the same browser. Two simultaneous pageviews in the same browser
148 * can have different session IDs. The ID is also not re-used when opening
149 * a new tab to a website after fully closing others.
150 *
151 * See https://phabricator.wikimedia.org/T118063#4547178 and
152 * https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
153 * for more information.
154 *
155 * @return {string} Random session ID
156 */
157 sessionId: function () {
158 var sessionId = mw.storage.session.get( 'mwuser-sessionId' );
159 if ( !sessionId ) {
160 sessionId = mw.user.generateRandomSessionId();
161 mw.storage.session.set( 'mwuser-sessionId', sessionId );
162 }
163 return sessionId;
164 },
165
166 /**
167 * Get the current user's name or the session ID
168 *
169 * Not to be confused with #getId.
170 *
171 * @return {string} User name or random session ID
172 */
173 id: function () {
174 return mw.user.getName() || mw.user.sessionId();
175 },
176
177 /**
178 * Get the current user's groups
179 *
180 * @param {Function} [callback]
181 * @return {jQuery.Promise}
182 */
183 getGroups: function ( callback ) {
184 var userGroups = mw.config.get( 'wgUserGroups', [] );
185
186 // Uses promise for backwards compatibility
187 return $.Deferred().resolve( userGroups ).then( callback );
188 },
189
190 /**
191 * Get the current user's rights
192 *
193 * @param {Function} [callback]
194 * @return {jQuery.Promise}
195 */
196 getRights: function ( callback ) {
197 return getUserInfo().then(
198 function ( userInfo ) { return userInfo.rights; },
199 function () { return []; }
200 ).then( callback );
201 }
202 } );
203
204 }() );