aboutsummaryrefslogtreecommitdiffstats
path: root/resources/src/mediawiki.cookie/index.js
blob: 1c233565e70adde92ef7e739214c2e849e33bf2e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
'use strict';

var config = require( './config.json' ),
	defaults = {
		prefix: config.prefix,
		domain: config.domain,
		path: config.path,
		expires: config.expires,
		secure: false,
		sameSite: '',
		sameSiteLegacy: config.sameSiteLegacy
	},
	jar = require( './jar.js' );

// define jQuery Cookie methods
require( './jquery.js' );

/**
 * Manage cookies in a way that is syntactically and functionally similar
 * to the `WebRequest#getCookie` and `WebResponse#setcookie` methods in PHP.
 * Provided by the mediawiki.cookie ResourceLoader module.
 *
 * @author Sam Smith <samsmith@wikimedia.org>
 * @author Matthew Flaschen <mflaschen@wikimedia.org>
 *
 * @namespace mw.cookie
 * @example
 *   mw.loader.using( 'mediawiki.cookie' ).then( () => {
 *     mw.cookie.set('hello', 'world' );
 *   })
 */
mw.cookie = {
	/**
	 * Set or delete a cookie.
	 *
	 * **Note:** If explicitly passing `null` or `undefined` for an options key,
	 * that will override the default. This is natural in JavaScript, but noted
	 * here because it is contrary to MediaWiki's `WebResponse#setcookie()` method
	 * in PHP.
	 *
	 * When using this for persistent storage of identifiers (e.g. for tracking
	 * sessions), be aware that persistence may vary slightly across browsers and
	 * browser versions, and can be affected by a number of factors such as
	 * storage limits (cookie eviction) and session restore features.
	 *
	 * Without an expiry, this creates a session cookie. In a browser, session cookies persist
	 * for the lifetime of the browser *process*. Including across tabs, page views, and windows,
	 * until the browser itself is *fully* closed, or until the browser clears all storage for
	 * a given website. An exception to this is if the user evokes a "restore previous
	 * session" feature that some browsers have.
	 *
	 * @param {string} key
	 * @param {string|null} value Value of cookie. If `value` is `null` then this method will
	 *   instead remove a cookie by name of `key`.
	 * @param {mw.cookie.CookieOptions|Date|number} [options] Options object, or expiry date
	 */

	set: function ( key, value, options ) {
		var prefix, date, sameSiteLegacy;

		// The 'options' parameter may be a shortcut for the expiry.
		if ( arguments.length > 2 && ( !options || options instanceof Date || typeof options === 'number' ) ) {
			options = { expires: options };
		}
		// Apply defaults
		options = Object.assign( {}, defaults, options );

		// Don't pass invalid option to jar.cookie
		prefix = options.prefix;
		delete options.prefix;

		if ( !options.expires ) {
			// Session cookie (null or zero)
			// Normalize to absent (undefined) for jar.cookie.
			delete options.expires;
		} else if ( typeof options.expires === 'number' ) {
			// Lifetime in seconds
			date = new Date();
			date.setTime( Number( date ) + ( options.expires * 1000 ) );
			options.expires = date;
		}

		sameSiteLegacy = options.sameSiteLegacy;
		delete options.sameSiteLegacy;

		if ( value !== null ) {
			value = String( value );
		}

		jar.cookie( prefix + key, value, options );
		if ( sameSiteLegacy && options.sameSite && options.sameSite.toLowerCase() === 'none' ) {
			// Make testing easy by not changing the object passed to the first jar.cookie call
			options = Object.assign( {}, options );
			delete options.sameSite;
			jar.cookie( prefix + 'ss0-' + key, value, options );
		}
	},

	/**
	 * Get the value of a cookie.
	 *
	 * @param {string} key
	 * @param {string} [prefix=wgCookiePrefix] The prefix of the key. If `prefix` is
	 *   `undefined` or `null`, then `wgCookiePrefix` is used
	 * @param {null|string} [defaultValue] defaults to null
	 * @return {string|null} If the cookie exists, then the value of the
	 *   cookie, otherwise `defaultValue`
	 */
	get: function ( key, prefix, defaultValue ) {
		var result;

		if ( prefix === undefined || prefix === null ) {
			prefix = defaults.prefix;
		}

		// Was defaultValue omitted?
		if ( arguments.length < 3 ) {
			defaultValue = null;
		}

		result = jar.cookie( prefix + key );

		return result !== null ? result : defaultValue;
	},

	/**
	 * Get the value of a SameSite=None cookie, using the legacy ss0- cookie if needed.
	 *
	 * @param {string} key
	 * @param {string} [prefix=wgCookiePrefix] The prefix of the key. If `prefix` is
	 *   `undefined` or `null`, then `wgCookiePrefix` is used
	 * @param {null|string} [defaultValue]
	 * @return {string|null} If the cookie exists, then the value of the
	 *   cookie, otherwise `defaultValue`
	 */
	getCrossSite: function ( key, prefix, defaultValue ) {
		var value;

		value = this.get( key, prefix, null );
		if ( value === null ) {
			value = this.get( 'ss0-' + key, prefix, null );
		}
		if ( value === null ) {
			value = defaultValue;
		}
		return value;
	}
};

if ( window.QUnit ) {
	module.exports = {
		jar,
		setDefaults: function ( value ) {
			var prev = defaults;
			defaults = value;
			return prev;
		}
	};
}