@@ -27,6 +27,7 @@ import {
2727} from "../navigation-guard.js" ;
2828import { getBrowserProfileCapabilities } from "../profile-capabilities.js" ;
2929import type { BrowserRouteContext } from "../server-context.js" ;
30+ import { resolveTargetIdFromTabs } from "../target-id.js" ;
3031import { matchBrowserUrlPattern } from "../url-pattern.js" ;
3132import { registerBrowserAgentActDownloadRoutes } from "./agent.act.download.js" ;
3233import {
@@ -35,7 +36,7 @@ import {
3536 jsonActError ,
3637} from "./agent.act.errors.js" ;
3738import { registerBrowserAgentActHookRoutes } from "./agent.act.hooks.js" ;
38- import { normalizeActRequest , validateBatchTargetIds } from "./agent.act.normalize.js" ;
39+ import { normalizeActRequest } from "./agent.act.normalize.js" ;
3940import { type ActKind , isActKind } from "./agent.act.shared.js" ;
4041import {
4142 readBody ,
@@ -357,6 +358,66 @@ function getExistingSessionUnsupportedMessage(action: BrowserActRequest): string
357358 throw new Error ( "Unsupported browser act kind" ) ;
358359}
359360
361+ type TargetComparableTab = {
362+ targetId : string ;
363+ suggestedTargetId ?: string ;
364+ tabId ?: string ;
365+ label ?: string ;
366+ } ;
367+
368+ function actionContainsTargetId ( action : BrowserActRequest ) : boolean {
369+ if ( action . targetId ) {
370+ return true ;
371+ }
372+ return action . kind === "batch" && action . actions . some ( actionContainsTargetId ) ;
373+ }
374+
375+ function actionTargetIdMatchesTab ( params : {
376+ requestedTargetId : string ;
377+ selectedTargetId : string ;
378+ tabs : TargetComparableTab [ ] ;
379+ } ) : boolean {
380+ if ( params . requestedTargetId === params . selectedTargetId ) {
381+ return true ;
382+ }
383+ const resolved = resolveTargetIdFromTabs ( params . requestedTargetId , params . tabs ) ;
384+ return resolved . ok && resolved . targetId === params . selectedTargetId ;
385+ }
386+
387+ function normalizeActionTargetIdsForSelectedTab ( params : {
388+ action : BrowserActRequest ;
389+ selectedTargetId : string ;
390+ tabs : TargetComparableTab [ ] ;
391+ mismatchMessage : string ;
392+ } ) : string | null {
393+ if ( params . action . targetId ) {
394+ if (
395+ ! actionTargetIdMatchesTab ( {
396+ requestedTargetId : params . action . targetId ,
397+ selectedTargetId : params . selectedTargetId ,
398+ tabs : params . tabs ,
399+ } )
400+ ) {
401+ return params . mismatchMessage ;
402+ }
403+ params . action . targetId = params . selectedTargetId ;
404+ }
405+ if ( params . action . kind === "batch" ) {
406+ for ( const nestedAction of params . action . actions ) {
407+ const nestedError = normalizeActionTargetIdsForSelectedTab ( {
408+ action : nestedAction ,
409+ selectedTargetId : params . selectedTargetId ,
410+ tabs : params . tabs ,
411+ mismatchMessage : "batched action targetId must match request targetId" ,
412+ } ) ;
413+ if ( nestedError ) {
414+ return nestedError ;
415+ }
416+ }
417+ }
418+ return null ;
419+ }
420+
360421/** Register browser action endpoints, including hook and download subroutes. */
361422export function registerBrowserAgentActRoutes (
362423 app : BrowserRouteRegistrar ,
@@ -412,6 +473,9 @@ export function registerBrowserAgentActRoutes(
412473 const hasNavigationResultPolicy = Boolean (
413474 withBrowserNavigationPolicy ( ssrfPolicy ) . ssrfPolicy ,
414475 ) ;
476+ const targetComparableTabs = actionContainsTargetId ( action )
477+ ? await profileCtx . listTabs ( ) . catch ( ( ) => [ ] as TargetComparableTab [ ] )
478+ : [ ] ;
415479 const jsonOk = async (
416480 extra ?: Record < string , unknown > ,
417481 options ?: { resolveCurrentTarget ?: boolean } ,
@@ -441,13 +505,14 @@ export function registerBrowserAgentActRoutes(
441505 ...extra ,
442506 } ) ;
443507 } ;
444- if ( action . targetId && action . targetId !== tab . targetId ) {
445- return jsonActError (
446- res ,
447- 403 ,
448- ACT_ERROR_CODES . targetIdMismatch ,
449- "action targetId must match request targetId" ,
450- ) ;
508+ const targetIdError = normalizeActionTargetIdsForSelectedTab ( {
509+ action,
510+ selectedTargetId : tab . targetId ,
511+ tabs : targetComparableTabs ,
512+ mismatchMessage : "action targetId must match request targetId" ,
513+ } ) ;
514+ if ( targetIdError ) {
515+ return jsonActError ( res , 403 , ACT_ERROR_CODES . targetIdMismatch , targetIdError ) ;
451516 }
452517 const profileName = profileCtx . profile . name ;
453518 if ( isExistingSession ) {
@@ -661,12 +726,6 @@ export function registerBrowserAgentActRoutes(
661726 if ( ! pw ) {
662727 return ;
663728 }
664- if ( action . kind === "batch" ) {
665- const targetIdError = validateBatchTargetIds ( action . actions , tab . targetId ) ;
666- if ( targetIdError ) {
667- return jsonActError ( res , 403 , ACT_ERROR_CODES . targetIdMismatch , targetIdError ) ;
668- }
669- }
670729 const result = await pw . executeActViaPlaywright ( {
671730 cdpUrl,
672731 action,
0 commit comments