Skip to content

Commit 995f01b

Browse files
committed
fix(pusher-web): silence 403 auth errors and guard against closed WebSocket
Treat 403 from the auth endpoint as a no-op (object not yet known to server) rather than logging an error. Add a destroyed guard to PusherListener so that effect cleanup ordering on unmount cannot trigger operations on an already-closed WebSocket connection.
1 parent 77d51d7 commit 995f01b

1 file changed

Lines changed: 28 additions & 15 deletions

File tree

packages/pluggableWidgets/pusher-web/src/utils/PusherListener.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)