Changeset

13721:8170dd8f370c

Merge 13.0->trunk
author Matthew Wild <mwild1@gmail.com>
date Sun, 16 Feb 2025 13:44:23 +0000
parents 13716:953108962cd0 (current diff) 13720:c3c4281c1339 (diff)
children 13724:650f3869de82
files
diffstat 7 files changed, 130 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES	Sun Feb 16 11:57:18 2025 +0100
+++ b/CHANGES	Sun Feb 16 13:44:23 2025 +0000
@@ -62,6 +62,7 @@
 - Method for retrieving integer settings from config
 - It is now easy for modules to expose a Prosody shell command, by adding a shell-command item
 - Modules can now implement a module.ready method which will be called after server initialization
+- module:depends() now accepts a second parameter 'soft' to enable soft dependencies
 
 ### Configuration
 
@@ -84,6 +85,7 @@
 - Support for systemd socket activation in server_epoll
 - mod_invites_adhoc gained a command for creating password resets
 - mod_cloud_notify imported from community modules for push notification support
+- mod_http_altconnect imported from community modules, simplifying web clients
 
 ## Removed
 
--- a/core/features.lua	Sun Feb 16 11:57:18 2025 +0100
+++ b/core/features.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -12,6 +12,8 @@
 		"mod_cloud_notify";
 		-- mod_muc has built-in vcard support
 		"muc_vcard";
+		-- mod_http_altconnect bundled
+		"http_altconnect";
 		-- Roles, module.may and per-session authz
 		"permissions";
 		-- prosody.* namespace
--- a/core/moduleapi.lua	Sun Feb 16 11:57:18 2025 +0100
+++ b/core/moduleapi.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -136,10 +136,14 @@
 	return f();
 end
 
-function api:depends(name)
+function api:depends(name, soft)
 	local modulemanager = require"prosody.core.modulemanager";
 	if self:get_option_inherited_set("modules_disabled", {}):contains(name) then
-		error("Dependency on disabled module mod_"..name);
+		if not soft then
+			error("Dependency on disabled module mod_"..name);
+		end
+		self:log("debug", "Not loading disabled soft dependency mod_%s", name);
+		return nil, "disabled";
 	end
 	if not self.dependencies then
 		self.dependencies = {};
--- a/plugins/mod_bosh.lua	Sun Feb 16 11:57:18 2025 +0100
+++ b/plugins/mod_bosh.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -557,6 +557,8 @@
 			["POST /"] = handle_POST;
 		};
 	});
+
+	module:depends("http_altconnect", true);
 end
 
 if require"prosody.core.modulemanager".get_modules_for_host("*"):contains(module.name) then
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/mod_http_altconnect.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -0,0 +1,52 @@
+-- mod_http_altconnect
+-- XEP-0156: Discovering Alternative XMPP Connection Methods
+
+module:depends"http";
+
+local mm = require "prosody.core.modulemanager";
+local json = require"prosody.util.json";
+local st = require"prosody.util.stanza";
+local array = require"prosody.util.array";
+
+local advertise_bosh = module:get_option_boolean("advertise_bosh", true);
+local advertise_websocket = module:get_option_boolean("advertise_websocket", true);
+
+local function get_supported()
+	local uris = array();
+	if advertise_bosh and (mm.is_loaded(module.host, "bosh") or mm.is_loaded("*", "bosh")) then
+		uris:push({ rel = "urn:xmpp:alt-connections:xbosh", href = module:http_url("bosh", "/http-bind") });
+	end
+	if advertise_websocket and (mm.is_loaded(module.host, "websocket") or  mm.is_loaded("*", "websocket")) then
+		uris:push({ rel = "urn:xmpp:alt-connections:websocket", href = module:http_url("websocket", "xmpp-websocket"):gsub("^http", "ws") });
+	end
+	return uris;
+end
+
+
+local function GET_xml(event)
+	local response = event.response;
+	local xrd = st.stanza("XRD", { xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0' });
+	local uris = get_supported();
+	for _, method in ipairs(uris) do
+		xrd:tag("Link", method):up();
+	end
+	response.headers.content_type = "application/xrd+xml"
+	response.headers.access_control_allow_origin = "*";
+	return '<?xml version="1.0" encoding="UTF-8"?>' .. tostring(xrd);
+end
+
+local function GET_json(event)
+	local response = event.response;
+	local jrd = { links = get_supported() };
+	response.headers.content_type = "application/json"
+	response.headers.access_control_allow_origin = "*";
+	return json.encode(jrd);
+end;
+
+module:provides("http", {
+	default_path = "/.well-known";
+	route = {
+		["GET /host-meta"] = GET_xml;
+		["GET /host-meta.json"] = GET_json;
+	};
+});
--- a/plugins/mod_websocket.lua	Sun Feb 16 11:57:18 2025 +0100
+++ b/plugins/mod_websocket.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -367,6 +367,8 @@
 		};
 	});
 
