Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions src/browser/BrowserFunc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export default class BrowserFunc {

try {
const initialChunks = new Set<string>()
const chunkRegex = /(?:\/_next\/)?(static\/chunks\/[\w\-./()]+?\.js)/g
const chunkRegex = /(?:\/_next\/)?(static\/(?:chunks|immutable|media)\/[\w\-./()]+?\.js)/g
for (const html of htmls) {
if (!html) continue
for (const match of html.matchAll(chunkRegex)) {
Expand Down Expand Up @@ -409,6 +409,7 @@ export default class BrowserFunc {
private extractDynamicChunkPaths(js: string): string[] {
const seen = new Set<string>()

// Webpack builder
const builder = /static\/chunks\/"\s*\+\s*\w+\s*\+\s*"([-.])"\s*\+\s*\{([\s\S]*?)\}\s*\[/g
for (const match of js.matchAll(builder)) {
const sep = match[1]!
Expand All @@ -417,12 +418,16 @@ export default class BrowserFunc {
}
}

// If the builder shape changes, scan id:hash pairs globally
if (!seen.size) {
for (const [, id, hash] of js.matchAll(/\b(\d{2,6}):"([a-f0-9]{12,})"/g)) {
seen.add(`/_next/static/chunks/${id}-${hash}.js`)
seen.add(`/_next/static/chunks/${id}.${hash}.js`)
}
// Webpack fallback
for (const [, id, hash] of js.matchAll(/\b(\d{2,6}):"([a-f0-9]{12,})"/g)) {
seen.add(`/_next/static/chunks/${id}-${hash}.js`)
seen.add(`/_next/static/chunks/${id}.${hash}.js`)
}

// Turbopack
const turbopackRegex = /"(static\/(?:immutable|chunks|media)\/[\w\-./()]+?\.js)"/g
for (const match of js.matchAll(turbopackRegex)) {
seen.add(`/_next/${match[1]}`)
}

return [...seen]
Expand Down
50 changes: 28 additions & 22 deletions src/browser/ReactFunc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,27 +579,40 @@ export default class ReactFunc {
const HEX = '[a-f0-9]{40,64}'

// Framework args that share the call shape but aren't the action name
const KNOWN_NON_NAMES = new Set(['callServer', 'findSourceMapURL', 'encodeFormAction'])
const KNOWN_NON_NAMES = new Set(['callServer', 'findSourceMapURL', 'encodeFormAction', 'default'])

try {
// I hate this so much honestly
const callRegex = new RegExp(`createServerReference\\s*\\)?\\s*\\(\\s*"(${HEX})"([\\s\\S]{0,400}?)\\)`, 'g')
// Support for both formats: (createServerReference)(id) and createServerReference(id)
// The argument search window has been expanded to 800 characters for Turbopack
const createRegex = new RegExp(`createServerReference\\s*\\)?\\s*\\(\\s*"(${HEX})"([\\s\\S]{0,800}?)\\)`, 'g')
const registerRegex = new RegExp(`registerServerReference\\s*\\)?\\s*\\([^,]+,\\s*"(${HEX})"([\\s\\S]{0,800}?)\\)`, 'g')

const strLitRe = /"([A-Za-z_$][\w$]*)"/g

for (const m of jsText.matchAll(callRegex)) {
const id = m[1]!
const argsBlock = m[2] ?? ''
all.add(id)

const candidates = [...argsBlock.matchAll(strLitRe)]
.map(x => x[1]!)
.filter(n => !KNOWN_NON_NAMES.has(n))
if (candidates.length) byName[candidates[candidates.length - 1]!] = id
const processMatches = (matches: IterableIterator<RegExpMatchArray>) => {
for (const m of matches) {
const id = m[1]!
const argsBlock = m[2] ?? ''
all.add(id)

const candidates = [...argsBlock.matchAll(strLitRe)]
.map(x => x[1]!)
.filter(n => !KNOWN_NON_NAMES.has(n) && n.length > 3)

if (candidates.length) {
byName[candidates[candidates.length - 1]!] = id
}
}
}

// bare reference without a name arg, still record the id
const bareRegex = new RegExp(`createServerReference\\s*\\)?\\s*\\(\\s*"(${HEX})"`, 'g')
for (const m of jsText.matchAll(bareRegex)) all.add(m[1]!)
processMatches(jsText.matchAll(createRegex))
processMatches(jsText.matchAll(registerRegex))

const bareCreateRegex = new RegExp(`createServerReference\\s*\\)?\\s*\\(\\s*"(${HEX})"`, 'g')
const bareRegisterRegex = new RegExp(`registerServerReference\\s*\\)?\\s*\\([^,]+,\\s*"(${HEX})"`, 'g')

for (const m of jsText.matchAll(bareCreateRegex)) all.add(m[1]!)
for (const m of jsText.matchAll(bareRegisterRegex)) all.add(m[1]!)

const actionIdRe = new RegExp(`\\$ACTION_ID_(${HEX})`, 'g')
for (const m of jsText.matchAll(actionIdRe)) all.add(m[1]!)
Expand All @@ -610,13 +623,6 @@ export default class ReactFunc {
`Extracted action ids | named=${Object.keys(byName).length} | total=${all.size}`
)

if (all.size === 0) {
this.bot.logger.debug(
this.bot.isMobile,
'REACT-PARSE',
'No server-action ids found in JS chunk - wrong chunk, or bundler output changed'
)
}
} catch (error) {
this.bot.logger.error(
this.bot.isMobile,
Expand Down
Loading