mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33ccba5059 | |||
| 1485bb4a5d | |||
| a12c16aa33 |
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Verify git tag
|
- name: Verify git tag
|
||||||
run: |
|
run: |
|
||||||
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||||
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
||||||
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||||
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -140,6 +140,3 @@ ios-ne-build.sh
|
|||||||
macos-ne-build.sh
|
macos-ne-build.sh
|
||||||
macos-signed-build.sh
|
macos-signed-build.sh
|
||||||
macos-with-sign-build.sh
|
macos-with-sign-build.sh
|
||||||
DeveloperIdApplicationCertificate.p12
|
|
||||||
DeveloperIdInstallerCertificate.p12
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,3 @@
|
|||||||
[submodule "client/3rd/QSimpleCrypto"]
|
[submodule "client/3rd/QSimpleCrypto"]
|
||||||
path = client/3rd/QSimpleCrypto
|
path = client/3rd/QSimpleCrypto
|
||||||
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||||
[submodule "client/3rd/qtgamepad"]
|
|
||||||
path = client/3rd/qtgamepad
|
|
||||||
url = https://github.com/amnezia-vpn/qtgamepad.git
|
|
||||||
branch = 6.6
|
|
||||||
|
|||||||
+2
-5
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.15.0)
|
set(AMNEZIAVPN_VERSION 4.8.12.9)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2118)
|
set(APP_ANDROID_VERSION_CODE 2105)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
@@ -61,9 +61,6 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
|||||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
||||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
||||||
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
|
|
||||||
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
|
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
|
|
||||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
||||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
|
GPL v3.0
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
# Third-Party Licenses
|
|
||||||
|
|
||||||
This project is licensed under the GNU General Public License v3.0.
|
|
||||||
This file lists third-party software components used by this repository.
|
|
||||||
Each component is distributed under its own license as linked below.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## QtKeychain
|
|
||||||
|
|
||||||
- Source: https://github.com/frankosterfeld/qtkeychain
|
|
||||||
- License: BSD License
|
|
||||||
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## QSimpleCrypto
|
|
||||||
|
|
||||||
- Source: https://github.com/n1flh31mur/QSimpleCrypto
|
|
||||||
- License: Apache License 2.0
|
|
||||||
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## SortFilterProxyModel
|
|
||||||
|
|
||||||
- Source: https://github.com/oKcerG/SortFilterProxyModel
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## QJsonStruct
|
|
||||||
|
|
||||||
- Source: https://github.com/Qv2ray/QJsonStruct
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## QR Code Generator (qrcodegen)
|
|
||||||
|
|
||||||
- Source: https://github.com/nayuki/QR-Code-generator
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://www.nayuki.io/page/qr-code-generator-library
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Qt Gamepad
|
|
||||||
|
|
||||||
- Source: https://github.com/qt/qtgamepad
|
|
||||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
|
||||||
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## AmneziaWG Apple (WireGuard)
|
|
||||||
|
|
||||||
- Source: https://github.com/amnezia-vpn/amneziawg-apple
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## AmneziaWG Android
|
|
||||||
|
|
||||||
- Source: https://github.com/amnezia-vpn/amneziawg-go
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Xray Core
|
|
||||||
|
|
||||||
- Source: https://github.com/XTLS/Xray-core
|
|
||||||
- License: Mozilla Public License 2.0 (MPL-2.0)
|
|
||||||
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Cloak
|
|
||||||
|
|
||||||
- Source: https://github.com/cbeuw/Cloak
|
|
||||||
- License: GNU General Public License v3.0 (GPL-3.0)
|
|
||||||
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Shadowsocks
|
|
||||||
|
|
||||||
- Source: https://github.com/shadowsocks/shadowsocks-libev
|
|
||||||
- License: GPL-3.0-or-later
|
|
||||||
- License Text: http://www.gnu.org/licenses/
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## OpenSSL
|
|
||||||
|
|
||||||
- Source: https://github.com/openssl/openssl
|
|
||||||
- License: Apache License 2.0
|
|
||||||
- License Text: https://www.openssl.org/source/license.html
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## libssh
|
|
||||||
|
|
||||||
- Source: https://www.libssh.org/
|
|
||||||
- License: GNU Lesser General Public License (LGPL)
|
|
||||||
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## OpenVPNAdapter
|
|
||||||
|
|
||||||
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
|
|
||||||
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
||||||
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Wintun
|
|
||||||
|
|
||||||
- Source: https://www.wintun.net/
|
|
||||||
- License: Prebuilt Binaries License
|
|
||||||
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mullvad Split Tunnel Driver
|
|
||||||
|
|
||||||
- Source: https://github.com/mullvad/win-split-tunnel
|
|
||||||
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
|
|
||||||
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## tun2socks
|
|
||||||
|
|
||||||
- Source: https://github.com/eycorsican/go-tun2socks
|
|
||||||
- License: MIT License
|
|
||||||
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## TAP-Windows Driver
|
|
||||||
|
|
||||||
- Source: https://github.com/OpenVPN/tap-windows6
|
|
||||||
- License: tap-windows6 license
|
|
||||||
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
|
|
||||||
+1
-1
Submodule client/3rd-prebuilt updated: 51bb4703a4...579673b2ed
Vendored
-1
Submodule client/3rd/qtgamepad deleted from f72b3e0c62
+2
-11
@@ -59,6 +59,7 @@ target_include_directories(${PROJECT} PUBLIC
|
|||||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||||
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||||
@@ -78,7 +79,6 @@ set(AMNEZIAVPN_TS_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
|
||||||
|
|
||||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||||
|
|
||||||
@@ -228,13 +228,4 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||||
|
qt_finalize_target(${PROJECT})
|
||||||
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
|
||||||
if(COMMAND qt_import_qml_plugins)
|
|
||||||
qt_import_qml_plugins(${PROJECT})
|
|
||||||
endif()
|
|
||||||
if(COMMAND qt_finalize_executable)
|
|
||||||
qt_finalize_executable(${PROJECT})
|
|
||||||
else()
|
|
||||||
qt_finalize_target(${PROJECT})
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -109,16 +109,6 @@ void AmneziaApplication::init()
|
|||||||
// install filter on main window
|
// install filter on main window
|
||||||
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||||
win->installEventFilter(this);
|
win->installEventFilter(this);
|
||||||
#ifdef Q_OS_ANDROID
|
|
||||||
QObject::connect(win, &QQuickWindow::sceneGraphError,
|
|
||||||
[](QQuickWindow::SceneGraphError, const QString &msg) {
|
|
||||||
qWarning() << "Scene graph error (suppressed):" << msg;
|
|
||||||
});
|
|
||||||
// Keep graphics context alive across hide/show cycles to avoid
|
|
||||||
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
|
|
||||||
win->setPersistentSceneGraph(true);
|
|
||||||
win->setPersistentGraphics(true);
|
|
||||||
#endif
|
|
||||||
win->show();
|
win->show();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,8 +24,5 @@
|
|||||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||||
|
|
||||||
<string name="vpnStateEventChannelName">Уведомления о VPN</string>
|
|
||||||
<string name="vpnStateEventChannelDescription">Краткие оповещения при подключении и отключении VPN</string>
|
|
||||||
|
|
||||||
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -24,8 +24,5 @@
|
|||||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||||
<string name="openNotificationSettings">Open notification settings</string>
|
<string name="openNotificationSettings">Open notification settings</string>
|
||||||
|
|
||||||
<string name="vpnStateEventChannelName">VPN connection alerts</string>
|
|
||||||
<string name="vpnStateEventChannelDescription">Brief alerts when VPN connects or disconnects</string>
|
|
||||||
|
|
||||||
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -26,8 +26,6 @@ import android.os.ParcelFileDescriptor
|
|||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.InputDevice
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -75,8 +73,6 @@ private const val OPEN_FILE_ACTION_CODE = 3
|
|||||||
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
||||||
|
|
||||||
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
||||||
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
|
|
||||||
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
|
|
||||||
|
|
||||||
class AmneziaActivity : QtActivity() {
|
class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
@@ -93,12 +89,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
|
|
||||||
private var isActivityResumed = false
|
|
||||||
private var hasWindowFocus = false
|
|
||||||
private val resumeHandler = Handler(Looper.getMainLooper())
|
|
||||||
private var pendingOpenFileUri: String? = null
|
|
||||||
private var openFileDeliveryScheduled = false
|
|
||||||
|
|
||||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||||
object : Handler(Looper.getMainLooper()) {
|
object : Handler(Looper.getMainLooper()) {
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
@@ -200,18 +190,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
|
|
||||||
openFileDeliveryScheduled = false
|
|
||||||
registerBroadcastReceivers()
|
registerBroadcastReceivers()
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
runBlocking { vpnProto = proto.await() }
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadLibs() {
|
private fun loadLibs() {
|
||||||
listOf(
|
listOf(
|
||||||
"rsapss",
|
"rsapss",
|
||||||
@@ -277,11 +260,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
isActivityResumed = false
|
|
||||||
hasWindowFocus = false
|
|
||||||
// Cancel all pending operations when activity stops
|
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
|
||||||
openFileDeliveryScheduled = false
|
|
||||||
Log.d(TAG, "Stop Amnezia activity")
|
Log.d(TAG, "Stop Amnezia activity")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
@@ -293,129 +271,35 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
super.onWindowFocusChanged(hasFocus)
|
super.onWindowFocusChanged(hasFocus)
|
||||||
hasWindowFocus = hasFocus
|
|
||||||
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
||||||
|
|
||||||
if (!hasFocus) {
|
|
||||||
// Cancel pending operations if window loses focus
|
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
|
||||||
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
window.decorView.apply {
|
|
||||||
invalidate()
|
|
||||||
resumeHandler.postDelayed({
|
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
|
||||||
sendTouch(1f, 1f)
|
|
||||||
}
|
|
||||||
}, 50)
|
|
||||||
resumeHandler.postDelayed({
|
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
|
||||||
sendTouch(2f, 2f)
|
|
||||||
requestLayout()
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}, 150)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
|
||||||
val keyCode = event.keyCode
|
|
||||||
val pressed = event.action == KeyEvent.ACTION_DOWN
|
|
||||||
|
|
||||||
when (keyCode) {
|
|
||||||
KeyEvent.KEYCODE_BUTTON_A,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_B,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_X,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_Y,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_START,
|
|
||||||
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
|
||||||
nativeGamepadKeyEvent(0, keyCode, pressed)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER,
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT,
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
|
||||||
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
|
||||||
val synthetic = KeyEvent(
|
|
||||||
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
|
||||||
event.repeatCount, event.metaState, -1, event.scanCode,
|
|
||||||
event.flags, InputDevice.SOURCE_KEYBOARD
|
|
||||||
)
|
|
||||||
return super.dispatchKeyEvent(synthetic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.dispatchKeyEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
|
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
|
|
||||||
// Using a coroutine here would be too late — the surface is gone by the time
|
|
||||||
// the coroutine runs. A direct synchronous call gives Qt's render thread the
|
|
||||||
// best chance to process visible=false before surface destruction.
|
|
||||||
if (qtInitialized.isCompleted) {
|
|
||||||
QtAndroidController.onActivityPaused()
|
|
||||||
}
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
isActivityResumed = false
|
|
||||||
// Cancel all pending operations when activity pauses
|
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
|
||||||
openFileDeliveryScheduled = false
|
|
||||||
Log.d(TAG, "Pause Amnezia activity")
|
Log.d(TAG, "Pause Amnezia activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
isActivityResumed = true
|
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
Log.d(TAG, "Resume Amnezia activity")
|
|
||||||
if (qtInitialized.isCompleted) {
|
|
||||||
QtAndroidController.onActivityResumed()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
|
|
||||||
val uri = pendingOpenFileUri!!
|
|
||||||
openFileDeliveryScheduled = true
|
|
||||||
resumeHandler.postDelayed({
|
|
||||||
if (!isFinishing && !isDestroyed) {
|
|
||||||
pendingOpenFileUri = null
|
|
||||||
openFileDeliveryScheduled = false
|
|
||||||
mainScope.launch {
|
|
||||||
qtInitialized.await()
|
|
||||||
QtAndroidController.onFileOpened(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
||||||
window.decorView.apply {
|
window.decorView.apply {
|
||||||
invalidate()
|
invalidate()
|
||||||
|
|
||||||
resumeHandler.postDelayed({
|
postDelayed({
|
||||||
// Check if activity is still resumed and has focus before executing
|
sendTouch(1f, 1f)
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
|
||||||
sendTouch(1f, 1f)
|
|
||||||
}
|
|
||||||
}, 100)
|
}, 100)
|
||||||
|
|
||||||
resumeHandler.postDelayed({
|
postDelayed({
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
sendTouch(2f, 2f)
|
||||||
sendTouch(2f, 2f)
|
|
||||||
}
|
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
resumeHandler.postDelayed({
|
postDelayed({
|
||||||
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
requestLayout()
|
||||||
requestLayout()
|
invalidate()
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}, 250)
|
}, 250)
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
Log.d(TAG, "Resume Amnezia activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun configureWindowForEdgeToEdge() {
|
private fun configureWindowForEdgeToEdge() {
|
||||||
@@ -453,35 +337,31 @@ class AmneziaActivity : QtActivity() {
|
|||||||
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
|
||||||
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
|
||||||
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
|
||||||
|
|
||||||
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
val imeHeight = if (imeVisible) imeInsets.bottom else 0
|
||||||
|
|
||||||
val density = resources.displayMetrics.density
|
val density = resources.displayMetrics.density
|
||||||
val imeHeightDp = (imeHeight / density).toInt()
|
val imeHeightDp = (imeHeight / density).toInt()
|
||||||
|
|
||||||
// Also track system bars (navigation bar, status bar) changes
|
// Also track system bars (navigation bar, status bar) changes
|
||||||
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
val navBarHeight = systemBarsInsets.bottom
|
val navBarHeight = systemBarsInsets.bottom
|
||||||
val navBarHeightDp = (navBarHeight / density).toInt()
|
val navBarHeightDp = (navBarHeight / density).toInt()
|
||||||
val statusBarHeight = systemBarsInsets.top
|
val statusBarHeight = systemBarsInsets.top
|
||||||
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
val statusBarHeightDp = (statusBarHeight / density).toInt()
|
||||||
|
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
qtInitialized.await()
|
qtInitialized.await()
|
||||||
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
QtAndroidController.onImeInsetsChanged(imeHeightDp)
|
||||||
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return windowInsets instead of CONSUMED to allow proper handling
|
// Return windowInsets instead of CONSUMED to allow proper handling
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
isActivityResumed = false
|
|
||||||
hasWindowFocus = false
|
|
||||||
// Cancel all pending operations when activity is destroyed
|
|
||||||
resumeHandler.removeCallbacksAndMessages(null)
|
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
notificationStateReceiver = null
|
notificationStateReceiver = null
|
||||||
@@ -807,13 +687,9 @@ class AmneziaActivity : QtActivity() {
|
|||||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}?.toString() ?: ""
|
}?.toString() ?: ""
|
||||||
Log.v(TAG, "Open file: $uri")
|
Log.v(TAG, "Open file: $uri")
|
||||||
if (uri.isNotEmpty()) {
|
mainScope.launch {
|
||||||
pendingOpenFileUri = uri
|
qtInitialized.await()
|
||||||
} else {
|
QtAndroidController.onFileOpened(uri)
|
||||||
mainScope.launch {
|
|
||||||
qtInitialized.await()
|
|
||||||
QtAndroidController.onFileOpened(uri)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -842,7 +718,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun getFd(fileName: String): Int {
|
fun getFd(fileName: String): Int {
|
||||||
Log.v(TAG, "Get fd for $fileName")
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
return blockingCall(Dispatchers.IO) {
|
return blockingCall {
|
||||||
try {
|
try {
|
||||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||||
pfd?.fd ?: -1
|
pfd?.fd ?: -1
|
||||||
@@ -1006,12 +882,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
||||||
|
|
||||||
/** Called from Qt (AndroidController) — CLI-570 VPN connect/disconnect heads-up. */
|
|
||||||
@Suppress("unused")
|
|
||||||
fun showVpnStateNotification(title: String, message: String) {
|
|
||||||
ServiceNotification.showVpnStateEvent(applicationContext, title, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun requestNotificationPermission() {
|
fun requestNotificationPermission() {
|
||||||
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notific
|
|||||||
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
||||||
const val NOTIFICATION_ID = 1337
|
const val NOTIFICATION_ID = 1337
|
||||||
|
|
||||||
const val VPN_STATE_EVENT_NOTIFICATION_ID = 1338
|
|
||||||
private const val VPN_STATE_EVENT_CHANNEL_ID = "org.amnezia.vpn.vpn_state_events"
|
|
||||||
|
|
||||||
private const val GET_ACTIVITY_REQUEST_CODE = 0
|
private const val GET_ACTIVITY_REQUEST_CODE = 0
|
||||||
private const val CONNECT_REQUEST_CODE = 1
|
private const val CONNECT_REQUEST_CODE = 1
|
||||||
private const val DISCONNECT_REQUEST_CODE = 2
|
private const val DISCONNECT_REQUEST_CODE = 2
|
||||||
@@ -165,42 +162,8 @@ class ServiceNotification(private val context: Context) {
|
|||||||
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
createNotificationChannel(
|
|
||||||
Builder(VPN_STATE_EVENT_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
|
||||||
.setShowBadge(false)
|
|
||||||
.setSound(null, null)
|
|
||||||
.setVibrationEnabled(false)
|
|
||||||
.setLightsEnabled(false)
|
|
||||||
.setName(context.getString(R.string.vpnStateEventChannelName))
|
|
||||||
.setDescription(context.getString(R.string.vpnStateEventChannelDescription))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Brief alert when VPN connects or disconnects (invoked from Qt via AmneziaActivity). */
|
|
||||||
fun showVpnStateEvent(context: Context, title: String, message: String) {
|
|
||||||
if (!context.isNotificationPermissionGranted()) return
|
|
||||||
val nm = NotificationManagerCompat.from(context)
|
|
||||||
val notification = NotificationCompat.Builder(context, VPN_STATE_EVENT_CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setContentText(message)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
|
||||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setOnlyAlertOnce(true)
|
|
||||||
.setContentIntent(
|
|
||||||
PendingIntent.getActivity(
|
|
||||||
context,
|
|
||||||
GET_ACTIVITY_REQUEST_CODE,
|
|
||||||
Intent(context, AmneziaActivity::class.java),
|
|
||||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
nm.notify(VPN_STATE_EVENT_NOTIFICATION_ID, notification)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -14,29 +11,8 @@ private const val TAG = "TvFilePicker"
|
|||||||
|
|
||||||
class TvFilePicker : ComponentActivity() {
|
class TvFilePicker : ComponentActivity() {
|
||||||
|
|
||||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
setResult(RESULT_OK, Intent().apply { data = it })
|
||||||
val intent = super.createIntent(context, input)
|
|
||||||
|
|
||||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
|
||||||
}
|
|
||||||
if (activitiesToResolveIntent.all {
|
|
||||||
val name = it.activityInfo.packageName
|
|
||||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
|
||||||
}) {
|
|
||||||
throw ActivityNotFoundException()
|
|
||||||
}
|
|
||||||
return intent
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
setResult(RESULT_OK, Intent().apply {
|
|
||||||
data = it
|
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
|
||||||
})
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +31,7 @@ class TvFilePicker : ComponentActivity() {
|
|||||||
private fun getFile() {
|
private fun getFile() {
|
||||||
try {
|
try {
|
||||||
Log.v(TAG, "getFile")
|
Log.v(TAG, "getFile")
|
||||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
fileChooseResultLauncher.launch("*/*")
|
||||||
} catch (_: ActivityNotFoundException) {
|
} catch (_: ActivityNotFoundException) {
|
||||||
Log.w(TAG, "Activity not found")
|
Log.w(TAG, "Activity not found")
|
||||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||||
|
|||||||
@@ -31,7 +31,4 @@ object QtAndroidController {
|
|||||||
|
|
||||||
external fun onImeInsetsChanged(heightDp: Int)
|
external fun onImeInsetsChanged(heightDp: Int)
|
||||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
||||||
|
|
||||||
external fun onActivityPaused()
|
|
||||||
external fun onActivityResumed()
|
|
||||||
}
|
}
|
||||||
@@ -83,26 +83,6 @@ add_compile_definitions(_WINSOCKAPI_)
|
|||||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
set(BUILD_WITH_QT6 ON)
|
set(BUILD_WITH_QT6 ON)
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||||
|
|
||||||
if(ANDROID)
|
|
||||||
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
|
||||||
# Only if Qt6CorePrivate is available (required by qtgamepad)
|
|
||||||
find_package(Qt6CorePrivate CONFIG QUIET)
|
|
||||||
if(Qt6CorePrivate_FOUND)
|
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
|
|
||||||
# Link both the C++ module and QML plugin
|
|
||||||
if(TARGET GamepadLegacy)
|
|
||||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
|
||||||
endif()
|
|
||||||
if(TARGET GamepadLegacyQuickPrivate)
|
|
||||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
|
||||||
endif()
|
|
||||||
message(STATUS "Gamepad support enabled for Android")
|
|
||||||
else()
|
|
||||||
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(LIBS ${LIBS} qt6keychain)
|
set(LIBS ${LIBS} qt6keychain)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ target_sources(${PROJECT} PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ set(LIBS ${LIBS}
|
|||||||
|
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
@@ -44,7 +43,6 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
|
|||||||
|
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos/macos_ne_vpn_notification.mm
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||||
@@ -133,7 +131,6 @@ target_sources(${PROJECT} PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
@@ -166,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
|
|||||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||||
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||||
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
|
||||||
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
COMMENT "Signing OpenVPNAdapter framework"
|
COMMENT "Signing OpenVPNAdapter framework"
|
||||||
|
|||||||
@@ -45,12 +45,9 @@ if(NOT IOS AND NOT MACOS_NE)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
if(NOT ANDROID)
|
||||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
|
||||||
)
|
|
||||||
if(ANDROID)
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.h
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -112,12 +109,9 @@ if(APPLE AND NOT IOS)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
if(NOT ANDROID)
|
||||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
|
||||||
)
|
|
||||||
if(ANDROID)
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CLIENT_ROOT_DIR}/platforms/android/android_notificationhandler.cpp
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -187,6 +181,7 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
|||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.h
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
@@ -199,6 +194,7 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
|||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ namespace apiDefs
|
|||||||
AmneziaFreeV3,
|
AmneziaFreeV3,
|
||||||
AmneziaPremiumV1,
|
AmneziaPremiumV1,
|
||||||
AmneziaPremiumV2,
|
AmneziaPremiumV2,
|
||||||
AmneziaTrialV2,
|
|
||||||
SelfHosted,
|
SelfHosted,
|
||||||
ExternalPremium,
|
ExternalPremium
|
||||||
ExternalTrial
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigSource {
|
enum ConfigSource {
|
||||||
@@ -34,7 +32,6 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String stackType("stack_type");
|
constexpr QLatin1String stackType("stack_type");
|
||||||
constexpr QLatin1String serviceType("service_type");
|
constexpr QLatin1String serviceType("service_type");
|
||||||
constexpr QLatin1String cliVersion("cli_version");
|
constexpr QLatin1String cliVersion("cli_version");
|
||||||
constexpr QLatin1String cliName("cli_name");
|
|
||||||
constexpr QLatin1String supportedProtocols("supported_protocols");
|
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||||
|
|
||||||
constexpr QLatin1String vpnKey("vpn_key");
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
@@ -56,13 +53,8 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||||
constexpr QLatin1String subscriptionExpiredByServer("subscription_expired_by_server");
|
|
||||||
constexpr QLatin1String subscription("subscription");
|
|
||||||
constexpr QLatin1String endDate("end_date");
|
|
||||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
constexpr QLatin1String subscriptionDescription("subscription_description");
|
||||||
constexpr QLatin1String termsOfUseUrl("terms_of_use_url");
|
|
||||||
constexpr QLatin1String privacyPolicyUrl("privacy_policy_url");
|
|
||||||
|
|
||||||
constexpr QLatin1String supportInfo("support_info");
|
constexpr QLatin1String supportInfo("support_info");
|
||||||
constexpr QLatin1String email("email");
|
constexpr QLatin1String email("email");
|
||||||
@@ -77,7 +69,6 @@ namespace apiDefs
|
|||||||
|
|
||||||
constexpr QLatin1String transactionId("transaction_id");
|
constexpr QLatin1String transactionId("transaction_id");
|
||||||
constexpr QLatin1String isTestPurchase("is_test_purchase");
|
constexpr QLatin1String isTestPurchase("is_test_purchase");
|
||||||
constexpr QLatin1String isInAppPurchase("is_in_app_purchase");
|
|
||||||
|
|
||||||
constexpr QLatin1String userCountryCode("user_country_code");
|
constexpr QLatin1String userCountryCode("user_country_code");
|
||||||
|
|
||||||
|
|||||||
@@ -3,32 +3,11 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||||
|
|
||||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
|
||||||
|
|
||||||
QDateTime subscriptionEndUtcFromString(const QString &subscriptionEndDate)
|
|
||||||
{
|
|
||||||
if (subscriptionEndDate.isEmpty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs).toUTC();
|
|
||||||
if (!endDate.isValid()) {
|
|
||||||
endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODate).toUTC();
|
|
||||||
}
|
|
||||||
return endDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString apiErrorMessageFromJson(const QJsonObject &jsonObj)
|
|
||||||
{
|
|
||||||
const QJsonValue value = jsonObj.value(QStringLiteral("message"));
|
|
||||||
return value.isString() ? value.toString().trimmed() : QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString escapeUnicode(const QString &input)
|
QString escapeUnicode(const QString &input)
|
||||||
{
|
{
|
||||||
QString output;
|
QString output;
|
||||||
@@ -45,30 +24,9 @@ namespace
|
|||||||
|
|
||||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
{
|
{
|
||||||
if (subscriptionEndDate.isEmpty()) {
|
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||||
return false;
|
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||||
}
|
return endDate < now;
|
||||||
const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate);
|
|
||||||
if (!endDate.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return endDate <= QDateTime::currentDateTimeUtc();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays)
|
|
||||||
{
|
|
||||||
if (subscriptionEndDate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate);
|
|
||||||
if (!endDate.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const QDateTime nowUtc = QDateTime::currentDateTimeUtc();
|
|
||||||
if (endDate <= nowUtc) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return endDate <= nowUtc.addDays(withinDays);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||||
@@ -100,24 +58,18 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
|
|||||||
};
|
};
|
||||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
constexpr QLatin1String serviceTrial("amnezia-trial");
|
|
||||||
constexpr QLatin1String serviceFree("amnezia-free");
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
constexpr QLatin1String serviceExternalTrial("external-trial");
|
|
||||||
|
|
||||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
if (serviceType == servicePremium) {
|
if (serviceType == servicePremium) {
|
||||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
} else if (serviceType == serviceTrial) {
|
|
||||||
return apiDefs::ConfigType::AmneziaTrialV2;
|
|
||||||
} else if (serviceType == serviceFree) {
|
} else if (serviceType == serviceFree) {
|
||||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
} else if (serviceType == serviceExternalPremium) {
|
} else if (serviceType == serviceExternalPremium) {
|
||||||
return apiDefs::ConfigType::ExternalPremium;
|
return apiDefs::ConfigType::ExternalPremium;
|
||||||
} else if (serviceType == serviceExternalTrial) {
|
|
||||||
return apiDefs::ConfigType::ExternalTrial;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -138,53 +90,39 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
const int httpStatusCodeConflict = 409;
|
const int httpStatusCodeConflict = 409;
|
||||||
const int httpStatusCodeNotFound = 404;
|
const int httpStatusCodeNotFound = 404;
|
||||||
const int httpStatusCodeNotImplemented = 501;
|
const int httpStatusCodeNotImplemented = 501;
|
||||||
const int httpStatusCodePaymentRequired = 402;
|
|
||||||
const int httpStatusCodeUnprocessableEntity = 422;
|
|
||||||
|
|
||||||
if (!sslErrors.empty()) {
|
if (!sslErrors.empty()) {
|
||||||
qDebug().noquote() << sslErrors;
|
qDebug().noquote() << sslErrors;
|
||||||
return amnezia::ErrorCode::ApiConfigSslError;
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
}
|
} else if (replyError == QNetworkReply::NoError) {
|
||||||
if (replyError == QNetworkReply::NoError) {
|
|
||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
}
|
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
|
||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
}
|
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
|
||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
}
|
} else {
|
||||||
|
qDebug() << QString::fromUtf8(responseBody);
|
||||||
|
qDebug() << replyError;
|
||||||
|
qDebug() << replyErrorString;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
|
||||||
qDebug() << QString::fromUtf8(responseBody);
|
int httpStatusFromBody = -1;
|
||||||
qDebug() << replyError;
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||||
qDebug() << replyErrorString;
|
if (jsonDoc.isObject()) {
|
||||||
qDebug() << httpStatusCode;
|
QJsonObject jsonObj = jsonDoc.object();
|
||||||
|
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
|
||||||
|
}
|
||||||
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
|
||||||
if (jsonDoc.isObject()) {
|
|
||||||
QJsonObject jsonObj = jsonDoc.object();
|
|
||||||
const int httpStatusFromBody = jsonObj.value(QStringLiteral("http_status")).toInt(-1);
|
|
||||||
if (httpStatusFromBody == httpStatusCodeConflict) {
|
if (httpStatusFromBody == httpStatusCodeConflict) {
|
||||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
}
|
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
|
||||||
if (httpStatusFromBody == httpStatusCodeNotFound) {
|
|
||||||
return amnezia::ErrorCode::ApiNotFoundError;
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
}
|
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
||||||
if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
}
|
}
|
||||||
if (httpStatusFromBody == httpStatusCodeUnprocessableEntity) {
|
|
||||||
if (apiErrorMessageFromJson(jsonObj) == unprocessableSubscriptionMessage) {
|
|
||||||
return amnezia::ErrorCode::ApiSubscriptionExpiredError;
|
|
||||||
}
|
|
||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
|
||||||
}
|
|
||||||
if (httpStatusFromBody == httpStatusCodePaymentRequired) {
|
|
||||||
return amnezia::ErrorCode::ApiSubscriptionNotActiveError;
|
|
||||||
}
|
|
||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,8 +133,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||||
apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium,
|
apiDefs::ConfigType::ExternalPremium };
|
||||||
apiDefs::ConfigType::ExternalTrial };
|
|
||||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,9 +177,7 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
auto configType = apiUtils::getConfigType(serverConfigObject);
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
|
||||||
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2
|
|
||||||
&& configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ namespace apiUtils
|
|||||||
|
|
||||||
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||||
|
|
||||||
bool isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays = 10);
|
|
||||||
|
|
||||||
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
|
|||||||
@@ -8,11 +8,6 @@
|
|||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// SystemTrayNotificationHandler exists only on desktop + macOS NE builds (see client/cmake/sources.cmake).
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
#include "ui/systemtray_notificationhandler.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
#if defined(Q_OS_IOS)
|
||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#include <AmneziaVPN-Swift.h>
|
#include <AmneziaVPN-Swift.h>
|
||||||
@@ -96,12 +91,6 @@ void CoreController::initModels()
|
|||||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||||
|
|
||||||
m_apiSubscriptionPlansModel.reset(new ApiSubscriptionPlansModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel.get());
|
|
||||||
|
|
||||||
m_apiBenefitsModel.reset(new ApiBenefitsModel(this));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiBenefitsModel", m_apiBenefitsModel.get());
|
|
||||||
|
|
||||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||||
|
|
||||||
@@ -146,7 +135,7 @@ void CoreController::initControllers()
|
|||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
|
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||||
@@ -162,11 +151,8 @@ void CoreController::initControllers()
|
|||||||
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
m_apiConfigsController.reset(
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
new ApiConfigsController(m_serversModel, m_apiServicesModel, m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_settings));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded,
|
|
||||||
this, [this]() { m_apiSettingsController->getAccountInfo(false); });
|
|
||||||
|
|
||||||
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||||
@@ -247,6 +233,7 @@ void CoreController::initSignalHandlers()
|
|||||||
|
|
||||||
void CoreController::initNotificationHandler()
|
void CoreController::initNotificationHandler()
|
||||||
{
|
{
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
@@ -259,11 +246,9 @@ void CoreController::initNotificationHandler()
|
|||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
|
||||||
if (auto *trayHandler = qobject_cast<SystemTrayNotificationHandler *>(m_notificationHandler.get())) {
|
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||||
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
#endif
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::updateTranslator(const QLocale &locale)
|
void CoreController::updateTranslator(const QLocale &locale)
|
||||||
@@ -383,11 +368,7 @@ void CoreController::initPrepareConfigHandler()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_installController->validateConfig();
|
if (!m_installController->isConfigValid()) {
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
|
|
||||||
if (!isValid) {
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "ui/notificationhandler.h"
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
#include "ui/systemtray_notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
@@ -30,11 +32,9 @@
|
|||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
#include "ui/models/api/apiBenefitsModel.h"
|
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
#include "ui/models/api/apiDevicesModel.h"
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
#include "ui/models/api/apiSubscriptionPlansModel.h"
|
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
@@ -49,6 +49,10 @@
|
|||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/newsModel.h"
|
#include "ui/models/newsModel.h"
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
#include "ui/notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class CoreController : public QObject
|
class CoreController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -95,7 +99,9 @@ private:
|
|||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QSharedPointer<QTranslator> m_translator;
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||||
|
|
||||||
@@ -127,8 +133,6 @@ private:
|
|||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
QSharedPointer<ApiSubscriptionPlansModel> m_apiSubscriptionPlansModel;
|
|
||||||
QSharedPointer<ApiBenefitsModel> m_apiBenefitsModel;
|
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|||||||
@@ -44,11 +44,8 @@ namespace
|
|||||||
|
|
||||||
constexpr int httpStatusCodeNotFound = 404;
|
constexpr int httpStatusCodeNotFound = 404;
|
||||||
constexpr int httpStatusCodeConflict = 409;
|
constexpr int httpStatusCodeConflict = 409;
|
||||||
constexpr int httpStatusCodeNotImplemented = 501;
|
|
||||||
constexpr int httpStatusCodePaymentRequired = 402;
|
|
||||||
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
|
||||||
|
|
||||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
constexpr int httpStatusCodeNotImplemented = 501;
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
@@ -336,20 +333,11 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
|
|
||||||
QStringList baseUrls;
|
QStringList baseUrls;
|
||||||
if (m_isDevEnvironment) {
|
if (m_isDevEnvironment) {
|
||||||
baseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||||
} else {
|
} else {
|
||||||
baseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseUrls.empty()) {
|
|
||||||
qDebug() << "empty storage endpoint list";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::random_device randomDevice;
|
|
||||||
std::mt19937 generator(randomDevice());
|
|
||||||
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
|
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
QStringList proxyStorageUrls;
|
QStringList proxyStorageUrls;
|
||||||
@@ -424,14 +412,12 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
|||||||
{
|
{
|
||||||
const QByteArray &responseBody = decryptedResponseBody;
|
const QByteArray &responseBody = decryptedResponseBody;
|
||||||
|
|
||||||
int apiHttpStatus = -1;
|
int httpStatus = -1;
|
||||||
QString apiErrorMessage;
|
|
||||||
if (isDecryptionSuccessful) {
|
if (isDecryptionSuccessful) {
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||||
if (jsonDoc.isObject()) {
|
if (jsonDoc.isObject()) {
|
||||||
QJsonObject jsonObj = jsonDoc.object();
|
QJsonObject jsonObj = jsonDoc.object();
|
||||||
apiHttpStatus = jsonObj.value("http_status").toInt(-1);
|
httpStatus = jsonObj.value("http_status").toInt(-1);
|
||||||
apiErrorMessage = jsonObj.value(QStringLiteral("message")).toString().trimmed();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "failed to decrypt the data";
|
qDebug() << "failed to decrypt the data";
|
||||||
@@ -442,12 +428,10 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
|||||||
qDebug() << "timeout occurred";
|
qDebug() << "timeout occurred";
|
||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (responseBody.contains("html")) {
|
||||||
if (responseBody.contains("html")) {
|
|
||||||
qDebug() << "the response contains an html tag";
|
qDebug() << "the response contains an html tag";
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (httpStatus == httpStatusCodeNotFound) {
|
||||||
if (apiHttpStatus == httpStatusCodeNotFound) {
|
|
||||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|| responseBody.contains(errorResponsePattern3)) {
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -455,25 +439,16 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
|
|||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
} else if (httpStatus == httpStatusCodeNotImplemented) {
|
||||||
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
|
||||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
} else if (httpStatus == httpStatusCodeConflict) {
|
||||||
if (apiHttpStatus == httpStatusCodeConflict) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
} else if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||||
if (apiHttpStatus == httpStatusCodePaymentRequired) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (apiHttpStatus == httpStatusCodeUnprocessableEntity) {
|
|
||||||
return apiErrorMessage != unprocessableSubscriptionMessage;
|
|
||||||
}
|
|
||||||
if (replyError != QNetworkReply::NetworkError::NoError) {
|
|
||||||
qDebug() << replyError;
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,18 +419,6 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
||||||
if (container == DockerContainer::Awg2) {
|
|
||||||
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
|
||||||
QRegularExpressionMatch match = regex.match(stdOut);
|
|
||||||
if (match.hasMatch()) {
|
|
||||||
int majorVersion = match.captured(1).toInt();
|
|
||||||
int minorVersion = match.captured(2).toInt();
|
|
||||||
|
|
||||||
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
|
|
||||||
return ErrorCode::ServerLinuxKernelTooOld;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stdOut.contains("lock"))
|
if (stdOut.contains("lock"))
|
||||||
return ErrorCode::ServerPacketManagerError;
|
return ErrorCode::ServerPacketManagerError;
|
||||||
if (stdOut.contains("command not found"))
|
if (stdOut.contains("command not found"))
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ namespace amnezia
|
|||||||
ServerDockerOnCgroupsV2 = 211,
|
ServerDockerOnCgroupsV2 = 211,
|
||||||
ServerCgroupMountpoint = 212,
|
ServerCgroupMountpoint = 212,
|
||||||
DockerPullRateLimit = 213,
|
DockerPullRateLimit = 213,
|
||||||
ServerLinuxKernelTooOld = 214,
|
|
||||||
|
|
||||||
// Ssh connection errors
|
// Ssh connection errors
|
||||||
SshRequestDeniedError = 300,
|
SshRequestDeniedError = 300,
|
||||||
@@ -123,8 +122,6 @@ namespace amnezia
|
|||||||
ApiUpdateRequestError = 1111,
|
ApiUpdateRequestError = 1111,
|
||||||
ApiSubscriptionExpiredError = 1112,
|
ApiSubscriptionExpiredError = 1112,
|
||||||
ApiPurchaseError = 1113,
|
ApiPurchaseError = 1113,
|
||||||
ApiSubscriptionNotActiveError = 1114,
|
|
||||||
ApiNoPurchasedSubscriptionsError = 1115,
|
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
|
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
|
||||||
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||||
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
||||||
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
|
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
@@ -80,8 +79,6 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||||
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||||
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
|
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
|
||||||
case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break;
|
|
||||||
case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break;
|
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|||||||
+63
-37
@@ -7,6 +7,7 @@ IpcClient::IpcClient(QObject *parent) : QObject(parent)
|
|||||||
{
|
{
|
||||||
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
|
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
|
||||||
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
|
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
|
||||||
|
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient& IpcClient::Instance()
|
IpcClient& IpcClient::Instance()
|
||||||
@@ -32,43 +33,68 @@ QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
|||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
|
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
||||||
{
|
{
|
||||||
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
|
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
|
||||||
auto createPrivilegedProcess = iface->createPrivilegedProcess();
|
if (rep.isNull()) {
|
||||||
if (!createPrivilegedProcess.waitForFinished()) {
|
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
|
||||||
qCritical() << "Failed to create privileged process";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int pid = createPrivilegedProcess.returnValue();
|
|
||||||
|
|
||||||
auto* node = new QRemoteObjectNode();
|
|
||||||
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
|
|
||||||
|
|
||||||
QSharedPointer<IpcProcessInterfaceReplica> rep(
|
|
||||||
node->acquire<IpcProcessInterfaceReplica>(),
|
|
||||||
[node] (IpcProcessInterfaceReplica *ptr) {
|
|
||||||
delete ptr;
|
|
||||||
node->deleteLater();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (rep.isNull()) {
|
|
||||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (!rep->waitForSource()) {
|
|
||||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (!rep->isReplicaValid()) {
|
|
||||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rep;
|
|
||||||
},
|
|
||||||
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
});
|
}
|
||||||
|
if (!rep->waitForSource(1000)) {
|
||||||
|
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!rep->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
|
||||||
|
}
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||||
|
{
|
||||||
|
QSharedPointer<IpcInterfaceReplica> rep = Interface();
|
||||||
|
if (!rep) {
|
||||||
|
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
|
||||||
|
if (!pidReply.waitForFinished(5000)){
|
||||||
|
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pid = pidReply.returnValue();
|
||||||
|
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
|
||||||
|
|
||||||
|
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
|
||||||
|
|
||||||
|
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
|
||||||
|
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
|
||||||
|
|
||||||
|
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
|
||||||
|
// TODO: rework the unsafe cast below
|
||||||
|
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
|
||||||
|
pd->ipcProcess.reset(priv);
|
||||||
|
if (!pd->ipcProcess) {
|
||||||
|
qWarning() << "Acquire PrivilegedProcess failed";
|
||||||
|
} else {
|
||||||
|
pd->ipcProcess->waitForSource(1000);
|
||||||
|
if (!pd->ipcProcess->isReplicaValid()) {
|
||||||
|
qWarning() << "PrivilegedProcess replica is not connected!";
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
|
||||||
|
[pd]() { pd->replicaNode->deleteLater(); });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
||||||
|
if (!pd->localSocket->waitForConnected()) {
|
||||||
|
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||||
|
return processReplica;
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -5,7 +5,9 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "rep_ipc_interface_replica.h"
|
#include "rep_ipc_interface_replica.h"
|
||||||
#include "rep_ipc_process_interface_replica.h"
|
#include "rep_ipc_process_tun2socks_replica.h"
|
||||||
|
|
||||||
|
#include "privileged_process.h"
|
||||||
|
|
||||||
class IpcClient : public QObject
|
class IpcClient : public QObject
|
||||||
{
|
{
|
||||||
@@ -16,7 +18,8 @@ public:
|
|||||||
static IpcClient& Instance();
|
static IpcClient& Instance();
|
||||||
|
|
||||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||||
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
|
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
||||||
|
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||||
|
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
static auto withInterface(Func func)
|
static auto withInterface(Func func)
|
||||||
@@ -51,6 +54,18 @@ signals:
|
|||||||
private:
|
private:
|
||||||
QRemoteObjectNode m_node;
|
QRemoteObjectNode m_node;
|
||||||
QSharedPointer<IpcInterfaceReplica> m_interface;
|
QSharedPointer<IpcInterfaceReplica> m_interface;
|
||||||
|
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
|
||||||
|
|
||||||
|
struct ProcessDescriptor {
|
||||||
|
ProcessDescriptor () {
|
||||||
|
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
|
||||||
|
ipcProcess = QSharedPointer<PrivilegedProcess>();
|
||||||
|
localSocket = QSharedPointer<QLocalSocket>();
|
||||||
|
}
|
||||||
|
QSharedPointer<PrivilegedProcess> ipcProcess;
|
||||||
|
QSharedPointer<QRemoteObjectNode> replicaNode;
|
||||||
|
QSharedPointer<QLocalSocket> localSocket;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPCCLIENT_H
|
#endif // IPCCLIENT_H
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "privileged_process.h"
|
||||||
|
|
||||||
|
PrivilegedProcess::PrivilegedProcess() :
|
||||||
|
IpcProcessInterfaceReplica()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivilegedProcess::~PrivilegedProcess()
|
||||||
|
{
|
||||||
|
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrivilegedProcess::waitForFinished(int msecs)
|
||||||
|
{
|
||||||
|
QSharedPointer<QEventLoop> loop(new QEventLoop);
|
||||||
|
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
|
||||||
|
loop->quit();
|
||||||
|
loop.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
QTimer::singleShot(msecs, this, [this, loop]() mutable {
|
||||||
|
loop->quit();
|
||||||
|
loop.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
loop->exec();
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef PRIVILEGED_PROCESS_H
|
||||||
|
#define PRIVILEGED_PROCESS_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "rep_ipc_process_interface_replica.h"
|
||||||
|
// This class is dangerous - instance of this class casted from base class,
|
||||||
|
// so it support only functions
|
||||||
|
// Do not add any members into it
|
||||||
|
//
|
||||||
|
class PrivilegedProcess : public IpcProcessInterfaceReplica
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
PrivilegedProcess();
|
||||||
|
~PrivilegedProcess() override;
|
||||||
|
|
||||||
|
void waitForFinished(int msecs);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PRIVILEGED_PROCESS_H
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M15 21V17C15 16.4696 15.2107 15.9609 15.5858 15.5858C15.9609 15.2107 16.4696 15 17 15H21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M7 4V6C7.21572 6.61347 7.62494 7.14024 8.16602 7.50096C8.7071 7.86168 9.35075 8.03682 10 8V8C10.5304 8 11.0391 8.21071 11.4142 8.58579C11.7893 8.96086 12 9.46957 12 10C12 10.5304 12.2107 11.0391 12.5858 11.4142C12.9609 11.7893 13.4696 12 14 12C14.5304 12 15.0391 11.7893 15.4142 11.4142C15.7893 11.0391 16 10.5304 16 10C16 9.46957 16.2107 8.96086 16.5858 8.58579C16.9609 8.21071 17.4696 8 18 8H21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M3 11H5C5.53043 11 6.03914 11.2107 6.41421 11.5858C6.78929 11.9609 7 12.4696 7 13V14C7 14.5304 7.21071 15.0391 7.58579 15.4142C7.96086 15.7893 8.46957 16 9 16C9.53043 16 10.0391 16.2107 10.4142 16.5858C10.7893 16.9609 11 17.4696 11 18V22" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M18.1777 8C23.2737 8 23.2737 16 18.1777 16C13.0827 16 11.0447 8 5.43875 8C0.85375 8 0.85375 16 5.43875 16C11.0447 16 13.0828 8 18.1788 8H18.1777Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 342 B |
@@ -1,4 +0,0 @@
|
|||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M17 2H7C5.89543 2 5 2.89543 5 4V20C5 21.1046 5.89543 22 7 22H17C18.1046 22 19 21.1046 19 20V4C19 2.89543 18.1046 2 17 2Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M12 18H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 423 B |
@@ -270,7 +270,12 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
|
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
|
||||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
|
|||||||
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
||||||
&NetworkWatcher::unsecuredNetwork);
|
&NetworkWatcher::unsecuredNetwork);
|
||||||
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
||||||
&NetworkWatcher::networkChanged);
|
&NetworkWatcher::networkChange);
|
||||||
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
|
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
|
||||||
&NetworkWatcher::wakeup);
|
&NetworkWatcher::onSleepMode);
|
||||||
m_impl->initialize();
|
m_impl->initialize();
|
||||||
|
|
||||||
// Enable sleep/wake monitoring for VPN auto-reconnection
|
// Enable sleep/wake monitoring for VPN auto-reconnection
|
||||||
@@ -97,6 +97,12 @@ void NetworkWatcher::settingsChanged() {
|
|||||||
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetworkWatcher::onSleepMode()
|
||||||
|
{
|
||||||
|
logger.debug() << "Resumed from sleep mode";
|
||||||
|
emit sleepMode();
|
||||||
|
}
|
||||||
|
|
||||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||||
const QString& networkId) {
|
const QString& networkId) {
|
||||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ public:
|
|||||||
// false to restore.
|
// false to restore.
|
||||||
void simulateDisconnection(bool simulatedDisconnection);
|
void simulateDisconnection(bool simulatedDisconnection);
|
||||||
|
|
||||||
|
void onSleepMode();
|
||||||
|
|
||||||
QNetworkInformation::Reachability getReachability();
|
QNetworkInformation::Reachability getReachability();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void networkChanged();
|
void networkChange();
|
||||||
void wakeup();
|
void sleepMode();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void settingsChanged();
|
void settingsChanged();
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ signals:
|
|||||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||||
// too.
|
// too.
|
||||||
void networkChanged(QString newBSSID);
|
void networkChanged(QString newBSSID);
|
||||||
void wakeup();
|
void sleepMode();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -101,9 +101,7 @@ bool AndroidController::initialize()
|
|||||||
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
||||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
|
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
|
||||||
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
|
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
|
||||||
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)},
|
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
|
||||||
{"onActivityPaused", "()V", reinterpret_cast<void *>(onActivityPaused)},
|
|
||||||
{"onActivityResumed", "()V", reinterpret_cast<void *>(onActivityResumed)}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QJniEnvironment env;
|
QJniEnvironment env;
|
||||||
@@ -307,16 +305,6 @@ void AndroidController::requestNotificationPermission()
|
|||||||
callActivityMethod("requestNotificationPermission", "()V");
|
callActivityMethod("requestNotificationPermission", "()V");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidController::showVpnStateNotification(const QString &title, const QString &message)
|
|
||||||
{
|
|
||||||
if (!isNotificationPermissionGranted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callActivityMethod("showVpnStateNotification", "(Ljava/lang/String;Ljava/lang/String;)V",
|
|
||||||
QJniObject::fromString(title).object<jstring>(),
|
|
||||||
QJniObject::fromString(message).object<jstring>());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AndroidController::requestAuthentication()
|
bool AndroidController::requestAuthentication()
|
||||||
{
|
{
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
@@ -570,22 +558,3 @@ void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jin
|
|||||||
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
|
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
|
||||||
void AndroidController::onActivityPaused(JNIEnv *env, jobject thiz)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
emit AndroidController::instance()->activityPaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void AndroidController::onActivityResumed(JNIEnv *env, jobject thiz)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
emit AndroidController::instance()->activityResumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ public:
|
|||||||
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||||
bool isNotificationPermissionGranted();
|
bool isNotificationPermissionGranted();
|
||||||
void requestNotificationPermission();
|
void requestNotificationPermission();
|
||||||
void showVpnStateNotification(const QString &title, const QString &message);
|
|
||||||
bool requestAuthentication();
|
bool requestAuthentication();
|
||||||
void sendTouch(float x, float y);
|
void sendTouch(float x, float y);
|
||||||
|
|
||||||
@@ -76,8 +75,6 @@ signals:
|
|||||||
void authenticationResult(bool result);
|
void authenticationResult(bool result);
|
||||||
void imeInsetsChanged(int heightDp);
|
void imeInsetsChanged(int heightDp);
|
||||||
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
|
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
|
||||||
void activityPaused();
|
|
||||||
void activityResumed();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isWaitingStatus = true;
|
bool isWaitingStatus = true;
|
||||||
@@ -108,8 +105,6 @@ private:
|
|||||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||||
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
|
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
|
||||||
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
|
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
|
||||||
static void onActivityPaused(JNIEnv *env, jobject thiz);
|
|
||||||
static void onActivityResumed(JNIEnv *env, jobject thiz);
|
|
||||||
|
|
||||||
template <typename Ret, typename ...Args>
|
template <typename Ret, typename ...Args>
|
||||||
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
#include "android_notificationhandler.h"
|
|
||||||
|
|
||||||
#include "android_controller.h"
|
|
||||||
|
|
||||||
AndroidNotificationHandler::AndroidNotificationHandler(QObject *parent)
|
|
||||||
: NotificationHandler(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidNotificationHandler::notify(Message type, const QString &title, const QString &message, int timerMsec)
|
|
||||||
{
|
|
||||||
Q_UNUSED(type);
|
|
||||||
Q_UNUSED(timerMsec);
|
|
||||||
// Permission is checked on the Kotlin side as well; avoid JNI if already denied.
|
|
||||||
if (!AndroidController::instance()->isNotificationPermissionGranted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AndroidController::instance()->showVpnStateNotification(title, message);
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#ifndef ANDROID_NOTIFICATIONHANDLER_H
|
|
||||||
#define ANDROID_NOTIFICATIONHANDLER_H
|
|
||||||
|
|
||||||
#include "ui/notificationhandler.h"
|
|
||||||
|
|
||||||
class AndroidNotificationHandler final : public NotificationHandler {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AndroidNotificationHandler(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void notify(Message type, const QString &title, const QString &message, int timerMsec) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ANDROID_NOTIFICATIONHANDLER_H
|
|
||||||
@@ -126,7 +126,8 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
self?.handleOpenVPNReachabilityChange(status)
|
guard status == .reachableViaWiFi else { return }
|
||||||
|
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
|
|||||||
@@ -21,44 +21,6 @@ extension Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension PacketTunnelProvider {
|
extension PacketTunnelProvider {
|
||||||
private func applyXraySplitTunnel(_ xrayConfig: XrayConfig,
|
|
||||||
settings: NEPacketTunnelNetworkSettings) {
|
|
||||||
guard let splitTunnelType = xrayConfig.splitTunnelType else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let splitTunnelSites = xrayConfig.splitTunnelSites else {
|
|
||||||
xrayLog(.error, message: "Split tunnel sites are not set")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if splitTunnelType == 1 {
|
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
|
||||||
|
|
||||||
for allowedIPString in splitTunnelSites {
|
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
|
||||||
destinationAddress: "\(allowedIP.address)",
|
|
||||||
subnetMask: "\(allowedIP.subnetMask())"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
|
|
||||||
} else if splitTunnelType == 2 {
|
|
||||||
var ipv4ExcludedRoutes = [NEIPv4Route]()
|
|
||||||
|
|
||||||
for excludedIPString in splitTunnelSites {
|
|
||||||
if let excludedIP = IPAddressRange(from: excludedIPString) {
|
|
||||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
|
||||||
destinationAddress: "\(excludedIP.address)",
|
|
||||||
subnetMask: "\(excludedIP.subnetMask())"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startXray(completionHandler: @escaping (Error?) -> Void) {
|
func startXray(completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
|
||||||
// Xray configuration
|
// Xray configuration
|
||||||
@@ -110,7 +72,6 @@ extension PacketTunnelProvider {
|
|||||||
settings.dnsSettings = !dnsArray.isEmpty
|
settings.dnsSettings = !dnsArray.isEmpty
|
||||||
? NEDNSSettings(servers: dnsArray)
|
? NEDNSSettings(servers: dnsArray)
|
||||||
: NEDNSSettings(servers: ["1.1.1.1"])
|
: NEDNSSettings(servers: ["1.1.1.1"])
|
||||||
applyXraySplitTunnel(xrayConfig, settings: settings)
|
|
||||||
|
|
||||||
let xrayConfigData = xrayConfig.config.data(using: .utf8)
|
let xrayConfigData = xrayConfig.config.data(using: .utf8)
|
||||||
|
|
||||||
|
|||||||
@@ -41,15 +41,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
var ovpnAdapter: OpenVPNAdapter?
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
||||||
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
||||||
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
|
|
||||||
private let pathMonitor = NWPathMonitor()
|
private let pathMonitor = NWPathMonitor()
|
||||||
private var didReceiveInitialPathUpdate = false
|
private var didReceiveInitialPathUpdate = false
|
||||||
private var currentPath: Network.NWPath?
|
private var currentPath: Network.NWPath?
|
||||||
private var currentPathSignature: String?
|
private var currentPathSignature: String?
|
||||||
private var pendingOpenVPNReconnectWorkItem: DispatchWorkItem?
|
|
||||||
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
|
|
||||||
private var isApplyingNetworkChange = false
|
|
||||||
private var lastOpenVPNReachabilityStatus: OpenVPNReachabilityStatus?
|
|
||||||
|
|
||||||
var splitTunnelType: Int?
|
var splitTunnelType: Int?
|
||||||
var splitTunnelSites: [String]?
|
var splitTunnelSites: [String]?
|
||||||
@@ -83,22 +78,14 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
||||||
|
|
||||||
// WireGuard/AWG manages network changes internally in its own adapter.
|
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
|
||||||
if proto == .wireguard {
|
if proto == .wireguard {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if proto == .openvpn {
|
DispatchQueue.main.async {
|
||||||
self.scheduleOpenVPNReconnect(reason: "NWPath changed")
|
self.handle(networkChange: path) { _ in }
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.isApplyingNetworkChange || self.reasserting {
|
|
||||||
xrayLog(.debug, message: "Ignoring path change while xray restart is in progress")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scheduleNetworkChangeHandling(for: proto, path: path)
|
|
||||||
}
|
}
|
||||||
pathMonitor.start(queue: pathMonitorQueue)
|
pathMonitor.start(queue: pathMonitorQueue)
|
||||||
|
|
||||||
@@ -210,8 +197,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelPendingOpenVPNReconnect()
|
|
||||||
cancelPendingNetworkChangeHandling()
|
|
||||||
didReceiveInitialPathUpdate = false
|
didReceiveInitialPathUpdate = false
|
||||||
updateActiveInterfaceIndexForCurrentPath()
|
updateActiveInterfaceIndexForCurrentPath()
|
||||||
|
|
||||||
@@ -230,9 +215,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
|
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
cancelPendingOpenVPNReconnect()
|
|
||||||
cancelPendingNetworkChangeHandling()
|
|
||||||
|
|
||||||
guard let protoType else {
|
guard let protoType else {
|
||||||
completionHandler()
|
completionHandler()
|
||||||
return
|
return
|
||||||
@@ -277,111 +259,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
guard protoType == .xray else {
|
|
||||||
updateActiveInterfaceIndex(for: changePath)
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateActiveInterfaceIndex(for: changePath)
|
updateActiveInterfaceIndex(for: changePath)
|
||||||
reasserting = true
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
xrayLog(.info, message: "Applying network change to xray tunnel")
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
stopXray { }
|
|
||||||
startXray { [weak self] error in
|
|
||||||
self?.reasserting = false
|
|
||||||
completion(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func scheduleNetworkChangeHandling(for proto: TunnelProtoType, path: Network.NWPath) {
|
|
||||||
guard proto == .xray else { return }
|
|
||||||
|
|
||||||
pendingNetworkChangeWorkItem?.cancel()
|
|
||||||
|
|
||||||
let workItem = DispatchWorkItem { [weak self] in
|
|
||||||
guard let self else { return }
|
|
||||||
self.pendingNetworkChangeWorkItem = nil
|
|
||||||
|
|
||||||
if self.isApplyingNetworkChange || self.reasserting {
|
|
||||||
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.isApplyingNetworkChange = true
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.handle(networkChange: path) { [weak self] _ in
|
|
||||||
self?.networkChangeQueue.async {
|
|
||||||
self?.isApplyingNetworkChange = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingNetworkChangeWorkItem = workItem
|
|
||||||
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func scheduleOpenVPNReconnect(reason: String) {
|
|
||||||
guard protoType == .openvpn else { return }
|
|
||||||
|
|
||||||
pendingOpenVPNReconnectWorkItem?.cancel()
|
|
||||||
|
|
||||||
let workItem = DispatchWorkItem { [weak self] in
|
|
||||||
guard let self else { return }
|
|
||||||
self.pendingOpenVPNReconnectWorkItem = nil
|
|
||||||
|
|
||||||
guard self.protoType == .openvpn else { return }
|
|
||||||
|
|
||||||
if self.reasserting {
|
|
||||||
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
guard let self else { return }
|
|
||||||
guard !self.reasserting else {
|
|
||||||
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ovpnLog(.info, message: "\(reason), reconnecting OpenVPN session")
|
|
||||||
self.ovpnAdapter?.reconnect(afterTimeInterval: 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingOpenVPNReconnectWorkItem = workItem
|
|
||||||
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleOpenVPNReachabilityChange(_ status: OpenVPNReachabilityStatus) {
|
|
||||||
defer { lastOpenVPNReachabilityStatus = status }
|
|
||||||
|
|
||||||
guard let previousStatus = lastOpenVPNReachabilityStatus else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard previousStatus != status else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case .reachableViaWiFi, .reachableViaWWAN:
|
|
||||||
scheduleOpenVPNReconnect(reason: "Reachability changed")
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func cancelPendingOpenVPNReconnect() {
|
|
||||||
pendingOpenVPNReconnectWorkItem?.cancel()
|
|
||||||
pendingOpenVPNReconnectWorkItem = nil
|
|
||||||
lastOpenVPNReachabilityStatus = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private func cancelPendingNetworkChangeHandling() {
|
|
||||||
pendingNetworkChangeWorkItem?.cancel()
|
|
||||||
pendingNetworkChangeWorkItem = nil
|
|
||||||
isApplyingNetworkChange = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,14 +271,8 @@ private extension PacketTunnelProvider {
|
|||||||
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
|
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
|
||||||
signatureComponents.append(path.isConstrained ? "con" : "nocon")
|
signatureComponents.append(path.isConstrained ? "con" : "nocon")
|
||||||
|
|
||||||
// Ignore loopback and tunnel-style `.other` interfaces so Xray does not
|
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
|
||||||
// react to its own utun lifecycle as if the physical uplink changed.
|
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
|
||||||
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular]
|
|
||||||
let externalInterfaces = path.availableInterfaces.filter { interface in
|
|
||||||
interface.type == .wiredEthernet || interface.type == .wifi || interface.type == .cellular
|
|
||||||
}
|
|
||||||
|
|
||||||
let sortedInterfaces = externalInterfaces.sorted { lhs, rhs in
|
|
||||||
if lhs.type == rhs.type {
|
if lhs.type == rhs.type {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
@@ -419,8 +293,8 @@ private extension PacketTunnelProvider {
|
|||||||
case .wiredEthernet: typeName = "ethernet"
|
case .wiredEthernet: typeName = "ethernet"
|
||||||
case .wifi: typeName = "wifi"
|
case .wifi: typeName = "wifi"
|
||||||
case .cellular: typeName = "cellular"
|
case .cellular: typeName = "cellular"
|
||||||
case .loopback, .other:
|
case .loopback: typeName = "loopback"
|
||||||
continue
|
case .other: typeName = "other"
|
||||||
@unknown default: typeName = "unknown"
|
@unknown default: typeName = "unknown"
|
||||||
}
|
}
|
||||||
signatureComponents.append("\(typeName):\(interface.index)")
|
signatureComponents.append("\(typeName):\(interface.index)")
|
||||||
|
|||||||
@@ -1,178 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import StoreKit
|
|
||||||
|
|
||||||
@available(iOS 15.0, macOS 12.0, *)
|
|
||||||
@objcMembers
|
|
||||||
public class StoreKit2Helper: NSObject {
|
|
||||||
|
|
||||||
public static let shared = StoreKit2Helper()
|
|
||||||
private static let errorDomain = "StoreKit2Helper"
|
|
||||||
|
|
||||||
private struct EntitlementInfo {
|
|
||||||
let transactionId: UInt64
|
|
||||||
let originalTransactionId: UInt64
|
|
||||||
let productId: String
|
|
||||||
let purchaseDate: Date
|
|
||||||
|
|
||||||
var dictionary: NSDictionary {
|
|
||||||
[
|
|
||||||
"transactionId": String(transactionId),
|
|
||||||
"originalTransactionId": String(originalTransactionId),
|
|
||||||
"productId": productId
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func fetchCurrentEntitlements(completion: @escaping (Bool, [NSDictionary]?, NSError?) -> Void) {
|
|
||||||
Task { @MainActor in
|
|
||||||
do {
|
|
||||||
try await AppStore.sync()
|
|
||||||
|
|
||||||
var entitlements: [EntitlementInfo] = []
|
|
||||||
for await result in Transaction.currentEntitlements {
|
|
||||||
switch result {
|
|
||||||
case .verified(let transaction):
|
|
||||||
entitlements.append(EntitlementInfo(transactionId: transaction.id,
|
|
||||||
originalTransactionId: transaction.originalID,
|
|
||||||
productId: transaction.productID,
|
|
||||||
purchaseDate: transaction.purchaseDate))
|
|
||||||
case .unverified(_, let error):
|
|
||||||
print("[IAP][StoreKit2] Unverified transaction skipped: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let sortedEntitlements = entitlements.sorted { lhs, rhs in
|
|
||||||
if lhs.purchaseDate != rhs.purchaseDate {
|
|
||||||
return lhs.purchaseDate > rhs.purchaseDate
|
|
||||||
}
|
|
||||||
return lhs.transactionId > rhs.transactionId
|
|
||||||
}.map { $0.dictionary }
|
|
||||||
completion(true, sortedEntitlements, nil)
|
|
||||||
} catch {
|
|
||||||
completion(false, nil, error as NSError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func purchaseProduct(productIdentifier: String, completion: @escaping (Bool, String?, String?, String?, NSError?) -> Void) {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
let products = try await Product.products(for: [productIdentifier])
|
|
||||||
guard let product = products.first else {
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: makeError(code: 0, description: "Product not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let result = try await product.purchase()
|
|
||||||
switch result {
|
|
||||||
case .success(let verification):
|
|
||||||
switch verification {
|
|
||||||
case .verified(let transaction):
|
|
||||||
await transaction.finish()
|
|
||||||
completePurchase(completion: completion, success: true, transactionId: String(transaction.id),
|
|
||||||
productId: transaction.productID, originalTransactionId: String(transaction.originalID), error: nil)
|
|
||||||
case .unverified(_, let error):
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: error as NSError)
|
|
||||||
}
|
|
||||||
case .userCancelled:
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: makeError(code: 1, description: "Purchase cancelled"))
|
|
||||||
case .pending:
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: makeError(code: 2, description: "Purchase pending"))
|
|
||||||
@unknown default:
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: makeError(code: 3, description: "Unknown purchase result"))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
completePurchase(completion: completion, success: false, transactionId: nil, productId: nil, originalTransactionId: nil,
|
|
||||||
error: error as NSError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func storefrontCurrencyCode(for product: Product) -> String {
|
|
||||||
product.priceFormatStyle.locale.currencyCode ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private func subscriptionBillingMonths(_ period: Product.SubscriptionPeriod) -> Double {
|
|
||||||
let periodValue = Double(period.value)
|
|
||||||
switch period.unit {
|
|
||||||
case .day:
|
|
||||||
return periodValue / 30.0
|
|
||||||
case .week:
|
|
||||||
return periodValue * 7.0 / 30.0
|
|
||||||
case .month:
|
|
||||||
return periodValue
|
|
||||||
case .year:
|
|
||||||
return periodValue * 12.0
|
|
||||||
@unknown default:
|
|
||||||
return periodValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func fetchProducts(identifiers: Set<String>, completion: @escaping ([NSDictionary], [String], NSError?) -> Void) {
|
|
||||||
Task {
|
|
||||||
do {
|
|
||||||
let products = try await Product.products(for: identifiers)
|
|
||||||
let productDicts = products.map { product in productDictionary(for: product) }
|
|
||||||
let fetchedIds = Set(products.map { $0.id })
|
|
||||||
let invalidIdentifiers = identifiers.filter { !fetchedIds.contains($0) }
|
|
||||||
DispatchQueue.main.async { completion(productDicts, Array(invalidIdentifiers), nil) }
|
|
||||||
} catch {
|
|
||||||
DispatchQueue.main.async { completion([], Array(identifiers), error as NSError) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeError(code: Int, description: String) -> NSError {
|
|
||||||
NSError(domain: Self.errorDomain, code: code, userInfo: [NSLocalizedDescriptionKey: description])
|
|
||||||
}
|
|
||||||
|
|
||||||
private func completePurchase(completion: @escaping (Bool, String?, String?, String?, NSError?) -> Void,
|
|
||||||
success: Bool,
|
|
||||||
transactionId: String?,
|
|
||||||
productId: String?,
|
|
||||||
originalTransactionId: String?,
|
|
||||||
error: NSError?) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
completion(success, transactionId, productId, originalTransactionId, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func productDictionary(for product: Product) -> NSDictionary {
|
|
||||||
let currencyCode = storefrontCurrencyCode(for: product)
|
|
||||||
var productData: [String: Any] = [
|
|
||||||
"productId": product.id,
|
|
||||||
"title": product.displayName,
|
|
||||||
"description": product.description,
|
|
||||||
"price": "\(product.price)",
|
|
||||||
"displayPrice": product.displayPrice,
|
|
||||||
"currencyCode": currencyCode,
|
|
||||||
"priceAmount": NSDecimalNumber(decimal: product.price).doubleValue
|
|
||||||
]
|
|
||||||
if let subscription = product.subscription {
|
|
||||||
let billingMonths = subscriptionBillingMonths(subscription.subscriptionPeriod)
|
|
||||||
productData["subscriptionBillingMonths"] = billingMonths
|
|
||||||
if let perMonthPrice = displayPricePerMonth(for: product, billingMonths: billingMonths, currencyCode: currencyCode) {
|
|
||||||
productData["displayPricePerMonth"] = perMonthPrice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return productData as NSDictionary
|
|
||||||
}
|
|
||||||
|
|
||||||
private func displayPricePerMonth(for product: Product, billingMonths: Double, currencyCode: String) -> String? {
|
|
||||||
if billingMonths <= 1e-6 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let perMonthPrice = product.price / Decimal(billingMonths)
|
|
||||||
let formatter = NumberFormatter()
|
|
||||||
formatter.numberStyle = .currency
|
|
||||||
formatter.locale = product.priceFormatStyle.locale
|
|
||||||
if !currencyCode.isEmpty {
|
|
||||||
formatter.currencyCode = currencyCode
|
|
||||||
}
|
|
||||||
return formatter.string(from: NSDecimalNumber(decimal: perMonthPrice))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,20 +4,27 @@
|
|||||||
|
|
||||||
#import "StoreKitController.h"
|
#import "StoreKitController.h"
|
||||||
#import <StoreKit/StoreKit.h>
|
#import <StoreKit/StoreKit.h>
|
||||||
#import <AmneziaVPN-Swift.h>
|
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QString>
|
#include <QtCore/QString>
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
QString toQString(NSString *value)
|
|
||||||
{
|
|
||||||
return QString::fromUtf8((value ?: @"").UTF8String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
API_AVAILABLE(ios(15.0), macos(12.0))
|
API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
|
@interface StoreKitController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
||||||
|
@property (nonatomic, copy) void (^purchaseCompletion)(BOOL success,
|
||||||
|
NSString *_Nullable transactionId,
|
||||||
|
NSString *_Nullable productId,
|
||||||
|
NSString *_Nullable originalTransactionId,
|
||||||
|
NSError *_Nullable error);
|
||||||
|
@property (nonatomic, copy) void (^restoreCompletion)(BOOL success,
|
||||||
|
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
||||||
|
NSError *_Nullable error);
|
||||||
|
@property (nonatomic, copy) void (^productsFetchCompletion)(NSArray<NSDictionary *> *products,
|
||||||
|
NSArray<NSString *> *invalidIdentifiers,
|
||||||
|
NSError *_Nullable error);
|
||||||
|
@property (nonatomic, strong) SKProductsRequest *productsRequest;
|
||||||
|
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *restoredTransactions;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation StoreKitController
|
@implementation StoreKitController
|
||||||
|
|
||||||
+ (instancetype)sharedInstance
|
+ (instancetype)sharedInstance
|
||||||
@@ -35,9 +42,17 @@ API_AVAILABLE(ios(15.0), macos(12.0))
|
|||||||
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
|
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
||||||
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)purchaseProduct:(NSString *)productIdentifier
|
- (void)purchaseProduct:(NSString *)productIdentifier
|
||||||
completion:(void (^)(BOOL success,
|
completion:(void (^)(BOOL success,
|
||||||
NSString *_Nullable transactionId,
|
NSString *_Nullable transactionId,
|
||||||
@@ -45,48 +60,41 @@ API_AVAILABLE(ios(15.0), macos(12.0))
|
|||||||
NSString *_Nullable originalTransactionId,
|
NSString *_Nullable originalTransactionId,
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
{
|
{
|
||||||
qInfo().noquote() << "[IAP][StoreKit2] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
|
self.purchaseCompletion = completion;
|
||||||
[[StoreKit2Helper shared] purchaseProductWithProductIdentifier:productIdentifier
|
|
||||||
completion:^(BOOL success,
|
qInfo().noquote() << "[IAP][StoreKit] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
|
||||||
NSString *transactionId,
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
NSString *productId,
|
[self performPurchaseAsync:productIdentifier];
|
||||||
NSString *originalTransactionId,
|
});
|
||||||
NSError *error) {
|
}
|
||||||
if (success) {
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit2] Purchase success. transactionId =" << toQString(transactionId)
|
- (void)performPurchaseAsync:(NSString *)productIdentifier API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
<< "originalTransactionId =" << toQString(originalTransactionId) << "productId =" << toQString(productId);
|
{
|
||||||
} else if (error) {
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
qWarning().noquote() << "[IAP][StoreKit2] Purchase failed:" << toQString(error.localizedDescription);
|
@try {
|
||||||
|
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productIdentifier]];
|
||||||
|
request.delegate = self;
|
||||||
|
[request start];
|
||||||
|
|
||||||
|
} @catch (NSException *exception) {
|
||||||
|
NSError *error = [NSError errorWithDomain:@"StoreKitController"
|
||||||
|
code:1
|
||||||
|
userInfo:@{ NSLocalizedDescriptionKey : exception.reason ?: @"Purchase failed" }];
|
||||||
|
if (self.purchaseCompletion) {
|
||||||
|
self.purchaseCompletion(NO, nil, nil, nil, error);
|
||||||
|
self.purchaseCompletion = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (completion) {
|
});
|
||||||
completion(success, transactionId, productId, originalTransactionId, error);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
|
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
|
||||||
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
{
|
{
|
||||||
[[StoreKit2Helper shared] fetchCurrentEntitlementsWithCompletion:^(BOOL success,
|
self.restoreCompletion = completion;
|
||||||
NSArray<NSDictionary *> *entitlements,
|
self.restoredTransactions = [NSMutableArray array];
|
||||||
NSError *error) {
|
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
|
||||||
if (success) {
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit2] currentEntitlements returned"
|
|
||||||
<< (int)(entitlements ? entitlements.count : 0) << "active entitlements";
|
|
||||||
for (NSDictionary *entitlement in entitlements) {
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit2] Active entitlement:"
|
|
||||||
<< "transactionId=" << toQString(entitlement[@"transactionId"])
|
|
||||||
<< "originalTransactionId=" << toQString(entitlement[@"originalTransactionId"])
|
|
||||||
<< "productId=" << toQString(entitlement[@"productId"]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning().noquote() << "[IAP][StoreKit2] fetchCurrentEntitlements failed:" << toQString(error.localizedDescription);
|
|
||||||
}
|
|
||||||
if (completion) {
|
|
||||||
completion(success, entitlements, error);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
|
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
|
||||||
@@ -94,21 +102,163 @@ API_AVAILABLE(ios(15.0), macos(12.0))
|
|||||||
NSArray<NSString *> *invalidIdentifiers,
|
NSArray<NSString *> *invalidIdentifiers,
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
||||||
{
|
{
|
||||||
[[StoreKit2Helper shared] fetchProductsWithIdentifiers:productIdentifiers
|
self.productsFetchCompletion = completion;
|
||||||
completion:^(NSArray<NSDictionary *> *products,
|
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
|
||||||
NSArray<NSString *> *invalidIdentifiers,
|
self.productsRequest.delegate = self;
|
||||||
NSError *error) {
|
[self.productsRequest start];
|
||||||
if (!error) {
|
}
|
||||||
for (NSDictionary *productInfo in products) {
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit2] Fetched product info" << toQString(productInfo[@"productId"])
|
#pragma mark - SKProductsRequestDelegate / SKRequestDelegate
|
||||||
<< "price=" << toQString(productInfo[@"price"])
|
|
||||||
<< "currency=" << toQString(productInfo[@"currencyCode"]);
|
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
|
||||||
|
{
|
||||||
|
if (self.purchaseCompletion) {
|
||||||
|
SKProduct *product = response.products.firstObject;
|
||||||
|
if (!product) {
|
||||||
|
NSError *error = [NSError errorWithDomain:@"StoreKitController"
|
||||||
|
code:0
|
||||||
|
userInfo:@{ NSLocalizedDescriptionKey : @"Product not found" }];
|
||||||
|
self.purchaseCompletion(NO, nil, nil, nil, error);
|
||||||
|
self.purchaseCompletion = nil;
|
||||||
|
self.productsRequest = nil;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
|
||||||
|
NSString *priceString = [product.price stringValue] ?: @"";
|
||||||
|
qInfo().noquote() << "[IAP][StoreKit] Received product" << QString::fromUtf8(product.productIdentifier.UTF8String)
|
||||||
|
<< "price=" << QString::fromUtf8(priceString.UTF8String)
|
||||||
|
<< "currency=" << QString::fromUtf8(currencyCode.UTF8String);
|
||||||
|
SKPayment *payment = [SKPayment paymentWithProduct:product];
|
||||||
|
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
||||||
|
self.productsRequest = nil;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.productsFetchCompletion) {
|
||||||
|
NSMutableArray<NSDictionary *> *productDicts = [NSMutableArray array];
|
||||||
|
for (SKProduct *p in response.products) {
|
||||||
|
NSDictionary *productDict = @{
|
||||||
|
@"productId": p.productIdentifier,
|
||||||
|
@"title": p.localizedTitle,
|
||||||
|
@"description": p.localizedDescription,
|
||||||
|
@"price": p.price.stringValue,
|
||||||
|
@"currencyCode": [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @""
|
||||||
|
};
|
||||||
|
[productDicts addObject:productDict];
|
||||||
|
NSString *productCurrency = [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
|
||||||
|
NSString *productPrice = [p.price stringValue] ?: @"";
|
||||||
|
qInfo().noquote() << "[IAP][StoreKit] Fetched product info" << QString::fromUtf8(p.productIdentifier.UTF8String)
|
||||||
|
<< "price=" << QString::fromUtf8(productPrice.UTF8String)
|
||||||
|
<< "currency=" << QString::fromUtf8(productCurrency.UTF8String);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.productsFetchCompletion(productDicts, response.invalidProductIdentifiers, nil);
|
||||||
|
self.productsFetchCompletion = nil;
|
||||||
|
self.productsRequest = nil;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
|
||||||
|
{
|
||||||
|
if (self.purchaseCompletion) {
|
||||||
|
self.purchaseCompletion(NO, nil, nil, nil, error);
|
||||||
|
self.purchaseCompletion = nil;
|
||||||
|
}
|
||||||
|
if (self.productsFetchCompletion) {
|
||||||
|
self.productsFetchCompletion(@[], @[], error);
|
||||||
|
self.productsFetchCompletion = nil;
|
||||||
|
}
|
||||||
|
self.productsRequest = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - SKPaymentTransactionObserver
|
||||||
|
|
||||||
|
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
|
||||||
|
{
|
||||||
|
for (SKPaymentTransaction *transaction in transactions) {
|
||||||
|
switch (transaction.transactionState) {
|
||||||
|
case SKPaymentTransactionStatePurchased: {
|
||||||
|
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier;
|
||||||
|
qInfo().noquote() << "[IAP][StoreKit] Transaction purchased" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
|
||||||
|
<< "original=" << QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
|
||||||
|
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String);
|
||||||
|
|
||||||
|
if (self.purchaseCompletion) {
|
||||||
|
self.purchaseCompletion(YES,
|
||||||
|
transaction.transactionIdentifier,
|
||||||
|
transaction.payment.productIdentifier,
|
||||||
|
originalTransactionId,
|
||||||
|
nil);
|
||||||
|
self.purchaseCompletion = nil;
|
||||||
}
|
}
|
||||||
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (completion) {
|
case SKPaymentTransactionStateFailed:
|
||||||
completion(products ?: @[], invalidIdentifiers ?: @[], error);
|
qInfo().noquote() << "[IAP][StoreKit] Transaction failed" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
|
||||||
|
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String)
|
||||||
|
<< "error=" << QString::fromUtf8(transaction.error.localizedDescription.UTF8String);
|
||||||
|
if (self.purchaseCompletion) {
|
||||||
|
self.purchaseCompletion(NO,
|
||||||
|
transaction.transactionIdentifier,
|
||||||
|
transaction.payment.productIdentifier,
|
||||||
|
nil,
|
||||||
|
transaction.error);
|
||||||
|
self.purchaseCompletion = nil;
|
||||||
|
}
|
||||||
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
|
break;
|
||||||
|
case SKPaymentTransactionStateRestored: {
|
||||||
|
if (self.restoreCompletion) {
|
||||||
|
NSString *transactionId = transaction.transactionIdentifier ?: @"";
|
||||||
|
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transactionId;
|
||||||
|
NSString *productId = transaction.payment.productIdentifier ?: @"";
|
||||||
|
|
||||||
|
qInfo().noquote() << "[IAP][StoreKit] Transaction restored"
|
||||||
|
<< QString::fromUtf8(transactionId.UTF8String)
|
||||||
|
<< "original="
|
||||||
|
<< QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
|
||||||
|
<< "product="
|
||||||
|
<< QString::fromUtf8((productId ?: @"").UTF8String);
|
||||||
|
|
||||||
|
NSDictionary *info = @{
|
||||||
|
@"transactionId": transactionId,
|
||||||
|
@"originalTransactionId": originalTransactionId ?: @"",
|
||||||
|
@"productId": productId ?: @""
|
||||||
|
};
|
||||||
|
if (!self.restoredTransactions) {
|
||||||
|
self.restoredTransactions = [NSMutableArray array];
|
||||||
|
}
|
||||||
|
[self.restoredTransactions addObject:info];
|
||||||
|
}
|
||||||
|
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}];
|
case SKPaymentTransactionStatePurchasing:
|
||||||
|
case SKPaymentTransactionStateDeferred:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
|
||||||
|
{
|
||||||
|
if (self.restoreCompletion) {
|
||||||
|
NSArray<NSDictionary *> *transactions = [self.restoredTransactions copy];
|
||||||
|
self.restoreCompletion(YES, transactions, nil);
|
||||||
|
self.restoreCompletion = nil;
|
||||||
|
self.restoredTransactions = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
|
||||||
|
{
|
||||||
|
if (self.restoreCompletion) {
|
||||||
|
self.restoreCompletion(NO, nil, error);
|
||||||
|
self.restoreCompletion = nil;
|
||||||
|
self.restoredTransactions = nil;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -3,7 +3,5 @@ import Foundation
|
|||||||
struct XrayConfig: Decodable {
|
struct XrayConfig: Decodable {
|
||||||
let dns1: String?
|
let dns1: String?
|
||||||
let dns2: String?
|
let dns2: String?
|
||||||
let splitTunnelType: Int?
|
|
||||||
let splitTunnelSites: [String]?
|
|
||||||
let config: String
|
let config: String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,9 +179,8 @@ bool IosController::initialize()
|
|||||||
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
|
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
|
||||||
@try {
|
@try {
|
||||||
if (error) {
|
if (error) {
|
||||||
qWarning() << "IosController::initialize : loadAllFromPreferences failed:"
|
qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String];
|
||||||
<< [error.localizedDescription UTF8String]
|
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||||
<< "domain:" << [error.domain UTF8String] << "code:" << error.code;
|
|
||||||
ok = false;
|
ok = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -398,14 +397,8 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
|||||||
{
|
{
|
||||||
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
|
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
|
||||||
|
|
||||||
if (!session) {
|
if (session /* && session == TunnelManager.session */ ) {
|
||||||
return;
|
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
|
||||||
}
|
|
||||||
if (!m_currentTunnel || (NETunnelProviderSession *)m_currentTunnel.connection != session) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
|
|
||||||
|
|
||||||
if (session.status == NEVPNStatusDisconnected) {
|
if (session.status == NEVPNStatusDisconnected) {
|
||||||
if (@available(iOS 16.0, *)) {
|
if (@available(iOS 16.0, *)) {
|
||||||
@@ -519,6 +512,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
|||||||
m_statusRequestInFlight = false;
|
m_statusRequestInFlight = false;
|
||||||
}
|
}
|
||||||
emitConnectionStateIfChanged(nextState);
|
emitConnectionStateIfChanged(nextState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosController::vpnConfigurationDidChange(void *pNotification)
|
void IosController::vpnConfigurationDidChange(void *pNotification)
|
||||||
@@ -690,15 +684,6 @@ bool IosController::setupXray()
|
|||||||
QJsonObject finalConfig;
|
QJsonObject finalConfig;
|
||||||
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
|
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
|
||||||
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
|
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
|
||||||
finalConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
|
|
||||||
|
|
||||||
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
|
|
||||||
|
|
||||||
for(int index = 0; index < splitTunnelSites.count(); index++) {
|
|
||||||
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
finalConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
|
|
||||||
finalConfig.insert(config_key::config, xrayConfigStr);
|
finalConfig.insert(config_key::config, xrayConfigStr);
|
||||||
|
|
||||||
QJsonDocument finalConfigDoc(finalConfig);
|
QJsonDocument finalConfigDoc(finalConfig);
|
||||||
@@ -850,49 +835,39 @@ void IosController::startTunnel()
|
|||||||
m_rxBytes = 0;
|
m_rxBytes = 0;
|
||||||
m_txBytes = 0;
|
m_txBytes = 0;
|
||||||
|
|
||||||
NETunnelProviderManager *tunnel = m_currentTunnel;
|
[m_currentTunnel setEnabled:YES];
|
||||||
[tunnel setEnabled:YES];
|
|
||||||
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
||||||
[tunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
if (saveError) {
|
|
||||||
qDebug().nospace() << "IosController::startTunnel" << protocolName << ": Connect " << protocolName
|
|
||||||
<< " Tunnel Save Error" << saveError.localizedDescription.UTF8String << " domain:"
|
|
||||||
<< saveError.domain.UTF8String << " code:" << saveError.code;
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
[tunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
|
if (saveError) {
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
qDebug().nospace() << "IosController::startTunnel" << protocolName << ": Connect " << protocolName << " Tunnel Save Error" << saveError.localizedDescription.UTF8String;
|
||||||
if (loadError) {
|
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||||
qDebug().nospace() << "IosController::startTunnel :" << tunnel.localizedDescription << protocolName
|
return;
|
||||||
<< ": Connect " << protocolName << " Tunnel Load Error"
|
}
|
||||||
<< loadError.localizedDescription.UTF8String;
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSError *startError = nil;
|
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
|
||||||
qDebug() << iosStatusToState(tunnel.connection.status);
|
if (loadError) {
|
||||||
|
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String;
|
||||||
|
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BOOL started = [tunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
|
NSError *startError = nil;
|
||||||
|
qDebug() << iosStatusToState(m_currentTunnel.connection.status);
|
||||||
|
|
||||||
if (!started || startError) {
|
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
|
||||||
qDebug().nospace() << "IosController::startTunnel :" << tunnel.localizedDescription << protocolName
|
|
||||||
<< " : Connect " << protocolName << " Tunnel Start Error"
|
if (!started || startError) {
|
||||||
<< (startError ? startError.localizedDescription.UTF8String : "");
|
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Connect " << protocolName << " Tunnel Start Error"
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
<< (startError ? startError.localizedDescription.UTF8String : "");
|
||||||
} else {
|
emit connectionStateChanged(Vpn::ConnectionState::Error);
|
||||||
qDebug().nospace() << "IosController::startTunnel :" << tunnel.localizedDescription << protocolName
|
} else {
|
||||||
<< " : Starting the tunnel succeeded";
|
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Starting the tunnel succeeded";
|
||||||
}
|
}
|
||||||
});
|
}];
|
||||||
}];
|
});
|
||||||
});
|
}];
|
||||||
}];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::isOurManager(NETunnelProviderManager* manager) {
|
bool IosController::isOurManager(NETunnelProviderManager* manager) {
|
||||||
@@ -1147,26 +1122,14 @@ void IosController::fetchProducts(const QStringList &productIds,
|
|||||||
NSArray<NSString *> * _Nonnull invalidIdentifiers,
|
NSArray<NSString *> * _Nonnull invalidIdentifiers,
|
||||||
NSError * _Nullable error) {
|
NSError * _Nullable error) {
|
||||||
QList<QVariantMap> outProducts;
|
QList<QVariantMap> outProducts;
|
||||||
for (NSDictionary *productInfo in products) {
|
for (NSDictionary *p in products) {
|
||||||
QVariantMap productData;
|
QVariantMap m;
|
||||||
productData["productId"] = QString::fromUtf8([productInfo[@"productId"] UTF8String]);
|
m["productId"] = QString::fromUtf8([p[@"productId"] UTF8String]);
|
||||||
productData["title"] = QString::fromUtf8([productInfo[@"title"] UTF8String]);
|
m["title"] = QString::fromUtf8([p[@"title"] UTF8String]);
|
||||||
productData["description"] = QString::fromUtf8([productInfo[@"description"] UTF8String]);
|
m["description"] = QString::fromUtf8([p[@"description"] UTF8String]);
|
||||||
productData["price"] = QString::fromUtf8([productInfo[@"price"] UTF8String]);
|
m["price"] = QString::fromUtf8([p[@"price"] UTF8String]);
|
||||||
if (productInfo[@"displayPrice"]) {
|
m["currencyCode"] = QString::fromUtf8([p[@"currencyCode"] UTF8String]);
|
||||||
productData["displayPrice"] = QString::fromUtf8([productInfo[@"displayPrice"] UTF8String]);
|
outProducts.push_back(m);
|
||||||
}
|
|
||||||
productData["currencyCode"] = QString::fromUtf8([productInfo[@"currencyCode"] UTF8String]);
|
|
||||||
if (productInfo[@"priceAmount"]) {
|
|
||||||
productData["priceAmount"] = [productInfo[@"priceAmount"] doubleValue];
|
|
||||||
}
|
|
||||||
if (productInfo[@"subscriptionBillingMonths"]) {
|
|
||||||
productData["subscriptionBillingMonths"] = [productInfo[@"subscriptionBillingMonths"] doubleValue];
|
|
||||||
}
|
|
||||||
if (productInfo[@"displayPricePerMonth"]) {
|
|
||||||
productData["displayPricePerMonth"] = QString::fromUtf8([productInfo[@"displayPricePerMonth"] UTF8String]);
|
|
||||||
}
|
|
||||||
outProducts.push_back(productData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList invalid;
|
QStringList invalid;
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
|||||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||||
const QString& message, int timerMsec) {
|
const QString& message, int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
// timerMsec is tray display hint on Windows, not a schedule delay — was wrongly used as seconds (CLI-570).
|
|
||||||
Q_UNUSED(timerMsec);
|
|
||||||
|
|
||||||
if (!m_delegate) {
|
if (!m_delegate) {
|
||||||
return;
|
return;
|
||||||
@@ -73,13 +71,11 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
|||||||
content.body = message.toNSString();
|
content.body = message.toNSString();
|
||||||
content.sound = [UNNotificationSound defaultSound];
|
content.sound = [UNNotificationSound defaultSound];
|
||||||
|
|
||||||
NSTimeInterval delay = 0.1;
|
int timerSec = timerMsec / 1000;
|
||||||
UNTimeIntervalNotificationTrigger* trigger =
|
UNTimeIntervalNotificationTrigger* trigger =
|
||||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||||
|
|
||||||
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
|
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||||
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
|
||||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
|
|
||||||
content:content
|
content:content
|
||||||
trigger:trigger];
|
trigger:trigger];
|
||||||
|
|
||||||
@@ -147,7 +143,6 @@ IOSNotificationHandler::~IOSNotificationHandler() { }
|
|||||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||||
const QString& message, int timerMsec) {
|
const QString& message, int timerMsec) {
|
||||||
Q_UNUSED(type);
|
Q_UNUSED(type);
|
||||||
Q_UNUSED(timerMsec);
|
|
||||||
|
|
||||||
if (!m_delegate) {
|
if (!m_delegate) {
|
||||||
return;
|
return;
|
||||||
@@ -158,13 +153,11 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
|||||||
content.body = message.toNSString();
|
content.body = message.toNSString();
|
||||||
content.sound = [UNNotificationSound defaultSound];
|
content.sound = [UNNotificationSound defaultSound];
|
||||||
|
|
||||||
NSTimeInterval delay = 0.1;
|
int timerSec = timerMsec / 1000;
|
||||||
UNTimeIntervalNotificationTrigger* trigger =
|
UNTimeIntervalNotificationTrigger* trigger =
|
||||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||||
|
|
||||||
NSString* requestId = [NSString stringWithFormat:@"amneziavpn.vpnstate.%lld",
|
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||||
(long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
|
||||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:requestId
|
|
||||||
content:content
|
content:content
|
||||||
trigger:trigger];
|
trigger:trigger];
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
|
|||||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||||
|
|
||||||
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
|
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
|
||||||
&NetworkWatcherImpl::wakeup);
|
&NetworkWatcherImpl::sleepMode);
|
||||||
|
|
||||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||||
// This is not strictly needed, but it's better for user experience because
|
// This is not strictly needed, but it's better for user experience because
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
|
|||||||
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
||||||
{
|
{
|
||||||
if (state == NM_STATE_ASLEEP) {
|
if (state == NM_STATE_ASLEEP) {
|
||||||
emit wakeup();
|
emit sleepMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug() << "NMStateChanged " << state;
|
logger.debug() << "NMStateChanged " << state;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||||
void wakeup();
|
void sleepMode();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#ifndef MACOS_NE_VPN_NOTIFICATION_H
|
|
||||||
#define MACOS_NE_VPN_NOTIFICATION_H
|
|
||||||
|
|
||||||
class QString;
|
|
||||||
|
|
||||||
void macosNePostVpnStateNotification(const QString &title, const QString &message);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#include "macos_ne_vpn_notification.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <UserNotifications/UserNotifications.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
@interface MacosNeVpnNotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation MacosNeVpnNotificationDelegate
|
|
||||||
|
|
||||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
||||||
willPresentNotification:(UNNotification *)notification
|
|
||||||
withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
|
|
||||||
{
|
|
||||||
Q_UNUSED(center)
|
|
||||||
Q_UNUSED(notification)
|
|
||||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
||||||
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|
||||||
withCompletionHandler:(void (^)(void))completionHandler
|
|
||||||
{
|
|
||||||
Q_UNUSED(center)
|
|
||||||
Q_UNUSED(response)
|
|
||||||
completionHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
MacosNeVpnNotificationDelegate *delegateInstance()
|
|
||||||
{
|
|
||||||
static MacosNeVpnNotificationDelegate *d;
|
|
||||||
static dispatch_once_t once;
|
|
||||||
dispatch_once(&once, ^{
|
|
||||||
d = [[MacosNeVpnNotificationDelegate alloc] init];
|
|
||||||
});
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ensureNotificationCenterSetup()
|
|
||||||
{
|
|
||||||
static dispatch_once_t once;
|
|
||||||
dispatch_once(&once, ^{
|
|
||||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
|
||||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
|
||||||
UNAuthorizationOptionBadge)
|
|
||||||
completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
|
||||||
Q_UNUSED(granted);
|
|
||||||
if (!error) {
|
|
||||||
center.delegate = delegateInstance();
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void macosNePostVpnStateNotification(const QString &title, const QString &message)
|
|
||||||
{
|
|
||||||
ensureNotificationCenterSetup();
|
|
||||||
|
|
||||||
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
|
|
||||||
content.title = title.toNSString();
|
|
||||||
content.body = message.toNSString();
|
|
||||||
content.sound = nil;
|
|
||||||
|
|
||||||
NSTimeInterval delay = 0.1;
|
|
||||||
UNTimeIntervalNotificationTrigger *trigger =
|
|
||||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:delay repeats:NO];
|
|
||||||
|
|
||||||
NSString *identifier =
|
|
||||||
[NSString stringWithFormat:@"amneziavpn.vpnstate.%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
|
|
||||||
|
|
||||||
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
|
|
||||||
content:content
|
|
||||||
trigger:trigger];
|
|
||||||
|
|
||||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
|
||||||
[center addNotificationRequest:request
|
|
||||||
withCompletionHandler:^(NSError *_Nullable error) {
|
|
||||||
if (error) {
|
|
||||||
NSLog(@"macosNePostVpnStateNotification failed: %@", error);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
|
|||||||
|
|
||||||
case kIOMessageSystemHasPoweredOn:
|
case kIOMessageSystemHasPoweredOn:
|
||||||
/* Announces that the system and its devices have woken up. */
|
/* Announces that the system and its devices have woken up. */
|
||||||
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
|
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
|
||||||
if (listener->m_watcher) {
|
if (listener->m_watcher) {
|
||||||
// Use QMetaObject::invokeMethod for thread-safe signal emission
|
// Use QMetaObject::invokeMethod for thread-safe signal emission
|
||||||
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,6 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
||||||
if (m_splitTunnelManager == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (config.m_vpnDisabledApps.length() > 0) {
|
if (config.m_vpnDisabledApps.length() > 0) {
|
||||||
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
|
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
|
||||||
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
|
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
|
|||||||
switch (uMsg) {
|
switch (uMsg) {
|
||||||
case WM_POWERBROADCAST:
|
case WM_POWERBROADCAST:
|
||||||
if (wParam == PBT_APMRESUMESUSPEND) {
|
if (wParam == PBT_APMRESUMESUSPEND) {
|
||||||
emit obj->wakeup();
|
emit obj->sleepMode();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -232,6 +232,12 @@ ErrorCode OpenVpnProtocol::start()
|
|||||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_openVpnProcess->waitForSource(5000);
|
||||||
|
if (!m_openVpnProcess->isInitialized()) {
|
||||||
|
qWarning() << "IpcProcess replica is not connected!";
|
||||||
|
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||||
|
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||||
|
}
|
||||||
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
|
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
|
||||||
QStringList arguments({
|
QStringList arguments({
|
||||||
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
|
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
|
||||||
@@ -240,13 +246,13 @@ ErrorCode OpenVpnProtocol::start()
|
|||||||
m_openVpnProcess->setArguments(arguments);
|
m_openVpnProcess->setArguments(arguments);
|
||||||
|
|
||||||
qDebug() << arguments.join(" ");
|
qDebug() << arguments.join(" ");
|
||||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
|
connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred,
|
||||||
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
|
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
|
||||||
|
|
||||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
|
connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged,
|
||||||
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
||||||
|
|
||||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this,
|
||||||
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||||
|
|
||||||
m_openVpnProcess->start();
|
m_openVpnProcess->start();
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ private:
|
|||||||
void updateRouteGateway(QString line);
|
void updateRouteGateway(QString line);
|
||||||
void updateVpnGateway(const QString &line);
|
void updateVpnGateway(const QString &line);
|
||||||
|
|
||||||
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
|
QSharedPointer<PrivilegedProcess> m_openVpnProcess;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPENVPNPROTOCOL_H
|
#endif // OPENVPNPROTOCOL_H
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ namespace amnezia
|
|||||||
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
|
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
|
||||||
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
|
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
|
||||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||||
constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
|
constexpr char defaultSpecialJunk1[] = "<b 0x084481800001000300000000077469636b65747306776964676574096b696e6f706f69736b0272750000010001c00c0005000100000039001806776964676574077469636b6574730679616e646578c025c0390005000100000039002b1765787465726e616c2d7469636b6574732d776964676574066166697368610679616e646578036e657400c05d000100010000001c000457fafe25>";
|
||||||
constexpr char defaultSpecialJunk2[] = "";
|
constexpr char defaultSpecialJunk2[] = "";
|
||||||
constexpr char defaultSpecialJunk3[] = "";
|
constexpr char defaultSpecialJunk3[] = "";
|
||||||
constexpr char defaultSpecialJunk4[] = "";
|
constexpr char defaultSpecialJunk4[] = "";
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
m_impl.reset(new LocalSocketController());
|
m_impl.reset(new LocalSocketController());
|
||||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||||
setConnectionState(Vpn::ConnectionState::Connected);
|
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
||||||
});
|
});
|
||||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||||
[this](const QString& serverIpv4Gateway,
|
[this](const QString& serverIpv4Gateway,
|
||||||
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||||
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
||||||
m_impl->initialize(nullptr, nullptr);
|
m_impl->initialize(nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "xrayprotocol.h"
|
#include "xrayprotocol.h"
|
||||||
|
|
||||||
#include "core/ipcclient.h"
|
#include "core/ipcclient.h"
|
||||||
#include "ipc.h"
|
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
#include "core/networkUtilities.h"
|
#include "core/networkUtilities.h"
|
||||||
|
|
||||||
@@ -10,37 +9,14 @@
|
|||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QtCore/qlogging.h>
|
|
||||||
#include <QtCore/qobjectdefs.h>
|
|
||||||
#include <QtCore/qprocess.h>
|
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
static const QString tunName = "utun22";
|
|
||||||
#else
|
|
||||||
static const QString tunName = "tun2";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||||
{
|
{
|
||||||
|
readXrayConfiguration(configuration);
|
||||||
|
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
|
||||||
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
||||||
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
|
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
|
||||||
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
|
m_t2sProcess = IpcClient::InterfaceTun2Socks();
|
||||||
|
|
||||||
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
|
|
||||||
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());
|
|
||||||
|
|
||||||
const QString primaryDns = configuration.value(amnezia::config_key::dns1).toString();
|
|
||||||
m_dnsServers.push_back(QHostAddress(primaryDns));
|
|
||||||
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
|
|
||||||
const QString secondaryDns = configuration.value(amnezia::config_key::dns2).toString();
|
|
||||||
m_dnsServers.push_back(QHostAddress(secondaryDns));
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
|
|
||||||
if (xrayConfiguration.isEmpty()) {
|
|
||||||
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
|
|
||||||
}
|
|
||||||
m_xrayConfig = xrayConfiguration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XrayProtocol::~XrayProtocol()
|
XrayProtocol::~XrayProtocol()
|
||||||
@@ -53,16 +29,72 @@ ErrorCode XrayProtocol::start()
|
|||||||
{
|
{
|
||||||
qDebug() << "XrayProtocol::start()";
|
qDebug() << "XrayProtocol::start()";
|
||||||
|
|
||||||
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
const ErrorCode err = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
|
iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
|
||||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
return ErrorCode::NoError;
|
||||||
qCritical() << "Failed to start xray";
|
|
||||||
return ErrorCode::XrayExecutableCrashed;
|
|
||||||
}
|
|
||||||
return startTun2Socks();
|
|
||||||
}, [] () {
|
}, [] () {
|
||||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||||
});
|
});
|
||||||
|
if (err != ErrorCode::NoError)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
|
return startTun2Sock();
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode XrayProtocol::startTun2Sock()
|
||||||
|
{
|
||||||
|
m_t2sProcess->start();
|
||||||
|
|
||||||
|
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
|
||||||
|
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
||||||
|
|
||||||
|
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
|
||||||
|
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
|
||||||
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
if (vpnState == Vpn::ConnectionState::Connected) {
|
||||||
|
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||||
|
QList<QHostAddress> dnsAddr;
|
||||||
|
|
||||||
|
dnsAddr.push_back(QHostAddress(m_primaryDNS));
|
||||||
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
|
if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||||
|
dnsAddr.push_back(QHostAddress(m_secondaryDNS));
|
||||||
|
}
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QThread::msleep(8000);
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
QThread::msleep(5000);
|
||||||
|
iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
|
||||||
|
iface->updateResolvers("utun22", dnsAddr);
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
QThread::msleep(1000);
|
||||||
|
iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
|
||||||
|
iface->updateResolvers("tun2", dnsAddr);
|
||||||
|
#endif
|
||||||
|
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
|
||||||
|
iface->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1");
|
||||||
|
}
|
||||||
|
iface->StopRoutingIpv6();
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
iface->updateResolvers("tun2", dnsAddr);
|
||||||
|
#endif
|
||||||
|
setConnectionState(Vpn::ConnectionState::Connected);
|
||||||
|
}
|
||||||
|
#if !defined(Q_OS_MACOS)
|
||||||
|
if (vpnState == Vpn::ConnectionState::Disconnected) {
|
||||||
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
|
iface->deleteTun("tun2");
|
||||||
|
iface->StartRoutingIpv6();
|
||||||
|
iface->clearSavedRoutes();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void XrayProtocol::stop()
|
void XrayProtocol::stop()
|
||||||
@@ -70,177 +102,43 @@ void XrayProtocol::stop()
|
|||||||
qDebug() << "XrayProtocol::stop()";
|
qDebug() << "XrayProtocol::stop()";
|
||||||
|
|
||||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
auto disableKillSwitch = iface->disableKillSwitch();
|
#ifdef AMNEZIA_DESKTOP
|
||||||
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = iface->StartRoutingIpv6();
|
||||||
qWarning() << "Failed to disable killswitch";
|
if (!StartRoutingIpv6Resp.waitForFinished(1000)) {
|
||||||
|
qWarning() << "XrayProtocol::stop(): Failed to start routing ipv6";
|
||||||
|
}
|
||||||
|
|
||||||
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
QRemoteObjectPendingReply<bool> restoreResolvers = iface->restoreResolvers();
|
||||||
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
|
if (!restoreResolvers.waitForFinished(1000)) {
|
||||||
qWarning() << "Failed to start routing ipv6";
|
qWarning() << "XrayProtocol::stop(): Failed to restore resolvers";
|
||||||
|
}
|
||||||
|
|
||||||
auto restoreResolvers = iface->restoreResolvers();
|
#if !defined(Q_OS_MACOS)
|
||||||
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
|
QRemoteObjectPendingReply<bool> deleteTunResp = iface->deleteTun("tun2");
|
||||||
qWarning() << "Failed to restore resolvers";
|
if (!deleteTunResp.waitForFinished(1000)) {
|
||||||
|
qWarning() << "XrayProtocol::stop(): Failed to delete tun";
|
||||||
auto deleteTun = iface->deleteTun(tunName);
|
}
|
||||||
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
#endif
|
||||||
qWarning() << "Failed to delete tun";
|
#endif
|
||||||
|
iface->xrayStop();
|
||||||
auto xrayStop = iface->xrayStop();
|
|
||||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
|
||||||
qWarning() << "Failed to stop xray";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (m_tun2socksProcess) {
|
if (m_t2sProcess) {
|
||||||
m_tun2socksProcess->blockSignals(true);
|
m_t2sProcess->stop();
|
||||||
|
QThread::msleep(200);
|
||||||
#ifndef Q_OS_WIN
|
|
||||||
m_tun2socksProcess->terminate();
|
|
||||||
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
|
|
||||||
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
|
|
||||||
qWarning() << "Failed to terminate tun2socks. Killing the process...";
|
|
||||||
m_tun2socksProcess->kill();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// terminate does not do anything useful on Windows
|
|
||||||
// so just kill the process
|
|
||||||
m_tun2socksProcess->kill();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_tun2socksProcess->close();
|
|
||||||
m_tun2socksProcess.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode XrayProtocol::startTun2Socks()
|
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
|
||||||
{
|
{
|
||||||
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
|
||||||
if (!m_tun2socksProcess->waitForSource()) {
|
if (xrayConfiguration.isEmpty()) {
|
||||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
|
||||||
}
|
}
|
||||||
|
m_xrayConfig = xrayConfiguration;
|
||||||
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
|
||||||
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", "socks5://127.0.0.1:10808" });
|
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
|
||||||
|
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
|
||||||
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
|
|
||||||
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
|
|
||||||
if (!readAllStandardOutput.waitForFinished()) {
|
|
||||||
qWarning() << "Failed to read output from tun2socks";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString line = readAllStandardOutput.returnValue();
|
|
||||||
|
|
||||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
|
||||||
qDebug() << "[tun2socks]:" << line;
|
|
||||||
|
|
||||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) {
|
|
||||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
|
||||||
|
|
||||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
|
||||||
stop();
|
|
||||||
setLastError(res);
|
|
||||||
} else {
|
|
||||||
setConnectionState(Vpn::ConnectionState::Connected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
|
||||||
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
|
||||||
qCritical() << "Tun2socks process crashed!";
|
|
||||||
} else {
|
|
||||||
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
|
|
||||||
}
|
|
||||||
stop();
|
|
||||||
setLastError(ErrorCode::Tun2SockExecutableCrashed);
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
m_tun2socksProcess->start();
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode XrayProtocol::setupRouting() {
|
|
||||||
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
|
||||||
#endif
|
|
||||||
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
|
||||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
|
||||||
qCritical() << "Failed to assign IP address for TUN";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
|
||||||
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
|
||||||
qCritical() << "Failed to set DNS resolvers for TUN";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
int vpnAdapterIndex = -1;
|
|
||||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
|
||||||
for (auto& netInterface : netInterfaces) {
|
|
||||||
for (auto& address : netInterface.addressEntries()) {
|
|
||||||
if (m_vpnLocalAddress == address.ip().toString())
|
|
||||||
vpnAdapterIndex = netInterface.index();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
static const int vpnAdapterIndex = 0;
|
|
||||||
#endif
|
|
||||||
const bool killSwitchEnabled = QVariant(m_rawConfig.value(config_key::killSwitchOption).toString()).toBool();
|
|
||||||
if (killSwitchEnabled) {
|
|
||||||
if (vpnAdapterIndex != -1) {
|
|
||||||
QJsonObject config = m_rawConfig;
|
|
||||||
config.insert("vpnServer", m_remoteAddress);
|
|
||||||
|
|
||||||
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
|
||||||
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
|
||||||
qCritical() << "Failed to enable killswitch";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
|
|
||||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
|
||||||
|
|
||||||
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
|
|
||||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
|
||||||
qCritical() << "Failed to set routes for TUN";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
|
||||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
|
||||||
qCritical() << "Failed to disable IPv6 routing";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
|
||||||
QJsonObject config = m_rawConfig;
|
|
||||||
config.insert("inetAdapterIndex", inetAdapterIndex);
|
|
||||||
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
|
||||||
config.insert("vpnGateway", m_vpnGateway);
|
|
||||||
config.insert("vpnServer", m_remoteAddress);
|
|
||||||
|
|
||||||
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
|
||||||
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
|
||||||
qCritical() << "Failed to enable peer traffic";
|
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
|
||||||
#endif
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
},
|
|
||||||
[] () {
|
|
||||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "core/ipcclient.h"
|
#include "core/ipcclient.h"
|
||||||
#include "vpnprotocol.h"
|
#include "vpnprotocol.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include <QtCore/qsharedpointer.h>
|
|
||||||
|
|
||||||
class XrayProtocol : public VpnProtocol
|
class XrayProtocol : public VpnProtocol
|
||||||
{
|
{
|
||||||
@@ -15,18 +14,19 @@ public:
|
|||||||
virtual ~XrayProtocol() override;
|
virtual ~XrayProtocol() override;
|
||||||
|
|
||||||
ErrorCode start() override;
|
ErrorCode start() override;
|
||||||
|
ErrorCode startTun2Sock();
|
||||||
void stop() override;
|
void stop() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ErrorCode setupRouting();
|
void readXrayConfiguration(const QJsonObject &configuration);
|
||||||
ErrorCode startTun2Socks();
|
|
||||||
|
|
||||||
QJsonObject m_xrayConfig;
|
QJsonObject m_xrayConfig;
|
||||||
Settings::RouteMode m_routeMode;
|
Settings::RouteMode m_routeMode;
|
||||||
QList<QHostAddress> m_dnsServers;
|
QString m_primaryDNS;
|
||||||
QString m_remoteAddress;
|
QString m_secondaryDNS;
|
||||||
|
#ifndef Q_OS_IOS
|
||||||
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
|
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // XRAYPROTOCOL_H
|
#endif // XRAYPROTOCOL_H
|
||||||
|
|||||||
+1
-13
@@ -27,12 +27,10 @@
|
|||||||
<file>images/controls/folder-open.svg</file>
|
<file>images/controls/folder-open.svg</file>
|
||||||
<file>images/controls/folder-search-2.svg</file>
|
<file>images/controls/folder-search-2.svg</file>
|
||||||
<file>images/controls/gauge.svg</file>
|
<file>images/controls/gauge.svg</file>
|
||||||
<file>images/controls/globe-2.svg</file>
|
|
||||||
<file>images/controls/github.svg</file>
|
<file>images/controls/github.svg</file>
|
||||||
<file>images/controls/help-circle.svg</file>
|
<file>images/controls/help-circle.svg</file>
|
||||||
<file>images/controls/history.svg</file>
|
<file>images/controls/history.svg</file>
|
||||||
<file>images/controls/home.svg</file>
|
<file>images/controls/home.svg</file>
|
||||||
<file>images/controls/infinity.svg</file>
|
|
||||||
<file>images/controls/info.svg</file>
|
<file>images/controls/info.svg</file>
|
||||||
<file>images/controls/mail.svg</file>
|
<file>images/controls/mail.svg</file>
|
||||||
<file>images/controls/map-pin.svg</file>
|
<file>images/controls/map-pin.svg</file>
|
||||||
@@ -57,7 +55,6 @@
|
|||||||
<file>images/controls/settings-news.svg</file>
|
<file>images/controls/settings-news.svg</file>
|
||||||
<file>images/controls/share-2.svg</file>
|
<file>images/controls/share-2.svg</file>
|
||||||
<file>images/controls/split-tunneling.svg</file>
|
<file>images/controls/split-tunneling.svg</file>
|
||||||
<file>images/controls/smartphone.svg</file>
|
|
||||||
<file>images/controls/tag.svg</file>
|
<file>images/controls/tag.svg</file>
|
||||||
<file>images/controls/telegram.svg</file>
|
<file>images/controls/telegram.svg</file>
|
||||||
<file>images/controls/text-cursor.svg</file>
|
<file>images/controls/text-cursor.svg</file>
|
||||||
@@ -132,17 +129,11 @@
|
|||||||
<file>ui/qml/Components/AdLabel.qml</file>
|
<file>ui/qml/Components/AdLabel.qml</file>
|
||||||
<file>ui/qml/Components/ConnectButton.qml</file>
|
<file>ui/qml/Components/ConnectButton.qml</file>
|
||||||
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
||||||
<file>ui/qml/Components/GamepadLoader.qml</file>
|
|
||||||
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
||||||
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
|
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
|
||||||
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
|
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
|
||||||
<file>ui/qml/Components/BenefitRow.qml</file>
|
|
||||||
<file>ui/qml/Components/BenefitsPanel.qml</file>
|
|
||||||
<file>ui/qml/Components/SubscriptionPlanCard.qml</file>
|
|
||||||
<file>ui/qml/Components/TermsAndPrivacyText.qml</file>
|
|
||||||
<file>ui/qml/Components/QuestionDrawer.qml</file>
|
<file>ui/qml/Components/QuestionDrawer.qml</file>
|
||||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||||
<file>ui/qml/Components/SubscriptionExpiredDrawer.qml</file>
|
|
||||||
<file>ui/qml/Components/ServersListView.qml</file>
|
<file>ui/qml/Components/ServersListView.qml</file>
|
||||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||||
@@ -188,7 +179,6 @@
|
|||||||
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
|
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
|
||||||
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
|
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
|
||||||
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
|
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
|
||||||
<file>ui/qml/Controls2/TextTypes/BadgeTextType.qml</file>
|
|
||||||
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
|
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
|
||||||
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
|
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
|
||||||
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
|
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
|
||||||
@@ -234,9 +224,7 @@
|
|||||||
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
<file>ui/qml/Pages2/PageSettingsNewsDetail.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiFreeInfo.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiPremiumInfo.qml</file>
|
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiTrialEmail.qml</file>
|
|
||||||
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
||||||
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
||||||
|
|||||||
+21
-14
@@ -35,12 +35,13 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_settings.setValue("Conf/encrypted", true);
|
m_settings.setValue("Conf/encrypted", true);
|
||||||
|
m_settings.sync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
|
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&mutex);
|
||||||
|
|
||||||
if (m_cache.contains(key)) {
|
if (m_cache.contains(key)) {
|
||||||
return m_cache.value(key);
|
return m_cache.value(key);
|
||||||
@@ -84,7 +85,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
|
|||||||
|
|
||||||
void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&mutex);
|
||||||
|
|
||||||
if (encryptionRequired() && encryptedKeys.contains(key)) {
|
if (encryptionRequired() && encryptedKeys.contains(key)) {
|
||||||
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
|
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
|
||||||
@@ -106,20 +107,26 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_cache.insert(key, value);
|
m_cache.insert(key, value);
|
||||||
|
sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureQSettings::remove(const QString &key)
|
void SecureQSettings::remove(const QString &key)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&mutex);
|
||||||
|
|
||||||
m_settings.remove(key);
|
m_settings.remove(key);
|
||||||
m_cache.remove(key);
|
m_cache.remove(key);
|
||||||
|
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SecureQSettings::sync()
|
||||||
|
{
|
||||||
|
m_settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray SecureQSettings::backupAppConfig() const
|
QByteArray SecureQSettings::backupAppConfig() const
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
|
|
||||||
QJsonObject cfg;
|
QJsonObject cfg;
|
||||||
|
|
||||||
const auto needToBackup = [this](const auto &key) {
|
const auto needToBackup = [this](const auto &key) {
|
||||||
@@ -154,8 +161,6 @@ QByteArray SecureQSettings::backupAppConfig() const
|
|||||||
|
|
||||||
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
|
|
||||||
QJsonObject cfg = QJsonDocument::fromJson(json).object();
|
QJsonObject cfg = QJsonDocument::fromJson(json).object();
|
||||||
if (cfg.isEmpty())
|
if (cfg.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
@@ -168,16 +173,10 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
|
|||||||
setValue(key, cfg.value(key).toVariant());
|
setValue(key, cfg.value(key).toVariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SecureQSettings::clearSettings()
|
|
||||||
{
|
|
||||||
QMutexLocker locker(&m_mutex);
|
|
||||||
m_settings.clear();
|
|
||||||
m_cache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
|
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
|
||||||
{
|
{
|
||||||
QSimpleCrypto::QBlockCipher cipher;
|
QSimpleCrypto::QBlockCipher cipher;
|
||||||
@@ -295,3 +294,11 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
|
|||||||
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
|
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SecureQSettings::clearSettings()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
m_settings.clear();
|
||||||
|
m_cache.clear();
|
||||||
|
sync();
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,16 +16,14 @@ public:
|
|||||||
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
|
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
|
||||||
QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||||
void setValue(const QString &key, const QVariant &value);
|
Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
|
||||||
void remove(const QString &key);
|
void remove(const QString &key);
|
||||||
|
void sync();
|
||||||
|
|
||||||
QByteArray backupAppConfig() const;
|
QByteArray backupAppConfig() const;
|
||||||
bool restoreAppConfig(const QByteArray &json);
|
bool restoreAppConfig(const QByteArray &json);
|
||||||
|
|
||||||
void clearSettings();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QByteArray encryptText(const QByteArray &value) const;
|
QByteArray encryptText(const QByteArray &value) const;
|
||||||
QByteArray decryptText(const QByteArray &ba) const;
|
QByteArray decryptText(const QByteArray &ba) const;
|
||||||
|
|
||||||
@@ -37,6 +35,9 @@ private:
|
|||||||
static QByteArray getSecTag(const QString &tag);
|
static QByteArray getSecTag(const QString &tag);
|
||||||
static void setSecTag(const QString &tag, const QByteArray &data);
|
static void setSecTag(const QString &tag, const QByteArray &data);
|
||||||
|
|
||||||
|
void clearSettings();
|
||||||
|
|
||||||
|
private:
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
|
|
||||||
mutable QHash<QString, QVariant> m_cache;
|
mutable QHash<QString, QVariant> m_cache;
|
||||||
@@ -52,7 +53,7 @@ private:
|
|||||||
|
|
||||||
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
|
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
|
||||||
|
|
||||||
mutable QRecursiveMutex m_mutex;
|
mutable QMutex mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SECUREQSETTINGS_H
|
#endif // SECUREQSETTINGS_H
|
||||||
|
|||||||
@@ -21,5 +21,4 @@ if [ "$(systemctl is-active docker)" != "active" ]; then \
|
|||||||
sleep 5; sudo systemctl start docker; sleep 5;\
|
sleep 5; sudo systemctl start docker; sleep 5;\
|
||||||
fi;\
|
fi;\
|
||||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
||||||
docker --version;\
|
docker --version
|
||||||
uname -sr
|
|
||||||
|
|||||||
+57
-34
@@ -21,10 +21,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
|
|||||||
{
|
{
|
||||||
// Import old settings
|
// Import old settings
|
||||||
if (serversCount() == 0) {
|
if (serversCount() == 0) {
|
||||||
QString user = m_settings.value("Server/userName").toString();
|
QString user = value("Server/userName").toString();
|
||||||
QString password = m_settings.value("Server/password").toString();
|
QString password = value("Server/password").toString();
|
||||||
QString serverName = m_settings.value("Server/serverName").toString();
|
QString serverName = value("Server/serverName").toString();
|
||||||
int port = m_settings.value("Server/serverPort").toInt();
|
int port = value("Server/serverPort").toInt();
|
||||||
|
|
||||||
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
|
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
|
||||||
QJsonObject server;
|
QJsonObject server;
|
||||||
@@ -222,7 +222,7 @@ QString Settings::nextAvailableServerName() const
|
|||||||
|
|
||||||
void Settings::setSaveLogs(bool enabled)
|
void Settings::setSaveLogs(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/saveLogs", enabled);
|
setValue("Conf/saveLogs", enabled);
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
if (!isSaveLogs()) {
|
if (!isSaveLogs()) {
|
||||||
Logger::deInit();
|
Logger::deInit();
|
||||||
@@ -242,12 +242,12 @@ void Settings::setSaveLogs(bool enabled)
|
|||||||
|
|
||||||
QDateTime Settings::getLogEnableDate()
|
QDateTime Settings::getLogEnableDate()
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/logEnableDate").toDateTime();
|
return value("Conf/logEnableDate").toDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setLogEnableDate(QDateTime date)
|
void Settings::setLogEnableDate(QDateTime date)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/logEnableDate", date);
|
setValue("Conf/logEnableDate", date);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::routeModeString(RouteMode mode) const
|
QString Settings::routeModeString(RouteMode mode) const
|
||||||
@@ -261,17 +261,17 @@ QString Settings::routeModeString(RouteMode mode) const
|
|||||||
|
|
||||||
Settings::RouteMode Settings::routeMode() const
|
Settings::RouteMode Settings::routeMode() const
|
||||||
{
|
{
|
||||||
return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
|
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isSitesSplitTunnelingEnabled() const
|
bool Settings::isSitesSplitTunnelingEnabled() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool();
|
return value("Conf/sitesSplitTunnelingEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
|
void Settings::setSitesSplitTunnelingEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled);
|
setValue("Conf/sitesSplitTunnelingEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
|
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
|
||||||
@@ -359,12 +359,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
|
|||||||
|
|
||||||
QString Settings::primaryDns() const
|
QString Settings::primaryDns() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
|
return value("Conf/primaryDns", cloudFlareNs1).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::secondaryDns() const
|
QString Settings::secondaryDns() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString();
|
return value("Conf/secondaryDns", cloudFlareNs2).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::clearSettings()
|
void Settings::clearSettings()
|
||||||
@@ -386,18 +386,18 @@ QString Settings::appsRouteModeString(AppsRouteMode mode) const
|
|||||||
|
|
||||||
Settings::AppsRouteMode Settings::getAppsRouteMode() const
|
Settings::AppsRouteMode Settings::getAppsRouteMode() const
|
||||||
{
|
{
|
||||||
return static_cast<AppsRouteMode>(m_settings.value("Conf/appsRouteMode", 0).toInt());
|
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAppsRouteMode(AppsRouteMode mode)
|
void Settings::setAppsRouteMode(AppsRouteMode mode)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/appsRouteMode", mode);
|
setValue("Conf/appsRouteMode", mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
|
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
|
||||||
{
|
{
|
||||||
QVector<InstalledAppInfo> apps;
|
QVector<InstalledAppInfo> apps;
|
||||||
auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray();
|
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray();
|
||||||
for (const auto &app : appsArray) {
|
for (const auto &app : appsArray) {
|
||||||
InstalledAppInfo appInfo;
|
InstalledAppInfo appInfo;
|
||||||
appInfo.appName = app.toObject().value("appName").toString();
|
appInfo.appName = app.toObject().value("appName").toString();
|
||||||
@@ -419,42 +419,43 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &a
|
|||||||
appInfo.insert("appPath", app.appPath);
|
appInfo.insert("appPath", app.appPath);
|
||||||
appsArray.push_back(appInfo);
|
appsArray.push_back(appInfo);
|
||||||
}
|
}
|
||||||
m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray);
|
setValue("Conf/" + appsRouteModeString(mode), appsArray);
|
||||||
|
m_settings.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isAppsSplitTunnelingEnabled() const
|
bool Settings::isAppsSplitTunnelingEnabled() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool();
|
return value("Conf/appsSplitTunnelingEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
|
void Settings::setAppsSplitTunnelingEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled);
|
setValue("Conf/appsSplitTunnelingEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isKillSwitchEnabled() const
|
bool Settings::isKillSwitchEnabled() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/killSwitchEnabled", true).toBool();
|
return value("Conf/killSwitchEnabled", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setKillSwitchEnabled(bool enabled)
|
void Settings::setKillSwitchEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/killSwitchEnabled", enabled);
|
setValue("Conf/killSwitchEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isStrictKillSwitchEnabled() const
|
bool Settings::isStrictKillSwitchEnabled() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool();
|
return value("Conf/strictKillSwitchEnabled", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setStrictKillSwitchEnabled(bool enabled)
|
void Settings::setStrictKillSwitchEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/strictKillSwitchEnabled", enabled);
|
setValue("Conf/strictKillSwitchEnabled", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::getInstallationUuid(const bool needCreate)
|
QString Settings::getInstallationUuid(const bool needCreate)
|
||||||
{
|
{
|
||||||
auto uuid = m_settings.value("Conf/installationUuid", "").toString();
|
auto uuid = value("Conf/installationUuid", "").toString();
|
||||||
if (needCreate && uuid.isEmpty()) {
|
if (needCreate && uuid.isEmpty()) {
|
||||||
uuid = QUuid::createUuid().toString();
|
uuid = QUuid::createUuid().toString();
|
||||||
|
|
||||||
@@ -475,7 +476,7 @@ QString Settings::getInstallationUuid(const bool needCreate)
|
|||||||
|
|
||||||
void Settings::setInstallationUuid(const QString &uuid)
|
void Settings::setInstallationUuid(const QString &uuid)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/installationUuid", uuid);
|
setValue("Conf/installationUuid", uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerCredentials Settings::defaultServerCredentials() const
|
ServerCredentials Settings::defaultServerCredentials() const
|
||||||
@@ -496,6 +497,28 @@ ServerCredentials Settings::serverCredentials(int index) const
|
|||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
|
||||||
|
{
|
||||||
|
QVariant returnValue;
|
||||||
|
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
|
||||||
|
returnValue = m_settings.value(key, defaultValue);
|
||||||
|
} else {
|
||||||
|
QMetaObject::invokeMethod(&m_settings, "value", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, returnValue),
|
||||||
|
Q_ARG(const QString &, key), Q_ARG(const QVariant &, defaultValue));
|
||||||
|
}
|
||||||
|
return returnValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setValue(const QString &key, const QVariant &value)
|
||||||
|
{
|
||||||
|
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
|
||||||
|
m_settings.setValue(key, value);
|
||||||
|
} else {
|
||||||
|
QMetaObject::invokeMethod(&m_settings, "setValue", Qt::BlockingQueuedConnection, Q_ARG(const QString &, key),
|
||||||
|
Q_ARG(const QVariant &, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Settings::resetGatewayEndpoint()
|
void Settings::resetGatewayEndpoint()
|
||||||
{
|
{
|
||||||
m_gatewayEndpoint = gatewayEndpoint;
|
m_gatewayEndpoint = gatewayEndpoint;
|
||||||
@@ -518,50 +541,50 @@ QString Settings::getGatewayEndpoint(bool isTestPurchase)
|
|||||||
|
|
||||||
bool Settings::isDevGatewayEnv(bool isTestPurchase)
|
bool Settings::isDevGatewayEnv(bool isTestPurchase)
|
||||||
{
|
{
|
||||||
return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool();
|
return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::toggleDevGatewayEnv(bool enabled)
|
void Settings::toggleDevGatewayEnv(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/devGatewayEnv", enabled);
|
setValue("Conf/devGatewayEnv", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isHomeAdLabelVisible()
|
bool Settings::isHomeAdLabelVisible()
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
|
return value("Conf/homeAdLabelVisible", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::disableHomeAdLabel()
|
void Settings::disableHomeAdLabel()
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/homeAdLabelVisible", false);
|
setValue("Conf/homeAdLabelVisible", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::isPremV1MigrationReminderActive()
|
bool Settings::isPremV1MigrationReminderActive()
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool();
|
return value("Conf/premV1MigrationReminderActive", true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::disablePremV1MigrationReminder()
|
void Settings::disablePremV1MigrationReminder()
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/premV1MigrationReminderActive", false);
|
setValue("Conf/premV1MigrationReminderActive", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Settings::allowedDnsServers() const
|
QStringList Settings::allowedDnsServers() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/allowedDnsServers").toStringList();
|
return value("Conf/allowedDnsServers").toStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setAllowedDnsServers(const QStringList &servers)
|
void Settings::setAllowedDnsServers(const QStringList &servers)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/allowedDnsServers", servers);
|
setValue("Conf/allowedDnsServers", servers);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Settings::readNewsIds() const
|
QStringList Settings::readNewsIds() const
|
||||||
{
|
{
|
||||||
return m_settings.value("News/readIds").toStringList();
|
return value("News/readIds").toStringList();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setReadNewsIds(const QStringList &ids)
|
void Settings::setReadNewsIds(const QStringList &ids)
|
||||||
{
|
{
|
||||||
m_settings.setValue("News/readIds", ids);
|
setValue("News/readIds", ids);
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-28
@@ -29,11 +29,11 @@ public:
|
|||||||
|
|
||||||
QJsonArray serversArray() const
|
QJsonArray serversArray() const
|
||||||
{
|
{
|
||||||
return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array();
|
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array();
|
||||||
}
|
}
|
||||||
void setServersArray(const QJsonArray &servers)
|
void setServersArray(const QJsonArray &servers)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson());
|
setValue("Servers/serversList", QJsonDocument(servers).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Servers section
|
// Servers section
|
||||||
@@ -45,11 +45,11 @@ public:
|
|||||||
|
|
||||||
int defaultServerIndex() const
|
int defaultServerIndex() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Servers/defaultServerIndex", 0).toInt();
|
return value("Servers/defaultServerIndex", 0).toInt();
|
||||||
}
|
}
|
||||||
void setDefaultServer(int index)
|
void setDefaultServer(int index)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Servers/defaultServerIndex", index);
|
setValue("Servers/defaultServerIndex", index);
|
||||||
}
|
}
|
||||||
QJsonObject defaultServer() const
|
QJsonObject defaultServer() const
|
||||||
{
|
{
|
||||||
@@ -78,34 +78,25 @@ public:
|
|||||||
// App settings section
|
// App settings section
|
||||||
bool isAutoConnect() const
|
bool isAutoConnect() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/autoConnect", false).toBool();
|
return value("Conf/autoConnect", false).toBool();
|
||||||
}
|
}
|
||||||
void setAutoConnect(bool enabled)
|
void setAutoConnect(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/autoConnect", enabled);
|
setValue("Conf/autoConnect", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStartMinimized() const
|
bool isStartMinimized() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/startMinimized", false).toBool();
|
return value("Conf/startMinimized", false).toBool();
|
||||||
}
|
}
|
||||||
void setStartMinimized(bool enabled)
|
void setStartMinimized(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/startMinimized", enabled);
|
setValue("Conf/startMinimized", enabled);
|
||||||
}
|
|
||||||
|
|
||||||
bool isNewsNotifications() const
|
|
||||||
{
|
|
||||||
return m_settings.value("Conf/newsNotifications", true).toBool();
|
|
||||||
}
|
|
||||||
void setNewsNotifications(bool enabled)
|
|
||||||
{
|
|
||||||
m_settings.setValue("Conf/newsNotifications", enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSaveLogs() const
|
bool isSaveLogs() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/saveLogs", false).toBool();
|
return value("Conf/saveLogs", false).toBool();
|
||||||
}
|
}
|
||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
|
|
||||||
@@ -122,18 +113,19 @@ public:
|
|||||||
QString routeModeString(RouteMode mode) const;
|
QString routeModeString(RouteMode mode) const;
|
||||||
|
|
||||||
RouteMode routeMode() const;
|
RouteMode routeMode() const;
|
||||||
void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); }
|
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); }
|
||||||
|
|
||||||
bool isSitesSplitTunnelingEnabled() const;
|
bool isSitesSplitTunnelingEnabled() const;
|
||||||
void setSitesSplitTunnelingEnabled(bool enabled);
|
void setSitesSplitTunnelingEnabled(bool enabled);
|
||||||
|
|
||||||
QVariantMap vpnSites(RouteMode mode) const
|
QVariantMap vpnSites(RouteMode mode) const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/" + routeModeString(mode)).toMap();
|
return value("Conf/" + routeModeString(mode)).toMap();
|
||||||
}
|
}
|
||||||
void setVpnSites(RouteMode mode, const QVariantMap &sites)
|
void setVpnSites(RouteMode mode, const QVariantMap &sites)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/" + routeModeString(mode), sites);
|
setValue("Conf/" + routeModeString(mode), sites);
|
||||||
|
m_settings.sync();
|
||||||
}
|
}
|
||||||
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
|
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
|
||||||
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
|
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
|
||||||
@@ -146,11 +138,11 @@ public:
|
|||||||
|
|
||||||
bool useAmneziaDns() const
|
bool useAmneziaDns() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/useAmneziaDns", true).toBool();
|
return value("Conf/useAmneziaDns", true).toBool();
|
||||||
}
|
}
|
||||||
void setUseAmneziaDns(bool enabled)
|
void setUseAmneziaDns(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/useAmneziaDns", enabled);
|
setValue("Conf/useAmneziaDns", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString primaryDns() const;
|
QString primaryDns() const;
|
||||||
@@ -159,13 +151,13 @@ public:
|
|||||||
// QString primaryDns() const { return m_primaryDns; }
|
// QString primaryDns() const { return m_primaryDns; }
|
||||||
void setPrimaryDns(const QString &primaryDns)
|
void setPrimaryDns(const QString &primaryDns)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/primaryDns", primaryDns);
|
setValue("Conf/primaryDns", primaryDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// QString secondaryDns() const { return m_secondaryDns; }
|
// QString secondaryDns() const { return m_secondaryDns; }
|
||||||
void setSecondaryDns(const QString &secondaryDns)
|
void setSecondaryDns(const QString &secondaryDns)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/secondaryDns", secondaryDns);
|
setValue("Conf/secondaryDns", secondaryDns);
|
||||||
}
|
}
|
||||||
|
|
||||||
// static constexpr char openNicNs5[] = "94.103.153.176";
|
// static constexpr char openNicNs5[] = "94.103.153.176";
|
||||||
@@ -187,16 +179,16 @@ public:
|
|||||||
};
|
};
|
||||||
void setAppLanguage(QLocale locale)
|
void setAppLanguage(QLocale locale)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/appLanguage", locale.name());
|
setValue("Conf/appLanguage", locale.name());
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isScreenshotsEnabled() const
|
bool isScreenshotsEnabled() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/screenshotsEnabled", true).toBool();
|
return value("Conf/screenshotsEnabled", true).toBool();
|
||||||
}
|
}
|
||||||
void setScreenshotsEnabled(bool enabled)
|
void setScreenshotsEnabled(bool enabled)
|
||||||
{
|
{
|
||||||
m_settings.setValue("Conf/screenshotsEnabled", enabled);
|
setValue("Conf/screenshotsEnabled", enabled);
|
||||||
emit screenshotsEnabledChanged(enabled);
|
emit screenshotsEnabledChanged(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,6 +246,9 @@ signals:
|
|||||||
void settingsCleared();
|
void settingsCleared();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||||
|
void setValue(const QString &key, const QVariant &value);
|
||||||
|
|
||||||
void setInstallationUuid(const QString &uuid);
|
void setInstallationUuid(const QString &uuid);
|
||||||
|
|
||||||
mutable SecureQSettings m_settings;
|
mutable SecureQSettings m_settings;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,9 @@
|
|||||||
#include "ui/controllers/systemController.h"
|
#include "ui/controllers/systemController.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QHash>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QVariantMap>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
|
||||||
@@ -44,15 +39,6 @@ namespace
|
|||||||
constexpr char serviceInfo[] = "service_info";
|
constexpr char serviceInfo[] = "service_info";
|
||||||
constexpr char serviceProtocol[] = "service_protocol";
|
constexpr char serviceProtocol[] = "service_protocol";
|
||||||
|
|
||||||
constexpr char services[] = "services";
|
|
||||||
constexpr char serviceDescription[] = "service_description";
|
|
||||||
constexpr char subscriptionPlans[] = "subscription_plans";
|
|
||||||
constexpr char storeProductId[] = "store_product_id";
|
|
||||||
constexpr char priceLabel[] = "price_label";
|
|
||||||
constexpr char subtitle[] = "subtitle";
|
|
||||||
constexpr char isTrial[] = "is_trial";
|
|
||||||
constexpr char minPriceLabel[] = "min_price_label";
|
|
||||||
|
|
||||||
constexpr char apiPayload[] = "api_payload";
|
constexpr char apiPayload[] = "api_payload";
|
||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
|
|
||||||
@@ -61,6 +47,9 @@ namespace
|
|||||||
|
|
||||||
constexpr char config[] = "config";
|
constexpr char config[] = "config";
|
||||||
|
|
||||||
|
constexpr char subscription[] = "subscription";
|
||||||
|
constexpr char endDate[] = "end_date";
|
||||||
|
|
||||||
constexpr char isConnectEvent[] = "is_connect_event";
|
constexpr char isConnectEvent[] = "is_connect_event";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,190 +241,13 @@ namespace
|
|||||||
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
|
||||||
struct StoreKitPlanQuote {
|
|
||||||
QString displayPrice;
|
|
||||||
double priceAmount = 0.0;
|
|
||||||
double subscriptionBillingMonths = 0.0;
|
|
||||||
QString displayPricePerMonth;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr double kOneMonthThreshold = 1.0 + 1e-6;
|
|
||||||
constexpr double kMonthsFallbackThreshold = 1e-6;
|
|
||||||
constexpr double kMonthlyPriceEpsilon = 1e-9;
|
|
||||||
|
|
||||||
QStringList collectPremiumStoreProductIds(const QJsonArray &services)
|
|
||||||
{
|
|
||||||
QStringList productIds;
|
|
||||||
QSet<QString> seenProductIds;
|
|
||||||
for (const QJsonValue &serviceValue : services) {
|
|
||||||
const QJsonObject serviceObject = serviceValue.toObject();
|
|
||||||
if (serviceObject.value(configKey::serviceType).toString() != serviceType::amneziaPremium) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const QJsonArray subscriptionPlans =
|
|
||||||
serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray();
|
|
||||||
for (const QJsonValue &planValue : subscriptionPlans) {
|
|
||||||
if (!planValue.isObject()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString();
|
|
||||||
if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
seenProductIds.insert(storeProductId);
|
|
||||||
productIds.append(storeProductId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return productIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<QString, StoreKitPlanQuote> buildStoreKitQuoteMap(const QList<QVariantMap> &fetchedProducts)
|
|
||||||
{
|
|
||||||
QHash<QString, StoreKitPlanQuote> quotesByProductId;
|
|
||||||
quotesByProductId.reserve(fetchedProducts.size());
|
|
||||||
|
|
||||||
for (const QVariantMap &productInfo : fetchedProducts) {
|
|
||||||
const QString productId = productInfo.value(QStringLiteral("productId")).toString();
|
|
||||||
if (productId.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString();
|
|
||||||
if (displayPrice.isEmpty()) {
|
|
||||||
const QString price = productInfo.value(QStringLiteral("price")).toString();
|
|
||||||
const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString();
|
|
||||||
displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
StoreKitPlanQuote quote;
|
|
||||||
quote.displayPrice = displayPrice;
|
|
||||||
quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble();
|
|
||||||
quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble();
|
|
||||||
quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString();
|
|
||||||
quotesByProductId.insert(productId, quote);
|
|
||||||
}
|
|
||||||
|
|
||||||
return quotesByProductId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data)
|
|
||||||
{
|
|
||||||
QJsonArray services = data.value(configKey::services).toArray();
|
|
||||||
if (services.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QStringList productIds = collectPremiumStoreProductIds(services);
|
|
||||||
if (productIds.isEmpty()) {
|
|
||||||
qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<QVariantMap> fetchedProducts;
|
|
||||||
QEventLoop loop;
|
|
||||||
IosController::Instance()->fetchProducts(productIds,
|
|
||||||
[&](const QList<QVariantMap> &products, const QStringList &invalidIds,
|
|
||||||
const QString &errorString) {
|
|
||||||
if (!errorString.isEmpty()) {
|
|
||||||
qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString;
|
|
||||||
}
|
|
||||||
if (!invalidIds.isEmpty()) {
|
|
||||||
qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds;
|
|
||||||
}
|
|
||||||
fetchedProducts = products;
|
|
||||||
loop.quit();
|
|
||||||
});
|
|
||||||
loop.exec();
|
|
||||||
|
|
||||||
const QHash<QString, StoreKitPlanQuote> quotesByProductId = buildStoreKitQuoteMap(fetchedProducts);
|
|
||||||
|
|
||||||
for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) {
|
|
||||||
QJsonObject serviceObject = services.at(serviceIndex).toObject();
|
|
||||||
if (serviceObject.value(configKey::serviceType).toString() != serviceType::amneziaPremium) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject();
|
|
||||||
const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray();
|
|
||||||
|
|
||||||
QJsonArray mergedPlans;
|
|
||||||
double minMonthlyAmount = std::numeric_limits<double>::infinity();
|
|
||||||
QString minMonthlyDisplay;
|
|
||||||
|
|
||||||
for (const QJsonValue &planValue : sourcePlans) {
|
|
||||||
if (!planValue.isObject()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject planObject = planValue.toObject();
|
|
||||||
const QString storeProductId = planObject.value(configKey::storeProductId).toString();
|
|
||||||
if (storeProductId.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto quoteIterator = quotesByProductId.constFind(storeProductId);
|
|
||||||
if (quoteIterator == quotesByProductId.cend()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool isTrialPlan = planObject.value(configKey::isTrial).toBool();
|
|
||||||
const StoreKitPlanQuote "e = *quoteIterator;
|
|
||||||
planObject.insert(configKey::priceLabel, quote.displayPrice);
|
|
||||||
|
|
||||||
const double months = quote.subscriptionBillingMonths;
|
|
||||||
if (!isTrialPlan && months > kOneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) {
|
|
||||||
planObject.insert(
|
|
||||||
configKey::subtitle,
|
|
||||||
QCoreApplication::translate("ApiConfigsController", "%1/mo", "IAP: price per month in plan subtitle")
|
|
||||||
.arg(quote.displayPricePerMonth));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTrialPlan && quote.priceAmount > 0.0) {
|
|
||||||
const double monthsForMin = months > kMonthsFallbackThreshold ? months : 1.0;
|
|
||||||
const double monthly = quote.priceAmount / monthsForMin;
|
|
||||||
if (monthly < minMonthlyAmount - kMonthlyPriceEpsilon) {
|
|
||||||
minMonthlyAmount = monthly;
|
|
||||||
minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mergedPlans.append(planObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
descriptionObject.insert(configKey::subscriptionPlans, mergedPlans);
|
|
||||||
if (minMonthlyAmount < std::numeric_limits<double>::infinity() && !minMonthlyDisplay.isEmpty()) {
|
|
||||||
descriptionObject.insert(configKey::minPriceLabel,
|
|
||||||
QCoreApplication::translate("ApiConfigsController", "from %1 per month",
|
|
||||||
"IAP: card footer minimum monthly price from StoreKit")
|
|
||||||
.arg(minMonthlyDisplay));
|
|
||||||
}
|
|
||||||
serviceObject.insert(configKey::serviceDescription, descriptionObject);
|
|
||||||
services.replace(serviceIndex, serviceObject);
|
|
||||||
}
|
|
||||||
data.insert(configKey::services, services);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
||||||
const QSharedPointer<ApiSubscriptionPlansModel> &subscriptionPlansModel,
|
|
||||||
const QSharedPointer<ApiBenefitsModel> &benefitsModel,
|
|
||||||
const std::shared_ptr<Settings> &settings, QObject *parent)
|
const std::shared_ptr<Settings> &settings, QObject *parent)
|
||||||
: QObject(parent)
|
: QObject(parent), m_serversModel(serversModel), m_apiServicesModel(apiServicesModel), m_settings(settings)
|
||||||
, m_serversModel(serversModel)
|
|
||||||
, m_apiServicesModel(apiServicesModel)
|
|
||||||
, m_subscriptionPlansModel(subscriptionPlansModel)
|
|
||||||
, m_benefitsModel(benefitsModel)
|
|
||||||
, m_settings(settings)
|
|
||||||
{
|
{
|
||||||
connect(m_apiServicesModel.data(), &ApiServicesModel::serviceSelectionChanged, this, [this]() {
|
|
||||||
const ApiServicesModel::ApiServicesData serviceData = m_apiServicesModel->selectedServiceData();
|
|
||||||
m_subscriptionPlansModel->updateModel(serviceData.subscriptionPlansJson);
|
|
||||||
m_benefitsModel->updateModel(serviceData.benefits);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::exportVpnKey(const QString &fileName)
|
bool ApiConfigsController::exportVpnKey(const QString &fileName)
|
||||||
@@ -488,8 +300,6 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << responseBody;
|
|
||||||
|
|
||||||
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
|
QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object();
|
||||||
QString nativeConfig = jsonConfig.value(configKey::config).toString();
|
QString nativeConfig = jsonConfig.value(configKey::config).toString();
|
||||||
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
|
nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey);
|
||||||
@@ -556,8 +366,6 @@ bool ApiConfigsController::fillAvailableServices()
|
|||||||
{
|
{
|
||||||
QJsonObject apiPayload;
|
QJsonObject apiPayload;
|
||||||
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
apiPayload[configKey::osVersion] = QSysInfo::productType();
|
||||||
apiPayload[configKey::appVersion] = QString(APP_VERSION);
|
|
||||||
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
|
|
||||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
@@ -574,11 +382,6 @@ bool ApiConfigsController::fillAvailableServices()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
|
QJsonObject data = QJsonDocument::fromJson(responseBody).object();
|
||||||
|
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
|
||||||
mergeStoreKitPricesIntoPremiumPlans(data);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_apiServicesModel->updateModel(data);
|
m_apiServicesModel->updateModel(data);
|
||||||
if (m_apiServicesModel->rowCount() > 0) {
|
if (m_apiServicesModel->rowCount() > 0) {
|
||||||
m_apiServicesModel->setServiceIndex(0);
|
m_apiServicesModel->setServiceIndex(0);
|
||||||
@@ -589,42 +392,39 @@ bool ApiConfigsController::fillAvailableServices()
|
|||||||
bool ApiConfigsController::importService()
|
bool ApiConfigsController::importService()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
const bool isIosOrMacOsNe = true;
|
bool isIosOrMacOsNe = true;
|
||||||
#else
|
#else
|
||||||
const bool isIosOrMacOsNe = false;
|
bool isIosOrMacOsNe = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaPremium) {
|
if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaPremium) {
|
||||||
if (isIosOrMacOsNe) {
|
if (isIosOrMacOsNe) {
|
||||||
return importPremiumFromAppStore(QString());
|
importSerivceFromAppStore();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) {
|
} else {
|
||||||
return importFreeFromGateway();
|
importServiceFromGateway();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProductId)
|
bool ApiConfigsController::importSerivceFromAppStore()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
QString productId = storeProductId.trimmed();
|
|
||||||
if (productId.isEmpty()) {
|
|
||||||
productId = QStringLiteral("amnezia_premium_6_month");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool purchaseOk = false;
|
bool purchaseOk = false;
|
||||||
QString originalTransactionId;
|
QString originalTransactionId;
|
||||||
QString storeTransactionId;
|
QString storeTransactionId;
|
||||||
QString purchasedStoreProductId;
|
QString storeProductId;
|
||||||
QString purchaseError;
|
QString purchaseError;
|
||||||
QEventLoop waitPurchase;
|
QEventLoop waitPurchase;
|
||||||
IosController::Instance()->purchaseProduct(productId,
|
IosController::Instance()->purchaseProduct(QStringLiteral("amnezia_premium_6_month"),
|
||||||
[&](bool success, const QString &transactionId, const QString &purchasedProductId,
|
[&](bool success, const QString &txId, const QString &purchasedProductId,
|
||||||
const QString &originalTransactionIdResponse, const QString &errorString) {
|
const QString &originalTxId, const QString &errorString) {
|
||||||
purchaseOk = success;
|
purchaseOk = success;
|
||||||
originalTransactionId = originalTransactionIdResponse;
|
originalTransactionId = originalTxId;
|
||||||
storeTransactionId = transactionId;
|
storeTransactionId = txId;
|
||||||
purchasedStoreProductId = purchasedProductId;
|
storeProductId = purchasedProductId;
|
||||||
purchaseError = errorString;
|
purchaseError = errorString;
|
||||||
waitPurchase.quit();
|
waitPurchase.quit();
|
||||||
});
|
});
|
||||||
@@ -636,7 +436,7 @@ bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProduct
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId
|
qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId
|
||||||
<< "originalTransactionId =" << originalTransactionId << "productId =" << purchasedStoreProductId;
|
<< "originalTransactionId =" << originalTransactionId << "productId =" << storeProductId;
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
@@ -660,26 +460,18 @@ bool ApiConfigsController::importPremiumFromAppStore(const QString &storeProduct
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int duplicateServerIndex = -1;
|
errorCode = importServiceFromBilling(responseBody, isTestPurchase);
|
||||||
errorCode = importServiceFromBilling(responseBody, isTestPurchase, duplicateServerIndex);
|
|
||||||
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
|
|
||||||
emit installServerFromApiFinished(tr("This subscription is already in the app."), duplicateServerIndex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit errorOccurred(errorCode);
|
emit errorOccurred(errorCode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
emit installServerFromApiFinished(
|
|
||||||
tr("%1 was added to the app.").arg(m_apiServicesModel->getSelectedServiceName()));
|
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
Q_UNUSED(storeProductId);
|
|
||||||
return false;
|
|
||||||
#endif
|
#endif
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::restoreServiceFromAppStore()
|
bool ApiConfigsController::restoreSerivceFromAppStore()
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
const QString premiumServiceType = QStringLiteral("amnezia-premium");
|
const QString premiumServiceType = QStringLiteral("amnezia-premium");
|
||||||
@@ -695,12 +487,20 @@ bool ApiConfigsController::restoreServiceFromAppStore()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int premiumServiceIndex = m_apiServicesModel->serviceIndexForType(premiumServiceType);
|
// Ensure we have a valid premium selection for gateway requests
|
||||||
if (premiumServiceIndex < 0) {
|
bool premiumSelected = false;
|
||||||
|
for (int i = 0; i < m_apiServicesModel->rowCount(); ++i) {
|
||||||
|
m_apiServicesModel->setServiceIndex(i);
|
||||||
|
if (m_apiServicesModel->getSelectedServiceType() == premiumServiceType) {
|
||||||
|
premiumSelected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!premiumSelected) {
|
||||||
emit errorOccurred(ErrorCode::ApiServicesMissingError);
|
emit errorOccurred(ErrorCode::ApiServicesMissingError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_apiServicesModel->setServiceIndex(premiumServiceIndex);
|
|
||||||
|
|
||||||
bool restoreSuccess = false;
|
bool restoreSuccess = false;
|
||||||
QList<QVariantMap> restoredTransactions;
|
QList<QVariantMap> restoredTransactions;
|
||||||
@@ -722,23 +522,15 @@ bool ApiConfigsController::restoreServiceFromAppStore()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (restoredTransactions.isEmpty()) {
|
if (restoredTransactions.isEmpty()) {
|
||||||
qInfo().noquote() << "[IAP] Restore completed, but no active entitlements found";
|
qInfo().noquote() << "[IAP] Restore completed, but no transactions were returned";
|
||||||
emit errorOccurred(ErrorCode::ApiNoPurchasedSubscriptionsError);
|
emit errorOccurred(ErrorCode::ApiPurchaseError);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool isTestPurchase = IosController::Instance()->isTestFlight();
|
|
||||||
const QString serviceType = m_apiServicesModel->getSelectedServiceType();
|
|
||||||
const QString serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol();
|
|
||||||
const QString countryCode = m_apiServicesModel->getCountryCode();
|
|
||||||
const QString appLanguage = m_settings->getAppLanguage().name().split("_").first();
|
|
||||||
const QString installationUuid = m_settings->getInstallationUuid(true);
|
|
||||||
|
|
||||||
bool hasInstalledConfig = false;
|
bool hasInstalledConfig = false;
|
||||||
bool duplicateConfigAlreadyPresent = false;
|
bool duplicateConfigAlreadyPresent = false;
|
||||||
int duplicateServerIndex = -1;
|
int duplicateCount = 0;
|
||||||
QSet<QString> processedOriginalTransactionIds;
|
QSet<QString> processedTransactions;
|
||||||
|
|
||||||
for (const QVariantMap &transaction : restoredTransactions) {
|
for (const QVariantMap &transaction : restoredTransactions) {
|
||||||
const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString();
|
const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString();
|
||||||
const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString();
|
const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString();
|
||||||
@@ -749,28 +541,28 @@ bool ApiConfigsController::restoreServiceFromAppStore()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedOriginalTransactionIds.contains(originalTransactionId)) {
|
if (processedTransactions.contains(originalTransactionId)) {
|
||||||
qInfo().noquote() << "[IAP] Skipping duplicate restored transaction" << originalTransactionId;
|
duplicateCount++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
processedOriginalTransactionIds.insert(originalTransactionId);
|
processedTransactions.insert(originalTransactionId);
|
||||||
|
|
||||||
qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId
|
qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId
|
||||||
<< "originalTransactionId =" << originalTransactionId << "productId =" << productId;
|
<< "originalTransactionId =" << originalTransactionId << "productId =" << productId;
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
appLanguage,
|
m_settings->getAppLanguage().name().split("_").first(),
|
||||||
installationUuid,
|
m_settings->getInstallationUuid(true),
|
||||||
countryCode,
|
m_apiServicesModel->getCountryCode(),
|
||||||
"",
|
"",
|
||||||
serviceType,
|
m_apiServicesModel->getSelectedServiceType(),
|
||||||
serviceProtocol,
|
m_apiServicesModel->getSelectedServiceProtocol(),
|
||||||
QJsonObject() };
|
QJsonObject() };
|
||||||
|
|
||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||||
apiPayload[apiDefs::key::transactionId] = originalTransactionId;
|
apiPayload[apiDefs::key::transactionId] = originalTransactionId;
|
||||||
|
auto isTestPurchase = IosController::Instance()->isTestFlight();
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
|
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
@@ -779,42 +571,34 @@ bool ApiConfigsController::restoreServiceFromAppStore()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int currentDuplicateServerIndex = -1;
|
ErrorCode installError = importServiceFromBilling(responseBody, isTestPurchase);
|
||||||
errorCode = importServiceFromBilling(responseBody, isTestPurchase, currentDuplicateServerIndex);
|
|
||||||
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
|
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
|
||||||
duplicateConfigAlreadyPresent = true;
|
duplicateConfigAlreadyPresent = true;
|
||||||
if (duplicateServerIndex < 0) {
|
qInfo().noquote() << "[IAP] Skipping restored transaction" << originalTransactionId
|
||||||
duplicateServerIndex = currentDuplicateServerIndex;
|
<< "because subscription config with the same vpn_key already exists";
|
||||||
}
|
} else if (errorCode != ErrorCode::NoError) {
|
||||||
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists" << originalTransactionId;
|
qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId;
|
||||||
continue;
|
} else {
|
||||||
|
hasInstalledConfig = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId
|
|
||||||
<< "errorCode =" << static_cast<int>(errorCode);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasInstalledConfig = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasInstalledConfig) {
|
if (!hasInstalledConfig) {
|
||||||
if (duplicateConfigAlreadyPresent) {
|
const ErrorCode restoreError = duplicateConfigAlreadyPresent ? ErrorCode::ApiConfigAlreadyAdded : ErrorCode::ApiPurchaseError;
|
||||||
emit installServerFromApiFinished(tr("This subscription is already in the app."), duplicateServerIndex);
|
emit errorOccurred(restoreError);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit errorOccurred(ErrorCode::ApiPurchaseError);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit installServerFromApiFinished(tr("Subscription restored successfully."));
|
emit installServerFromApiFinished(tr("Subscription restored successfully."));
|
||||||
|
if (duplicateCount > 0) {
|
||||||
|
qInfo().noquote() << "[IAP] Skipped" << duplicateCount
|
||||||
|
<< "duplicate restored transactions for original transaction IDs already processed";
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::importFreeFromGateway()
|
bool ApiConfigsController::importServiceFromGateway()
|
||||||
{
|
{
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||||
QString(APP_VERSION),
|
QString(APP_VERSION),
|
||||||
@@ -866,72 +650,6 @@ bool ApiConfigsController::importFreeFromGateway()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ApiConfigsController::importTrialFromGateway(const QString &email)
|
|
||||||
{
|
|
||||||
const QString trimmedEmail = email.trimmed();
|
|
||||||
if (trimmedEmail.isEmpty()) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
|
||||||
QString(APP_VERSION),
|
|
||||||
m_settings->getAppLanguage().name().split("_").first(),
|
|
||||||
m_settings->getInstallationUuid(true),
|
|
||||||
m_apiServicesModel->getCountryCode(),
|
|
||||||
"",
|
|
||||||
m_apiServicesModel->getSelectedServiceType(),
|
|
||||||
m_apiServicesModel->getSelectedServiceProtocol(),
|
|
||||||
QJsonObject() };
|
|
||||||
|
|
||||||
if (m_serversModel->isServerFromApiAlreadyExists(gatewayRequestData.userCountryCode, gatewayRequestData.serviceType,
|
|
||||||
gatewayRequestData.serviceProtocol)) {
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol);
|
|
||||||
|
|
||||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
|
||||||
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
|
|
||||||
apiPayload.insert(apiDefs::key::email, trimmedEmail);
|
|
||||||
|
|
||||||
QByteArray responseBody;
|
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/trial"), apiPayload, responseBody);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
|
|
||||||
QString key = responseObject.value(apiDefs::key::config).toString();
|
|
||||||
if (key.isEmpty()) {
|
|
||||||
qWarning().noquote() << "[Trial] trial response does not contain config field";
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
key.replace(QStringLiteral("vpn://"), QString());
|
|
||||||
QByteArray configBytes = QByteArray::fromBase64(key.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
||||||
QByteArray uncompressed = qUncompress(configBytes);
|
|
||||||
if (!uncompressed.isEmpty()) {
|
|
||||||
configBytes = uncompressed;
|
|
||||||
}
|
|
||||||
if (configBytes.isEmpty()) {
|
|
||||||
qWarning().noquote() << "[Trial] trial response config payload is empty";
|
|
||||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject configObject = QJsonDocument::fromJson(configBytes).object();
|
|
||||||
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
|
|
||||||
configObject.insert(config_key::crc, crc);
|
|
||||||
m_serversModel->addServer(configObject);
|
|
||||||
|
|
||||||
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
bool reloadServiceConfig)
|
bool reloadServiceConfig)
|
||||||
{
|
{
|
||||||
@@ -958,7 +676,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
||||||
bool wasSubscriptionExpired = m_serversModel->data(serverIndex, ServersModel::IsSubscriptionExpiredRole).toBool();
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||||
|
|
||||||
@@ -975,12 +692,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
newApiConfig.insert(configKey::serviceType, apiConfig.value(configKey::serviceType));
|
||||||
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
newApiConfig.insert(configKey::serviceProtocol, apiConfig.value(configKey::serviceProtocol));
|
||||||
newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey));
|
newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey));
|
||||||
if (apiConfig.contains(apiDefs::key::isInAppPurchase)) {
|
|
||||||
newApiConfig.insert(apiDefs::key::isInAppPurchase, apiConfig.value(apiDefs::key::isInAppPurchase));
|
|
||||||
}
|
|
||||||
if (apiConfig.contains(apiDefs::key::isTestPurchase)) {
|
|
||||||
newApiConfig.insert(apiDefs::key::isTestPurchase, apiConfig.value(apiDefs::key::isTestPurchase));
|
|
||||||
}
|
|
||||||
|
|
||||||
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
newServerConfig.insert(configKey::apiConfig, newApiConfig);
|
||||||
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
newServerConfig.insert(configKey::authData, gatewayRequestData.authData);
|
||||||
@@ -991,11 +702,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
newServerConfig.insert(config_key::nameOverriddenByUser, true);
|
newServerConfig.insert(config_key::nameOverriddenByUser, true);
|
||||||
}
|
}
|
||||||
m_serversModel->editServer(newServerConfig, serverIndex);
|
m_serversModel->editServer(newServerConfig, serverIndex);
|
||||||
|
|
||||||
if (wasSubscriptionExpired) {
|
|
||||||
emit subscriptionRefreshNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reloadServiceConfig) {
|
if (reloadServiceConfig) {
|
||||||
emit reloadServerFromApiFinished(tr("API config reloaded"));
|
emit reloadServerFromApiFinished(tr("API config reloaded"));
|
||||||
} else if (newCountryName.isEmpty()) {
|
} else if (newCountryName.isEmpty()) {
|
||||||
@@ -1005,18 +711,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
|
emit errorOccurred(errorCode);
|
||||||
if (!apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) {
|
|
||||||
apiConfig.insert(apiDefs::key::subscriptionExpiredByServer, true);
|
|
||||||
serverConfig.insert(configKey::apiConfig, apiConfig);
|
|
||||||
m_serversModel->editServer(serverConfig, serverIndex);
|
|
||||||
emit subscriptionExpiredOnServer();
|
|
||||||
} else {
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1202,63 +897,43 @@ QString ApiConfigsController::getVpnKey()
|
|||||||
return m_vpnKey;
|
return m_vpnKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase,
|
ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase)
|
||||||
int &duplicateServerIndex)
|
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
#ifdef Q_OS_IOS
|
||||||
duplicateServerIndex = -1;
|
|
||||||
QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
|
QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
|
||||||
const QString rawVpnKey = responseObject.value(QStringLiteral("key")).toString();
|
QString key = responseObject.value(QStringLiteral("key")).toString();
|
||||||
if (rawVpnKey.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
qWarning().noquote() << "[IAP] Subscription response does not contain a key field";
|
qWarning().noquote() << "[IAP] Subscription response does not contain a key field";
|
||||||
return ErrorCode::ApiPurchaseError;
|
return ErrorCode::ApiPurchaseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString normalizedVpnKey = rawVpnKey;
|
if (m_serversModel->hasServerWithVpnKey(key)) {
|
||||||
normalizedVpnKey.replace(QStringLiteral("vpn://"), QString());
|
|
||||||
|
|
||||||
duplicateServerIndex = m_serversModel->indexOfServerWithVpnKey(normalizedVpnKey);
|
|
||||||
if (duplicateServerIndex >= 0) {
|
|
||||||
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists";
|
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists";
|
||||||
return ErrorCode::ApiConfigAlreadyAdded;
|
return ErrorCode::ApiConfigAlreadyAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray configPayload =
|
QString normalizedKey = key;
|
||||||
QByteArray::fromBase64(normalizedVpnKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
normalizedKey.replace(QStringLiteral("vpn://"), QString());
|
||||||
QByteArray configUncompressed = qUncompress(configPayload);
|
|
||||||
const bool payloadWasCompressed = !configUncompressed.isEmpty();
|
QByteArray configString = QByteArray::fromBase64(normalizedKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
if (payloadWasCompressed) {
|
QByteArray configUncompressed = qUncompress(configString);
|
||||||
configPayload = configUncompressed;
|
if (!configUncompressed.isEmpty()) {
|
||||||
|
configString = configUncompressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configPayload.isEmpty()) {
|
if (configString.isEmpty()) {
|
||||||
qWarning().noquote() << "[IAP] Subscription response config payload is empty";
|
qWarning().noquote() << "[IAP] Subscription response config payload is empty";
|
||||||
return ErrorCode::ApiPurchaseError;
|
return ErrorCode::ApiPurchaseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject configObject = QJsonDocument::fromJson(configPayload).object();
|
QJsonObject configObject = QJsonDocument::fromJson(configString).object();
|
||||||
|
|
||||||
auto apiConfig = configObject.value(apiDefs::key::apiConfig).toObject();
|
|
||||||
apiConfig.insert(apiDefs::key::isTestPurchase, isTestPurchase);
|
|
||||||
apiConfig.insert(apiDefs::key::isInAppPurchase, true);
|
|
||||||
configObject.insert(apiDefs::key::apiConfig, apiConfig);
|
|
||||||
|
|
||||||
configPayload = QJsonDocument(configObject).toJson();
|
|
||||||
if (payloadWasCompressed) {
|
|
||||||
configPayload = qCompress(configPayload, 8);
|
|
||||||
}
|
|
||||||
normalizedVpnKey = QString(configPayload.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
|
||||||
|
|
||||||
duplicateServerIndex = m_serversModel->indexOfServerWithVpnKey(normalizedVpnKey);
|
|
||||||
if (duplicateServerIndex >= 0) {
|
|
||||||
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists";
|
|
||||||
return ErrorCode::ApiConfigAlreadyAdded;
|
|
||||||
}
|
|
||||||
|
|
||||||
apiConfig.insert(apiDefs::key::vpnKey, normalizedVpnKey);
|
|
||||||
configObject.insert(apiDefs::key::apiConfig, apiConfig);
|
|
||||||
|
|
||||||
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
|
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
|
||||||
|
auto apiConfig = configObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
|
apiConfig[apiDefs::key::vpnKey] = normalizedKey;
|
||||||
|
apiConfig[apiDefs::key::isTestPurchase] = isTestPurchase;
|
||||||
|
|
||||||
|
configObject.insert(apiDefs::key::apiConfig, apiConfig);
|
||||||
configObject.insert(config_key::crc, crc);
|
configObject.insert(config_key::crc, crc);
|
||||||
m_serversModel->addServer(configObject);
|
m_serversModel->addServer(configObject);
|
||||||
|
|
||||||
@@ -1266,7 +941,6 @@ ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &respo
|
|||||||
#else
|
#else
|
||||||
Q_UNUSED(responseBody)
|
Q_UNUSED(responseBody)
|
||||||
Q_UNUSED(isTestPurchase)
|
Q_UNUSED(isTestPurchase)
|
||||||
duplicateServerIndex = -1;
|
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
#ifndef APICONFIGSCONTROLLER_H
|
#ifndef APICONFIGSCONTROLLER_H
|
||||||
#define APICONFIGSCONTROLLER_H
|
#define APICONFIGSCONTROLLER_H
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
#include "ui/models/api/apiBenefitsModel.h"
|
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
#include "ui/models/api/apiSubscriptionPlansModel.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
class ApiConfigsController : public QObject
|
class ApiConfigsController : public QObject
|
||||||
@@ -15,9 +12,7 @@ class ApiConfigsController : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, const QSharedPointer<ApiServicesModel> &apiServicesModel,
|
||||||
const QSharedPointer<ApiSubscriptionPlansModel> &subscriptionPlansModel,
|
const std::shared_ptr<Settings> &settings, QObject *parent = nullptr);
|
||||||
const QSharedPointer<ApiBenefitsModel> &benefitsModel, const std::shared_ptr<Settings> &settings,
|
|
||||||
QObject *parent = nullptr);
|
|
||||||
|
|
||||||
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
|
Q_PROPERTY(QList<QString> qrCodes READ getQrCodes NOTIFY vpnKeyExportReady)
|
||||||
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady)
|
Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY vpnKeyExportReady)
|
||||||
@@ -32,10 +27,9 @@ public slots:
|
|||||||
|
|
||||||
bool fillAvailableServices();
|
bool fillAvailableServices();
|
||||||
bool importService();
|
bool importService();
|
||||||
bool importPremiumFromAppStore(const QString &storeProductId);
|
bool importSerivceFromAppStore();
|
||||||
bool restoreServiceFromAppStore();
|
bool restoreSerivceFromAppStore();
|
||||||
bool importFreeFromGateway();
|
bool importServiceFromGateway();
|
||||||
bool importTrialFromGateway(const QString &email);
|
|
||||||
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
|
||||||
bool reloadServiceConfig = false);
|
bool reloadServiceConfig = false);
|
||||||
bool updateServiceFromTelegram(const int serverIndex);
|
bool updateServiceFromTelegram(const int serverIndex);
|
||||||
@@ -49,10 +43,8 @@ public slots:
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
void subscriptionExpiredOnServer();
|
|
||||||
void subscriptionRefreshNeeded();
|
|
||||||
|
|
||||||
void installServerFromApiFinished(const QString &message, int preferredDefaultServerIndex = -1);
|
void installServerFromApiFinished(const QString &message);
|
||||||
void changeApiCountryFinished(const QString &message);
|
void changeApiCountryFinished(const QString &message);
|
||||||
void reloadServerFromApiFinished(const QString &message);
|
void reloadServerFromApiFinished(const QString &message);
|
||||||
void updateServerFromApiFinished();
|
void updateServerFromApiFinished();
|
||||||
@@ -65,7 +57,7 @@ private:
|
|||||||
QString getVpnKey();
|
QString getVpnKey();
|
||||||
|
|
||||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||||
ErrorCode importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase, int &duplicateServerIndex);
|
ErrorCode importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase);
|
||||||
|
|
||||||
QList<QString> m_qrCodes;
|
QList<QString> m_qrCodes;
|
||||||
QString m_vpnKey;
|
QString m_vpnKey;
|
||||||
@@ -73,9 +65,6 @@ private:
|
|||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
QSharedPointer<ApiSubscriptionPlansModel> m_subscriptionPlansModel;
|
|
||||||
QSharedPointer<ApiBenefitsModel> m_benefitsModel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // APICONFIGSCONTROLLER_H
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "apiSettingsController.h"
|
#include "apiSettingsController.h"
|
||||||
|
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "core/api/apiUtils.h"
|
#include "core/api/apiUtils.h"
|
||||||
@@ -86,42 +85,6 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiSettingsController::getRenewalLink()
|
|
||||||
{
|
|
||||||
auto processedIndex = m_serversModel->getProcessedServerIndex();
|
|
||||||
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
|
|
||||||
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
|
|
||||||
auto authData = serverConfig.value(configKey::authData).toObject();
|
|
||||||
|
|
||||||
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
|
||||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(isTestPurchase),
|
|
||||||
m_settings->isDevGatewayEnv(isTestPurchase),
|
|
||||||
requestTimeoutMsecs,
|
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
|
||||||
|
|
||||||
QJsonObject apiPayload;
|
|
||||||
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
|
||||||
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
|
|
||||||
apiPayload[configKey::authData] = authData;
|
|
||||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
|
||||||
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
|
|
||||||
|
|
||||||
auto future = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
|
|
||||||
future.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
|
|
||||||
auto [errorCode, responseBody] = result;
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit errorOccurred(errorCode);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object();
|
|
||||||
QString url = responseJson.value("renewal_url").toString();
|
|
||||||
if (!url.isEmpty()) {
|
|
||||||
emit renewalLinkReceived(url);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiSettingsController::updateApiCountryModel()
|
void ApiSettingsController::updateApiCountryModel()
|
||||||
{
|
{
|
||||||
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ public slots:
|
|||||||
bool getAccountInfo(bool reload);
|
bool getAccountInfo(bool reload);
|
||||||
void updateApiCountryModel();
|
void updateApiCountryModel();
|
||||||
void updateApiDevicesModel();
|
void updateApiDevicesModel();
|
||||||
void getRenewalLink();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void errorOccurred(ErrorCode errorCode);
|
void errorOccurred(ErrorCode errorCode);
|
||||||
void renewalLinkReceived(const QString &url);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
@@ -82,8 +81,6 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
|||||||
m_connectionStateText = tr("Connecting...");
|
m_connectionStateText = tr("Connecting...");
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Vpn::ConnectionState::Connected: {
|
case Vpn::ConnectionState::Connected: {
|
||||||
amnApp->networkManager()->clearConnectionCache();
|
|
||||||
|
|
||||||
m_isConnectionInProgress = false;
|
m_isConnectionInProgress = false;
|
||||||
m_isConnected = true;
|
m_isConnected = true;
|
||||||
m_connectionStateText = tr("Connected");
|
m_connectionStateText = tr("Connected");
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/qrCodeUtils.h"
|
#include "core/qrCodeUtils.h"
|
||||||
#include "core/serialization/serialization.h"
|
#include "core/serialization/serialization.h"
|
||||||
@@ -169,7 +170,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
|
|||||||
m_config.append(line + "\n");
|
m_config.append(line + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
|
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
|
||||||
|
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||||
|
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
@@ -189,7 +191,8 @@ void ExportController::generateAwgConfig(const QString &clientName)
|
|||||||
m_config.append(line + "\n");
|
m_config.append(line + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
|
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8());
|
||||||
|
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
|
||||||
|
|
||||||
emit exportConfigChanged();
|
emit exportConfigChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,8 +291,6 @@ void ImportController::processNativeWireGuardConfig()
|
|||||||
clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0";
|
clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0";
|
||||||
clientProtocolConfig[config_key::transportPacketJunkSize] = "0";
|
clientProtocolConfig[config_key::transportPacketJunkSize] = "0";
|
||||||
|
|
||||||
clientProtocolConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1;
|
|
||||||
|
|
||||||
clientProtocolConfig[config_key::isObfuscationEnabled] = true;
|
clientProtocolConfig[config_key::isObfuscationEnabled] = true;
|
||||||
|
|
||||||
serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson());
|
serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson());
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
|
|
||||||
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
||||||
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||||
int s3 = QRandomGenerator::global()->bounded(1, 64);
|
int s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||||
int s4 = QRandomGenerator::global()->bounded(1, 20);
|
int s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||||
|
|
||||||
// Ensure all values are unique and don't create equal packet sizes
|
// Ensure all values are unique and don't create equal packet sizes
|
||||||
QSet<int> usedValues;
|
QSet<int> usedValues;
|
||||||
@@ -97,12 +97,12 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
|
|
||||||
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|
||||||
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
|
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
|
||||||
s3 = QRandomGenerator::global()->bounded(1, 64);
|
s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||||
}
|
}
|
||||||
usedValues.insert(s3);
|
usedValues.insert(s3);
|
||||||
|
|
||||||
while (usedValues.contains(s4)) {
|
while (usedValues.contains(s4)) {
|
||||||
s4 = QRandomGenerator::global()->bounded(1, 20);
|
s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString initPacketJunkSize = QString::number(s1);
|
QString initPacketJunkSize = QString::number(s1);
|
||||||
@@ -987,94 +987,79 @@ void InstallController::addEmptyServer()
|
|||||||
emit installServerFinished(tr("Server added successfully"));
|
emit installServerFinished(tr("Server added successfully"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallController::validateConfig()
|
bool InstallController::isConfigValid()
|
||||||
{
|
{
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
|
||||||
if (apiUtils::isServerFromApi(serverConfigObject)) {
|
if (apiUtils::isServerFromApi(serverConfigObject)) {
|
||||||
emit configValidated(true);
|
return true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
emit noInstalledContainers();
|
emit noInstalledContainers();
|
||||||
emit configValidated(false);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
|
|
||||||
if (container == DockerContainer::None) {
|
if (container == DockerContainer::None) {
|
||||||
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
|
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
|
||||||
emit configValidated(false);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
|
|
||||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
|
||||||
|
|
||||||
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
|
QFutureWatcher<ErrorCode> watcher;
|
||||||
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QString protocolConfig =
|
|
||||||
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
|
|
||||||
|
|
||||||
if (protocolConfig.isEmpty()) {
|
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() {
|
||||||
return false;
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
|
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
|
||||||
|
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
QString protocolConfig =
|
||||||
|
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
|
||||||
|
|
||||||
|
if (protocolConfig.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isProtocolConfigExists(containerConfig, container)) {
|
||||||
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
|
errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
m_serversModel->updateContainerConfig(container, containerConfig);
|
||||||
|
|
||||||
|
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
|
||||||
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return errorCode;
|
||||||
};
|
});
|
||||||
|
|
||||||
if (isProtocolConfigExists(containerConfig, container)) {
|
QEventLoop wait;
|
||||||
emit configValidated(true);
|
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
||||||
return;
|
watcher.setFuture(future);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
ErrorCode errorCode = watcher.result();
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit installationErrorOccurred(errorCode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
struct ValidationResult {
|
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
|
||||||
QJsonObject containerConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
QFuture<ValidationResult> future =
|
|
||||||
QtConcurrent::run([settings = m_settings, serverController, credentials, containerConfig, container]() mutable {
|
|
||||||
ValidationResult result;
|
|
||||||
result.containerConfig = containerConfig;
|
|
||||||
|
|
||||||
VpnConfigurationsController vpnConfigurationController(settings, serverController);
|
|
||||||
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container,
|
|
||||||
result.containerConfig);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto *watcher = new QFutureWatcher<ValidationResult>(this);
|
|
||||||
connect(watcher, &QFutureWatcher<ValidationResult>::finished, this,
|
|
||||||
[this, watcher, container, credentials, serverController]() {
|
|
||||||
auto result = watcher->result();
|
|
||||||
watcher->deleteLater();
|
|
||||||
|
|
||||||
if (result.errorCode != ErrorCode::NoError) {
|
|
||||||
emit installationErrorOccurred(result.errorCode);
|
|
||||||
emit configValidated(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_serversModel->updateContainerConfig(container, result.containerConfig);
|
|
||||||
|
|
||||||
ErrorCode appendError = m_clientManagementModel->appendClient(
|
|
||||||
container, credentials, result.containerConfig,
|
|
||||||
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
|
||||||
|
|
||||||
if (appendError != ErrorCode::NoError) {
|
|
||||||
emit installationErrorOccurred(appendError);
|
|
||||||
emit configValidated(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit configValidated(true);
|
|
||||||
});
|
|
||||||
watcher->setFuture(future);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
|
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
|
||||||
@@ -1085,7 +1070,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
|
|||||||
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
|
|
||||||
if (ContainerProps::isAwgContainer(container)) {
|
if (container == DockerContainer::Awg2) {
|
||||||
const AwgConfig oldConfig(oldProtoConfig);
|
const AwgConfig oldConfig(oldProtoConfig);
|
||||||
const AwgConfig newConfig(newProtoConfig);
|
const AwgConfig newConfig(newProtoConfig);
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,9 @@ public slots:
|
|||||||
|
|
||||||
void addEmptyServer();
|
void addEmptyServer();
|
||||||
|
|
||||||
void validateConfig();
|
bool isConfigValid();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void configValidated(bool isValid);
|
|
||||||
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
|
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
|
||||||
void installServerFinished(const QString &finishMessage);
|
void installServerFinished(const QString &finishMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace PageLoader
|
|||||||
PageSetupWizardViewConfig,
|
PageSetupWizardViewConfig,
|
||||||
PageSetupWizardQrReader,
|
PageSetupWizardQrReader,
|
||||||
PageSetupWizardApiServicesList,
|
PageSetupWizardApiServicesList,
|
||||||
PageSetupWizardApiFreeInfo,
|
PageSetupWizardApiServiceInfo,
|
||||||
|
|
||||||
PageProtocolOpenVpnSettings,
|
PageProtocolOpenVpnSettings,
|
||||||
PageProtocolShadowSocksSettings,
|
PageProtocolShadowSocksSettings,
|
||||||
@@ -76,9 +76,6 @@ namespace PageLoader
|
|||||||
PageShareFullAccess,
|
PageShareFullAccess,
|
||||||
PageShareConnection,
|
PageShareConnection,
|
||||||
|
|
||||||
PageSetupWizardApiPremiumInfo,
|
|
||||||
PageSetupWizardApiTrialEmail,
|
|
||||||
|
|
||||||
PageDevMenu
|
PageDevMenu
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(PageEnum)
|
Q_ENUM_NS(PageEnum)
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
|||||||
emit safeAreaBottomMarginChanged();
|
emit safeAreaBottomMarginChanged();
|
||||||
emit safeAreaTopMarginChanged();
|
emit safeAreaTopMarginChanged();
|
||||||
});
|
});
|
||||||
connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsController::activityPaused);
|
|
||||||
connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsController::activityResumed);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
m_isDevModeEnabled = m_settings->isDevGatewayEnv();
|
||||||
@@ -180,11 +178,12 @@ void SettingsController::backupAppConfig(const QString &fileName)
|
|||||||
|
|
||||||
void SettingsController::restoreAppConfig(const QString &fileName)
|
void SettingsController::restoreAppConfig(const QString &fileName)
|
||||||
{
|
{
|
||||||
QByteArray data;
|
QFile file(fileName);
|
||||||
if (!SystemController::readFile(fileName, data)) {
|
|
||||||
emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName));
|
file.open(QIODevice::ReadOnly);
|
||||||
return;
|
|
||||||
}
|
QByteArray data = file.readAll();
|
||||||
|
|
||||||
restoreAppConfigFromData(data);
|
restoreAppConfigFromData(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,15 +308,6 @@ void SettingsController::toggleStartMinimized(bool enable)
|
|||||||
emit startMinimizedChanged();
|
emit startMinimizedChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SettingsController::isNewsNotificationsEnabled()
|
|
||||||
{
|
|
||||||
return m_settings->isNewsNotifications();
|
|
||||||
}
|
|
||||||
void SettingsController::toggleNewsNotificationsEnabled(bool enable)
|
|
||||||
{
|
|
||||||
m_settings->setNewsNotifications(enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SettingsController::isScreenshotsEnabled()
|
bool SettingsController::isScreenshotsEnabled()
|
||||||
{
|
{
|
||||||
return m_settings->isScreenshotsEnabled();
|
return m_settings->isScreenshotsEnabled();
|
||||||
|
|||||||
@@ -73,9 +73,6 @@ public slots:
|
|||||||
bool isStartMinimizedEnabled();
|
bool isStartMinimizedEnabled();
|
||||||
void toggleStartMinimized(bool enable);
|
void toggleStartMinimized(bool enable);
|
||||||
|
|
||||||
bool isNewsNotificationsEnabled();
|
|
||||||
void toggleNewsNotificationsEnabled(bool enable);
|
|
||||||
|
|
||||||
bool isScreenshotsEnabled();
|
bool isScreenshotsEnabled();
|
||||||
void toggleScreenshotsEnabled(bool enable);
|
void toggleScreenshotsEnabled(bool enable);
|
||||||
|
|
||||||
@@ -141,9 +138,6 @@ signals:
|
|||||||
void safeAreaTopMarginChanged();
|
void safeAreaTopMarginChanged();
|
||||||
void safeAreaBottomMarginChanged();
|
void safeAreaBottomMarginChanged();
|
||||||
|
|
||||||
void activityPaused();
|
|
||||||
void activityResumed();
|
|
||||||
|
|
||||||
void isHomeAdLabelVisibleChanged(bool visible);
|
void isHomeAdLabelVisibleChanged(bool visible);
|
||||||
void startMinimizedChanged();
|
void startMinimizedChanged();
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#include "core/networkUtilities.h"
|
#include "core/networkUtilities.h"
|
||||||
|
|
||||||
SitesController::SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
|
SitesController::SitesController(const std::shared_ptr<Settings> &settings,
|
||||||
: QObject(parent), m_settings(settings), m_sitesModel(sitesModel)
|
const QSharedPointer<VpnConnection> &vpnConnection,
|
||||||
|
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
|
||||||
|
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,20 +34,32 @@ void SitesController::addSite(QString hostname)
|
|||||||
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
|
hostname = hostname.split("/", Qt::SkipEmptyParts).first();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &resolveCallback = [this](const QHostInfo &hostInfo) {
|
const auto &processSite = [this](const QString &hostname, const QString &ip) {
|
||||||
|
m_sitesModel->addSite(hostname, ip);
|
||||||
|
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QStringList, QStringList() << ip));
|
||||||
|
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
|
||||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||||
for (const QHostAddress &addr : hostInfo.addresses()) {
|
for (const QHostAddress &addr : hostInfo.addresses()) {
|
||||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||||
m_sitesModel->addSite(hostInfo.hostName(), addr.toString());
|
processSite(hostInfo.hostName(), addr.toString());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
|
||||||
m_sitesModel->addSite(hostname, "");
|
processSite(hostname, "");
|
||||||
} else {
|
} else {
|
||||||
m_sitesModel->addSite(hostname, "");
|
processSite(hostname, "");
|
||||||
QHostInfo::lookupHost(hostname, this, resolveCallback);
|
QHostInfo::lookupHost(hostname, this, resolveCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +72,9 @@ void SitesController::removeSite(int index)
|
|||||||
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
|
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
|
||||||
m_sitesModel->removeSite(modelIndex);
|
m_sitesModel->removeSite(modelIndex);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
|
||||||
|
Q_ARG(QStringList, QStringList() << hostname));
|
||||||
|
|
||||||
emit finished(tr("Site removed: %1").arg(hostname));
|
emit finished(tr("Site removed: %1").arg(hostname));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,6 +128,8 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
|
|||||||
|
|
||||||
m_sitesModel->addSites(sites, replaceExisting);
|
m_sitesModel->addSites(sites, replaceExisting);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
|
||||||
|
|
||||||
emit finished(tr("Import completed"));
|
emit finished(tr("Import completed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ class SitesController : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel,
|
explicit SitesController(const std::shared_ptr<Settings> &settings,
|
||||||
QObject *parent = nullptr);
|
const QSharedPointer<VpnConnection> &vpnConnection,
|
||||||
|
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void addSite(QString hostname);
|
void addSite(QString hostname);
|
||||||
@@ -30,6 +31,8 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "apiAccountInfoModel.h"
|
#include "apiAccountInfoModel.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/api/apiUtils.h"
|
#include "core/api/apiUtils.h"
|
||||||
@@ -33,7 +32,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
|
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
|
||||||
: tr("<p><a style=\"color: #28c840;\">Active</a>");
|
: tr("Active");
|
||||||
}
|
}
|
||||||
case EndDateRole: {
|
case EndDateRole: {
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
||||||
@@ -53,14 +52,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
case IsComponentVisibleRole: {
|
case IsComponentVisibleRole: {
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium;
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium
|
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
|
|
||||||
}
|
|
||||||
case IsSubscriptionRenewalAvailableRole: {
|
|
||||||
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|
|
||||||
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
|
|
||||||
}
|
}
|
||||||
case HasExpiredWorkerRole: {
|
case HasExpiredWorkerRole: {
|
||||||
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
|
||||||
@@ -81,33 +73,6 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case IsSubscriptionExpiredRole: {
|
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_accountInfoData.isInAppPurchase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_accountInfoData.subscriptionEndDate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate);
|
|
||||||
}
|
|
||||||
case IsSubscriptionExpiringSoonRole: {
|
|
||||||
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_accountInfoData.isInAppPurchase) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_accountInfoData.subscriptionEndDate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return apiUtils::isSubscriptionExpiringSoon(m_accountInfoData.subscriptionEndDate);
|
|
||||||
}
|
|
||||||
case IsInAppPurchaseRole: {
|
|
||||||
return m_accountInfoData.isInAppPurchase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -128,9 +93,6 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
|
|||||||
|
|
||||||
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
accountInfoData.configType = apiUtils::getConfigType(serverConfig);
|
||||||
|
|
||||||
const QJsonObject apiConfig = serverConfig.value(apiDefs::key::apiConfig).toObject();
|
|
||||||
accountInfoData.isInAppPurchase = apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false);
|
|
||||||
|
|
||||||
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
|
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
|
||||||
|
|
||||||
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
|
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
|
||||||
@@ -200,12 +162,8 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
|
|||||||
roles[ConnectedDevicesRole] = "connectedDevices";
|
roles[ConnectedDevicesRole] = "connectedDevices";
|
||||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
roles[IsComponentVisibleRole] = "isComponentVisible";
|
roles[IsComponentVisibleRole] = "isComponentVisible";
|
||||||
roles[IsSubscriptionRenewalAvailableRole] = "isSubscriptionRenewalAvailable";
|
|
||||||
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
|
||||||
roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported";
|
roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported";
|
||||||
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
|
|
||||||
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
|
|
||||||
roles[IsInAppPurchaseRole] = "isInAppPurchase";
|
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,8 @@ public:
|
|||||||
ServiceDescriptionRole,
|
ServiceDescriptionRole,
|
||||||
EndDateRole,
|
EndDateRole,
|
||||||
IsComponentVisibleRole,
|
IsComponentVisibleRole,
|
||||||
IsSubscriptionRenewalAvailableRole,
|
|
||||||
HasExpiredWorkerRole,
|
HasExpiredWorkerRole,
|
||||||
IsProtocolSelectionSupportedRole,
|
IsProtocolSelectionSupportedRole
|
||||||
IsSubscriptionExpiredRole,
|
|
||||||
IsSubscriptionExpiringSoonRole,
|
|
||||||
IsInAppPurchaseRole
|
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
explicit ApiAccountInfoModel(QObject *parent = nullptr);
|
||||||
@@ -35,6 +31,7 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
|
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
|
||||||
QVariant data(const QString &roleString);
|
QVariant data(const QString &roleString);
|
||||||
|
|
||||||
QJsonArray getAvailableCountries();
|
QJsonArray getAvailableCountries();
|
||||||
QJsonArray getIssuedConfigsInfo();
|
QJsonArray getIssuedConfigsInfo();
|
||||||
|
|
||||||
@@ -59,8 +56,6 @@ private:
|
|||||||
QStringList supportedProtocols;
|
QStringList supportedProtocols;
|
||||||
|
|
||||||
QString subscriptionDescription;
|
QString subscriptionDescription;
|
||||||
|
|
||||||
bool isInAppPurchase = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
AccountInfoData m_accountInfoData;
|
AccountInfoData m_accountInfoData;
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
#include "apiBenefitsModel.h"
|
|
||||||
|
|
||||||
#include <QHash>
|
|
||||||
#include <utility>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonValue>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
namespace configKey
|
|
||||||
{
|
|
||||||
constexpr char title[] = "title";
|
|
||||||
constexpr char body[] = "body";
|
|
||||||
constexpr char icon[] = "icon";
|
|
||||||
constexpr char accent[] = "accent";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString gatewayIconKeyToUrl(const QString &iconKey)
|
|
||||||
{
|
|
||||||
if (iconKey.startsWith(QLatin1String("qrc:"))) {
|
|
||||||
return iconKey;
|
|
||||||
}
|
|
||||||
static const QHash<QString, QString> map = {
|
|
||||||
{ QStringLiteral("globe-2"), QStringLiteral("qrc:/images/controls/globe-2.svg") },
|
|
||||||
{ QStringLiteral("smartphone"), QStringLiteral("qrc:/images/controls/smartphone.svg") },
|
|
||||||
{ QStringLiteral("gauge"), QStringLiteral("qrc:/images/controls/gauge.svg") },
|
|
||||||
{ QStringLiteral("infinity"), QStringLiteral("qrc:/images/controls/infinity.svg") },
|
|
||||||
{ QStringLiteral("tag"), QStringLiteral("qrc:/images/controls/tag.svg") },
|
|
||||||
{ QStringLiteral("history"), QStringLiteral("qrc:/images/controls/history.svg") },
|
|
||||||
{ QStringLiteral("info"), QStringLiteral("qrc:/images/controls/info.svg") },
|
|
||||||
{ QStringLiteral("app"), QStringLiteral("qrc:/images/controls/app.svg") },
|
|
||||||
{ QStringLiteral("download"), QStringLiteral("qrc:/images/controls/download.svg") },
|
|
||||||
{ QStringLiteral("help-circle"), QStringLiteral("qrc:/images/controls/help-circle.svg") },
|
|
||||||
};
|
|
||||||
return map.value(iconKey, QStringLiteral("qrc:/images/controls/info.svg"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiBenefitsModel::ApiBenefitsModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int ApiBenefitsModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
if (parent.isValid()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return m_serviceBenefits.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ApiBenefitsModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_serviceBenefits.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const ServiceBenefitItem &item = m_serviceBenefits.at(index.row());
|
|
||||||
switch (role) {
|
|
||||||
case IconRole:
|
|
||||||
return item.icon;
|
|
||||||
case TitleRole:
|
|
||||||
return item.title;
|
|
||||||
case BodyRole:
|
|
||||||
return item.body;
|
|
||||||
case AccentRole:
|
|
||||||
return item.accent;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ApiBenefitsModel::roleNames() const
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{ IconRole, "icon" },
|
|
||||||
{ TitleRole, "title" },
|
|
||||||
{ BodyRole, "body" },
|
|
||||||
{ AccentRole, "accent" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiBenefitsModel::updateModel(const QJsonArray &benefits)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_serviceBenefits.clear();
|
|
||||||
for (const QJsonValue &benefitValue : benefits) {
|
|
||||||
if (!benefitValue.isObject()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const QJsonObject benefitObject = benefitValue.toObject();
|
|
||||||
QString title = benefitObject.value(configKey::title).toString();
|
|
||||||
QString body = benefitObject.value(configKey::body).toString();
|
|
||||||
const QString iconKey = benefitObject.value(configKey::icon).toString();
|
|
||||||
if (title.isEmpty() && body.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ServiceBenefitItem item;
|
|
||||||
item.icon = gatewayIconKeyToUrl(iconKey);
|
|
||||||
item.title = std::move(title);
|
|
||||||
item.body = std::move(body);
|
|
||||||
item.accent = benefitObject.value(configKey::accent).toBool();
|
|
||||||
m_serviceBenefits.append(std::move(item));
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiBenefitsModel::clear()
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_serviceBenefits.clear();
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#ifndef APIBENEFITSMODEL_H
|
|
||||||
#define APIBENEFITSMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QString>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
class ApiBenefitsModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Roles {
|
|
||||||
IconRole = Qt::UserRole + 1,
|
|
||||||
TitleRole,
|
|
||||||
BodyRole,
|
|
||||||
AccentRole
|
|
||||||
};
|
|
||||||
Q_ENUM(Roles)
|
|
||||||
|
|
||||||
explicit ApiBenefitsModel(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
void updateModel(const QJsonArray &benefits);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct ServiceBenefitItem
|
|
||||||
{
|
|
||||||
QString icon;
|
|
||||||
QString title;
|
|
||||||
QString body;
|
|
||||||
bool accent = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
QVector<ServiceBenefitItem> m_serviceBenefits;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
#include "apiServicesModel.h"
|
#include "apiServicesModel.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/api/apiDefs.h"
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -21,9 +17,15 @@ namespace
|
|||||||
constexpr char serviceProtocol[] = "service_protocol";
|
constexpr char serviceProtocol[] = "service_protocol";
|
||||||
constexpr char serviceDescription[] = "service_description";
|
constexpr char serviceDescription[] = "service_description";
|
||||||
|
|
||||||
|
constexpr char name[] = "name";
|
||||||
|
constexpr char price[] = "price";
|
||||||
|
constexpr char speed[] = "speed";
|
||||||
|
constexpr char timelimit[] = "timelimit";
|
||||||
|
constexpr char region[] = "region";
|
||||||
|
|
||||||
constexpr char description[] = "description";
|
constexpr char description[] = "description";
|
||||||
constexpr char cardDescription[] = "card_description";
|
constexpr char cardDescription[] = "card_description";
|
||||||
constexpr char serviceName[] = "service_name";
|
constexpr char features[] = "features";
|
||||||
|
|
||||||
constexpr char availableCountries[] = "available_countries";
|
constexpr char availableCountries[] = "available_countries";
|
||||||
|
|
||||||
@@ -31,9 +33,8 @@ namespace
|
|||||||
|
|
||||||
constexpr char isAvailable[] = "is_available";
|
constexpr char isAvailable[] = "is_available";
|
||||||
|
|
||||||
constexpr char subscriptionPlans[] = "subscription_plans";
|
constexpr char subscription[] = "subscription";
|
||||||
constexpr char minPriceLabel[] = "min_price_label";
|
constexpr char endDate[] = "end_date";
|
||||||
constexpr char benefits[] = "benefits";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace serviceType
|
namespace serviceType
|
||||||
@@ -43,9 +44,7 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiServicesModel::ApiServicesModel(QObject *parent)
|
ApiServicesModel::ApiServicesModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
: QAbstractListModel(parent)
|
|
||||||
, m_selectedServiceIndex(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +68,9 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
return apiServiceData.serviceInfo.name;
|
return apiServiceData.serviceInfo.name;
|
||||||
}
|
}
|
||||||
case CardDescriptionRole: {
|
case CardDescriptionRole: {
|
||||||
|
auto speed = apiServiceData.serviceInfo.speed;
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return apiServiceData.serviceInfo.cardDescription;
|
return apiServiceData.serviceInfo.cardDescription.arg(speed);
|
||||||
} else if (serviceType == serviceType::amneziaFree) {
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
QString description = apiServiceData.serviceInfo.cardDescription;
|
QString description = apiServiceData.serviceInfo.cardDescription;
|
||||||
if (!isServiceAvailable) {
|
if (!isServiceAvailable) {
|
||||||
@@ -91,29 +91,38 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case SpeedRole: {
|
||||||
|
return tr("%1 MBit/s").arg(apiServiceData.serviceInfo.speed);
|
||||||
|
}
|
||||||
|
case TimeLimitRole: {
|
||||||
|
auto timeLimit = apiServiceData.serviceInfo.timeLimit;
|
||||||
|
if (timeLimit == "0") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return tr("%1 days").arg(timeLimit);
|
||||||
|
}
|
||||||
|
case RegionRole: {
|
||||||
|
return apiServiceData.serviceInfo.region;
|
||||||
|
}
|
||||||
|
case FeaturesRole: {
|
||||||
|
return apiServiceData.serviceInfo.features;
|
||||||
|
}
|
||||||
case PriceRole: {
|
case PriceRole: {
|
||||||
return apiServiceData.minPriceLabel;
|
auto price = apiServiceData.serviceInfo.price;
|
||||||
|
if (price == "free") {
|
||||||
|
return tr("Free");
|
||||||
|
}
|
||||||
|
return tr("%1 $/month").arg(price);
|
||||||
}
|
}
|
||||||
case EndDateRole: {
|
case EndDateRole: {
|
||||||
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");
|
||||||
}
|
}
|
||||||
case TermsOfUseUrlRole: {
|
|
||||||
return apiServiceData.serviceInfo.termsOfUseUrl;
|
|
||||||
}
|
|
||||||
case PrivacyPolicyUrlRole: {
|
|
||||||
return apiServiceData.serviceInfo.privacyPolicyUrl;
|
|
||||||
}
|
|
||||||
case ShowRecommendedRole: {
|
|
||||||
return serviceType == serviceType::amneziaPremium;
|
|
||||||
}
|
|
||||||
case OrderRole: {
|
case OrderRole: {
|
||||||
if (serviceType == serviceType::amneziaPremium) {
|
if (serviceType == serviceType::amneziaPremium) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
} else if (serviceType == serviceType::amneziaFree) {
|
||||||
if (serviceType == serviceType::amneziaFree) {
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return QVariant();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,27 +148,12 @@ void ApiServicesModel::updateModel(const QJsonObject &data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_services.isEmpty() && m_selectedServiceIndex >= m_services.size()) {
|
|
||||||
m_selectedServiceIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
emit serviceSelectionChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiServicesModel::setServiceIndex(const int index)
|
void ApiServicesModel::setServiceIndex(const int index)
|
||||||
{
|
{
|
||||||
m_selectedServiceIndex = index;
|
m_selectedServiceIndex = index;
|
||||||
emit serviceSelectionChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiServicesModel::ApiServicesData ApiServicesModel::selectedServiceData() const
|
|
||||||
{
|
|
||||||
if (m_services.isEmpty() || m_selectedServiceIndex < 0 || m_selectedServiceIndex >= m_services.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return m_services.at(m_selectedServiceIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject ApiServicesModel::getSelectedServiceInfo()
|
QJsonObject ApiServicesModel::getSelectedServiceInfo()
|
||||||
@@ -216,16 +210,6 @@ QVariant ApiServicesModel::getSelectedServiceData(const QString roleString)
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
int ApiServicesModel::serviceIndexForType(const QString &type) const
|
|
||||||
{
|
|
||||||
for (int serviceIndex = 0; serviceIndex < m_services.size(); ++serviceIndex) {
|
|
||||||
if (m_services.at(serviceIndex).type == type) {
|
|
||||||
return serviceIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ApiServicesModel::roleNames() const
|
QHash<int, QByteArray> ApiServicesModel::roleNames() const
|
||||||
{
|
{
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
@@ -233,11 +217,12 @@ QHash<int, QByteArray> ApiServicesModel::roleNames() const
|
|||||||
roles[CardDescriptionRole] = "cardDescription";
|
roles[CardDescriptionRole] = "cardDescription";
|
||||||
roles[ServiceDescriptionRole] = "serviceDescription";
|
roles[ServiceDescriptionRole] = "serviceDescription";
|
||||||
roles[IsServiceAvailableRole] = "isServiceAvailable";
|
roles[IsServiceAvailableRole] = "isServiceAvailable";
|
||||||
|
roles[SpeedRole] = "speed";
|
||||||
|
roles[TimeLimitRole] = "timeLimit";
|
||||||
|
roles[RegionRole] = "region";
|
||||||
|
roles[FeaturesRole] = "features";
|
||||||
roles[PriceRole] = "price";
|
roles[PriceRole] = "price";
|
||||||
roles[EndDateRole] = "endDate";
|
roles[EndDateRole] = "endDate";
|
||||||
roles[TermsOfUseUrlRole] = "termsOfUseUrl";
|
|
||||||
roles[PrivacyPolicyUrlRole] = "privacyPolicyUrl";
|
|
||||||
roles[ShowRecommendedRole] = "showRecommended";
|
|
||||||
roles[OrderRole] = "order";
|
roles[OrderRole] = "order";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
@@ -251,22 +236,18 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
|||||||
auto availableCountries = data.value(configKey::availableCountries).toArray();
|
auto availableCountries = data.value(configKey::availableCountries).toArray();
|
||||||
auto serviceDescription = data.value(configKey::serviceDescription).toObject();
|
auto serviceDescription = data.value(configKey::serviceDescription).toObject();
|
||||||
|
|
||||||
auto subscriptionObject = data.value(apiDefs::key::subscription).toObject();
|
auto subscriptionObject = data.value(configKey::subscription).toObject();
|
||||||
|
|
||||||
ApiServicesData serviceData;
|
ApiServicesData serviceData;
|
||||||
serviceData.serviceInfo.name = serviceDescription.value(configKey::serviceName).toString();
|
serviceData.serviceInfo.name = serviceInfo.value(configKey::name).toString();
|
||||||
|
serviceData.serviceInfo.price = serviceInfo.value(configKey::price).toString();
|
||||||
|
serviceData.serviceInfo.region = serviceInfo.value(configKey::region).toString();
|
||||||
|
serviceData.serviceInfo.speed = serviceInfo.value(configKey::speed).toString();
|
||||||
|
serviceData.serviceInfo.timeLimit = serviceInfo.value(configKey::timelimit).toString();
|
||||||
|
|
||||||
serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString();
|
serviceData.serviceInfo.cardDescription = serviceDescription.value(configKey::cardDescription).toString();
|
||||||
serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString();
|
serviceData.serviceInfo.description = serviceDescription.value(configKey::description).toString();
|
||||||
serviceData.serviceInfo.termsOfUseUrl = serviceDescription.value(apiDefs::key::termsOfUseUrl).toString();
|
serviceData.serviceInfo.features = serviceDescription.value(configKey::features).toString();
|
||||||
serviceData.serviceInfo.privacyPolicyUrl = serviceDescription.value(apiDefs::key::privacyPolicyUrl).toString();
|
|
||||||
|
|
||||||
serviceData.subscriptionPlansJson = serviceDescription.value(configKey::subscriptionPlans).toArray();
|
|
||||||
serviceData.benefits = serviceDescription.value(configKey::benefits).toArray();
|
|
||||||
|
|
||||||
serviceData.minPriceLabel = serviceDescription.value(configKey::minPriceLabel).toString().trimmed();
|
|
||||||
|
|
||||||
serviceData.supportInfo = data.value(apiDefs::key::supportInfo).toObject();
|
|
||||||
|
|
||||||
serviceData.type = serviceType;
|
serviceData.type = serviceType;
|
||||||
serviceData.protocol = serviceProtocol;
|
serviceData.protocol = serviceProtocol;
|
||||||
@@ -282,7 +263,7 @@ ApiServicesModel::ApiServicesData ApiServicesModel::getApiServicesData(const QJs
|
|||||||
serviceData.serviceInfo.object = serviceInfo;
|
serviceData.serviceInfo.object = serviceInfo;
|
||||||
serviceData.availableCountries = availableCountries;
|
serviceData.availableCountries = availableCountries;
|
||||||
|
|
||||||
serviceData.subscription.endDate = subscriptionObject.value(apiDefs::key::endDate).toString();
|
serviceData.subscription.endDate = subscriptionObject.value(configKey::endDate).toString();
|
||||||
|
|
||||||
return serviceData;
|
return serviceData;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,61 +4,23 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
class ApiServicesModel : public QAbstractListModel
|
class ApiServicesModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct ServiceInfo
|
|
||||||
{
|
|
||||||
QString name;
|
|
||||||
|
|
||||||
QString description;
|
|
||||||
QString cardDescription;
|
|
||||||
|
|
||||||
QString termsOfUseUrl;
|
|
||||||
QString privacyPolicyUrl;
|
|
||||||
|
|
||||||
QJsonObject object;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Subscription
|
|
||||||
{
|
|
||||||
QString endDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ApiServicesData
|
|
||||||
{
|
|
||||||
bool isServiceAvailable;
|
|
||||||
|
|
||||||
QString type;
|
|
||||||
QString protocol;
|
|
||||||
QString storeEndpoint;
|
|
||||||
|
|
||||||
ServiceInfo serviceInfo;
|
|
||||||
QJsonObject supportInfo;
|
|
||||||
Subscription subscription;
|
|
||||||
|
|
||||||
QJsonArray availableCountries;
|
|
||||||
|
|
||||||
QJsonArray subscriptionPlansJson;
|
|
||||||
QJsonArray benefits;
|
|
||||||
|
|
||||||
QString minPriceLabel;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Roles {
|
enum Roles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
CardDescriptionRole,
|
CardDescriptionRole,
|
||||||
ServiceDescriptionRole,
|
ServiceDescriptionRole,
|
||||||
IsServiceAvailableRole,
|
IsServiceAvailableRole,
|
||||||
|
SpeedRole,
|
||||||
|
TimeLimitRole,
|
||||||
|
RegionRole,
|
||||||
|
FeaturesRole,
|
||||||
PriceRole,
|
PriceRole,
|
||||||
EndDateRole,
|
EndDateRole,
|
||||||
TermsOfUseUrlRole,
|
|
||||||
PrivacyPolicyUrlRole,
|
|
||||||
ShowRecommendedRole,
|
|
||||||
OrderRole
|
OrderRole
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,8 +30,6 @@ public:
|
|||||||
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
ApiServicesData selectedServiceData() const;
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateModel(const QJsonObject &data);
|
void updateModel(const QJsonObject &data);
|
||||||
|
|
||||||
@@ -87,15 +47,44 @@ public slots:
|
|||||||
|
|
||||||
QVariant getSelectedServiceData(const QString roleString);
|
QVariant getSelectedServiceData(const QString roleString);
|
||||||
|
|
||||||
Q_INVOKABLE int serviceIndexForType(const QString &type) const;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void serviceSelectionChanged();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct ServiceInfo
|
||||||
|
{
|
||||||
|
QString name;
|
||||||
|
QString speed;
|
||||||
|
QString timeLimit;
|
||||||
|
QString region;
|
||||||
|
QString price;
|
||||||
|
|
||||||
|
QString description;
|
||||||
|
QString features;
|
||||||
|
QString cardDescription;
|
||||||
|
|
||||||
|
QJsonObject object;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Subscription
|
||||||
|
{
|
||||||
|
QString endDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ApiServicesData
|
||||||
|
{
|
||||||
|
bool isServiceAvailable;
|
||||||
|
|
||||||
|
QString type;
|
||||||
|
QString protocol;
|
||||||
|
QString storeEndpoint;
|
||||||
|
|
||||||
|
ServiceInfo serviceInfo;
|
||||||
|
Subscription subscription;
|
||||||
|
|
||||||
|
QJsonArray availableCountries;
|
||||||
|
};
|
||||||
|
|
||||||
ApiServicesData getApiServicesData(const QJsonObject &data);
|
ApiServicesData getApiServicesData(const QJsonObject &data);
|
||||||
|
|
||||||
QString m_countryCode;
|
QString m_countryCode;
|
||||||
@@ -104,4 +93,4 @@ private:
|
|||||||
int m_selectedServiceIndex;
|
int m_selectedServiceIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // APISERVICESMODEL_H
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
#include "apiSubscriptionPlansModel.h"
|
|
||||||
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonValue>
|
|
||||||
#include <QModelIndex>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
namespace configKey
|
|
||||||
{
|
|
||||||
constexpr char billingPeriod[] = "billing_period";
|
|
||||||
constexpr char priceLabel[] = "price_label";
|
|
||||||
constexpr char subtitle[] = "subtitle";
|
|
||||||
constexpr char recommended[] = "recommended";
|
|
||||||
constexpr char checkoutUrl[] = "checkout_url";
|
|
||||||
constexpr char isTrial[] = "is_trial";
|
|
||||||
constexpr char serviceProtocol[] = "service_protocol";
|
|
||||||
constexpr char storeProductId[] = "store_product_id";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiSubscriptionPlansModel::ApiSubscriptionPlansModel(QObject *parent)
|
|
||||||
: QAbstractListModel(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int ApiSubscriptionPlansModel::rowCount(const QModelIndex &parent) const
|
|
||||||
{
|
|
||||||
if (parent.isValid()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return m_subscriptionPlans.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariant ApiSubscriptionPlansModel::data(const QModelIndex &index, int role) const
|
|
||||||
{
|
|
||||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_subscriptionPlans.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const SubscriptionPlanItem &plan = m_subscriptionPlans.at(index.row());
|
|
||||||
switch (role) {
|
|
||||||
case BillingPeriodRole:
|
|
||||||
return plan.billingPeriod;
|
|
||||||
case PriceLabelRole:
|
|
||||||
return plan.priceLabel;
|
|
||||||
case SubtitleRole:
|
|
||||||
return plan.subtitle;
|
|
||||||
case RecommendedRole:
|
|
||||||
return plan.recommended;
|
|
||||||
case CheckoutUrlRole:
|
|
||||||
return plan.checkoutUrl;
|
|
||||||
case IsTrialRole:
|
|
||||||
return plan.isTrial;
|
|
||||||
case ServiceProtocolRole:
|
|
||||||
return plan.serviceProtocol;
|
|
||||||
case StoreProductIdRole:
|
|
||||||
return plan.storeProductId;
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> ApiSubscriptionPlansModel::roleNames() const
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{ BillingPeriodRole, "billingPeriod" },
|
|
||||||
{ PriceLabelRole, "priceLabel" },
|
|
||||||
{ SubtitleRole, "subtitle" },
|
|
||||||
{ RecommendedRole, "recommended" },
|
|
||||||
{ CheckoutUrlRole, "checkoutUrl" },
|
|
||||||
{ IsTrialRole, "isTrial" },
|
|
||||||
{ ServiceProtocolRole, "serviceProtocol" },
|
|
||||||
{ StoreProductIdRole, "storeProductId" },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiSubscriptionPlansModel::updateModel(const QJsonArray &arr)
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_subscriptionPlans.clear();
|
|
||||||
m_subscriptionPlans.reserve(arr.size());
|
|
||||||
for (const QJsonValue &planValue : arr) {
|
|
||||||
if (!planValue.isObject()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const QJsonObject planObject = planValue.toObject();
|
|
||||||
SubscriptionPlanItem subscriptionPlan;
|
|
||||||
subscriptionPlan.billingPeriod = planObject.value(configKey::billingPeriod).toString();
|
|
||||||
subscriptionPlan.priceLabel = planObject.value(configKey::priceLabel).toString();
|
|
||||||
subscriptionPlan.subtitle = planObject.value(configKey::subtitle).toString();
|
|
||||||
subscriptionPlan.recommended = planObject.value(configKey::recommended).toBool();
|
|
||||||
subscriptionPlan.checkoutUrl = planObject.value(configKey::checkoutUrl).toString();
|
|
||||||
subscriptionPlan.isTrial = planObject.value(configKey::isTrial).toBool();
|
|
||||||
subscriptionPlan.serviceProtocol = planObject.value(configKey::serviceProtocol).toString();
|
|
||||||
subscriptionPlan.storeProductId = planObject.value(configKey::storeProductId).toString();
|
|
||||||
m_subscriptionPlans.append(std::move(subscriptionPlan));
|
|
||||||
}
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApiSubscriptionPlansModel::clear()
|
|
||||||
{
|
|
||||||
beginResetModel();
|
|
||||||
m_subscriptionPlans.clear();
|
|
||||||
endResetModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap ApiSubscriptionPlansModel::planAt(int row) const
|
|
||||||
{
|
|
||||||
if (row < 0 || row >= m_subscriptionPlans.size()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const QModelIndex modelIndex = index(row, 0);
|
|
||||||
QVariantMap planMap;
|
|
||||||
const QHash<int, QByteArray> roles = roleNames();
|
|
||||||
for (auto roleIt = roles.cbegin(); roleIt != roles.cend(); ++roleIt) {
|
|
||||||
planMap.insert(QString::fromUtf8(roleIt.value()), data(modelIndex, roleIt.key()));
|
|
||||||
}
|
|
||||||
return planMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ApiSubscriptionPlansModel::recommendedRowIndex() const
|
|
||||||
{
|
|
||||||
for (int planIndex = 0; planIndex < m_subscriptionPlans.size(); ++planIndex) {
|
|
||||||
if (m_subscriptionPlans.at(planIndex).recommended) {
|
|
||||||
return planIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#ifndef APISUBSCRIPTIONPLANSMODEL_H
|
|
||||||
#define APISUBSCRIPTIONPLANSMODEL_H
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QVector>
|
|
||||||
|
|
||||||
class ApiSubscriptionPlansModel : public QAbstractListModel
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum Roles {
|
|
||||||
BillingPeriodRole = Qt::UserRole + 1,
|
|
||||||
PriceLabelRole,
|
|
||||||
SubtitleRole,
|
|
||||||
RecommendedRole,
|
|
||||||
CheckoutUrlRole,
|
|
||||||
IsTrialRole,
|
|
||||||
ServiceProtocolRole,
|
|
||||||
StoreProductIdRole
|
|
||||||
};
|
|
||||||
Q_ENUM(Roles)
|
|
||||||
|
|
||||||
explicit ApiSubscriptionPlansModel(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
|
||||||
|
|
||||||
void updateModel(const QJsonArray &arr);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
Q_INVOKABLE QVariantMap planAt(int row) const;
|
|
||||||
Q_INVOKABLE int recommendedRowIndex() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct SubscriptionPlanItem
|
|
||||||
{
|
|
||||||
QString billingPeriod;
|
|
||||||
QString priceLabel;
|
|
||||||
QString subtitle;
|
|
||||||
bool recommended = false;
|
|
||||||
QString checkoutUrl;
|
|
||||||
bool isTrial = false;
|
|
||||||
QString serviceProtocol;
|
|
||||||
QString storeProductId;
|
|
||||||
};
|
|
||||||
|
|
||||||
QVector<SubscriptionPlanItem> m_subscriptionPlans;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -141,12 +141,10 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
|
|||||||
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
|
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
|
||||||
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
|
m_serverProtocolConfig[config_key::responsePacketJunkSize] =
|
||||||
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
|
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
|
||||||
if (protocolVersion == protocols::awg::awgV2) {
|
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
|
||||||
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
|
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
|
||||||
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
|
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
|
||||||
m_serverProtocolConfig[config_key::transportPacketJunkSize] =
|
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
|
||||||
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
|
|
||||||
}
|
|
||||||
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
|
m_serverProtocolConfig[config_key::initPacketMagicHeader] =
|
||||||
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
|
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
|
||||||
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
|
m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
|
||||||
|
|||||||
@@ -179,37 +179,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
|
|||||||
case AdEndpointRole: {
|
case AdEndpointRole: {
|
||||||
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
|
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
|
||||||
}
|
}
|
||||||
case IsSubscriptionExpiredRole: {
|
|
||||||
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (apiConfig.value(apiDefs::key::subscriptionExpiredByServer).toBool(false)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const QString endDate =
|
|
||||||
apiConfig.value(apiDefs::key::subscription).toObject().value(apiDefs::key::endDate).toString();
|
|
||||||
if (endDate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return apiUtils::isSubscriptionExpired(endDate);
|
|
||||||
}
|
|
||||||
case IsSubscriptionExpiringSoonRole: {
|
|
||||||
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (apiConfig.value(apiDefs::key::isInAppPurchase).toBool(false)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const QString endDate =
|
|
||||||
apiConfig.value(apiDefs::key::subscription).toObject().value(apiDefs::key::endDate).toString();
|
|
||||||
if (endDate.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return apiUtils::isSubscriptionExpiringSoon(endDate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
@@ -474,9 +443,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
|
|||||||
roles[AdDescriptionRole] = "adDescription";
|
roles[AdDescriptionRole] = "adDescription";
|
||||||
roles[AdEndpointRole] = "adEndpoint";
|
roles[AdEndpointRole] = "adEndpoint";
|
||||||
|
|
||||||
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
|
|
||||||
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
|
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,21 +727,21 @@ bool ServersModel::isServerFromApiAlreadyExists(const QString &userCountryCode,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ServersModel::indexOfServerWithVpnKey(const QString &vpnKey) const
|
bool ServersModel::hasServerWithVpnKey(const QString &vpnKey) const
|
||||||
{
|
{
|
||||||
const QString normalizedInput = normalizeVpnKey(vpnKey);
|
const QString normalizedInput = normalizeVpnKey(vpnKey);
|
||||||
if (normalizedInput.isEmpty()) {
|
if (normalizedInput.isEmpty()) {
|
||||||
return -1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < m_servers.size(); ++i) {
|
for (const auto &server : std::as_const(m_servers)) {
|
||||||
const auto apiConfig = m_servers.at(i).toObject().value(configKey::apiConfig).toObject();
|
const auto apiConfig = server.toObject().value(configKey::apiConfig).toObject();
|
||||||
const QString existingKey = normalizeVpnKey(apiConfig.value(apiDefs::key::vpnKey).toString());
|
const QString existingKey = normalizeVpnKey(apiConfig.value(apiDefs::key::vpnKey).toString());
|
||||||
if (!existingKey.isEmpty() && existingKey == normalizedInput) {
|
if (!existingKey.isEmpty() && existingKey == normalizedInput) {
|
||||||
return i;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
||||||
|
|||||||
@@ -52,9 +52,6 @@ public:
|
|||||||
AdDescriptionRole,
|
AdDescriptionRole,
|
||||||
AdEndpointRole,
|
AdEndpointRole,
|
||||||
|
|
||||||
IsSubscriptionExpiredRole,
|
|
||||||
IsSubscriptionExpiringSoonRole,
|
|
||||||
|
|
||||||
HasAmneziaDns
|
HasAmneziaDns
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -143,7 +140,7 @@ public slots:
|
|||||||
|
|
||||||
bool isServerFromApiAlreadyExists(const quint16 crc);
|
bool isServerFromApiAlreadyExists(const quint16 crc);
|
||||||
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol);
|
bool isServerFromApiAlreadyExists(const QString &userCountryCode, const QString &serviceType, const QString &serviceProtocol);
|
||||||
int indexOfServerWithVpnKey(const QString &vpnKey) const;
|
bool hasServerWithVpnKey(const QString &vpnKey) const;
|
||||||
|
|
||||||
QVariant getDefaultServerData(const QString roleString);
|
QVariant getDefaultServerData(const QString roleString);
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,16 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "notificationhandler.h"
|
#include "notificationhandler.h"
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_IOS)
|
||||||
# include "platforms/android/android_notificationhandler.h"
|
|
||||||
#elif defined(Q_OS_IOS)
|
|
||||||
# include "platforms/ios/iosnotificationhandler.h"
|
# include "platforms/ios/iosnotificationhandler.h"
|
||||||
#else
|
#else
|
||||||
# include "systemtray_notificationhandler.h"
|
# include "systemtray_notificationhandler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
// static
|
// static
|
||||||
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
NotificationHandler* NotificationHandler::create(QObject* parent) {
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_IOS)
|
||||||
return new AndroidNotificationHandler(parent);
|
|
||||||
#elif defined(Q_OS_IOS)
|
|
||||||
return new IOSNotificationHandler(parent);
|
return new IOSNotificationHandler(parent);
|
||||||
#else
|
#else
|
||||||
return new SystemTrayNotificationHandler(parent);
|
return new SystemTrayNotificationHandler(parent);
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
|
|
||||||
import Style 1.0
|
|
||||||
|
|
||||||
import "../Controls2/TextTypes"
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
property string iconSource: ""
|
|
||||||
property string titleText: ""
|
|
||||||
property string bodyText: ""
|
|
||||||
property bool accent: false
|
|
||||||
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
Image {
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
Layout.preferredWidth: 22
|
|
||||||
Layout.preferredHeight: 22
|
|
||||||
source: root.iconSource
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
LabelTextType {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: root.titleText
|
|
||||||
color: AmneziaStyle.color.paleGray
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.weight: Font.DemiBold
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitHeight: bodyLabel.implicitHeight
|
|
||||||
|
|
||||||
LabelTextType {
|
|
||||||
id: bodyLabel
|
|
||||||
width: parent.width
|
|
||||||
text: root.bodyText
|
|
||||||
color: root.accent ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray
|
|
||||||
font.pixelSize: 14
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: bodyLabel
|
|
||||||
visible: root.accent && root.bodyText.length > 0
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
var t = root.bodyText.trim()
|
|
||||||
if (t.startsWith("@")) {
|
|
||||||
Qt.openUrlExternally("https://t.me/" + t.substring(1))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user