1- import { Channel , CondVar } from './utils.js' ;
2- import { decodeSessionMessage , encodeSessionMessage } from './msgpack.js' ;
3- import { Mutex } from 'async-mutex' ;
1+ import { v4 as uuidv4 } from 'uuid' ;
2+
3+ import { Channel , CondVar , Mutex } from './utils.js' ;
4+
5+ let kWorker : Worker | null = null ;
6+
7+ class WSWorkerManager {
8+ private static instance : WSWorkerManager | null = null ;
9+
10+ private _openHandlers : Map < string , ( ) => void | Promise < void > > ;
11+ private _messageHandlers : Map <
12+ string ,
13+ ( msg : SessionMessage ) => void | Promise < void >
14+ > ;
15+ private _errorHandlers : Map <
16+ string ,
17+ ( message : string ) => void | Promise < void >
18+ > ;
19+ private _closeHandlers : Map <
20+ string ,
21+ ( event ?: CloseEvent ) => void | Promise < void >
22+ > ;
23+
24+ private constructor ( ) {
25+ this . _openHandlers = new Map ( ) ;
26+ this . _messageHandlers = new Map ( ) ;
27+ this . _errorHandlers = new Map ( ) ;
28+ this . _closeHandlers = new Map ( ) ;
29+
30+ kWorker = new Worker ( new URL ( './wsworker.js' , import . meta. url ) , {
31+ type : 'module' ,
32+ } ) ;
33+
34+ kWorker . onmessage = async ( event : MessageEvent ) => {
35+ const { socketId, type, message } = event . data ;
36+
37+ switch ( type ) {
38+ case 'open' : {
39+ const openHandler = this . _openHandlers . get ( socketId ) ;
40+ if ( openHandler ) {
41+ await openHandler ( ) ;
42+ }
43+ break ;
44+ }
45+ case 'message' : {
46+ const messageHandler = this . _messageHandlers . get ( socketId ) ;
47+ if ( messageHandler ) {
48+ await messageHandler ( message ) ;
49+ }
50+ break ;
51+ }
52+ case 'error' : {
53+ const errorHandler = this . _errorHandlers . get ( socketId ) ;
54+ if ( errorHandler ) {
55+ await errorHandler ( message ) ;
56+ }
57+ break ;
58+ }
59+ case 'close' : {
60+ const closeHandler = this . _closeHandlers . get ( socketId ) ;
61+ if ( closeHandler ) {
62+ await closeHandler ( ) ;
63+ }
64+ break ;
65+ }
66+ }
67+ } ;
68+ }
69+
70+ public static getInstance ( ) : WSWorkerManager {
71+ if ( ! WSWorkerManager . instance ) {
72+ WSWorkerManager . instance = new WSWorkerManager ( ) ;
73+ }
74+ return WSWorkerManager . instance ;
75+ }
76+
77+ public createSocket (
78+ socketId : string ,
79+ url : string ,
80+ onOpen ?: ( ) => void | Promise < void > ,
81+ onMessage ?: ( msg : SessionMessage ) => void | Promise < void > ,
82+ onError ?: ( message : string ) => void | Promise < void > ,
83+ onClose ?: ( event ?: CloseEvent ) => void | Promise < void > ,
84+ ) {
85+ if ( this . _messageHandlers . has ( socketId ) ) {
86+ throw new Error ( `Socket with id ${ socketId } already exists` ) ;
87+ }
88+
89+ this . _openHandlers . set ( socketId , onOpen || ( ( ) => { } ) ) ;
90+ this . _messageHandlers . set ( socketId , onMessage || ( ( ) => { } ) ) ;
91+ this . _errorHandlers . set ( socketId , onError || ( ( ) => { } ) ) ;
92+ this . _closeHandlers . set ( socketId , ( ) => {
93+ onClose ( ) ;
94+ this . _openHandlers . delete ( socketId ) ;
95+ this . _messageHandlers . delete ( socketId ) ;
96+ this . _errorHandlers . delete ( socketId ) ;
97+ this . _closeHandlers . delete ( socketId ) ;
98+ } ) ;
99+
100+ const send = ( message : SessionMessage ) => {
101+ if ( ! this . _messageHandlers . has ( socketId ) ) {
102+ throw new Error ( `Socket with id ${ socketId } does not exist` ) ;
103+ }
104+ kWorker . postMessage ( {
105+ command : 'send' ,
106+ socketId,
107+ message,
108+ } ) ;
109+ } ;
110+
111+ const close = ( ) => {
112+ if ( ! this . _messageHandlers . has ( socketId ) ) {
113+ return ;
114+ }
115+ kWorker . postMessage ( {
116+ command : 'close' ,
117+ socketId,
118+ } ) ;
119+ } ;
120+
121+ kWorker . postMessage ( {
122+ command : 'open' ,
123+ socketId,
124+ url,
125+ } ) ;
126+ console . log ( `Creating socket with id ${ socketId } for URL ${ url } ` ) ;
127+
128+ return {
129+ send,
130+ close,
131+ } ;
132+ }
133+ }
4134
5135export class EvergreenStream {
6- private socket : WebSocket ;
7- private readonly url : string ;
136+ private readonly uid : string ;
8137
9138 private channel : Channel < SessionMessage | null > ;
10139
11140 private readonly mutex : Mutex ;
12141 private readonly cv : CondVar ;
13142
143+ private socket : {
144+ send : ( message : SessionMessage ) => void ;
145+ close : ( ) => void ;
146+ } ;
14147 private socketOpen : boolean ;
15148 private closed : boolean ;
16149
17150 constructor ( url : string ) {
18- this . url = url ;
19- this . socket = new WebSocket ( this . url ) ;
151+ this . uid = uuidv4 ( ) ;
20152
21153 this . channel = new Channel < SessionMessage | null > ( ) ;
22154
@@ -26,27 +158,50 @@ export class EvergreenStream {
26158 this . mutex = new Mutex ( ) ;
27159 this . cv = new CondVar ( ) ;
28160
29- this . socket . onopen = async ( ) => {
161+ const onopen = async ( ) => {
30162 await this . mutex . runExclusive ( async ( ) => {
31163 console . log ( 'socket opened' ) ;
32164 this . socketOpen = true ;
33165 this . cv . notifyAll ( ) ;
34166 } ) ;
35167 } ;
36168
37- this . socket . onerror = async ( event ) => {
169+ const onerror = async ( message : string ) => {
170+ await this . mutex . runExclusive ( async ( ) => {
171+ if ( this . closed ) {
172+ return ;
173+ }
174+ console . error ( 'socket error:' , message ) ;
175+ await this . closeInternal ( ) ;
176+ } ) ;
177+ } ;
178+
179+ const onmessage = async ( message : SessionMessage ) => {
180+ await this . channel . sendNowait ( message ) ;
181+ } ;
182+
183+ const onclose = async ( ) => {
38184 await this . mutex . runExclusive ( async ( ) => {
39185 if ( this . closed ) {
40186 return ;
41187 }
42- console . error ( 'socket error:' , event ) ;
188+ console . log ( 'socket closed.' ) ;
43189 await this . closeInternal ( ) ;
44190 } ) ;
45191 } ;
46192
47- this . socket . onmessage = async ( event ) => {
48- const array = new Uint8Array ( await event . data . arrayBuffer ( ) ) ;
49- await this . channel . sendNowait ( decodeSessionMessage ( array ) ) ;
193+ const wsWorkerManager = WSWorkerManager . getInstance ( ) ;
194+ const { send, close } = wsWorkerManager . createSocket (
195+ this . uid ,
196+ url ,
197+ onopen ,
198+ onmessage ,
199+ onerror ,
200+ onclose ,
201+ ) ;
202+ this . socket = {
203+ send : send ,
204+ close : close ,
50205 } ;
51206 }
52207
@@ -64,7 +219,7 @@ export class EvergreenStream {
64219 }
65220 } ) ;
66221
67- this . socket . send ( encodeSessionMessage ( message ) ) ;
222+ this . socket . send ( message ) ;
68223 }
69224
70225 private async closeInternal ( ) {
0 commit comments