You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mod_jibri_queue_component.lua 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. local st = require "util.stanza";
  2. local jid = require "util.jid";
  3. local http = require "net.http";
  4. local json = require "cjson";
  5. local inspect = require('inspect');
  6. local socket = require "socket";
  7. local uuid_gen = require "util.uuid".generate;
  8. local jwt = require "luajwtjitsi";
  9. local it = require "util.iterators";
  10. local get_room_from_jid = module:require "util".get_room_from_jid;
  11. local room_jid_match_rewrite = module:require "util".room_jid_match_rewrite;
  12. local is_healthcheck_room = module:require "util".is_healthcheck_room;
  13. local ASAPKeyPath
  14. = module:get_option_string("asap_key_path", '/etc/prosody/certs/asap.key');
  15. local ASAPKeyId
  16. = module:get_option_string("asap_key_id", 'jitsi');
  17. local ASAPIssuer
  18. = module:get_option_string("asap_issuer", 'jitsi');
  19. local ASAPAudience
  20. = module:get_option_string("asap_audience", 'jibriqueue');
  21. local ASAPTTL
  22. = module:get_option_number("asap_ttl", 3600);
  23. local ASAPTTL_THRESHOLD
  24. = module:get_option_number("asap_ttl_threshold", 600);
  25. local ASAPKey;
  26. local http_headers = {
  27. ["User-Agent"] = "Prosody ("..prosody.version.."; "..prosody.platform..")",
  28. ["Content-Type"] = "application/json"
  29. };
  30. -- we use async to detect Prosody 0.10 and earlier
  31. local have_async = pcall(require, "util.async");
  32. if not have_async then
  33. module:log("warn", "conference duration will not work with Prosody version 0.10 or less.");
  34. return;
  35. end
  36. local muc_component_host = module:get_option_string("muc_component");
  37. if muc_component_host == nil then
  38. log("error", "No muc_component specified. No muc to operate on for jibri queue!");
  39. return;
  40. end
  41. log("info", "Starting jibri queue handling for %s", muc_component_host);
  42. -- Read ASAP key once on module startup
  43. local f = io.open(ASAPKeyPath, "r");
  44. if f then
  45. ASAPKey = f:read("*all");
  46. f:close();
  47. if not ASAPKey then
  48. module:log("warn", "No ASAP Key read, disabling muc_events plugin");
  49. return
  50. end
  51. else
  52. module:log("warn", "Error reading ASAP Key, disabling muc_events plugin");
  53. return
  54. end
  55. -- TODO: Figure out a less arbitrary default cache size.
  56. local jwtKeyCacheSize = module:get_option_number("jwt_pubkey_cache_size", 128);
  57. local jwtKeyCache = require"util.cache".new(jwtKeyCacheSize);
  58. local queueServiceURL
  59. = module:get_option_string("jibri_queue_url");
  60. if queueServiceURL == nil then
  61. log("error", "No jibri_queue_url specified. No service to contact!");
  62. return;
  63. end
  64. local function round(num, numDecimalPlaces)
  65. local mult = 10^(numDecimalPlaces or 0)
  66. return math.floor(num * mult + 0.5) / mult
  67. end
  68. local function generateToken(audience)
  69. audience = audience or ASAPAudience
  70. local t = os.time()
  71. local err
  72. local exp_key = 'asap_exp.'..audience
  73. local token_key = 'asap_token.'..audience
  74. local exp = jwtKeyCache:get(exp_key)
  75. local token = jwtKeyCache:get(token_key)
  76. --if we find a token and it isn't too far from expiry, then use it
  77. if token ~= nil and exp ~= nil then
  78. exp = tonumber(exp)
  79. if (exp - t) > ASAPTTL_THRESHOLD then
  80. return token
  81. end
  82. end
  83. --expiry is the current time plus TTL
  84. exp = t + ASAPTTL
  85. local payload = {
  86. iss = ASAPIssuer,
  87. aud = audience,
  88. nbf = t,
  89. exp = exp,
  90. }
  91. -- encode
  92. local alg = "RS256"
  93. token, err = jwt.encode(payload, ASAPKey, alg, {kid = ASAPKeyId})
  94. if not err then
  95. token = 'Bearer '..token
  96. jwtKeyCache:set(exp_key,exp)
  97. jwtKeyCache:set(token_key,token)
  98. return token
  99. else
  100. return ''
  101. end
  102. end
  103. local function cb(content_, code_, response_, request_)
  104. if code_ == 200 or code_ == 204 then
  105. module:log("debug", "URL Callback: Code %s, Content %s, Request (host %s, path %s, body %s), Response: %s",
  106. code_, content_, request_.host, request_.path, inspect(request_.body), inspect(response_));
  107. else
  108. module:log("warn", "URL Callback non successful: Code %s, Content %s, Request (%s), Response: %s",
  109. code_, content_, inspect(request_), inspect(response_));
  110. end
  111. end
  112. local function sendEvent(type,room_address,participant,edetails)
  113. local event_ts = round(socket.gettime()*1000);
  114. local out_event = {
  115. ["conference"] = room_address,
  116. ["event_type"] = "Event"..type,
  117. ["participant"] = participant,
  118. ["event_details"] = edetails,
  119. ["event_ts"] = event_ts
  120. }
  121. module:log("debug","Sending event %s",inspect(out_event));
  122. local headers = http_headers or {}
  123. headers['Authorization'] = generateToken()
  124. module:log("debug","Sending headers %s",inspect(headers));
  125. local request = http.request(queueServiceURL, {
  126. headers = headers,
  127. method = "POST",
  128. body = json.encode(out_event)
  129. }, cb);
  130. end
  131. -- receives messages from client currently connected to the room
  132. -- clients indicates their own dominant speaker events
  133. function on_message(event)
  134. -- Check the type of the incoming stanza to avoid loops:
  135. if event.stanza.attr.type == "error" then
  136. return; -- We do not want to reply to these, so leave.
  137. end
  138. local jibriQueue
  139. = jibriQueue.stanza:get_child('jibriqueue', 'http://jitsi.org/jitmeet');
  140. if jibriQueue then
  141. local roomAddress = jibriQueue.attr.room;
  142. local room = get_room_from_jid(room_jid_match_rewrite(roomAddress));
  143. if not room then
  144. log("warn", "No room found %s", roomAddress);
  145. return false;
  146. end
  147. local from = event.stanza.attr.from;
  148. local occupant = room:get_occupant_by_real_jid(from);
  149. if not occupant then
  150. log("warn", "No occupant %s found for %s", from, roomAddress);
  151. return false;
  152. end
  153. -- now handle new jibri queue message
  154. local edetails = {
  155. ["foo"] = "bar"
  156. }
  157. sendEvent('Message',roomAddress,from,edetails)
  158. end
  159. return true
  160. end
  161. function occupant_joined(event)
  162. local room = event.room;
  163. local occupant = event.occupant;
  164. if is_healthcheck_room(room.jid) then
  165. return;
  166. end
  167. local participant_count = it.count(room:each_occupant());
  168. -- now handle new jibri queue message
  169. local edetails = {
  170. ["participant_count"] = participant_count
  171. }
  172. sendEvent('Join',room.jid,occupant.jid,edetails)
  173. end
  174. module:hook("message/host", on_message);
  175. -- executed on every host added internally in prosody, including components
  176. function process_host(host)
  177. if host == muc_component_host then -- the conference muc component
  178. module:log("info","Hook to muc events on %s", host);
  179. local muc_module = module:context(host);
  180. -- muc_module:hook("muc-room-created", room_created, -1);
  181. muc_module:hook("muc-occupant-joined", occupant_joined, -1);
  182. -- muc_module:hook("muc-occupant-pre-leave", occupant_leaving, -1);
  183. -- muc_module:hook("muc-room-destroyed", room_destroyed, -1);
  184. end
  185. end
  186. if prosody.hosts[muc_component_host] == nil then
  187. module:log("info","No muc component found, will listen for it: %s", muc_component_host)
  188. -- when a host or component is added
  189. prosody.events.add_handler("host-activated", process_host);
  190. else
  191. process_host(muc_component_host);
  192. end
  193. module:log("info", "Loading jibri_queue_component");