2using System.Collections.Generic;
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);
382 if (!(Obj is
string JID))
387 string JidToValidate = JID;
389 JidToValidate +=
"example.org";
392 await Response.
Write(
"0");
400 await Response.
Write(
"1");
406 await Response.
Write(
"2");
409 await Response.
Write(
"3");
413 private async Task<string> NickName()
415 SuggestionEventArgs e =
new SuggestionEventArgs(
string.Empty);
418 string[] Suggestions = e.ToArray();
419 string NickName = Suggestions.Length > 0 ? Suggestions[0] : (string)Gateway.Domain;
426 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
432 if (!(Obj is
string JID))
440 await Response.
Write(
"0");
445 await Response.
Write(
"1");
451 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
457 if (!(Obj is
string JID))
465 await Response.
Write(
"0");
470 await Response.
Write(
"1");
476 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
482 if (!(Obj is
string JID))
490 await Response.
Write(
"0");
495 await Response.
Write(
"1");
501 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
507 string JID = Request.
Header[
"X-BareJID"];
508 string NewName = Obj as string;
509 if (JID is
null ||
string.IsNullOrEmpty(JID))
517 await Response.
Write(
"0");
522 await Response.
Write(
"1");
528 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
534 string BareJid = Obj as string;
535 if (
string.IsNullOrEmpty(BareJid))
542 SortedDictionary<string, bool> Groups =
new SortedDictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
543 string[] GroupsArray;
547 while (!
string.IsNullOrEmpty(s = System.Web.HttpUtility.UrlDecode(Request.
Header[
"X-Group-" + (++i).ToString()])))
550 if (Groups.Count > 0)
552 GroupsArray =
new string[Groups.Count];
553 Groups.Keys.CopyTo(GroupsArray, 0);
556 GroupsArray =
new string[0];
558 StringBuilder sb =
new StringBuilder();
561 if (!(Groups is
null))
563 foreach (
string Group
in GroupsArray)
575 Log.
Informational(
"Contact groups updated.", Contact.
BareJid,
new KeyValuePair<string, object>(
"Groups", sb.ToString()));
578 await Response.
Write(
"1");
583 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
589 if (
string.IsNullOrEmpty(StartsWith))
592 SuggestionEventArgs e =
new SuggestionEventArgs(StartsWith.Trim());
594 foreach (
RosterItem Item
in Gateway.XmppClient.Roster)
596 foreach (
string Group
in Item.
Groups)
597 e.AddSuggestion(Group);
602 StringBuilder sb =
new StringBuilder();
603 string[] Groups = e.ToArray();
607 sb.Append(
"{\"groups\":[");
609 foreach (
string Group
in Groups)
623 sb.Append(
"],\"count\":");
624 sb.Append(Nr.ToString());
628 await Response.
Write(sb.ToString());
649 if (!(Obj is
string JID))
657 if (SubscriptionRequest is
null)
658 await Response.
Write(
"0");
661 await SubscriptionRequest.
Accept();
671 await Response.
Write(
"1");
683 if (!(Obj is
string JID))
691 if (SubscriptionRequest is
null)
692 await Response.
Write(
"0");
695 await SubscriptionRequest.
Decline();
698 this.RosterItemRemoved(JID);
700 await Response.
Write(
"1");
710 return Task.FromResult(
true);
751 return Task.FromResult(
false);
755 return Task.FromResult(
false);
759 return Task.FromResult(
false);
763 if (ToAdd is
null && ToSubscribe is
null && ToAccept is
null && GroupNames is
null)
764 return Task.FromResult(
true);
766 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid =
767 new Dictionary<string, SortedDictionary<string, bool>>(StringComparer.InvariantCultureIgnoreCase);
769 foreach (
string Group
in GroupNames)
772 string[] Jids = GetElements(Name);
776 return Task.FromResult(
false);
780 return Task.FromResult(
false);
782 foreach (
string Jid
in Jids)
784 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> Groups))
786 Groups =
new SortedDictionary<string, bool>();
787 GroupsByJid[Jid] = Groups;
790 Groups[Group] =
true;
795 this.toSubscribe = ToSubscribe;
796 this.toAccept = ToAccept;
797 this.groupsByJid = GroupsByJid;
801 return Task.FromResult(
true);
804 private Dictionary<string, string> toAdd =
null;
805 private Dictionary<string, bool> toSubscribe =
null;
806 private Dictionary<string, bool> toAccept =
null;
807 private Dictionary<string, SortedDictionary<string, bool>> groupsByJid =
null;
809 private bool ValidateJids(IEnumerable<string> Jids,
string ParameterName)
811 foreach (
string Jid
in Jids)
823 private (string, string,
string[]) PopToAdd()
825 Dictionary<string, string> ToAdd = this.toAdd;
827 return (
null,
null,
null);
834 foreach (KeyValuePair<string, string> P
in this.toAdd)
838 this.toAdd.Remove(Jid);
846 return (
null,
null,
null);
849 Dictionary<string, SortedDictionary<string, bool>> GroupsByJid = this.groupsByJid;
850 if (GroupsByJid is
null)
851 return (Jid, Name,
null);
857 if (!GroupsByJid.TryGetValue(Jid, out SortedDictionary<string, bool> GroupsOrdered))
858 return (Jid, Name,
null);
860 GroupsByJid.Remove(Jid);
861 if (GroupsByJid.Count == 0)
862 this.groupsByJid =
null;
864 Groups =
new string[GroupsOrdered.Count];
865 GroupsOrdered.Keys.CopyTo(Groups, 0);
868 return (Jid, Name, Groups);
871 private string PopToSubscribe()
873 Dictionary<string, bool> ToSubscribe = this.toSubscribe;
874 if (ToSubscribe is
null)
879 foreach (
string Jid
in this.toSubscribe.Keys)
881 this.toSubscribe.Remove(Jid);
886 this.toSubscribe =
null;
890 private bool AcceptSubscriptionRequest(
string Jid)
892 Dictionary<string, bool> ToAccept = this.toAccept;
893 if (ToAccept is
null)
898 if (!ToAccept.ContainsKey(Jid))
901 ToAccept.Remove(Jid);
902 if (ToAccept.Count == 0)
903 this.toAccept =
null;
909 private static string[] GetElements(
string VariableName)
911 string Value = Environment.GetEnvironmentVariable(VariableName);
912 if (
string.IsNullOrEmpty(Value))
915 return Value.Split(
',');
918 private static Dictionary<string, bool> GetDictionaryElements(
string VariableName)
920 string[] Elements = GetElements(VariableName);
921 if (Elements is
null)
924 Dictionary<string, bool> Result =
new Dictionary<string, bool>(StringComparer.InvariantCultureIgnoreCase);
926 foreach (
string Element
in Elements)
927 Result[Element] =
true;
932 private static Dictionary<string, string> GetDictionaryElementWithNames(
string VariableName)
934 string[] Elements = GetElements(VariableName);
935 if (Elements is
null)
938 Dictionary<string, string> Result =
new Dictionary<string, string>();
940 foreach (
string Element
in Elements)
943 Result[Element] = Name ??
string.Empty;
Helps with parsing of commong data types.
static string JsonStringEncode(string s)
Encodes a string for inclusion in JSON.
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.
Static class managing loading of resources stored as embedded resources or in content files.
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
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 CaseInsensitiveString Domain
Domain name.
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
static RequiredUserPrivileges 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 string Resource
Resource to be redirected to, to perform the configuration.
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< object > DecodeDataAsync()
Decodes data sent in request.
Base class for all HTTP resources.
Represets a response of an HTTP client request.
async Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
static Variables CreateVariables()
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.
Represents a case-insensitive string.
static bool IsNullOrEmpty(CaseInsensitiveString value)
Indicates whether the specified string is null or an CaseInsensitiveString.Empty string.
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.