@@ -8,14 +8,17 @@ import {
88 messageIdKey ,
99 sessionIdFromParams ,
1010} from "./protocol.js" ;
11-
11+ import { onWebSocket , webSocketMessageToString } from "./ws-utils.js" ;
1212import type { Agent , AgentSideConnection } from "./acp.js" ;
1313import type {
1414 ConnectionRegistry ,
1515 ConnectionState ,
1616 ResponseRoute ,
1717} from "./connection.js" ;
1818import type { AnyMessage , AnyRequest } from "./jsonrpc.js" ;
19+ import type { WebSocketLike } from "./ws-utils.js" ;
20+
21+ export type WebSocketServerSocket = WebSocketLike ;
1922
2023type ForwardResult =
2124 | {
@@ -26,27 +29,13 @@ type ForwardResult =
2629 message : string ;
2730 } ;
2831
29- export interface WebSocketServerSocket {
30- readonly readyState ?: number ;
31- send ( data : string ) : void ;
32- close ( code ?: number , reason ?: string ) : void ;
33- addEventListener ?( type : string , listener : ( event : unknown ) => void ) : void ;
34- removeEventListener ?( type : string , listener : ( event : unknown ) => void ) : void ;
35- on ?( type : string , listener : ( ...args : unknown [ ] ) => void ) : unknown ;
36- off ?( type : string , listener : ( ...args : unknown [ ] ) => void ) : unknown ;
37- removeListener ?(
38- type : string ,
39- listener : ( ...args : unknown [ ] ) => void ,
40- ) : unknown ;
41- }
42-
4332export interface WebSocketConnectionOptions {
4433 readonly registry : ConnectionRegistry ;
4534 readonly createAgent : ( conn : AgentSideConnection ) => Agent ;
4635}
4736
4837export function handleWebSocketConnection (
49- socket : WebSocketServerSocket ,
38+ socket : WebSocketLike ,
5039 options : WebSocketConnectionOptions ,
5140) : void {
5241 const session = new WebSocketServerSession ( socket , options ) ;
@@ -56,29 +45,30 @@ export function handleWebSocketConnection(
5645class WebSocketServerSession {
5746 private connection : ConnectionState | undefined ;
5847 private outboundReader : ReadableStreamDefaultReader < AnyMessage > | undefined ;
48+ private inboundWriteChain : Promise < void > = Promise . resolve ( ) ;
5949 private isClosed = false ;
6050 private readonly detachListeners : Array < ( ) => void > = [ ] ;
6151
6252 constructor (
63- private readonly socket : WebSocketServerSocket ,
53+ private readonly socket : WebSocketLike ,
6454 private readonly options : WebSocketConnectionOptions ,
6555 ) { }
6656
6757 start ( ) : void {
6858 this . detachListeners . push (
69- onSocket ( this . socket , "message" , ( ...args ) => {
59+ onWebSocket ( this . socket , "message" , ( ...args ) => {
7060 void this . handleSocketMessage ( args ) ;
7161 } ) ,
7262 ) ;
7363
7464 this . detachListeners . push (
75- onSocket ( this . socket , "close" , ( ) => {
65+ onWebSocket ( this . socket , "close" , ( ) => {
7666 void this . closeSession ( ) ;
7767 } ) ,
7868 ) ;
7969
8070 this . detachListeners . push (
81- onSocket ( this . socket , "error" , ( ) => {
71+ onWebSocket ( this . socket , "error" , ( ) => {
8272 void this . shutdown ( 1011 , "WebSocket error" ) ;
8373 } ) ,
8474 ) ;
@@ -89,7 +79,7 @@ class WebSocketServerSession {
8979 return ;
9080 }
9181
92- const text = socketMessageToString ( args ) ;
82+ const text = webSocketMessageToString ( args ) ;
9383 if ( text === undefined ) {
9484 console . warn ( "Ignoring non-text ACP WebSocket frame" ) ;
9585 return ;
@@ -202,19 +192,33 @@ class WebSocketServerSession {
202192 connection . pendingRoutes . set ( key , route ) ;
203193 }
204194
205- await writeInbound ( connection , message ) ;
195+ await this . writeInbound ( message ) ;
206196 return { ok : true } ;
207197 }
208198
209199 if ( isResponseMessage ( message ) ) {
210- await writeInbound ( connection , message ) ;
200+ await this . writeInbound ( message ) ;
211201 return { ok : true } ;
212202 }
213203
214- await writeInbound ( connection , message ) ;
204+ await this . writeInbound ( message ) ;
215205 return { ok : true } ;
216206 }
217207
208+ private async writeInbound ( message : AnyMessage ) : Promise < void > {
209+ const connection = this . connection ;
210+
211+ if ( ! connection ) {
212+ throw new Error ( "ACP WebSocket connection is not initialized" ) ;
213+ }
214+
215+ const write = this . inboundWriteChain . then ( ( ) =>
216+ writeInbound ( connection , message ) ,
217+ ) ;
218+ this . inboundWriteChain = write . catch ( ( ) => undefined ) ;
219+ await write ;
220+ }
221+
218222 private startOutboundPump ( connection : ConnectionState ) : void {
219223 const subscription = connection . allOutbound . subscribe ( ) ;
220224 const reader = subscription . stream . getReader ( ) ;
@@ -345,81 +349,3 @@ function determineWebSocketRoute(message: AnyRequest): ResponseRoute {
345349
346350 return "connection" ;
347351}
348-
349- function onSocket (
350- socket : WebSocketServerSocket ,
351- type : string ,
352- listener : ( ...args : unknown [ ] ) => void ,
353- ) : ( ) => void {
354- if ( socket . addEventListener ) {
355- const eventListener = ( event : unknown ) => listener ( event ) ;
356- socket . addEventListener ( type , eventListener ) ;
357-
358- return ( ) => {
359- socket . removeEventListener ?.( type , eventListener ) ;
360- } ;
361- }
362-
363- if ( socket . on ) {
364- socket . on ( type , listener ) ;
365-
366- return ( ) => {
367- if ( socket . off ) {
368- socket . off ( type , listener ) ;
369- return ;
370- }
371-
372- socket . removeListener ?.( type , listener ) ;
373- } ;
374- }
375-
376- throw new Error ( "WebSocket object does not support event listeners" ) ;
377- }
378-
379- function socketMessageToString ( args : unknown [ ] ) : string | undefined {
380- const data = extractMessageData ( args ) ;
381-
382- if ( typeof data === "string" ) {
383- return data ;
384- }
385-
386- if ( data instanceof ArrayBuffer || ArrayBuffer . isView ( data ) ) {
387- return new TextDecoder ( ) . decode ( data ) ;
388- }
389-
390- if ( Array . isArray ( data ) && data . every ( ArrayBuffer . isView ) ) {
391- return decodeArrayBufferViews ( data ) ;
392- }
393-
394- return undefined ;
395- }
396-
397- function extractMessageData ( args : unknown [ ] ) : unknown {
398- const [ first ] = args ;
399-
400- if ( isMessageEventLike ( first ) ) {
401- return first . data ;
402- }
403-
404- return first ;
405- }
406-
407- function isMessageEventLike ( value : unknown ) : value is { data : unknown } {
408- return typeof value === "object" && value !== null && "data" in value ;
409- }
410-
411- function decodeArrayBufferViews ( views : ArrayBufferView [ ] ) : string {
412- const totalLength = views . reduce ( ( sum , view ) => sum + view . byteLength , 0 ) ;
413- const combined = new Uint8Array ( totalLength ) ;
414- let offset = 0 ;
415-
416- for ( const view of views ) {
417- combined . set (
418- new Uint8Array ( view . buffer , view . byteOffset , view . byteLength ) ,
419- offset ,
420- ) ;
421- offset += view . byteLength ;
422- }
423-
424- return new TextDecoder ( ) . decode ( combined ) ;
425- }
0 commit comments