File size: 24,164 Bytes
979bf48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
/**
 * Manages unhandled rejection listeners to intelligently filter rejections
 * from aborted prerenders when cache components are enabled.
 *
 * THE PROBLEM:
 * When we abort prerenders we expect to find numerous unhandled promise rejections due to
 * things like awaiting Request data like `headers()`. The rejections are fine and should
 * not be construed as problematic so we need to avoid the appearance of a problem by
 * omitting them from the logged output.
 *
 * THE STRATEGY:
 * 1. Install a filtering unhandled rejection handler
 * 2. Intercept process event methods to capture new handlers in our internal queue
 * 3. For each rejection, check if it comes from an aborted prerender context
 * 4. If yes, suppress it. If no, delegate to all handlers in our queue
 * 5. This provides precise filtering without time-based windows
 *
 * This ensures we suppress noisy prerender-related rejections while preserving
 * normal error logging for genuine unhandled rejections.
 */ "use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
const _workunitasyncstorageexternal = require("../app-render/work-unit-async-storage.external");
const MODE = process.env.NEXT_UNHANDLED_REJECTION_FILTER;
let ENABLE_UHR_FILTER = true;
let UHR_FILTER_LOG_LEVEL = 'warn';
switch(MODE){
    case 'silent':
        UHR_FILTER_LOG_LEVEL = 'silent';
        break;
    case 'debug':
        UHR_FILTER_LOG_LEVEL = 'debug';
        break;
    case 'false':
    case 'disabled':
    case '0':
        ENABLE_UHR_FILTER = false;
        break;
    case '':
    case undefined:
    case 'enabled':
    case 'true':
    case '1':
        break;
    default:
        if (typeof MODE === 'string') {
            console.error(`NEXT_UNHANDLED_REJECTION_FILTER has an unrecognized value: ${JSON.stringify(MODE)}. Use "enabled", "disabled", "silent", or "debug", or omit the environment variable altogether`);
        }
}
let debug;
let debugWithTrace;
let warn;
let warnWithTrace;
switch(UHR_FILTER_LOG_LEVEL){
    case 'debug':
        debug = (message)=>console.log('[Next.js Unhandled Rejection Filter]: ' + message);
        debugWithTrace = (message)=>{
            console.log(new DebugWithStack(message));
        };
    // Intentional fallthrough
    case 'warn':
        warn = (message)=>{
            console.warn('[Next.js Unhandled Rejection Filter]: ' + message);
        };
        warnWithTrace = (message)=>{
            console.warn(new WarnWithStack(message));
        };
        break;
    case 'silent':
    default:
}
class DebugWithStack extends Error {
    constructor(message){
        super(message);
        this.name = '[Next.js Unhandled Rejection Filter]';
    }
}
class WarnWithStack extends Error {
    constructor(message){
        super(message);
        this.name = '[Next.js Unhandled Rejection Filter]';
    }
}
let didWarnUninstalled = false;
const warnUninstalledOnce = warn ? function warnUninstalledOnce(...args) {
    if (!didWarnUninstalled) {
        didWarnUninstalled = true;
        warn(...args);
    }
} : undefined;
let filterInstalled = false;
// We store the proxied listeners for unhandled rejections here.
let underlyingListeners = [];
// We store a unique pointer to each event listener registration to track
// details like whether the listener is a once listener.
let listenerMetadata = [];
// These methods are used to restore the original implementations when uninstalling the patch
let originalProcessAddListener;
let originalProcessRemoveListener;
let originalProcessOn;
let originalProcessOff;
let originalProcessPrependListener;
let originalProcessOnce;
let originalProcessPrependOnceListener;
let originalProcessRemoveAllListeners;
let originalProcessListeners;
// Some of these base methods call others and we don't want them to call the patched version so we
// need a way to synchronously disable the patch temporarily.
let bypassPatch = false;
// This patch ensures that if any patched methods end up calling other methods internally they will
// bypass the patch during their execution. This is important for removeAllListeners in particular
// because it calls removeListener internally and we want to ensure it actually clears the listeners
// from the process queue and not our private queue.
function patchWithoutReentrancy(original, patchedImpl) {
    // Produce a function which has the correct name
    const patched = {
        [original.name]: function(...args) {
            if (bypassPatch) {
                return Reflect.apply(original, process, args);
            }
            const previousBypassPatch = bypassPatch;
            bypassPatch = true;
            try {
                return Reflect.apply(patchedImpl, process, args);
            } finally{
                bypassPatch = previousBypassPatch;
            }
        }
    }[original.name];
    // Preserve the original toString behavior
    Object.defineProperty(patched, 'toString', {
        value: original.toString.bind(original),
        writable: true,
        configurable: true
    });
    return patched;
}
const MACGUFFIN_EVENT = 'Next.UnhandledRejectionFilter.MacguffinEvent';
/**
 * Installs a filtering unhandled rejection handler that intelligently suppresses
 * rejections from aborted prerender contexts.
 *
 * This should be called once during server startup to install the global filter.
 */ function installUnhandledRejectionFilter() {
    if (filterInstalled) {
        warnWithTrace == null ? void 0 : warnWithTrace('Unexpected subsequent filter installation. This is a bug in Next.js');
        return;
    }
    debug == null ? void 0 : debug('Installing Filter');
    // Capture existing handlers
    underlyingListeners = Array.from(process.listeners('unhandledRejection'));
    // We assume all existing handlers are not "once"
    listenerMetadata = underlyingListeners.map((l)=>({
            listener: l,
            once: false
        }));
    // Remove all existing handlers
    process.removeAllListeners('unhandledRejection');
    // Install our filtering handler
    process.addListener('unhandledRejection', filteringUnhandledRejectionHandler);
    // Store the original process methods
    originalProcessAddListener = process.addListener;
    originalProcessRemoveListener = process.removeListener;
    originalProcessOn = process.on;
    originalProcessOff = process.off;
    originalProcessPrependListener = process.prependListener;
    originalProcessOnce = process.once;
    originalProcessPrependOnceListener = process.prependOnceListener;
    originalProcessRemoveAllListeners = process.removeAllListeners;
    originalProcessListeners = process.listeners;
    process.addListener = patchWithoutReentrancy(originalProcessAddListener, function(event, listener) {
        if (event === 'unhandledRejection') {
            debugWithTrace == null ? void 0 : debugWithTrace(`Appending 'unhandledRejection' listener with name \`${listener.name}\`.`);
            // We add the listener to a dummy event in case it throws. We don't catch it intentionally
            try {
                originalProcessAddListener.call(process, MACGUFFIN_EVENT, listener);
            } finally{
                // We clean up the added event
                originalProcessRemoveAllListeners.call(process, MACGUFFIN_EVENT);
            }
            // Add new handlers to our internal queue instead of the process
            underlyingListeners.push(listener);
            listenerMetadata.push({
                listener,
                once: false
            });
            return process;
        }
        // For other events, use the original method
        return originalProcessAddListener.call(process, event, listener);
    });
    // Intercept process.removeListener (alias for process.off)
    process.removeListener = patchWithoutReentrancy(originalProcessRemoveListener, function(event, listener) {
        if (event === 'unhandledRejection') {
            // Check if they're trying to remove our filtering handler
            if (listener === filteringUnhandledRejectionHandler) {
                warnUninstalledOnce == null ? void 0 : warnUninstalledOnce(`Uninstalling filter because \`process.removeListener('unhandledRejection', listener)\` was called with the filter listener. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.

You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.

You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.`);
                uninstallUnhandledRejectionFilter();
                return process;
            }
            debugWithTrace == null ? void 0 : debugWithTrace(`Removing 'unhandledRejection' listener with name \`${listener.name}\`.`);
            // We remove the listener on a dummy event in case it throws. We don't catch it intentionally
            originalProcessRemoveListener.call(process, MACGUFFIN_EVENT, listener);
            const index = underlyingListeners.lastIndexOf(listener);
            if (index > -1) {
                debug == null ? void 0 : debug(`listener found index ${index} and removed.`);
                underlyingListeners.splice(index, 1);
                listenerMetadata.splice(index, 1);
            } else {
                debug == null ? void 0 : debug(`listener not found.`);
            }
            return process;
        }
        // For other events, use the original method
        return originalProcessRemoveListener.call(process, event, listener);
    });
    // If the process.on is referentially process.addListener then share the patched version as well
    if (originalProcessOn === originalProcessAddListener) {
        process.on = process.addListener;
    } else {
        process.on = patchWithoutReentrancy(originalProcessOn, function(event, listener) {
            if (event === 'unhandledRejection') {
                debugWithTrace == null ? void 0 : debugWithTrace(`Appending 'unhandledRejection' listener with name \`${listener.name}\`.`);
                // We add the listener to a dummy event in case it throws. We don't catch it intentionally
                try {
                    originalProcessOn.call(process, MACGUFFIN_EVENT, listener);
                } finally{
                    // We clean up the added event
                    originalProcessRemoveAllListeners.call(process, MACGUFFIN_EVENT);
                }
                // Add new handlers to our internal queue instead of the process
                underlyingListeners.push(listener);
                listenerMetadata.push({
                    listener,
                    once: false
                });
                return process;
            }
            // For other events, use the original method
            return originalProcessOn.call(process, event, listener);
        });
    }
    // If the process.off is referentially process.addListener then share the patched version as well
    if (originalProcessOff === originalProcessRemoveListener) {
        process.off = process.removeListener;
    } else {
        process.off = patchWithoutReentrancy(originalProcessOff, function(event, listener) {
            if (event === 'unhandledRejection') {
                // Check if they're trying to remove our filtering handler
                if (listener === filteringUnhandledRejectionHandler) {
                    warnUninstalledOnce == null ? void 0 : warnUninstalledOnce(`Uninstalling filter because \`process.off('unhandledRejection', listener)\` was called with the filter listener. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.

You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.

You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.`);
                    uninstallUnhandledRejectionFilter();
                    return process;
                }
                debugWithTrace == null ? void 0 : debugWithTrace(`Removing 'unhandledRejection' listener with name \`${listener.name}\`.`);
                // We remove the listener on a dummy event in case it throws. We don't catch it intentionally
                originalProcessOff.call(process, MACGUFFIN_EVENT, listener);
                const index = underlyingListeners.lastIndexOf(listener);
                if (index > -1) {
                    debug == null ? void 0 : debug(`listener found index ${index} and removed.`);
                    underlyingListeners.splice(index, 1);
                    listenerMetadata.splice(index, 1);
                } else {
                    debug == null ? void 0 : debug(`listener not found.`);
                }
                return process;
            }
            // For other events, use the original method
            return originalProcessOff.call(process, event, listener);
        });
    }
    // Intercept process.prependListener for handlers that should go first
    process.prependListener = patchWithoutReentrancy(originalProcessPrependListener, function(event, listener) {
        if (event === 'unhandledRejection') {
            debugWithTrace == null ? void 0 : debugWithTrace(`(Prepending) Inserting 'unhandledRejection' listener with name \`${listener.name}\` immediately following the Next.js 'unhandledRejection' filter listener.`);
            // We add the listener to a dummy event in case it throws. We don't catch it intentionally
            try {
                originalProcessPrependListener.call(process, MACGUFFIN_EVENT, listener);
            } finally{
                // We clean up the added event
                originalProcessRemoveAllListeners.call(process, MACGUFFIN_EVENT);
            }
            // Add new handlers to the beginning of our internal queue
            underlyingListeners.unshift(listener);
            listenerMetadata.unshift({
                listener,
                once: false
            });
            return process;
        }
        // For other events, use the original method
        return originalProcessPrependListener.call(process, event, listener);
    });
    // Intercept process.once for one-time handlers
    process.once = patchWithoutReentrancy(originalProcessOnce, function(event, listener) {
        if (event === 'unhandledRejection') {
            debugWithTrace == null ? void 0 : debugWithTrace(`Appending 'unhandledRejection' once-listener with name \`${listener.name}\`.`);
            // We add the listener to a dummy event in case it throws. We don't catch it intentionally
            try {
                originalProcessOnce.call(process, MACGUFFIN_EVENT, listener);
            } finally{
                // We clean up the added event
                originalProcessRemoveAllListeners.call(process, MACGUFFIN_EVENT);
            }
            underlyingListeners.push(listener);
            listenerMetadata.push({
                listener: listener,
                once: true
            });
            return process;
        }
        // For other events, use the original method
        return originalProcessOnce.call(process, event, listener);
    });
    // Intercept process.prependOnceListener for one-time handlers that should go first
    process.prependOnceListener = patchWithoutReentrancy(originalProcessPrependOnceListener, function(event, listener) {
        if (event === 'unhandledRejection') {
            debugWithTrace == null ? void 0 : debugWithTrace(`(Prepending) Inserting 'unhandledRejection' once-listener with name \`${listener.name}\` immediately following the Next.js 'unhandledRejection' filter listener.`);
            // We add the listener to a dummy event in case it throws. We don't catch it intentionally
            try {
                originalProcessPrependOnceListener.call(process, MACGUFFIN_EVENT, listener);
            } finally{
                // We clean up the added event
                originalProcessRemoveAllListeners.call(process, MACGUFFIN_EVENT);
            }
            // Add to the beginning of our internal queue
            underlyingListeners.unshift(listener);
            listenerMetadata.unshift({
                listener: listener,
                once: true
            });
            return process;
        }
        // For other events, use the original method
        return originalProcessPrependOnceListener.call(process, event, listener);
    });
    // Intercept process.removeAllListeners
    process.removeAllListeners = patchWithoutReentrancy(originalProcessRemoveAllListeners, function(event) {
        if (event === 'unhandledRejection') {
            // TODO add warning for this case once we stop importing this in test scopes automatically. Currently
            // we pull this file in whenever build/utils.tsx is imported which is not the right layering.
            // The extensions should be loaded from entrypoints like build/index or next-server
            //         warnRemoveAllOnce?.(
            //           `\`process.removeAllListeners('unhandledRejection')\` was called. Next.js maintains the first 'unhandledRejection' listener to filter out unnecessary rejection warnings caused by aborting prerenders early. It is not recommended that you uninstall this behavior, but if you want to you must you can acquire the listener with \`process.listeners('unhandledRejection')[0]\` and remove it with \`process.removeListener('unhandledRejection', listener)\`.
            // You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.
            // You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.`
            //         )
            debugWithTrace == null ? void 0 : debugWithTrace(`Removing all 'unhandledRejection' listeners except for the Next.js filter.`);
            underlyingListeners.length = 0;
            listenerMetadata.length = 0;
            return process;
        }
        // For other specific events, use the original method
        if (event !== undefined) {
            return originalProcessRemoveAllListeners.call(process, event);
        }
        // If no event specified (removeAllListeners()), uninstall our patch completely
        warnUninstalledOnce == null ? void 0 : warnUninstalledOnce(`Uninstalling filter because \`process.removeAllListeners()\` was called. Uninstalling this filter is not recommended and will cause you to observe 'unhandledRejection' events related to intentionally aborted prerenders.

You can silence warnings related to this behavior by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=silent\` environment variable.

You can debug event listener operations by running Next.js with \`NEXT_UNHANDLED_REJECTION_FILTER=debug\` environment variable.`);
        uninstallUnhandledRejectionFilter();
        return originalProcessRemoveAllListeners.call(process);
    });
    // Intercept process.listeners to return our internal handlers for unhandled rejection
    process.listeners = patchWithoutReentrancy(originalProcessListeners, function(event) {
        if (event === 'unhandledRejection') {
            debugWithTrace == null ? void 0 : debugWithTrace(`Retrieving all 'unhandledRejection' listeners.`);
            return [
                filteringUnhandledRejectionHandler,
                ...underlyingListeners
            ];
        }
        return originalProcessListeners.call(process, event);
    });
    filterInstalled = true;
}
/**
 * Uninstalls the unhandled rejection filter and restores original process methods.
 * This is called when someone explicitly removes our filtering handler.
 * @internal
 */ function uninstallUnhandledRejectionFilter() {
    if (!filterInstalled) {
        warnWithTrace == null ? void 0 : warnWithTrace('Unexpected subsequent filter uninstallation. This is a bug in Next.js');
        return;
    }
    debug == null ? void 0 : debug('Uninstalling Filter');
    // Restore original process methods
    process.on = originalProcessOn;
    process.addListener = originalProcessAddListener;
    process.once = originalProcessOnce;
    process.prependListener = originalProcessPrependListener;
    process.prependOnceListener = originalProcessPrependOnceListener;
    process.removeListener = originalProcessRemoveListener;
    process.off = originalProcessOff;
    process.removeAllListeners = originalProcessRemoveAllListeners;
    process.listeners = originalProcessListeners;
    // Remove our filtering handler
    process.removeListener('unhandledRejection', filteringUnhandledRejectionHandler);
    // Re-register all the handlers that were in our internal queue
    for (const meta of listenerMetadata){
        if (meta.once) {
            process.once('unhandledRejection', meta.listener);
        } else {
            process.addListener('unhandledRejection', meta.listener);
        }
    }
    // Reset state
    filterInstalled = false;
    underlyingListeners.length = 0;
    listenerMetadata.length = 0;
}
/**
 * The filtering handler that decides whether to suppress or delegate unhandled rejections.
 */ function filteringUnhandledRejectionHandler(reason, promise) {
    const capturedListenerMetadata = Array.from(listenerMetadata);
    const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
    if (workUnitStore) {
        switch(workUnitStore.type){
            case 'prerender':
            case 'prerender-client':
            case 'prerender-runtime':
                {
                    const signal = workUnitStore.renderSignal;
                    if (signal.aborted) {
                        // This unhandledRejection is from async work spawned in a now
                        // aborted prerender. We don't need to report this.
                        return;
                    }
                    break;
                }
            case 'prerender-ppr':
            case 'prerender-legacy':
            case 'request':
            case 'cache':
            case 'private-cache':
            case 'unstable-cache':
                break;
            default:
                workUnitStore;
        }
    }
    // Not from an aborted prerender, delegate to original handlers
    if (capturedListenerMetadata.length === 0) {
        // We need to log something because the default behavior when there is
        // no event handler installed is to trigger an Unhandled Exception.
        // We don't do that here b/c we don't want to rely on this implicit default
        // to kill the process since it can be disabled by installing a userland listener
        // and you may also choose to run Next.js with args such that unhandled rejections
        // do not automatically terminate the process.
        console.error('Unhandled Rejection:', reason);
    } else {
        try {
            for (const meta of capturedListenerMetadata){
                if (meta.once) {
                    // This is a once listener. we remove it from our set before we call it
                    const index = listenerMetadata.indexOf(meta);
                    if (index !== -1) {
                        underlyingListeners.splice(index, 1);
                        listenerMetadata.splice(index, 1);
                    }
                }
                const listener = meta.listener;
                listener(reason, promise);
            }
        } catch (error) {
            // If any handlers error we produce an Uncaught Exception
            setImmediate(()=>{
                throw error;
            });
        }
    }
}
// Install the filter when this module is imported
if (ENABLE_UHR_FILTER) {
    installUnhandledRejectionFilter();
}

//# sourceMappingURL=unhandled-rejection.js.map