PageSourceSearch

https://avaaz.org/campaign/static/js/global.js?1787928924

js avaaz.org collected 2026-10-01 09:15:28 UTC 17,773 bytes, 555 lines download raw bytes

1/**
2 * This file used for miscellaneous functions, need entire all all project
3 */
4
5// when using browser native Promises we need a special pattern when the promise is lazily-resolved,
6// eg when consumers need to listen to a promise long before that promise's resolve conditions can be known
7// this is generally when a third party page injection is occurring, eg for Facebook or Google Tag Manager init
8function DeferredPromise() {
9    var self = this;
10    var p = null;
11
12    this.resolve = function(){};
13    this.reject = function(){};
14    this.promise = function promise() {
15        return p;
16    };
17
18    p = new Promise(function(resolve, reject) {
19        self.resolve = resolve;
20        self.reject = reject;
21    });
22}
23
24function AjaxError(jqXHR, textStatus, errorThrown, jqueryThis) {
25    var errorDetails = errorThrown || "'" + textStatus + "' (status: " + jqXHR.status + ')';
26
27    this.status = jqXHR.status;
28    this.message = 'Ajax call failed with ' + errorDetails + ': ' + jqueryThis.type + ' ' + jqueryThis.url;
29    this.url = jqueryThis.url;
30    this.responseJSON = jqXHR.responseJSON;
31
32    this.toString = function() {
33        return this.message;
34    }
35}
36
37// get a browser native promise from a JQuery $.ajax call
38// the promise will resolve with the response data if successful,
39// or reject with the most detailed error available if the ajax request fails (eg not a HTTP 200)
40function ajaxPromise(ajaxRequest) {
41    ajaxRequest = _addCrossOriginAjaxParams(ajaxRequest);
42    ajaxRequest = _addCsrfTokenToAjaxRequest(ajaxRequest);
43
44    return new Promise(function(resolve, reject) {
45        $.ajax(ajaxRequest)
46            .done(function(data, textStatus, jqXHR) {
47                _setCsrfTokenCookieFromResponse(jqXHR);
48
49                resolve(data);
50            })
51            .fail(function(jqXHR, textStatus, errorThrown) {
52                _setCsrfTokenCookieFromResponse(jqXHR);
53
54                if (_isInvalidCsrfError(jqXHR) && !('isRetry' in ajaxRequest)) {
55                    // retry once, as we should have received a fresh CSRF token in this response
56                    ajaxRequest['isRetry'] = true;
57                    return ajaxPromise(ajaxRequest).then(resolve, reject);
58                }
59
60                if (jqXHR.status === 0 && textStatus === 'error') {
61                    // the ajax call was cancelled by the browser, due to one of:
62                    //   - invalid CORS header responses
63                    //   - navigation / the window being closed
64                    resolve(null);
65                    return;
66                }
67                reject(new AjaxError(jqXHR, textStatus, errorThrown, this));
68            });
69    });
70}
71
72// same as ajaxPromise, but returns both response data and response headers.
73function ajaxPromiseWithHeaders(ajaxRequest) {
74    ajaxRequest = _addCrossOriginAjaxParams(ajaxRequest);
75    ajaxRequest = _addCsrfTokenToAjaxRequest(ajaxRequest);
76
77    return new Promise(function(resolve, reject) {
78        var requestStartTime = new Date().getTime();
79        $.ajax(ajaxRequest)
80            .done(function(data, textStatus, jqXHR) {
81                _setCsrfTokenCookieFromResponse(jqXHR);
82                resolve([data, _parseResponseHeaders(jqXHR)]);
83            })
84            .fail(function(jqXHR, textStatus, errorThrown) {
85                _setCsrfTokenCookieFromResponse(jqXHR);
86
87                if (_isInvalidCsrfError(jqXHR) && !('isRetry' in ajaxRequest)) {
88                    // retry once, as we should have received a fresh CSRF token in this response
89                    ajaxRequest['isRetry'] = true;
90                    return ajaxPromiseWithHeaders(ajaxRequest).then(resolve, reject);
91                }
92
93                var response = {};
94                var responseHeaders = _parseResponseHeaders(jqXHR);
95                try {
96                    response = $.parseJSON(jqXHR.responseText);
97                } catch (e) {
98                    response.responseText = jqXHR.responseText || '';
99                }
100                if (jqXHR.status === 0) {
101                    // the ajax call was cancelled by the browser, due to one of:
102                    //   - invalid CORS header responses
103                    //   - ajax timeout
104                    //   - navigation / the window being closed
105                    var duration = (new Date().getTime() - requestStartTime) * 0.001;
106                    var errorDetails = errorThrown || "'" + textStatus + "' (status: " + jqXHR.status + ')';
107                    var errorMessage = 'Ajax call failed with ' + errorDetails + ': ' + this.type + ' ' + this.url + ', after ' + duration + ' seconds';
108                    if (duration < 0.5) {
109                        errorMessage += ', this very short duration suggests CORS header error';
110                    }
111
112                    var error = {
113                        'status': 'error',
114                        'statusCode': jqXHR.status,
115                        'message': errorMessage,
116                        'duration': duration,
117                    }
118                    reject([error, responseHeaders])
119                    return;
120                }
121
122                if (jqXHR.status >= 500) {
123                    response.status = jqXHR.status;
124                    reject([response, responseHeaders]);
125                    return;
126                }
127
128                resolve([response, responseHeaders]);
129            });
130    });
131}
132
133function _addCrossOriginAjaxParams(ajaxRequest) {
134    if ('url' in ajaxRequest && ajaxRequest['url'].indexOf('https') === 0) {
135        // jQuery does not include x-requested-with in cross-domain ajax, so we manually add it
136        // as CloudFlare is much harsher on non-ajax requests, and may block them
137        ajaxRequest['crossDomain'] = false;
138
139        // and jQuery will default not include cookies to cross-domain requests, even though the cookies match
140        ajaxRequest['xhrFields'] = ajaxRequest['xhrFields'] || {};
141        if (!('withCredentials' in ajaxRequest['xhrFields'])) {
142            ajaxRequest['xhrFields']['withCredentials'] = true;
143        }
144    }
145
146    return ajaxRequest;
147}
148
149function _parseResponseHeaders(jqXHR) {
150    var headersLines = jqXHR.getAllResponseHeaders().trim().split(/[\r\n]+/);
151    var headers = {};
152    headersLines.forEach(function(line) {
153        var parts = line.split(': ');
154        var header = parts.shift().toLowerCase();
155        headers[header] = parts.join(': ');
156    });
157
158    return headers;
159}
160
161var _csrfTokenKey = 'avaaz-gk-public-csrf-token';
162
163function _addCsrfTokenToAjaxRequest(ajaxRequest) {
164    // if this ajax request is a PUT / POST / PATCH / DELETE
165    // and it's going to our own domain
166    // and we have a CSRF token cookie
167    // then append it to the request
168
169    function methodRequiresCsrf(ajaxRequest) {
170        var requestMethod = 'GET';
171        ['method', 'type'].forEach(function(requestKey) {
172            if (requestKey in ajaxRequest) {
173                requestMethod = ajaxRequest[requestKey];
174            }
175        });
176        var csrfMethods = ['PUT', 'POST', 'PATCH', 'DELETE'];
177
178        return (csrfMethods.indexOf(requestMethod.toUpperCase()) >= 0);
179    }
180
181    function urlDomain(urlString) {
182        var a = document.createElement('a');
183        a.href = urlString;
184        return a.hostname;
185    }
186
187    function isToOurDomain(ajaxRequest) {
188        if (!('url' in ajaxRequest)) {
189            return false;
190        }
191        var submitDomain = urlDomain(ajaxRequest['url']).toLowerCase();
192
193        return /\.avaaz\.(org|dev|local)$/.test(submitDomain) || submitDomain === 'avaaz.org';
194    }
195
196    function getCsrfTokenFromCookie() {
197        var csrfTokenCookieValue = null;
198
199        document.cookie.split(';').forEach(function(cookie) {
200            var parts = cookie.split(_csrfTokenKey + '=');
201            if (parts.length == 2) {
202                csrfTokenCookieValue = parts[1];
203            }
204        });
205
206        return csrfTokenCookieValue;
207    }
208
209    if (methodRequiresCsrf(ajaxRequest) && isToOurDomain(ajaxRequest)) {
210        var csrfTokenCookieValue = getCsrfTokenFromCookie();
211        if (csrfTokenCookieValue) {
212            // add this as a custom header
213            if (!('headers' in ajaxRequest)) {
214                ajaxRequest['headers'] = {};
215            }
216            ajaxRequest['headers'][_csrfTokenKey] = csrfTokenCookieValue;
217        }
218    }
219
220    return ajaxRequest;
221}
222
223function _setCsrfTokenCookieFromResponse(jqXHR) {
224    var responseHeaders = _parseResponseHeaders(jqXHR);
225
226    if (_csrfTokenKey in responseHeaders) {
227        // set the cookie, with no domain (will be current domain / subdomain), or expiration (will be a session cookie)
228        var cookieValue = _csrfTokenKey + '=' + escape(responseHeaders[_csrfTokenKey]) + '; path=/; secure';
229        document.cookie = cookieValue;
230    }
231}
232
233function _isInvalidCsrfError(jqXHR) {
234    if (jqXHR.status != 401) {
235        return false;
236    }
237
238    try {
239        var response = $.parseJSON(jqXHR.responseText);
240        // Make sure this string is exactly what Gatekeeper responds with
241        if (response && response.error == 'Invalid CSRF Token') {
242            return true;
243        }
244    } catch (e) {
245    }
246
247    return false;
248}
249
250
251/**
252 * Get image src according to current page port (80|443). #566 bug in IE
253 * @param string input SRC of need image in amazon
254 */
255function get_secure_src(input) {
256    // this is no longer referenced in our codebase, but some old blurbs do still use it
257    return (input.indexOf('amazonaws.com') !== -1) ? input.replace(/\s*((\S+\s*)*)/, "$1").replace(/((\s*\S+)*)\s*/, "$1").replace(/^http:\/\//, "https://") : input;
258}
259
260function formSpreadGetUserHashFromUrl() {
261    var aUrlParams = window.location.search.replace('?', '').split('&'),
262        sEntry = '';
263    for (var i = 0; i < aUrlParams.length; i++) {
264        sEntry = aUrlParams[i];
265        if (sEntry.indexOf('=') === -1 && !/^\d+$/.test(sEntry) && sEntry.length >= 6) {
266            return sEntry.substring(1);
267        }
268    }
269    return '';
270}
271
272function pushStateOnPostaction(userHash) {
273    if (supportsHistoryApi() && typeof userHash === 'string' && userHash !== '') {
274        window.history.pushState({ hash: userHash }, 'hash ' + userHash, '?' + userHash);
275    }
276}
277
278function getArgs(query, returnParametersWithoutValues) {
279    var args = {};
280    var value;
281    var argname;
282
283    query = query || location.search.substring(1);
284    var pairs = query.split('&');
285
286    for (var i = 0; i < pairs.length; i++) {
287        var pos = pairs[i].indexOf('=');
288        if (pos === -1) {
289            if (returnParametersWithoutValues) {
290                argname = pairs[i];
291                args[argname] = null;
292            }
293        } else {
294            argname = pairs[i].substring(0, pos);
295            value = pairs[i].substring(pos + 1);
296            value = decodeURIComponent(value);
297            args[argname] = value;
298        }
299    }
300    return args;
301}
302
303function getArgsWithHash() {
304    var query = location.search.substring(1);
305    query += '&';
306    query += location.hash.substring(1);
307    return getArgs(query);
308}
309
310function escapeHtml(text) {
311    return ('' + text)
312        .replace(/&/g, "&amp;")
313        .replace(/</g, "&lt;")
314        .replace(/>/g, "&gt;")
315        .replace(/"/g, "&quot;")
316        .replace(/'/g, "&#039;");
317}
318
319/**
320 * Replaces tokens in text
321 *
322 * @param oData tokens with prelacements
323 * @param sText text to parse
324 *
325 * @return {String}
326 */
327function getEdgeData(oData, sText) {
328    if (sText) {
329        for (var sKey in oData) {
330            if (oData.hasOwnProperty(sKey)) {
331                var oRegEx = RegExp('{' + sKey + '}', 'ig');
332                sText = sText.replace(oRegEx, oData[sKey]);
333
334                oRegEx = RegExp('tag_open' + sKey + 'tag_close', 'ig');
335                sText = sText.replace(oRegEx, oData[sKey]);
336            }
337        }
338    }
339    return sText;
340}
341
342function appendQueryParametersToForms() {
343    var parametersToAppend = ['v', 't', 'c', '_checksum', 'sandbox'];
344    var queryParameters = getArgs();
345
346    parametersToAppend.forEach(function(parameter) {
347        if (parameter in queryParameters) {
348            var value = queryParameters[parameter];
349
350            if (parameter === 'v') {
351                parameter = 'blast_version';
352                value = parseInt(value, 10);
353            }
354            appendHiddenInputToForms(parameter, value);
355        }
356    });
357
358    var userHash = getUserHash();
359    if (userHash) {
360        appendHiddenInputToForms('hash', userHash);
361    }
362}
363
364function appendHiddenInputToForms(name, value) {
365    $('form').append('<input type="hidden" name="' + name + '" value="' + value + '" />');
366}
367
368function getUserHash(noPrefix) {
369    var queryString = unescape(location.search.substring(1));
370    var queryParameters = getArgs(queryString, true);
371    var userHash = null;
372
373    Object.keys(queryParameters).forEach(function(parameter) {
374        var value = queryParameters[parameter];
375
376        // donation postaction uses hash parameter in URL
377        if (parameter === 'uHash' ||
378            parameter === 'hash' ||
379            (parameter.length > 6 && (value == null || value === ''))) {
380            userHash = normalizeUserHash(value ? value : parameter);
381        }
382    });
383
384    if (userHash && noPrefix) {
385        userHash = userHash.substr(-6);
386    }
387
388    return userHash;
389}
390
391function normalizeUserHash(userHash) {
392    if (userHash == null) {
393        return null;
394    }
395
396    // remove all characters after space
397    var spacePosition = userHash.indexOf(' ');
398    if (spacePosition > 0) {
399        userHash = userHash.substr(0, spacePosition);
400    }
401
402    var matches = userHash.match(/https?:\/\//i);
403    if (matches) {
404        userHash = userHash.substr(0, userHash.indexOf(matches[0]));
405    }
406
407    // and replace all non letters, digits or hyphens with empty string
408    return userHash.replace(/[^a-z\-0-9]+/ig, '');
409}
410
411function createModalLoadingOverlay(options) {
412    if ($("#modalLoadingOverlay").length === 0) {
413        // create the modal window
414        $('<div id="modalLoadingOverlay" style="display:none;"><div style="margin: 200px auto;"><img src="/campaign/static/images/ajax-loader3.gif" width="130" height="130"/></div></div>').appendTo('body');
415
416        if (!options || options.click !== false) {
417            $("#modalLoadingOverlay").on('click', function() {
418                hideModalLoadingOverlay();
419            });
420        }
421
422        if (!options || options.escape !== false) {
423            $(document).keyup(function(event) {
424                if (event.keyCode === 27) {
425                    hideModalLoadingOverlay();
426                }
427            });
428        }
429    }
430}
431function showModalLoadingOverlay() {
432    createModalLoadingOverlay();
433    $("#modalLoadingOverlay").css('z-index', '9999').fadeIn();
434}
435
436function hideModalLoadingOverlay() {
437    $('#modalLoadingOverlay').fadeOut();
438}
439
440/*Add Validation for History Support*/
441function supportsHistoryApi() {
442    return (window.history && 'pushState' in history);
443}
444
445/** Returns language code for recaptcha */
446function get_standard_language_code(lang) {
447    switch (lang) {
448        case 'po': lang = 'pt'; break;
449        case 'jp': lang = 'ja'; break;
450        case 'kr': lang = 'ko'; break;
451        case 'ct': lang = 'zh-TW'; break;
452    }
453
454    return lang;
455}
456
457function getDateTimePickerLanguageCode(lang) {
458    lang = get_standard_language_code(lang);
459    if (lang === 'zh-TW') {
460        return 'ch';
461    }
462
463    return lang;
464}
465
466var fbReady = new DeferredPromise();
467setTimeout(function() {
468    // stop waiting for FB to load
469    fbReady.resolve(false);
470}, 5000);
471
472window.facebookSdkLoaded = fbReady.promise();
473window.facebookSdkLoaded.then(function(loadedSuccessfully) {
474    if (!loadedSuccessfully) {
475        // the Facebook SDK is probably blocked by an adblocker etc
476        console.log('Facebook SDK did not load, probably blocked');
477    }
478});
479
480function isValidDateOfBirth(day, month, year) {
481    var birthdayParts = [day, month, year];
482    var allPartsAreNumeric = birthdayParts.every(function(part) {
483        return /^\d+$/.test(part);
484    });
485
486    if (!allPartsAreNumeric) {
487        return false;
488    }
489
490    var currentDate = new Date();
491    var selectedDateOfBirth = new Date();
492    selectedDateOfBirth.setDate(day);
493    selectedDateOfBirth.setMonth(month - 1);
494    selectedDateOfBirth.setFullYear(year);
495
496    return selectedDateOfBirth < currentDate;
497}
498
499function isDateEmpty(day, month, year) {
500    var anyPartIsNumeric = [day, month, year].some(function(part) {
501        return /^\d+$/.test(part);
502    });
503
504    return !anyPartIsNumeric;
505}
506
507function replaceAll(text, search, replacement) {
508    var escapedSearch = escapeRegExp(search);
509    var regexp = new RegExp(escapedSearch, 'gi');
510
511    return text.replace(regexp, replacement);
512}
513
514function escapeRegExp(string) {
515    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
516}
517
518function sha256(text) {
519    var encoded = new TextEncoder().encode(text);
520    var promise = crypto.subtle.digest('SHA-256', encoded);
521
522    return promise.then(function(hash) {
523        var hashBytes = Array.from(new Uint8Array(hash));
524
525        return hashBytes.map(function(byte) {
526            return byte.toString(16).padStart(2, '0');
527        }).join('');
528    }).catch(function(err) {
529        console.error(err);
530
531        return '';
532    });
533}
534
535(function($) {
536    $.fn.facebookReady = function(fn) {
537        window.facebookSdkLoaded.then(function(loadedSuccessfully) {
538            if (loadedSuccessfully) {
539                fn();
540            }
541        });
542        return this;
543    };
544})(jQuery);
545
546$(function() {
547    appendQueryParametersToForms();
548
549    $('a.form-submit').on('click', function(event) {
550        event.preventDefault();
551        $(this).closest('form').submit();
552    });
553
554    $('[name="im_not_a_bot"]').hide();
555});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.