The Socket Framework provides support for real-time two-way communication between Telligent Community and the browser. This allows for both receiving and sending programmatic messages directly to the user.
[toc]
When would I want to use sockets?
Sockets are useful when you need to push messages from the server to the browser. An easy example to consider is a notification popup that is pushed to a user in the event of an action occurring on the server. Sockets are the mechanism through which built-in platform features including notifications, chat, live theme previewing, and tracing communicate with the browser.
Overview
Built on top of SignalR, the Socket Framework inherits all of SignalR's performance, stability, and diversity of browser support. Because it is Telligent Community-specific, the Socket Framework also is able to provide:
- plugin based extensibility
- Both built-in and third-party plugin can implement their own sockets which each define a pair of server and client methods and events for bi-directional communication, all multiplexed over a single underlying PersistentConnection.
- Built-in user mapping
- User-to-connection mapping is maintained transparently by Telligent Community, allowing knowledge of which Telligent Community user has sent a message and enabling sending of a message directly to all active connections associated with a given Telligent Community user. User mapping persistence is maintained efficiently and shared immediately, both on single app instances as well as synchronized across multiple application instances.
- Built-in, hot-swappable, scale-out
- To support multiple application instances, Telligent Community bundles an optionally installable Message Bus Server. Alternatively, Telligent Community can use any message bus service through plugin extensibility. The configuration of which message bus server to use, if any at all, is defined by plugin-based connectors, which can be enabled or disabled through standard plugin management without restarting the application. Socket plugins also expose the underlying message bus for use as a way to keep real-time application state in synchronization across multiple instances.
- Much more!
The socket framework powers notifications, chat, live previewing, and tracing in Telligent Community. Socket plugins could be developed to easily power other functionality like real-time whiteboards, analytics, or even games.
[DIAGRAM: Sockets]
API
The API exists solely as a set of interfaces implementable by plugins.
ISocket Plugin
A socket is defined by a plugin which implements the ISocket interface. A single socket plugin represents a single channel of communication between the server and browser. For example, a socket plugin may represent all functionality related to sending and receiving realtime chat messages. A single socket plugin can send and receive arbitrary messages and optional data with those messages, but can only send or receive messages sent by or to the same socket plugin.
Socket plugins are injected with instances of ISocketController.
The ISocketController exposes an instance of an IClientsController which contains events for when a user connects, disconnects, or sends a message. The IClientsController also supports sending messages to a user or broadcasting to all users.
Each enabled socket plugin yields a corresponding client-side API.
Socket plugins will often also implement the IHtmlHeaderExtension interface to register JavaScript which handles or sends socket messages along with custom logic required by the plugin.
ISocketMessageBus Plugin
Multiple-application instance scale-out is achieved through usage of plugins which implement the ISocketMessageBus interface. Telligent Community bundles a default implementation, but supports integrating with other message busses as well. For details, see the discussion on Scale-Out.
Client-Side
Socket Endpoints
Each ISocket
plugin implements a SocketName
. This one-word string becomes the name of the client-side API. For example, given the socket:
public class MySocket : ISocket { // ... string SocketName { get { return "mySocket"; } } // ... }
Then the following JavaScript API will be automatically exposed:
// receive and handle a message with optional data from the server $.telligent.evolution.sockets.mySocket.on(messageName, function(data) { // handle }); // send data to the server an optional object message $.telligent.evolution.sockets.mySocket.send(messageName, data);
Initiation
Before attempting to send or receive messages from the client side, the Socket Framework must initiate itself. This happens automatically, and when complete, it publishes a socket.connected client-side message.
$.telligent.evolution.messaging.subscribe('socket.connected', function() { // set up message listeners or send messages });
Example Socket
The following shows a basic example of a socket which routes greetings from one user to another. A user provides the user name of another community member, clicks 'greet', and an alert is displayed to the other user.
Greetings are initiated from a user via a widget, sent via the client-side socket API to the ISocket
plugin, and then delivered to the recipient user where the same widget instance for the other user will display the message in a JavaScript alert()
.
Server Side
Create a new plugin which implements the ISocket interface. Its Name
, Description
, and Initialize()
are unimportant. For SocketName
and SetController()
, use the following:
public string SocketName { get { return "greeter"; } } public void SetController(ISocketController controller) { controller.Clients.Received += (sender, e) => { // if this was a greting message, let's process it if (e.MessageName == "greet") { // get the associated recipient user // data passed from the client side is available on the dynamic MessageData object var to = PublicApi.Users.Get(new UsersGetOptions { Username = (string)e.MessageData.Recipient }); // get the sender user var from = PublicApi.Users.Get(new UsersGetOptions { Id = e.UserId }); // send a new message to the recipient controller.Clients.Send(to.Id.GetValueOrDefault(), "greet", new { Message = String.Format("Hello {0}, from {1}", to.DisplayName, from.DisplayName) }); } }; }
Client Side
Create a new Studio Widget with content:
## set up some unique ids for interface elements #set($recipientInputId = $core_v2_widget.UniqueId('recipient')) #set($sendLinkId = $core_v2_widget.UniqueId('greet')) ## basic interface <input type="text" id="$recipientInputId" /> <a href="#" id="$sendLinkId">Greet</a> <script type="text/javascript"> // Wait for socket to be connected jQuery.telligent.evolution.messaging.subscribe('socket.connected', function() { // when 'Greet' is clicked, send a message to be delivered to the recipient jQuery('#$sendLinkId').on('click', function(e) { e.preventDefault(); jQuery.telligent.evolution.sockets.greeter.send('greet', { Recipient: jQuery('#$recipientInputId').val() }); }); // when a 'greet' message is received from the socket, display it as an alert jQuery.telligent.evolution.sockets.greeter.on('greet', function(data){ alert(data.Message); }); }); </script>
Note use of the socket.connected client-side message as well as the automatically-generated jQuery.telligent.evolution.sockets.testSocket
API.
The result is a simple UI that enables sending a greeting to another user by their user name.
Download
The complete sample can be downloaded:
using System; using Telligent.Evolution.Extensibility.Api.Version1; using Telligent.Evolution.Extensibility.Sockets.Version1; namespace Samples { public class Greeter : ISocket { #region IPlugin public string Name { get { return "Sample Greeter Socket Plugin"; } } public string Description { get { return "This plugin will demonstrate a simple socket plugin which supports sending a message to a user"; } } public void Initialize() { } #endregion #region ISocket public string SocketName { get { return "greeter"; } } public void SetController(ISocketController controller) { controller.Clients.Received += (sender, e) => { // if this was a greting message, let's process it if (e.MessageName == "greet") { // get the associated recipient user // data passed from the client side is available on the dynamic MessageData object var to = PublicApi.Users.Get(new UsersGetOptions { Username = (string)e.MessageData.Recipient }); // get the sender user var from = PublicApi.Users.Get(new UsersGetOptions { Id = e.UserId }); // send a new message to the recipient controller.Clients.Send(to.Id.GetValueOrDefault(), "greet", new { Message = String.Format("Hello {0}, from {1}", to.DisplayName, from.DisplayName) }); } }; } #endregion } }
<scriptedContentFragments> <scriptedContentFragment name="Greeter UI" version="9.0.0.0" description="Sample UI corresponding to the Greeter Socket Plugin Demo" instanceIdentifier="b6261cfed4f4466591e39743c153725e" theme="" isCacheable="false" varyCacheByUser="false" showHeaderByDefault="true" cssClass=""> <contentScript><![CDATA[## set up some unique ids for interface elements #set($recipientInputId = $core_v2_widget.UniqueId('recipient')) #set($sendLinkId = $core_v2_widget.UniqueId('greet')) ## basic interface <input type="text" id="$recipientInputId" /> <a href="#" id="$sendLinkId">Greet</a> <script type="text/javascript"> // Wait for socket to be connected jQuery.telligent.evolution.messaging.subscribe('socket.connected', function() { // when 'Greet' is clicked, send a message to be delivered to the recipient jQuery('#$sendLinkId').on('click', function(e) { e.preventDefault(); jQuery.telligent.evolution.sockets.greeter.send('greet', { Recipient: jQuery('#$recipientInputId').val() }); }); // when a 'greet' message is received from the socket, display it as an alert jQuery.telligent.evolution.sockets.greeter.on('greet', function(data){ alert(data.Message); }); }); </script>]]></contentScript> </scriptedContentFragment> </scriptedContentFragments>
Scale-out
Overview
The socket message bus is used both internally by the Socket Framework to push messages to multiple application instances when sending messages as well as exposed as a generic message bus for ISocket plugins to consume through the injected IMessageBusController instance.
When only a single application instance is needed, the Socket Message Bus uses an internal memory-only bus. When multiple application instances are needed, the in-memory bus cannot be used. Instead a Plugins which implements ISocketMessageBus must be enabled.
Socket Message Bus Server
Telligent Telligent Community 7.5 comes with the optionally-installed Socket Message Bus Windows Service and its corresponding ISocketMessageBus
plugin-based connector, Socket Message Bus Service Connector. This is simply a Windows Service which echoes messages it receives to all connected Telligent Community instances.
Custom Message Bus
In cases where a community would prefer to use an existing third party message bus solution, custom ISocketMessageBus plugins can be written against it and deployed. An example could be a message bus service which supports multiple Telligent Community communities on a single bus server by potentially adding an identifier to the string messages sent and received by the plugin for routing the message on the bus server.
Generic Messages
Custom logic in ISocket
plugins can also make use of the message bus. Generic messages can be useful for keeping realtime state synchronized across application instances. For example, chat presence may be persisted in a database, but cached locally in multiple instances. Since it is important that these caches remain synchronized, presence change messages can be sent and received across the message bus for other app instances' plugins to handle and process.
The ISocketController injected into ISocket plugins contains an instance of an IMessageBusController. The IMessageBusController exposes events for when generic messages are received from instances of the application along with a method for sending them.
A useful pattern for state change is to send a generic message to the bus, and only handle it once it is re-received. This allows all instances to know about the state. Additionally, the instance that sends it receives the message immediately without having to wait for the round-trip, and is made aware of the local origin of the message. In this manner, the sender can also be exclusively responsible for potentially persisting the state change elsewhere.
controller.MessageBus.Publish("something.happened",""); controller.MessageBus.Received += (sender, e) => { if (e.MessageName == "something.happened") { // update some local caches // ... if (e.Source == BusMessageSource.Local) { // update DB // ... } } };
Tips and Tricks
- Sockets can only be used by authenticated, non-system users