This Flutter application provides a user-friendly interface for controlling a VPN daemon through socket communication, displaying the current connection status, and handling VPN actions such as connect and disconnect. The app polls the daemon status every 5 seconds and manages the lifetime of the authentication token to ensure smooth VPN operations.
- Features
- Requirements
- Recommended IDE
- Architecture
- Folder Structure
- Libraries & Packages
- Setup and Installation
- Running the App
- Running the Daemon-Lite
- Testing
- macOS Packaging
- Limitations & Future Work
- Daemon Communication: Interacts with a mock VPN daemon via UNIX sockets.
- Polling: Continuously polls the daemon status every 5 seconds.
- Connection Control: Provides buttons for connecting and disconnecting the VPN.
- Error Handling: Displays user-friendly error messages when issues occur during communication with the VPN daemon or when network issues are encountered.
- Debouncing: Ensures that rapid successive VPN actions (connect/disconnect) are debounced to avoid unnecessary socket connections or failures.
- Token Management: Manages the lifetime of the authentication token (valid for 5 minutes).
- macOS/Linux: Required to use UNIX sockets for communication with the daemon.
- Flutter: Version 3.10.0 or above
- Dart: Version 3.0 or above
- Xcode: Required for building and running the app on iOS.
- Android Studio: Required for building and running the app on Android*.
The recommended IDE for working on this project is Visual Studio Code with dart and flutter plugins installed.
This project follows a Layered Architecture approach that enforces separation of concerns, making the code more modular, scalable, and testable.
The project is divided into the following layers:
Handles the UI and user interaction. It contains the VpnActionNotifier
, which orchestrates the flow of data from repositories and services to the UI, ensuring that the VPN status and actions (connect/disconnect) are properly handled.
Manages the state and provides access to repositories and services using Riverpod
providers. This layer includes the RepositoryProvider
and ServiceProvider
, which offer a clean and modular way to access services like SocketService
, AuthService
, and repositories like ConnectionRepository
and TokenRepository
.
Contains the core business logic and models. This layer includes business-critical elements such as DaemonStatusState
and AuthToken
, and handles domain-related logic, including error handling with custom exceptions (e.g., DataSourceException
).
Responsible for interacting with external services such as the VPN daemon and secure token storage. This layer contains the actual implementation of repositories like ConnectionRepository
(for VPN control) and TokenRepository
(for managing authentication tokens).
The following diagram illustrates the flow of data and interactions between these layers:
lib/
├── application/
│ ├── providers/
│ │ ├── repository.abs.dart
│ │ └── service.abs.dart
│ └── services/
│ ├── api_config.dart
│ ├── auth_service.dart
│ ├── network_client.dart
│ ├── secure_storage_service.dart
│ └── socket_service.dart
├── core/
│ ├── utils/
│ │ ├── constants/
│ │ │ ├── dimensions.dart
│ │ │ └── display_breakpoint.dart
│ │ ├── logger/
│ │ │ └── app_logger.dart
│ │ └── theme/
│ │ └── app_style.dart
│ └── widgets/
│ └── responsive_container.dart
├── data/
│ ├── repositories/
│ │ ├── daemon_connection_repository.dart
│ │ └── token_repository.dart
├── domain/
│ ├── errors/
│ │ ├── data_source_exception.dart
│ │ └── network_exception.dart
│ └── models/
│ ├── auth/
│ ├── daemon/
│ └── daemon_status.dart
├── presentation/
│ ├── providers/
│ │ ├── vpn_action_notifier.dart
│ │ └── vpn_action_notifier.freezed.dart
│ └── screens/
│ └── daemon_status_screen.dart
└── main.dart
- application: Contains providers that manage the state and business logic for the app.
- core: Contains utility classes such as logging.
- data: Handles external service interactions, including the connection to the daemon and token management.
- domain: Contains models, errors, and business logic.
- presentation: Contains the UI, with a primary screen for the VPN control interface.
The following packages are used in the project:
Package | Purpose |
---|---|
flutter_riverpod |
State management for handling daemon status and actions. |
freezed_annotation |
Data class immutability and union types for error handling. |
flutter_secure_storage |
Storing sensitive data securely (e.g., tokens). |
dio |
HTTP client for API requests. |
build_runner |
Code generation for freezed and riverpod annotations. |
json_serializable |
Automatically generating JSON serialization boilerplate. |
logging |
Structured logging throughout the app. |
mocktail |
Unit testing with mocks. |
-
Ensure your system is set up correctly by running:
flutter doctor
This command will check if your Flutter and Dart versions are installed and correctly configured, and it will provide information about any missing dependencies. Ensure all checks pass.
-
Clone the repository:
git clone https://github.com/CuriousDev21/ZTClient-Flutter.git cd cloudflare_zt_flutter
-
Install dependencies:
flutter pub get
-
Generate necessary files (e.g., for
freezed
andriverpod
):flutter pub run build_runner build --delete-conflicting-outputs
Before running the app, it's a good idea to check the connected devices or simulators available using the following command:
flutter devices
This will list all connected devices and simulators. For example, if you're targeting iOS, you will get a list of available iOS simulators, and you can choose one to run the app. Use the specific device ID shown in the flutter devices
output.
To run the app on a specific device or simulator, use the following command (replace device_id
with the actual device or simulator ID you got from flutter devices
):
flutter run -d device_id
However, if you're targeting macOS, you can run the following command directly:
flutter run -d macos
Alternatively, you can use the Build button in Visual Studio Code, which automatically detects and builds the app for the selected device or simulator. This is an easier approach if you are working in the Visual Studio Code environment.
Before running the application, you must start the mock daemon-lite
on your system. This provides the VPN daemon service the app interacts with.
-
Navigate to the
daemon-lite
directory (where the daemon binary is stored):cd daemon-lite
-
Start the daemon-lite with default options:
./daemon-lite
This will start the daemon listening on a UNIX socket at
/tmp/daemon-lite
. -
Optionally, you can start the daemon with additional parameters such as:
-
Failure rate (
-f
): How often the requests should fail (once every X calls). For example:./daemon-lite -f 3
-
Connection timeout (
-c
): The maximum amount of time the connect request can take to establish a connection (in milliseconds):./daemon-lite -c 5000
-
Disconnect timeout (
-d
): The maximum amount of time the disconnect request can take to disconnect (in milliseconds):./daemon-lite -d 3000
You can see a list of all available parameters using:
./daemon-lite --help
-
Unit tests are provided using the mocktail
package for mocking service and repository layers.
Run all tests:
flutter test
- Daemon Interaction: Ensures that the app can effectively communicate with the VPN daemon and handle edge cases.
- Error Handling: Ensures that errors from the daemon are handled appropriately and shown to the user.
- Token Management: Ensures the auth token is fetched, cached, and refreshed as needed.
The application has already been packaged as a DMG (Disk Image) file, which is included in the releases/
folder of this repository.
- Navigate to the
releases/
folder in this repository's Flutter project foldercloudflare_zt_flutter/
. - Find the file
VPNControl-Flutter.dmg
. - Double-click to open the DMG file.
- Open it directly or Drag and drop the app into your Applications folder.
Since the application is not signed with an Apple Developer ID, macOS will block the app from opening initially. To bypass this, follow these steps:
-
After downloading and moving the application to your Applications folder, try opening the app by double-clicking. You will see a warning stating that the app "can't be opened because Apple cannot check it for malicious software."
-
Open System Preferences and navigate to Security & Privacy.
-
In the General tab, you will see a message that the app was blocked. Click Open Anyway.
-
You will now be able to launch the app.
Alternatively, you
can build the app locally using Flutter:
-
Clone the repository:
git clone https://github.com/CuriousDev21/ZTClient-Flutter.git cd ZTClient-Flutter
-
Run the macOS build command:
flutter build macos
-
Navigate to the generated build folder:
open build/macos/Build/Products/Release/
-
Run the app directly from the Release folder.
- The app runs perfectly on macOS and iOS. However, on Android, the
/tmp
path is reserved and cannot be used for socket communication. Altering the daemon to use another directory would have been required to make it work on Android emulators. - Packaging the app as
.pkg
for macOS requires additional notarization, which wasn't possible without a Developer ID Installer Certificate.
- Support for Android: In a real-world scenario, modifying the daemon to use a different socket path (instead of
/tmp
) would be essential for supporting Android. - Daemon Code Refactor: Potential improvements to the daemon itself could allow it to be more portable across different operating systems.
- Notifications on Status Changes: Adding OS-level notifications for VPN status changes would enhance user experience.
- Improve Error Handling: More advanced error handling mechanisms, such as retries with exponential backoff, could be introduced for robustness.
- Integration Testing: Using tools like
Patrol
andPatrol_CLI
to perform integration tests on the app's UI and interactions with a daemon instance. - Localization Support: Adding support for multiple languages would make the app more accessible to a wider audience.