+	module:depends("http_altconnect", true);
+
 	module:hook("c2s-read-timeout", keepalive, -0.9);
 end
 
--- a/util/prosodyctl/check.lua	Sun Feb 16 11:57:18 2025 +0100
+++ b/util/prosodyctl/check.lua	Sun Feb 16 13:44:23 2025 +0000
@@ -1486,6 +1486,10 @@
 			muc = "groups";
 		};
 
+		local recommended_component_modules = {
+			muc = { "muc_mam" };
+		};
+
 		local function print_feature_status(feature, host)
 			if quiet then return; end
 			print("", feature.ok and "OK" or "(!)", feature.name);
@@ -1501,11 +1505,20 @@
 					table.sort(feature.lacking_components);
 					for _, component_module in ipairs(feature.lacking_components) do
 						local subdomain = common_subdomains[component_module];
+						local recommended_mods = recommended_component_modules[component_module];
 						if subdomain then
 							print("", "", "Suggested component:");
 							print("");
+							print("", "", "", ("-- Documentation: https://prosody.im/doc/modules/mod_%s"):format(component_module));
 							print("", "", "", ("Component %q %q"):format(subdomain.."."..host, component_module));
-							print("", "", "", ("-- Documentation: https://prosody.im/doc/modules/mod_%s"):format(component_module));
+							if recommended_mods then
+								print("", "", "", "    modules_enabled = {");
+								table.sort(recommended_mods);
+								for _, mod in ipairs(recommended_mods) do
+									print("", "", "", ("        %q;"):format(mod));
+								end
+								print("", "", "", "    }");
+							end
 						else
 							print("", "", ("Suggested component: %s"):format(component_module));
 						end
@@ -1514,6 +1527,30 @@
 					print("", "", "If you have already configured any these components, they may not be");
 					print("", "", "linked correctly to "..host..". For more info see https://prosody.im/doc/components");
 				end
+				if feature.lacking_component_modules then
+					table.sort(feature.lacking_component_modules, function (a, b)
+						return a.host < b.host;
+					end);
+					for _, problem in ipairs(feature.lacking_component_modules) do
+						local hostapi = api(problem.host);
+						local current_modules_enabled = hostapi:get_option_array("modules_enabled", {});
+						print("", "", ("Component %q is missing the following modules: %s"):format(problem.host, table.concat(problem.missing_mods)));
+						print("");
+						print("","", "Add the missing modules to your modules_enabled under the Component, like this:");
+						print("");
+						print("");
+						print("", "", "", ("-- Documentation: https://prosody.im/doc/modules/mod_%s"):format(problem.component_module));
+						print("", "", "", ("Component %q %q"):format(problem.host, problem.component_module));
+						print("", "", "", ("    modules_enabled = {"));
+						for _, mod in ipairs(current_modules_enabled) do
+							print("", "", "", ("        %q;"):format(mod));
+						end
+						for _, mod in ipairs(problem.missing_mods) do
+							print("", "", "", ("        %q; -- Add this!"):format(mod));
+						end
+						print("", "", "", ("    }"));
+					end
+				end
 			end
 			print("");
 		end
@@ -1572,8 +1609,27 @@
 			local function check_component(suggested, alternate, ...)
 				local found;
 				for _, component_module in ipairs({ suggested, alternate, ... }) do
-					found = #host_components[component_module] > 0;
-					if found then break; end
+					found = host_components[component_module][1];
+					if found then
+						local enabled_component_modules = api(found):get_option_inherited_set("modules_enabled");
+						local recommended_mods = recommended_component_modules[component_module];
+						local missing_mods = {};
+						for _, mod in ipairs(recommended_mods) do
+							if not enabled_component_modules:contains(mod) then
+								table.insert(missing_mods, mod);
+							end
+						end
+						if #missing_mods > 0 then
+							if not current_feature.lacking_component_modules then
+								current_feature.lacking_component_modules = {};
+							end
+							table.insert(current_feature.lacking_component_modules, {
+								host = found;
+								component_module = component_module;
+								missing_mods = missing_mods;
+							});
+						end
+					end
 				end
 				if not found then
 					current_feature.lacking_components = current_feature.lacking_components or {};
@@ -1664,7 +1720,11 @@
 			for _, feature in ipairs(features) do
 				current_feature = feature;
 				feature.check();
-				feature.ok = not feature.lacking_modules and not feature.lacking_components;
+				feature.ok = (
+					not feature.lacking_modules and
+					not feature.lacking_components and
+					not feature.lacking_component_modules
+				);
 				-- For improved presentation, we group the (ok) and (not ok) features
 				if feature.ok then
 					print_feature_status(feature, host);