| author | Eric Roberts
<eric@devl.me> 2026-08-26 18:14:56 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-08-26 18:16:17 UTC |
| parent | 50d17c29551e1a00ccdc3cebe3ad191002027d57 |
| borogove/OMEMO.hx | +1 | -0 |
| borogove/Persistence.hx | +2 | -2 |
| borogove/persistence/Dummy.hx | +5 | -3 |
| borogove/persistence/IDB.js | +5 | -6 |
| borogove/persistence/Sqlite.hx | +33 | -3 |
| test/TestSqlite.hx | +33 | -0 |
| test/persistence-tests.ts | +38 | -0 |
diff --git a/borogove/OMEMO.hx b/borogove/OMEMO.hx index f5c6e07..097cce0 100644 --- a/borogove/OMEMO.hx +++ b/borogove/OMEMO.hx @@ -313,6 +313,7 @@ class OMEMODecryptionResult { } } +@:expose class OMEMOSessionMetadata { // True when we have successfully received and decrypted any // non-prekey message from this session diff --git a/borogove/Persistence.hx b/borogove/Persistence.hx index 894a06f..257ab17 100644 --- a/borogove/Persistence.hx +++ b/borogove/Persistence.hx @@ -425,11 +425,11 @@ interface Persistence { /** Store extra metadata associated with an OMEMO session **/ - public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Void; + public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Promise<OMEMOSessionMetadata>; /** Load stored metadata associated with an OMEMO session **/ - public function getOmemoMetadata(account:String, address:String): Promise<OMEMOSessionMetadata>; + public function getOmemoMetadata(account:String, address:String): Promise<Null<OMEMOSessionMetadata>>; #end } diff --git a/borogove/persistence/Dummy.hx b/borogove/persistence/Dummy.hx index a86d289..9c1bebd 100644 --- a/borogove/persistence/Dummy.hx +++ b/borogove/persistence/Dummy.hx @@ -269,11 +269,13 @@ class Dummy implements Persistence { } @HaxeCBridge.noemit - public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Void { } + public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Promise<OMEMOSessionMetadata> { + return Promise.resolve(metadata); + } @HaxeCBridge.noemit - public function getOmemoMetadata(account:String, address:String): Promise<OMEMOSessionMetadata> { - return Promise.reject("Not found"); + public function getOmemoMetadata(account:String, address:String): Promise<Null<OMEMOSessionMetadata>> { + return Promise.resolve(null); } #end } diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js index f90bad4..c34c9fc 100644 --- a/borogove/persistence/IDB.js +++ b/borogove/persistence/IDB.js @@ -1472,23 +1472,22 @@ tx.onerror = console.error; return session; }, - storeOmemoMetadata(account, address, metadata) { + async storeOmemoMetadata(account, address, metadata) { const tx = db.transaction(["omemo_sessions_meta"], "readwrite"); const store = tx.objectStore("omemo_sessions_meta"); - promisifyRequest(store.put({ + await promisifyRequest(store.put({ account: account, address: address, metadata: metadata, - })).catch((e) => { - console.error("Failed to store OMEMO session metadata: " + e); - }); + })); + return metadata; }, async getOmemoMetadata(account, address) { const tx = db.transaction(["omemo_sessions_meta"], "readonly"); const store = tx.objectStore("omemo_sessions_meta"); const result = await promisifyRequest(store.get([account, address])); - return result?.metadata; + return result?.metadata ?? null; }, async removeOmemoSession(account, address) { diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx index d3809c2..73463e6 100644 --- a/borogove/persistence/Sqlite.hx +++ b/borogove/persistence/Sqlite.hx @@ -335,6 +335,19 @@ class Sqlite implements Persistence implements KeyValueStore { "PRAGMA user_version = 19"]); } return Promise.resolve(null); + }).then(_ -> { + if (version < 20) { + return exec(["CREATE TABLE omemo_sessions_meta ( + account_id TEXT NOT NULL, + address TEXT NOT NULL, + received_session_message_ok INTEGER NOT NULL, + last_message_decrypted_ok INTEGER NOT NULL, + sent_key_exchange INTEGER NOT NULL, + PRIMARY KEY (account_id, address) + ) STRICT", + "PRAGMA user_version = 20"]); + } + return Promise.resolve(null); }); }); }); @@ -1632,11 +1645,28 @@ class Sqlite implements Persistence implements KeyValueStore { } @HaxeCBridge.noemit - public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Void { } + public function storeOmemoMetadata(account:String, address:String, metadata:OMEMOSessionMetadata):Promise<OMEMOSessionMetadata> { + return db.exec( + "INSERT OR REPLACE INTO omemo_sessions_meta VALUES (?,?,?,?,?)", + [account, address, metadata.receivedSessionMessageOk, metadata.lastMessageDecryptedOk, metadata.sentKeyExchange], + ).then(_ -> metadata); + } @HaxeCBridge.noemit - public function getOmemoMetadata(account:String, address:String): Promise<OMEMOSessionMetadata> { - return Promise.reject("TODO"); + public function getOmemoMetadata(account:String, address:String): Promise<Null<OMEMOSessionMetadata>> { + return db.exec( + "SELECT received_session_message_ok, last_message_decrypted_ok, sent_key_exchange FROM omemo_sessions_meta WHERE account_id=? AND address=? LIMIT 1", + [account, address], + ).then(result -> { + for (row in result) { + return new OMEMOSessionMetadata( + row.received_session_message_ok == 1, + row.last_message_decrypted_ok == 1, + row.sent_key_exchange == 1, + ); + } + return null; + }); } #end } diff --git a/test/TestSqlite.hx b/test/TestSqlite.hx index e3b2211..daf01fe 100644 --- a/test/TestSqlite.hx +++ b/test/TestSqlite.hx @@ -1527,6 +1527,39 @@ class TestSqlite extends utest.Test { }); } + public function testGetOmemoMetadataNotFound(async: Async) { + persistence + .getOmemoMetadata("metadata-notfound@example.com", "contact@example.com/1") + .then(result -> { + Assert.equals(null, result); + async.done(); + }) + .catchError(e -> { + Assert.fail(Std.string(e)); + async.done(); + }); + } + + public function testOmemoMetadata(async: Async) { + final account = "metadata-existing@example.com"; + final address = "contact@example.com/1"; + final metadata = new OMEMOSessionMetadata(true, false, true); + + persistence + .storeOmemoMetadata(account, address, metadata) + .then(_ -> persistence.getOmemoMetadata(account, address)) + .then(result -> { + Assert.equals(metadata.receivedSessionMessageOk, result.receivedSessionMessageOk); + Assert.equals(metadata.lastMessageDecryptedOk, result.lastMessageDecryptedOk); + Assert.equals(metadata.sentKeyExchange, result.sentKeyExchange); + async.done(); + }) + .catchError(e -> { + Assert.fail(Std.string(e)); + async.done(); + }); + } + public function testOmemoSession(async: Async) { final account = "session-existing@example.com"; final address = "contact@example.com/1"; diff --git a/test/persistence-tests.ts b/test/persistence-tests.ts index 7c8257f..a1c7061 100644 --- a/test/persistence-tests.ts +++ b/test/persistence-tests.ts @@ -1782,6 +1782,44 @@ export function sharedPersistenceTests(test: PersistenceTest) { expect(result).toBeNull(); }); + test("getOmemoMetadata returns null when none is stored", async ({ + page, + persistence, + }) => { + const result = await page.evaluate( + async ({ persistence }) => + persistence.getOmemoMetadata( + "omemo-metadata-not-found@example.com", + "contact@example.com/1", + ), + { persistence }, + ); + + expect(result).toBeNull(); + }); + + test("storeOmemoMetadata and getOmemoMetadata", async ({ + page, + persistence, + }) => { + const account = "omemo-metadata-existing@example.com"; + const address = "contact@example.com/1"; + const metadata = { + receivedSessionMessageOk: true, + lastMessageDecryptedOk: false, + sentKeyExchange: true, + }; + const result = await page.evaluate( + async ({ persistence, account, address, metadata }) => { + await persistence.storeOmemoMetadata(account, address, metadata); + return persistence.getOmemoMetadata(account, address); + }, + { persistence, account, address, metadata }, + ); + + expect(result).toEqual(metadata); + }); + test("storeOmemoSession, getOmemoSession, and removeOmemoSession", async ({ page, persistence,