git » sdk » commit b30a5a4

Document some more undocumented APIs with basics

author Stephen Paul Weber
2026-05-03 20:38:34 UTC
committer Stephen Paul Weber
2026-05-03 20:38:34 UTC
parent 087fc331050e74888d0f5bbd7bd395183362eee1

Document some more undocumented APIs with basics

borogove/AsyncLock.hx +9 -0
borogove/AttachmentSource.cpp.hx +6 -0
borogove/AvailableChatIterator.hx +10 -0
borogove/Caps.hx +42 -0
borogove/Chat.hx +31 -5
borogove/Client.hx +10 -0
borogove/DataForm.hx +3 -0
borogove/EncryptionInfo.hx +3 -0
borogove/Form.hx +6 -0
borogove/Identicon.hx +3 -0
borogove/JID.hx +32 -0
borogove/Message.hx +9 -0
borogove/Presence.hx +3 -0
borogove/Profile.hx +33 -0
borogove/ReactionUpdate.hx +12 -0
borogove/Status.hx +6 -0

diff --git a/borogove/AsyncLock.hx b/borogove/AsyncLock.hx
index e9e4e36..4970b40 100644
--- a/borogove/AsyncLock.hx
+++ b/borogove/AsyncLock.hx
@@ -6,10 +6,19 @@ import thenshim.Promise;
 class AsyncLock {
 	private var p: Promise<Any>;
 
+	/**
+		Create a new lock with no pending work.
+	**/
 	public function new() {
 		p = Promise.resolve(null);
 	}
 
+	/**
+		Run one async operation at a time in call order.
+
+		@param fn operation to enqueue
+		@returns Promise resolving or rejecting with the result of `fn`
+	**/
 	public function run<T>(fn: () -> Promise<T>): Promise<T> {
 		final next = p.then(_ -> fn());
 		p = next.then(_->{}, _->{}); // prevent chain break
diff --git a/borogove/AttachmentSource.cpp.hx b/borogove/AttachmentSource.cpp.hx
index 4f4e5c3..3906d76 100644
--- a/borogove/AttachmentSource.cpp.hx
+++ b/borogove/AttachmentSource.cpp.hx
@@ -15,6 +15,12 @@ class AttachmentSource {
 	public final name: String;
 	public final size: Int;
 
+	/**
+		Create an attachment source from a local file path and MIME type.
+
+		@param path path to the local file
+		@param mime MIME type to advertise for the upload
+	**/
 	public function new(path: String, mime: String) {
 		this.name = haxe.io.Path.withoutDirectory(path);
 		this.path = sys.FileSystem.fullPath(path);
diff --git a/borogove/AvailableChatIterator.hx b/borogove/AvailableChatIterator.hx
index 2b97757..9860e93 100644
--- a/borogove/AvailableChatIterator.hx
+++ b/borogove/AvailableChatIterator.hx
@@ -165,12 +165,22 @@ class AvailableChatIterator {
 		return this;
 	}
 
+	/**
+		Get the next AvailableChat from this iterator for JavaScript async iteration.
+
+		@returns Promise resolving to the next iterator result
+	**/
 	public function next(): Promise<{ done: Bool, ?value: AvailableChat }> {
 		return internalNext().then(v -> {
 			return { done: v == null, value: v };
 		});
 	}
 	#else
+	/**
+		Get the next AvailableChat from this iterator.
+
+		@returns Promise resolving to the next result, or null when exhausted
+	**/
 	public function next(): Promise<Null<AvailableChat>> {
 		return internalNext();
 	}
diff --git a/borogove/Caps.hx b/borogove/Caps.hx
index eb055f8..8da2acd 100644
--- a/borogove/Caps.hx
+++ b/borogove/Caps.hx
@@ -50,6 +50,15 @@ class Caps {
 		return result;
 	}
 
+	/**
+		Create a capabilities description.
+
+		@param node capability node identifier
+		@param identities disco identities advertised by the entity
+		@param features disco feature namespaces advertised by the entity
+		@param data extended disco data forms
+		@param ver optional precomputed capability hash bytes
+	**/
 	public function new(node: String, identities: Array<Identity>, features: Array<String>, data: Array<DataForm>, ?ver: BytesData) {
 		if (ver == null) {
 			// If we won't need to generate ver we don't actually need to sort
@@ -67,11 +76,20 @@ class Caps {
 		}
 	}
 
+	/**
+		Check whether these capabilities describe a channel-like chat target.
+
+		@param chatId ID to evaluate against the capability set
+		@returns true when the target looks like a MUC/channel
+	**/
 	public function isChannel(chatId: String) {
 		if (chatId.indexOf("@") < 0) return false; // MUC must have a localpart
 		return features.contains("http://jabber.org/protocol/muc") && identities.find((identity) -> identity.category == "conference") != null;
 	}
 
+	/**
+		Build a disco#info query payload for this capability set.
+	**/
 	public function discoReply():Stanza {
 		final query = new Stanza("query", { xmlns: "http://jabber.org/protocol/disco#info" });
 		for (identity in identities) {
@@ -84,6 +102,12 @@ class Caps {
 		return query;
 	}
 
+	/**
+		Add capability advertisements to a stanza.
+
+		@param stanza stanza to mutate
+		@returns the same stanza for chaining
+	**/
 	public function addC(stanza: Stanza): Stanza {
 		stanza.tag("c", {
 			xmlns: "http://jabber.org/protocol/caps",
@@ -160,11 +184,17 @@ class Caps {
 		return Hash.sha1(bytesOfString(s));
 	}
 
+	/**
+		Get the raw XEP-0115 capability hash object for this capability set.
+	**/
 	public function verRaw(): Hash {
 		if (_ver == null) _ver = computeVer();
 		return _ver;
 	}
 
+	/**
+		Get the XEP-0115 capability hash encoded in base64.
+	**/
 	public function ver(): String {
 		return verRaw().toBase64();
 	}
@@ -177,6 +207,9 @@ class Identity {
 	public final name:String;
 	public final lang:String;
 
+	/**
+		Create a disco identity.
+	**/
 	public function new(category:String, type: String, name: String, lang: Null<String> = null) {
 		this.category = category;
 		this.type = type;
@@ -184,16 +217,25 @@ class Identity {
 		this.lang = lang ?? "";
 	}
 
+	/**
+		Add this identity to a disco#info payload.
+	**/
 	public function addToDisco(stanza: Stanza) {
 		var attrs: haxe.DynamicAccess<String> = { category: category, type: type, name: name };
 		if (lang != null && lang != "") attrs.set("xml:lang", lang);
 		stanza.tag("identity", attrs).up();
 	}
 
+	/**
+		Get the identity string used when computing capability hashes.
+	**/
 	public function ver(): String {
 		return category + "/" + type + "/" + (lang ?? "") + "/" + name;
 	}
 
+	/**
+		Write the identity in canonical capability-hash form.
+	**/
 	public function writeTo(out: haxe.io.Output) {
 		out.writeS(category);
 		out.writeByte(0x1f);
diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index 3bff4c3..1a6ac4c 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -35,6 +35,9 @@ typedef StringMapNullableKey = Map<Null<String>, String>;
 typedef StringMapNullableKey = haxe.ds.ObjectMap<Null<String>, String>;
 #end
 
+/**
+	Persistent UI state for a chat in the local client.
+**/
 enum abstract UiState(Int) {
 	var Pinned;
 	var Open; // or Unspecified
@@ -42,6 +45,9 @@ enum abstract UiState(Int) {
 	var Invited;
 }
 
+/**
+	Chat state notifications received from another participant.
+**/
 enum abstract UserState(Int) {
 	var Gone;
 	var Inactive;
@@ -50,11 +56,14 @@ enum abstract UserState(Int) {
 	var Paused;
 }
 
-// Describes the current encryption mode of the conversation
-// This mode is a high-level representation of the user/app *intent*
-// for the current conversation - e.g. not a guarantee that incoming
-// messages will always match this expectation. It is used to determine
-// the logic for outgoing messages, though.
+/**
+	Describes the current encryption mode of the conversation.
+
+	This mode is a high-level representation of the user/app *intent*
+	for the current conversation - e.g. not a guarantee that incoming
+	messages will always match this expectation. It is used to determine
+	the logic for outgoing messages, though.
+**/
 enum abstract EncryptionMode(Int) {
 	var Unencrypted; // No end-to-end encryption
 	var EncryptedOMEMO; // Use OMEMO
@@ -750,6 +759,11 @@ abstract class Chat {
 		return session;
 	}
 
+	/**
+		Add additional media streams to the active call in this chat.
+
+		@param streams media streams to add to the current session
+	**/
 	@HaxeCBridge.noemit
 	public function addMedia(streams: Array<MediaStream>) {
 		if (callStatus() != Ongoing) throw "cannot add media when no call ongoing";
@@ -890,6 +904,9 @@ abstract class Chat {
 		return commandJids().length > 0;
 	}
 
+	/**
+		List commands exposed by this chat
+	**/
 	public function commands(): Promise<Array<Command>> {
 		return thenshim.PromiseTools.all(commandJids().map(jid -> new Promise((resolve, reject) -> {
 			final itemsGet = new DiscoItemsGet(jid.asString(), "http://jabber.org/protocol/commands");
@@ -1669,6 +1686,9 @@ class Channel extends Chat {
 		return uiState != Closed && uiState != Invited;
 	}
 
+	/**
+		Whether this channel is members-only/private.
+	**/
 	public function isPrivate() {
 		return disco.features.contains("muc_membersonly");
 	}
@@ -2237,6 +2257,9 @@ class SerializedChat {
 	public final notifyMention: Bool;
 	public final notifyReply: Bool;
 
+	/**
+		Create a serialized chat snapshot suitable for persistence.
+	**/
 	public function new(chatId: String, trusted: Bool, isBookmarked: Bool, avatarSha1: Null<BytesData>, presence: Map<String, Presence>, displayName: Null<String>, uiState: Null<UiState>, isBlocked: Null<Bool>, status: Status, extensions: Null<String>, readUpToId: Null<String>, readUpToBy: Null<String>, notificationsFiltered: Null<Bool>, notifyMention: Bool, notifyReply: Bool, threads: StringMapNullableKey, disco: Null<Caps>, omemoContactDeviceIDs: Array<Int>, klass: String) {
 		this.chatId = chatId;
 		this.trusted = trusted;
@@ -2259,6 +2282,9 @@ class SerializedChat {
 		this.klass = klass;
 	}
 
+	/**
+		Recreate a live Chat object from this serialized representation.
+	**/
 	public function toChat(client: Client, stream: GenericStream, persistence: Persistence) {
 		final extensionsStanza = Stanza.parse(extensions);
 		var filterN = notificationsFiltered ?? false;
diff --git a/borogove/Client.hx b/borogove/Client.hx
index 18264d0..77723e1 100644
--- a/borogove/Client.hx
+++ b/borogove/Client.hx
@@ -44,6 +44,9 @@ using StringTools;
 import HaxeCBridge;
 #end
 
+/**
+	Reason a chat-message listener was triggered.
+**/
 enum abstract ChatMessageEvent(Int) {
 	var DeliveryEvent;
 	var CorrectionEvent;
@@ -859,6 +862,13 @@ class Client extends EventEmitter {
 		);
 	}
 
+	/**
+		Publish an account status/activity item.
+
+		@param status status payload to publish
+		@param expires expiration in seconds for the published item
+		@param publicAccess when true, make the item world-readable
+	**/
 	public function setStatus(status: Status, expires: Int = 86400, publicAccess: Bool = false) {
 		publishWithOptions(
 			new Stanza("iq", { type: "set" })
diff --git a/borogove/DataForm.hx b/borogove/DataForm.hx
index dac063e..9f9872f 100644
--- a/borogove/DataForm.hx
+++ b/borogove/DataForm.hx
@@ -34,6 +34,9 @@ abstract DataForm(Stanza) from Stanza to Stanza {
 		return this.allTags("item")?.map(row -> row.allTags("field"));
 	}
 
+	/**
+		Get the field with the given name, if present.
+	**/
 	public function field(name: String): Null<Field> {
 		final matches = fields.filter(f -> f.name == name);
 		if (matches.length > 1) {
diff --git a/borogove/EncryptionInfo.hx b/borogove/EncryptionInfo.hx
index 7fd1691..358eaa7 100644
--- a/borogove/EncryptionInfo.hx
+++ b/borogove/EncryptionInfo.hx
@@ -4,6 +4,9 @@ package borogove;
 import HaxeCBridge;
 #end
 
+/**
+	Outcome of decrypting an incoming message.
+**/
 enum abstract EncryptionStatus(Int) {
 	var DecryptionSuccess; // Message was encrypted, and we decrypted it
 	var DecryptionFailure; // Message is encrypted, and we failed to decrypt it
diff --git a/borogove/Form.hx b/borogove/Form.hx
index 82d02b3..2221c0a 100644
--- a/borogove/Form.hx
+++ b/borogove/Form.hx
@@ -272,10 +272,16 @@ class FormLayoutSection implements FormSection {
 		this.section = section;
 	}
 
+	/**
+		Get the layout section label, if any.
+	**/
 	public function title() {
 		return section.attr.get("label");
 	}
 
+	/**
+		Get the renderable items contained in this layout section.
+	**/
 	public function items() {
 		final items = [];
 		for (child in section.allTags()) {
diff --git a/borogove/Identicon.hx b/borogove/Identicon.hx
index d9e9615..db56328 100644
--- a/borogove/Identicon.hx
+++ b/borogove/Identicon.hx
@@ -16,6 +16,9 @@ import HaxeCBridge;
 @:build(HaxeSwiftBridge.expose())
 #end
 class Identicon {
+	/**
+		Generate a deterministic SVG identicon as a data URI.
+	**/
 	public static function svg(source: String) {
 		final sha = Sha1.make(bytesOfString(source));
 		final input = new BytesInput(sha);
diff --git a/borogove/JID.hx b/borogove/JID.hx
index 54f32c3..c583769 100644
--- a/borogove/JID.hx
+++ b/borogove/JID.hx
@@ -6,6 +6,14 @@ class JID {
 	public final domain : String;
 	public final resource : Null<String>;
 
+	/**
+		Create a JID from its parts.
+
+		@param node localpart, or null for a domain JID
+		@param domain domainpart
+		@param resource resourcepart, if any
+		@param raw when false, escape the node according to JID escaping rules
+	**/
 	public function new(?node:String, domain:String, ?resource:String, ?raw = false) {
 		this.node = node == null || raw == true ? node :
 			StringTools.replace(StringTools.replace(StringTools.replace(
@@ -38,6 +46,9 @@ class JID {
 		this.resource = resource;
 	}
 
+	/**
+		Parse a JID string into its components.
+	**/
 	public static function parse(jid:String):JID {
 		var resourceDelimiter = jid.indexOf("/");
 		var nodeDelimiter = jid.indexOf("@");
@@ -52,26 +63,44 @@ class JID {
 		);
 	}
 
+	/**
+		Get the bare JID without any resource.
+	**/
 	public function asBare():JID {
 		return new JID(this.node, this.domain, null, true);
 	}
 
+	/**
+		Return a copy of this JID with a different resource.
+	**/
 	public function withResource(resource: String): JID {
 		return new JID(this.node, this.domain, resource, true);
 	}
 
+	/**
+		Check whether this JID looks valid enough to use.
+	**/
 	public function isValid():Bool {
 		return domain.indexOf(".") >= 0;
 	}
 
+	/**
+		Check whether this JID has no localpart.
+	**/
 	public function isDomain():Bool {
 		return node == null;
 	}
 
+	/**
+		Check whether this JID has no resourcepart.
+	**/
 	public function isBare():Bool {
 		return resource == null;
 	}
 
+	/**
+		Compare two JIDs for exact equality.
+	**/
 	public function equals(rhs:JID):Bool {
 		return (
 			this.node == rhs.node &&
@@ -80,6 +109,9 @@ class JID {
 		);
 	}
 
+	/**
+		Render this JID back to its string form.
+	**/
 	public function asString():String {
 		return (
 			(this.node != null ? this.node + "@" : "") +
diff --git a/borogove/Message.hx b/borogove/Message.hx
index b992114..29f59ea 100644
--- a/borogove/Message.hx
+++ b/borogove/Message.hx
@@ -5,11 +5,17 @@ import borogove.Reaction;
 using Lambda;
 using StringTools;
 
+/**
+	Direction of a chat message relative to the local account.
+**/
 enum abstract MessageDirection(Int) {
 	var MessageReceived;
 	var MessageSent;
 }
 
+/**
+	Delivery state for an outgoing or incoming message.
+**/
 enum abstract MessageStatus(Int) {
 	var MessagePending; // Message is waiting in client for sending
 	var MessageDeliveredToServer; // Server acknowledged receipt of the message
@@ -17,6 +23,9 @@ enum abstract MessageStatus(Int) {
 	var MessageFailedToSend; // There was an error sending this message
 }
 
+/**
+	High-level category of the message
+**/
 enum abstract MessageType(Int) {
 	var MessageChat;
 	var MessageCall;
diff --git a/borogove/Presence.hx b/borogove/Presence.hx
index a537e20..9ab8b52 100644
--- a/borogove/Presence.hx
+++ b/borogove/Presence.hx
@@ -14,6 +14,9 @@ abstract Presence(Stanza) from Stanza to Stanza {
 	public var hats(get, never): Null<Array<Role>>;
 	public var avatarHash(get, never): Null<Hash>;
 
+	/**
+		Create a presence stanza wrapper from caps, MUC metadata, and avatar hash.
+	**/
 	public function new(caps: Null<Caps>, mucUser: Null<MucUser>, avatarHash: Null<Hash>): Presence {
 		final stanza = new Stanza("presence", { xmlns: "jabber:client" });
 		if (caps != null) caps.addC(stanza);
diff --git a/borogove/Profile.hx b/borogove/Profile.hx
index 8ab1724..4b68ed5 100644
--- a/borogove/Profile.hx
+++ b/borogove/Profile.hx
@@ -53,41 +53,68 @@ class ProfileItem {
 		this.key = item.name;
 	}
 
+	/**
+		Get parameter items attached to this profile item.
+	**/
 	public function parameters(): Array<ProfileItem> {
 		final params = item.getChild("parameters")?.allTags() ?? [];
 		return params.map(param -> new ProfileItem(param, id + "/" + ID.unique()));
 	}
 
+	/**
+		Get text values for this profile item.
+	**/
 	public function text(): Array<String> {
 		return item.allTags("text").map(s -> s.getText());
 	}
 
+	/**
+		Get URI values for this profile item.
+	**/
 	public function uri(): Array<String> {
 		return item.allTags("uri").map(s -> s.getText());
 	}
 
+	/**
+		Get date values for this profile item.
+	**/
 	public function date(): Array<String> {
 		return item.allTags("date").map(s -> s.getText());
 	}
 
+	/**
+		Get time values for this profile item.
+	**/
 	public function time(): Array<String> {
 		return item.allTags("time").map(s -> s.getText());
 	}
 
+	/**
+		Get datetime values for this profile item.
+	**/
 	public function datetime(): Array<String> {
 		return item.allTags("datetime").map(s -> s.getText());
 	}
 
+	/**
+		Get boolean values for this profile item.
+	**/
 	@HaxeCBridge.noemit
 	public function boolean(): Array<Bool> {
 		return item.allTags("boolean").map(s -> s.getText() == "true");
 	}
 
+	/**
+		Get integer values for this profile item.
+	**/
 	@HaxeCBridge.noemit
 	public function integer(): Array<Int> {
 		return item.allTags("integer").map(s -> Std.parseInt(s.getText()) ?? 0);
 	}
 
+	/**
+		Get language-tag values for this profile item.
+	**/
 	public function languageTag(): Array<String> {
 		return item.allTags("language-tag").map(s -> s.getText());
 	}
@@ -138,6 +165,9 @@ class ProfileBuilder {
 	private final vcard: Stanza;
 	private var items: Array<ProfileItem> = [];
 
+	/**
+		Create a mutable builder from an existing profile.
+	**/
 	public function new(profile: Profile) {
 		vcard = profile.vcard.clone();
 		final els = vcard.allTags().filter(el ->
@@ -210,6 +240,9 @@ class ProfileBuilder {
 		vcard.removeChild(prop.item);
 	}
 
+	/**
+		Build an immutable Profile from the current builder state.
+	**/
 	public function build() {
 		return new Profile(vcard.clone(), items.array());
 	}
diff --git a/borogove/ReactionUpdate.hx b/borogove/ReactionUpdate.hx
index 292b57b..1b460be 100644
--- a/borogove/ReactionUpdate.hx
+++ b/borogove/ReactionUpdate.hx
@@ -3,6 +3,9 @@ package borogove;
 import borogove.Reaction;
 using Lambda;
 
+/**
+	How a reaction update should be applied to the existing reaction set.
+**/
 enum abstract ReactionUpdateKind(Int) {
 	var EmojiReactions;
 	var AppendReactions;
@@ -22,6 +25,9 @@ class ReactionUpdate {
 	public final reactions: Array<Reaction>;
 	public final kind: ReactionUpdateKind;
 
+	/**
+		Create a reaction update for one message.
+	**/
 	public function new(updateId: String, serverId: Null<String>, serverIdBy: Null<String>, localId: Null<String>, chatId: String, senderId: String, timestamp: String, reactions: Array<Reaction>, kind: ReactionUpdateKind) {
 		if (serverId == null && localId == null) throw "ReactionUpdate serverId and localId cannot both be null";
 		if (serverId != null && serverIdBy == null) throw "serverId requires serverIdBy";
@@ -36,6 +42,12 @@ class ReactionUpdate {
 		this.kind = kind;
 	}
 
+	/**
+		Apply this update to an existing reaction list.
+
+		@param existingReactions reactions already known for the message
+		@returns the updated reaction list
+	**/
 	public function getReactions(existingReactions: Null<Array<Reaction>>): Array<Reaction> {
 		if (kind == AppendReactions) { // TODO: make sure a new non-custom react doesn't override any customs we've added
 			final set: Map<String, Bool> = [];
diff --git a/borogove/Status.hx b/borogove/Status.hx
index 15a956c..2333448 100644
--- a/borogove/Status.hx
+++ b/borogove/Status.hx
@@ -14,11 +14,17 @@ class Status {
 	public final emoji: String;
 	public final text: String;
 
+	/**
+		Create a status value with emoji and text.
+	**/
 	public function new(emoji: String, text: String) {
 		this.emoji = emoji;
 		this.text = text;
 	}
 
+	/**
+		Render this status as plain text.
+	**/
 	public function toString() {
 		return emoji + (emoji == "" || text == "" ? "" : " ") + text;
 	}