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 // ... } } };
Presence Services
User Presence
The User Presence service tracks which users are online. The service is responsible for the presence indicator often displayed along side the user's name. In conjunction with the Content Presence service (discussed below), the service sends socket messages as users go offline and online to update the presence indicators in near real-time for users who are currently viewing the user whose online status has changed.
The platform has the options for Enable Presence Tracking, Allow Members To Toggle Presence Tracking Enablement and Default Presence Tracking Enablement Value available under Membership in the Membership Options panel in Administration. Enable Presence Tracking controls whether user presence tracking is available for the community. If tracking is enabled, Allow Members To Toggle Presence Tracking Enablement controls whether or not each user should have the option to disable tracking of their online status. If that option is enabled the Default Presence Tracking Enablement Value is also available. If users are giving the option to control their presence tracking, the Enable presence tracking option will be available in the membership settings.
Content Presence
The Content Presence service tracks which content users are currently viewing. This enables the platform to update users in real time to changes in content they are currently viewing as well as providing indicators on who is currently viewing content.
How is content presence determined?
- Page Context: The service uses the page content to track what content the using is currently viewing. For instance if a user is currently viewing a blog post, the content service would be aware that user is currently viewing that blog post, its author, the blog the post belonged to and the group the blog belonged to.
- Html attributes: Html attributes can be added to through the
core_v2_contentPresence.RenderAttributes
method. This method will render the proper attributes to automatically track a user's content presence, when the decorated element is currently visible on the page they are viewing. The comment stream and activity stream are two examples where this method is in use. - APIs: In Process, Rest and Widget APIs are available to directly add and remove which content a user is present for. Additionally listing which users are currently viewing a piece of content is available in the same APIs.
Service Presence
The Service Presence service tracks what services a user is currently viewing. When a user is present for a service, messages about changes in that service can be sent to the user's browser so that browser can be updated to show the latest changes. The activity story service is an example of how this service is used. When an activity story is created, deleted or updated any user that is present to the activity story service will be see that updated of that change and their activity story stream can be updated as needed.
How is service presence determined?
- Html attributes: Html attributes can be added to through the
core_v2_servicePresence.RenderAttributes
method. This method will render the proper attributes to automatically track a user's presence to a service, when the decorated element is currently visible on the page they are viewing. - APIs: In Process, Rest and Widget APIs are available to directly add and remove which service a user is present for. Additionally listing which users are currently present to a service is available in the same APIs.
Tips and Tricks
- Sockets can only be used by authenticated, non-system users