src/
: Common part and the desktop client codeapp-android/
: Android specific partsapp-ios/
: iOS specific partslibs/
: "vendor" directory containing our dependencies in non-minified and minified form. May be improved. We take security seriously so we review diff between each version.resources/
: some resources (mostly images) which are used in the project. Most of the are embedded to the code.test/
: test codeandroid.js
: script for building Android appmake.js
: script for building dev versiondesktop.js
: script for building the release version of the desktop clientswebapp.js
: script for building release versions of the web applicationfdroid-metadata-workaround
: is a link inside app-android so that F-Droid can find our metadata because our Android project is not in the root. Can be removed once it's fixed in F-Droid.tutao-pub.pem
: public key which is used to verify desktop clients
Web part of the app is split in three parts: client, worker and common. All code in the src/
except for the api/
directory is intended for GUI and system interaction. Code in the api
contains most of the logic for server communication, encryption, indexing etc.
SomethingView
: Big part of the app, corresponds to the URL, e.g.mail
,contact
,settings
,search
SomethingListView
: Component which displays things in the list, usually in the second columnSomethingViewer
: Component which usually displays one element (e.g. selected email or contact)SomethingModel
: Logic for some part of the app, lives in the main partSomethingController
: Something that does some bookkeeping or general action but is not tied to the specific partSomethingFacade
: Logic for one domain, lives in the api partSomethingApp
: Something that communicates with native part to execute tasks in certain domainapp
: Part of the bigger domain structure. Currently there'ssystem
app for accounts and such andtutanota
app for mails and contactsEntity
: Object corresponding to the server database entityTypeModel
: Describes entity typeTypeRef
: Small object which lets us know which entity it is and findTypeModel
if needed
Worker, main thread & apps communicate through the messages. Protocol is described in the RemoteMessageDispatcher. See WorkerClient and WorkerImpl for the client and server part.
Native code communicates through the NativeInterface.
UI code uses Mithril. It is a tiny framework which does routing & implement virtual DOM. It
uses a "hyperscript" language (m(ComponentOrDomElement, {param: value}, [children]
). It may seem intimidating at first
but it's actually quite simple.
Our preferred way of making Mithril components is through the ES6 classes. Before we've been creating instances of these
classes manually but that's not how Mithril should be used. Preferred way is to pass class and attributes
("props" if you come from React) to hyperscript and let it do its thing. Because of that we sometimes have two versions
of the components, newer one has "N" suffix like ButtonN
. It is almost always preferable to use new-style components.
Current preferred way looks roughly like that:
// Defining
import {Component} from "mithril"
type Attrs = { param1: string, paramTwo?: number }
class MyComponent implements Component<Attrs> {
view(vnode: Vnode<Attrs>) {
return m(".h1", "Hello " + vnode.attrs.param1)
}
}
// Usage
// ...
m(MyComponent, {param1: "Mithril", param2: 1})
For working with entities it is preferable to use injected EntityWorker
whenever possible and not using freestanding
functions. It makes easier to substitute network interfaces when needed.
One level below EntityWorker
lays EntityRestInterface
which is either EntityRestClient
or EntityRestCache
currently. Caches saves requested entities is the memory and updates them with WebSocket events.
If you're listening for WebSocket updates in the worker part (and you should justify doing that) then you should change EventBus to do that. For the main thread you can subscribe to the EventController.
EventBus
and EntityRestClient
make sure that entities are automatically encrypted/decrypted when needed. See
decryptAndMapToInstance().
Most of the server database changes are reflected in the EntityUpdate
s we receive from server. They describe operation
which happened to the entity. Updates are grouped into EntityEventBatch
es. These batches are ordered and client tries
tp stay up-to-date with the server (for caching and indexing).
node make
Start any web server serving build
directory, and you should be good to go.
To run tests:
npm test
To run only the primary project tests and no tests for the modules:
npm run test:app
To run only specific tests:
npm run test:app -- -f 'CalendarModel'
To run only specific tests without npm:
node test -f CalendarModel
To run tests in browser:
npm run test:app -- -br
To run tests only in browser:
npm run test:app -- --no-run -br
To show all test options:
npm:run test:app -- --help
- Don't import things statically which you don't want to be bundled together (e.g. importing settings from login will load whole settings at startup)
common-min
is api/common which is used by main and worker threads and is needed on startup (marked by@bundleInto
) . rest of api/common is justcommon
.main
is the rest of the main thread code that is not gui related and does not depend on sanitizer/luxondate
is luxon and everything that depends on it statically- rest is obvious:
login
,mail-view
,mail-editor
,calendar-view
,search
,settings
,worker
- anything can depend on
common-min
- anything can depend on
common
except forcommon-min
andapp.js
- anything can depend on
app.js
except worker, common-min, common - gui-related things (like
login
ormail-view
) can depend ongui-base
. Currently main also depends ongui-base
but it's not good - don't depend on
settings
/subscription
/login
/mail-view
/mail-editor
/calendar-view
/contacts
things statically - anything that depends on luxon goes into
date
and is being imported dynamically - native code is only imported from common code dynamically. Worker is exception for technical reasons.
contacts
andmail-editor
depend on sanitizer statically, rest of the app doesn't
You can check if your imports respect chunking by running node webapp local
.