There’s another aspect of unread/inbox that is worth thinking about while we’re 
trying to solve this, I think, which is that many modern systems have two 
levels of unread - unread message, and unread message that triggers a 
notification (e.g. @Kev @everyone), and we probably want to know that. Yes, 
that probably means doing server-side reference following, and has potential 
implications for E2E, but is worth thinking about.

/K

> On 3 Jun 2019, at 15:31, Ненахов Андрей <[email protected]> 
> wrote:
> 
> On unread markers, having re-read the sent message, I think I wasn't clear 
> enough. What was meant is that the last read message ID will likely point to 
> a message that we don't yet have on device, and thus will be unable to 
> calculate, how many messages we missed. 
> 
> 
> 
> пн, 3 июн. 2019 г. в 19:26, Ненахов Андрей <[email protected] 
> <mailto:[email protected]>>:
> Ok, it took me some time to get to this. I think some preamble is necessary, 
> with explanation of our motives. Those who're just interested in stanza 
> format can skip to big TL;DR header below. 
> 
> The whole idea, and even utter necessity of such mechanics came to us when we 
> were preparing to make an XMPP client that is capable to work on iOS. As you 
> all know, basic XMPP works well enough when there is a persistent connection 
> with server. This persistent connection can be more or less maintained on 
> most modern OSs, even on Android (though it is more and more difficult, later 
> versions of Android really fight against any persistent processes). But with 
> iOS, there is no way other than to work fully offline, relying on push 
> notifications to wake up. 
> 
> Not only that, on iOS, an app has less than 30 seconds before it is shut 
> down, or brought into foreground by user. Thus, an iOS app has to quickly 
> connect, fetch all the necessary data, present, if necessary, all 
> notifications to a user, and prepare to go offline. 
> 
> The most direct approach to catch up what was missed is with offline 
> messages. It works more or less OK (more on that later) if a user is active 
> on a single device, but starts to fail utterly if user is trying to use 
> several devices simultaneously. Second approach is to fetch all messages from 
> an archive, between now and the latest message received from the server. This 
> *can* work more or less OK, if periods of disconnection are relatively brief. 
> (btw this is the method employed in current builds for Xabber for iOS). 
> However, there are scenarios when such approach will clearly fail. Most of 
> them revolve around device being offline for a significant amount of time and 
> a user being active on another device, sending and receiving multiple 
> messages in one conversation, 'burying' incoming unread message from another 
> conversation deeper than query to message archive.
> 
> To effectively tackle this problem we came up with an approach, essentially 
> orthogonal to what was tried with XMPP before: instead of an attempt to 
> re-establish a stream, catching up on what was missed, we're trying to 
> receive a slice of a current account state. 
> 
> This, naturally, leads to ideas about 'inbox' that are very alike to Dave's 
> proposal. True, with regular messages it is enough for us to build a list of 
> recent conversations and some delivered/read marker IDs. However, we do not 
> agree that unread message counter is not needed: if a client is doing a cold 
> sync, it'll only have the most recent message in a conversation, and won't 
> know how many are there until it loads the whole history up to that point. As 
> telegram channels show, there are often many thousands of unread messages in 
> some channels, and it is not a good idea to make clients load all those 
> messages only to present user a 4-digit number. 
> 
> There are two more cases that we think should be accounted for: VoIP calls 
> and Message Editing/Deleting/Retracting. 
> 
> First, in VoIP. We've stumbled in this just recently, when it became clear 
> that if a user is called via XEP-0353, and a message with session initiation 
> ends up in an archive, it can be quickly followed by subsequent messages, 
> even from a same contact, which will inevitably 'bury' call in some unfetched 
> unread messages and recipient device would never know that there was a call 
> (or that there is still a call in progress!)
> 
> Second, Message Editing (as we call it for now cause we didn't yet come up 
> with a proper name, thought we have a pretty nicely working implementation). 
> If you have 7 unread messages, 3 or them can very well be edits to your 
> previously received messages, and you'll never know that.
> 
> One more thing to consider: we don't think this will work well as a roster 
> extension. Users generally want to be able to access chat history even with 
> those who are already deleted from their rosters, and maybe even want to have 
> separate chat threads with single contact. Great example for this and most 
> obvious use is separating chats with e2e and without them into separate 
> chats, like Telegram or WhatsApp do. So, instead of roster we came up with 
> entity we call 'conversation', and last message, unread counter, message 
> edits and VoIP calls are property of that entity. 
> 
> To make this work, a server intercepts all passing stanzas, and creates a 
> slice of current user account's state, based on messages, chat markers, voip 
> session messages, retract/edit messages.
> Reconnecting client tells server the timestamp of the last message received 
> from the server (in our implementation all stanzas received from the server 
> are timestamped by the server, and clients rely on server time to be correct 
> for the needs of synchronization and message ordering), and server responds 
> with a list of conversations that was updated since that time. 
>  
> TL;DR
> Client initiates synchronization with sync request:
> 
> <iq type='get' id=’id2'>
>     <query xmlns='http://xabber.com/protocol/synchronization 
> <http://xabber.com/protocol/synchronization>' stamp=’1556111424456379’/>
> </iq>
> 
> Server responds with list of conversations updated since '1556111424456379':
> 
> <iq type=’result’ to=’[email protected]/Xabber-web 
> <http://[email protected]/Xabber-web>’ from=’redsolution.com 
> <http://redsolution.com/>’ id=’id1’>
>     <synchronization xmlns=’http://xabber.com/protocol/synchronization 
> <http://xabber.com/protocol/synchronization>’ stamp=’1556111424456980’>       
>      
>         <conversation jid=’[email protected] 
> <mailto:[email protected]>’ thread=’1asd123sd’ 
> stamp=’1556111424456980’>
>             <retract version='3'>
>             <unread count=’4’ after=’andrew_id_211’ />
>             <displayed id=’andrey_id_210’ />
>             <delivered id=’andrey_id_215’ />
>             <call>
>                 <propose xmlns='urn:xmpp:jingle-message:0' 
> id='a73sjjvkla37jfea'>
>                 <description xmlns='urn:xmpp:jingle:apps:rtp:1' 
> media='audio'/>
>                 </propose>
>             </call>
>             <last-message>
>                 <message from=’[email protected]/Xabber-web 
> <http://[email protected]/Xabber-web>’ 
> to=’[email protected]/Xabber-web 
> <http://[email protected]/Xabber-web>’ id=’andrew_id_214’>
>                     <body>Hi!</body>
>                     <stanza-id id=’id342’ by=’[email protected] 
> <mailto:[email protected]>’ />
>                 </message>
>             </last-message>
>         </conversation>
>         <set xmlns='http://jabber.org/protocol/rsm 
> <http://jabber.org/protocol/rsm>'>
>              <first index='0'>1556111424456980</first>
>              <last>1556111424456980</last>
>              <count>1</count>
>         </set>
>     </synchronization>
> </iq>
> 
> thread - name of a thread, or, 'conversation'. Same conversation should have 
> same thread, and default is nil, like, general converstion with a person
> displayed - AKA 'read' by remote chat partner
> deliverred - received by remote partner's client
> unread ... after - refers to an ID of a message of a last message read by user
> call - if there is an active call in progress, we pass all the propose 
> stanza, so client can immediately pick up the phone.
> retract - versions of edits in this convesation
> 
> 
> 
> In case of a cold start (like, first connection with new client), client just 
> asks for a synchoronization without timestamp, and receives full list of 
> conversations in return. 
> 
> 
> 
> 
> 
> 
> ср, 29 мая 2019 г. в 19:28, Dave Cridland <[email protected] 
> <mailto:[email protected]>>:
> 
> 
> On Wed, 29 May 2019 at 12:27, Ненахов Андрей <[email protected] 
> <mailto:[email protected]>> wrote:
> We have this (not exactly this, but for the very same purpose) mostly 
> implemented and already working, would be happy to share the results with 
> everyone. Currently, it's implemented on a server, client support (in Web 
> version of Xabber) will arrive in a week or two. We also plan to release an 
> open-source server (ejabberd fork) that will support this. 
> 
> Problem is, we definitely lack skills putting this as a 'XEP' formatted thing 
> with proper description, and, what's worse, current documentation is mostly 
> in Russian, but XMLs are in english and if someone would volunteer help us 
> putting it in a XEP-like way, I'll muster myself to translate crucial bits 
> into English. 
> 
> 
> Right - I knew you had this but had forgotten. It'd be great to collate all 
> these ideas and pick the best bits from them all.
> 
> Just a high-level technical sketch would be really useful.
>  
> ср, 29 мая 2019 г. в 16:12, Dave Cridland <[email protected] 
> <mailto:[email protected]>>:
> Having spent a while playing with - gasp! - non-XMPP based chat systems, I'm 
> quite taken with the notion that some kind of Inbox might be rather useful to 
> us.
> 
> Currently, there is Erlang Solutions (ESL)'s Inbox, which is essentially a 
> duplication of MAM with some chat state tracking. It's more than just that, 
> of course, but the essential concept I see is that it's a different, but 
> largely equivalent interface to MAM, with the concept of an unread counter 
> added.
> 
> Instagram, on the other hand, has no roster, as such, and its Inbox simply 
> lists (recent) conversations, in much the same way as a client might display 
> them. Each record contains the conversation's participants, and the most 
> recent message. Things like presence subscription, in the Instagram model, 
> are simply the open conversations.
> 
> We do have a roster, of course, and we're putting more things into it - MIX 
> channels, MUC Light in ESL's case, and so on.
> 
> This makes me wonder if the right way to design an Inbox is actually to 
> enhance our Roster with MAM and state awareness, and make it the conversation 
> information hub for IM.
> 
> Let's suppose that the nature of what is "unread" is equivalent across all 
> clients of a particular user, to begin with.
> 
> If a client requests the inbox "since" a particular point, it would then 
> receive a series of records:
> 
> First, a set of N records similar to a roster item, containing a jid, 
> subscription state, the last archive-id received in the conversation, and the 
> number of unacknowledged (by some definition) messages. Our roster also 
> includes groups and a name; we could also include the type (MUC, MIX, or a 
> user), the full message, etc - these are all optimisations.
> 
> We do not, actually, need the numbers of unread messages - a client seeing 
> that the last archive-id isn't in its cache knows the conversation has 
> messages that are unread to it, at least. But if we can track message read 
> state, that's useful for multi-device.
> 
> Finally, an update message to indicate the current point, where things 
> change. I would use an archive-id again here - even if the thing causing the 
> update isn't an archived message at all. This allows a client to ask for the 
> archive either since a particular message, or since an event.
> 
> You'll note I'm not building this directly on PubSub in the XEP-0060 (or 
> XEP-0163) sense - instead I'm proposing building this on the existing Roster 
> and MAM.
> 
> So:
> 
> <iq type='get' id='some-id'>
>   <query xmlns='jabber:iq:roster' ver='last-archive-id'>
>     <inbox xmlns='urn:xmpp:inbox'><!-- Add enhanced inbox info -->
>       <messages/><!-- Include the entire last message? -->
>       <unread/><!-- Include unread count -->
>     </inbox>
>    </query>
> </iq>
> 
> I'd dearly love to return updates using <message/>, MAM-style, actually. But 
> let's say we stick with the roster design, the pushes look like:
> 
> <iq from='[email protected]' id='b2gs90j5' to='[email protected]/home' 
> type='set'>
>      <query xmlns='jabber:iq:roster' ver='ver42'>
>        <item jid='[email protected]' subscription='both'>
>           <unread count='4'/>
>           <stanza-id id='ver42' xmlns='...'/>
>           <message ...>
>                <body>Yeah, sure - whenever you like.</body>
>           </message>
>      </query>
>    </iq>
> 
> Any message "counts" for updating the roster version, by dint of unifying the 
> namespace of stanza-id (for archive) and roster version. So any new message 
> arriving updates the current point of the roster as well, and any new change 
> in the roster changes the archive pointer similarly.
> 
> Updating the shared unread count could be by Carbonizing the read-receipts 
> (or similar), and using the stanza-id from that, or by an explicit roster 
> push. Either's good - I think the roster pushes are more explicit, which is 
> helpful.
> 
> I'm fully expecting some push-back here. Comments are, of course, welcome.
> 
> Dave.
> _______________________________________________
> Standards mailing list
> Info: https://mail.jabber.org/mailman/listinfo/standards 
> <https://mail.jabber.org/mailman/listinfo/standards>
> Unsubscribe: [email protected] 
> <mailto:[email protected]>
> _______________________________________________
> 
> 
> -- 
> Andrew Nenakhov
> CEO, Redsolution, Inc.
> https://redsolution.com 
> <http://www.redsolution.com/>_______________________________________________
> Standards mailing list
> Info: https://mail.jabber.org/mailman/listinfo/standards 
> <https://mail.jabber.org/mailman/listinfo/standards>
> Unsubscribe: [email protected] 
> <mailto:[email protected]>
> _______________________________________________
> _______________________________________________
> Standards mailing list
> Info: https://mail.jabber.org/mailman/listinfo/standards 
> <https://mail.jabber.org/mailman/listinfo/standards>
> Unsubscribe: [email protected] 
> <mailto:[email protected]>
> _______________________________________________
> 
> 
> -- 
> Andrew Nenakhov
> CEO, Redsolution, Inc.
> https://redsolution.com <http://www.redsolution.com/>
> 
> -- 
> Andrew Nenakhov
> CEO, Redsolution, Inc.
> https://redsolution.com 
> <http://www.redsolution.com/>_______________________________________________
> Standards mailing list
> Info: https://mail.jabber.org/mailman/listinfo/standards 
> <https://mail.jabber.org/mailman/listinfo/standards>
> Unsubscribe: [email protected] 
> <mailto:[email protected]>
> _______________________________________________

_______________________________________________
Standards mailing list
Info: https://mail.jabber.org/mailman/listinfo/standards
Unsubscribe: [email protected]
_______________________________________________

Reply via email to