Skip to content

Support listener context on Pusher subscriptions#490

Closed
jlswanson28694 wants to merge 1 commit intolaravel:2.xfrom
jlswanson28694:pusher-context
Closed

Support listener context on Pusher subscriptions#490
jlswanson28694 wants to merge 1 commit intolaravel:2.xfrom
jlswanson28694:pusher-context

Conversation

@jlswanson28694
Copy link
Copy Markdown

Pusher has the ability to add context as the third argument when binding and unbinding callbacks, which conveniently lets me remove multiple bound callbacks at once using channel.unbind(undefined, undefined, context). This PR adds support for context when using Pusher channels, and adds a method stopListeningForContext to remove all listeners for the given context.

I've also added a PusherChannel test for this feature. In order to properly test PusherChannel, the test starts a bare-bones WebSocketServer with the ws library. I'm no expert on frontend testing, so feel free to clean it up as you see fit.

@taylorotwell taylorotwell marked this pull request as draft April 18, 2026 13:27
@joetannenbaum
Copy link
Copy Markdown
Contributor

Thanks for the PR and the detailed write-up. I went back and forth on this one, but I think we're going to pass for now. A couple of reasons:

  • Most cleanup scenarios in Echo apps are already covered by Echo.leaveChannel(name), and for the rarer case where you need finer-grained cleanup on a shared channel, channel.subscription.bind/unbind is exposed directly and supports context natively.
  • We try to keep the cross-driver API surface consistent, and a Pusher-only stopListeningForContext becomes a long-term maintenance signal ("why doesn't socket.io support this?") that's hard to take back once shipped.

If you find this comes up a lot in practice and you can show real-world usage that leaveChannel doesn't cover well, I'm open to revisiting, but as a net-new public API I don't think the win is there yet.

@jlswanson28694
Copy link
Copy Markdown
Author

@joetannenbaum Let me add more context by attempting to explain my use-case.

I have a Vue component ReportDownload that renders a button that, when clicked, will tell the backend and add a new job to the queue to generate a large report. The Vue component then connects to Reverb on the reports.{userId} channel, displays a "processing" toast, and waits for a ReportReady event (or ReportFailed).

I'm allowing the user to generate multiple reports like this simultaneously. As a result, if one report completes, I can't run leaveChannel without killing the connection for other in-progress reports. This is a simplified version of what my Vue component looks like using this new API:

new Promise<ReportReadyData>((resolve, reject) => {
    this.channel = this.echo.private(BroadcastChannels.reports(user.value.id))
        .listen(
            events.ReportReady,
            (e: ReportReadyData) => {
                if (e.report_id === this.report_id) resolve(e);
            },
            this.report_id
        )
        .listen(
            events.ReportFailed,
            (e: ReportIdentifier) => {
                if (e.report_id === this.report_id) reject();
            }, 
            this.report_id
        )
        .error(reject, this.report_id);
})
.finally(() => {
    this.channel.stopListeningForContext(this.report_id);
});

Thought it looked pretty clean. As you said, channel.subscription.bind/unbind does work, but that was definitely more verbose. The error callback in particular translates to channel.subscription.bind("pusher:subscription_error", reject, this.report_id).

Looking at this again, I suppose I could open a new channel for each report instead, but this is just to show you where my head was when I wrote the PR. No worries if you don't think it's worth adding.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants