git » sdk » commit 3b3e93d

UUIDv7 for all IDs

author Stephen Paul Weber
2026-03-16 04:33:14 UTC
committer Stephen Paul Weber
2026-03-16 13:49:15 UTC
parent 3abba97af50047a93ab7bde3354b1d578d8df983

UUIDv7 for all IDs

Not always needed but never hurts

Makefile +2 -1
borogove/Chat.hx +18 -18
borogove/ChatMessage.hx +1 -1
borogove/Client.hx +6 -7
borogove/GenericStream.hx +1 -2
borogove/ID.hx +3 -15
borogove/Message.hx +3 -3
borogove/Profile.hx +3 -3
borogove/calls/PeerConnection.cpp.hx +1 -1
borogove/calls/Session.hx +7 -7
borogove/queries/BlocklistGet.hx +1 -1
borogove/queries/BoB.hx +1 -1
borogove/queries/CommandExecute.hx +1 -1
borogove/queries/DiscoInfoGet.hx +1 -1
borogove/queries/DiscoItemsGet.hx +1 -1
borogove/queries/ExtDiscoGet.hx +1 -1
borogove/queries/HttpUploadSlot.hx +1 -1
borogove/queries/JabberIqGatewayGet.hx +1 -1
borogove/queries/MAMQuery.hx +1 -1
borogove/queries/PubsubGet.hx +1 -1
borogove/queries/Push2Disable.hx +1 -1
borogove/queries/Push2Enable.hx +1 -1
borogove/queries/RosterGet.hx +1 -1
borogove/queries/VcardTempGet.hx +1 -1
borogove/streams/TestStream.hx +0 -4
borogove/streams/XmppJsStream.hx +0 -10
borogove/streams/XmppStropheStream.hx +0 -4
browserjs.hxml +1 -1
cpp.hxml +1 -1
nodejs.hxml +1 -1
test.hxml +1 -1

diff --git a/Makefile b/Makefile
index 3b11168..749a557 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,7 @@ hx-build-dep:
 	haxelib --quiet install haxe-strings
 	haxelib --quiet install hsluv
 	haxelib --quiet install tink_http
-	haxelib --quiet install sha
+	haxelib --quiet install uuidv7
 	haxelib --quiet install thenshim
 	haxelib --quiet install HtmlParser
 	haxelib --quiet install hxnodejs
@@ -28,6 +28,7 @@ npm/borogove-browser.js:
 	sed -i 's/^$$hx_exports[^=]*=\(.*\);$$/export {\1 };/g' npm/borogove-browser.js
 	sed -i 's/"\[Symbol.asyncIterator\]"() {/[Symbol.asyncIterator]() {/g' npm/borogove-browser.js
 	cd npm && npx cjstoesm borogove-browser.js
+	sed -i 's/import crypto from "crypto";//g' npm/borogove-browser.js
 	awk -f optional-sqlite.awk npm/borogove-browser.js
 	mv npm/browser-no-sqlite.js npm/borogove-browser.js
 	awk -f optional-sqlite-types.awk npm/borogove-browser.d.ts
diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index 3c67453..0944090 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -247,7 +247,7 @@ abstract class Chat {
 	**/
 	public function addReaction(m:ChatMessage, reaction:Reaction) {
 		final toSend = m.reply();
-		toSend.localId = ID.long();
+		toSend.localId = ID.unique();
 		reaction.render(
 			(text) -> {
 				toSend.text = text.replace("\u{fe0f}", "");
@@ -362,7 +362,7 @@ abstract class Chat {
 				final inviteFromBareChat = client.getChat(inviteFrom.asBare().asString());
 				final toBlock = inviteFromBareChat != null && Std.isOfType(inviteFromBareChat, Channel) ? inviteFrom.asString() : inviteFrom.asBare().asString();
 
-				final iq = new Stanza("iq", { type: "set", id: ID.short() })
+				final iq = new Stanza("iq", { type: "set", id: ID.unique() })
 					.tag("block", { xmlns: "urn:xmpp:blocking" })
 					.tag("item", { jid: toBlock });
 				if (reportSpam) {
@@ -384,7 +384,7 @@ abstract class Chat {
 			close(); // close persists
 		}
 		if (onServer) {
-			final iq = new Stanza("iq", { type: "set", id: ID.short() })
+			final iq = new Stanza("iq", { type: "set", id: ID.unique() })
 				.tag("block", { xmlns: "urn:xmpp:blocking" })
 				.tag("item", { jid: chatId });
 			if (reportSpam) {
@@ -413,7 +413,7 @@ abstract class Chat {
 		client.trigger("chats/update", [this]);
 		if (onServer) {
 			stream.sendIq(
-				new Stanza("iq", { type: "set", id: ID.short() })
+				new Stanza("iq", { type: "set", id: ID.unique() })
 					.tag("unblock", { xmlns: "urn:xmpp:blocking" })
 					.tag("item", { jid: chatId }).up().up(),
 				(response) -> {}
@@ -1020,7 +1020,7 @@ class DirectChat extends Chat {
 		message.replyTo = [message.sender];
 		message.recipients = counterparts().map((p) -> JID.parse(p));
 		message.to = message.recipients[0];
-		if (message.localId == null) message.localId = ID.long();
+		if (message.localId == null) message.localId = ID.unique();
 		return message;
 	}
 
@@ -1085,7 +1085,7 @@ class DirectChat extends Chat {
 		if (Std.isOfType(reaction, CustomEmojiReaction)) {
 			if (reaction.envelopeId == null) throw "Cannot remove custom emoji reaction without envelopeId";
 			final correct = m.reply();
-			correct.localId = ID.long();
+			correct.localId = ID.unique();
 			correct.setHtml("");
 			correct.text = null;
 
@@ -1106,7 +1106,7 @@ class DirectChat extends Chat {
 				}
 			}
 		}
-		final update = new ReactionUpdate(ID.long(), null, null, m.localId, m.chatId(), client.accountId(), Date.format(std.Date.now()), reactions, EmojiReactions);
+		final update = new ReactionUpdate(ID.unique(), null, null, m.localId, m.chatId(), client.accountId(), Date.format(std.Date.now()), reactions, EmojiReactions);
 		final outboxItem = outbox.newItem();
 		persistence.storeReaction(client.accountId(), update).then((stored) -> {
 			sendMessageStanza(update.asStanza(), outboxItem);
@@ -1160,7 +1160,7 @@ class DirectChat extends Chat {
 			if (message.isIncoming() && message.localId != null) {
 				for (recipient in counterparts()) {
 					// TODO: extended addressing when relevant
-					final stanza = new Stanza("message", { to: recipient, id: ID.long() })
+					final stanza = new Stanza("message", { to: recipient, id: ID.unique() })
 						.tag("displayed", { xmlns: "urn:xmpp:chat-markers:0", id: message.localId }).up();
 					if (message.threadId != null) {
 						stanza.textTag("thread", message.threadId);
@@ -1193,8 +1193,8 @@ class DirectChat extends Chat {
 				.up().up(),
 			(response) -> {
 				if (response.attr.get("type") == "error") return;
-				stream.sendStanza(new Stanza("presence", { to: chatId, type: "subscribe", id: ID.short() }));
-				if (isTrusted()) stream.sendStanza(new Stanza("presence", { to: chatId, type: "subscribed", id: ID.short() }));
+				stream.sendStanza(new Stanza("presence", { to: chatId, type: "subscribe", id: ID.unique() }));
+				if (isTrusted()) stream.sendStanza(new Stanza("presence", { to: chatId, type: "subscribed", id: ID.unique() }));
 			}
 		);
 	}
@@ -1204,7 +1204,7 @@ class DirectChat extends Chat {
 
 		for (recipient in counterparts()) {
 			final stanza = new Stanza("message", {
-					id: ID.long(),
+					id: ID.unique(),
 					type: "chat",
 					from: client.jid.asString(),
 					to: recipient
@@ -1222,7 +1222,7 @@ class DirectChat extends Chat {
 	public function close() {
 		if (typingTimer != null) typingTimer.stop();
 		if (uiState == Invited) {
-			client.sendStanza(new Stanza("presence", { to: chatId, type: "unsubscribed", id: ID.short() }));
+			client.sendStanza(new Stanza("presence", { to: chatId, type: "unsubscribed", id: ID.unique() }));
 		}
 		// Should this remove from roster? Or set untrusted?
 		uiState = Closed;
@@ -1710,7 +1710,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 		message.replyTo = [message.sender];
 		message.to = JID.parse(chatId);
 		message.recipients = [message.to];
-		if (message.localId == null) message.localId = ID.long();
+		if (message.localId == null) message.localId = ID.unique();
 		return message;
 	}
 
@@ -1774,7 +1774,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 		if (Std.isOfType(reaction, CustomEmojiReaction)) {
 			if (reaction.envelopeId == null) throw "Cannot remove custom emoji reaction without envelopeId";
 			final correct = m.reply();
-			correct.localId = ID.long();
+			correct.localId = ID.unique();
 			correct.setHtml("");
 			correct.text = null;
 
@@ -1794,7 +1794,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 				if (react != null && !Std.isOfType(react, CustomEmojiReaction)) reactions.push(react);
 			}
 		}
-		final update = new ReactionUpdate(ID.long(), m.serverId, m.chatId(), null, m.chatId(), getFullJid().asString(), Date.format(std.Date.now()), reactions, EmojiReactions);
+		final update = new ReactionUpdate(ID.unique(), m.serverId, m.chatId(), null, m.chatId(), getFullJid().asString(), Date.format(std.Date.now()), reactions, EmojiReactions);
 		final outboxItem = outbox.newItem();
 		persistence.storeReaction(client.accountId(), update).then((stored) -> {
 			sendMessageStanza(update.asStanza(), outboxItem);
@@ -1822,7 +1822,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 			// Only send markers for others messages,
 			// it's obvious we've read our own
 			if (message.isIncoming() && message.serverId != null) {
-				final stanza = new Stanza("message", { to: chatId, id: ID.long(), type: "groupchat" })
+				final stanza = new Stanza("message", { to: chatId, id: ID.unique(), type: "groupchat" })
 					.tag("displayed", { xmlns: "urn:xmpp:chat-markers:0", id: message.serverId }).up();
 				if (message.threadId != null) {
 					stanza.textTag("thread", message.threadId);
@@ -1890,7 +1890,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 
 	private function sendChatState(state: String, threadId: Null<String>) {
 		final stanza = new Stanza("message", {
-				id: ID.long(),
+				id: ID.unique(),
 				type: "groupchat",
 				from: client.jid.asString(),
 				to: chatId
@@ -1909,7 +1909,7 @@ trace("XYZZY no MUC avatar locally matching so fetch vcard", chatId, avatarSha1H
 		if (uiState == Invited) {
 			for (invite in invites()) {
 				client.sendStanza(
-					new Stanza("message", { id: ID.long(), to: chatId })
+					new Stanza("message", { id: ID.unique(), to: chatId })
 						.tag("x", { xmlns: "http://jabber.org/protocol/muc#user" })
 						.tag("decline", { to: invite.attr.get("from") })
 						.up().up()
diff --git a/borogove/ChatMessage.hx b/borogove/ChatMessage.hx
index 67eff35..f827ace 100644
--- a/borogove/ChatMessage.hx
+++ b/borogove/ChatMessage.hx
@@ -318,7 +318,7 @@ class ChatMessage {
 	public function reply() {
 		final m = new ChatMessageBuilder();
 		m.type = type;
-		m.threadId = threadId ?? ID.long();
+		m.threadId = threadId ?? ID.unique();
 		m.replyToMessage = this;
 		return m;
 	}
diff --git a/borogove/Client.hx b/borogove/Client.hx
index 8e9f193..2e50b6f 100644
--- a/borogove/Client.hx
+++ b/borogove/Client.hx
@@ -1,8 +1,7 @@
 package borogove;
 
-import sha.SHA256;
-
 import haxe.crypto.Base64;
+import haxe.crypto.Sha256;
 import haxe.io.Bytes;
 import haxe.io.BytesData;
 import thenshim.Promise;
@@ -708,7 +707,7 @@ class Client extends EventEmitter {
 		return persistence.getLogin(accountId()).then(login -> {
 			token = login.token;
 			fastCount = login.fastCount;
-			stream.clientId = login.clientId ?? ID.long();
+			stream.clientId = login.clientId ?? ID.unique();
 			jid = jid.withResource(stream.clientId);
 			if (!updateDisplayName(login.displayName) && login.clientId == null) {
 				persistence.storeLogin(jid.asBare().asString(), stream.clientId, this.displayName(), null);
@@ -805,7 +804,7 @@ class Client extends EventEmitter {
 			new Stanza("iq", { type: "set" })
 				.tag("pubsub", { xmlns: "http://jabber.org/protocol/pubsub" })
 				.tag("publish", { node: "urn:xmpp:vcard4" })
-				.tag("item", { id: ID.long() })
+				.tag("item", { id: ID.unique() })
 				.addChild(profile.buildStanza()),
 			new Stanza("x", { xmlns: "jabber:x:data", type: "submit" })
 				.tag("field", { "var": "FORM_TYPE", type: "hidden" }).textTag("value", "http://jabber.org/protocol/pubsub#publish-options").up()
@@ -882,7 +881,7 @@ class Client extends EventEmitter {
 					if (sendAvailable) {
 						// Enable carbons
 						sendStanza(
-							new Stanza("iq", { type: "set", id: ID.short() })
+							new Stanza("iq", { type: "set", id: ID.unique() })
 								.tag("enable", { xmlns: "urn:xmpp:carbons:2" })
 								.up()
 						);
@@ -908,7 +907,7 @@ class Client extends EventEmitter {
 	**/
 	public function prepareAttachment(source: AttachmentSource): Promise<Null<ChatAttachment>> {
 		return persistence.findServicesWithFeature(accountId(), "urn:xmpp:http:upload:0").then((services) -> {
-			final sha256 = new sha.SHA256();
+			final sha256 = new Sha256();
 			return new Promise((resolve, reject) -> {
 				source.tinkSource().chunked().forEach((chunk) -> {
 					sha256.update(chunk);
@@ -1492,7 +1491,7 @@ class Client extends EventEmitter {
 
 	@:allow(borogove)
 	private function sendStanza(stanza:Stanza) {
-		if (stanza.attr.get("id") == null) stanza.attr.set("id", ID.long());
+		if (stanza.attr.get("id") == null) stanza.attr.set("id", ID.unique());
 		stream.sendStanza(stanza);
 	}
 
diff --git a/borogove/GenericStream.hx b/borogove/GenericStream.hx
index 4404049..54203ea 100644
--- a/borogove/GenericStream.hx
+++ b/borogove/GenericStream.hx
@@ -26,11 +26,10 @@ abstract class GenericStream extends EventEmitter {
 	abstract public function connect(jid:String, sm:Null<BytesData>):Void;
 	abstract public function disconnect():Void;
 	abstract public function sendStanza(stanza:Stanza):Void;
-	abstract public function newId():String;
 	abstract public function onIq(type:IqRequestType, tag:String, xmlns:String, handler:(Stanza)->IqResult):Void;
 
 	public function sendIq(stanza:Stanza, callback:(stanza:Stanza)->Void):Void {
-		var id = newId();
+		var id = ID.unique();
 		stanza.attr.set("id", id);
 		this.once('iq-response/$id', function (event) {
 			callback(event.stanza);
diff --git a/borogove/ID.hx b/borogove/ID.hx
index 5f14454..3c80ee8 100644
--- a/borogove/ID.hx
+++ b/borogove/ID.hx
@@ -1,21 +1,9 @@
 package borogove;
 
-import hx.strings.RandomStrings;
+import UUIDv7;
 
 class ID {
-	public static function tiny():String {
-		return RandomStrings.randomAsciiAlphaNumeric(6);
-	}
-
-	public static function short():String {
-		return RandomStrings.randomAsciiAlphaNumeric(18);
-	}
-
-	public static function medium():String {
-		return RandomStrings.randomAsciiAlphaNumeric(32);
-	}
-
-	public static function long():String {
-		return RandomStrings.randomUUIDv4();
+	public static inline function unique():String {
+		return uuidv7();
 	}
 }
diff --git a/borogove/Message.hx b/borogove/Message.hx
index 5374456..197d5ff 100644
--- a/borogove/Message.hx
+++ b/borogove/Message.hx
@@ -200,7 +200,7 @@ class Message {
 			final reactionId = reactionsEl.attr.get("id");
 			if (reactionId != null) {
 				return new Message(msg.chatId(), msg.senderId, msg.threadId, ReactionUpdateStanza(new ReactionUpdate(
-					stanza.attr.get("id") ?? ID.long(),
+					stanza.attr.get("id") ?? ID.unique(),
 					isGroupchat ? reactionId : null,
 					isGroupchat ? msg.chatId() : null,
 					isGroupchat ? null : reactionId,
@@ -280,7 +280,7 @@ class Message {
 			final text = msg.text;
 			if (text != null && EmojiUtil.isOnlyEmoji(text.trim())) {
 				return new Message(msg.chatId(), msg.senderId, msg.threadId, ReactionUpdateStanza(new ReactionUpdate(
-					stanza.attr.get("id") ?? ID.long(),
+					stanza.attr.get("id") ?? ID.unique(),
 					isGroupchat ? replyToID : null,
 					isGroupchat ? msg.chatId() : null,
 					isGroupchat ? null : replyToID,
@@ -300,7 +300,7 @@ class Message {
 						final hash = Hash.fromUri(els[0].attr.get("src") ?? "");
 						if (hash != null) {
 							return new Message(msg.chatId(), msg.senderId, msg.threadId, ReactionUpdateStanza(new ReactionUpdate(
-								stanza.attr.get("id") ?? ID.long(),
+								stanza.attr.get("id") ?? ID.unique(),
 								isGroupchat ? replyToID : null,
 								isGroupchat ? msg.chatId() : null,
 								isGroupchat ? null : replyToID,
diff --git a/borogove/Profile.hx b/borogove/Profile.hx
index bc4d65d..8ab1724 100644
--- a/borogove/Profile.hx
+++ b/borogove/Profile.hx
@@ -29,7 +29,7 @@ class Profile {
 		this.vcard = vcard;
 		this.items = items != null ? items : vcard.allTags().filter(el ->
 			TYPES[el.name] != null // remove unknown or compound property
-		).map(child -> new ProfileItem(child, child.name + "/" + ID.short()));
+		).map(child -> new ProfileItem(child, child.name + "/" + ID.unique()));
 	}
 }
 
@@ -55,7 +55,7 @@ class ProfileItem {
 
 	public function parameters(): Array<ProfileItem> {
 		final params = item.getChild("parameters")?.allTags() ?? [];
-		return params.map(param -> new ProfileItem(param, id + "/" + ID.short()));
+		return params.map(param -> new ProfileItem(param, id + "/" + ID.unique()));
 	}
 
 	public function text(): Array<String> {
@@ -159,7 +159,7 @@ class ProfileBuilder {
 		if (type != null) {
 			final el = new Stanza(k).textTag(type, v);
 			vcard.addChild(el);
-			items.push(new ProfileItem(el, k + "/" + ID.short()));
+			items.push(new ProfileItem(el, k + "/" + ID.unique()));
 		} else {
 			throw 'Unknown profile property ${k}';
 		}
diff --git a/borogove/calls/PeerConnection.cpp.hx b/borogove/calls/PeerConnection.cpp.hx
index 445a761..9ee06fa 100644
--- a/borogove/calls/PeerConnection.cpp.hx
+++ b/borogove/calls/PeerConnection.cpp.hx
@@ -650,7 +650,7 @@ class MediaStream {
 		Create default bidirectional audio track
 	**/
 	public static function makeAudio(): MediaStream {
-		final audio = new DescriptionAudio(cpp.StdString.ofString(ID.tiny()), SendRecv);
+		final audio = new DescriptionAudio(cpp.StdString.ofString(ID.unique()), SendRecv);
 		audio.addOpusCodec(107); // May need to get from rtpmap?
 		audio.addPCMUCodec(0);
 		audio.addAudioCodec(101, cpp.StdString.ofString("telephone-event/8000")); // May need to get from rtpmap?
diff --git a/borogove/calls/Session.hx b/borogove/calls/Session.hx
index ad61083..c50f878 100644
--- a/borogove/calls/Session.hx
+++ b/borogove/calls/Session.hx
@@ -63,7 +63,7 @@ private function mkCallMessage(to: JID, from: JID, event: Stanza) {
 	m.text = "call " + event.name;
 	m.timestamp = Date.format(std.Date.now());
 	m.payloads.push(event);
-	m.localId = ID.long();
+	m.localId = ID.unique();
 	if (event.name != "propose") {
 		m.versions = [m.build()];
 	}
@@ -205,7 +205,7 @@ class OutgoingProposedSession implements Session {
 	private function new(client: Client, to: JID) {
 		this.client = client;
 		this.to = to;
-		this._sid = ID.long();
+		this._sid = ID.unique();
 	}
 
 	@:allow(borogove)
@@ -398,7 +398,7 @@ class InitiatedSession implements Session {
 	@HaxeCBridge.noemit
 	public function hangup() {
 		client.sendStanza(
-			new Stanza("iq", { to: counterpart.asString(), type: "set", id: ID.medium() })
+			new Stanza("iq", { to: counterpart.asString(), type: "set", id: ID.unique() })
 				.tag("jingle", { xmlns: "urn:xmpp:jingle:1", action: "session-terminate", sid: sid })
 				.tag("reason").tag("success")
 				.up().up().up()
@@ -467,7 +467,7 @@ class InitiatedSession implements Session {
 						) != null) {
 							final modify = gonnaAccept.toStanza("content-modify", sid, initiator);
 							modify.attr.set("to", counterpart.asString());
-							modify.attr.set("id", ID.medium());
+							modify.attr.set("id", ID.unique());
 							client.sendStanza(modify);
 						}
 					}
@@ -588,7 +588,7 @@ class InitiatedSession implements Session {
 			sid
 		).toStanza(initiator);
 		transportInfo.attr.set("to", counterpart.asString());
-		transportInfo.attr.set("id", ID.medium());
+		transportInfo.attr.set("id", ID.unique());
 		client.sendStanza(transportInfo);
 	}
 
@@ -629,7 +629,7 @@ class InitiatedSession implements Session {
 				if (pc != null) client.trigger("call/updateStatus", { session: this });
 				if (pc != null && (pc.connectionState == "closed" || pc.connectionState == "failed")) {
 					client.sendStanza(
-						new Stanza("iq", { to: counterpart.asString(), type: "set", id: ID.medium() })
+						new Stanza("iq", { to: counterpart.asString(), type: "set", id: ID.unique() })
 							.tag("jingle", { xmlns: "urn:xmpp:jingle:1", action: "session-terminate", sid: sid })
 							.tag("reason").tag("connectivity-error")
 							.up().up().up()
@@ -673,7 +673,7 @@ class InitiatedSession implements Session {
 			if (beforeSend != null) beforeSend(descriptionToSend);
 			final sessionAccept = descriptionToSend.toStanza(type, sid, initiator);
 			sessionAccept.attr.set("to", counterpart.asString());
-			sessionAccept.attr.set("id", ID.medium());
+			sessionAccept.attr.set("id", ID.unique());
 			client.sendStanza(sessionAccept);
 
 			final outboundCandidate = queuedOutboundCandidate.copy();
diff --git a/borogove/queries/BlocklistGet.hx b/borogove/queries/BlocklistGet.hx
index 156de6e..985dc54 100644
--- a/borogove/queries/BlocklistGet.hx
+++ b/borogove/queries/BlocklistGet.hx
@@ -18,7 +18,7 @@ class BlocklistGet extends GenericQuery {
 
 	public function new() {
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { type: "get", id: queryId })
 			.tag("blocklist", { xmlns: xmlns }).up();
 	}
diff --git a/borogove/queries/BoB.hx b/borogove/queries/BoB.hx
index a5515f4..4e55f58 100644
--- a/borogove/queries/BoB.hx
+++ b/borogove/queries/BoB.hx
@@ -18,7 +18,7 @@ class BoB extends GenericQuery {
 	public function new(to: Null<String>, uri: String) {
 		if (!uri.startsWith("cid:") || !uri.endsWith("@bob.xmpp.org") || !uri.contains("+")) throw "invalid BoB URI";
 
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { to: to, type: "get", id: queryId })
 			.tag("data", { xmlns: xmlns, cid: uri.substr(4) }).up();
 	}
diff --git a/borogove/queries/CommandExecute.hx b/borogove/queries/CommandExecute.hx
index a265093..6ade4db 100644
--- a/borogove/queries/CommandExecute.hx
+++ b/borogove/queries/CommandExecute.hx
@@ -26,7 +26,7 @@ class CommandExecute extends GenericQuery {
 		attr["action"] = action ?? "execute";
 		if (sessionid != null) attr["sessionid"] = sessionid;
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "set", id: queryId }
diff --git a/borogove/queries/DiscoInfoGet.hx b/borogove/queries/DiscoInfoGet.hx
index 366d725..90b53a7 100644
--- a/borogove/queries/DiscoInfoGet.hx
+++ b/borogove/queries/DiscoInfoGet.hx
@@ -20,7 +20,7 @@ class DiscoInfoGet extends GenericQuery {
 		var attr: DynamicAccess<String> = { xmlns: xmlns };
 		if (node != null) attr["node"] = node;
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "get", id: queryId }
diff --git a/borogove/queries/DiscoItemsGet.hx b/borogove/queries/DiscoItemsGet.hx
index 0f6394c..642680d 100644
--- a/borogove/queries/DiscoItemsGet.hx
+++ b/borogove/queries/DiscoItemsGet.hx
@@ -20,7 +20,7 @@ class DiscoItemsGet extends GenericQuery {
 		var attr: DynamicAccess<String> = { xmlns: xmlns };
 		if (node != null) attr["node"] = node;
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "get", id: queryId }
diff --git a/borogove/queries/ExtDiscoGet.hx b/borogove/queries/ExtDiscoGet.hx
index 3288511..0a75888 100644
--- a/borogove/queries/ExtDiscoGet.hx
+++ b/borogove/queries/ExtDiscoGet.hx
@@ -19,7 +19,7 @@ class ExtDiscoGet extends GenericQuery {
 
 	public function new(to: String) {
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "get", id: queryId }
diff --git a/borogove/queries/HttpUploadSlot.hx b/borogove/queries/HttpUploadSlot.hx
index b1d4ead..df0b574 100644
--- a/borogove/queries/HttpUploadSlot.hx
+++ b/borogove/queries/HttpUploadSlot.hx
@@ -21,7 +21,7 @@ class HttpUploadSlot extends GenericQuery {
 
 	public function new(to: String, filename: String, size: Int, mime: String, hashes: Array<Hash>) {
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "get", id: queryId }
diff --git a/borogove/queries/JabberIqGatewayGet.hx b/borogove/queries/JabberIqGatewayGet.hx
index d6edc8e..c36566d 100644
--- a/borogove/queries/JabberIqGatewayGet.hx
+++ b/borogove/queries/JabberIqGatewayGet.hx
@@ -19,7 +19,7 @@ class JabberIqGatewayGet extends GenericQuery {
 	private var result:Null<Either<String, String>>;
 
 	public function new(to: String, ?prompt: String) {
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: prompt == null ? "get" : "set", id: queryId }
diff --git a/borogove/queries/MAMQuery.hx b/borogove/queries/MAMQuery.hx
index c31d390..869e37e 100644
--- a/borogove/queries/MAMQuery.hx
+++ b/borogove/queries/MAMQuery.hx
@@ -57,7 +57,7 @@ class MAMQuery extends GenericQuery {
 
 	public function new(params:MAMQueryParams, ?jid:String) {
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { type: "set", to: jid })
 			.tag("query", { xmlns: xmlns, queryid: queryId })
 				.tag("x", { xmlns: "jabber:x:data", type: "submit" })
diff --git a/borogove/queries/PubsubGet.hx b/borogove/queries/PubsubGet.hx
index 53e84e8..55b3b0e 100644
--- a/borogove/queries/PubsubGet.hx
+++ b/borogove/queries/PubsubGet.hx
@@ -19,7 +19,7 @@ class PubsubGet extends GenericQuery {
 		var attr: DynamicAccess<String> = { node: node };
 		if (ver != null) attr["ver"] = ver;
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { to: to, type: "get", id: queryId });
 		final items = queryStanza
 			.tag("pubsub", { xmlns: xmlns })
diff --git a/borogove/queries/Push2Disable.hx b/borogove/queries/Push2Disable.hx
index 9f5311c..a06abd5 100644
--- a/borogove/queries/Push2Disable.hx
+++ b/borogove/queries/Push2Disable.hx
@@ -14,7 +14,7 @@ class Push2Disable extends GenericQuery {
 	private var responseStanza:Stanza;
 
 	public function new(to: String) {
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "set", id: queryId }
diff --git a/borogove/queries/Push2Enable.hx b/borogove/queries/Push2Enable.hx
index cce915b..71f9e9e 100644
--- a/borogove/queries/Push2Enable.hx
+++ b/borogove/queries/Push2Enable.hx
@@ -14,7 +14,7 @@ class Push2Enable extends GenericQuery {
 	private var responseStanza:Stanza;
 
 	public function new(to: String, service: String, client: String, ua_public: Bytes, auth_secret: Bytes, jwt_alg: Null<String>, jwt_key: Bytes, jwt_claims: Map<String, String>, grace: Int, filters: Array<{ jid: String, mention: Bool, reply: Bool }>) {
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza(
 			"iq",
 			{ to: to, type: "set", id: queryId }
diff --git a/borogove/queries/RosterGet.hx b/borogove/queries/RosterGet.hx
index 9932a9f..b599b39 100644
--- a/borogove/queries/RosterGet.hx
+++ b/borogove/queries/RosterGet.hx
@@ -20,7 +20,7 @@ class RosterGet extends GenericQuery {
 		var attr: DynamicAccess<String> = { xmlns: xmlns };
 		if (ver != null) attr["ver"] = ver;
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { type: "get" })
 			.tag("query", attr)
 			.up();
diff --git a/borogove/queries/VcardTempGet.hx b/borogove/queries/VcardTempGet.hx
index 2e31d9a..424d1dd 100644
--- a/borogove/queries/VcardTempGet.hx
+++ b/borogove/queries/VcardTempGet.hx
@@ -20,7 +20,7 @@ class VcardTempGet extends GenericQuery {
 
 	public function new(to: JID) {
 		/* Build basic query */
-		queryId = ID.short();
+		queryId = ID.unique();
 		queryStanza = new Stanza("iq", { to: to.asString(), type: "get", id: queryId });
 		queryStanza.tag("vCard", { xmlns: xmlns }).up();
 	}
diff --git a/borogove/streams/TestStream.hx b/borogove/streams/TestStream.hx
index f6ee0d1..3242eee 100644
--- a/borogove/streams/TestStream.hx
+++ b/borogove/streams/TestStream.hx
@@ -18,10 +18,6 @@ class TestStream extends GenericStream {
 
 	public function disconnect() { }
 
-	public function newId() {
-		return ID.long();
-	}
-
 	public function sendStanza(stanza:Stanza) {
 		this.trigger("sendStanza", stanza);
 	}
diff --git a/borogove/streams/XmppJsStream.hx b/borogove/streams/XmppJsStream.hx
index 54f62b5..8650fa6 100644
--- a/borogove/streams/XmppJsStream.hx
+++ b/borogove/streams/XmppJsStream.hx
@@ -89,12 +89,6 @@ extern class XmppJsLtx {
 	static function parse(input:String):XmppJsXml;
 }
 
-@:js.import(@default "@xmpp/id")
-extern class XmppJsId {
-	@:selfCall
-	static function id():String;
-}
-
 @:js.import(@default "@xmpp/error")
 extern class XmppJsError {
 	public final name: String;
@@ -436,10 +430,6 @@ class XmppJsStream extends GenericStream {
 		triggerSMupdate();
 	}
 
-	public function newId():String {
-		return XmppJsId.id();
-	}
-
 	private function triggerSMupdate() {
 		if ((lastSMState == null || pending.length < 1) && (client == null || !client.streamManagement?.enabled || !emitSMupdates)) return;
 		if (client?.streamManagement?.enabled) {
diff --git a/borogove/streams/XmppStropheStream.hx b/borogove/streams/XmppStropheStream.hx
index e120db7..f19d01c 100644
--- a/borogove/streams/XmppStropheStream.hx
+++ b/borogove/streams/XmppStropheStream.hx
@@ -230,10 +230,6 @@ class XmppStropheStream extends GenericStream {
 
 	}
 
-	public function newId():String {
-		return ID.long();
-	}
-
 	public static function strophe_certfail_handler(cert:StropheTlsCert, err:RawConstPointer<Char>): Int {
 #if STROPHE_HAVE_TLS_CERT_USERDATA
 		final userdata = StropheTlsCert.get_userdata(cert);
diff --git a/browserjs.hxml b/browserjs.hxml
index fd448c5..72e8b01 100644
--- a/browserjs.hxml
+++ b/browserjs.hxml
@@ -4,9 +4,9 @@
 --library hsluv
 --library hxtsdgen
 --library jsImport
---library sha
 --library thenshim
 --library tink_http
+--library uuidv7
 
 borogove.Client
 borogove.Register
diff --git a/cpp.hxml b/cpp.hxml
index 439517c..b83d044 100644
--- a/cpp.hxml
+++ b/cpp.hxml
@@ -4,9 +4,9 @@
 --library haxe-strings
 --library hsluv
 --library tink_http
---library sha
 --library thenshim
 --library HtmlParser
+--library uuidv7
 
 HaxeCBridge
 borogove.Client
diff --git a/nodejs.hxml b/nodejs.hxml
index d34e603..f8de008 100644
--- a/nodejs.hxml
+++ b/nodejs.hxml
@@ -5,9 +5,9 @@
 --library hxnodejs
 --library hxtsdgen
 --library jsImport
---library sha
 --library thenshim
 --library tink_http
+--library uuidv7
 
 borogove.Client
 borogove.Register
diff --git a/test.hxml b/test.hxml
index faf9ca4..6534de8 100644
--- a/test.hxml
+++ b/test.hxml
@@ -3,9 +3,9 @@
 --library haxe-strings
 --library hsluv
 --library hxtsdgen
---library sha
 --library thenshim
 --library tink_http
+--library uuidv7
 
 --library utest