@@ -4,7 +4,6 @@ import type { EventContext } from '../../../context'
44import type { DirectionalEventa , Eventa } from '../../../eventa'
55
66import { createContext as createBaseContext } from '../../../context'
7- import { registerInvokeAbortEventListeners } from '../../../context-extension-invoke-internal'
87import { and , defineEventa , defineInboundEventa , defineOutboundEventa , EventaFlowDirection , matchBy } from '../../../eventa'
98import { generateWebsocketPayload , parseWebsocketPayload } from '../internal'
109
@@ -13,28 +12,12 @@ export const wsDisconnectedEvent = defineEventa<{ id: string }>('eventa:adapters
1312export const wsErrorEvent = defineEventa < { error : unknown } > ( 'eventa:adapters:websocket-peer:error' )
1413
1514export function createPeerContext ( peer : Peer ) : {
16- hooks : Pick < Hooks , 'message' >
15+ hooks : Pick < Hooks , 'message' | 'close' | 'error' >
1716 context : EventContext < any , { raw : { message : Message } } >
1817} {
1918 const peerId = peer . id
2019 const ctx = createBaseContext < any , { raw : { message : Message } } > ( )
2120
22- // Reject any in-flight `defineInvoke(...)` promises if this peer's transport
23- // dies. Mirrors the native ws adapter so server-side code that issues an
24- // invoke back to a client (push-style RPC) doesn't hang on close.
25- registerInvokeAbortEventListeners ( ctx , wsDisconnectedEvent , ( payload ) => {
26- if ( payload . id === wsDisconnectedEvent . id ) {
27- const id = ( payload as Eventa < { id ?: string } > ) . body ?. id
28- return new Error ( `eventa: invoke cancelled, peer disconnected${ id ? ` (${ id } )` : '' } ` )
29- }
30- if ( payload . id === wsErrorEvent . id ) {
31- const err = ( payload as Eventa < { error ?: unknown } > ) . body ?. error
32- return err instanceof Error ? err : new Error ( 'eventa: invoke cancelled, peer error' )
33- }
34- return undefined
35- } )
36- registerInvokeAbortEventListeners ( ctx , wsErrorEvent )
37-
3821 ctx . on ( and (
3922 matchBy ( ( e : DirectionalEventa < any > ) => e . _flowDirection === EventaFlowDirection . Outbound || ! e . _flowDirection ) ,
4023 matchBy ( '*' ) ,
@@ -52,11 +35,30 @@ export function createPeerContext(peer: Peer): {
5235 ctx . emit ( defineInboundEventa ( type ) , payload . body , { raw : { message } } )
5336 }
5437 catch ( error ) {
38+ // Per-message parse failure — recoverable, do NOT abort lifetime.
5539 console . error ( 'Failed to parse WebSocket message:' , error )
5640 ctx . emit ( wsErrorEvent , { error } , { raw : { message } } )
5741 }
5842 }
5943 } ,
44+ close ( peer , details ) {
45+ // crossws fires close for ANY peer; filter to our own.
46+ if ( peer . id !== peerId ) {
47+ return
48+ }
49+ const reasonText = details . reason ? ` (${ details . reason } )` : ''
50+ // Cascade-cancel any in-flight `defineInvoke(...)` so server-side code
51+ // that issued an invoke back to this peer doesn't hang on close.
52+ ctx . abort ( new Error ( `eventa: invoke cancelled, peer disconnected${ reasonText } ` ) )
53+ ctx . emit ( wsDisconnectedEvent , { id : peerId } )
54+ } ,
55+ error ( peer , error ) {
56+ if ( peer . id !== peerId ) {
57+ return
58+ }
59+ ctx . abort ( error instanceof Error ? error : new Error ( 'eventa: invoke cancelled, peer error' ) )
60+ ctx . emit ( wsErrorEvent , { error } )
61+ } ,
6062 } ,
6163 context : ctx ,
6264 }
@@ -70,18 +72,30 @@ export function createPeerHooks(): { hooks: Partial<Hooks>, untilLeastOneConnect
7072 resolve = r
7173 } )
7274
75+ // NOTICE: single-peer model — these closure-scoped hook refs get overwritten
76+ // when a second peer connects, so `createPeerHooks` only correctly serves the
77+ // most-recently-opened peer. Multi-peer support requires a peerId-keyed Map
78+ // and a different "untilLeastOneConnected" semantic; out of scope here.
7379 let message : Hooks [ 'message' ] | undefined
80+ let close : Hooks [ 'close' ] | undefined
81+ let error : Hooks [ 'error' ] | undefined
7482
75- const hooks : Pick < Hooks , 'open' | 'message' > = {
83+ const hooks : Pick < Hooks , 'open' | 'message' | 'close' | 'error' > = {
7684 open : ( peer ) => {
7785 const { context, hooks } = createPeerContext ( peer )
7886 message = hooks . message
87+ close = hooks . close
88+ error = hooks . error
7989 resolve ( { peer, context } )
8090 } ,
8191 message : ( peer , msg ) => {
82- if ( message != null ) {
83- message ( peer , msg )
84- }
92+ message ?.( peer , msg )
93+ } ,
94+ close : ( peer , details ) => {
95+ close ?.( peer , details )
96+ } ,
97+ error : ( peer , err ) => {
98+ error ?.( peer , err )
8599 } ,
86100 }
87101
0 commit comments