# Hacking on the Tuta Mail client ![Overview](Overview.svg) ## Basic structure * [`src/`](../src): Common part and the desktop client code * [`app-android/`](../app-android): Android specific parts * [`app-ios/`](../app-ios): iOS specific parts * [`libs/`](../libs): "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/`](../resources): some resources (mostly images) which are used in the project. Most of the are embedded to the code. * [`test/`](../test): test code * [`android.js`](../android.js): script for building Android app * [`make.js`](../make.js): script for building dev version * [`desktop.js`](../desktop.js): script for building the release version of the desktop clients * [`webapp.js`](../webapp.js): script for building release versions of the web application * [`fdroid-metadata-workaround`](../fdroid-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`](../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`, `search` * `SomethingListView`: Component which displays things in the list, usually in the second column * `SomethingViewer`: Component which usually displays one element (e.g. selected email or contact) * `SomethingModel`: Logic for some part of the app, lives in the main part * `SomethingController`: Something that does some bookkeeping or general action but is not tied to the specific part * `SomethingFacade`: Logic for one domain, lives in the api part * `SomethingApp`: Something that communicates with native part to execute tasks in certain domain * `app`: Part of the bigger domain structure. Currently there's `system` app for accounts and such and `tutanota` app for mails and contacts * `Entity`: Object corresponding to the server database entity * `TypeModel`: Describes entity type * `TypeRef`: Small object which lets us know which entity it is and find `TypeModel` if needed ### Communication Worker, main thread & apps communicate through the messages. Protocol is described in the [RemoteMessageDispatcher](../src/common/api/common/threading/MessageDispatcher.ts). See [WorkerClient](../src/common/api/main/WorkerClient.ts) and [WorkerImpl](../src/common/api/worker/WorkerImpl.ts) for the client and server part. Native code communicates through the [NativeInterface](../src/common/native/common/NativeInterface.ts). ### UI code UI code uses [Mithril](http://mithril.js.org/). 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: ```typescript // Defining import {Component} from "mithril" type Attrs = { param1: string, paramTwo?: number } class MyComponent implements Component { view(vnode: Vnode) { 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](../src/common/api/worker/EventBusClient.ts) to do that. For the main thread you can subscribe to the [EventController](../src/common/api/main/EventController.ts). `EventBus` and `EntityRestClient` make sure that entities are automatically encrypted/decrypted when needed. See [decryptAndMapToInstance()](../src/common/api/worker/crypto/CryptoFacade.ts). #### Entity updates 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). ## Workflow and Testing See [HACKING](./HACKING.md) for build pre-requisites. Prepare the project: 1. Clone the repository: `git clone https://github.com/tutao/tutanota.git` 2. Switch into the repository directory: `cd tutanota` 3. Initialize liboqs and argon2 submodules: `git submodule init` 4. Synchronize submodules: `git submodule sync --recursive` 5. Update submodules: `git submodule update` 6. Run `npm ci` to install dependencies. To build the web client without specific target (will use browser URL as an API endpoint). ```bash 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. ```bash npx serve build -s -p 9000` or `python -m SimpleHTTPServer 9000 ``` To build desktop client against the production server: ```bash 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: ```bash 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: ```bash 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: ```bash npm test ``` To run only the primary project tests and no tests for the modules: ```bash npm run test:app ``` To run only specific tests: ```bash npm run test:app -- -f 'CalendarModel' ``` To run only specific tests without npm: ```bash node test -f CalendarModel ``` To run tests in browser: ```bash npm run test:app -- -br ``` To run tests only in browser: ```bash npm run test:app -- --no-run -br ``` To show all test options: ```bash 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-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 just `common`. - `main` is the rest of the main thread code that is not gui related and does not depend on sanitizer/luxon - `date` 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 for `common-min` and `app.js` - anything can depend on `app.js` except worker, common-min, common - gui-related things (like `login` or `mail-view`) can depend on `gui-base`. Currently main also depends on `gui-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` and `mail-editor` depend on sanitizer statically, rest of the app doesn't You can check if your imports respect chunking by running `node webapp local`.