# HG changeset patch # User Matthew Wild # Date 1519078658 0 # Node ID 7c16afc70d11f3bb2a0999da261f912519b2f72b # Parent 6f289283feb17c3a902de2bda4c901d10633ad56 mod_muc_eventsource: New module forked from mod_pubsub_eventsource, exposes room message stream over SSE diff -r 6f289283feb1 -r 7c16afc70d11 mod_muc_eventsource/README.markdown --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_eventsource/README.markdown Mon Feb 19 22:17:38 2018 +0000 @@ -0,0 +1,78 @@ +--- +labels: 'Stage-Beta' +summary: Subscribe to MUC rooms using the HTML5 EventSource API +... + +Introduction +------------ + +This module and its docs shamelessly forked from mod_pubsub_eventsource. + +[Server-Sent Events](https://en.wikipedia.org/wiki/Server-sent_events) +is a simple HTTP/line-based protocol supported in HTML5, making it easy +to receive a stream of "events" in realtime using the Javascript +[EventSource +API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource). + +EventSource is supported in [most modern +browsers](http://caniuse.com/#feat=eventsource), and for the remainder +there are 'polyfill' compatibility layers such as +[EventSource.js](https://github.com/remy/polyfills/blob/master/EventSource.js) +and [jquery.eventsource](https://github.com/rwldrn/jquery.eventsource). + +Details +------- + +Subscribing to a node from Javascript is easy: + + var source = new EventSource('http://muc.example.org:5280/eventsource/myroom'); + source.onmessage = function (event) { + console.log(event.data); // Do whatever you want with the data here + }; + +### Access control + +Be warned that this module currently performs no access control. It will expose +the messages of ALL rooms on the host it is loaded on. This may be changed in +future revisions. + +### Cross-domain issues + +The same cross-domain restrictions apply to EventSource that apply to +BOSH, and support for CORS is not clearly standardized yet. You may want +to proxy connections through your web server for this reason. See [BOSH: +Cross-domain +issues](https://prosody.im/doc/setting_up_bosh#proxying_requests) for +more information. + +Configuration +------------- + +There is no special configuration for this module. Simply load it onto a +MUC component like so: + + Component "muc.example.org" "muc" + modules_enabled = { "muc_eventsource" } + +As it uses HTTP to serve the event streams, you can use Prosody's +standard [HTTP configuration options](https://prosody.im/doc/http) to +control how/where the streams are served. + +**Note about URLs:** It is important to get the event streams from the +correct hostname (that of the MUC host). An example stream URL is +`http://muc.example.org:5280/eventsource/myroom`. If you need to +access the streams using another hostname (e.g. `example.org`) you can +use the `http_host` option under the Component, e.g. +`http_host = "example.org"`. For more information see the ['Virtual +Hosts'](https://prosody.im/doc/http#virtual_hosts) section of our HTTP +documentation. + +Compatibility +------------- + + ------- -------------- + 0.10 ? + 0.9 ? + 0.8 Doesn't work + Trunk Works + ------- -------------- diff -r 6f289283feb1 -r 7c16afc70d11 mod_muc_eventsource/mod_muc_eventsource.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mod_muc_eventsource/mod_muc_eventsource.lua Mon Feb 19 22:17:38 2018 +0000 @@ -0,0 +1,75 @@ +module:depends("http"); + +local jid_split = require "util.jid".split; +local json = require "util.json"; + +local streams = {}; + +function client_closed(response) + local node = response._eventsource_node; + module:log("debug", "Destroying client for %q", node); + streams[node][response] = nil; + if next(streams[node]) == nil then + streams[node] = nil; + end +end + +function serve_stream(event, node) + module:log("debug", "Client subscribed to: %s", node); + + local response = event.response; + response.on_destroy = client_closed; + response._eventsource_node = node; + + response.conn:write(table.concat({ + "HTTP/1.1 200 OK"; + "Content-Type: text/event-stream"; + "Access-Control-Allow-Origin: *"; + "Access-Control-Allow-Methods: GET"; + "Access-Control-Max-Age: 7200"; + ""; + ""; + }, "\r\n")); + + local clientlist = streams[node]; + if not clientlist then + clientlist = {}; + streams[node] = clientlist; + end + clientlist[response] = response.conn; + + return true; +end + +function handle_message(event) + local room, stanza = event.room, event.stanza; + local node = (jid_split(event.room.jid)); + local clientlist = streams[node]; + if not clientlist then module:log("debug", "No clients for %q", node); return; end + + -- Extract body from message + local body = event.stanza:get_child_text("body"); + if not body then + return; + end + local nick = select(3, jid_split(stanza.attr.from)); + -- Encode body and broadcast to eventsource subscribers + local json_data = json.encode({ + nick = nick; + body = body; + }); + local data = "data: "..json_data:gsub("\n", "\ndata: \n").."\n\n"; + for response, conn in pairs(clientlist) do + conn:write(data); + end +end + +module:provides("http", { + name = "eventsource"; + route = { + ["GET /*"] = serve_stream; + }; +}); + + +module:hook("muc-broadcast-message", handle_message);