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?
Middleware Role: A middleware (like Mule or a custom application) is established to connect to Salesforce's platform event.
EventSource Bridge: This middleware then transmits the events in the
text/event-stream
format.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.