annotate mod_component_http/README.md @ 6299:5cf5ee23b361

mod_voipms: New Module to send and receive SMS/MMS via VoIP.ms APIs. diff --git a/mod_voipms/README.md b/mod_voipms/README.md new file mode 100644 --- /dev/null +++ b/mod_voipms/README.md @@ -0,0 +1,51 @@ +--- +labels: +- 'Stage-Alpha' +- 'Type-Web' +summary: Send and receive SMS/MMS via VoIP.ms APIs. +rockspec: + build: + modules: + mod_voipms: mod_voipms.lua +... + +Introduction +============ + +This is a Prosody module to map JIDs to DIDs on VoIP.ms and support sending/receiving SMS/MMS. + +Configuration +============= + +| option | type | default | +|-----------------------|--------|---------| +| voipms\_api\_username | string | nil | +| voipms\_api\_password | string | nil | +| voipms\_query\_key | string | nil | +| voipms\_jid\_map | table | nil | +``` +VirtualHost "sms.example.com" +modules_enabled = { + "voipms"; +} +voipms_api_username = john@example.com -- E-mail registered with VoIP.ms +voipms_api_password = abcd1234 -- API password configured in VoIP.ms +voipms_query_key = some_query_key -- query param 'key' part of your URL callback +voipms_jid_map = { + ["your_jid@your_domain.com"] = "+1234567890" +} +``` + +HTTP +==== + +The module is served on Prosody's default HTTP ports at the path /voipms. More details on configuring HTTP modules in Prosody can be found in the HTTP documentation. + +VoIP.ms Webhook URL +=================== + +This module receives the VoIP.ms Webhook URL (POST) at the /voipms endpoint. It uses the sendSMS/sendMMS GET methods against the VoIP.ms APIs. This is an example webhook to use in VoIP.ms: + +``` +https://sms.example.com/voipms?key=some_query_key +``` diff --git a/mod_voipms/mod_voipms.lua b/mod_voipms/mod_voipms.lua new file mode 100644 --- /dev/null +++ b/mod_voipms/mod_voipms.lua @@ -0,0 +1,166 @@ +local http = require "net.http" +local json = require "util.json" +local st = require "util.stanza" + +local api_username = module:get_option_string("voipms_api_username") +local api_password = module:get_option_string("voipms_api_password") +local query_key = module:get_option("voipms_query_key") +local jid_map = module:get_option("voipms_jid_map") or {} +local rest_endpoint = "https://voip.ms/api/v1/rest.php" + +if not api_username or not api_password or not query_key then + module:log("error", "Missing required config values (voipms_api_username, voipms_api_password, voipms_query_key)") + return +end + +module:depends("http") + +local function normalize_number(num) + if not num then return nil end + if num:sub(1, 1) ~= "+" then + return "+1" .. num + end + return num +end + +local function extract_query_key(event) + return (event.request.url.query or ""):match("key=([^&]+)") +end + +module:provides("http", { + route = { + ["POST"] = function(event) + local req = event.request + local body = req.body or "" + + if extract_query_key(event) ~= query_key then + module:log("warn", "Unauthorized webhook: missing or invalid key") + return { status_code = 403 } + end + + local json_payload, err = json.decode(body) + if not json_payload then + module:log("warn", "Invalid JSON: %s", err or "unknown error") + return { status_code = 400 } + end + + local payload = json_payload.data and json_payload.data.payload + if not payload then + module:log("warn", "Missing payload in JSON") + return { status_code = 400 } + end + + local from = payload.from and payload.from.phone_number + local to_list = payload.to or {} + local to = #to_list > 0 and to_list[1].phone_number or nil + + if not from or not to then + module:log("warn", "Missing phone numbers (from: %s, to: %s)", tostring(from), tostring(to)) + return { status_code = 400 } + end + + local normalized_to = normalize_number(to) + local target_jid = nil + + for jid, did in pairs(jid_map) do + if normalize_number(did) == normalized_to then + target_jid = jid + break + end + end + + if not target_jid then + module:log("warn", "No JID mapping for DID %s", normalized_to) + return { status_code = 404 } + end + + local normalized_from = normalize_number(from) + local message_text = payload.text or "" + + local message = st.message({ + from = normalized_from .. "@" .. module.host, + to = target_jid, + type = "chat" + }):tag("body"):text(message_text):up() + + if payload.media and #payload.media > 0 then + for _, media_item in ipairs(payload.media) do + if media_item.url then + message_text = message_text .. "\n" .. media_item.url + message:tag("x", { xmlns = "jabber:x:oob" }) + :tag("url"):text(media_item.url):up():up() + end + end + end + + module:send(message) + module:log("info", "Delivered SMS from %s to %s", normalized_from, target_jid) + + return { status_code = 204 } + end + } +}) + +module:hook("message/bare", function(event) + local stanza = event.stanza + if stanza.attr.type ~= "chat" then return end + + local from_jid = stanza.attr.from + local to_jid = stanza.attr.to + local body = stanza:get_child_text("body") + if not body or body == "" then return end + + local from_number = jid_map[from_jid] + if not from_number then + module:log("warn", "No DID mapping for JID %s", from_jid) + return + end + + local to_number = to_jid:match("^([^@]+)") + if not to_number then + module:log("warn", "Malformed JID in message.to: %s", to_jid) + return + end + + to_number = normalize_number(to_number) + + local media_urls = {} + for line in body:gmatch("[^\r\n]+") do + if line:match("^https?://") then + table.insert(media_urls, line) + end + end + + local method = (#media_urls > 0) and "sendMMS" or "sendSMS" + local query = { + api_username = api_username, + api_password = api_password, + method = method, + did = from_number, + dst = to_number, + message = body + } + + if method == "sendMMS" then + for i, url in ipairs(media_urls) do + query["media_url[" .. (i - 1) .. "]"] = url + end + end + + local query_str = http.formencode(query) + + http.request(rest_endpoint .. "?" .. query_str, { + method = "GET"; + }, function(response_body, code) + if code == 200 then + local resp, err = json.decode(response_body) + if not resp or resp.status ~= "success" then + module:log("error", "Failed to send %s: %s", method, err or (resp and resp.status) or "unknown") + else + module:log("info", "Sent %s from %s to %s", method, from_number, to_number) + end + else + module:log("error", "HTTP error sending %s: code %s", method, tostring(code)) + end + end) +end)
author Chaz <menel@snikket.de>
date Mon, 21 Jul 2025 23:57:37 +0200
parents fe081789f7b5
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
1 ---
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
2 summary: 'Allows implementing a component or bot over HTTP'
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
3 ...
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
4
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
5 Introduction
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
6 ============
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
7
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
8 This module allows you to implement a component that speaks HTTP. Stanzas (such as messages) coming from XMPP are sent to
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
9 a configurable URL as a HTTP POST. If the POST returns a response, that response is returned to the sender over XMPP.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
10
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
11 See also mod_post_msg.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
12
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
13 Example usage
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
14 -------------
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
15
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
16 Example echo bot in PHP:
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
17
2954
1f06a7fe75a8 mod_component_http/README: Include language tag in example to enable syntax highlighting in rendered version
Kim Alvefur <zash@zash.se>
parents: 2953
diff changeset
18 ``` php
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
19 <?php
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
20
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
21 // Receive and decode message JSON
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
22 $post_data = file_get_contents('php://input');
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
23 $received = json_decode($post_data)->body;
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
24
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
25 // Send response
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
26 header('Content-Type: application/json');
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
27 echo json_encode(array(
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
28 'body' => "Did you say $received?"
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
29 ));
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
30
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
31 ?>
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
32 ```
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
33
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
34 Configuration
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
35 =============
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
36
2953
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
37 The module is quite flexible, but should generally be loaded as a component like this:
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
38
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
39 ```
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
40 Component "yourservice.example.com" "component_http"
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
41 component_post_url = "https://example.com/your-api"
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
42 ```
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
43
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
44 Such a component would handle traffic for all JIDs with 'yourservice.example.com' as the hostname, such
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
45 as 'foobar@yourservice.example.com'. Although this example uses a subdomain, there is no requirement for
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
46 the component to use a subdomain.
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
47
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
48 Available configuration options are:
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
49
0092bcceda0a mod_component_http: Update README to include config snippet example
Matthew Wild <mwild1@gmail.com>
parents: 2951
diff changeset
50
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
51 Option Description
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
52 ------------------------------------ -------------------------------------------------------------------------------------------------------------------------------------------------
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
53 component\_post\_url The URL that will handle incoming stanzas
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
54 component\_post\_stanzas A list of stanza types to forward over HTTP. Defaults to `{ "message" }`.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
55
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
56 Details
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
57 =======
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
58
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
59 Requests
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
60 --------
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
61
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
62 Each received stanza is converted into a JSON object, and submitted to `component_post_url` using a HTTP POST request.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
63
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
64 The JSON object always has the following properties:
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
65
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
66 Property Description
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
67 -------------------------- ------------
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
68 to The JID that the stanza was sent to (e.g. foobar@your.component.domain)
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
69 from The sender's JID.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
70 kind The kind of stanza (will always be "message", "presence" or "iq".
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
71 stanza The full XML of the stanza.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
72
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
73 Additionally, the JSON object may contain the following properties:
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
74
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
75 Property Description
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
76 -------------------------- ------------
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
77 body If the stanza is a message, and it contains a body, this is the string content of the body.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
78
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
79
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
80 Responses
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
81 ---------
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
82
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
83 If you wish to respond to a stanza, you may include a reply when you respond to the HTTP request.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
84
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
85 Responses must have a HTTP status 200 (OK), and must set the Conent-Type header to `application/json`.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
86
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
87 A response may contain any of the properties of a request. If not supplied, then defaults are chosen.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
88
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
89 If 'to' and 'from' are not specified in the response, they are automatically swapped so that the reply is sent to the original sender of the stanza.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
90
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
91 If 'kind' is not set, it defaults to 'message', and if 'body' is set, this is automatically added as a message body.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
92
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
93 If 'stanza' is set, it overrides all of the above, and the supplied stanza is sent as-is using Prosody's normal routing rules. Note that stanzas
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
94 sent by components must have a 'to' and 'from'.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
95
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
96 Presence
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
97 --------
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
98
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
99 By default the module automatically handles presence to provide an always-on component, that automatically accepts subscription requests.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
100
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
101 This means that by default presence stanzas are not forwarded to the configured URL. To provide your own presence handling, you can override
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
102 this by adding "presence" to the component\_post\_stanzas option in your config.
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
103
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
104
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
105 Compatibility
2951
01ed9eb111d4 mod_component_http: Fix headings in README
Matthew Wild <mwild1@gmail.com>
parents: 2950
diff changeset
106 =============
2950
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
107
18e6d437003f mod_component_http: Allow implementing a component over HTTP
Matthew Wild <mwild1@gmail.com>
parents:
diff changeset
108 Should work with all versions of Prosody from 0.9 upwards.