Difference between revisions of "Extension:WebSocket"

From Organic Design wiki
(example code)
m
Line 21: Line 21:
 
}</js>}}
 
}</js>}}
  
The extension which is upgrading to WebSocket must also tell the ''webSocket'' object to connect to the daemon and declare it's data-updating function as a callback in an appropriately named event queue as in the following example:
+
The extension which is upgrading to WebSocket must also tell the ''webSocket'' object to connect to the daemon and declare it's data-updating function as a callback for the desired message type as in the following example:
{{code|<js>if('webSocket' in window) webSocket.connect();
+
{{code|<js>if('webSocket' in window) {
$(document).on( "ws_FooDataChanged", updateData );</js>}}
+
webSocket.connect();
 +
webSocket.subscribe( 'FooDataChanged', updateData );
 +
}</js>}}
  
Finally, a message of the type we've declared our callback for must be sent by the source of the changing state. Normally this would be within MediaWiki, for example when an article with specific properties has saved. Here's an example PHP snippet of how to broadcast such a message to all clients when a new article revision is created:
+
=== Messaging ===
 +
A message of the type we've declared our callback for must be sent by the source of the changing state. Normally this would be within MediaWiki, for example when an article with specific properties has saved. Here's an example PHP snippet of how to broadcast such a message to all clients when a new article revision is created:
 
{{code|<php>function onRevisionInsertComplete( &$rev, $data, $flags ) {
 
{{code|<php>function onRevisionInsertComplete( &$rev, $data, $flags ) {
 
$page = $rev->getTitle()->getText();
 
$page = $rev->getTitle()->getText();
WebSocket::send('FooDataChanged', "$page has been changed");
+
WebSocket::send( 'FooDataChanged', "$page has been changed" );
 
return true;
 
return true;
 
}</php>}}
 
}</php>}}
  
It's also possible that changes to the environment outside of the MediaWiki context need to notify the WebSocket clients of change. Here's an example Perl snippet that triggers our call to ''updateFooData'':
+
Events may also arise on the client side in which case the JavaScript obect's ''send'' method can be used,
 +
{{code|<js>webSocket.send( 'FooDataChanged', "Client-side Foo data has changed" );</js>}}
 +
 
 +
It's also possible that changes to data of interest to the clients originates outside of the MediaWiki context, such as items being added to log files, or incoming emails etc. Here's an example Perl snippet that triggers our call to ''updateFooData'' (these dependencies can be installed via CPAN):
 
{{code|<perl>use IO::Async::Loop;
 
{{code|<perl>use IO::Async::Loop;
 
use Net::Async::WebSocket::Client;
 
use Net::Async::WebSocket::Client;
Line 44: Line 50:
  
 
# Send a message to the WebSocket connection when some condition occurs (note $msg is JSON)
 
# Send a message to the WebSocket connection when some condition occurs (note $msg is JSON)
$ws->send_frame( '{"type": "FooDataChanged"}' )->then()->get;
+
$ws->send_frame( '{ "type": "FooDataChanged", "msg": "Server-side Foo data has changed" }' )->then()->get;
 
</perl>}}
 
</perl>}}
  
== Messaging ==
+
=== Message recipients ===
 
Each wiki client has a unique ID which is automatically added to a "from" argument in the ''send'' method. When messages are sent an optional third argument (or "to" field in JSON context) can be added to the function which is an array of recipient IDs. If the argument is missing then the message is broadcast to all clients (but only those hooked in to the event corresponding to the "type" argument will actually receive it).
 
Each wiki client has a unique ID which is automatically added to a "from" argument in the ''send'' method. When messages are sent an optional third argument (or "to" field in JSON context) can be added to the function which is an array of recipient IDs. If the argument is missing then the message is broadcast to all clients (but only those hooked in to the event corresponding to the "type" argument will actually receive it).
  
When the PHP ''WebSocket::send'' method is used the ID of the client that requested the currently running PHP instance is used (''WebSocket::$clientID''). When the sending is done from the daemon no ''from'' field is used since it merely acts as a forwarder and expects no messages directly to itself. Other code running outside of the PHP context that send messages may however need to receive responses, so in that case they can simply set the ''from'' argument to a unique ID (or even a fixed ID such as "1" if there's only ever one instance of that code running).
+
When the PHP ''WebSocket::send'' or the JavaScript ''webSocket.send'' methods are used, the ID of the currently running client instance is automatically used for the ''from'' argument. When the sending is done from the daemon, no ''from'' field is used since it merely acts as a forwarder and expects no messages directly to itself. Other code running outside of the PHP context that send messages may however need to receive responses, so in that case they can simply set the ''from'' argument to a unique ID (or even a fixed ID such as "1" if there's only ever one instance of that code running).
  
 
The Perl daemon keeps a record of sockets mapped by ''from'' ID so that it can forward messages to the intended recipients when it sees a ''to'' field in messages it receives, so merely including a ''from'' argument is all that's required to become a sender that can be replied to.
 
The Perl daemon keeps a record of sockets mapped by ''from'' ID so that it can forward messages to the intended recipients when it sees a ''to'' field in messages it receives, so merely including a ''from'' argument is all that's required to become a sender that can be replied to.
Line 64: Line 70:
 
|-
 
|-
 
|WebSocket::$perl||/usr/bin/perl||Location of the Perl interpreter
 
|WebSocket::$perl||/usr/bin/perl||Location of the Perl interpreter
 +
|-
 +
|WebSocket::$log||''false''||Set to a writable file to log events and errors from the daemon
 
|}
 
|}
  
