5using System.Threading.Tasks;
55 public override string Resource =>
"/Settings/Roster.md";
78 return Task.CompletedTask;
81 private void AddHandlers()
85 this.handlersAdded =
true;
88 Gateway.XmppClient.OnRosterItemAdded += this.XmppClient_OnRosterItemAdded;
89 Gateway.XmppClient.OnRosterItemRemoved += this.XmppClient_OnRosterItemRemoved;
90 Gateway.XmppClient.OnRosterItemUpdated += this.XmppClient_OnRosterItemUpdated;
91 Gateway.XmppClient.OnPresence += this.XmppClient_OnPresence;
92 Gateway.XmppClient.OnPresenceSubscribe += this.XmppClient_OnPresenceSubscribe;
93 Gateway.XmppClient.OnStateChanged += this.XmppClient_OnStateChanged;
97 private bool handlersAdded =
false;
100 private async Task XmppClient_OnStateChanged(
object _,
XmppState NewState)
104 string[] TabIDs = this.GetTabIDs();
105 if (TabIDs.Length > 0 && !(Gateway.XmppClient is
null))
107 string Json =
JSON.
Encode(
new KeyValuePair<string, object>[]
109 new KeyValuePair<string, object>(
"html", await this.RosterItemsHtml(Gateway.XmppClient.Roster, Gateway.XmppClient.SubscriptionRequests))
112 Task _2 = ClientEvents.PushEvent(TabIDs,
"UpdateRoster", Json,
true,
"User");
115 while (Gateway.XmppClient?.State ==
XmppState.Connected)
117 (
string Jid,
string Name,
string[] Groups) = this.PopToAdd();
119 if (!
string.IsNullOrEmpty(Jid))
121 RosterItem Item = Gateway.XmppClient.GetRosterItem(Jid);
124 await Gateway.XmppClient.AddRosterItem(
new RosterItem(Jid, Name, Groups));
125 else if (NeedsUpdate(Item, Name, Groups))
126 await Gateway.XmppClient.AddRosterItem(
new RosterItem(Jid, Name, Union(Item.
Groups, Groups)));
131 Jid = this.PopToSubscribe();
132 if (!
string.IsNullOrEmpty(Jid))
134 RosterItem Item = Gateway.XmppClient.GetRosterItem(Jid);
137 await Gateway.XmppClient.RequestPresenceSubscription(Jid);
147 private static bool NeedsUpdate(
RosterItem Item,
string Name,
string[] Groups)
149 if (Item.
Name != Name)
155 Dictionary<string, bool> Found =
new Dictionary<string, bool>();
157 if (!(Item.
Groups is
null))
159 foreach (
string Group
in Item.
Groups)
163 if (!(Groups is
null))
165 foreach (
string Group
in Groups)
167 if (!Found.ContainsKey(Group))
175 private static string[] Union(
string[] A1,
string[] A2)
177 SortedDictionary<string, bool> Entries =
new SortedDictionary<string, bool>();
181 foreach (
string s
in A1)
187 foreach (
string s
in A2)
191 string[] Result =
new string[Entries.Count];
192 Entries.Keys.CopyTo(Result, 0);
197 private async Task XmppClient_OnPresenceSubscribe(
object Sender,
PresenceEventArgs e)
199 if (
string.Compare(e.
FromBareJID, Gateway.XmppClient.BareJID,
true) == 0)
208 StringBuilder Markdown =
new StringBuilder();
210 Markdown.Append(
"Presence subscription request received from **");
212 Markdown.Append(
"**. You can accept or decline the request from the roster configuration in the Administration portal.");
214 await Gateway.SendNotification(Markdown.ToString());
216 string[] TabIDs = this.GetTabIDs();
217 if (TabIDs.Length > 0)
219 string Json =
JSON.
Encode(
new KeyValuePair<string, object>[]
221 new KeyValuePair<string, object>(
"bareJid", e.
FromBareJID),
225 Task
_ = ClientEvents.PushEvent(TabIDs,
"UpdateRosterItem", Json,
true,
"User");
233 await this.XmppClient_OnRosterItemUpdated(Sender, Item);
236 private async Task XmppClient_OnRosterItemUpdated(
object _,
RosterItem Item)
238 string[] TabIDs = this.GetTabIDs();
239 if (TabIDs.Length > 0)
241 string Json =
JSON.
Encode(
new KeyValuePair<string, object>[]
243 new KeyValuePair<string, object>(
"bareJid", Item.
BareJid),
247 await ClientEvents.PushEvent(TabIDs,
"UpdateRosterItem", Json,
true,
"User");
251 private Task XmppClient_OnRosterItemRemoved(
object _,
RosterItem Item)
253 this.RosterItemRemoved(Item.
BareJid);
254 return Task.CompletedTask;
257 private void RosterItemRemoved(
string BareJid)
259 string[] TabIDs = this.GetTabIDs();
260 if (TabIDs.Length > 0)
262 string Json =
JSON.
Encode(
new KeyValuePair<string, object>[]
264 new KeyValuePair<string, object>(
"bareJid", BareJid)
267 Task
_ = ClientEvents.PushEvent(TabIDs,
"RemoveRosterItem", Json,
true,
"User");
271 private Task XmppClient_OnRosterItemAdded(
object Sender,
RosterItem Item)
273 return this.XmppClient_OnRosterItemUpdated(Sender, Item);
276 private string[] GetTabIDs()
278 if (Gateway.Configuring)
279 return ClientEvents.GetTabIDs();
281 return ClientEvents.GetTabIDsForLocation(
"/Settings/Roster.md");
286 string FileName = Path.Combine(Gateway.RootFolder,
"Settings",
"RosterItems.md");
289 v[
"Contacts"] = Contacts;
290 v[
"Requests"] = SubscriptionRequests;
323 this.connectToJID = WebServer.
Register(
"/Settings/ConnectToJID",
null, this.ConnectToJID,
true,
false,
true, Auth);
324 this.removeContact = WebServer.
Register(
"/Settings/RemoveContact",
null, this.RemoveContact,
true,
false,
true, Auth);
325 this.unsubscribeContact = WebServer.
Register(
"/Settings/UnsubscribeContact",
null, this.UnsubscribeContact,
true,
false,
true, Auth);
326 this.subscribeToContact = WebServer.
Register(
"/Settings/SubscribeToContact",
null, this.SubscribeToContact,
true,
false,
true, Auth);
327 this.renameContact = WebServer.
Register(
"/Settings/RenameContact",
null, this.RenameContact,
true,
false,
true, Auth);
328 this.updateContactGroups = WebServer.
Register(
"/Settings/UpdateContactGroups",
null, this.UpdateContactGroups,
true,
false,
true, Auth);
329 this.getGroups = WebServer.
Register(
"/Settings/GetGroups",
null, this.GetGroups,
true,
false,
true, Auth);
330 this.acceptRequest = WebServer.
Register(
"/Settings/AcceptRequest",
null, this.AcceptRequest,
true,
false,
true, Auth);
331 this.declineRequest = WebServer.
Register(
"/Settings/DeclineRequest",
null, this.DeclineRequest,
true,
false,
true, Auth);
333 return base.InitSetup(WebServer);
344 WebServer.
Unregister(this.unsubscribeContact);
345 WebServer.
Unregister(this.subscribeToContact);
347 WebServer.
Unregister(this.updateContactGroups);
352 return base.UnregisterSetup(WebServer);
371 return await base.SetupConfiguration(WebServer);
393 string JidToValidate = JID;
394 if (!Gateway.HasDomain && JidToValidate.EndsWith(
"@"))
395 JidToValidate +=
"example.org";
398 await Response.
Write(
"0");
401 RosterItem Item = Gateway.XmppClient.GetRosterItem(JID);
404 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
406 await Response.
Write(
"1");
410 await Gateway.XmppClient.RequestPresenceSubscription(JID, await this.NickName());
412 await Response.
Write(
"2");
415 await Response.
Write(
"3");
419 private async Task<string> NickName()
421 SuggestionEventArgs e =
new SuggestionEventArgs(
string.Empty);
424 string[] Suggestions = e.ToArray();
425 string NickName = Suggestions.Length > 0 ? Suggestions[0] : (string)Gateway.Domain;
432 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
452 await Response.
Write(
"0");
457 await Response.
Write(
"1");
463 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
483 await Response.
Write(
"0");
488 await Response.
Write(
"1");
494 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
514 await Response.
Write(
"0");
519 await Response.
Write(
"1");
525 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
540 string JID = Request.
Header[
"X-BareJID"];
541 if (JID is
null ||
string.IsNullOrEmpty(JID))
552 await Response.
Write(
"0");
557 await Response.
Write(
"1");
563 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
572 if (Content.
HasError || !(Content.
Decoded is
string BareJid) ||
string.IsNullOrEmpty(BareJid))
586 SortedDictionary<string, bool> Groups =
new SortedDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
587 string[] GroupsArray;
591 while (!
string.IsNullOrEmpty(s =
System.Web.HttpUtility.UrlDecode(Request.
Header[
"X-Group-" + (++i).ToString()])))
594 if (Groups.Count > 0)
596 GroupsArray =
new string[Groups.Count];
597 Groups.Keys.CopyTo(GroupsArray, 0);
600 GroupsArray = Array.Empty<
string>();
602 StringBuilder sb =
new StringBuilder();
605 if (!(Groups is
null))
607 foreach (
string Group
in GroupsArray)
619 Log.
Informational(
"Contact groups updated.", Contact.
BareJid,
new KeyValuePair<string, object>(
"Groups", sb.ToString()));
622 await Response.
Write(
"1");
627 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
637 if (Content.
HasError || !(Content.
Decoded is
string StartsWith) ||
string.IsNullOrEmpty(StartsWith))
643 SuggestionEventArgs e =
new SuggestionEventArgs(StartsWith.Trim());
645 foreach (
RosterItem Item
in Gateway.XmppClient.Roster)
647 foreach (
string Group
in Item.
Groups)
648 e.AddSuggestion(Group);
653 StringBuilder sb =
new StringBuilder();
654 string[] Groups = e.ToArray();
658 sb.Append(
"{\"groups\":[");
660 foreach (
string Group
in Groups)
674 sb.Append(
"],\"count\":");
675 sb.Append(Nr.ToString());
679 await Response.
Write(sb.ToString());
714 if (SubscriptionRequest is
null)
715 await Response.
Write(
"0");
718 await SubscriptionRequest.
Accept();
728 await Response.
Write(
"1");
754 if (SubscriptionRequest is
null)
755 await Response.
Write(
"0");
758 await SubscriptionRequest.
Decline();
761 this.RosterItemRemoved(JID);
763 await Response.
Write(
"1");
773 return Task.FromResult(
true);
814 return Task.FromResult(
false);
818 return Task.FromResult(
false);
822 return Task.FromResult(
false);
826 if (ToAdd is
null && ToSubscribe is
null && ToAccept is
null && GroupNames is
null)
827 return Task.FromResult(
true);
829 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid =
830 new Dictionary<string, SortedDictionary<string, bool>>(StringComparer.InvariantCultureIgnoreCase);
832 foreach (
string Group
in GroupNames)
835 string[] Jids = GetElements(Name);
839 return Task.FromResult(
false);
843 return Task.FromResult(
false);
845 foreach (
string Jid
in Jids)
847 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> Groups))
849 Groups =
new SortedDictionary<string, bool>();
850 GroupsByJid[Jid] = Groups;
853 Groups[Group] =
true;
858 this.toSubscribe = ToSubscribe;
859 this.toAccept = ToAccept;
860 this.groupsByJid = GroupsByJid;
864 return Task.FromResult(
true);
867 private Dictionary<string, string> toAdd =
null;
868 private Dictionary<string, bool> toSubscribe =
null;
869 private Dictionary<string, bool> toAccept =
null;
870 private Dictionary<string, SortedDictionary<string, bool>> groupsByJid =
null;
872 private bool ValidateJids(IEnumerable<string> Jids,
string ParameterName)
874 foreach (
string Jid
in Jids)
886 private (string, string,
string[]) PopToAdd()
888 Dictionary<string, string> ToAdd = this.toAdd;
890 return (
null,
null,
null);
897 foreach (KeyValuePair<string, string> P
in this.toAdd)
901 this.toAdd.Remove(Jid);
909 return (
null,
null,
null);
912 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid = this.groupsByJid;
913 if (GroupsByJid is
null)
914 return (Jid, Name,
null);
920 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> GroupsOrdered))
921 return (Jid, Name,
null);
923 GroupsByJid.Remove(Jid);
924 if (GroupsByJid.Count == 0)
925 this.groupsByJid =
null;
927 Groups =
new string[GroupsOrdered.Count];
928 GroupsOrdered.Keys.CopyTo(Groups, 0);
931 return (Jid, Name, Groups);
934 private string PopToSubscribe()
936 Dictionary<string, bool> ToSubscribe = this.toSubscribe;
937 if (ToSubscribe is
null)
942 foreach (
string Jid
in this.toSubscribe.Keys)
944 this.toSubscribe.Remove(Jid);
949 this.toSubscribe =
null;
953 private bool AcceptSubscriptionRequest(
string Jid)
955 Dictionary<string, bool> ToAccept = this.toAccept;
956 if (ToAccept is
null)
961 if (!ToAccept.ContainsKey(Jid))
964 ToAccept.Remove(Jid);
965 if (ToAccept.Count == 0)
966 this.toAccept =
null;
972 private static string[] GetElements(
string VariableName)
974 string Value = Environment.GetEnvironmentVariable(VariableName);
975 if (
string.IsNullOrEmpty(Value))
978 return Value.Split(
',');
981 private static Dictionary<string, bool> GetDictionaryElements(
string VariableName)
983 string[] Elements = GetElements(VariableName);
984 if (Elements is
null)
987 Dictionary<string, bool> Result =
new Dictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
989 foreach (
string Element
in Elements)
990 Result[Element] =
true;
995 private static Dictionary<string, string> GetDictionaryElementWithNames(
string VariableName)
997 string[] Elements = GetElements(VariableName);
998 if (Elements is
null)
1001 Dictionary<string, string> Result =
new Dictionary<string, string>();
1003 foreach (
string Element
in Elements)
1006 Result[Element] = Name ??
string.Empty;
Helps with parsing of commong data types.
static string JsonStringEncode(string s)
Encodes a string for inclusion in JSON.
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
Helps with common JSON-related tasks.
static string Encode(string s)
Encodes a string for inclusion in JSON.
const string DefaultContentType
application/json
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Plain text encoder/decoder.
const string DefaultContentType
text/plain
Static class managing the application event log. Applications and services log events on this static ...
static void Informational(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an informational event.
Static class managing the runtime environment of the IoT Gateway.
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
static RequiredPrivileges LoggedIn(string[] Privileges)
Authentication mechanism that makes sure the call is made from a session with a valid authenticated u...
static XmppClient XmppClient
XMPP Client connection of gateway.
Allows the user to configure the XMPP Roster of the gateway.
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
override Task ConfigureSystem()
Is called during startup to configure the system.
const string GATEWAY_ROSTER_ADD
Optional Comma-separated list of Bare JIDs to add to the roster.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
override Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
const string GATEWAY_ROSTER_ACCEPT
Optional Comma-separated list of Bare JIDs to accept presence subscription requests from.
static EventHandlerAsync< SuggestionEventArgs > OnGetNickNameSuggestions
Event raised when list of nickname suggestions is populated.
const string GATEWAY_ROSTER_GRP_
Optional Comma-separated list of Bare JIDs in the roster to add to the group [group].
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
const string GATEWAY_ROSTER_GROUPS
Optional Comma-separated list of groups to define.
const string GATEWAY_ROSTER_SUBSCRIBE
Optional Comma-separated list of Bare JIDs to send presence subscription requests to.
RosterConfiguration()
Allows the user to configure the XMPP Roster of the gateway.
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
static RosterConfiguration Instance
Instance of configuration object.
static EventHandlerAsync< SuggestionEventArgs > OnGetGroupSuggestions
Event raised when list of group suggestions is populated.
override async Task< bool > SetupConfiguration(HttpServer WebServer)
Waits for the user to provide configuration.
const string GATEWAY_ROSTER_NAME_
Optional human-readable name of a JID in the roster.
Abstract base class for system configurations.
bool Complete
If the configuration is complete.
void LogEnvironmentVariableMissingError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value is missing.
void LogEnvironmentError(string EnvironmentVariable, object Value)
Logs an error to the event log, telling the operator an environment variable value contains an error.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
Base class for all HTTP authentication schemes, as defined in RFC-7235: https://datatracker....
Represents an HTTP request.
HttpRequestHeader Header
Request header.
bool HasData
If the request has data.
async Task< ContentResponse > DecodeDataAsync()
Decodes data sent in request.
Base class for all HTTP resources.
Represets a response of an HTTP client request.
async Task SendResponse()
Sends the response back to the client. If the resource is synchronous, there's no need to call this m...
Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
static SessionVariables CreateSessionVariables()
Creates a new collection of variables, that contains access to the global set of variables.
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
The server has not found anything matching the Request-URI. No indication is given of whether the con...
Event arguments for presence events.
string FromBareJID
Bare JID of resource sending the presence.
async Task Decline()
Declines a subscription or unsubscription request.
async Task Accept()
Accepts a subscription or unsubscription request.
Maintains information about an item in the roster.
SubscriptionState State
roup Current subscription state.
string[] Groups
Any groups the roster item belongs to.
string BareJid
Bare JID of the roster item.
string Name
Name of the roster item.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
XmppState State
Current state of connection.
KeyValuePair< string, string >[] LastSetPresenceCustomStatus
Last custom status set by the client when setting presence.
Task RemoveRosterItem(string BareJID)
Removes an item from the roster.
Task UpdateRosterItem(string BareJID, string Name, params string[] Groups)
Updates an item in the roster.
XmppClient(string Host, int Port, string UserName, string Password, string Language, Assembly AppAssembly, params ISniffer[] Sniffers)
Manages an XMPP client connection over a traditional binary socket connection.
Task RequestPresenceSubscription(string BareJid)
Requests subscription of presence information from a contact.
PresenceEventArgs GetSubscriptionRequest(string BareJID)
Gets a presence subscription request
static string EmbedNickName(string NickName)
Generates custom XML for embedding a nickname, as defined in XEP-0172. Can be used with RequestPresen...
static readonly Regex BareJidRegEx
Regular expression for Bare JIDs
Availability LastSetPresenceAvailability
Last availability set by the client when setting presence.
Task RequestPresenceUnsubscription(string BareJid)
Requests unssubscription of presence information from a contact.
Task Connect()
Connects the client.
Task SetPresence()
Sets the presence of the connection. Add a CustomPresenceXml event handler to add custom presence XML...
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Contains information about a language.
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Interface for system configurations. The gateway will scan all module for system configuration classe...
Availability
Resource availability.
SubscriptionState
State of a presence subscription.
XmppState
State of XMPP connection.