2026-01-25 07:22:36 -05:00
import type { IncomingMessage , ServerResponse } from "node:http" ;
2026-02-18 01:29:02 +00:00
import { normalizePluginHttpPath } from "./http-path.js" ;
2026-03-07 19:54:53 +00:00
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js" ;
2026-02-18 01:34:35 +00:00
import type { PluginHttpRouteRegistration , PluginRegistry } from "./registry.js" ;
2026-02-01 10:03:47 +09:00
import { requireActivePluginRegistry } from "./runtime.js" ;
2026-01-25 07:22:36 -05:00
export type PluginHttpRouteHandler = (
req : IncomingMessage ,
res : ServerResponse ,
2026-03-02 16:22:31 +00:00
) = > Promise < boolean | void > | boolean | void ;
2026-01-25 07:22:36 -05:00
export function registerPluginHttpRoute ( params : {
path? : string | null ;
fallbackPath? : string | null ;
handler : PluginHttpRouteHandler ;
2026-03-02 16:47:51 +00:00
auth : PluginHttpRouteRegistration [ "auth" ] ;
2026-03-02 16:22:31 +00:00
match? : PluginHttpRouteRegistration [ "match" ] ;
2026-03-02 16:47:51 +00:00
replaceExisting? : boolean ;
2026-01-25 07:22:36 -05:00
pluginId? : string ;
source? : string ;
accountId? : string ;
log ? : ( message : string ) = > void ;
registry? : PluginRegistry ;
} ) : ( ) = > void {
const registry = params . registry ? ? requireActivePluginRegistry ( ) ;
const routes = registry . httpRoutes ? ? [ ] ;
registry . httpRoutes = routes ;
const normalizedPath = normalizePluginHttpPath ( params . path , params . fallbackPath ) ;
const suffix = params . accountId ? ` for account " ${ params . accountId } " ` : "" ;
if ( ! normalizedPath ) {
params . log ? . ( ` plugin: webhook path missing ${ suffix } ` ) ;
return ( ) = > { } ;
}
2026-03-02 16:22:31 +00:00
const routeMatch = params . match ? ? "exact" ;
2026-03-07 19:54:53 +00:00
const overlappingRoute = findOverlappingPluginHttpRoute ( routes , {
path : normalizedPath ,
match : routeMatch ,
} ) ;
if ( overlappingRoute && overlappingRoute . auth !== params . auth ) {
params . log ? . (
` plugin: route overlap denied at ${ normalizedPath } ( ${ routeMatch } , ${ params . auth } ) ${ suffix } ; ` +
` overlaps ${ overlappingRoute . path } ( ${ overlappingRoute . match } , ${ overlappingRoute . auth } ) ` +
` owned by ${ overlappingRoute . pluginId ? ? "unknown-plugin" } ( ${ overlappingRoute . source ? ? "unknown-source" } ) ` ,
) ;
return ( ) = > { } ;
}
2026-03-02 16:22:31 +00:00
const existingIndex = routes . findIndex (
( entry ) = > entry . path === normalizedPath && entry . match === routeMatch ,
) ;
2026-02-24 04:01:41 +00:00
if ( existingIndex >= 0 ) {
2026-03-02 16:47:51 +00:00
const existing = routes [ existingIndex ] ;
if ( ! existing ) {
return ( ) = > { } ;
}
if ( ! params . replaceExisting ) {
params . log ? . (
` plugin: route conflict at ${ normalizedPath } ( ${ routeMatch } ) ${ suffix } ; owned by ${ existing . pluginId ? ? "unknown-plugin" } ( ${ existing . source ? ? "unknown-source" } ) ` ,
) ;
return ( ) = > { } ;
}
if ( existing . pluginId && params . pluginId && existing . pluginId !== params . pluginId ) {
params . log ? . (
` plugin: route replacement denied for ${ normalizedPath } ( ${ routeMatch } ) ${ suffix } ; owned by ${ existing . pluginId } ` ,
) ;
return ( ) = > { } ;
}
2026-01-25 07:22:36 -05:00
const pluginHint = params . pluginId ? ` ( ${ params . pluginId } ) ` : "" ;
2026-03-02 16:22:31 +00:00
params . log ? . (
` plugin: replacing stale webhook path ${ normalizedPath } ( ${ routeMatch } ) ${ suffix } ${ pluginHint } ` ,
) ;
2026-02-24 04:01:41 +00:00
routes . splice ( existingIndex , 1 ) ;
2026-01-25 07:22:36 -05:00
}
const entry : PluginHttpRouteRegistration = {
path : normalizedPath ,
handler : params.handler ,
2026-03-02 16:47:51 +00:00
auth : params.auth ,
2026-03-02 16:22:31 +00:00
match : routeMatch ,
2026-01-25 07:22:36 -05:00
pluginId : params.pluginId ,
source : params.source ,
} ;
routes . push ( entry ) ;
return ( ) = > {
const index = routes . indexOf ( entry ) ;
if ( index >= 0 ) {
routes . splice ( index , 1 ) ;
}
} ;
}