git » sdk » commit 7b42212

Store bookmarked state

author Stephen Paul Weber
2026-02-23 14:37:45 UTC
committer Stephen Paul Weber
2026-02-23 14:37:45 UTC
parent f0a0c43f46285f328a83c4f49405b8d616ae35c0

Store bookmarked state

We don't fetch bookmarks on resume so without this we can forget.

borogove/Chat.hx +11 -8
borogove/Client.hx +2 -2
borogove/persistence/IDB.js +2 -0
borogove/persistence/Sqlite.hx +11 -4

diff --git a/borogove/Chat.hx b/borogove/Chat.hx
index c62d420..cd5be03 100644
--- a/borogove/Chat.hx
+++ b/borogove/Chat.hx
@@ -114,7 +114,7 @@ abstract class Chat {
 	private var omemoContactDeviceIDs: Null<Array<Int>> = null;
 
 	@:allow(borogove)
-	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBlocked = false, extensions: Null<Stanza> = null, readUpToId: Null<String> = null, readUpToBy: Null<String> = null, omemoContactDeviceIDs: Array<Int> = null) {
+	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBookmarked = false, isBlocked = false, extensions: Null<Stanza> = null, readUpToId: Null<String> = null, readUpToBy: Null<String> = null, omemoContactDeviceIDs: Array<Int> = null) {
 		if (chatId == null || chatId == "") {
 			throw "chatId may not be empty";
 		}
@@ -123,6 +123,7 @@ abstract class Chat {
 		this.persistence = persistence;
 		this.chatId = chatId;
 		this.uiState = uiState;
+		this.isBookmarked = isBookmarked;
 		this.isBlocked = isBlocked;
 		this.extensions = extensions ?? new Stanza("extensions", { xmlns: "urn:xmpp:bookmarks:1" });
 		this.readUpToId = readUpToId;
@@ -936,8 +937,8 @@ abstract class Chat {
 #end
 class DirectChat extends Chat {
 	@:allow(borogove)
-	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBlocked = false, extensions: Null<Stanza> = null, readUpToId: Null<String> = null, readUpToBy: Null<String> = null, omemoContactDeviceIDs: Array<Int> = null) {
-		super(client, stream, persistence, chatId, uiState, isBlocked, extensions, readUpToId, readUpToBy, omemoContactDeviceIDs);
+	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBookmarked = false, isBlocked = false, extensions: Null<Stanza> = null, readUpToId: Null<String> = null, readUpToBy: Null<String> = null, omemoContactDeviceIDs: Array<Int> = null) {
+		super(client, stream, persistence, chatId, uiState, isBookmarked, isBlocked, extensions, readUpToId, readUpToBy, omemoContactDeviceIDs);
 		outbox.start();
 	}
 
@@ -1243,8 +1244,8 @@ class Channel extends Chat {
 	private var _nickInUse = null;
 
 	@:allow(borogove)
-	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBlocked = false, extensions = null, readUpToId = null, readUpToBy = null, ?disco: Caps) {
-		super(client, stream, persistence, chatId, uiState, isBlocked, extensions, readUpToId, readUpToBy);
+	private function new(client:Client, stream:GenericStream, persistence:Persistence, chatId:String, uiState = Open, isBookmarked = false, isBlocked = false, extensions = null, readUpToId = null, readUpToBy = null, ?disco: Caps) {
+		super(client, stream, persistence, chatId, uiState, isBookmarked, isBlocked, extensions, readUpToId, readUpToBy);
 		if (disco != null) {
 			this.disco = disco;
 			if (!disco.features.contains("http://jabber.org/protocol/muc")) {
@@ -1949,6 +1950,7 @@ class AvailableChat {
 class SerializedChat {
 	public final chatId:String;
 	public final trusted:Bool;
+	public final isBookmarked:Bool;
 	public final avatarSha1:Null<BytesData>;
 	public final presence:Map<String, Presence>;
 	public final displayName:Null<String>;
@@ -1964,9 +1966,10 @@ class SerializedChat {
 	public final notifyMention: Bool;
 	public final notifyReply: Bool;
 
-	public function new(chatId: String, trusted: Bool, avatarSha1: Null<BytesData>, presence: Map<String, Presence>, displayName: Null<String>, uiState: Null<UiState>, isBlocked: Null<Bool>, extensions: Null<String>, readUpToId: Null<String>, readUpToBy: Null<String>, notificationsFiltered: Null<Bool>, notifyMention: Bool, notifyReply: Bool, disco: Null<Caps>, omemoContactDeviceIDs: Array<Int>, klass: String) {
+	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>, extensions: Null<String>, readUpToId: Null<String>, readUpToBy: Null<String>, notificationsFiltered: Null<Bool>, notifyMention: Bool, notifyReply: Bool, disco: Null<Caps>, omemoContactDeviceIDs: Array<Int>, klass: String) {
 		this.chatId = chatId;
 		this.trusted = trusted;
+		this.isBookmarked = isBookmarked;
 		this.avatarSha1 = avatarSha1;
 		this.presence = presence;
 		this.displayName = displayName;
@@ -1989,9 +1992,9 @@ class SerializedChat {
 		var mention = notifyMention;
 
 		final chat = if (klass == "DirectChat") {
-			new DirectChat(client, stream, persistence, chatId, uiState, isBlocked, extensionsStanza, readUpToId, readUpToBy, omemoContactDeviceIDs);
+			new DirectChat(client, stream, persistence, chatId, uiState, isBookmarked, isBlocked, extensionsStanza, readUpToId, readUpToBy, omemoContactDeviceIDs);
 		} else if (klass == "Channel") {
-			final channel = new Channel(client, stream, persistence, chatId, uiState, isBlocked, extensionsStanza, readUpToId, readUpToBy);
+			final channel = new Channel(client, stream, persistence, chatId, uiState, isBookmarked, isBlocked, extensionsStanza, readUpToId, readUpToBy);
 			channel.disco = disco ?? new Caps("", [], ["http://jabber.org/protocol/muc"], []);
 			if (notificationsFiltered == null && !channel.isPrivate()) {
 				mention = filterN = true;
diff --git a/borogove/Client.hx b/borogove/Client.hx
index 474506b..a19a29c 100644
--- a/borogove/Client.hx
+++ b/borogove/Client.hx
@@ -1083,7 +1083,7 @@ class Client extends EventEmitter {
 		}
 
 		final chat = if (availableChat.isChannel()) {
-			final channel = new Channel(this, this.stream, this.persistence, availableChat.chatId, Open, false, null, availableChat.caps);
+			final channel = new Channel(this, this.stream, this.persistence, availableChat.chatId, Open, false, false, null, availableChat.caps);
 			channel.setupNotifications();
 			chats.unshift(channel);
 			channel.selfPing(false);
@@ -1680,7 +1680,7 @@ class Client extends EventEmitter {
 			} else {
 				persistence.storeCaps(resultCaps);
 				if (resultCaps.isChannel(jid)) {
-					final chat = new Channel(this, this.stream, this.persistence, jid, uiState, false, null, resultCaps);
+					final chat = new Channel(this, this.stream, this.persistence, jid, uiState, false, false, null, resultCaps);
 					chat.setupNotifications();
 					chats.unshift(chat);
 					if (inSync && sendAvailable) chat.selfPing(false);
diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js
index e25ab58..8abd974 100644
--- a/borogove/persistence/IDB.js
+++ b/borogove/persistence/IDB.js
@@ -288,6 +288,7 @@ export default async (dbname, media, tokenize, stemmer) => {
 					account: account,
 					chatId: chat.chatId,
 					trusted: chat.trusted,
+					isBookmarked: chat.isBookmarked,
 					avatarSha1: chat.avatarSha1,
 					presence: new Map([...chat.presence.entries()].map(([k, p]) => [k, { caps: p.caps?.ver(), mucUser: p.mucUser?.toString(), avatarHash: p.avatarHash?.serializeUri() }])),
 					displayName: chat.displayName,
@@ -312,6 +313,7 @@ export default async (dbname, media, tokenize, stemmer) => {
 			return await Promise.all(result.map(async (r) => new borogove_SerializedChat(
 				r.chatId,
 				r.trusted,
+				r.isBookmarked,
 				r.avatarSha1,
 				new Map(await Promise.all((r.presence instanceof Map ? [...r.presence.entries()] : Object.entries(r.presence)).map(
 					async ([k, p]) => [k, new borogove_Presence(p.caps && await this.getCaps(p.caps), p.mucUser && borogove_Stanza.parse(p.mucUser), p.avatarHash && borogove_Hash.fromUri(p.avatarHash))]
diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx
index babd06b..a9bfe4e 100644
--- a/borogove/persistence/Sqlite.hx
+++ b/borogove/persistence/Sqlite.hx
@@ -184,6 +184,12 @@ class Sqlite implements Persistence implements KeyValueStore {
 						"PRAGMA user_version = 5"]);
 					}
 					return Promise.resolve(null);
+				}).then(_ -> {
+					if (version < 6) {
+						return exec(["ALTER TABLE chats ADD COLUMN bookmarked INTEGER NOT NULL DEFAULT 0",
+						"PRAGMA user_version = 6"]);
+					}
+					return Promise.resolve(null);
 				});
 			});
 		});
@@ -271,7 +277,7 @@ class Sqlite implements Persistence implements KeyValueStore {
 			for (_ in storeChatBuffer) {
 				if (!first) q.add(",");
 				first = false;
-				q.add("(?,?,?,?,?,?,?,?,?,?,?,jsonb(?),?,?,?,?)");
+				q.add("(?,?,?,?,?,?,?,?,?,?,?,jsonb(?),?,?,?,?,?)");
 			}
 			db.exec(
 				q.toString(),
@@ -284,7 +290,8 @@ class Sqlite implements Persistence implements KeyValueStore {
 						chat.extensions.toString(), chat.readUpTo(), chat.readUpToBy,
 						channel?.disco?.verRaw().hash, Json.stringify(mapPresence(chat)),
 						Type.getClassName(Type.getClass(chat)).split(".").pop(),
-						chat.notificationsFiltered(), chat.notifyMention(), chat.notifyReply()
+						chat.notificationsFiltered(), chat.notifyMention(), chat.notifyReply(),
+						chat.isBookmarked
 					];
 					return row;
 				})
@@ -297,7 +304,7 @@ class Sqlite implements Persistence implements KeyValueStore {
 	@HaxeCBridge.noemit
 	public function getChats(accountId: String): Promise<Array<SerializedChat>> {
 		return db.exec(
-			"SELECT chat_id, trusted, avatar_sha1, fn, ui_state, blocked, extensions, read_up_to_id, read_up_to_by, notifications_filtered, notify_mention, notify_reply, json(caps) AS caps, caps_ver, json(presence) AS presence, class FROM chats LEFT JOIN caps ON chats.caps_ver=caps.sha1 WHERE account_id=?",
+			"SELECT chat_id, trusted, bookmarked, avatar_sha1, fn, ui_state, blocked, extensions, read_up_to_id, read_up_to_by, notifications_filtered, notify_mention, notify_reply, json(caps) AS caps, caps_ver, json(presence) AS presence, class FROM chats LEFT JOIN caps ON chats.caps_ver=caps.sha1 WHERE account_id=?",
 			[accountId]
 		).then(result -> {
 			final fetchCaps: Map<BytesData, Bool> = [];
@@ -339,7 +346,7 @@ class Sqlite implements Persistence implements KeyValueStore {
 					);
 				}
 				// FIXME: Empty OMEMO contact device ids hardcoded in next line
-				chats.push(new SerializedChat(row.chat_id, row.trusted != 0, row.avatar_sha1, presenceMap, row.fn, row.ui_state, row.blocked != 0, row.extensions, row.read_up_to_id, row.read_up_to_by, row.notifications_filtered == null ? null : row.notifications_filtered != 0, row.notify_mention != 0, row.notify_reply != 0, row.capsObj, [], Reflect.field(row, "class")));
+				chats.push(new SerializedChat(row.chat_id, row.trusted != 0, row.bookmarked != 0, row.avatar_sha1, presenceMap, row.fn, row.ui_state, row.blocked != 0, row.extensions, row.read_up_to_id, row.read_up_to_by, row.notifications_filtered == null ? null : row.notifications_filtered != 0, row.notify_mention != 0, row.notify_reply != 0, row.capsObj, [], Reflect.field(row, "class")));
 			}
 			return chats;
 		});