When initialized, or when expiration-related attributes change, expiring
messages will set timers to self-destruct. On self-destruct they trigger
'expired' events so that frontend listeners can clean up any collections
and views referencing them.
At startup, load all messages pending expiration so they can start their
timers even if they haven't been loaded in the frontend yet.
Todo: Remove expired conversation snippets from the left pane.
Move away from inline style attributes for setting contact colors.
Apply colors by name via css classes instead. Also lays groundwork
for syncing contact colors.
// FREEBIE
In some cases, we have already received a read receipt for an incoming
message by the time we go to create a notification about it. In this
case, we should skip the notification.
// FREEBIE
Fixes a session management problem where, after resolving a conflict
with some contact, that contact would get bad mac as a result of us
sending them a new prekey message before processing a pending conflicted
prekey message received from them earlier.
Fixes#806
// FREEBIE
In the case of a double send (same message encrypted and sent twice due
to key conflict bug), we would mark the first instance read twice rather
than marking both instances read. Fix by searching for matching messages
that have not yet been marked read.
// FREEBIE
When deleting all messages in a conversation, the entry in the left pane
should be inserted into the alphabetical portion of the list. To keep it
in this collection, do not nullify active_at.
To ensure the list view is keeping itself correctly sorted, make sure
that resorting behavior is triggered any time a relevant attribute is
changed.
This fixes deleted conversations jumping to the top of the list, and
conversation order scrambling when getting a group or contact sync
message from our master device.
Fixes#734
// FREEBIE
For messages that failed to send due to network errors, this change
allows retrying them directly from the main conversation view rather
than only from the message detail view.
// FREEBIE
Previously, if a message was sent in between the receive time of an
incoming message and the time it is actually added to the conversation's
message collection (which only occurs later after several async
callbacks), the incoming message would be inserted not-at-the-end of the
collection since it is ordered by receive time. This tricked the front
end into assuming the message was an older message instead of a new one.
Fixes#490
// FREEBIE
Remove individual messages from Notifications when marked read.
Previously this was only done from the conversation model when marking
the entire conversation as read.
Fixes#717
// FREEBIE
Don't save the change until we successfully process the message, but
make it first so that the user sees the error disappear when the new key
is accepted.
// FREEBIE
If the replay failed due to a bad mac or other decryption error for some
other reason we still want to clear the conflict. If it failed because
it's still in conflict then the newly returned error will reflect that
and be saved.
// FREEBIE
Since the introduction of infinite scroll, the delete messages function
has only deleted the currently loaded set of messages in a conversation.
To fix this, we should fetch all the messages and then delete them.
Fixes#645
// FREEBIE
Group updates in which nothing change should still display 'Updated the
group'. Previously they would display as empty message bubbles. Fixed by
ensuring that the 'group_update' attribute is set on the model, even if
it is an empty object.
// FREEBIE
Previously, when processing a backlog of sync messages and their
delivery receipts, we would fail to mark some messages as delivered even
though we got a receipt. This was due to an async race condition between
saving a sync message and fetching it after the receipt arrives.
Fix by re-ordering idb requests such that we save the message first and
fetch it after.
Fixes#479
// FREEBIE
Refactor libphonenumber.validateNumber into libphonenumber.parseNumber,
which encapsulates the try-catch pattern used in number parsing and
returns an object of info about the input number rather tha throwing
since we expect to get some invalid number inputs the user is typing.
In the conversation model,
* Separate phone number validation from search token updating.
* Perform token update before save if the number was valid.
* Stop storing unneeded number variants as conversation properties.
// FREEBIE
Search box finds or creates a conversation given a phone number in
local (to the user's region) or international format.
Previously you had to enter e164 format to set up the conversation
correctly.
If the number is not valid, do not open the conversation.
TODO: user feedback on invalid numbers.
// FREEBIE
Previously, libtextsecure would send a sync message automatically
when appropriate. This fails if any recipient has a key conflict
or if our network connection fails mid-send.
Instead, when appropriate, return a the DataMessage encoded as an array
buffer for later syncing. This lets the application choose when to send
it, which we now do after any successful send to a recipient, rather
than after all recipients are successfully sent to.
Eventually we should move the DataMessage protobuf construction and
group sending logic to the application layer entirely, in which case
we wouldn't need libtextsecure to construct the sync message either.
Fixes#408
Only load the most recent messages when initially rendering a
conversation. Scrolling to the top of a message list loads older
messages.
This required some slight refactoring of how we insert message elements
into the dom. If the message is added to the end of the collection,
append it at the end. Otherwise, assume it is an older message and
prepend it.
When adding elements to the top, reset the scrollPosition to its
previous distance from scrollHeight. This keeps the current set of
elements fixed in the viewport.
// FREEBIE
Create a cleaner seperation between generating notifications
and updating frontend conversation views. The former is now
handled by `conversation.notify` while the latter is achieved
by triggering an event on the conversation model, which will
only be acted on if there are any views listening for it.
Additionally, instead of re-fetching the entire message history,
which is overkill, just add or update the new/modified message.
This will help speed up the newmessage event handler and also
help avoid unnecessary re-rendering when resolving key conflicts.
// FREEBIE
Follow up to ddd2e67eb5
but for incoming messages.
* Conflict state sometimes failed to be removed even though the
conflict was resolved.
* Messages failed to re-render after a conflict. We want to
re-render only the error state on outgoing messages, to avoid
flickering attachments. On incoming messages, we need to call
render to populate the message text, avatar, etc...
// FREEBIE
Previously, with a mix of text and media messages in conflict,
asynchronous callbacks aligned so as to fail to remove some of
the conflict objects on messages.
Fix by serializing conflict processing, but making sure to move
on through the message list even if some conflict resolutions fail.
Fixes#370
// FREEBIE
For non text messages (ex: media messages and group updates), the
lastMessage field was being populated with empty string, resulting in an
empty message preview in the conversation list. Instead, display 'Media
message' or 'Updated the group', etc...
// FREEBIE
Using the search field produces a filtered view of all contacts and
groups containing the input. To make this fast and scalable, add an
index on a 'tokens' array containing words from the conversation name
and different forms of phone number.
Closes#365
// FREEBIE
An exception to the previous commit, for incoming messages we should not
show a mysterious empty bubble. Instead there is some generic
non-technical error message.
// FREEBIE
Change how message errors are rendered. Errors associated with a number
will be shown under that number in the detail view rather than piling up
in the message bubble.
// FREEBIE
Fix inconsistency in error format, where we sometimes get an unexpected
Error object and sometimes get a wrapper object containing an Error.
Also start saving network errors.
// FREEBIE
Refactor outgoing message error handling to use the same success and
error handlers. This creates a somewhat strange pattern, where we call
send and pass in the promise that resolves when sending is complete, but
there's enough variety in the libtextsecure syntax for different message
sending routines that it belongs at the conversation level and only the
post-processing stuff is really shared by all messages.
// FREEBIE
The conversation's contactCollection only contains references to the
current membership, and will not provide contact info for people who
have left the group, causing their messages to render without numbers or
avatars.
// FREEBIE
Only allow one notification at a time. Use a basic notification for
normal messages, and image notification for image messages, and a list
notification when there are multiple unread messages.
// FREEBIE
When resolving conflicts, we should not only discard the old key, but
set the new trusted key to the one the user has verified. Previously, we
would end up trusting the first-seen new key, which may not be the one
the user verified.
// FREEBIE
This trigger function uses chrome's runtime message passing api, which
traverses between different windows in our runtime, but we only trigger
the updateInbox event from the backgroud page, so we don't need to use
that api, which requires some extra cpu/memory overhead.
// FREEBIE
We're overriding the default with null often enough that we should
just change the default.
Consequently, no more phantom blank conversations with oneself should
appear after receiving a group update. They were being added to the
inbox because they were incorrectly initialized with an active_at value.
Fixes#281
This bug was caused by a race between indexeddb requests and sending
messages. Order of events to repro was roughly:
1. send async idb request for current message list
2. add new message(s)
3. idb request returns with now incomplete message list
4. message collection gets reset to list from 3, removing messages
added in 2, but not removing their phantom views/dom elements. (bug)
5. send another idb request for current message list
6. idb request returns bearing all messages including those from 2.
7. messages from 2 are added and rendered a second time.
The fix was simply to not remove messages in 4, which means we reuse the
original message model object rather than recreating it in 7.
Fixes#243
// FREEBIE
The unset function, and the series of events/callbacks triggered by its
use, are not as similar to the set/save functions as previously
anticipated, leading to flux in the state of the 'pending' attribute.
Fixes#283
// FREEBIE
Fixes#264
Implement the equivalent of java's String.hashCode on the conversation model.
Change avatar template and attributes. Use css classes for colors.
Update protobuf definitions and refactor message receive and decrypt
codepath to support new protocol, including various flavors of sync
messages (sent messages, contacts, and groups).
Also cleans up background.js and lets libtextsecure internalize
textsecure.processDecrypted and ensure that it is called before handing
DataMessages off to the application.
The Envelope structure now has a generic content field and a
legacyMessage field for backwards compatibility. We'll send outgoing
messages as legacy messages, and sync messages as "content" while
continuing to support both legacy and non-legacy messages on the receive
side until old clients have a chance to transition.
These collections should always be operating with the same model
instances, so let the inbox reset it self from the same in-memory
cache of conversation models used by the conversation windows.
In a multi device world, it's possible to receive a receipt for a sync
message before the sync message actually arrives. In this case we need
to keep the receipt around and the process it when the message shows up.
Clicking on a key conflict message opens the message detail view,
which displays the contact(s) in this conversation. If the message
contains a key conflict with any of these contacts, a button is
displayed which attempts to resolve that conflict and any other
conflicts in the conversation that are related to that contact.
We'd like to live in a world where we can retry all the pending
conflicts in a conversation as a batch, which means we don't want to
wipe the identity key before processing each message. Thus, remove that
step from these handlers and encapsulate in a method on the conversation
model.
Ensure that both tryAgain functions return promises, allowing the
application to take appropriate action in the result of success or
failure. This lets us remove all dependency from libtextsecure on
app-level constructs like message objects/ids and the `extenion.trigger`
function.
Corresponding frontend changes to follow in another commit.
Update unreadCounts per-conversation on incoming messages. Render unread
conversations with font-weigh: bold in the inbox view.
To ensure that the inbox and conversation views remain in sync, the
background page now ensures that the same models objects are used for
both views.
Sometimes a conversation's messages would be reverse-ordered on first
load, correcting themselves after a refresh. This is an artifact of the
order we load messages from the database. To fix, load them in the
opposite order.
The alternative solution would be to reset the collection every time we
fetch new messages, but this would create an entirely new set of model
objects each time, which seems unnecessary.
The message view has three flavors so far, a normal text+attachments
message, a group update, and an end session message. This changeset
extracts the normal message rendering into its own subview, and adds
some convenience functions to the message model in order to simplify
some of that flavoring logic.
Just display a sensible default in the frontend if it's unset.
For private conversations this should be the phone number, for
groups, the list of numbers.
Uses app-level timestamps for outgoing messages.
Adds timestamp property to the outgoing jsonData.
Triggers a runtime event to notify frontend on delivery receipts.
Renders delivered messages with a 'delivered' class.
This change removes the timestamp field from messages and conversations
in favor of multiple semantically named timestamp fields: sent_at,
received_at on messages; active_at on conversations. This requires/lets
us rethink and improve our indexing scheme thusly:
The inbox index on conversations will order entries by the
conversation.active_at property, which should only appear on
conversations destined for the inbox.
The receipt index will use the message.sent_at property, for effecient
lookup of outgoing messages by timestamp, for use in processing delivery
receipts.
The group index on conversation.members is multi-entry, meaning that
looking up any phone number in this index will efficiently yield all
groups the number belongs to.
The conversation index lets us scan messages in a single conversation,
in the order they were received (or the reverse order). It is a compound
index on [conversationId, received_at].
This was used to conditionally render messages in the group style, but
it's actually unnecessary. We can render the same markup in both cases
and change the appearance with css.
Move base64 encoding of attachments to an AttachmentView. This makes
image rendering an asynchronous task so we fire an update event to
indicate to the parent MessageListView that its content has changed
height and it is time to scroll down.
Register the runtime callback at the top level view rather than having
each conversation view register independently.
Also refactors Layout into InboxView.
After a message is saved asynchronsly, fire an event and pass the
message attributes to frontend listeners via the chrome-runtime API.
This behavior is similar to the 'storage' event fired by localStorage.
Getting up and running with IndexedDB was pretty easy, thanks to
backbone. The tricky part was making reads and writes asynchronous.
In that process I did some refactoring on Whisper.Threads, which
has been renamed Conversations for consistency with the view names.
This change also adds the unlimitedStorage permission.
Eliminates the global Whisper.Messages object and consolidates shared
send/receive logic in Whisper.Threads.
To the latter end, note that the decrypted array buffer on an attachment
pointer is now named data instead of decrypted, in order to match the
format of outgoing attachments presented by
FileReader.readAsArrayBuffers and let us use the same handler to base64
encode them.
Each conversation views now manages its own separate elements
rather than all binding to a shared #conversation element, and
similarly for message composition ui.
Also includes the beginnings of group creation UI (not working yet),
featuring bootstrap-tagsinput field for entering group recipients
When a thread is 'destroyed' from the UI we delete its messages and mark
the thread as inactive, (in other words, keep it around as contact info).
Additionally, we only load active threads when initializing the UI, and
reactivate threads when new messages are added to them.
Conflicts:
js/models/messages.js
js/models/threads.js
js/views/conversations/show.js
There were a few problems.
1. The message event was being triggered in background, not popup
2. The initial message/thread fetches from localStorage were mis-ordered
3. The timestamp wasn't being extracted from the right place
4. #3 caused messages to fail validation and not be saved
1-3 are fixed. To address 4 I switched validate() to log a warning
instead of preventing save.
Adds thread model/collection for managing conversation-level state, such
as unreadCounts, group membership, thread order, etc... plus various UI
improvements enabled by thread model, including an improved compose
flow, and thread-destroy button.
Adds Whisper.notify for presenting messages to the user in an orderly
fashion. Currently using a growl-style fade in/out effect.
Also some housekeeping:
Cut up views into separate files.
Partial fix for formatTimestamp.
Tweaked buttons and other styles.
The 'sender' field actually holds the recipient for outgoing
messages. Rename that field to 'person', indicating the 2nd
party generically.
Also decouples the thread name from thread recipients at the
view layer, in preparation for group support.
Adds Backbone-based Whisper.Messages model/collection with local storage
extension. Saves sent and received messages in Whisper.Messages instead
of message map. This will assign a unique id to the message and save it
to localStorage.
Adds Backbone-based view to popup.html
Automatically updates itself when new messages are saved to
Whisper.Messages db from the background page.
Added some shiny new styles, and started splitting up css into multiple
files for sanity's sake.