EventSource v/s PlatformEvent in Salesforce: Unlimited UI Notifications Solution

Salesforce, as a leading CRM platform, continuously evolves to cater to diverse business requirements. One such need is notifying users of backend changes in real-time, be it a case update or the success of a background calculation. However, like any platform, Salesforce has its limitations. In this post, we'll dive deep into a novel approach for UI notifications that leverages EventSource potentially overcomes the limitations of the conventional PlatformEvent.

The Conventional Way: lightning:empApi & PlatformEvent

Salesforce provides the lightning:empApi component to subscribe and listen to backend changes. This allows for real-time notifications, especially useful in scenarios where:

  • A case gets updated by one user, and other users with the case opened on their UI need to be informed of the change.

  • Notifications are required to let users know the successful execution of backend calculations.

The lightning:empApi component supports all streaming channels, and notably, it uses a shared CometD connection. This means multiple streaming apps can run in one browser session, but the connection isn't shared among different user sessions.

Limitation: There's a subscription limit for the Platform event, which is set to 2000 in production by default. Thus, when 2000 users have already subscribed, any additional user attempting to subscribe will face a handshake exception, breaking the flow.

Rethinking the Approach: EventSource

A potential solution is to switch from a many-to-one connection model to a one-to-one model. Instead of all client browsers connecting to Salesforce's streaming channel bus, they could instead connect to EventSource, a JavaScript native feature.

The EventSource establishes a persistent connection to an HTTP server and awaits events in the text/event-stream format. This connection persists until manually closed. The beauty of this is that being native to JavaScript, there isn’t a subscription limit like in Salesforce's PlatformEvent.

How Does It Work?

  1. Middleware Role: A middleware (like Mule or a custom application) is established to connect to Salesforce's platform event.

  2. EventSource Bridge: This middleware then transmits the events in the text/event-stream format.

  3. Frontend Integration: On the frontend side, Salesforce browsers will not directly listen to the platform event. Instead, they'll connect to this middleware service, which is relaying the necessary notifications via EventSource.

For a prototype, a simple Node.js application hosted on Heroku was set up. This application:

  • Subscribes to Salesforce's platform events.

  • Emits the necessary notifications in the text/event-stream format.

With this setup, the limitation of subscriber count is bypassed. It's a one-to-one connection where each Salesforce browser client independently listens to the Node.js app for notifications.

Note: Although this concept sounds promising, it's crucial to mention that bulk testing hasn't been performed on this model. The idea here is to present a potential workaround for UI updates/alerts based on backend changes using EventSource.

Code : The fun part

To keep things simple I wrote a simple nodejs-based app and hosted it on Heroku.

Relevant nodejs code.

app.get('/events', sseMiddleware, (req, res) => {
    // Ensure that we're authenticated
    if (!accessToken) {
        return res.status(401).send('Not authenticated with Salesforce');
    }

    const intervalId = setInterval(() => {
        res.write('data: heartbeat\n\n');
    }, 20000);

    let connection = new jsforce.Connection({ accessToken: accessToken, instanceUrl: instanceUrl });

    // Subscribe to the streaming channel
    const channel = `/event/${SF_CHANNEL_NAME}`;
    connection.streaming.topic(channel).subscribe((message) => {
        res.write(`data: ${JSON.stringify(message)}\n\n`);
    });

    // Ensure connection is closed when the client disconnects
    req.on('close', () => {
        clearInterval(intervalId);  // clear the interval
        connection.logout();
    });
});

app.get('/oauth2/auth', (req, res) => {
    res.redirect(oauth2.getAuthorizationUrl());
});

// Callback endpoint for OAuth2 flow
app.get('/oauth2/callback', (req, res) => {
    const conn = new jsforce.Connection({ oauth2 });
    const code = req.query.code;
    conn.authorize(code, (err, userInfo) => {
        if (err) {
            return res.status(500).send(err.toString());
        }
        accessToken = conn.accessToken;
        instanceUrl = conn.instanceUrl;

        res.redirect('/'); // Redirect to the client, or wherever appropriate
    });
});

Here I have used jsforce and subscribed to a platform event in salesforce.
When the platform event gets triggered from salesforce, this heroku-hosted node js app listens to it and emits the data to /events .

I have used OAuth flow as of now to fetch access token so that it's not hardcoded.

And in Salesforce, I have a lightning web component, which shows up a toast event whenever it receives and event from EventSource URL.

import {LightningElement} from 'lwc';
import {ShowToastEvent} from "lightning/platformShowToastEvent";

export default class EventSourceListener extends LightningElement {
    evtSource;

    connectedCallback() {
        this.evtSource = new EventSource('https://MY_HEROKU_URL/events');

        this.evtSource.onmessage = (event) => {
            try{
                let sObjectIdsC = JSON.parse(event.data).payload.SObjectIds__c;
                const evt = new ShowToastEvent({
                    title: "Record updated successfully",
                    message: sObjectIdsC + ' updated successfully',
                    variant: "info "
                });
                this.dispatchEvent(evt);
            } catch (e){

            }
        };

        this.evtSource.onerror = (error) => {
            console.error('EventSource failed:', error);
            this.evtSource.close();
        };
    }

    disconnectedCallback() {
        if (this.evtSource) {
            this.evtSource.close();
        }
    }
}

NOTE: Add your event source URL to CSP trusted site, because it violates the Content Security Policy directive if not added to CSP trusted site.

Demo :

Conclusion

The harmonious integration of Salesforce and EventSource paves the way for inventive solutions. Although PlatformEvent is a potent instrument, employing EventSource could potentially offer a more scalable alternative for real-time user interface updates, particularly for organizations with an extensive user base. Nonetheless, it is crucial to thoroughly assess the solution through meticulous testing prior to contemplating its implementation on a larger scale.

Did you find this article valuable?

Support Nagendra Singh by becoming a sponsor. Any amount is appreciated!