@@ -25,6 +25,7 @@ export class PusherListener {
2525 private eventHandlersMap : Map < string , ( ) => void > = new Map ( ) ;
2626 private globalCallback : ( ( eventName : string , data : unknown ) => void ) | null = null ;
2727 private onError ?: ( error : Error ) => void ;
28+ private destroyed = false ;
2829
2930 constructor ( private config : PusherConfig ) {
3031 this . pusher = new Pusher ( this . config . key , {
@@ -47,6 +48,9 @@ export class PusherListener {
4748 * Only resubscribes when channel name changes, not when handlers change
4849 */
4950 subscribe ( config : SubscriptionConfig ) : void {
51+ if ( this . destroyed ) {
52+ return ;
53+ }
5054 // Only resubscribe if channel name changes
5155 if ( config . channelName !== this . currentChannelName ) {
5256 this . unsubscribe ( ) ;
@@ -64,12 +68,13 @@ export class PusherListener {
6468
6569 // Bind error handler
6670 this . currentChannel . bind ( "pusher:subscription_error" , ( error : unknown ) => {
67- console . error ( error ) ;
68- const errorMsg =
69- error === 515
70- ? "Authentication failed. Please verify Pusher configuration constants."
71- : `Subscription error: ${ String ( error ) } ` ;
72- this . onError ?.( new Error ( errorMsg ) ) ;
71+ if ( isAuthError ( error ) ) {
72+ // 403 from auth endpoint — object not yet known to server, silent in happy flow
73+ console . debug ( "[PusherListener] Channel auth returned 403, skipping subscription." ) ;
74+ return ;
75+ }
76+ console . error ( "[PusherListener] Subscription error:" , error ) ;
77+ this . onError ?.( new Error ( `Subscription error: ${ String ( error ) } ` ) ) ;
7378 } ) ;
7479 }
7580
@@ -87,23 +92,27 @@ export class PusherListener {
8792 * Unsubscribe from current channel
8893 */
8994 unsubscribe ( ) : void {
90- if ( this . currentChannel && this . currentChannelName ) {
91- // Unbind all channel events (both global and specific)
92- this . currentChannel . unbind_all ( ) ;
93- this . pusher . unsubscribe ( this . currentChannelName ) ;
94- this . currentChannel = null ;
95- this . currentChannelName = null ;
96- this . globalCallback = null ;
97- this . eventHandlersMap . clear ( ) ;
98- this . onError = undefined ;
95+ if ( this . destroyed || ! this . currentChannel || ! this . currentChannelName ) {
96+ return ;
9997 }
98+ this . currentChannel . unbind_all ( ) ;
99+ this . pusher . unsubscribe ( this . currentChannelName ) ;
100+ this . currentChannel = null ;
101+ this . currentChannelName = null ;
102+ this . globalCallback = null ;
103+ this . eventHandlersMap . clear ( ) ;
104+ this . onError = undefined ;
100105 }
101106
102107 /**
103108 * Disconnect and cleanup
104109 * Should be called on widget unmount
105110 */
106111 destroy ( ) : void {
112+ if ( this . destroyed ) {
113+ return ;
114+ }
115+ this . destroyed = true ;
107116 this . unsubscribe ( ) ;
108117 this . pusher . connection . unbind ( ) ;
109118 this . pusher . disconnect ( ) ;
@@ -117,3 +126,7 @@ export class PusherListener {
117126 console . debug ( `[PusherListener] State changed: ${ states . previous } → ${ states . current } ` ) ;
118127 } ;
119128}
129+
130+ function isAuthError ( error : unknown ) : boolean {
131+ return typeof error === "object" && error !== null && ( error as { status ?: number } ) . status === 403 ;
132+ }
0 commit comments