| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-09-28 18:55:59 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2026-09-30 18:16:02 UTC |
| parent | b800d5df38bc5abda136a90a278d605a6a519593 |
| borogove/Chat.hx | +5 | -3 |
| borogove/ChatMessage.hx | +18 | -6 |
| borogove/ChatMessageBuilder.hx | +26 | -11 |
| borogove/ChatMessageCombiner.hx | +4 | -3 |
| borogove/Client.hx | +4 | -4 |
| borogove/Importer.hx | +4 | -4 |
| borogove/Message.hx | +9 | -10 |
| borogove/MessageSync.hx | +6 | -6 |
| borogove/ModerationAction.hx | +5 | -3 |
| borogove/Notification.hx | +1 | -1 |
| borogove/calls/Session.hx | +0 | -1 |
| borogove/persistence/IDB.js | +36 | -14 |
| borogove/persistence/Sqlite.hx | +47 | -26 |
| test/TestChatMessageCombiner.hx | +1 | -1 |
| test/TestClient.hx | +5 | -5 |
| test/TestImporter.hx | +4 | -4 |
| test/TestSortId.hx | +1 | -1 |
| test/TestSqlite.hx | +31 | -28 |
| test/persistence-factories.ts | +2 | -2 |
| test/persistence-tests.ts | +30 | -30 |
diff --git a/borogove/Chat.hx b/borogove/Chat.hx index 7aa5406..4082999 100644 --- a/borogove/Chat.hx +++ b/borogove/Chat.hx @@ -1274,7 +1274,8 @@ class DirectChat extends Chat { } private function prepareOutgoingMessage(message:ChatMessageBuilder) { - message.timestamp = message.timestamp ?? Date.format(std.Date.now()); + message.timeSent = message.timeSent ?? Date.format(std.Date.now()); + message.timeReceived = Date.format(std.Date.now()); message.direction = MessageSent; message.from = client.jid; message.sender = message.from.asBare(); @@ -1970,7 +1971,7 @@ class Channel extends Chat { } // Sort by time so that eg edits go into the past - dedupedMessages.sort((x, y) -> Reflect.compare(x.timestamp, y.timestamp)); + dedupedMessages.sort((x, y) -> Reflect.compare(x.timeReceived, y.timeReceived)); final readIndex = dedupedMessages.findLastIndex((m) -> m.serverId == readUpToId || !m.isIncoming()); if (readIndex < 0) { @@ -2348,7 +2349,8 @@ class Channel extends Chat { private function prepareOutgoingMessage(message:ChatMessageBuilder) { message.type = MessageChannel; - message.timestamp = message.timestamp ?? Date.format(std.Date.now()); + message.timeSent = message.timeSent ?? Date.format(std.Date.now()); + message.timeReceived = Date.format(std.Date.now()); message.direction = MessageSent; message.from = client.jid; message.senderId = self?.id ?? getFullJid().asString(); // TODO: what if we aren't joined and self is null and this is an occupant id room? diff --git a/borogove/ChatMessage.hx b/borogove/ChatMessage.hx index 22ad453..de53f3f 100644 --- a/borogove/ChatMessage.hx +++ b/borogove/ChatMessage.hx @@ -193,9 +193,19 @@ class ChatMessage { private final replyId : Null<String>; /** - The timestamp of this message, in format YYYY-MM-DDThh:mm:ss[.sss]Z + The timestamp this message was sent, in format YYYY-MM-DDThh:mm:ss[.sss]Z + + If the exact time is not known, the received time may be used + **/ + public final timeSent: String; + + /** + The timestamp this message was received, in format YYYY-MM-DDThh:mm:ss[.sss]Z + + This will be the time received by our server, if known, or the time + received at the client otherwise. **/ - public final timestamp: String; + public final timeReceived: String; @:allow(borogove) private final to: JID; @@ -311,7 +321,8 @@ class ChatMessage { ?type: MessageType, ?syncPoint: Bool, ?replyId: Null<String>, - timestamp: String, + timeSent: String, + timeReceived: String, to: JID, from: JID, senderId: String, @@ -340,7 +351,8 @@ class ChatMessage { this.type = params.type ?? MessageChat; this.syncPoint = params.syncPoint ?? false; this.replyId = params.replyId; - this.timestamp = params.timestamp; + this.timeSent = params.timeSent; + this.timeReceived = params.timeReceived; this.to = params.to; this.from = params.from; this.senderId = params.senderId; @@ -564,11 +576,11 @@ class ChatMessage { **/ public function callDuration(): Null<String> { if (versions.length < 2) return null; - final startedStr = versions[versions.length - 1].timestamp; + final startedStr = versions[versions.length - 1].timeReceived; return switch (callStatus()) { case "finish": - final endedStr = versions[0].timestamp; + final endedStr = versions[0].timeReceived; if (startedStr == null || endedStr == null) return null; final started = DateTime.fromString(startedStr); final ended = DateTime.fromString(endedStr); diff --git a/borogove/ChatMessageBuilder.hx b/borogove/ChatMessageBuilder.hx index fe9aee9..a79d138 100644 --- a/borogove/ChatMessageBuilder.hx +++ b/borogove/ChatMessageBuilder.hx @@ -59,9 +59,19 @@ class ChatMessageBuilder { private var replyId: Null<String> = null; /** - The timestamp of this message, in format YYYY-MM-DDThh:mm:ss[.sss]+00:00 + The timestamp this message was sent, in format YYYY-MM-DDThh:mm:ss[.sss]Z + + If the exact time is not known, the received time may be used + **/ + public var timeSent: Null<String> = null; + + /** + The timestamp this message was received, in format YYYY-MM-DDThh:mm:ss[.sss]Z + + This will be the time received by our server, if known, or the time + received at the client otherwise. **/ - public var timestamp: Null<String> = null; + public var timeReceived: Null<String> = null; @:allow(borogove) private var to: Null<JID> = null; @@ -176,7 +186,8 @@ class ChatMessageBuilder { ?type: MessageType, ?syncPoint: Bool, ?replyId: Null<String>, - ?timestamp: String, + ?timeSent: String, + ?timeReceived: String, ?senderId: String, ?replyToMessage: Null<ChatMessage>, ?threadId: Null<String>, @@ -199,7 +210,8 @@ class ChatMessageBuilder { this.type = params?.type ?? MessageChat; this.syncPoint = params?.syncPoint ?? false; this.replyId = params?.replyId; - this.timestamp = params?.timestamp; + this.timeSent = params?.timeSent; + this.timeReceived = params?.timeReceived; this.senderId = params?.senderId; this.replyToMessage = params?.replyToMessage; this.threadId = params?.threadId; @@ -232,7 +244,8 @@ class ChatMessageBuilder { builder.type = m.type; builder.syncPoint = m.syncPoint; builder.replyId = m.replyId; - builder.timestamp = m.timestamp; + builder.timeSent = m.timeSent; + builder.timeReceived = m.timeReceived; builder.to = m.to; builder.from = m.from; builder.senderId = m.senderId; @@ -257,7 +270,7 @@ class ChatMessageBuilder { } @:allow(borogove) - private static function makeModerated(m: ChatMessage, timestamp: String, moderatorId: Null<String>, reason: Null<String>) { + private static function makeModerated(m: ChatMessage, timeSent: String, timeReceived: String, moderatorId: Null<String>, reason: Null<String>) { final builder = new ChatMessageBuilder(); builder.localId = m.localId; builder.serverId = m.serverId; @@ -266,7 +279,8 @@ class ChatMessageBuilder { builder.type = m.type; builder.syncPoint = m.syncPoint; builder.replyId = m.replyId; - builder.timestamp = m.timestamp; + builder.timeSent = m.timeSent; + builder.timeReceived = m.timeReceived; builder.to = m.to; builder.from = m.from; builder.senderId = m.senderId; @@ -279,13 +293,13 @@ class ChatMessageBuilder { builder.direction = m.direction; builder.status = m.status; final cleanedStub = builder.build(); - final payload = new Stanza("retracted", { xmlns: "urn:xmpp:message-retract:1", stamp: timestamp }); + final payload = new Stanza("retracted", { xmlns: "urn:xmpp:message-retract:1", stamp: timeSent }); if (reason != null) payload.textTag("reason", reason); payload.tag("moderated", { by: moderatorId, xmlns: "urn:xmpp:message-moderate:1" }).up(); builder.payloads.push(payload); - builder.timestamp = timestamp; + builder.timeSent = timeSent; + builder.timeReceived = timeReceived; builder.versions = [builder.build(), cleanedStub]; - builder.timestamp = m.timestamp; return builder.build(); } @@ -405,7 +419,8 @@ class ChatMessageBuilder { type: type, syncPoint: syncPoint, replyId: replyId, - timestamp: timestamp ?? Date.format(std.Date.now()), + timeReceived: timeReceived ?? Date.format(std.Date.now()), + timeSent: timeSent ?? timeReceived ?? Date.format(std.Date.now()), to: to, from: from, senderId: senderId, diff --git a/borogove/ChatMessageCombiner.hx b/borogove/ChatMessageCombiner.hx index be89ab2..4ef7d23 100644 --- a/borogove/ChatMessageCombiner.hx +++ b/borogove/ChatMessageCombiner.hx @@ -37,7 +37,8 @@ class ChatMessageCombiner { builder.serverId = original.serverId; builder.serverIdBy = original.serverIdBy; builder.sortId = original.sortId; - builder.timestamp = original.timestamp; + builder.timeSent = original.timeSent; + builder.timeReceived = original.timeReceived; builder.replyId = original.replyId; builder.reactions = original.reactions; if (original.type == MessageCall) { @@ -63,14 +64,14 @@ class ChatMessageCombiner { (version, uniqueVersions:Map<String, ChatMessage>) -> { final id = identity(version); final previous = uniqueVersions[id]; - if (previous == null || version.timestamp > previous.timestamp) { + if (previous == null || version.timeReceived > previous.timeReceived) { uniqueVersions.set(id, version); } uniqueVersions; }, new Map<String, ChatMessage>() ).map(version -> version); - sortedVersions.sort((a, b) -> Reflect.compare(b.timestamp, a.timestamp)); + sortedVersions.sort((a, b) -> Reflect.compare(b.timeReceived, a.timeReceived)); return sortedVersions; } diff --git a/borogove/Client.hx b/borogove/Client.hx index 641f5c7..bc39a66 100644 --- a/borogove/Client.hx +++ b/borogove/Client.hx @@ -1533,7 +1533,7 @@ class Client extends EventEmitter { persistence.removeMedia(hash.algorithm, hash.hash); } } - moderateMessage = ChatMessageBuilder.makeModerated(moderateMessage, action.timestamp, action.moderatorId, action.reason); + moderateMessage = ChatMessageBuilder.makeModerated(moderateMessage, action.timeSent, action.timeReceived, action.moderatorId, action.reason); persistence.updateMessage(accountId(), moderateMessage); resolve(moderateMessage); }) @@ -1989,7 +1989,7 @@ class Client extends EventEmitter { private function sortChats() { chats.sort((a, b) -> { if (a.uiState == b.uiState) { - final tcompare = -Reflect.compare(a.lastMessage?.timestamp ?? "0", b.lastMessage?.timestamp ?? "0"); + final tcompare = -Reflect.compare(a.lastMessage?.timeReceived ?? "0", b.lastMessage?.timeReceived ?? "0"); if (tcompare != 0) return tcompare; return Reflect.compare(a.getDisplayName(), b.getDisplayName()); } else { @@ -2047,7 +2047,7 @@ class Client extends EventEmitter { persistence.getMessagesByStatus(accountId(), MessagePending).then(messages -> { for (m in messages.filter(m -> m.type != MessageCall)) { - if (m.timestamp < tooOld) { + if (m.timeSent < tooOld) { persistence.updateMessageStatus(accountId(), m.localId, MessageFailedToSend, "Message too old to auto-resend").then(newM -> { notifyMessageHandlers(newM, StatusEvent); }); @@ -2060,7 +2060,7 @@ class Client extends EventEmitter { stanza.textTag("delay", "Resending after reconnect", { xmlns: "urn:xmpp:delay", from: accountId(), - stamp: m.timestamp + stamp: m.timeSent }); // TODO: don't know if was originally send with OMEMO chat.sendMessageStanza(stanza); diff --git a/borogove/Importer.hx b/borogove/Importer.hx index f3961e8..3d5d861 100644 --- a/borogove/Importer.hx +++ b/borogove/Importer.hx @@ -146,10 +146,10 @@ class Importer { final sortLower = sortA; sortA = FractionalIndexing.between(sortLower, sortB, FractionalIndexing.BASE_95_DIGITS); final serverReceivedAt = resultStanza.findText("{urn:xmpp:forward:0}forwarded/{urn:xmpp:delay}delay@stamp"); - var timestamp = serverReceivedAt; - if (timestamp != null) { + var timeReceived = serverReceivedAt; + if (timeReceived != null) { // If no subseconds, fix them to at least sort right - timestamp = ~/([0-9][0-9]:[0-9][0-9]:[0-9][0-9])(\.[0-9][0-9][0-9])?/.map(timestamp, (ereg) -> { + timeReceived = ~/([0-9][0-9]:[0-9][0-9]:[0-9][0-9])(\.[0-9][0-9][0-9])?/.map(timeReceived, (ereg) -> { if (ereg.matched(2) == null || ereg.matched(2) == ".000") { if (ereg.matched(1) == previousMessageTime) { counterSameTime++; @@ -179,7 +179,7 @@ class Importer { }; builder.serverId = resultStanza.attr.get("id"); builder.serverIdBy = source; - if (timestamp != null && builder.timestamp == null) builder.timestamp = timestamp; + if (timeReceived != null && builder.timeReceived == null) builder.timeReceived = timeReceived; return builder; }); diff --git a/borogove/Message.hx b/borogove/Message.hx index b3c7408..064ad16 100644 --- a/borogove/Message.hx +++ b/borogove/Message.hx @@ -74,7 +74,7 @@ class Message { var msg = new ChatMessageBuilder(); msg.stanza = stanza; - msg.timestamp = stanza.findText("{urn:xmpp:delay}delay@stamp"); + msg.timeSent = stanza.findText("{urn:xmpp:delay}delay@stamp"); msg.threadId = stanza.getChildText("thread"); msg.lang = stanza.attr.get("xml:lang"); msg.text = stanza.getChildText("body"); @@ -247,8 +247,7 @@ class Message { } if (addContext != null) msg = addContext(msg, stanza); - final timestamp = msg.timestamp ?? Date.format(std.Date.now()); - msg.timestamp = timestamp; + final timeSent = msg.timeSent ?? msg.timeReceived ?? Date.format(std.Date.now()); if (jmi != null && jmi.name != "accept" && jmi.name != "propose") { msg.versions = [msg.build()]; @@ -267,8 +266,8 @@ class Message { isGroupchat ? null : reactionId, msg.chatId(), msg.senderId ?? throw "no sender", - timestamp, - reactions.map(text -> new Reaction(msg.senderId ?? throw "no sender", timestamp, text, msg.localId)), + timeSent, + reactions.map(text -> new Reaction(msg.senderId ?? throw "no sender", timeSent, text, msg.localId)), EmojiReactions )), encryptionInfo); } @@ -286,7 +285,7 @@ class Message { msg.chatId(), msg.senderId, msg.threadId, - ModerateMessageStanza(new ModerationAction(msg.chatId(), moderateServerId, timestamp, by, reason)), + ModerateMessageStanza(new ModerationAction(msg.chatId(), moderateServerId, timeSent, msg.timeReceived ?? Date.format(std.Date.now()), by, reason)), encryptionInfo ); } @@ -336,8 +335,8 @@ class Message { isGroupchat ? null : replyToID, msg.chatId(), msg.senderId ?? throw "no sender", - timestamp, - [new Reaction(msg.senderId ?? throw "no sender", timestamp, text.trim(), msg.localId)], + timeSent, + [new Reaction(msg.senderId ?? throw "no sender", timeSent, text.trim(), msg.localId)], AppendReactions )), encryptionInfo); } @@ -356,8 +355,8 @@ class Message { isGroupchat ? null : replyToID, msg.chatId(), msg.senderId ?? throw "no sender", - timestamp, - [new CustomEmojiReaction(msg.senderId ?? throw "no sender", timestamp, els[0].attr.get("alt") ?? "", hash.serializeUri(), msg.localId)], + timeSent, + [new CustomEmojiReaction(msg.senderId ?? throw "no sender", timeSent, els[0].attr.get("alt") ?? "", hash.serializeUri(), msg.localId)], AppendReactions )), encryptionInfo); } diff --git a/borogove/MessageSync.hx b/borogove/MessageSync.hx index 098ab7e..fbf6cc5 100644 --- a/borogove/MessageSync.hx +++ b/borogove/MessageSync.hx @@ -87,12 +87,12 @@ class MessageSync { final sortLower = sortA; sortA = FractionalIndexing.between(sortLower, sortB, FractionalIndexing.BASE_95_DIGITS); final serverReceivedAt = result.findText("{urn:xmpp:forward:0}forwarded/{urn:xmpp:delay}delay@stamp"); - var timestamp = serverReceivedAt; - if (timestamp == null) { + var timeReceived = serverReceivedAt; + if (timeReceived == null) { trace("MAM result with no timestamp", result); } else { // If no subseconds, fix them to at least sort right - timestamp = ~/([0-9][0-9]:[0-9][0-9]:[0-9][0-9])(\.[0-9][0-9][0-9])?/.map(timestamp, (ereg) -> { + timeReceived = ~/([0-9][0-9]:[0-9][0-9]:[0-9][0-9])(\.[0-9][0-9][0-9])?/.map(timeReceived, (ereg) -> { if (ereg.matched(2) == null || ereg.matched(2) == ".000") { if (ereg.matched(1) == previousMessageTime) { counterSameTime++; @@ -136,7 +136,7 @@ class MessageSync { builder.serverId = result.attr.get("id"); builder.serverIdBy = serviceJID; builder.encryption = decryptionResult.encryptionInfo; - if (timestamp != null && builder.timestamp == null) builder.timestamp = timestamp; + if (timeReceived != null && builder.timeReceived == null) builder.timeReceived = timeReceived; return contextHandler(builder, stanza); }); }, (err) -> { @@ -155,7 +155,7 @@ class MessageSync { }; builder.serverId = result.attr.get("id"); builder.serverIdBy = serviceJID; - if (timestamp != null && builder.timestamp == null) builder.timestamp = timestamp; + if (timeReceived != null && builder.timeReceived == null) builder.timeReceived = timeReceived; return contextHandler(builder, stanza); }, new EncryptionInfo(DecryptionFailure, NS.OMEMO, "OMEMO", "internal-error", Std.string(err)) @@ -181,7 +181,7 @@ class MessageSync { }; builder.serverId = result.attr.get("id"); builder.serverIdBy = serviceJID; - if (timestamp != null && builder.timestamp == null) builder.timestamp = timestamp; + if (timeReceived != null && builder.timeReceived == null) builder.timeReceived = timeReceived; return contextHandler(builder, stanza); }); diff --git a/borogove/ModerationAction.hx b/borogove/ModerationAction.hx index 18b438f..bf9ecdc 100644 --- a/borogove/ModerationAction.hx +++ b/borogove/ModerationAction.hx @@ -3,14 +3,16 @@ package borogove; class ModerationAction { public final chatId: String; public final moderateServerId: String; - public final timestamp: String; + public final timeSent: String; + public final timeReceived: String; public final moderatorId: Null<String>; public final reason: Null<String>; - public function new(chatId: String, moderateServerId: String, timestamp: String, moderatorId: Null<String>, reason: Null<String>) { + public function new(chatId: String, moderateServerId: String, timeSent: String, timeReceived: String, moderatorId: Null<String>, reason: Null<String>) { this.chatId = chatId; this.moderateServerId = moderateServerId; - this.timestamp = timestamp; + this.timeSent = timeSent; + this.timeReceived = timeReceived; this.moderatorId = moderatorId; this.reason = reason; } diff --git a/borogove/Notification.hx b/borogove/Notification.hx index ec88e23..0d115d8 100644 --- a/borogove/Notification.hx +++ b/borogove/Notification.hx @@ -98,7 +98,7 @@ class Notification { m.callSid(), imageUri, m.lang, - m.timestamp + m.timeSent ); } diff --git a/borogove/calls/Session.hx b/borogove/calls/Session.hx index 6f3f38e..8d9bff7 100644 --- a/borogove/calls/Session.hx +++ b/borogove/calls/Session.hx @@ -61,7 +61,6 @@ private function mkCallMessage(to: JID, client: Client, event: Stanza) { m.replyTo = [m.sender]; m.direction = MessageSent; m.setBody(Html.text("call " + event.name)); - m.timestamp = Date.format(std.Date.now()); m.payloads.push(event); m.localId = ID.unique(); if (event.name != "propose") { diff --git a/borogove/persistence/IDB.js b/borogove/persistence/IDB.js index 97eb0d6..79aaf5b 100644 --- a/borogove/persistence/IDB.js +++ b/borogove/persistence/IDB.js @@ -454,6 +454,12 @@ export default async (dbname, media, tokenize, stemmer) => { return newMap; } + function toISOString(dateOrNothing) { + if (!dateOrNothing) return dateOrNothing; + + return dateOrNothing.toISOString(); + } + function hydrateMessageSync(value) { if (!value) return null; @@ -467,7 +473,8 @@ export default async (dbname, media, tokenize, stemmer) => { message.direction = value.direction; message.status = value.status; message.statusText = value.statusText; - message.timestamp = value.timestamp && value.timestamp.toISOString(); + message.timeSent = toISOString(value.timeSent || value.timestamp); + message.timeReceived = toISOString(value.timeReceived || value.timestamp); message.from = value.from && borogove_JID.parse(value.from); message.sender = value.sender && borogove_JID.parse(value.sender); message.senderId = value.senderId; @@ -489,7 +496,7 @@ export default async (dbname, media, tokenize, stemmer) => { ); message.debug = value.debug ?? null; message.linkMetadata = value.linkMetadata ?? []; - message.reactions = hydrateReactions(value.reactions, message.timestamp); + message.reactions = hydrateReactions(value.reactions, message.timeSent); message.text = value.text; message.lang = value.lang; message.type = @@ -572,7 +579,9 @@ export default async (dbname, media, tokenize, stemmer) => { senderId: message.senderId, recipients: message.recipients.map((r) => r.asString()), replyTo: message.replyTo.map((r) => r.asString()), - timestamp: new Date(message.timestamp), + timeReceived: undefined, // We store it as timestamp for now + timestamp: new Date(message.timeReceived), + timeSent: new Date(message.timeSent), replyToMessage: message.replyToMessage && [ account, message.replyToMessage.serverId || "", @@ -620,7 +629,8 @@ export default async (dbname, media, tokenize, stemmer) => { head.localId = outer.localId; head.replyId = outer.replyId; // Edited version is not newer - head.timestamp = new Date(outer.timestamp); + head.timestamp = outer.timestamp ?? new Date(outer.timeReceived); + head.timeSent = new Date(outer.timeSent); head.sortId = outer.sortId; head.versions = versions; head.reactions = outer.reactions; // Preserve these, edit doesn't touch them @@ -1351,7 +1361,7 @@ export default async (dbname, media, tokenize, stemmer) => { reactionResult.value.localId, message.chatId(), message.senderId, - message.timestamp, + message.timeSent, reactions, enums.borogove_ReactionUpdateKind.CompleteReactions, ), @@ -1487,7 +1497,7 @@ export default async (dbname, media, tokenize, stemmer) => { .openCursor( IDBKeyRange.bound( [account, chatId], - [account, chatId, new Date(before.timestamp)], + [account, chatId, new Date(before.timeReceived)], ), "prev", ) @@ -1507,7 +1517,7 @@ export default async (dbname, media, tokenize, stemmer) => { ); if (messages.length > 0 && messages[0].serverIdBy === chatId) { - const earliest = new Date(messages[messages.length - 1].timestamp); + const earliest = new Date(messages[messages.length - 1].timeReceived); const tx = db.transaction(["messages"], "readonly"); const store = tx.objectStore("messages"); const pmCursor = store @@ -1515,7 +1525,7 @@ export default async (dbname, media, tokenize, stemmer) => { .openCursor( IDBKeyRange.bound( [account, chatId], - [account, chatId, before ? new Date(before.timestamp) : []], + [account, chatId, before ? new Date(before.timeReceived) : []], ), "prev", ); @@ -1537,7 +1547,9 @@ export default async (dbname, media, tokenize, stemmer) => { const pms = await Promise.all(promisePMs); for (const pm of pms) { - const idx = messages.findIndex((m) => m.timestamp <= pm.timestamp); + const idx = messages.findIndex( + (m) => m.timeReceived <= pm.timeReceived, + ); if (idx >= 0) messages.splice(idx, 0, pm); } } @@ -1553,7 +1565,7 @@ export default async (dbname, media, tokenize, stemmer) => { const bound = after ? [ after?.type === enums.borogove_MessageType.MessageChannelPrivate - ? new Date(after.timestamp) + ? new Date(after.timeReceived) : after.sortId, ] : []; @@ -1572,14 +1584,18 @@ export default async (dbname, media, tokenize, stemmer) => { ); if (messages.length > 0 && messages[0].serverIdBy === chatId) { - const latest = new Date(messages[messages.length - 1].timestamp); + const latest = new Date(messages[messages.length - 1].timeReceived); const tx = db.transaction(["messages"], "readonly"); const store = tx.objectStore("messages"); const pmCursor = store .index("chats") .openCursor( IDBKeyRange.bound( - [account, chatId, ...(after ? [new Date(after.timestamp)] : [])], + [ + account, + chatId, + ...(after ? [new Date(after.timeReceived)] : []), + ], [account, chatId, []], ), "next", @@ -1604,7 +1620,9 @@ export default async (dbname, media, tokenize, stemmer) => { const pms = await Promise.all(promisePMs); for (const pm of pms) { - const idx = messages.findLastIndex((m) => m.timestamp < pm.timestamp); + const idx = messages.findLastIndex( + (m) => m.timeReceived < pm.timeReceived, + ); if (idx >= 0) messages.splice(idx + 1, 0, pm); } } @@ -1720,7 +1738,11 @@ export default async (dbname, media, tokenize, stemmer) => { } return result.sort((a, b) => - a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0, + a.timeReceived < b.timeReceived + ? -1 + : a.timeReceived > b.timeReceived + ? 1 + : 0, ); }, diff --git a/borogove/persistence/Sqlite.hx b/borogove/persistence/Sqlite.hx index 83a4b71..4d6d677 100644 --- a/borogove/persistence/Sqlite.hx +++ b/borogove/persistence/Sqlite.hx @@ -374,6 +374,18 @@ class Sqlite implements Persistence implements KeyValueStore { "PRAGMA user_version = 23"]); } return Promise.resolve(null); + }).then(_ -> { + if (version < 24) { + return exec(["DROP INDEX messages_created_at", + "DROP INDEX messages_status", + "ALTER TABLE messages RENAME COLUMN created_at TO time_sent", + "ALTER TABLE messages ADD COLUMN time_received INTEGER", // should be NOT NULL ideally + "CREATE INDEX messages_time_received ON messages (account_id, chat_id, time_received)", + "CREATE INDEX messages_status ON messages (account_id, status, time_received)", + "UPDATE messages SET time_received=time_sent", // They're all NOT NULL now but sqlite won't let us change it + "PRAGMA user_version = 24"]); + } + return Promise.resolve(null); }); }); }); @@ -924,9 +936,9 @@ class Sqlite implements Persistence implements KeyValueStore { var q = "WITH page AS (SELECT stanza_id, mam_id FROM messages where account_id=? AND chat_id=? AND (stanza_id IS NULL OR stanza_id='' OR stanza_id=correction_id) AND type<>?"; final params: Array<Dynamic> = [accountId, chatId, MessageChannelPrivate]; if (useTimestamp) { - q += " AND messages.created_at " + op + " unixepoch(?, 'subsec') * 1000"; + q += " AND messages.time_received " + op + " unixepoch(?, 'subsec') * 1000"; params.push(timestamp); - q += " ORDER BY messages.created_at"; + q += " ORDER BY messages.time_received"; } else if (sortId != null) { q += " AND messages.sort_id " + op + " ?"; params.push(sortId); @@ -949,7 +961,7 @@ class Sqlite implements Persistence implements KeyValueStore { '; q += " ORDER BY messages.sort_id"; if (op == "<" || op == "<=") q += " DESC"; - q += ", messages.created_at"; + q += ", messages.time_received"; if (op == "<" || op == "<=") q += " DESC"; params.push(accountId); @@ -959,7 +971,7 @@ class Sqlite implements Persistence implements KeyValueStore { return db.exec(q, params).then(result -> { final messages = hydrateMessages(accountId, result); if (messages.length > 0 && messages[0].serverIdBy == chatId) { - final boundary = messages[messages.length - 1].timestamp; + final boundary = messages[messages.length - 1].timeReceived; var pmQ = ' SELECT ${messageColumnsString()} @@ -969,18 +981,18 @@ class Sqlite implements Persistence implements KeyValueStore { final pmParams: Array<Dynamic> = [accountId, chatId, MessageChannelPrivate]; if (timestamp != null) { - pmQ += " AND messages.created_at " + op + " unixepoch(?, 'subsec') * 1000"; + pmQ += " AND messages.time_received " + op + " unixepoch(?, 'subsec') * 1000"; pmParams.push(timestamp); } if (op == "<" || op == "<=") { - pmQ += " AND messages.created_at > unixepoch(?, 'subsec') * 1000"; + pmQ += " AND messages.time_received > unixepoch(?, 'subsec') * 1000"; pmParams.push(boundary); } else { // We don't want to limit by timestamp in SQL because we want any PMs coming after the end but before the next message } - pmQ += " ORDER BY messages.created_at"; + pmQ += " ORDER BY messages.time_received"; if (op == "<" || op == "<=") q += " DESC"; pmQ += " LIMIT 50"; @@ -988,19 +1000,19 @@ class Sqlite implements Persistence implements KeyValueStore { final pms = hydrateMessages(accountId, pmResult); for (pm in pms) { if (op == "<" || op == "<=") { - final idx = messages.findIndex(m -> m.timestamp <= pm.timestamp); + final idx = messages.findIndex(m -> m.timeReceived <= pm.timeReceived); if (idx >= 0) messages.insert(idx, pm); } else { var idx = messages.length - 1; while (idx >= 0) { - if (messages[idx].timestamp < pm.timestamp) { + if (messages[idx].timeReceived < pm.timeReceived) { break; } idx--; } if (idx >= 0) messages.insert(idx + 1, pm); - if (pm.timestamp > boundary) break; + if (pm.timeReceived > boundary) break; } } return messages; @@ -1017,20 +1029,20 @@ class Sqlite implements Persistence implements KeyValueStore { @HaxeCBridge.noemit public function getMessagesBefore(accountId: String, chatId: String, before: Null<ChatMessage>): Promise<Array<ChatMessage>> { - return getMessages(accountId, chatId, before?.sortId, "<", before?.type == MessageChannelPrivate, before?.timestamp); + return getMessages(accountId, chatId, before?.sortId, "<", before?.type == MessageChannelPrivate, before?.timeReceived); } @HaxeCBridge.noemit public function getMessagesAfter(accountId: String, chatId: String, after: Null<ChatMessage>): Promise<Array<ChatMessage>> { - return getMessages(accountId, chatId, after?.sortId, ">", after?.type == MessageChannelPrivate, after?.timestamp); + return getMessages(accountId, chatId, after?.sortId, ">", after?.type == MessageChannelPrivate, after?.timeReceived); } @HaxeCBridge.noemit public function getMessagesAround(accountId: String, around: ChatMessage): Promise<Array<ChatMessage>> { final chatId = around.chatId(); return thenshim.PromiseTools.all([ - getMessages(accountId, chatId, around.sortId, "<", around?.type == MessageChannelPrivate, around?.timestamp), - getMessages(accountId, chatId, around.sortId, ">=", around?.type == MessageChannelPrivate, around?.timestamp) + getMessages(accountId, chatId, around.sortId, "<", around?.type == MessageChannelPrivate, around?.timeReceived), + getMessages(accountId, chatId, around.sortId, ">=", around?.type == MessageChannelPrivate, around?.timeReceived) ]).then(results -> results.flatten()); } @@ -1042,7 +1054,7 @@ class Sqlite implements Persistence implements KeyValueStore { ${messageColumnsString()} FROM messages WHERE account_id=? AND status=? - ORDER BY created_at + ORDER BY time_received ', [accountId, status] ).then(result -> hydrateMessagesAndLoadMeta(accountId, result)); @@ -1464,7 +1476,8 @@ class Sqlite implements Persistence implements KeyValueStore { accountId: String, rows: Iterator<{ stanza: String, - timestamp: String, + time_sent: String, + time_received: String, direction: MessageDirection, type: MessageType, status: MessageStatus, @@ -1503,7 +1516,8 @@ class Sqlite implements Persistence implements KeyValueStore { accountId: String, rows: Iterator<{ stanza: String, - timestamp: String, + time_sent: String, + time_received: String, direction: MessageDirection, type: MessageType, status: MessageStatus, @@ -1523,7 +1537,8 @@ class Sqlite implements Persistence implements KeyValueStore { final accountJid = JID.parse(accountId); return { iterator: () -> rows }.map(row -> ChatMessage.fromStanza(Stanza.parse(row.stanza), accountJid, (builder, _) -> { builder.syncPoint = row.sync_point != 0; - builder.timestamp = row.timestamp; + builder.timeSent = row.time_sent; + builder.timeReceived = row.time_received; builder.type = row.type; builder.status = row.status; builder.statusText = row.status_text; @@ -1541,7 +1556,8 @@ class Sqlite implements Persistence implements KeyValueStore { if (row.stanza_id != null && row.stanza_id != "") builder.localId = row.stanza_id; if (row.versions != null) { final versions: DynamicAccess<{ - timestamp: String, + time_sent: String, + time_received: String, sort_id: String, stanza: String, encryption: Dynamic, @@ -1552,7 +1568,8 @@ class Sqlite implements Persistence implements KeyValueStore { for (versionId => version in versions) { final versionM = ChatMessage.fromStanza(Stanza.parse(version.stanza), accountJid, (toPushB, _) -> { if (toPushB.serverId == null && versionId != toPushB.localId) toPushB.serverId = versionId; - toPushB.timestamp = version.timestamp; + toPushB.timeSent = version.time_sent; + toPushB.timeReceived = version.time_received; toPushB.sortId = version.sort_id; toPushB.debug = version.debug; return toPushB; @@ -1562,7 +1579,7 @@ class Sqlite implements Persistence implements KeyValueStore { builder.versions.push(toPush); } } - builder.versions.sort((a, b) -> Reflect.compare(b.timestamp, a.timestamp)); + builder.versions.sort((a, b) -> Reflect.compare(b.timeReceived, a.timeReceived)); } } return builder; @@ -1813,7 +1830,8 @@ class Sqlite implements Persistence implements KeyValueStore { col("type"), col("status"), col("status_text"), - col("timestamp", "strftime('%FT%H:%M:%fZ', created_at / 1000.0, 'unixepoch') AS timestamp"), + col("time_sent", "strftime('%FT%H:%M:%fZ', time_sent / 1000.0, 'unixepoch') AS time_sent"), + col("time_received", "strftime('%FT%H:%M:%fZ', time_received / 1000.0, 'unixepoch') AS time_received"), col("sender_id"), col("mam_id"), col("mam_by"), @@ -1839,7 +1857,8 @@ class Sqlite implements Persistence implements KeyValueStore { ELSE versions.mam_id END, json_object( - 'timestamp', strftime('%FT%H:%M:%fZ', versions.created_at / 1000.0, 'unixepoch'), + 'time_sent', strftime('%FT%H:%M:%fZ', versions.time_sent / 1000.0, 'unixepoch'), + 'time_received', strftime('%FT%H:%M:%fZ', versions.time_received / 1000.0, 'unixepoch'), 'sort_id', versions.sort_id, 'stanza', versions.stanza, 'encryption', json(versions.encryption), @@ -1850,13 +1869,14 @@ class Sqlite implements Persistence implements KeyValueStore { messages.type, messages.status, messages.status_text, - strftime('%FT%H:%M:%fZ', messages.created_at / 1000.0, 'unixepoch') AS timestamp, + strftime('%FT%H:%M:%fZ', messages.time_sent / 1000.0, 'unixepoch') AS time_sent, + strftime('%FT%H:%M:%fZ', messages.time_received / 1000.0, 'unixepoch') AS time_received, messages.sender_id, messages.mam_id, messages.mam_by, messages.sort_id, messages.sync_point, - MAX(versions.created_at), + MAX(versions.time_received), json(versions.encryption) AS encryption, json(versions.debug) AS debug, versions.stanza"; @@ -1883,7 +1903,8 @@ class Sqlite implements Persistence implements KeyValueStore { insertCol("sync_point", (m) -> m.syncPoint), insertCol("chat_id", (m) -> m.chatId()), insertCol("sender_id", (m) -> m.senderId), - insertCol("created_at", (m) -> originalMessage(m).timestamp, "CAST(unixepoch(?, 'subsec') * 1000 AS INTEGER)"), + insertCol("time_sent", (m) -> originalMessage(m).timeSent, "CAST(unixepoch(?, 'subsec') * 1000 AS INTEGER)"), + insertCol("time_received", (m) -> originalMessage(m).timeReceived, "CAST(unixepoch(?, 'subsec') * 1000 AS INTEGER)"), insertCol("status", (m) -> originalMessage(m).status), insertCol("direction", (m) -> originalMessage(m).direction), insertCol("type", (m) -> originalMessage(m).type), diff --git a/test/TestChatMessageCombiner.hx b/test/TestChatMessageCombiner.hx index 0a7f468..1459cff 100644 --- a/test/TestChatMessageCombiner.hx +++ b/test/TestChatMessageCombiner.hx @@ -116,7 +116,7 @@ class TestChatMessageCombiner extends utest.Test { builder.senderId = params.senderId ?? "alice@example.com"; builder.type = params.type ?? MessageChat; builder.versions = params.versions ?? []; - builder.timestamp = params.timestamp; + builder.timeReceived = params.timestamp; builder.sortId = params.sortId; builder.setBody(borogove.Html.text(params.body ?? "message")); return builder.build(); diff --git a/test/TestClient.hx b/test/TestClient.hx index 026b659..8b0fa6e 100644 --- a/test/TestClient.hx +++ b/test/TestClient.hx @@ -142,10 +142,10 @@ class TestClient extends utest.Test { client.checkForResends(); } - private function pendingMessage(localId: String, timestamp: String): ChatMessage { + private function pendingMessage(localId: String, timeSent: String): ChatMessage { final builder = new ChatMessageBuilder(); builder.localId = localId; - builder.timestamp = timestamp; + builder.timeSent = timeSent; builder.senderId = "test@example.com"; builder.from = JID.parse("test@example.com"); builder.to = JID.parse("friend@example.com"); @@ -311,7 +311,7 @@ class TestClient extends utest.Test { final originalMessage = builder.build(); persistence.storeMessages(client.accountId(), [originalMessage]).then((_) -> { - final action = new ModerationAction(chatId, serverId, "2023-01-01T00:00:00Z", "mod@example.com", "Spam"); + final action = new ModerationAction(chatId, serverId, "2023-01-01T00:00:00Z", "2023-01-01T00:00:00Z", "mod@example.com", "Spam"); client.moderateMessage(action).then((moderatedMessage) -> { Assert.notNull(moderatedMessage); @@ -337,7 +337,7 @@ class TestClient extends utest.Test { final originalMessage = builder.build(); persistence.storeMessages(client.accountId(), [originalMessage]).then((_) -> { - final action = new ModerationAction(chatId, serverId, "2023-01-01T00:00:00Z", "mod@example.com", null); + final action = new ModerationAction(chatId, serverId, "2023-01-01T00:00:00Z", "2023-01-01T00:00:00Z", "mod@example.com", null); client.moderateMessage(action).then((moderatedMessage) -> { Assert.notNull(moderatedMessage); @@ -364,7 +364,7 @@ class TestClient extends utest.Test { builder.to = JID.parse("test@example.com"); builder.senderId = "alice@example.com"; builder.text = "hello"; - builder.timestamp = "2023-01-01T00:00:00Z"; + builder.timeReceived = "2023-01-01T00:00:00Z"; builder.sortId = "1"; builder.direction = MessageReceived; final originalMessage = builder.build(); diff --git a/test/TestImporter.hx b/test/TestImporter.hx index c876b79..83d2f85 100644 --- a/test/TestImporter.hx +++ b/test/TestImporter.hx @@ -83,7 +83,7 @@ class TestImporter extends utest.Test { Assert.equals("Hello Romeo", chatMsg.body().toPlainText()); Assert.equals("mam-id-1", chatMsg.serverId); Assert.equals("juliet@capulet.com", chatMsg.serverIdBy); - Assert.equals("2023-10-26T09:00:00Z", chatMsg.timestamp); + Assert.equals("2023-10-26T09:00:00Z", chatMsg.timeSent); Assert.equals("2023-10-27T10:00:00Z", chatMsg.debug.serverReceivedAt); Assert.equals("import", chatMsg.debug.source); Assert.isFalse(chatMsg.isIncoming()); @@ -169,15 +169,15 @@ class TestImporter extends utest.Test { Assert.equals(3, items.length); switch (items[0].parsed) { - case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:00.001Z", m.timestamp); + case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:00.001Z", m.timeReceived); default: Assert.fail(); } switch (items[1].parsed) { - case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:00.002Z", m.timestamp); + case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:00.002Z", m.timeReceived); default: Assert.fail(); } switch (items[2].parsed) { - case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:01.001Z", m.timestamp); + case ChatMessageStanza(m): Assert.equals("2023-10-27T10:00:01.001Z", m.timeReceived); default: Assert.fail(); } } diff --git a/test/TestSortId.hx b/test/TestSortId.hx index 9f016d1..79c8427 100644 --- a/test/TestSortId.hx +++ b/test/TestSortId.hx @@ -142,7 +142,7 @@ class TestSortId extends utest.Test { Assert.isTrue("a " < m1.sortId, "\"a \" < m1.sortId"); Assert.isTrue(m1.sortId < m2.sortId, "m1.sortId < m2.sortId"); Assert.isTrue(m2.sortId < "b00", "m2.sortId < \"b00\""); - Assert.isTrue(m1.timestamp < m2.timestamp, "m1.timestamp < m2.timestamp"); // fake fractional part + Assert.isTrue(m1.timeReceived < m2.timeReceived, "m1.timeReceived < m2.timeReceived"); // fake fractional part Assert.equals("mam", m1.debug.source); Assert.equals("2023-01-01T00:00:00Z", m1.debug.serverReceivedAt); Assert.equals("2023-01-01T00:00:00Z", m2.debug.serverReceivedAt); diff --git a/test/TestSqlite.hx b/test/TestSqlite.hx index 7eb75ef..7e76a97 100644 --- a/test/TestSqlite.hx +++ b/test/TestSqlite.hx @@ -152,7 +152,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:01Z"; + builder.timeReceived = "2020-01-01T00:00:01Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -164,7 +164,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:00Z"; + builder2.timeReceived = "2020-01-01T00:00:00Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -176,7 +176,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "a0"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -208,7 +208,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:01Z"; + builder.timeReceived = "2020-01-01T00:00:01Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -220,7 +220,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:00Z"; + builder2.timeReceived = "2020-01-01T00:00:00Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -232,7 +232,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "Z~"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -244,7 +244,7 @@ class TestSqlite extends utest.Test { builder4.senderId = "teaparty@example.com/hatter"; builder4.direction = MessageReceived; builder4.type = MessageChannel; - builder4.timestamp = "2020-01-01T00:00:04Z"; + builder4.timeReceived = "2020-01-01T00:00:04Z"; builder4.sortId = "c0"; builder4.to = JID.parse("alice@example.com"); builder4.from = JID.parse("teaparty@example.com/hatter"); @@ -277,7 +277,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:00Z"; + builder.timeReceived = "2020-01-01T00:00:00Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -289,7 +289,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:01Z"; + builder2.timeReceived = "2020-01-01T00:00:01Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -301,7 +301,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "Z~"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -313,7 +313,7 @@ class TestSqlite extends utest.Test { builder4.senderId = "teaparty@example.com/hatter"; builder4.direction = MessageReceived; builder4.type = MessageChannel; - builder4.timestamp = "2020-01-01T00:00:04Z"; + builder4.timeReceived = "2020-01-01T00:00:04Z"; builder4.sortId = "c0"; builder4.to = JID.parse("alice@example.com"); builder4.from = JID.parse("teaparty@example.com/hatter"); @@ -345,7 +345,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:00Z"; + builder.timeReceived = "2020-01-01T00:00:00Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -357,7 +357,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:01Z"; + builder2.timeReceived = "2020-01-01T00:00:01Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -369,7 +369,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "a1"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -401,7 +401,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:01Z"; + builder.timeReceived = "2020-01-01T00:00:01Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -413,7 +413,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:00Z"; + builder2.timeReceived = "2020-01-01T00:00:00Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -425,7 +425,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "Z~"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -437,7 +437,7 @@ class TestSqlite extends utest.Test { builder4.senderId = "teaparty@example.com/hatter"; builder4.direction = MessageReceived; builder4.type = MessageChannel; - builder4.timestamp = "2020-01-01T00:00:04Z"; + builder4.timeReceived = "2020-01-01T00:00:04Z"; builder4.sortId = "c0"; builder4.to = JID.parse("alice@example.com"); builder4.from = JID.parse("teaparty@example.com/hatter"); @@ -470,7 +470,7 @@ class TestSqlite extends utest.Test { builder.senderId = "teaparty@example.com/hatter"; builder.direction = MessageReceived; builder.type = MessageChannel; - builder.timestamp = "2020-01-01T00:00:00Z"; + builder.timeReceived = "2020-01-01T00:00:00Z"; builder.sortId = "a0"; builder.to = JID.parse("alice@example.com"); builder.from = JID.parse("teaparty@example.com/hatter"); @@ -482,7 +482,7 @@ class TestSqlite extends utest.Test { builder2.senderId = "teaparty@example.com/hatter"; builder2.direction = MessageReceived; builder2.type = MessageChannel; - builder2.timestamp = "2020-01-01T00:00:01Z"; + builder2.timeReceived = "2020-01-01T00:00:01Z"; builder2.sortId = "b0"; builder2.to = JID.parse("alice@example.com"); builder2.from = JID.parse("teaparty@example.com/hatter"); @@ -494,7 +494,7 @@ class TestSqlite extends utest.Test { builder3.senderId = "teaparty@example.com/hatter"; builder3.direction = MessageReceived; builder3.type = MessageChannelPrivate; - builder3.timestamp = "2020-01-01T00:00:03Z"; + builder3.timeReceived = "2020-01-01T00:00:03Z"; builder3.sortId = "Z~"; builder3.to = JID.parse("alice@example.com"); builder3.from = JID.parse("teaparty@example.com/hatter"); @@ -506,7 +506,7 @@ class TestSqlite extends utest.Test { builder4.senderId = "teaparty@example.com/hatter"; builder4.direction = MessageReceived; builder4.type = MessageChannel; - builder4.timestamp = "2020-01-01T00:00:04Z"; + builder4.timeReceived = "2020-01-01T00:00:04Z"; builder4.sortId = "c0"; builder4.to = JID.parse("alice@example.com"); builder4.from = JID.parse("teaparty@example.com/hatter"); @@ -706,9 +706,9 @@ class TestSqlite extends utest.Test { }); persistence.storeMessages(account, [message]).then(_ -> { - return persistence.db.exec("SELECT strftime('%FT%H:%M:%fZ', created_at / 1000.0, 'unixepoch') AS timestamp FROM messages WHERE mam_id=?", [message.serverId]); + return persistence.db.exec("SELECT strftime('%FT%H:%M:%fZ', time_received / 1000.0, 'unixepoch') AS time_received FROM messages WHERE mam_id=?", [message.serverId]); }).then(rows -> { - Assert.equals("2020-01-01T00:00:00.123Z", rows.next().timestamp); + Assert.equals("2020-01-01T00:00:00.123Z", rows.next().time_received); async.done(); }).catchError(e -> { Assert.fail(Std.string(e)); @@ -781,7 +781,7 @@ class TestSqlite extends utest.Test { builder.senderId = senderId; builder.direction = params.received ?? false ? MessageReceived : MessageSent; builder.sortId = params.sortId ?? params.localId ?? params.serverId ?? "message"; - builder.timestamp = params.timestamp; + builder.timeReceived = params.timestamp; builder.syncPoint = params.syncPoint ?? false; builder.status = params.status ?? MessagePending; builder.versions = params.versions ?? []; @@ -2065,7 +2065,8 @@ class TestSqlite extends utest.Test { "type", "status", "status_text", - "strftime('%FT%H:%M:%fZ', created_at / 1000.0, 'unixepoch') AS timestamp", + "strftime('%FT%H:%M:%fZ', time_sent / 1000.0, 'unixepoch') AS time_sent", + "strftime('%FT%H:%M:%fZ', time_received / 1000.0, 'unixepoch') AS time_received", "sender_id", "mam_id", "mam_by", @@ -2089,7 +2090,8 @@ class TestSqlite extends utest.Test { "type", "status", "status_text", - "strftime('%FT%H:%M:%fZ', created_at / 1000.0, 'unixepoch') AS timestamp", + "strftime('%FT%H:%M:%fZ', time_sent / 1000.0, 'unixepoch') AS time_sent", + "strftime('%FT%H:%M:%fZ', time_received / 1000.0, 'unixepoch') AS time_received", "sender_id", "mam_id", "mam_by", @@ -2117,7 +2119,8 @@ class TestSqlite extends utest.Test { "type", "status", "status_text", - "strftime('%FT%H:%M:%fZ', created_at / 1000.0, 'unixepoch') AS timestamp", + "strftime('%FT%H:%M:%fZ', time_sent / 1000.0, 'unixepoch') AS time_sent", + "strftime('%FT%H:%M:%fZ', time_received / 1000.0, 'unixepoch') AS time_received", "sender_id", "mam_id", "mam_by", diff --git a/test/persistence-factories.ts b/test/persistence-factories.ts index 53cb915..0963be2 100644 --- a/test/persistence-factories.ts +++ b/test/persistence-factories.ts @@ -53,13 +53,13 @@ export function createFactories(borogove: any) { builder.replyTo = replyTo ?? [fromJID]; builder.versions = versions ?? []; builder.text = text ?? faker.lorem.sentence(); - builder.timestamp = timestamp?.toISOString() ?? new Date().toISOString(); + builder.timeReceived = timestamp?.toISOString() ?? new Date().toISOString(); return builder.build(); }; const corrections = (original: any, props: CorrectionProps[]) => { - const originalTimestamp = new Date(original.timestamp).getTime(); + const originalTimestamp = new Date(original.timeReceived).getTime(); return props.map(({ text }, index) => { const versionTimestamp = new Date(originalTimestamp + index + 1); diff --git a/test/persistence-tests.ts b/test/persistence-tests.ts index a35a3e7..6c2ace1 100644 --- a/test/persistence-tests.ts +++ b/test/persistence-tests.ts @@ -13,7 +13,7 @@ export function sharedPersistenceTests(test) { senderId: account, direction: borogove.MessageDirection.MessageSent, }); - builder.timestamp = timestamp; + builder.timeReceived = timestamp; builder.sortId = localId; builder.status = status; builder.to = borogove.JID.parse("hatter@example.com"); @@ -441,7 +441,7 @@ export function sharedPersistenceTests(test) { localId: "encrypted-original", senderId: account, direction: 1, - timestamp: "2026-08-26T12:00:00Z", + timeReceived: "2026-08-26T12:00:00Z", }); originalBuilder.sortId = "encrypted-c0"; originalBuilder.text = "Original encrypted text"; @@ -463,7 +463,7 @@ export function sharedPersistenceTests(test) { localId: "encrypted-correction", senderId: account, direction: 1, - timestamp: "2026-08-26T12:01:00Z", + timeReceived: "2026-08-26T12:01:00Z", }); correctionBuilder.sortId = "encrypted-c0"; correctionBuilder.text = "Corrected encrypted text"; @@ -527,7 +527,7 @@ export function sharedPersistenceTests(test) { localId: "correction-sort-original", senderId: chatId, direction: 0, - timestamp: "2026-08-26T12:00:00Z", + timeReceived: "2026-08-26T12:00:00Z", }); originalBuilder.sortId = "b0"; originalBuilder.text = "Original text"; @@ -543,7 +543,7 @@ export function sharedPersistenceTests(test) { localId: "correction-sort-correction", senderId: chatId, direction: 0, - timestamp: "2026-08-26T12:01:00Z", + timeReceived: "2026-08-26T12:01:00Z", }); correctionBuilder.sortId = "b0"; correctionBuilder.text = "Corrected text"; @@ -581,7 +581,7 @@ export function sharedPersistenceTests(test) { localId: "correction-sort-original", senderId: chatId, direction: 0, - timestamp: "2026-08-26T12:00:00Z", + timeReceived: "2026-08-26T12:00:00Z", }); originalBuilder.sortId = "b0"; originalBuilder.text = "Original text"; @@ -597,7 +597,7 @@ export function sharedPersistenceTests(test) { localId: "correction-sort-correction", senderId: chatId, direction: 0, - timestamp: "2026-08-26T12:01:00Z", + timeReceived: "2026-08-26T12:01:00Z", }); correctionBuilder.sortId = "b1"; correctionBuilder.text = "Corrected text"; @@ -752,7 +752,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -765,7 +765,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -778,7 +778,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "a0"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -810,7 +810,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -823,7 +823,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -836,7 +836,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "Z~"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -849,7 +849,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:04Z", + timeReceived: "2020-01-01T00:00:04Z", }); builder4.sortId = "c0"; builder4.to = borogove.JID.parse("alice@example.com"); @@ -880,7 +880,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -893,7 +893,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -906,7 +906,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "Z~"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -919,7 +919,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:04Z", + timeReceived: "2020-01-01T00:00:04Z", }); builder4.sortId = "c0"; builder4.to = borogove.JID.parse("alice@example.com"); @@ -952,7 +952,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -965,7 +965,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -978,7 +978,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "a1"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -1010,7 +1010,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -1023,7 +1023,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -1036,7 +1036,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "Z~"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -1049,7 +1049,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:04Z", + timeReceived: "2020-01-01T00:00:04Z", }); builder4.sortId = "c0"; builder4.to = borogove.JID.parse("alice@example.com"); @@ -1080,7 +1080,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:00Z", + timeReceived: "2020-01-01T00:00:00Z", }); builder.sortId = "a0"; builder.to = borogove.JID.parse("alice@example.com"); @@ -1093,7 +1093,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:01Z", + timeReceived: "2020-01-01T00:00:01Z", }); builder2.sortId = "b0"; builder2.to = borogove.JID.parse("alice@example.com"); @@ -1106,7 +1106,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannelPrivate, - timestamp: "2020-01-01T00:00:03Z", + timeReceived: "2020-01-01T00:00:03Z", }); builder3.sortId = "Z~"; builder3.to = borogove.JID.parse("alice@example.com"); @@ -1119,7 +1119,7 @@ export function sharedPersistenceTests(test) { senderId: "teaparty@example.com/hatter", direction: 0, type: borogove.MessageType.MessageChannel, - timestamp: "2020-01-01T00:00:04Z", + timeReceived: "2020-01-01T00:00:04Z", }); builder4.sortId = "c0"; builder4.to = borogove.JID.parse("alice@example.com"); @@ -2156,7 +2156,7 @@ export function sharedPersistenceTests(test) { { text: "First correction" }, { text: "Second correction" }, ]) - .sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); + .sort((a, b) => new Date(b.timeReceived) - new Date(a.timeReceived)); const [stored, ...restStored] = await persistence.storeMessages( account,