Changes the android sdk location from /opt/android-sdk-linux to /opt/android-sdk. We automatically install the android sdk cmdline-tools to that folder and install platform, build-tools and platforms via puppet. Co-authored-by: bed <bed@tutao.de> Co-authored-by: map <mpfau@users.noreply.github.com>
9.4 KiB
Hacking on the Tuta Mail client
Basic structure
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
Code structure
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.
Glossary
SomethingView: Big part of the app, corresponds to the URL, e.g.mail,contact,settings,searchSomethingListView: 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'ssystemapp for accounts and such andtutanotaapp 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 findTypeModelif needed
Communication
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
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.
Defining a new Mithril component
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})
Network
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().
Entity updates
Most of the server database changes are reflected in the EntityUpdates we receive from server. They describe operation
which happened to the entity. Updates are grouped into EntityEventBatches. These batches are ordered and client tries
tp stay up-to-date with the server (for caching and indexing).
Workflow and Testing
See HACKING for build pre-requisites.
Prepare the project:
- Clone the repository:
git clone https://github.com/tutao/tutanota.git - Switch into the repository directory:
cd tutanota - Initialize liboqs and argon2 submodules:
git submodule init - Synchronize submodules:
git submodule sync --recursive - Update submodules:
git submodule update - Run
npm cito install dependencies.
To build the web client without specific target (will use browser URL as an API endpoint).
node make
You can run node make prod to run it against the production server.
Start any web server serving build directory, and you should be good to go. e.g.
npx serve build -s -p 9000` or `python -m SimpleHTTPServer 9000
To build desktop client against the production server:
node make -d prod
Android app
Prerequisites:
You need to have Android SDK and NDK (28.2.13676358). The simplest way it to use Android studio but anything that can run Gradle will do.
To build Android app against production server you first need to build webapp (like node make prod) and then build
the Android app like you would normally (e.g. import the project under android-app in Android Studio, run the app
target for the mail app).
For building calendar app run `node make prod -a calendar instead.
iOS app
Prerequisites:
You need XCode, xcodegen. You might need swiftlint swift-format. You can install them through homebrew.
To build iOS app, build the web part (node make prod). Then generate iOS projects:
pushd tuta-sdk/ios # go into SDK directory
xcodegen # generate XCode project
popd # go back
mkdir -p build
mkdir -p build-calendar-app
cd app-ios # go into iOS app directory, generate projects for both apps
xcodegen --spec calendar-project.yml
xcodegen --spec mail-project.yml
After that you can open app-ios/tuta.xcworkspace in XCode and build the mail app.
For building calendar app run node make prod -a calenar instead.
Tests
To run sdk and node mimimi test:
cargo test --all
# Some sdk & node mimimi test requires actual local http server running, as we do not have "mock" server yet
# If on dev-machine, to run test including tests that requires local http server running:
cargo test --all --features test-with-local-http-server
To run all TypeScript 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
Chunking rules
- 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-minis api/common which is used by main and worker threads and is needed on startup (marked by@bundleInto) . rest of api/common is justcommon.mainis the rest of the main thread code that is not gui related and does not depend on sanitizer/luxondateis 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
commonexcept forcommon-minandapp.js - anything can depend on
app.jsexcept worker, common-min, common - gui-related things (like
loginormail-view) can depend ongui-base. Currently main also depends ongui-basebut it's not good - don't depend on
settings/subscription/login/mail-view/mail-editor/calendar-view/contactsthings statically - anything that depends on luxon goes into
dateand is being imported dynamically - native code is only imported from common code dynamically. Worker is exception for technical reasons.
contactsandmail-editordepend on sanitizer statically, rest of the app doesn't
You can check if your imports respect chunking by running node webapp local.