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.
If all the application windows are closed (and not merely hidden), the
background page will go inactive and there's nothing we can do to stop
it. However, we can ask chrome to trigger an alarm once per minute,
which will spin up the background page and check for new messages.
This will effectively keep us alive as long as chrome has open windows
or is running in the background, subject to chrome settings'
Advanced -> System -> Continue running background apps
As a chrome packaged app, we have to keep at least one window open in
order to maintain our websocket connection in the background page.
This change replaces the system window frame with custom buttons in the
inbox header, such that the 'close' button merely hides the window
rather than unloading it.
Fixes#237
FREEBIE
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.
My current version of chromium inexplicably exposes a crippled version
of chrome.browserAction even though we are now a packaged app and should
not have that functionality exposed to us anymore. This results in some
errors to the tune of "property 'foo' of undefined".
It also doesn't support the innerBounds property for window creation,
only the older (deprecated) bounds property.
Also make it accessible by providing a mode argument to the install
function. Previously developers could just edit the url but we no longer
have the address bar as an app window, so now they must close the
default installer and run the following from the background page
console: `extension.install('standalone')`.
In the production build, this should result in an error since it is not
supported / the register page is not included there.
Appify tabs, windows, browserAction
Port the extension.windows.focus function to new window api and
generalize its error handling in the case where the requested window
does not exist. An error will be passed to the callback.
Port extension.browserAction and rename it to the more generic
extension.onLaunched.
Use of the id option when opening a window ensures that attempting to
open a duplicate window merely focuses the existing window.
Finally, after registration, close the options window and open the
inbox.
Port extension.remove
Add window.storage to the background page, which loads all data from the
'items' store in indexeddb, caching them in memory for synchronous
access, then override textsecure storage to use that in memory store.
Storing multiple sessions in a single indexeddb record is prone to
clobbering data due to races between requests to update multiple device
sessions for the same number, since you have to read the current state
of the device->session map and update it. Splitting the records up makes
it so that those updates can be made in parallel. Selecting all the
sessions for a given number can still be done efficiently thanks to
indexeddb range queries.
* Session records are now opaque strings, so treat them that way:
- no more cross checking identity key and session records
- Move hasOpenSession to axolotl wrapper
- Remote registration ids must be fetched async'ly via protocol wrapper
* Implement async AxolotlStore using textsecure.storage
* Add some db stores and move prekeys and signed keys to indexeddb
* Add storage tests
* Rename identityKey storage key from libaxolotl25519KeyidentityKey to
simply identityKey, since it's no longer hardcoded in libaxolotl
* Rework registration and key-generation, keeping logic in libtextsecure
and rendering in options.js.
* Remove key_worker since workers are handled at the libaxolotl level
now
Encapsulate the websocket resources and socket setup process in a
friendly OO class. The MessageReceiver constructor expects an instance
of EventTarget on which to fire message events asynchronously. The
provider of the EventTarget can then add/remove listeners as desired.
Ground work for a smoother registration flow. Overall UX still needs
some polish but at least now we can have a progress gif or animation or
whatever. Also adds the phonenumber-confirmation step as a simple alert
box, which will be replaced with a nice dialogue in a later commit.
Creating a group with a member who's identity key has changed would
previously fail silently. Now, we catch and save the error, allowing the
same conflict resolution process as with regular messages.
Fixes#205
Previously there was a long pause between confirming the group details
and opening the conversation. Fix by first saving/opening the
conversation, rather than waiting for the initial group update to finish
transmitting.
This reverts commit 31e7d285e3.
This seemed like a nice feature, but the popup bubble isn't very
conducive to nontrivial user inputs, e.g. file inputs.
Fixes#211
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.
1. Return the value returned by the registered function, to expose the
underlying promise to the caller.
2. Stop accepting extra arguments to the replay function. The caller
should be able to do what they want with the returned promise instead.
3. Add a timestamp argument to the outgoing case, needed to re-try
sending a message.
Rather than opening the inbox in its own window, let it appear as a
browser action popup by default, but allow promotion to its own window
if requested.
Let libaxolotl throw a generic error instead of a replayable error, and
add an helper function in libtextsecure's axolotl_wrapper to catch and
convert from the generic error to the replayable one. This allows the
ReplayableError to remain a libtextsecure-level concept only.
Somewhat unrelatedly, but nearby, fix some whitespace and add missing
semicolon.
`emoji.init_colons` creates and populates `emoji.map.colons`, a global
map from common names to emoji code points. It's safe to call
repeatedly, but unecessary.
Now with actual malloc/free implementations. Had to drop back to -O1
optimization because the ed25519 signature test broke with -O2. :(
Closes#153
The toolchain install and build process, for reference:
```
wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
tar xvfz emsdk-portable.tar.gz
cd emsdk-portable
./emsdk update
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
cd ../TextSecure-Browser
grunt build
```
I had some trouble with the registration..
Quickly checked TextSecure Server API and found the problems:
number has to be valid PSTN. (Including regionCode, precisely what validateNumber() returns)
verificationCode has to be all numbers, no dashes or spaces
Closes#193
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.
Turns out we can get ABNORMAL_CODE (1006) for disconnects where (for
instance) we pause the background page too long. However, in these cases
there is no preceeding ErrorEvent. In contrast, when we have bad
authentication credentials, there is an ErrorEvent. Thus, this change
ensures that we only reconnect if there was no Error.
Templatize the inbox view and use the same pattern for in-window view
switching as is now used with the conversation/message detail views.
This means doing more with markup and less jquery manipulation of
individual subelements of the inbox view.
Closes#173
Previously, in the event of a failed websocket auth, we would attempt to
reconnect once a second ad infinitum. This changeset ensures that we
only reconnect automatically if the socket closed 'normally' as
indicated by the code on the socket's CloseEvent. Otherwise, show a
'Websocket closed' error on the inbox view.
Ideally we would show a more contextual error (ie, 'Unauthorized'), but
unfortunately the actual server response code is not available to our
code. It can be observed in the console output from the background page,
but programmatically, we only receive the WebSocket CloseEvent codes
listed here:
https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
The websocket error message is displayed by a normally-hidden but ever
present socket status element. Clicking this element will immediately
refresh the background page, which will try again to open the websocket
connection.
When first intalling, users will no longer be presented with the option
to register as a standalone client.
For developer convenience, the standalone form can still be found at
chrome-extension://.../register.html
Closes#159
Only re-render a message if the body changed. Re-render only the
delivery receipt checkmark if the delivered property changes.
Fix a bug where attachments flash in and out of existance when a
delivery receipt arrives.
Define a Whisper.View base class that automatically parses and renders
templates and attributes defined by the subclass. This saves us a good
number of lines of code as well as some marginal memory overhead, since
we are no longer saving per-instance copies of template strings.
Although I find the previous implementation more elegant, it results in
a deeper nesting of Promises than necessary, which can make debugging
more complicated. The canvas scaling and compression apis are actually
synchronous, so the callback structure isn't really recessary here.
Converting to a loop also makes this process easier to understand at
a glance.
Fixed some bugs along the way:
* accidentally scaling small images up to 1920px
* jpeg compressing gifs and other formats even if unnecessary
Previously we would not scale large resolution images with small file
sizes, but in fact, both resolution and file size constraints should be
enforced.
With these changes, message bubbles in the default-sized chat popup are
just wide enough to display the full complement of html5 media player
controls.
Converting attachment data to base64-encoded data uris takes O(n) and
there's no need! URL.createObjectURL returns a magic link that can be
set as the `src` attribute to `img`, `video`, and `audio` tags to load
blob data directly without copying.
https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
Add contentType-specific limits, switch to lazy-init iff we encounter an
oversized file, and restyle as a toast, factoring out a generic
ToastView along the way.
Wait a little longer on initial scroll down. Previous timeout sometimes
triggered before all text is finished rendering.
Remove redundant resize calls.
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.
Background page conversations were trying to trigger events on the inbox
list view which had been destroyed, resulting in a background page
console error of "can't read innerHeight of null".
Avoid this by removing listeners when the inbox window is closed.
When sending an constrct a copy of the PushMessageContent protobuf, add
a SyncMessageContext, and send it to ourselves. Do this for all kinds of
group messages, and individual text/media messages, but not closeSession
messages as the latter are device-specific.
Do not sync messages if we are the primary device, which should only be
supported in development. Normal web clients must be paired with a
android or ios master device, and even in dev, a primary/standalone web
client does not support linking additional devices.
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.
The first message sent to a new contact was throwing 'Unknown Group'.
This was because we didn't wait for the initial save to sync the `type`
attribute to indexedDB. Instead, don't trigger the conversation to open
until it has finished saving.
This is an artifact of a time when conversation elements would pop in
and out of the dom at a moment's notice, and thus needed to rebind their
event listeners regularly.
Previously the conversation window would query the background page
for a model id and then fetch the conversation. Instead, we can fetch
the conversation before opening the window, which simplifies the front
end scripts and avoids creating multiple copies of the same model.
Unless the background page fetches the latest details of a conversation
before updating it, it may clobber or nullify some attributes e.g., the
contact's name.
When a new message arrives, if its conversation is not already opened,
the background page opens it. If it is alrady open the window is
focused. Finally, the 'message' event is triggered, resulting in
1. the inbox refetches conversations
2. all conversations fetch new messages
TODO: only send this event to the target window
This collection is just an in-memory indexer used for typeaheads. For
display, the matching models are added to a separate collection. Thus,
the order of the elements in the typeahead collection does not matter.
It feels a little weird when you can't see the matching member. Would
consider putting this back in if we display the member list in the
contact list item view.
Previously, the ugly file input was hidden with opacity, and styled as a
square paperclip icon, but its drop and click zones were not constrained
to the visible square. They remained active across the whole 'Choose
File' button, which overlapped with the textarea. Instead, hide the file
input complete (display: none) and transmit click events from the
paperclip to the input programmatically.
Eventually, we'll need to address drag and drop events, but I want to do
that at the window level. Otherwise dropping a file outside the file
input drop zone causes the browser to navigate to the file://... url.
Render the entire conversation from a template, because some parts of it
must be rendered conditionally if it is a group vs private conversation.
Also apply some style fixes and restore lost functionality:
* Make conversation title bar fixed.
* Widens message bubbles.
* Unhide message list.
* Restore attachment rendering.
* Restore message sending and attachment file selection.
* Style attachments file input as a paperclip.
* Style send button like on Android and make it a submit input.
Don't auto open the last conversation. It doesn't make sense now that we
no longer have two column layout.
Don't trigger/listen for selected events. There's no need since the list
item opens a new popup now.