11/**
2- * Doppar .js - WebSocket Broadcasting Client
2+ * Airbender .js - WebSocket Broadcasting Client
33 *
44 * A powerful, WebSocket client for Doppar framework
55 * Supports public, private, and presence channels with automatic reconnection
88 * @author Mahedi Hasan
99 */
1010
11- class Doppar {
11+ class Airbender {
1212 constructor ( options = { } ) {
1313 this . options = {
1414 host : options . host || "ws://127.0.0.1:6001" ,
@@ -40,9 +40,6 @@ class Doppar {
4040 const protocol = this . options . encrypted ? "wss" : "ws" ;
4141 const host = this . options . host . replace ( / ^ ( w s | w s s ) : \/ \/ / , "" ) ;
4242 const url = `${ protocol } ://${ host } ` ;
43-
44- console . log ( `[Doppar] Connecting to ${ url } ...` ) ;
45-
4643 this . socket = new WebSocket ( url ) ;
4744 this . setupEventHandlers ( ) ;
4845 }
@@ -52,7 +49,6 @@ class Doppar {
5249 */
5350 setupEventHandlers ( ) {
5451 this . socket . onopen = ( ) => {
55- console . log ( "[Doppar] Connection established" ) ;
5652 this . isConnected = true ;
5753 this . reconnectCount = 0 ;
5854
@@ -64,7 +60,16 @@ class Doppar {
6460 } ;
6561
6662 this . socket . onmessage = ( event ) => {
67- this . handleMessage ( JSON . parse ( event . data ) ) ;
63+ let parsed ;
64+
65+ try {
66+ parsed = JSON . parse ( event . data ) ;
67+ } catch ( e ) {
68+ console . error ( "[Doppar] Invalid JSON from server:" , event . data ) ;
69+ return ;
70+ }
71+
72+ this . handleMessage ( parsed ) ;
6873 } ;
6974
7075 this . socket . onerror = ( error ) => {
@@ -73,7 +78,6 @@ class Doppar {
7378 } ;
7479
7580 this . socket . onclose = ( ) => {
76- console . log ( "[Doppar] Connection closed" ) ;
7781 this . isConnected = false ;
7882 this . socketId = null ;
7983 this . trigger ( "disconnected" ) ;
@@ -100,7 +104,7 @@ class Doppar {
100104 break ;
101105
102106 case "doppar:subscription_succeeded" :
103- this . handleSubscriptionSucceeded ( channel ) ;
107+ this . handleSubscriptionSucceeded ( channel , JSON . parse ( data ) ) ;
104108 break ;
105109
106110 case "doppar:member_added" :
@@ -142,8 +146,6 @@ class Doppar {
142146 this . socketId = data . socket_id ;
143147 this . isReady = true ;
144148
145- console . log ( `[Doppar] Socket ID: ${ this . socketId } ` ) ;
146-
147149 // Trigger new "ready" event
148150 this . trigger ( "ready" , this . socketId ) ;
149151
@@ -160,12 +162,11 @@ class Doppar {
160162 /**
161163 * Handle subscription succeeded
162164 */
163- handleSubscriptionSucceeded ( channelName ) {
165+ handleSubscriptionSucceeded ( channelName , payload ) {
164166 const channel = this . channels . get ( channelName ) ;
165167 if ( channel ) {
166168 channel . subscribed = true ;
167- channel . trigger ( "subscribed" ) ;
168- console . log ( `[Doppar] Subscribed to ${ channelName } ` ) ;
169+ channel . trigger ( "subscribed" , payload ) ;
169170 }
170171 }
171172
@@ -188,7 +189,10 @@ class Doppar {
188189 handleMemberAdded ( channelName , data ) {
189190 const channel = this . channels . get ( channelName ) ;
190191 if ( channel && channel . type === "presence" ) {
191- channel . members . push ( data ) ;
192+ if ( ! channel . members . find ( ( m ) => m . user_id === data . user_id ) ) {
193+ channel . members . push ( data ) ;
194+ }
195+
192196 channel . trigger ( "member-added" , data ) ;
193197 }
194198 }
@@ -214,7 +218,7 @@ class Doppar {
214218 return this . channels . get ( channelName ) ;
215219 }
216220
217- const channel = new DopparChannel ( this , channelName , "public" ) ;
221+ const channel = new AirbenderChannel ( this , channelName , "public" ) ;
218222 this . channels . set ( channelName , channel ) ;
219223 channel . subscribe ( ) ;
220224
@@ -233,7 +237,7 @@ class Doppar {
233237 return this . channels . get ( fullName ) ;
234238 }
235239
236- const channel = new DopparPrivateChannel ( this , fullName ) ;
240+ const channel = new AirbenderPrivateChannel ( this , fullName ) ;
237241 this . channels . set ( fullName , channel ) ;
238242 channel . subscribe ( ) ;
239243
@@ -252,7 +256,7 @@ class Doppar {
252256 return this . channels . get ( fullName ) ;
253257 }
254258
255- const channel = new DopparPresenceChannel ( this , fullName ) ;
259+ const channel = new AirbenderPresenceChannel ( this , fullName ) ;
256260 this . channels . set ( fullName , channel ) ;
257261 channel . subscribe ( ) ;
258262
@@ -274,8 +278,12 @@ class Doppar {
274278 * Send message to server
275279 */
276280 send ( message ) {
277- if ( this . isConnected && this . socket . readyState === WebSocket . OPEN ) {
278- this . socket . send ( JSON . stringify ( message ) ) ;
281+ if ( this . socket && this . socket . readyState === WebSocket . OPEN ) {
282+ try {
283+ this . socket . send ( JSON . stringify ( message ) ) ;
284+ } catch ( e ) {
285+ this . messageQueue . push ( message ) ;
286+ }
279287 } else {
280288 this . messageQueue . push ( message ) ;
281289 }
@@ -286,19 +294,26 @@ class Doppar {
286294 */
287295 processMessageQueue ( ) {
288296 while ( this . messageQueue . length > 0 ) {
289- const message = this . messageQueue . shift ( ) ;
290- this . send ( message ) ;
297+ const queue = [ ...this . messageQueue ] ;
298+ this . messageQueue = [ ] ;
299+ queue . forEach ( ( msg ) => this . send ( msg ) ) ;
291300 }
292301 }
293302
303+ cleanup ( ) {
304+ if ( ! this . socket ) return ;
305+ this . socket . onopen = null ;
306+ this . socket . onmessage = null ;
307+ this . socket . onerror = null ;
308+ this . socket . onclose = null ;
309+ }
310+
294311 /**
295312 * Reconnect to server
296313 */
297314 reconnect ( ) {
315+ this . cleanup ( ) ;
298316 this . reconnectCount ++ ;
299- console . log (
300- `[Doppar] Reconnecting (${ this . reconnectCount } /${ this . options . reconnectAttempts } )...`
301- ) ;
302317
303318 setTimeout ( ( ) => {
304319 this . connect ( ) ;
@@ -345,7 +360,7 @@ class Doppar {
345360/**
346361 * Base Channel Class
347362 */
348- class DopparChannel {
363+ class AirbenderChannel {
349364 constructor ( doppar , name , type = "public" ) {
350365 this . doppar = doppar ;
351366 this . name = name ;
@@ -370,10 +385,8 @@ class DopparChannel {
370385 * Resubscribe to channel (after reconnection)
371386 */
372387 resubscribe ( ) {
373- if ( this . subscribed ) {
374- this . subscribed = false ;
375- this . subscribe ( ) ;
376- }
388+ this . subscribed = false ;
389+ this . subscribe ( ) ;
377390 }
378391
379392 /**
@@ -426,7 +439,7 @@ class DopparChannel {
426439/**
427440 * Private Channel Class
428441 */
429- class DopparPrivateChannel extends DopparChannel {
442+ class AirbenderPrivateChannel extends AirbenderChannel {
430443 constructor ( doppar , name ) {
431444 super ( doppar , name , "private" ) ;
432445 }
@@ -495,7 +508,7 @@ class DopparPrivateChannel extends DopparChannel {
495508/**
496509 * Presence Channel Class
497510 */
498- class DopparPresenceChannel extends DopparPrivateChannel {
511+ class AirbenderPresenceChannel extends AirbenderPrivateChannel {
499512 constructor ( doppar , name ) {
500513 super ( doppar , name ) ;
501514 this . type = "presence" ;
@@ -520,11 +533,11 @@ class DopparPresenceChannel extends DopparPrivateChannel {
520533 * Get here (current members)
521534 */
522535 here ( callback ) {
523- return this . listen ( " subscribed" , ( data ) => {
524- if ( data && data . presence ) {
525- callback ( this . members ) ;
526- }
527- } ) ;
536+ if ( this . subscribed && this . members . length > 0 ) {
537+ callback ( this . members ) ;
538+ }
539+
540+ return super . listen ( "subscribed" , ( ) => callback ( this . members ) ) ;
528541 }
529542
530543 /**
@@ -544,8 +557,8 @@ class DopparPresenceChannel extends DopparPrivateChannel {
544557
545558// Export for use in different environments
546559if ( typeof module !== "undefined" && module . exports ) {
547- module . exports = Doppar ;
560+ module . exports = Airbender ;
548561}
549562if ( typeof window !== "undefined" ) {
550- window . Doppar = Doppar ;
563+ window . Airbender = Airbender ;
551564}
0 commit comments