borogove
created pr with
borogove/call-fixes.1
added borogove/call-fixes.2
1: dbd8243 ! 1: a3fc6ef Never write ring before propose
2: 5ebad9d = 2: 5888d2b Presetve stanza ID for call updates
3: 9d8f8cc = 3: fc1c6dd Get all versions of a call from sqlite
commented
lgtm
cmds
checkout latest patchset:
ssh pr.pico.sh pull borogove:call-fixes | git am -3checkout specific patchset revision:
ssh pr.pico.sh pull borogove:call-fixes [rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh borogove:call-fixesadd comment to patch request:
ssh pr.pico.sh comment borogove:call-fixes "lgtm!"
Patchset
borogove/call-fixes.2
Get all versions of a call from sqlite
Stephen Paul Weber
2026-11-03T21:13:30ZSemantic diff summary
1 added,
1 modified,
0 signature changed,
0 removed
across 1 analyzed file
(2 files skipped: unsupported file type)
+1
-1
borogove/Message.hx
#
| ... | ... | @@ -252,7 +252,7 @@ class Message { | |
| 252 | 252 | final version = msg.build(); | |
| 253 | 253 | // The session id is what really identifies us | |
| 254 | 254 | msg.localId = jmi.attr.get("id"); | |
| 255 | - | if (jmi.name != "propose") { | |
| 255 | + | if (jmi.name != "propose" && msg.versions.length < 1) { | |
| 256 | 256 | msg.versions = [version]; | |
| 257 | 257 | } | |
| 258 | 258 | } |
+22
-9
borogove/persistence/Sqlite.hx
#
| ... | ... | @@ -876,17 +876,20 @@ class Sqlite implements Persistence implements KeyValueStore { | |
| 876 | 876 | SELECT | |
| 877 | 877 | $versionedMessageColumns | |
| 878 | 878 | FROM messages | |
| 879 | - | INNER JOIN messages versions USING (correction_id, sender_id) | |
| 879 | + | $versionedMessagesJoin | |
| 880 | 880 | WHERE | |
| 881 | 881 | messages.account_id=? | |
| 882 | 882 | AND messages.chat_id=? | |
| 883 | - | AND (messages.stanza_id IS NULL OR messages.stanza_id=\'\' OR messages.stanza_id=correction_id) | |
| 883 | + | AND (messages.stanza_id IS NULL OR messages.stanza_id=\'\' OR messages.stanza_id=messages.correction_id) | |
| 884 | 884 | '; | |
| 885 | - | var params = [accountId, stub.chatId()]; | |
| 885 | + | var params:Array<Dynamic> = [MessageCall, accountId, stub.chatId()]; | |
| 886 | 886 | if (stub.versions.length > 0 || stub.serverId == null) { | |
| 887 | - | q += " AND correction_id=? AND sender_id=?"; | |
| 887 | + | q += " AND messages.correction_id=?"; | |
| 888 | 888 | params.push(stub.localId); | |
| 889 | - | params.push(stub.senderId); | |
| 889 | + | if (stub.type != MessageCall) { | |
| 890 | + | q += " AND messages.sender_id=?"; | |
| 891 | + | params.push(stub.senderId); | |
| 892 | + | } | |
| 890 | 893 | } else if (stub.serverId != null) { | |
| 891 | 894 | q += " AND messages.mam_id=? AND messages.mam_by=?"; | |
| 892 | 895 | params.push(stub.serverId); |
| ... | ... | @@ -894,7 +897,8 @@ class Sqlite implements Persistence implements KeyValueStore { | |
| 894 | 897 | } else { | |
| 895 | 898 | throw "No way to look up this message"; | |
| 896 | 899 | } | |
| 897 | - | q += " GROUP BY correction_id, CASE WHEN messages.type=? THEN 'call' ELSE messages.sender_id END"; | |
| 900 | + | q += " GROUP BY messages.correction_id, CASE WHEN messages.type=? THEN 'call' ELSE messages.sender_id END"; | |
| 901 | + | params.push(MessageCall); | |
| 898 | 902 | return db.exec(q, params).then(results -> { | |
| 899 | 903 | final row = results.next(); | |
| 900 | 904 | if (row == null) return stub; |
| ... | ... | @@ -952,18 +956,19 @@ class Sqlite implements Persistence implements KeyValueStore { | |
| 952 | 956 | SELECT | |
| 953 | 957 | $versionedMessageColumns | |
| 954 | 958 | FROM messages | |
| 955 | - | INNER JOIN messages versions USING (correction_id, sender_id) | |
| 959 | + | $versionedMessagesJoin | |
| 956 | 960 | WHERE | |
| 957 | 961 | (messages.stanza_id, messages.mam_id) IN (SELECT * FROM page) | |
| 958 | 962 | AND messages.account_id=? | |
| 959 | 963 | AND messages.chat_id=? | |
| 960 | - | GROUP BY correction_id, CASE WHEN messages.type=? THEN \'call\' ELSE messages.sender_id END | |
| 964 | + | GROUP BY messages.correction_id, CASE WHEN messages.type=? THEN \'call\' ELSE messages.sender_id END | |
| 961 | 965 | '; | |
| 962 | 966 | q += " ORDER BY messages.sort_id"; | |
| 963 | 967 | if (op == "<" || op == "<=") q += " DESC"; | |
| 964 | 968 | q += ", messages.time_received"; | |
| 965 | 969 | if (op == "<" || op == "<=") q += " DESC"; | |
| 966 | 970 | ||
| 971 | + | params.push(MessageCall); | |
| 967 | 972 | params.push(accountId); | |
| 968 | 973 | params.push(chatId); | |
| 969 | 974 | params.push(MessageCall); |
| ... | ... | @@ -1848,8 +1853,16 @@ class Sqlite implements Persistence implements KeyValueStore { | |
| 1848 | 1853 | .map(column -> column.sql); | |
| 1849 | 1854 | } | |
| 1850 | 1855 | ||
| 1856 | + | private final versionedMessagesJoin = " | |
| 1857 | + | INNER JOIN messages versions | |
| 1858 | + | ON versions.correction_id = messages.correction_id | |
| 1859 | + | AND CASE | |
| 1860 | + | WHEN messages.type = ? THEN versions.type = messages.type | |
| 1861 | + | ELSE versions.sender_id = messages.sender_id | |
| 1862 | + | END"; | |
| 1863 | + | ||
| 1851 | 1864 | private final versionedMessageColumns = " | |
| 1852 | - | correction_id AS stanza_id, | |
| 1865 | + | messages.correction_id AS stanza_id, | |
| 1853 | 1866 | json_group_object( | |
| 1854 | 1867 | CASE | |
| 1855 | 1868 | WHEN versions.mam_id IS NULL OR versions.mam_id='' |
+134
-43
test/persistence-tests.ts
#
| ... | ... | @@ -1,6 +1,57 @@ | |
| 1 | 1 | import { faker } from "@faker-js/faker"; | |
| 2 | 2 | import { expect } from "vitest"; | |
| 3 | 3 | ||
| 4 | + | function createCallMessage( | |
| 5 | + | borogove: any, | |
| 6 | + | account: string, | |
| 7 | + | contact: string, | |
| 8 | + | sid: string, | |
| 9 | + | ) { | |
| 10 | + | return ({ | |
| 11 | + | direction, | |
| 12 | + | event, | |
| 13 | + | localId = sid, | |
| 14 | + | serverId = null, | |
| 15 | + | sortId, | |
| 16 | + | timeReceived, | |
| 17 | + | versions = [], | |
| 18 | + | }: { | |
| 19 | + | direction: number; | |
| 20 | + | event: string; | |
| 21 | + | localId?: string; | |
| 22 | + | serverId?: string | null; | |
| 23 | + | sortId: string; | |
| 24 | + | timeReceived: string; | |
| 25 | + | versions?: any[]; | |
| 26 | + | }) => { | |
| 27 | + | const received = direction === borogove.MessageDirection.MessageReceived; | |
| 28 | + | const from = received ? contact : account; | |
| 29 | + | const to = received ? account : contact; | |
| 30 | + | const builder = new borogove.ChatMessageBuilder({ | |
| 31 | + | localId, | |
| 32 | + | serverId, | |
| 33 | + | serverIdBy: serverId == null ? null : account, | |
| 34 | + | senderId: from, | |
| 35 | + | direction, | |
| 36 | + | type: borogove.MessageType.MessageCall, | |
| 37 | + | timeReceived, | |
| 38 | + | versions, | |
| 39 | + | payloads: [ | |
| 40 | + | borogove.Stanza.parse( | |
| 41 | + | `<${event} xmlns="urn:xmpp:jingle-message:0" id="${sid}"/>`, | |
| 42 | + | ), | |
| 43 | + | ], | |
| 44 | + | text: "call " + event, | |
| 45 | + | }); | |
| 46 | + | builder.sortId = sortId; | |
| 47 | + | builder.to = borogove.JID.parse(to); | |
| 48 | + | builder.from = borogove.JID.parse(from); | |
| 49 | + | builder.recipients = [builder.to]; | |
| 50 | + | builder.replyTo = [builder.from]; | |
| 51 | + | return builder.build(); | |
| 52 | + | }; | |
| 53 | + | } | |
| 54 | + | ||
| 4 | 55 | export function sharedPersistenceTests(test) { | |
| 5 | 56 | test("getMessagesByStatus filters and orders messages", async ({ | |
| 6 | 57 | borogove, |
| ... | ... | @@ -2382,49 +2433,7 @@ export function sharedPersistenceTests(test) { | |
| 2382 | 2433 | const sid = "ordering-outgoing-call"; | |
| 2383 | 2434 | const proposedAt = "2024-01-01T00:00:00.000Z"; | |
| 2384 | 2435 | const rejectedAt = "2024-01-01T00:00:01.000Z"; | |
| 2385 | - | const callMessage = ({ | |
| 2386 | - | direction, | |
| 2387 | - | event, | |
| 2388 | - | localId, | |
| 2389 | - | sortId, | |
| 2390 | - | timeReceived, | |
| 2391 | - | serverId, | |
| 2392 | - | versions, | |
| 2393 | - | }: { | |
| 2394 | - | direction: number; | |
| 2395 | - | event: string; | |
| 2396 | - | localId?: string; | |
| 2397 | - | sortId: string; | |
| 2398 | - | timeReceived: string; | |
| 2399 | - | serverId?: string; | |
| 2400 | - | versions?: any[]; | |
| 2401 | - | }) => { | |
| 2402 | - | const received = direction === borogove.MessageDirection.MessageReceived; | |
| 2403 | - | const from = received ? contact : account; | |
| 2404 | - | const to = received ? account : contact; | |
| 2405 | - | const builder = new borogove.ChatMessageBuilder({ | |
| 2406 | - | localId: localId ?? sid, | |
| 2407 | - | serverId: serverId ?? null, | |
| 2408 | - | serverIdBy: serverId == null ? null : account, | |
| 2409 | - | senderId: from, | |
| 2410 | - | direction, | |
| 2411 | - | type: borogove.MessageType.MessageCall, | |
| 2412 | - | timeReceived, | |
| 2413 | - | versions: versions ?? [], | |
| 2414 | - | payloads: [ | |
| 2415 | - | borogove.Stanza.parse( | |
| 2416 | - | `<${event} xmlns="urn:xmpp:jingle-message:0" id="${sid}"/>`, | |
| 2417 | - | ), | |
| 2418 | - | ], | |
| 2419 | - | text: "call " + event, | |
| 2420 | - | }); | |
| 2421 | - | builder.sortId = sortId; | |
| 2422 | - | builder.to = borogove.JID.parse(to); | |
| 2423 | - | builder.from = borogove.JID.parse(from); | |
| 2424 | - | builder.recipients = [builder.to]; | |
| 2425 | - | builder.replyTo = [builder.from]; | |
| 2426 | - | return builder.build(); | |
| 2427 | - | }; | |
| 2436 | + | const callMessage = createCallMessage(borogove, account, contact, sid); | |
| 2428 | 2437 | ||
| 2429 | 2438 | const propose = callMessage({ | |
| 2430 | 2439 | direction: borogove.MessageDirection.MessageSent, |
| ... | ... | @@ -2463,6 +2472,88 @@ export function sharedPersistenceTests(test) { | |
| 2463 | 2472 | borogove.MessageDirection.MessageSent, | |
| 2464 | 2473 | ); | |
| 2465 | 2474 | }); | |
| 2475 | + | ||
| 2476 | + | test("getMessagesBefore includes the full call log across directions", async ({ | |
| 2477 | + | borogove, | |
| 2478 | + | persistence, | |
| 2479 | + | }) => { | |
| 2480 | + | const account = "call-log-alice@example.com"; | |
| 2481 | + | const contact = "call-log-hatter@example.com"; | |
| 2482 | + | const sid = "call-log-sid"; | |
| 2483 | + | const callMessage = createCallMessage(borogove, account, contact, sid); | |
| 2484 | + | const callUpdate = ({ | |
| 2485 | + | direction, | |
| 2486 | + | event, | |
| 2487 | + | sortId, | |
| 2488 | + | timeReceived, | |
| 2489 | + | }: { | |
| 2490 | + | direction: number; | |
| 2491 | + | event: string; | |
| 2492 | + | sortId: string; | |
| 2493 | + | timeReceived: string; | |
| 2494 | + | }) => { | |
| 2495 | + | const serverId = `call-log-${event}-server-id`; | |
| 2496 | + | const version = callMessage({ | |
| 2497 | + | direction, | |
| 2498 | + | event, | |
| 2499 | + | localId: `call-log-${event}-stanza-id`, | |
| 2500 | + | serverId, | |
| 2501 | + | sortId, | |
| 2502 | + | timeReceived, | |
| 2503 | + | }); | |
| 2504 | + | return callMessage({ | |
| 2505 | + | direction, | |
| 2506 | + | event, | |
| 2507 | + | serverId, | |
| 2508 | + | sortId, | |
| 2509 | + | timeReceived, | |
| 2510 | + | versions: [version], | |
| 2511 | + | }); | |
| 2512 | + | }; | |
| 2513 | + | ||
| 2514 | + | await persistence.storeMessages(account, [ | |
| 2515 | + | callMessage({ | |
| 2516 | + | direction: borogove.MessageDirection.MessageSent, | |
| 2517 | + | event: "propose", | |
| 2518 | + | serverId: "call-log-propose-server-id", | |
| 2519 | + | sortId: "call-log-a0", | |
| 2520 | + | timeReceived: "2024-02-01T00:00:00.000Z", | |
| 2521 | + | }), | |
| 2522 | + | ]); | |
| 2523 | + | for (const update of [ | |
| 2524 | + | { | |
| 2525 | + | direction: borogove.MessageDirection.MessageReceived, | |
| 2526 | + | event: "ringing", | |
| 2527 | + | sortId: "call-log-a1", | |
| 2528 | + | timeReceived: "2024-02-01T00:00:01.000Z", | |
| 2529 | + | }, | |
| 2530 | + | { | |
| 2531 | + | direction: borogove.MessageDirection.MessageReceived, | |
| 2532 | + | event: "proceed", | |
| 2533 | + | sortId: "call-log-a2", | |
| 2534 | + | timeReceived: "2024-02-01T00:00:02.000Z", | |
| 2535 | + | }, | |
| 2536 | + | { | |
| 2537 | + | direction: borogove.MessageDirection.MessageSent, | |
| 2538 | + | event: "finish", | |
| 2539 | + | sortId: "call-log-a3", | |
| 2540 | + | timeReceived: "2024-02-01T00:00:03.000Z", | |
| 2541 | + | }, | |
| 2542 | + | ]) { | |
| 2543 | + | await persistence.storeMessages(account, [callUpdate(update)]); | |
| 2544 | + | } | |
| 2545 | + | ||
| 2546 | + | const messages = await persistence.getMessagesBefore(account, contact); | |
| 2547 | + | const call = messages.find((message) => message.localId === sid); | |
| 2548 | + | expect(call).toBeDefined(); | |
| 2549 | + | expect(call?.type).toBe(borogove.MessageType.MessageCall); | |
| 2550 | + | expect(call?.versions.map((version) => version.callStatus())).toEqual([ | |
| 2551 | + | "finish", | |
| 2552 | + | "proceed", | |
| 2553 | + | "ringing", | |
| 2554 | + | "propose", | |
| 2555 | + | ]); | |
| 2556 | + | }); | |
| 2466 | 2557 | } | |
| 2467 | 2558 | ||
| 2468 | 2559 | type TestKeyPair = { |