Line 75: Line 83:
 
}</pre>}}
 
}</pre>}}
  
 +
== Issues ==
 +
*The WebSocket client code for PHP has a problem and can only send one message, subsequent messages do nothing.
 
[[Category:Extensions]]
 
[[Category:Extensions]]

Revision as of 15:54, 18 April 2015

Info.svg This code is in our Git repository here.

Note: If there is no information in this page about this code and it's a MediaWiki extension, there may be something at mediawiki.org.


This extension allows the wiki to respond dynamically to change events using WebSockets instead of Ajax polling. It currently doesn't have any fall-back to Ajax because it was written primarily to augment extensions that already use Ajax polling such as AjaxComments, so these extensions will just continue with Ajax polling as usual if the WebSocket extension is not present or is present but not connected.

Installation

The extension itself is installed the usual way with an include of WebSocket.php in your LocalSettings.php file. Note that the extension must reside within a directory called "WebSocket" directly in the wikis extensions directory.

There is also a background Perl daemon (WebSocket.pl) which is started by the PHP if it's found not to be running. The daemon uses the Net::WebSocket::Server library to implement a minimal WebSocket service.

Usage

This extension is intended to be used by other extensions that currently implement an Ajax polling system to determine if a particular event has occurred. Such extensions can be adjusted so that their Ajax polling code includes a condition to poll only if a WebSocket connection is not present. The WebSocket and it's state are available in a global object called window.webSocket which is defined in websocket.js. Here's an example snippet where the setTimeout callback that would re-call the updateData function again after five seconds has been made conditional upon their being no WebSocket connection.

<js>function updateFooData() {

$.ajax({ type: 'GET', url: mw.util.wikiScript(), data: { action: 'ajax', rs: 'Foo::getMyData', rsargs: [arg1,arg2,arg3] }, dataType: 'json', success: renderFooData, }).then(function() { if(!('webSocket' in window && window.webSocket.connected())) setTimeout(updateFooData, 5000); }); }</js>

The extension which is upgrading to WebSocket must also tell the webSocket object to connect to the daemon and declare it's data-updating function as a callback for the desired message type as in the following example:

<js>if('webSocket' in window) {

webSocket.connect(); webSocket.subscribe( 'FooDataChanged', updateData ); }</js>

Messaging

A message of the type we've declared our callback for must be sent by the source of the changing state. Normally this would be within MediaWiki, for example when an article with specific properties has saved. Here's an example PHP snippet of how to broadcast such a message to all clients when a new article revision is created:

{{{1}}}

Events may also arise on the client side in which case the JavaScript obect's send method can be used,

<js>webSocket.send( 'FooDataChanged', "Client-side Foo data has changed" );</js>

It's also possible that changes to data of interest to the clients originates outside of the MediaWiki context, such as items being added to log files, or incoming emails etc. Here's an example Perl snippet that triggers our call to updateFooData (these dependencies can be installed via CPAN):

{{{1}}}

Message recipients

Each wiki client has a unique ID which is automatically added to a "from" argument in the send method. When messages are sent an optional third argument (or "to" field in JSON context) can be added to the function which is an array of recipient IDs. If the argument is missing then the message is broadcast to all clients (but only those hooked in to the event corresponding to the "type" argument will actually receive it).

When the PHP WebSocket::send or the JavaScript webSocket.send methods are used, the ID of the currently running client instance is automatically used for the from argument. When the sending is done from the daemon, no from field is used since it merely acts as a forwarder and expects no messages directly to itself. Other code running outside of the PHP context that send messages may however need to receive responses, so in that case they can simply set the from argument to a unique ID (or even a fixed ID such as "1" if there's only ever one instance of that code running).

The Perl daemon keeps a record of sockets mapped by from ID so that it can forward messages to the intended recipients when it sees a to field in messages it receives, so merely including a from argument is all that's required to become a sender that can be replied to.

Configuration

The configuration options for the extension are set after the include statement in your LocalSettings.php and are as follows.

Name Default Description
WebSocket::$port 1729 Port the WebSocket daemon will run on
WebSocket::$rewrite false Configure URL rewriting so that the WebSocket port doesn't need to be public
WebSocket::$perl /usr/bin/perl Location of the Perl interpreter
WebSocket::$log false Set to a writable file to log events and errors from the daemon

The "rewrite" option

This option is used if the server environment doesn't allow new ports to be open to the public. If it's set then the WebSocket requests will be directed to the same domain and port as the wiki is running on with the URI of /websocket. The web-server must be configured to match this URL and redirect it to the local WebSocket daemon's port. A typical Nginx configuration might contain something like the following example snippet:

location ~ websocket:([0-9]+) {
	proxy_pass http://127.0.0.1:$1;
	proxy_http_version 1.1;
	proxy_set_header Upgrade websocket;
	proxy_set_header Connection upgrade;
}

Issues

  • The WebSocket client code for PHP has a problem and can only send one message, subsequent messages do nothing.