@@ -15,6 +15,7 @@ interface Props {
1515 userPath : string ;
1616 selectedBayDbId ?: number | null ;
1717 selectedBayId ?: string | null ;
18+ clearSignal ?: number ;
1819}
1920
2021const getBayIdFromEvent = ( e : EventItem ) => {
@@ -35,11 +36,12 @@ const getBayIdFromEvent = (e: EventItem) => {
3536 }
3637} ;
3738
38- const WebhookInspector : React . FC < Props > = ( { userPath, selectedBayDbId = null , selectedBayId = null } ) => {
39+ const WebhookInspector : React . FC < Props > = ( { userPath, selectedBayDbId = null , selectedBayId = null , clearSignal } ) => {
3940 const [ allEvents , setAllEvents ] = React . useState < EventItem [ ] > ( [ ] ) ;
4041 const [ connected , setConnected ] = React . useState ( false ) ;
4142 const [ selectedIndex , setSelectedIndex ] = React . useState < number | null > ( null ) ;
4243 const listRef = React . useRef < HTMLUListElement | null > ( null ) ;
44+ const listContainerRef = React . useRef < HTMLDivElement | null > ( null ) ;
4345
4446 // Fetch initial events
4547 React . useEffect ( ( ) => {
@@ -77,7 +79,20 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
7779 es . onmessage = ( ev ) => {
7880 try {
7981 const data = JSON . parse ( ev . data ) ;
80- setAllEvents ( prev => [ { id : data . id , eventType : data . eventType , timestamp : data . timestamp , data : data . data , raw : data . raw , expanded : false } , ...prev ] ) ;
82+ const newItem : EventItem = { id : data . id , eventType : data . eventType , timestamp : data . timestamp , data : data . data , raw : data . raw , expanded : false } ;
83+ setAllEvents ( prev => [ newItem , ...prev ] ) ;
84+ // If the new item matches the current bay filter (or there is no filter) select it and focus the list
85+ try {
86+ const bayId = getBayIdFromEvent ( newItem ) ;
87+ const matches = ( ! selectedBayDbId && ! selectedBayId ) || ( bayId && ( String ( bayId ) === String ( selectedBayId ) || String ( bayId ) === String ( selectedBayDbId ) ) ) ;
88+ if ( matches ) {
89+ setSelectedIndex ( 0 ) ;
90+ // focus the list container so keyboard navigation continues from the newly added item
91+ setTimeout ( ( ) => listContainerRef . current ?. focus ( ) , 0 ) ;
92+ }
93+ } catch ( e ) {
94+ // ignore
95+ }
8196 } catch ( err ) {
8297 console . warn ( 'Invalid SSE payload' , err ) ;
8398 }
@@ -113,6 +128,30 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
113128 }
114129 } , [ selectedIndex , filtered ] ) ;
115130
131+ // clear local events when requested
132+ React . useEffect ( ( ) => {
133+ if ( typeof clearSignal === 'undefined' ) return ;
134+ setAllEvents ( [ ] ) ;
135+ setSelectedIndex ( null ) ;
136+ } , [ clearSignal ] ) ;
137+
138+ // global fallback: listen for 'webhook:clear' events
139+ React . useEffect ( ( ) => {
140+ const handler = ( ev : any ) => {
141+ try {
142+ if ( ! ev || ! ev . detail ) return ;
143+ const detailPath = ev . detail . userPath ;
144+ if ( ! detailPath ) return ;
145+ if ( String ( detailPath ) === String ( userPath ) ) {
146+ setAllEvents ( [ ] ) ;
147+ setSelectedIndex ( null ) ;
148+ }
149+ } catch ( err ) { /* ignore */ }
150+ } ;
151+ window . addEventListener ( 'webhook:clear' , handler as EventListener ) ;
152+ return ( ) => window . removeEventListener ( 'webhook:clear' , handler as EventListener ) ;
153+ } , [ userPath ] ) ;
154+
116155 const select = ( idx : number ) => {
117156 setSelectedIndex ( idx ) ;
118157 } ;
@@ -132,9 +171,29 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
132171
133172 const selectedEvent = selectedIndex === null ? null : filtered [ selectedIndex ] ;
134173
174+ const getEventModelPayload = ( e : EventItem ) => {
175+ try {
176+ // Common places where the EventModel might appear
177+ const maybe = ( e . data ?? e . raw ) as any ;
178+ if ( ! maybe ) return e . data ?? e . raw ?? { } ;
179+ // If envelope where data contains EventModel
180+ if ( maybe . EventModel ) return maybe . EventModel ;
181+ // Some payloads might have data: { EventModel: {...} }
182+ if ( maybe . data && maybe . data . EventModel ) return maybe . data . EventModel ;
183+ // Some normalized records put typed payload under 'data' already
184+ if ( e . data && ( e . data . EventModel || e . data . eventModel ) ) return e . data . EventModel ?? e . data . eventModel ;
185+ // Fallback to raw.data.EventModel
186+ if ( e . raw && e . raw . data && ( e . raw . data . EventModel || e . raw . data . eventModel ) ) return e . raw . data . EventModel ?? e . raw . data . eventModel ;
187+ // Last resort: return the whole data/raw object
188+ return maybe ;
189+ } catch ( err ) {
190+ return e . data ?? e . raw ?? { } ;
191+ }
192+ } ;
193+
135194 return (
136195 < div className = "webhook-inspector" >
137- < div className = "webhook-inspector-list" tabIndex = { 0 } onKeyDown = { onListKeyDown } >
196+ < div ref = { listContainerRef } className = "webhook-inspector-list" tabIndex = { 0 } onKeyDown = { onListKeyDown } >
138197 < div className = "webhook-events-header" >
139198 < strong > Events</ strong >
140199 < span className = { `webhook-events-status ${ connected ? 'live' : '' } ` } > { connected ? 'live' : 'disconnected' } </ span >
@@ -159,7 +218,7 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedBayDbId = null, s
159218 < h4 className = "preview-title" > { selectedEvent . eventType } </ h4 >
160219 < div className = "preview-time" > { new Date ( selectedEvent . timestamp ) . toLocaleString ( ) } </ div >
161220 { /* Version 1: render JSON fallback of event.data or raw */ }
162- < pre className = "preview-json" > { JSON . stringify ( selectedEvent . data || selectedEvent . raw || { } , null , 2 ) } </ pre >
221+ < pre className = "preview-json" > { JSON . stringify ( getEventModelPayload ( selectedEvent ) , null , 2 ) } </ pre >
163222 </ div >
164223 ) : (
165224 < div className = "preview-empty" > Select an event to preview</ div >
0 commit comments