Proton Authenticator
This repository contains the source code for the Proton Authenticator application. * Project Architecture Overview * Architecture Diagram * Dependency manager * Modularization * Debug network traffic * Debug Sentry activities * Dependency injection * SwiftLint * SwiftFormat * SourceryInstallation
The app targets iOS 18 and above. Make sure you have Xcode 16+ installed, check out the repo and open Authenticator.xcodeproj to run the project.
Technical Choices
Project Architecture Overview
This document outlines our project structure based on a layered (Clean Architecture) approach. The code is organized into local Swift packages to enforce separation of concerns and improve maintainability.
The main layers are: • Entities: Core domain models (data structures). • Domain: Business logic including use cases and repository protocols. • Data: Concrete implementations for data operations (networking, persistence). • Presentation: UI components, views, and view models.
Architecture Diagram
Below is a Mermaid diagram that visualizes the dependency flow between the layers:
graph TD
%% Define Layers
subgraph Models [Models Package]
E[Models]
end
subgraph Domain [Domain Package]
D1[Repository Protocols]
D2[Use Cases / Interactors]
end
subgraph Data [Data Package]
DA[Repository Implementations]
DB[Networking / Persistence]
end
subgraph Presentation [Presentation Package]
P1[Views]
P2[View Models]
end
subgraph Shared [Shared Modules]
S1[Common Utilities]
end
%% Dependencies between layers
D1 --> E
%% Domain uses Models
D2 --> D1
%% Use Cases depend on Repository Protocols
DA --> D1
%% Data layer implements Domain protocols
DA --> DB
%% Data layer handles external data (API, DB)
P2 --> D2
%% Presentation layer (View Models) uses Use Cases
P1 --> P2
%% Views bind to View Models
%% Shared modules dependencies
D1 & D2 & DA & P1 & P2 --> S1
%% All layers can use common utilities
Package Functions
Models Package
- Purpose:
- Examples:
// User.swift
public struct User {
public let id: String
public let name: String
}
Domain Layer (Domain Package)
- Purpose:
- What to Include:
- Example:
// UserRepository.swift (protocol)
public protocol UserRepository {
func fetchUser(withID id: String, completion: @escaping (Result<User, Error>) -> Void)
}
// LoginUseCase.swift
public struct LoginUseCase {
private let userRepository: UserRepository
public init(userRepository: UserRepository) {
self.userRepository = userRepository
}
public func execute(login: String, password: String, completion: @escaping (Result<User, Error>) -> Void) {
// Business logic can be inserted here, e.g. validation, transformation, etc.
userRepository.fetchUser(withID: login, completion: completion)
}
}
Data Layer (Data Package)
- Purpose:
- What to Include:
- Example:
// UserRepositoryImpl.swift
public class UserRepositoryImpl: UserRepository {
private let apiClient: APIClient // Assume APIClient is a networking abstraction
public init(apiClient: APIClient) {
self.apiClient = apiClient
}
public func fetchUser(withID id: String, completion: @escaping (Result<User, Error>) -> Void) {
let endpoint = "https://api.example.com/users/\(id)"
apiClient.get(url: endpoint) { result in
switch result {
case .success(let data):
// Map data to User (consider using a dedicated mapper)
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
Presentation Layer (Screens Package)
- Purpose:
- What to Include:
Common Utilities
- Purpose:
Dependency manager
Swift Package ManagerDebug
Tools
Dependency injection
The main DI tool used is Factory. It is very light but yet very powerful.
SwiftLint
This is the main linter for the project. To install run the following Homebrew command:
brew install swiftlint
If you don't have this tool installed please refer to the following link to set it up: SwiftLint
The configuration for this tool can be found in the .swiftlint.yml file.
SwiftFormat
This is the main code reformatting tool for the project. To install run the following Homebrew command:
brew install swiftformat
If you don't have this tool installed please refer to the following link to set it up: SwiftFormat
The configuration for this tool can be found in the .swiftformat file
Periphery
This is the main tool to detect and remove unused code in the project. To install run the following Homebrew command:
brew install periphery
If you don't have this tool installed please refer to the following link to set it up: Periphery
The configuration for this tool can be found in the .periphery.yml file.
To scan and detect unused code just execute the following CLI command:
periphery scan
You can now remove all the unused code.
Pre-commit
Make sure codes are properly linted and formatted before committing.
First install pre-commit
brew install pre-commit
Then create a pre-commit hook
pre-commit install --hook-type pre-commit
After initializing pre-commit for the repo, the next first commit will take a bit of time because pre-commit needs to download and compile necessary tools configured in .pre-commit-config.yaml (swiftlint, swiftformat...)
Changelog
For a detailed list of changes in each version of the project, please refer to the CHANGELOG file.swiftlint--> swiftformat --lint .--> swiftformat .-->
License
The code and data files in this distribution are licensed under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. SeeSee LICENSE file