| | import type { SourceMap } from 'module' |
| | import { LRUCache } from './lru-cache' |
| |
|
| | function noSourceMap(): SourceMap | undefined { |
| | return undefined |
| | } |
| |
|
| | |
| | const findSourceMap = |
| | process.env.NEXT_RUNTIME === 'edge' |
| | ? noSourceMap |
| | : (require('module') as typeof import('module')).findSourceMap |
| |
|
| | |
| | |
| | |
| | interface IndexSourceMapSection { |
| | offset: { |
| | line: number |
| | column: number |
| | } |
| | map: BasicSourceMapPayload |
| | } |
| |
|
| | |
| | |
| | interface IndexSourceMap { |
| | version: number |
| | file: string |
| | sections: IndexSourceMapSection[] |
| | } |
| |
|
| | |
| | export interface BasicSourceMapPayload { |
| | version: number |
| | |
| | |
| | file: string |
| | sourceRoot?: string |
| | |
| | |
| | sources: Array<string> |
| | names: Array<string> |
| | mappings: string |
| | ignoreList?: number[] |
| | } |
| |
|
| | export type ModernSourceMapPayload = BasicSourceMapPayload | IndexSourceMap |
| |
|
| | export function sourceMapIgnoreListsEverything( |
| | sourceMap: BasicSourceMapPayload |
| | ): boolean { |
| | return ( |
| | sourceMap.ignoreList !== undefined && |
| | sourceMap.sources.length === sourceMap.ignoreList.length |
| | ) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export function findApplicableSourceMapPayload( |
| | line0: number, |
| | column0: number, |
| | payload: ModernSourceMapPayload |
| | ): BasicSourceMapPayload | undefined { |
| | if ('sections' in payload) { |
| | if (payload.sections.length === 0) { |
| | return undefined |
| | } |
| |
|
| | |
| | |
| | const sections = payload.sections |
| | let left = 0 |
| | let right = sections.length - 1 |
| | let result: IndexSourceMapSection | null = null |
| |
|
| | while (left <= right) { |
| | |
| | const middle = ~~((left + right) / 2) |
| | const section = sections[middle] |
| | const offset = section.offset |
| |
|
| | if ( |
| | offset.line < line0 || |
| | (offset.line === line0 && offset.column <= column0) |
| | ) { |
| | result = section |
| | left = middle + 1 |
| | } else { |
| | right = middle - 1 |
| | } |
| | } |
| |
|
| | return result === null ? undefined : result.map |
| | } else { |
| | return payload |
| | } |
| | } |
| |
|
| | const didWarnAboutInvalidSourceMapDEV = new Set<string>() |
| |
|
| | export function filterStackFrameDEV( |
| | sourceURL: string, |
| | functionName: string, |
| | line1: number, |
| | column1: number |
| | ): boolean { |
| | if (sourceURL === '') { |
| | |
| | |
| | |
| | |
| | |
| | return functionName !== 'new Promise' |
| | } |
| | if (sourceURL.startsWith('node:') || sourceURL.includes('node_modules')) { |
| | return false |
| | } |
| | try { |
| | |
| | |
| | |
| | const sourceMap = findSourceMap(sourceURL) |
| | if (sourceMap === undefined) { |
| | |
| | |
| | return true |
| | } |
| | const sourceMapPayload = findApplicableSourceMapPayload( |
| | line1 - 1, |
| | column1 - 1, |
| | sourceMap.payload |
| | ) |
| | if (sourceMapPayload === undefined) { |
| | |
| | return true |
| | } |
| | return !sourceMapIgnoreListsEverything(sourceMapPayload) |
| | } catch (cause) { |
| | if (process.env.NODE_ENV !== 'production') { |
| | |
| | if (!didWarnAboutInvalidSourceMapDEV.has(sourceURL)) { |
| | didWarnAboutInvalidSourceMapDEV.add(sourceURL) |
| | |
| | |
| | console.error( |
| | `${sourceURL}: Invalid source map. Only conformant source maps can be used to filter stack frames. Cause: ${cause}` |
| | ) |
| | } |
| | } |
| |
|
| | return true |
| | } |
| | } |
| |
|
| | const invalidSourceMap = Symbol('invalid-source-map') |
| | const sourceMapURLs = new LRUCache<string | typeof invalidSourceMap>( |
| | 512 * 1024 * 1024, |
| | (url) => |
| | url === invalidSourceMap |
| | ? |
| | |
| | 8 * 1024 |
| | : |
| | url.length |
| | ) |
| | export function findSourceMapURLDEV( |
| | scriptNameOrSourceURL: string |
| | ): string | null { |
| | let sourceMapURL = sourceMapURLs.get(scriptNameOrSourceURL) |
| | if (sourceMapURL === undefined) { |
| | let sourceMapPayload: ModernSourceMapPayload | undefined |
| | try { |
| | sourceMapPayload = findSourceMap(scriptNameOrSourceURL)?.payload |
| | } catch (cause) { |
| | console.error( |
| | `${scriptNameOrSourceURL}: Invalid source map. Only conformant source maps can be used to find the original code. Cause: ${cause}` |
| | ) |
| | } |
| |
|
| | if (sourceMapPayload === undefined) { |
| | sourceMapURL = invalidSourceMap |
| | } else { |
| | |
| | |
| | const sourceMapJSON = JSON.stringify(sourceMapPayload) |
| | const sourceMapURLData = Buffer.from(sourceMapJSON, 'utf8').toString( |
| | 'base64' |
| | ) |
| | sourceMapURL = `data:application/json;base64,${sourceMapURLData}` |
| | } |
| |
|
| | sourceMapURLs.set(scriptNameOrSourceURL, sourceMapURL) |
| | } |
| |
|
| | return sourceMapURL === invalidSourceMap ? null : sourceMapURL |
| | } |
| |
|
| | export function devirtualizeReactServerURL(sourceURL: string): string { |
| | if (sourceURL.startsWith('about://React/')) { |
| | |
| | const envIdx = sourceURL.indexOf('/', 'about://React/'.length) |
| | const suffixIdx = sourceURL.lastIndexOf('?') |
| | if (envIdx > -1 && suffixIdx > -1) { |
| | return decodeURI(sourceURL.slice(envIdx + 1, suffixIdx)) |
| | } |
| | } |
| | return sourceURL |
| | } |
| |
|
| | function isAnonymousFrameLikelyJSNative(methodName: string): boolean { |
| | |
| | |
| | |
| | |
| | return ( |
| | |
| | methodName.startsWith('JSON.') || |
| | |
| | methodName.startsWith('Function.') || |
| | |
| | methodName.startsWith('Promise.') || |
| | methodName.startsWith('Array.') || |
| | methodName.startsWith('Set.') || |
| | methodName.startsWith('Map.') |
| | ) |
| | } |
| |
|
| | export function ignoreListAnonymousStackFramesIfSandwiched<Frame>( |
| | frames: Frame[], |
| | isAnonymousFrame: (frame: Frame) => boolean, |
| | isIgnoredFrame: (frame: Frame) => boolean, |
| | getMethodName: (frame: Frame) => string, |
| | |
| | ignoreFrame: (frame: Frame) => void |
| | ): void { |
| | for (let i = 1; i < frames.length; i++) { |
| | const currentFrame = frames[i] |
| | if ( |
| | !( |
| | isAnonymousFrame(currentFrame) && |
| | isAnonymousFrameLikelyJSNative(getMethodName(currentFrame)) |
| | ) |
| | ) { |
| | continue |
| | } |
| |
|
| | const previousFrameIsIgnored = isIgnoredFrame(frames[i - 1]) |
| | if (previousFrameIsIgnored && i < frames.length - 1) { |
| | let ignoreSandwich = false |
| | let j = i + 1 |
| | for (j; j < frames.length; j++) { |
| | const nextFrame = frames[j] |
| | const nextFrameIsAnonymous = |
| | isAnonymousFrame(nextFrame) && |
| | isAnonymousFrameLikelyJSNative(getMethodName(nextFrame)) |
| | if (nextFrameIsAnonymous) { |
| | continue |
| | } |
| |
|
| | const nextFrameIsIgnored = isIgnoredFrame(nextFrame) |
| | if (nextFrameIsIgnored) { |
| | ignoreSandwich = true |
| | break |
| | } |
| | } |
| |
|
| | if (ignoreSandwich) { |
| | for (i; i < j; i++) { |
| | ignoreFrame(frames[i]) |
| | } |
| | } |
| | } |
| | } |
| | } |
| |
|