Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Adding legacy contributing docs #3471

Merged
merged 1 commit into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions docs/architecture/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
sidebar_position: 5
---

# .NET MAUI (legacy)

:::warning Legacy

This represents the **legacy** mobile app architecture done in .NET MAUI.

:::

The mobile .NET MAUI clients are Android and iOS applications with extensions and watchOS. They are
all located at https://github.com/bitwarden/mobile.

Principal structure is a follows:

- `App`: Main .NET MAUI project that shares code between both platforms (Android & iOS). One can see
specific platform code under the `Platforms` folder.
- `Core`: Shared code having both logical and UI parts of the app. Several classes are a port from
the Web Clients to C#. Here one can find most of the UI and logic since it's shared between App
and the iOS extensions.
- `iOS.Core`: Shared code used by the main iOS app and its extensions
- `iOS.Autofill`: iOS extension that handles Autofill
- `iOS.Extensions`: iOS extension that handles Autofill from the bottom sheet extension
- `iOS.ShareExtension`: iOS extension that handles sharing files through Send
- `watchOS`: All the code specific to the watchOS platform
- `bitwarden`: Stub iOS app so that the watchOS app has a companion app on Xcode
- `bitwarden WatchKit App`: Main Watch app where we set assets.
- `bitwarden WatchKit Extension`: All the logic and presentation logic for the Watch app is here

## Dependencies diagram

Below is a simplified dependencies diagram of the mobile repository.

```kroki type=plantuml
@startuml
skinparam BackgroundColor transparent
skinparam componentStyle rectangle
skinparam linetype ortho

title Simplified Dependencies Diagram

component "Core"
component "App"
component "iOS.Core"
component "iOS.Autofill"
component "iOS.Extension"
component "iOS.ShareExtension"
component "watchOS" {
component "bitwarden"
component "bitwarden WatchKit App"
component "bitwarden WatchKit Extension"
}

[App] --> [Core]

[iOS.Core] --> [App]

[App] --> [iOS.Core]
[App] --> [iOS.Autofill]
[App] --> [iOS.Extension]
[App] --> [iOS.ShareExtension]
[App] --> [bitwarden WatchKit App]

[iOS.Autofill] --> [Core]
[iOS.Autofill] --> [iOS.Core]

[iOS.Extension] --> [Core]
[iOS.Extension] --> [iOS.Core]

[iOS.ShareExtension] --> [Core]
[iOS.ShareExtension] --> [iOS.Core]

[bitwarden] --> [bitwarden WatchKit App]
[bitwarden WatchKit App] --> [bitwarden WatchKit Extension]

@enduml
```
26 changes: 26 additions & 0 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
sidebar_position: 1
---

# Overview

:::warning Legacy

This represents the **legacy** mobile app overview architecture done in .NET MAUI.

:::

The overall architecture of the mobile applications is pretty similar to the
[web clients](../../clients/overview.md) one following a layered architecture:

- State
- Services
- Presentation

Even though the State and Services layers are pretty similar to the web ones the Presentation layer
differs:

## Presentation

The presentation layer is implemented using .NET MAUI for the mobile apps, except for the watchOS
one which uses SwiftUI [see ADR](../../adr/0017-watchOS-use-swift.md)
186 changes: 186 additions & 0 deletions docs/architecture/watchOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# watchOS

:::warning Legacy

This represents the **legacy** watchOS app architecture done in .NET MAUI.

:::

## Overall architecture

The watchOS application is organized as follows:

- `src/watchOS`: All the code specific to the watchOS platform
- `bitwarden`: Stub iOS app so that the watchOS app has a companion app on Xcode
- `bitwarden WatchKit App`: Main Watch app where we set assets.
- `bitwarden WatchKit Extension`: All the logic and presentation logic for the Watch app is here

So almost all the things related to the watch app will be in the **WatchKit Extension**, the
WatchKit App one will be only for assets and some configs.

Then in the Extension we have a layered architecture:

- State (it's a really simplified version of the iOS state)
- Persistence (here we use `CoreData` to interact with the Database)
- Services (totp generation, crypto services and business logic)
- Presentation (use `SwiftUI` for the UI with an MVVM pattern)

## Integration with iOS

The watchOS app is developed using `Xcode` and `Swift` and we need to integrate it to the .NET MAUI
iOS application.

For this, the `iOS.csproj` has been adapted taking a
[solution](https://github.com/xamarin/xamarin-macios/issues/10070#issuecomment-1033428823) provided
in the `Xamarin.Forms` GitHub repository and modified to our needs:

```xml
<PropertyGroup>
<WatchAppBuildPath Condition=" '$(Configuration)' == 'Debug' ">$(Home)/Library/Developer/Xcode/DerivedData/bitwarden-cbtqsueryycvflfzbsoteofskiyr/Build/Products</WatchAppBuildPath>
<WatchAppBuildPath Condition=" '$(Configuration)' != 'Debug' ">$([System.IO.Path]::GetFullPath('$(MSBuildProjectDirectory)\..'))/watchOS/bitwarden.xcarchive/Products/Applications/bitwarden.app/Watch</WatchAppBuildPath>
<WatchAppBundle>Bitwarden.app</WatchAppBundle>
<WatchAppConfiguration Condition=" '$(Platform)' == 'iPhoneSimulator' ">watchsimulator</WatchAppConfiguration>
<WatchAppConfiguration Condition=" '$(Platform)' == 'iPhone' ">watchos</WatchAppConfiguration>
<WatchAppBundleFullPath Condition=" '$(Configuration)' == 'Debug' ">$(WatchAppBuildPath)/$(Configuration)-$(WatchAppConfiguration)/$(WatchAppBundle)</WatchAppBundleFullPath>
<WatchAppBundleFullPath Condition=" '$(Configuration)' != 'Debug' ">$(WatchAppBuildPath)/$(WatchAppBundle)</WatchAppBundleFullPath>
</PropertyGroup>

...

<ItemGroup Condition=" '$(Configuration)' == 'Debug' AND Exists('$(WatchAppBundleFullPath)') ">
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<_ResolvedWatchAppReferences Include="$(WatchAppBundleFullPath)" />
</ItemGroup>
<PropertyGroup Condition=" '$(_ResolvedWatchAppReferences)' != '' ">
<CodesignExtraArgs>--deep</CodesignExtraArgs>
</PropertyGroup>
<Target Name="PrintWatchAppBundleStatus" BeforeTargets="Build">
<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' exists" Condition=" Exists('$(WatchAppBundleFullPath)') " />
<Message Text="WatchAppBundleFullPath: '$(WatchAppBundleFullPath)' does NOT exist" Condition=" !Exists('$(WatchAppBundleFullPath)') " />
</Target>
```

So on the `PropertyGroup` the `WatchAppBundleFullPath` is assembled together depending on the
Configuration and the Platform taking the output of the Xcode watchOS app build. Then there are some
`ItemGroup` to include the watch app depending on if it exists and the Configuration. The task
`_ResolvedWatchAppReferences` is the one responsible to peek into the `Bitwarden.app` built by Xcode
and if it finds a Watch app, it will just bundle it to the Xamarin iOS application. Finally, if the
Watch app is bundled, deep signing is enabled and the build path is printed.

:::caution

As one can see in the csproj, to bundle the watchOS app into the iOS app one needs to target the
correct platform. So if one is going to use a device, target the device on Xcode to build the
watchOS app and after the build is done one can go to VS4M to build the iOS app (which will bundle
the watchOS one) and run it on the device.

:::

## Synchronization between iPhone and Watch

In order to sync data between the iPhone and the Watch apps the
[Watch Connectivity Framework](https://developer.apple.com/documentation/watchconnectivity) is used.

So there is a Watch Connectivity Manager on each side that is the interface used for the services on
each platform to communicate.

For the sync communication, mainly
[updateApplicationContext](https://developer.apple.com/documentation/watchconnectivity/wcsession/1615621-updateapplicationcontext)
is used given that it always have the latest data sent available, it's sent in the background and
the counterpart device doesn't necessarily needs to be in range (so it's cached until it can be
delivered). Additionally,
[sendMessage](https://developer.apple.com/documentation/watchconnectivity/wcsession/1615687-sendmessage)
is also used to signal the counterpart of some action to take quickly (like triggering a sync from
the Watch).

The `WatchDTO` is the object that is sent in the synchronization that has all the information for
the Watch.

```kroki type=plantuml
title= iOS part
@startuml

title iOS

participant C as "Caller"
participant BWDS as "BaseWatchDeviceService"
participant WDS as "WatchDeviceService"
participant WCSM as "WCSessionManager"
boundary WCF as "Watch Connectivity Framework"

group Sync
C->>BWDS: SyncDataToWatchAsync(...)
BWDS->BWDS: GetStateAsync(...)
BWDS->>WDS: SendDataToWatchAsync(...)
WDS->>WCSM: SendBackgroundHighPriorityMessage(...)
WCSM->>WCF: UpdateApplicationContext(...)
end
@enduml
```

```kroki type=plantuml
title= iOS part
@startuml

title watchOS

boundary WCF as "Watch Connectivity Framework"
participant WCM as "WatchConnectivityManager"
participant SS as "StateService"
participant ES as "EnvironmentService"
participant CS as "CipherService"
participant WCS as "watchConnectivitySubject"

group Sync
WCF->>WCM: didReceiveApplicationContext(...)
WCM->>SS: update state
WCM->>ES: update environment
WCM->>CS: saveCiphers(...)
WCM->>WCS: fire notification change to subscribers
end
@enduml
```

## States

The next ones are the states in which the Watch application can be at a given time:

- **Valid:** Everything it's ok and the user can see the vault ciphers with TOTP
- **Need Login:** The user needs to log in using the iPhone
- **Need Setup:** The user needs to set up an account with "Connect to Watch" enabled on their
iPhone
- **Need Premium:** The current account is not a premium account
- **Need 2FA item:** The current account doesn't have any cipher with TOTP set up
- **Syncing:** Displayed when changing accounts and syncing the new vault TOTPs
- **Need Device Owner Auth:** The user needs to set up an Apple Watch Passcode in order to use the
app

## Persistence and encryption

On the Watch [CoreData](https://developer.apple.com/documentation/coredata) is used as persistence
for the ciphers. So in order to encrypt the data in them a Value Transformer in each encrypted
attribute is used: `StringEncryptionTransformer`.

Inside the transformer a call to the `CryptoService` is used that ends up using
[AES.GCM](https://developer.apple.com/documentation/cryptokit/aes/gcm) to encrypt the data with a
256 bits [SymmetricKey](https://developer.apple.com/documentation/cryptokit/symmetrickey). The key
is generated/loaded the first time something needs to be encrypted and stored in the device
Keychain.

## Crash reporting

On all the other mobile applications, [AppCenter](https://appcenter.ms/) is being used as Crash
reporting tool. However, it doesn't have support for watchOS (nor its internal library to handle
crashes).

So, on the watchOS app [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics) is used
with basic crash reporting enabled (there is no handled error logging here yet). For this to work a
`GoogleService-Info.plist` file is needed which is injected on the CI.

At the moment of writing this document, no plist is configured for dev environment so `Crashlytics`
is enabled on **non-DEBUG** configurations.

There is a `Log` class to log errors happened in the app, but it's only enabled in **DEBUG**
configuration.
Binary file added docs/getting-started/android/android-sdk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading