PageSourceSearch

https://hearthis.at/_/javascripts/unhandled-rejection-handler.js?version=373057

js hearthis.at collected 2026-09-24 08:24:50 UTC 11,221 bytes, 278 lines download raw bytes

1/**
2 * Global handler for unhandled promise rejections
3 * Ensures that all rejections are Error objects before propagating to Sentry
4 * Fixes: "Non-Error promise rejection captured with value: overlay"
5 */
6
7// CRITICAL: Define global function stubs IMMEDIATELY to prevent ReferenceErrors
8// These must be defined BEFORE any other code runs (including the IIFE below)
9(function() {
10  'use strict';
11  var noop = function() {};
12  var globalFunctions = ['calculateWidth', 'becomeFan', 'sortIsotope', 'arrParsedGenres', 'l18nSearchMoreResults', 'Hogan', 'cast', 'LIDNotify', 'LIDNotifyId', 'codemirrKeydownEvent', 'codemirrorSuggest',
13                         'importRemote', 'importSoundCloud', 'reportStoryAbuse', 'followSet',
14                         // JAVASCRIPT-16WX: loadGroupInside only defined in v3 user template, missing in v4
15                         'loadGroupInside',
16                         // JAVASCRIPT-11P4: loadAjaxUrl not defined on all pages
17                         'loadAjaxUrl'];
18  globalFunctions.forEach(function(fnName) {
19    if (typeof window[fnName] === 'undefined') {
20      window[fnName] = noop;
21    }
22  });
23
24  // JAVASCRIPT-11HV / JAVASCRIPT-15Q: findTrack() stub must return object with setPosition
25  // so that callers like findTrack().setPosition(0) don't throw TypeError
26  if (typeof window.findTrack === 'undefined') {
27    window.findTrack = function() {
28      return { setPosition: noop, play: noop, pause: noop, stop: noop };
29    };
30  }
31
32  // playlist object stub — player.js defines window.playlist but it may not be ready yet
33  if (typeof window.playlist === 'undefined') {
34    window.playlist = { add: noop, remove: noop, next: noop, prev: noop };
35  }
36
37  // JAVASCRIPT-Y3H: Promise.allSettled polyfill for older browsers (Firefox <71, Safari <13)
38  if (typeof Promise !== 'undefined' && typeof Promise.allSettled !== 'function') {
39    Promise.allSettled = function(promises) {
40      return Promise.all(promises.map(function(p) {
41        return Promise.resolve(p).then(
42          function(value) { return { status: 'fulfilled', value: value }; },
43          function(reason) { return { status: 'rejected', reason: reason }; }
44        );
45      }));
46    };
47  }
48
49  // Hls.js stub — prevents crash when HLS library hasn't loaded yet
50  if (typeof window.Hls === 'undefined') {
51    window.Hls = { isSupported: function() { return false; }, Events: {}, ErrorDetails: {} };
52  }
53
54  // Livestream global vars — set by AJAX response, default to 0 until response arrives
55  var livestreamVars = ['intRecording', 'intFacebook', 'intYoutube'];
56  livestreamVars.forEach(function(v) {
57    if (typeof window[v] === 'undefined') { window[v] = 0; }
58  });
59
60  // l18n string stubs — fallback empty strings until js_language.php loads (Fixes JAVASCRIPT-119H class)
61  var globalStrings = [
62    'l18nWriteCommentAt', 'l18nWriteReactionAt',
63    'l18nTimeAmPrefix', 'l18nTimeVorPrefix',
64    'l18nTimeVorSuffixT', 'l18nTimeVorSuffixTSingle',
65    'l18nTimeVorSuffixH', 'l18nTimeVorSuffixHSingle',
66    'l18nTimeVorSuffixM', 'l18nTimeVorSuffixMSingle',
67    'l18nTimeVorNow', 'l18nFollowAction', 'l18nFollow', 'l18nSendMessage'
68  ];
69  globalStrings.forEach(function(varName) {
70    if (typeof window[varName] === 'undefined') {
71      window[varName] = '';
72    }
73  });
74})();
75
76(function() {
77  'use strict';
78
79  // Handle unhandledrejection events
80  window.addEventListener('unhandledrejection', function(event) {
81    // Filter out common user-intentional rejections
82    var reason = event.reason;
83    var reasonStr = String(reason);
84
85    // Filter out empty/undefined/null rejections that don't have meaningful error info
86    if (!reason || reasonStr === 'undefined' || reasonStr === 'null' || reasonStr === '' || reasonStr === '[object Object]') {
87      event.preventDefault();
88      return;
89    }
90
91    // Common cancellations that should be silently ignored
92    var ignoredReasons = ['cancel', 'esc', 'overlay', 'close', 'dismiss'];
93    if (ignoredReasons.indexOf(reasonStr) !== -1 || ignoredReasons.indexOf(reasonStr.toLowerCase()) !== -1) {
94      event.preventDefault();
95      return;
96    }
97
98    // Filter out external/browser extension errors that we cannot control
99    // These are typically from browser extensions or third-party scripts
100    var externalErrors = [
101      'performanceMetrics', // Browser extension or third-party feature flag system
102      'cashbackReminder',   // Third-party cashback extension (Fixes JAVA
102SCRIPT-11NG)
103    ];
104    for (var i = 0; i < externalErrors.length; i++) {
105      if (reasonStr.indexOf(externalErrors[i]) !== -1) {
106        event.preventDefault();
107        return;
108      }
109    }
110
111    // If the rejection reason is not an Error object, convert it
112    if (reason && !(reason instanceof Error)) {
113      // Create an error from the rejection value
114      var message = 'Promise rejection: ' + reasonStr;
115      var error = new Error(message);
116      error.originalReason = reason;
117
118      // Replace the reason with the error object
119      Object.defineProperty(event, 'reason', {
120        value: error,
121        writable: false,
122        configurable: true
123      });
124    }
125  }, false);
126
127  // Also handle regular promise catches that might be passing non-Error values
128  // This patches Promise.prototype.catch to ensure proper error handling
129  if (Promise && Promise.prototype) {
130    var originalCatch = Promise.prototype.catch;
131
132    Promise.prototype.catch = function(onRejected) {
133      return originalCatch.call(this, function(reason) {
134        // If rejected with non-Error value, convert it
135        if (reason && !(reason instanceof Error)) {
136          reason = new Error('Promise rejection: ' + String(reason));
137        }
138
139        // Call the original handler with the (possibly converted) reason
140        if (typeof onRejected === 'function') {
141          return onRejected(reason);
142        }
143        throw reason;
144      });
145    };
146  }
147
148  // Early stubs for common global functions to prevent ReferenceErrors during early user interaction
149  (function() {
150    var noop = function() {};
151    // Includes functions defined in jsfooter (initialize15.js) called from PHP onclick handlers (Fixes JAVASCRIPT-119H class)
152    var stubs = ['play', 'pause', 'stop', 'countLike', 'countLikeSet', 'reFeed', 'reFeedSet', 'countCommentLike',
153                 'closeLayer', 'getAllComments', 'setShare', 'loadMoreFeedStart', 'followUser', 'followGroup',
154                 'ajaxLogin', 'loadAjaxUrl', 'calcTime'];
155    
156    stubs.forEach(function(name) {
157      if (typeof window[name] === 'undefined') {
158        window[name] = function() {
159          var args = arguments;
160          var fnName = name;
161          if (window.jQuery && window.jQuery(document).ready) {
162            window.jQuery(document).ready(function() {
163              if (typeof window[fnName] === 'function' && window[fnName].toString().indexOf('native code') === -1) {
164                window[fnName].apply(null, args);
165              }
166            });
167          }
168        };
169      }
170    });
171
172    // Guard for jQuery plugins and methods
173    if (window.jQuery) {
174      // Guard $.bridget (used by Isotope/Masonry)
175      if (typeof window.jQuery.bridget === 'undefined') {
176        window.jQuery.bridget = noop;
177      }
178
179      // Ensure common methods exist on jQuery prototype to prevent "is not a function" errors
180      var proto = window.jQuery.prototype;
181      var methods = ['text', 'off', 'on', 'hide', 'show', 'addClass', 'removeClass', 'attr', 'data', 'find', 'ajaxQueue', 'sortable', 'append', 'expander', 'typeahead', 'hovercard'];
182      methods.forEach(function(m) {
183        if (typeof proto[m] === 'undefined') {
184          proto[m] = function() { return this; };
185        }
186      });
187
188      // Guard $.ajaxQueue which is often used directly
189      if (typeof window.jQuery.ajaxQueue === 'undefined') {
190          window.jQuery.ajaxQueue = function() { return { done: noop, fail: noop, always: noop }; };
191      }
192
193      // Wrap jQuery.globalEval to catch SyntaxErrors from malformed AJAX content
194      if (window.jQuery.globalEval) {
195        var originalGlobalEval = window.jQuery.globalEval;
196        window.jQuery.globalEval = function(code) {
197          try {
198            return originalGlobalEval.call(window.jQuery, code);
199          } catch (e) {
200            // Silently ignore syntax errors from malformed AJAX responses
201            if (e.name === 'SyntaxError') {
202              console.warn && console.warn('globalEval SyntaxError (ignored):', e.message);
203              return;
204            }
205            // Re-throw other errors
206            throw e;
207          }
208        };
209      }
210    }
211
212    // Add defensive wrappers for document.querySelector/querySelectorAll
213    if (typeof document !== 'undefined') {
214      var originalQuerySelector = document.querySelector;
215      var originalQuerySelectorAll = document.querySelectorAll;
216      var originalGetElementById = document.getElementById;
217
218      // Wrap querySelector to handle null results safely
219      document.querySelector = function(selector) {
220        try {
221          return originalQuerySelector.call(document, selector);
222        } catch (e) {
223          // Ignore jQuery Migrate feature detection selectors
224          if (selector && selector.indexOf(':jqfake') === -1) {
225            console.warn && console.warn('querySelector error:', selector, e);
226          }
227          return null;
228        }
229      };
230
231      // Wrap querySelectorAll to handle null results safely
232      document.querySelectorAll = function(selector) {
233        try {
234          return originalQuerySelectorAll.call(document, selector);
235        } catch (e) {
236          // Ignore jQuery Migrate feature detection selectors
237          if (selector && selector.indexOf(':jqfake') === -1) {
238            console.warn && console.warn('querySelectorAll error:', selector, e);
239          }
240          return [];
241        }
242      };
243
244      // Wrap getElementById to handle null results safely
245      document.getElementById = function(id) {
246        try {
247          return originalGetElementById.call(document, id);
248        } catch (e) {
249          console.warn && console.warn('getElementById error:', id, e);
250          return null;
251        }
252      };
253    }
254
255    // Note: Global function stubs (LIDNotifyId, findTrack, etc.) are now defined
256    // at the top of this file to prevent ReferenceErrors during early script execution
257
258    // Guard CodeMirror/SimpleMDE editor interactions
259    // Wrap event handlers that interact with editor instances to prevent "editor.trigger is not a function" errors
260    if (window.jQuery) {
261      // Intercept clicks on codemirror-user-suggest elements
262      window.jQuery(document).on('click', '.codemirror-user-suggest li', function(e) {
263        try {
264          // Find the associated editor instance
265          var editor = window.simplemde || window.editor;
266          if (editor && editor.codemirror && typeof editor.codemirror.trigger === 'function') {
267            editor.codemirror.trigger('change');
268          }
269        } catch (err) {
270          console.warn && console.warn('CodeMirror editor interaction error (handled):', err);
271          // Prevent error from propagating
272          e.stopPropagation();
273          e.preventDefault();
274        }
275      });
276    }
277  })();
278})();

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.