chore(CPP): foundation for user onboarding
Contains minimal account creation and login Considerations: - migrated status-go wrapper and login code from the fix/cpp-structure (241eec) - Minimal refactoring and changes at the moment. Expect further refactoring follow up to reach the desired state. - Fix missing keychain initialization - Fix accounts DB initialization call done by startup -> Controller.openedAccounts -> status-go.OpenAccounts calls - Small refactoring and todos for other steps - fix SignalsManager - fix async access to dereferenced status-go memory from SignalsManager - fix SignalsManager not starting when registering - finish dev end to end test for create account and login - small improvements and added TODOs for future work - add onboarding test helpers and start messaging test - Refactoring towards Login UI integration Closes: #5909 Closes: #6028
This commit is contained in:
parent
9c45dcad8b
commit
a710558c6b
|
@ -90,9 +90,3 @@ build/
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# Cpp App
|
||||
*build-src-cpp-structure*
|
||||
src-cpp-structure/projects/App/translations.qrc
|
||||
src-cpp-structure/projects/App/i18n/*
|
||||
src-cpp-structure/projects/App/translations/*
|
|
@ -31,5 +31,6 @@ add_subdirectory(libs)
|
|||
add_subdirectory(app)
|
||||
add_subdirectory(test)
|
||||
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
|
||||
add_subdirectory(resources)
|
||||
add_subdirectory(ui/imports/assets)
|
||||
add_subdirectory(ui/fonts)
|
||||
|
|
|
@ -5,7 +5,7 @@ set(CMAKE_AUTORCC On)
|
|||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_executable(${PROJECT_NAME} "")
|
||||
qt6_add_executable(${PROJECT_NAME})
|
||||
|
||||
# TODO: Fix temporarly workaround until we make qt6_target_qml_sources work
|
||||
# Adds qml files as /Status/Application/qml/.../<file name>
|
||||
|
@ -45,7 +45,6 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_PROJECT_APPLICATION_NAM
|
|||
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_BINARY_DIR=${CMAKE_BINARY_DIR})
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_SOURCE_DIR=${CMAKE_SOURCE_DIR})
|
||||
|
||||
add_subdirectory(qml)
|
||||
add_subdirectory(qml/Status/Application/Navigation)
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(res)
|
||||
|
@ -63,14 +62,15 @@ target_link_libraries(${PROJECT_NAME}
|
|||
PRIVATE
|
||||
Qt6::Quick
|
||||
|
||||
StatusQ_Application_Navigation
|
||||
Status::Application::Navigation
|
||||
|
||||
# TODO: Use Status:: namespace
|
||||
#Core
|
||||
Helpers
|
||||
Onboarding
|
||||
Assets
|
||||
StatusQ
|
||||
Status::ApplicationCore
|
||||
Status::Helpers
|
||||
Status::Onboarding
|
||||
Status::Assets
|
||||
Status::StatusQ
|
||||
Status::StatusGoQt
|
||||
)
|
||||
|
||||
# QtCreator needs this
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
# Note that the automatic plugin generation is only possible if the module does not do anything besides registering the types.
|
||||
# If it needs to do something more advanced like registering an image provider in initializeEngine, you still need to manually write the plugin. qt6_add_qml_module has support for this with NO_GENERATE_PLUGIN_SOURCE.
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
|
||||
)
|
||||
|
||||
add_subdirectory(Status/Application)
|
||||
|
||||
# Qt 6.3 fails calling qt6_target_qml_sources
|
||||
#qt6_target_qml_sources(${PROJECT_NAME}
|
||||
# QML_FILES
|
||||
# main.qml
|
||||
#)
|
|
@ -1,12 +0,0 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
|
||||
)
|
||||
|
||||
# Qt 6.3 fails calling qt6_target_qml_sources
|
||||
#qt6_target_qml_sources(${PROJECT_NAME}
|
||||
# QML_FILES
|
||||
# StatusWindow.qml
|
||||
# StatusContentView.qml
|
||||
# TODO
|
||||
#)
|
|
@ -1,5 +1,5 @@
|
|||
# Controls specialized on user workflows
|
||||
project(StatusQ_Application_Navigation)
|
||||
project(Status_Application_Navigation)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
|
@ -23,3 +23,4 @@ qt6_add_qml_module(${PROJECT_NAME}
|
|||
OUTPUT_DIRECTORY
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application/Navigation
|
||||
)
|
||||
add_library(Status::Application::Navigation ALIAS Status_Application_Navigation)
|
||||
|
|
|
@ -44,9 +44,8 @@ pipeline {
|
|||
stage('CMake Build') {
|
||||
steps {
|
||||
sh "conan install ${env.WORKSPACE}/ --profile=${env.WORKSPACE}/vendor/conan-configs/linux.ini -s build_type=Release --build=missing -if=${env.WORKSPACE}/build/conan -of=${env.WORKSPACE}/build"
|
||||
// TODO: switch CMAKE_PREFIX_PATH with "-DCMAKE_TOOLCHAIN_FILE=${env.WORKSPACE}/build/conan/conan_toolchain.cmake" after fixing error "c++: error: unrecognized command line option '-stdlib=libc++'"
|
||||
sh "qt-cmake ${env.WORKSPACE} -G Ninja -B ${env.WORKSPACE}/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${env.WORKSPACE}/build/conan/conan_home/.conan/data/gtest/1.11.0/_/_/package/521ce6d2b56041e08ea425948717819429cfbc29/"
|
||||
sh "cmake --build ${env.WORKSPACE}/build"
|
||||
// TODO: This fails compiling status-go with Jenkins user but not when run with docker's user. Fix go installation to work for all users or build docker with jenkin's
|
||||
sh "conan build ${env.WORKSPACE} --build-folder=${env.WORKSPACE}/build/conan"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ FROM stateoftheartio/qt6:6.3-gcc-aqt
|
|||
|
||||
RUN export DEBIAN_FRONTEND=noninteractive \
|
||||
&& sudo apt update -yq \
|
||||
&& sudo apt install -yq libgl-dev libvulkan-dev libxcb*-dev libxkbcommon-x11-dev python3-pip gcc-10
|
||||
&& sudo apt install -yq libgl-dev libvulkan-dev libxcb*-dev libxkbcommon-x11-dev python3-pip gcc-10 golang-go
|
||||
|
||||
RUN sudo pip install conan
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ class StatusDesktop(ConanFile):
|
|||
name = "status-desktop"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
|
||||
requires = "gtest/1.11.0" #"fruit/3.6.0",
|
||||
requires = "gtest/1.11.0", "nlohmann_json/3.10.5" # "fruit/3.6.0",
|
||||
|
||||
# cmake_find_package and cmake_find_package_multi should be substituted with CMakeDeps
|
||||
# as soon as Conan 2.0 is released and all conan-center packages are adapted
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
# Base library. Expect most of the module libraries to depend on it
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(ApplicationCore
|
||||
VERSION 0.1.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(ApplicationCore
|
||||
URI Status.ApplicationCore
|
||||
VERSION 1.0
|
||||
)
|
||||
add_library(Status::ApplicationCore ALIAS ApplicationCore)
|
||||
|
||||
target_link_libraries(ApplicationCore
|
||||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
ApplicationCore
|
||||
RUNTIME
|
||||
)
|
||||
|
||||
target_include_directories(ApplicationCore
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore
|
||||
# TODO: Workaround to QML_ELEMENT Qt6
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore
|
||||
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_sources(ApplicationCore
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/Conversions.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/Conversions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/UserConfiguration.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationCore/UserConfiguration.cpp
|
||||
)
|
||||
|
||||
# QtCreator needs this
|
||||
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
|
||||
list(REMOVE_DUPLICATES QML_IMPORT_PATH)
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include "Conversions.h"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toString(const fs::path &path) {
|
||||
return QString::fromStdString(path.string());
|
||||
}
|
||||
|
||||
fs::path toPath(const QString &pathStr) {
|
||||
return fs::path(pathStr.toStdString());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Status {
|
||||
|
||||
QString toString(const std::filesystem::path& path);
|
||||
std::filesystem::path toPath(const QString& pathStr);
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#include "UserConfiguration.h"
|
||||
|
||||
#include "Conversions.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::ApplicationCore {
|
||||
|
||||
namespace {
|
||||
/// `status-go` data location
|
||||
constexpr auto dataSubfolder = "data";
|
||||
}
|
||||
|
||||
UserConfiguration::UserConfiguration(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
generateReleaseConfiguration();
|
||||
}
|
||||
|
||||
const QString UserConfiguration::qmlUserDataFolder() const
|
||||
{
|
||||
return toString(m_userDataFolder.string());
|
||||
}
|
||||
|
||||
const fs::path &UserConfiguration::userDataFolder() const
|
||||
{
|
||||
return m_userDataFolder;
|
||||
}
|
||||
|
||||
void UserConfiguration::setUserDataFolder(const QString &newUserDataFolder)
|
||||
{
|
||||
auto newVal = Status::toPath(newUserDataFolder);
|
||||
if (m_userDataFolder.compare(newVal) == 0)
|
||||
return;
|
||||
m_userDataFolder = newVal;
|
||||
emit userDataFolderChanged();
|
||||
}
|
||||
|
||||
void UserConfiguration::generateReleaseConfiguration()
|
||||
{
|
||||
m_userDataFolder = toPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation))/dataSubfolder;
|
||||
emit userDataFolderChanged();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QtQmlIntegration>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Status::ApplicationCore {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class UserConfiguration: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QString userDataFolder READ qmlUserDataFolder WRITE setUserDataFolder NOTIFY userDataFolderChanged)
|
||||
public:
|
||||
explicit UserConfiguration(QObject *parent = nullptr);
|
||||
|
||||
const QString qmlUserDataFolder() const;
|
||||
const fs::path &userDataFolder() const;
|
||||
void setUserDataFolder(const QString &newUserDataFolder);
|
||||
|
||||
signals:
|
||||
void userDataFolderChanged();
|
||||
|
||||
private:
|
||||
void generateReleaseConfiguration();
|
||||
|
||||
fs::path m_userDataFolder;
|
||||
};
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@ set_source_files_properties(qml/Status/Assets/Resources.qml PROPERTIES
|
|||
QT_QML_SINGLETON_TYPE TRUE
|
||||
)
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
qt6_add_qml_module(Assets
|
||||
URI Status.Assets
|
||||
VERSION 1.0
|
||||
|
||||
|
@ -27,8 +27,9 @@ qt6_add_qml_module(${PROJECT_NAME}
|
|||
# Required to suppress "qmllint may not work" warning
|
||||
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Assets/
|
||||
)
|
||||
add_library(Status::Assets ALIAS Assets)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
target_link_libraries(Assets
|
||||
PRIVATE
|
||||
Qt6::Qml
|
||||
|
||||
|
@ -43,6 +44,6 @@ list(REMOVE_DUPLICATES QML_IMPORT_PATH)
|
|||
|
||||
install(
|
||||
TARGETS
|
||||
${PROJECT_NAME}
|
||||
Assets
|
||||
RUNTIME
|
||||
)
|
|
@ -1,6 +1,6 @@
|
|||
add_subdirectory(Helpers)
|
||||
# TODO: enable after adding content
|
||||
#add_subdirectory(Core)
|
||||
add_subdirectory(StatusQ)
|
||||
add_subdirectory(Onboarding)
|
||||
add_subdirectory(ApplicationCore)
|
||||
add_subdirectory(Assets)
|
||||
add_subdirectory(Helpers)
|
||||
add_subdirectory(Onboarding)
|
||||
add_subdirectory(StatusGoQt)
|
||||
add_subdirectory(StatusQ)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
# Base library. Expect most of the module libraries to depend on it
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(Core
|
||||
VERSION 0.1.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
URI Status.Core
|
||||
VERSION 1.0
|
||||
|
||||
# TODO: temporary until we make qt_target_qml_sources work
|
||||
QML_FILES
|
||||
qml/Status/Core/DevTest.qml
|
||||
|
||||
# Required to suppress "qmllint may not work" warning
|
||||
OUTPUT_DIRECTORY
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core
|
||||
)
|
||||
|
||||
add_subdirectory(qml/Status/Core)
|
||||
add_subdirectory(src)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
${PROJECT_NAME}
|
||||
RUNTIME
|
||||
)
|
|
@ -1,4 +0,0 @@
|
|||
qt6_add_resources(${PROJECT_NAME} imageresources
|
||||
PREFIX "/images"
|
||||
FILES logo.png splashscreen.png
|
||||
)
|
|
@ -1,5 +0,0 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
|
||||
)
|
|
@ -11,7 +11,8 @@ set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
|||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED "")
|
||||
add_library(Helpers SHARED)
|
||||
add_library(Status::Helpers ALIAS Helpers)
|
||||
|
||||
# Setup configuration type (Debug/Release)
|
||||
# Inspired by https://programmingrecluse.wordpress.com/2020/02/04/detect-debug-build-with-cmake/
|
||||
|
@ -36,21 +37,17 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/template/BuildConfiguration.h.in"
|
|||
"${BUILD_GENERATED_DIRECTORY}/Helpers/BuildConfiguration.h"
|
||||
@ONLY)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
target_include_directories(Helpers
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${BUILD_GENERATED_DIRECTORY}
|
||||
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers
|
||||
${BUILD_GENERATED_DIRECTORY}/Helpers
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/template/BuildConfiguration.h.in
|
||||
)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
target_link_libraries(Helpers
|
||||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
|
@ -58,6 +55,14 @@ target_link_libraries(${PROJECT_NAME}
|
|||
|
||||
install(
|
||||
TARGETS
|
||||
${PROJECT_NAME}
|
||||
Helpers
|
||||
RUNTIME
|
||||
)
|
||||
|
||||
target_sources(Helpers
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/helpers.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/logs.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Helpers/Singleton.h
|
||||
)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Helpers
|
||||
)
|
||||
|
||||
add_subdirectory(Helpers)
|
|
@ -1,7 +0,0 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/helpers.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/logs.h
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/logs.cpp
|
||||
)
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Status::Helpers
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
class Singleton
|
||||
{
|
||||
public:
|
||||
virtual ~Singleton<T>() = default;
|
||||
|
||||
static T& getInstance()
|
||||
{
|
||||
static T instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Singleton<T>(const Singleton<T>&) = delete;
|
||||
Singleton<T>& operator = (const Singleton<T>&) = delete;
|
||||
private:
|
||||
|
||||
Singleton<T>() = default;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#include <Helpers/BuildConfiguration.h>
|
||||
#include "Helpers/BuildConfiguration.h"
|
||||
|
||||
namespace Status::Helpers {
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#include <Helpers/BuildConfiguration.h>
|
||||
#include "BuildConfiguration.h"
|
||||
|
||||
namespace Status::Helpers {
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Base library. Expect most of the module libraries to depend on it
|
||||
# Onboarding Module build definition
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
|
@ -8,33 +8,61 @@ project(Onboarding
|
|||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml Concurrent REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
qt6_add_qml_module(Onboarding
|
||||
URI Status.Onboarding
|
||||
VERSION 1.0
|
||||
|
||||
# TODO: temporary until we make qt_target_qml_sources work
|
||||
QML_FILES
|
||||
qml/Status/Onboarding/base/SetupNewProfilePageBase.qml
|
||||
qml/Status/Onboarding/base/OnboardingPageBase.qml
|
||||
|
||||
qml/Status/Onboarding/ConfirmPasswordPage.qml
|
||||
qml/Status/Onboarding/CreatePasswordPage.qml
|
||||
qml/Status/Onboarding/LoginView.qml
|
||||
qml/Status/Onboarding/OnboardingView.qml
|
||||
qml/Status/Onboarding/SetupNewProfileView.qml
|
||||
qml/Status/Onboarding/SetUserNameAndPicturePage.qml
|
||||
qml/Status/Onboarding/TempTextInput.qml
|
||||
qml/Status/Onboarding/WelcomeView.qml
|
||||
|
||||
# Required to suppress "qmllint may not work" warning
|
||||
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Onboarding/
|
||||
)
|
||||
add_library(Status::Onboarding ALIAS Onboarding)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(tests)
|
||||
|
||||
target_link_libraries(Onboarding
|
||||
PRIVATE
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
Qt6::Concurrent
|
||||
|
||||
Status::ApplicationCore
|
||||
|
||||
Status::StatusGoQt
|
||||
Status::StatusGoConfig
|
||||
)
|
||||
|
||||
# Required by the MacOS authenticator. Consider moving platform particular implementation in its own static linked library
|
||||
if(APPLE)
|
||||
target_link_libraries(Onboarding
|
||||
PRIVATE
|
||||
"-framework Security"
|
||||
"-framework LocalAuthentication"
|
||||
)
|
||||
endif()
|
||||
|
||||
# QtCreator needs this
|
||||
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
|
||||
list(REMOVE_DUPLICATES QML_IMPORT_PATH)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
${PROJECT_NAME}
|
||||
Onboarding
|
||||
RUNTIME
|
||||
)
|
|
@ -0,0 +1,85 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import Status.Onboarding
|
||||
|
||||
import "base"
|
||||
|
||||
SetupNewProfilePageBase {
|
||||
id: root
|
||||
|
||||
TempTextInput {
|
||||
id: confirmPasswordInput
|
||||
|
||||
text: qsTr("1234567890")
|
||||
|
||||
width: 416
|
||||
height: 44
|
||||
|
||||
anchors {
|
||||
horizontalCenter: alignmentItem.horizontalCenter
|
||||
verticalCenter: alignmentItem.verticalCenter
|
||||
verticalCenterOffset: -baselineOffset
|
||||
}
|
||||
|
||||
font.pointSize: 23
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
}
|
||||
|
||||
Label {
|
||||
id: errorLabel
|
||||
|
||||
anchors {
|
||||
bottom: finalizeButton.top
|
||||
horizontalCenter: finalizeButton.horizontalCenter
|
||||
margins: 10
|
||||
}
|
||||
|
||||
color: "red"
|
||||
text: qsTr("Something went wrong")
|
||||
visible: false
|
||||
}
|
||||
|
||||
Button {
|
||||
id: finalizeButton
|
||||
text: qsTr("Finalize Status Password Creation")
|
||||
|
||||
anchors {
|
||||
horizontalCenter: alignmentItem.horizontalCenter
|
||||
top: alignmentItem.bottom
|
||||
topMargin: 125
|
||||
}
|
||||
|
||||
enabled: confirmPasswordInput.text === newAccountController.password
|
||||
|
||||
onClicked: {
|
||||
// TODO have states to drive async creation
|
||||
errorLabel.visible = false
|
||||
finalizeButton.enabled = false
|
||||
busyIndicatorMouseArea.cursorShape = Qt.BusyCursor
|
||||
|
||||
newAccountController.createAccount()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: newAccountController
|
||||
function onAccountCreatedAndLoggedIn() {
|
||||
busyIndicatorMouseArea.cursorShape = undefined
|
||||
root.pageDone()
|
||||
}
|
||||
function onAccountCreationError() {
|
||||
errorLabel.visible = true;
|
||||
busyIndicatorMouseArea.cursorShape = undefined
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: busyIndicatorMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
acceptedButtons: Qt.NoButton
|
||||
enabled: false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import Status.Onboarding
|
||||
|
||||
import "base"
|
||||
|
||||
SetupNewProfilePageBase {
|
||||
id: root
|
||||
|
||||
TempTextInput {
|
||||
id: passwordInput
|
||||
|
||||
text: newAccountController.password
|
||||
Binding {
|
||||
target: newAccountController
|
||||
property: "password"
|
||||
value: passwordInput.text
|
||||
}
|
||||
|
||||
width: 416
|
||||
height: 44
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: alignmentItem.top
|
||||
bottomMargin: 112
|
||||
}
|
||||
|
||||
font.pointSize: 23
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
}
|
||||
|
||||
TempTextInput {
|
||||
id: confirmPasswordInput
|
||||
|
||||
text: newAccountController.confirmationPassword
|
||||
Binding {
|
||||
target: newAccountController
|
||||
property: "confirmationPassword"
|
||||
value: confirmPasswordInput.text
|
||||
}
|
||||
|
||||
width: 416
|
||||
height: 44
|
||||
|
||||
anchors {
|
||||
horizontalCenter: alignmentItem.horizontalCenter
|
||||
verticalCenter: alignmentItem.verticalCenter
|
||||
verticalCenterOffset: -baselineOffset
|
||||
}
|
||||
|
||||
font.pointSize: 23
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Create Password")
|
||||
|
||||
anchors.horizontalCenter: alignmentItem.horizontalCenter
|
||||
anchors.top: alignmentItem.bottom
|
||||
anchors.topMargin: 125
|
||||
|
||||
enabled: newAccountController.passwordIsValid && newAccountController.confirmationPasswordIsValid
|
||||
|
||||
onClicked: root.pageDone()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Status.Containers
|
||||
|
||||
import "base"
|
||||
|
||||
OnboardingPageBase {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
centerIn: parent
|
||||
verticalCenterOffset: 50
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Welcome back")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
LayoutSpacer {
|
||||
Layout.preferredHeight: 210
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,36 +2,64 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import QtQml
|
||||
|
||||
import Qt.labs.platform
|
||||
|
||||
import Status.Containers
|
||||
import Status.Controls.Navigation
|
||||
import Status.Onboarding
|
||||
import Status.ApplicationCore
|
||||
|
||||
/** \brief Drives the onboarding workflow
|
||||
*
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal userLoggedIn()
|
||||
|
||||
implicitWidth: mainLayout.implicitWidth
|
||||
implicitHeight: mainLayout.implicitHeight
|
||||
implicitWidth: 1232
|
||||
implicitHeight: 770
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
UserConfiguration {
|
||||
id: userConfiguration
|
||||
}
|
||||
|
||||
OnboardingModule {
|
||||
id: onboardingModule
|
||||
|
||||
userDataPath: userConfiguration.userDataFolder
|
||||
}
|
||||
|
||||
MacTrafficLights {
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 13
|
||||
anchors.top: parent.top
|
||||
z: stackView.z + 1
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: stackView
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
MacTrafficLights {
|
||||
Layout.margins: 13
|
||||
initialItem: WelcomeView {
|
||||
onboardingController: onboardingModule.controller
|
||||
onSetupNewAccount: stackView.push(setupNewProfileViewComponent)
|
||||
onAccountLoggedIn: root.userLoggedIn()
|
||||
}
|
||||
}
|
||||
|
||||
LayoutSpacer {}
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: "TODO OnboardingWorkflow"
|
||||
Component {
|
||||
id: setupNewProfileViewComponent
|
||||
|
||||
SetupNewProfileView {
|
||||
onAbortAccountCreation: stackView.pop()
|
||||
onUserLoggedIn: root.userLoggedIn()
|
||||
|
||||
newAccountController: onboardingModule.controller.initNewAccountController()
|
||||
Component.onDestruction: onboardingModule.controller.terminateNewAccountController()
|
||||
}
|
||||
Button {
|
||||
text: "Done"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
onClicked: root.userLoggedIn()
|
||||
}
|
||||
LayoutSpacer {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Status.Containers
|
||||
|
||||
import "base"
|
||||
|
||||
SetupNewProfilePageBase {
|
||||
id: root
|
||||
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
centerIn: parent
|
||||
verticalCenterOffset: 50
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr("Your profile")
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
LayoutSpacer {
|
||||
Layout.preferredHeight: 210
|
||||
}
|
||||
|
||||
TempTextInput {
|
||||
id: nameInput
|
||||
|
||||
text: newAccountController.name
|
||||
Binding {
|
||||
target: newAccountController
|
||||
property: "name"
|
||||
value: nameInput.text
|
||||
}
|
||||
|
||||
Layout.preferredWidth: 328
|
||||
Layout.preferredHeight: 44
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
font.pointSize: 23
|
||||
}
|
||||
|
||||
LayoutSpacer {
|
||||
Layout.preferredHeight: 144
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Next")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
enabled: newAccountController.nameIsValid
|
||||
|
||||
onClicked: root.pageDone()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
import QtQml
|
||||
|
||||
import Qt.labs.platform
|
||||
|
||||
import Status.Containers
|
||||
import Status.Controls.Navigation
|
||||
import Status.Onboarding
|
||||
|
||||
/** \brief Drives the onboarding workflow
|
||||
*
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// TODO: fix error "Unable to assign Status::Onboarding::NewAccountController to Status::Onboarding::NewAccountController" then enable typed properties
|
||||
required property var/*NewAccountController*/ newAccountController
|
||||
|
||||
signal userLoggedIn()
|
||||
signal abortAccountCreation()
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
function goToPreviousPage() {
|
||||
if(swipeView.currentItem === setUserNameAndPicturePage)
|
||||
root.abortAccountCreation()
|
||||
else
|
||||
swipeView.currentIndex--
|
||||
}
|
||||
function goToNextPage() {
|
||||
if(swipeView.currentItem === confirmPasswordPage)
|
||||
root.userLoggedIn()
|
||||
else
|
||||
swipeView.currentIndex++
|
||||
}
|
||||
}
|
||||
|
||||
ObjectModel {
|
||||
id: pagesModel
|
||||
|
||||
SetUserNameAndPicturePage {
|
||||
id: setUserNameAndPicturePage
|
||||
newAccountController: root.newAccountController
|
||||
}
|
||||
CreatePasswordPage {
|
||||
newAccountController: root.newAccountController
|
||||
}
|
||||
ConfirmPasswordPage {
|
||||
id: confirmPasswordPage
|
||||
newAccountController: root.newAccountController
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: swipeView
|
||||
anchors.fill: parent
|
||||
|
||||
Repeater {
|
||||
id: pageRepeater
|
||||
model: pagesModel
|
||||
|
||||
Loader {
|
||||
id: pageLoader
|
||||
active: SwipeView.isCurrentItem || SwipeView.isNextItem || SwipeView.isPreviousItem
|
||||
source: modelData
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: pageRepeater.itemAt(swipeView.currentIndex)
|
||||
function onPageDone() { d.goToNextPage() }
|
||||
function onGoBack() { d.goToPreviousPage() }
|
||||
}
|
||||
}
|
||||
|
||||
PageIndicator {
|
||||
count: swipeView.count
|
||||
currentIndex: swipeView.currentIndex
|
||||
|
||||
anchors.bottom: swipeView.bottom
|
||||
anchors.horizontalCenter: swipeView.horizontalCenter
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import QtQuick
|
||||
|
||||
TextInput {
|
||||
width: 416
|
||||
height: 44
|
||||
|
||||
font.pointSize: 23
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: -1
|
||||
}
|
||||
border.width: 1
|
||||
z: parent.z - 1
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Status.Containers
|
||||
|
||||
import "base"
|
||||
|
||||
OnboardingPageBase {
|
||||
id: root
|
||||
|
||||
required property var onboardingController // OnboardingController
|
||||
|
||||
signal setupNewAccount()
|
||||
signal accountLoggedIn()
|
||||
|
||||
backAvailable: false
|
||||
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
centerIn: parent
|
||||
verticalCenterOffset: -117
|
||||
}
|
||||
spacing: 10
|
||||
|
||||
Label {
|
||||
text: qsTr("Welcome to Status")
|
||||
}
|
||||
|
||||
LayoutSpacer {
|
||||
Layout.preferredHeight: 50
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: accountsComboBox.count > 0
|
||||
ComboBox {
|
||||
id: accountsComboBox
|
||||
|
||||
Layout.preferredWidth: 328
|
||||
Layout.preferredHeight: 44
|
||||
|
||||
model: onboardingController.accounts
|
||||
textRole: "name"
|
||||
valueRole: "account"
|
||||
}
|
||||
|
||||
TempTextInput {
|
||||
id: passwordInput
|
||||
Layout.preferredWidth: 328
|
||||
Layout.preferredHeight: 44
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("Login")
|
||||
enabled: passwordInput.text.length >= 10
|
||||
onClicked: {
|
||||
errorLabel.visible = false
|
||||
onboardingController.login(accountsComboBox.currentValue, passwordInput.text)
|
||||
}
|
||||
}
|
||||
Label {
|
||||
id: errorLabel
|
||||
text: qsTr("Failed logging in")
|
||||
visible: false
|
||||
color: "red"
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr("I am new to Status")
|
||||
onClicked: root.setupNewAccount()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: onboardingController
|
||||
|
||||
function onAccountLoggedIn() {
|
||||
root.accountLoggedIn()
|
||||
}
|
||||
function onAccountLoginError(error) {
|
||||
console.warn(`Error logging in "${error}"`)
|
||||
errorLabel.visible = true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Status.Onboarding
|
||||
import Status.Controls.Navigation
|
||||
|
||||
/*! Template to guide the onboarding layout
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool backAvailable: true
|
||||
|
||||
/// All done in the current page
|
||||
signal pageDone()
|
||||
signal goBack()
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
text: "<"
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
margins: 16
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
visible: root.backAvailable
|
||||
flat: true
|
||||
background: Rectangle {
|
||||
height: width
|
||||
radius: width/2
|
||||
color: "#4360DF"
|
||||
opacity: parent.hovered ? parent.pressed ? 1 : 0.5 : 0.1
|
||||
}
|
||||
|
||||
onClicked: root.goBack()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import QtQuick
|
||||
|
||||
/*! Proposal on how to templetize the alignment requirement of some views
|
||||
*/
|
||||
OnboardingPageBase {
|
||||
// TODO: fix error "Unable to assign Status::Onboarding::NewAccountController to Status::Onboarding::NewAccountController" then enable typed properties
|
||||
required property var/*NewAccountController*/ newAccountController
|
||||
|
||||
/// Common reference item that doesn't change between common views/pages
|
||||
readonly property Item alignmentItem: alignmentBaselineItem
|
||||
|
||||
Item {
|
||||
id: alignmentBaselineItem
|
||||
|
||||
width: 1
|
||||
height: 1
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: (root.height * 477/770) - baselineOffset
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
# Internally we use includes directly
|
||||
# External clients have to explicitly use the module name
|
||||
add_subdirectory(Core)
|
||||
add_subdirectory(Onboarding)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Core
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Onboarding
|
||||
|
||||
# TODO: Workaround to QML_ELEMENT Qt6
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Core
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Onboarding
|
||||
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/Constants.h"
|
||||
#include "Common/SigningPhrases.h"
|
||||
#include "Common/Json.h"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
// TODO: Move to StatusGo library
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
struct AccountDto
|
||||
{
|
||||
QString name;
|
||||
long timestamp;
|
||||
QString identicon;
|
||||
QString keycardPairing;
|
||||
QString keyUid;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !(name.isEmpty() || keyUid.isEmpty());
|
||||
}
|
||||
|
||||
static AccountDto toAccountDto(const QJsonObject& jsonObj)
|
||||
{
|
||||
auto result = AccountDto();
|
||||
|
||||
try
|
||||
{
|
||||
result.name = Json::getMandatoryProp(jsonObj, "name")->toString();
|
||||
auto timestampIt = Json::getProp(jsonObj, "timestamp");
|
||||
if(timestampIt != jsonObj.constEnd()) {
|
||||
bool ok = false;
|
||||
auto t = timestampIt->toString().toLong(&ok);
|
||||
if(ok)
|
||||
result.timestamp = t;
|
||||
}
|
||||
result.identicon = Json::getMandatoryProp(jsonObj, "identicon")->toString();
|
||||
result.keycardPairing = Json::getMandatoryProp(jsonObj, "keycard-pairing")->toString();
|
||||
result.keyUid = Json::getMandatoryProp(jsonObj, "key-uid")->toString();
|
||||
|
||||
/// TODO: investigate unhandled `photo-path` value
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
qWarning() << QObject::tr("Mapping AccountDto failed: %1").arg(e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
#include "AccountsService.h"
|
||||
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
#include <StatusGo/General.h>
|
||||
#include <StatusGo/Utils.h>
|
||||
#include <StatusGo/Messenger/Service.h>
|
||||
|
||||
#include <ApplicationCore/Conversions.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
|
||||
std::optional<QString>
|
||||
getDataFromFile(const fs::path &path)
|
||||
{
|
||||
QFile jsonFile{Status::toString(path)};
|
||||
if(!jsonFile.open(QIODevice::ReadOnly))
|
||||
{
|
||||
qDebug() << "unable to open" << path.filename().c_str() << " for reading";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QString data = jsonFile.readAll();
|
||||
jsonFile.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
namespace Utils = Status::StatusGo::Utils;
|
||||
|
||||
AccountsService::AccountsService()
|
||||
: m_isFirstTimeAccountLogin(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool AccountsService::init(const fs::path& statusgoDataDir)
|
||||
{
|
||||
m_statusgoDataDir = statusgoDataDir;
|
||||
auto response = StatusGo::Accounts::generateAddresses(Constants::General::AccountDefaultPaths);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(const auto &genAddressObj : response.result)
|
||||
{
|
||||
auto gAcc = GeneratedAccountDto::toGeneratedAccountDto(genAddressObj.toObject());
|
||||
gAcc.alias = generateAlias(gAcc.derivedAccounts.whisper.publicKey);
|
||||
gAcc.identicon = generateIdenticon(gAcc.derivedAccounts.whisper.publicKey);
|
||||
m_generatedAccounts.push_back(std::move(gAcc));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<AccountDto> AccountsService::openAndListAccounts()
|
||||
{
|
||||
auto response = StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str());
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return std::vector<AccountDto>();
|
||||
}
|
||||
|
||||
const auto multiAccounts = response.result;
|
||||
std::vector<AccountDto> result;
|
||||
for(const auto &value : multiAccounts)
|
||||
{
|
||||
result.push_back(AccountDto::toAccountDto(value.toObject()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::vector<GeneratedAccountDto>& AccountsService::generatedAccounts() const
|
||||
{
|
||||
return m_generatedAccounts;
|
||||
}
|
||||
|
||||
bool AccountsService::setupAccountAndLogin(const QString &accountId, const QString &password, const QString &displayName)
|
||||
{
|
||||
QString installationId(QUuid::createUuid().toString(QUuid::WithoutBraces));
|
||||
QJsonObject accountData(getAccountDataForAccountId(accountId, displayName));
|
||||
|
||||
if(!setKeyStoreDir(accountData.value("key-uid").toString()))
|
||||
return false;
|
||||
|
||||
QJsonArray subAccountData(getSubaccountDataForAccountId(accountId, displayName));
|
||||
QJsonObject settings(getAccountSettings(accountId, installationId, displayName));
|
||||
QJsonObject nodeConfig(getDefaultNodeConfig(installationId));
|
||||
|
||||
QString hashedPassword(Utils::hashString(password));
|
||||
|
||||
// This initialize the DB if first time running. Required for storing accounts
|
||||
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
|
||||
return false;
|
||||
|
||||
AccountsService::storeDerivedAccounts(accountId, hashedPassword, Constants::General::AccountDefaultPaths);
|
||||
|
||||
m_loggedInAccount = saveAccountAndLogin(hashedPassword, accountData, subAccountData, settings, nodeConfig);
|
||||
|
||||
return getLoggedInAccount().isValid();
|
||||
}
|
||||
|
||||
const AccountDto& AccountsService::getLoggedInAccount() const
|
||||
{
|
||||
return m_loggedInAccount;
|
||||
}
|
||||
|
||||
const GeneratedAccountDto& AccountsService::getImportedAccount() const
|
||||
{
|
||||
return m_importedAccount;
|
||||
}
|
||||
|
||||
bool AccountsService::isFirstTimeAccountLogin() const
|
||||
{
|
||||
return m_isFirstTimeAccountLogin;
|
||||
}
|
||||
|
||||
bool AccountsService::setKeyStoreDir(const QString &key)
|
||||
{
|
||||
auto keyStoreDir = m_statusgoDataDir / m_keyStoreDirName / key.toStdString();
|
||||
auto response = StatusGo::General::initKeystore(keyStoreDir.c_str());
|
||||
return !response.containsError();
|
||||
}
|
||||
|
||||
QString AccountsService::login(AccountDto account, const QString& password)
|
||||
{
|
||||
// This is a requirement. Make it more explicit into the status go module
|
||||
if(!setKeyStoreDir(account.keyUid))
|
||||
return QString("Failed to initialize keystore before logging in");
|
||||
|
||||
// This initialize the DB if first time running. Required before logging in
|
||||
if(StatusGo::Accounts::openAccounts(m_statusgoDataDir.c_str()).containsError())
|
||||
return QString("Failed to open accounts before logging in");
|
||||
|
||||
QString hashedPassword(Utils::hashString(password));
|
||||
|
||||
QString thumbnailImage;
|
||||
QString largeImage;
|
||||
auto response = StatusGo::Accounts::login(account.name, account.keyUid, hashedPassword, account.identicon,
|
||||
thumbnailImage, largeImage);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return QString();
|
||||
}
|
||||
|
||||
m_loggedInAccount = std::move(account);
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void AccountsService::clear()
|
||||
{
|
||||
m_generatedAccounts.clear();
|
||||
m_loggedInAccount = AccountDto();
|
||||
m_importedAccount = GeneratedAccountDto();
|
||||
m_isFirstTimeAccountLogin = false;
|
||||
}
|
||||
|
||||
QString AccountsService::generateAlias(const QString& publicKey)
|
||||
{
|
||||
auto response = StatusGo::Accounts::generateAlias(publicKey);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
QString AccountsService::generateIdenticon(const QString& publicKey)
|
||||
{
|
||||
auto response = StatusGo::Accounts::generateIdenticon(publicKey);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return QString();
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
DerivedAccounts AccountsService::storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||
const QVector<QString>& paths)
|
||||
{
|
||||
auto response = StatusGo::Accounts::storeDerivedAccounts(accountId, hashedPassword, paths);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return DerivedAccounts();
|
||||
}
|
||||
return DerivedAccounts::toDerivedAccounts(response.result);
|
||||
}
|
||||
|
||||
StoredAccountDto AccountsService::storeAccount(const QString& accountId, const QString& hashedPassword)
|
||||
{
|
||||
auto response = StatusGo::Accounts::storeAccount(accountId, hashedPassword);
|
||||
if(response.containsError())
|
||||
{
|
||||
qWarning() << response.error.message;
|
||||
return StoredAccountDto();
|
||||
}
|
||||
return toStoredAccountDto(response.result);
|
||||
}
|
||||
|
||||
AccountDto AccountsService::saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
const QJsonArray& subaccounts, const QJsonObject& settings,
|
||||
const QJsonObject& config)
|
||||
{
|
||||
if(!StatusGo::Accounts::saveAccountAndLogin(hashedPassword, account, subaccounts, settings, config)) {
|
||||
qWarning() << "Failed saving acccount" << account.value("name");
|
||||
return AccountDto();
|
||||
}
|
||||
|
||||
m_isFirstTimeAccountLogin = true;
|
||||
return AccountDto::toAccountDto(account);
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::prepareAccountJsonObject(const GeneratedAccountDto& account, const QString &displayName) const
|
||||
{
|
||||
return QJsonObject{{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||
{"address", account.address},
|
||||
{"photo-path", account.identicon},
|
||||
{"identicon", account.identicon},
|
||||
{"key-uid", account.keyUid},
|
||||
{"keycard-pairing", QJsonValue()}};
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::getAccountDataForAccountId(const QString &accountId, const QString &displayName) const
|
||||
{
|
||||
|
||||
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
||||
{
|
||||
if(acc.id == accountId)
|
||||
{
|
||||
return AccountsService::prepareAccountJsonObject(acc, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
if(m_importedAccount.isValid())
|
||||
{
|
||||
if(m_importedAccount.id == accountId)
|
||||
{
|
||||
return AccountsService::prepareAccountJsonObject(m_importedAccount, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "account not found";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonArray AccountsService::prepareSubaccountJsonObject(const GeneratedAccountDto& account, const QString &displayName) const
|
||||
{
|
||||
return {
|
||||
QJsonObject{
|
||||
{"public-key", account.derivedAccounts.defaultWallet.publicKey},
|
||||
{"address", account.derivedAccounts.defaultWallet.address},
|
||||
{"color", "#4360df"},
|
||||
{"wallet", true},
|
||||
{"path", Constants::General::PathDefaultWallet},
|
||||
{"name", "Status account"}
|
||||
},
|
||||
QJsonObject{
|
||||
{"public-key", account.derivedAccounts.whisper.publicKey},
|
||||
{"address", account.derivedAccounts.whisper.address},
|
||||
{"path", Constants::General::PathWhisper},
|
||||
{"name", displayName.isEmpty() ? account.alias : displayName},
|
||||
{"identicon", account.identicon},
|
||||
{"chat", true}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
QJsonArray AccountsService::getSubaccountDataForAccountId(const QString& accountId, const QString &displayName) const
|
||||
{
|
||||
// "All these for loops with a nested if cry for a std::find_if :)"
|
||||
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
||||
{
|
||||
if(acc.id == accountId)
|
||||
{
|
||||
return prepareSubaccountJsonObject(acc, displayName);
|
||||
}
|
||||
}
|
||||
if(m_importedAccount.isValid())
|
||||
{
|
||||
if(m_importedAccount.id == accountId)
|
||||
{
|
||||
return prepareSubaccountJsonObject(m_importedAccount, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Is this expected? Have proper error propagation, otherwise throw
|
||||
qDebug() << "account not found";
|
||||
return QJsonArray();
|
||||
}
|
||||
|
||||
QString AccountsService::generateSigningPhrase(int count) const
|
||||
{
|
||||
QStringList words;
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
words.append(Constants::SigningPhrases[QRandomGenerator::global()->bounded(
|
||||
static_cast<int>(Constants::SigningPhrases.size()))]);
|
||||
}
|
||||
return words.join(" ");
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::prepareAccountSettingsJsonObject(const GeneratedAccountDto& account,
|
||||
const QString& installationId,
|
||||
const QString& displayName) const
|
||||
{
|
||||
try {
|
||||
auto templateDefaultNetworksJson = getDataFromFile(":/Status/StaticConfig/default-networks.json").value();
|
||||
auto infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
||||
|
||||
QString defaultNetworksContent = templateDefaultNetworksJson.replace("%INFURA_KEY%", infuraKey);
|
||||
QJsonArray defaultNetworksJson = QJsonDocument::fromJson(defaultNetworksContent.toUtf8()).array();
|
||||
|
||||
return QJsonObject{
|
||||
{"key-uid", account.keyUid},
|
||||
{"mnemonic", account.mnemonic},
|
||||
{"public-key", account.derivedAccounts.whisper.publicKey},
|
||||
{"name", account.alias},
|
||||
{"display-name", displayName},
|
||||
{"address", account.address},
|
||||
{"eip1581-address", account.derivedAccounts.eip1581.address},
|
||||
{"dapps-address", account.derivedAccounts.defaultWallet.address},
|
||||
{"wallet-root-address", account.derivedAccounts.walletRoot.address},
|
||||
{"preview-privacy", true},
|
||||
{"signing-phrase", generateSigningPhrase(3)},
|
||||
{"log-level", "INFO"},
|
||||
{"latest-derived-path", 0},
|
||||
{"networks/networks", defaultNetworksJson},
|
||||
{"currency", "usd"},
|
||||
{"identicon", account.identicon},
|
||||
{"waku-enabled", true},
|
||||
{"wallet/visible-tokens", {
|
||||
{Constants::General::DefaultNetworkName, QJsonArray{"SNT"}}
|
||||
}
|
||||
},
|
||||
{"appearance", 0},
|
||||
{"networks/current-network", Constants::General::DefaultNetworkName},
|
||||
{"installation-id", installationId}
|
||||
};
|
||||
} catch (std::bad_optional_access) {
|
||||
return QJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::getAccountSettings(const QString& accountId, const QString& installationId, const QString &displayName) const
|
||||
{
|
||||
for(const GeneratedAccountDto &acc : m_generatedAccounts)
|
||||
|
||||
if(acc.id == accountId)
|
||||
{
|
||||
return AccountsService::prepareAccountSettingsJsonObject(acc, installationId, displayName);
|
||||
}
|
||||
|
||||
if(m_importedAccount.isValid())
|
||||
{
|
||||
if(m_importedAccount.id == accountId)
|
||||
{
|
||||
return AccountsService::prepareAccountSettingsJsonObject(m_importedAccount, installationId, displayName);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Is this expected? Have proper error propagation, otherwise throw
|
||||
qDebug() << "account not found";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonArray getNodes(const QJsonObject& fleet, const QString& nodeType)
|
||||
{
|
||||
auto nodes = fleet[nodeType].toObject();
|
||||
QJsonArray result;
|
||||
for(auto it = nodes.begin(); it != nodes.end(); ++it)
|
||||
result << *it;
|
||||
return result;
|
||||
}
|
||||
|
||||
QJsonObject AccountsService::getDefaultNodeConfig(const QString& installationId) const
|
||||
{
|
||||
try {
|
||||
auto templateNodeConfigJsonStr = getDataFromFile(":/Status/StaticConfig/node-config.json").value();
|
||||
auto fleetJson = getDataFromFile(":/Status/StaticConfig/fleets.json").value();
|
||||
auto infuraKey = getDataFromFile(":/Status/StaticConfig/infura_key").value();
|
||||
|
||||
auto nodeConfigJsonStr = templateNodeConfigJsonStr.replace("%INSTALLATIONID%", installationId)
|
||||
.replace("%INFURA_KEY%", infuraKey);
|
||||
QJsonObject nodeConfigJson = QJsonDocument::fromJson(nodeConfigJsonStr.toUtf8()).object();
|
||||
QJsonObject clusterConfig = nodeConfigJson["ClusterConfig"].toObject();
|
||||
|
||||
QJsonObject fleetsJson = QJsonDocument::fromJson(fleetJson.toUtf8()).object()["fleets"].toObject();
|
||||
auto fleet = fleetsJson[Constants::Fleet::Prod].toObject();
|
||||
|
||||
clusterConfig["Fleet"] = Constants::Fleet::Prod;
|
||||
clusterConfig["BootNodes"] = getNodes(fleet, Constants::FleetNodes::Bootnodes);
|
||||
clusterConfig["TrustedMailServers"] = getNodes(fleet, Constants::FleetNodes::Mailservers);
|
||||
clusterConfig["StaticNodes"] = getNodes(fleet, Constants::FleetNodes::Whisper);
|
||||
clusterConfig["RendezvousNodes"] = getNodes(fleet, Constants::FleetNodes::Rendezvous);
|
||||
clusterConfig["RelayNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||
clusterConfig["StoreNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||
clusterConfig["FilterNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||
clusterConfig["LightpushNodes"] = getNodes(fleet, Constants::FleetNodes::Waku);
|
||||
|
||||
nodeConfigJson["ClusterConfig"] = clusterConfig;
|
||||
|
||||
return nodeConfigJson;
|
||||
} catch (std::bad_optional_access) {
|
||||
return QJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include "AccountsServiceInterface.h"
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief The Service class
|
||||
*
|
||||
* \todo Refactor static dependencies
|
||||
* :/resources/default-networks.json
|
||||
* :/resources/node-config.json
|
||||
* :/resources/fleets.json
|
||||
* :/resources/infura_key
|
||||
* \todo AccountsService
|
||||
* \todo Consider removing unneded states (first time account login, user)
|
||||
*/
|
||||
class AccountsService : public AccountsServiceInterface
|
||||
{
|
||||
public:
|
||||
AccountsService();
|
||||
|
||||
/// \see ServiceInterface
|
||||
bool init(const fs::path& statusgoDataDir) override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
[[nodiscard]] std::vector<AccountDto> openAndListAccounts() override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
[[nodiscard]] const std::vector<GeneratedAccountDto>& generatedAccounts() const override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
bool setupAccountAndLogin(const QString& accountId, const QString& password, const QString& displayName) override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
[[nodiscard]] const AccountDto& getLoggedInAccount() const override;
|
||||
|
||||
[[nodiscard]] const GeneratedAccountDto& getImportedAccount() const override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
[[nodiscard]] bool isFirstTimeAccountLogin() const override;
|
||||
|
||||
/// \see ServiceInterface
|
||||
bool setKeyStoreDir(const QString &key) override;
|
||||
|
||||
QString login(AccountDto account, const QString& password) override;
|
||||
|
||||
void clear() override;
|
||||
|
||||
QString generateAlias(const QString& publicKey) override;
|
||||
|
||||
QString generateIdenticon(const QString& publicKey) override;
|
||||
|
||||
private:
|
||||
QJsonObject prepareAccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
|
||||
|
||||
DerivedAccounts storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||
const QVector<QString>& paths);
|
||||
StoredAccountDto storeAccount(const QString& accountId, const QString& hashedPassword);
|
||||
|
||||
AccountDto saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
const QJsonArray& subaccounts, const QJsonObject& settings,
|
||||
const QJsonObject& config);
|
||||
|
||||
QJsonObject getAccountDataForAccountId(const QString& accountId, const QString& displayName) const;
|
||||
|
||||
QJsonArray prepareSubaccountJsonObject(const GeneratedAccountDto& account, const QString& displayName) const;
|
||||
|
||||
QJsonArray getSubaccountDataForAccountId(const QString& accountId, const QString& displayName) const;
|
||||
|
||||
QString generateSigningPhrase(int count) const;
|
||||
|
||||
QJsonObject prepareAccountSettingsJsonObject(const GeneratedAccountDto& account,
|
||||
const QString& installationId,
|
||||
const QString& displayName) const;
|
||||
|
||||
QJsonObject getAccountSettings(const QString& accountId, const QString& installationId, const QString& displayName) const;
|
||||
|
||||
QJsonObject getDefaultNodeConfig(const QString& installationId) const;
|
||||
|
||||
private:
|
||||
std::vector<GeneratedAccountDto> m_generatedAccounts;
|
||||
|
||||
fs::path m_statusgoDataDir;
|
||||
bool m_isFirstTimeAccountLogin;
|
||||
// TODO: don't see the need for this state here
|
||||
AccountDto m_loggedInAccount;
|
||||
GeneratedAccountDto m_importedAccount;
|
||||
|
||||
// Here for now. Extract them if used by other services
|
||||
static constexpr auto m_keyStoreDirName = "keystore";
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "AccountDto.h"
|
||||
#include "GeneratedAccountDto.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
class AccountsServiceInterface
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~AccountsServiceInterface() = default;
|
||||
|
||||
/// Generates and cache addresses accessible by \c generatedAccounts
|
||||
virtual bool init(const fs::path& statusgoDataDir) = 0;
|
||||
|
||||
/// opens database and returns accounts list.
|
||||
[[nodiscard]] virtual std::vector<AccountDto> openAndListAccounts() = 0;
|
||||
|
||||
/// Retrieve cached accounts generated in \c init
|
||||
[[nodiscard]] virtual const std::vector<GeneratedAccountDto>& generatedAccounts() const = 0;
|
||||
|
||||
/// Configure an generated account. \a accountID must be sourced from \c generatedAccounts
|
||||
virtual bool setupAccountAndLogin(const QString& accountID, const QString& password, const QString& displayName) = 0;
|
||||
|
||||
/// Account that is currently logged-in
|
||||
[[nodiscard]] virtual const AccountDto& getLoggedInAccount() const = 0;
|
||||
|
||||
[[nodiscard]] virtual const GeneratedAccountDto& getImportedAccount() const = 0;
|
||||
|
||||
/// Check if the login was never done in the current \c data directory
|
||||
[[nodiscard]] virtual bool isFirstTimeAccountLogin() const = 0;
|
||||
|
||||
/// Set and initializes the keystore directory. \see StatusGo::General::initKeystore
|
||||
virtual bool setKeyStoreDir(const QString &key) = 0;
|
||||
|
||||
virtual QString login(AccountDto account, const QString& password) = 0;
|
||||
|
||||
virtual void clear() = 0;
|
||||
|
||||
virtual QString generateAlias(const QString& publicKey) = 0;
|
||||
|
||||
virtual QString generateIdenticon(const QString& publicKey) = 0;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common/Constants.h"
|
||||
#include "Common/SigningPhrases.h"
|
||||
#include "Common/Json.h"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
struct DerivedAccountDetails
|
||||
{
|
||||
QString publicKey;
|
||||
QString address;
|
||||
QString derivationPath;
|
||||
|
||||
static DerivedAccountDetails toDerivedAccountDetails(const QJsonObject& jsonObj, const QString& derivationPath)
|
||||
{
|
||||
// Mapping this DTO is not strightforward since only keys are used for id. We
|
||||
// handle it a bit different.
|
||||
auto result = DerivedAccountDetails();
|
||||
|
||||
try
|
||||
{
|
||||
result.derivationPath = derivationPath;
|
||||
result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString();
|
||||
result.address = Json::getMandatoryProp(jsonObj, "address")->toString();
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
qWarning() << QString("Mapping DerivedAccountDetails failed: %1").arg(e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct DerivedAccounts
|
||||
{
|
||||
DerivedAccountDetails whisper;
|
||||
DerivedAccountDetails walletRoot;
|
||||
DerivedAccountDetails defaultWallet;
|
||||
DerivedAccountDetails eip1581;
|
||||
|
||||
static DerivedAccounts toDerivedAccounts(const QJsonObject& jsonObj)
|
||||
{
|
||||
auto result = DerivedAccounts();
|
||||
|
||||
for(const auto &derivationPath : jsonObj.keys())
|
||||
{
|
||||
auto derivedObj = jsonObj.value(derivationPath).toObject();
|
||||
if(derivationPath == Constants::General::PathWhisper)
|
||||
{
|
||||
result.whisper = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
|
||||
}
|
||||
else if(derivationPath == Constants::General::PathWalletRoot)
|
||||
{
|
||||
result.walletRoot = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
|
||||
}
|
||||
else if(derivationPath == Constants::General::PathDefaultWallet)
|
||||
{
|
||||
result.defaultWallet = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
|
||||
}
|
||||
else if(derivationPath == Constants::General::PathEIP1581)
|
||||
{
|
||||
result.eip1581 = DerivedAccountDetails::toDerivedAccountDetails(derivedObj, derivationPath);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct StoredAccountDto
|
||||
{
|
||||
QString publicKey;
|
||||
QString address;
|
||||
|
||||
};
|
||||
|
||||
static StoredAccountDto toStoredAccountDto(const QJsonObject& jsonObj)
|
||||
{
|
||||
auto result = StoredAccountDto();
|
||||
|
||||
try {
|
||||
result.address = Json::getMandatoryProp(jsonObj, "address")->toString();
|
||||
result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString();
|
||||
} catch (std::exception e) {
|
||||
qWarning() << QString("Mapping StoredAccountDto failed: %1").arg(e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct GeneratedAccountDto
|
||||
{
|
||||
QString id;
|
||||
QString publicKey;
|
||||
QString address;
|
||||
QString keyUid;
|
||||
QString mnemonic;
|
||||
DerivedAccounts derivedAccounts;
|
||||
|
||||
// The following two are set additionally.
|
||||
QString alias;
|
||||
QString identicon;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !(id.isEmpty() || publicKey.isEmpty() || address.isEmpty() || keyUid.isEmpty());
|
||||
}
|
||||
|
||||
static GeneratedAccountDto toGeneratedAccountDto(const QJsonObject& jsonObj)
|
||||
{
|
||||
auto result = GeneratedAccountDto();
|
||||
|
||||
try
|
||||
{
|
||||
result.id = Json::getMandatoryProp(jsonObj, "id")->toString();
|
||||
result.address = Json::getMandatoryProp(jsonObj, "address")->toString();
|
||||
result.keyUid = Json::getMandatoryProp(jsonObj, "keyUid")->toString();
|
||||
result.mnemonic = Json::getMandatoryProp(jsonObj, "mnemonic")->toString();
|
||||
result.publicKey = Json::getMandatoryProp(jsonObj, "publicKey")->toString();
|
||||
|
||||
auto derivedObj = Json::getProp(jsonObj, "derived")->toObject();
|
||||
if(!derivedObj.isEmpty())
|
||||
{
|
||||
result.derivedAccounts = DerivedAccounts::toDerivedAccounts(derivedObj);
|
||||
}
|
||||
}
|
||||
catch (std::exception e)
|
||||
{
|
||||
qWarning() << QString("Mapping GeneratedAccountDto failed: %1").arg(e.what());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/NewAccountController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/OnboardingController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/OnboardingModule.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserAccount.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserAccountsModel.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountDto.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/GeneratedAccountDto.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountsService.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountsServiceInterface.h
|
||||
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/NewAccountController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/OnboardingController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/OnboardingModule.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserAccount.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserAccountsModel.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/AccountsService.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Common/Constants.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Common/Json.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Common/SigningPhrases.h
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore>
|
||||
#include <QStringLiteral>
|
||||
|
||||
namespace Status::Constants
|
||||
{
|
||||
|
||||
namespace Fleet
|
||||
{
|
||||
inline const auto Prod = u"eth.prod"_qs;
|
||||
inline const auto Staging = u"eth.staging"_qs;
|
||||
inline const auto Test = u"eth.test"_qs;
|
||||
inline const auto WakuV2Prod = u"wakuv2.prod"_qs;
|
||||
inline const auto WakuV2Test = u"wakuv2.test"_qs;
|
||||
inline const auto GoWakuTest = u"go-waku.test"_qs;
|
||||
}
|
||||
|
||||
namespace FleetNodes
|
||||
{
|
||||
inline const auto Bootnodes = u"boot"_qs;
|
||||
inline const auto Mailservers = u"mail"_qs;
|
||||
inline const auto Rendezvous = u"rendezvous"_qs;
|
||||
inline const auto Whisper = u"whisper"_qs;
|
||||
inline const auto Waku = u"waku"_qs;
|
||||
inline const auto LibP2P = u"libp2p"_qs;
|
||||
inline const auto Websocket = u"websocket"_qs;
|
||||
}
|
||||
|
||||
namespace General
|
||||
{
|
||||
inline const auto DefaultNetworkName = u"mainnet_rpc"_qs;
|
||||
//const DEFAULT_NETWORKS_IDS* = @["mainnet_rpc", "testnet_rpc", "rinkeby_rpc", "goerli_rpc", "xdai_rpc", "poa_rpc" ]
|
||||
|
||||
inline const auto ZeroAddress = u"0x0000000000000000000000000000000000000000"_qs;
|
||||
|
||||
inline const auto PathWalletRoot = u"m/44'/60'/0'/0"_qs;
|
||||
// EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived
|
||||
inline const auto PathEIP1581 = u"m/43'/60'/1581'"_qs;
|
||||
// BIP44-0 Wallet key, the default wallet key
|
||||
inline const auto PathDefaultWallet = PathWalletRoot + u"/0"_qs;
|
||||
// EIP1581 Chat Key 0, the default whisper key
|
||||
inline const auto PathWhisper = PathEIP1581 + u"/0'/0"_qs;
|
||||
|
||||
inline const QVector<QString> AccountDefaultPaths {PathWalletRoot, PathEIP1581, PathWhisper, PathDefaultWallet};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
namespace Status
|
||||
{
|
||||
|
||||
class Json
|
||||
{
|
||||
public:
|
||||
/// TODO: refactor to get std::optional<QJsonObject> or rename it to get*It
|
||||
static QJsonObject::const_iterator getProp(const QJsonObject& object, const QString& field)
|
||||
{
|
||||
const auto it = object.constFind(field);
|
||||
return it;
|
||||
}
|
||||
|
||||
/// TODO: refactor to get QJsonObject
|
||||
static QJsonObject::const_iterator getMandatoryProp(const QJsonObject& object, const QString& field)
|
||||
{
|
||||
const auto it = getProp(object, field);
|
||||
|
||||
if (it == object.constEnd())
|
||||
{
|
||||
throw std::logic_error(QString("No field `%1`").arg(field).toStdString());
|
||||
}
|
||||
|
||||
return it;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
namespace Status::Constants
|
||||
{
|
||||
|
||||
constexpr std::array SigningPhrases {
|
||||
"area", "army", "atom", "aunt", "babe", "baby", "back", "bail", "bait", "bake", "ball", "band", "bank", "barn",
|
||||
"base", "bass", "bath", "bead", "beak", "beam", "bean", "bear", "beat", "beef", "beer", "beet", "bell", "belt",
|
||||
"bend", "bike", "bill", "bird", "bite", "blow", "blue", "boar", "boat", "body", "bolt", "bomb", "bone", "book",
|
||||
"boot", "bore", "boss", "bowl", "brow", "bulb", "bull", "burn", "bush", "bust", "cafe", "cake", "calf", "call",
|
||||
"calm", "camp", "cane", "cape", "card", "care", "carp", "cart", "case", "cash", "cast", "cave", "cell", "cent",
|
||||
"chap", "chef", "chin", "chip", "chop", "chub", "chug", "city", "clam", "clef", "clip", "club", "clue", "coal",
|
||||
"coat", "code", "coil", "coin", "coke", "cold", "colt", "comb", "cone", "cook", "cope", "copy", "cord", "cork",
|
||||
"corn", "cost", "crab", "craw", "crew", "crib", "crop", "crow", "curl", "cyst", "dame", "dare", "dark", "dart",
|
||||
"dash", "data", "date", "dead", "deal", "dear", "debt", "deck", "deep", "deer", "desk", "dhow", "diet", "dill",
|
||||
"dime", "dirt", "dish", "disk", "dock", "doll", "door", "dory", "drag", "draw", "drop", "drug", "drum", "duck",
|
||||
"dump", "dust", "duty", "ease", "east", "eave", "eddy", "edge", "envy", "epee", "exam", "exit", "face", "fact",
|
||||
"fail", "fall", "fame", "fang", "farm", "fawn", "fear", "feed", "feel", "feet", "file", "fill", "film", "find",
|
||||
"fine", "fire", "fish", "flag", "flat", "flax", "flow", "foam", "fold", "font", "food", "foot", "fork", "form",
|
||||
"fort", "fowl", "frog", "fuel", "full", "gain", "gale", "galn", "game", "garb", "gate", "gear", "gene", "gift",
|
||||
"girl", "give", "glad", "glen", "glue", "glut", "goal", "goat", "gold", "golf", "gong", "good", "gown", "grab",
|
||||
"gram", "gray", "grey", "grip", "grit", "gyro", "hail", "hair", "half", "hall", "hand", "hang", "harm", "harp",
|
||||
"hate", "hawk", "head", "heat", "heel", "hell", "helo", "help", "hemp", "herb", "hide", "high", "hill", "hire",
|
||||
"hive", "hold", "hole", "home", "hood", "hoof", "hook", "hope", "hops", "horn", "hose", "host", "hour", "hunt",
|
||||
"hurt", "icon", "idea", "inch", "iris", "iron", "item", "jail", "jeep", "jeff", "joey", "join", "joke", "judo",
|
||||
"jump", "junk", "jury", "jute", "kale", "keep", "kick", "kill", "kilt", "kind", "king", "kiss", "kite", "knee",
|
||||
"knot", "lace", "lack", "lady", "lake", "lamb", "lamp", "land", "lark", "lava", "lawn", "lead", "leaf", "leek",
|
||||
"lier", "life", "lift", "lily", "limo", "line", "link", "lion", "lisa", "list", "load", "loaf", "loan", "lock",
|
||||
"loft", "long", "look", "loss", "lout", "love", "luck", "lung", "lute", "lynx", "lyre", "maid", "mail", "main",
|
||||
"make", "male", "mall", "manx", "many", "mare", "mark", "mask", "mass", "mate", "math", "meal", "meat", "meet",
|
||||
"menu", "mess", "mice", "midi", "mile", "milk", "mime", "mind", "mine", "mini", "mint", "miss", "mist", "moat",
|
||||
"mode", "mole", "mood", "moon", "most", "moth", "move", "mule", "mutt", "nail", "name", "neat", "neck", "need",
|
||||
"neon", "nest", "news", "node", "nose", "note", "oboe", "okra", "open", "oval", "oven", "oxen", "pace", "pack",
|
||||
"page", "pail", "pain", "pair", "palm", "pard", "park", "part", "pass", "past", "path", "peak", "pear", "peen",
|
||||
"peer", "pelt", "perp", "pest", "pick", "pier", "pike", "pile", "pimp", "pine", "ping", "pink", "pint", "pipe",
|
||||
"piss", "pith", "plan", "play", "plot", "plow", "poem", "poet", "pole", "polo", "pond", "pony", "poof", "pool",
|
||||
"port", "post", "prow", "pull", "puma", "pump", "pupa", "push", "quit", "race", "rack", "raft", "rage", "rail",
|
||||
"rain", "rake", "rank", "rate", "read", "rear", "reef", "rent", "rest", "rice", "rich", "ride", "ring", "rise",
|
||||
"risk", "road", "robe", "rock", "role", "roll", "roof", "room", "root", "rope", "rose", "ruin", "rule", "rush",
|
||||
"ruth", "sack", "safe", "sage", "sail", "sale", "salt", "sand", "sari", "sash", "save", "scow", "seal", "seat",
|
||||
"seed", "self", "sell", "shed", "shin", "ship", "shoe", "shop", "shot", "show", "sick", "side", "sign", "silk",
|
||||
"sill", "silo", "sing", "sink", "site", "size", "skin", "sled", "slip", "smog", "snob", "snow", "soap", "sock",
|
||||
"soda", "sofa", "soft", "soil", "song", "soot", "sort", "soup", "spot", "spur", "stag", "star", "stay", "stem",
|
||||
"step", "stew", "stop", "stud", "suck", "suit", "swan", "swim", "tail", "tale", "talk", "tank", "tard", "task",
|
||||
"taxi", "team", "tear", "teen", "tell", "temp", "tent", "term", "test", "text", "thaw", "tile", "till", "time",
|
||||
"tire", "toad", "toga", "togs", "tone", "tool", "toot", "tote", "tour", "town", "tram", "tray", "tree", "trim",
|
||||
"trip", "tuba", "tube", "tuna", "tune", "turn", "tutu", "twig", "type", "unit", "user", "vane", "vase", "vast",
|
||||
"veal", "veil", "vein", "vest", "vibe", "view", "vise", "wait", "wake", "walk", "wall", "wash", "wasp", "wave",
|
||||
"wear", "weed", "week", "well", "west", "whip", "wife", "will", "wind", "wine", "wing", "wire", "wish", "wolf",
|
||||
"wood", "wool", "word", "work", "worm", "wrap", "wren", "yard", "yarn", "yawl", "year", "yoga", "yoke", "yurt",
|
||||
"zinc", "zone"
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
#include "NewAccountController.h"
|
||||
|
||||
#include "Accounts/AccountsService.h"
|
||||
|
||||
#include <StatusGo/SignalsManager.h>
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
|
||||
NewAccountController::NewAccountController(std::shared_ptr<AccountsServiceInterface> accountsService, QObject *parent)
|
||||
// TODO: remove dev dev setup after the final implementation
|
||||
: m_name("TestAccount")
|
||||
, m_nameIsValid(true)
|
||||
, m_password("1234567890")
|
||||
, m_passwordIsValid(true)
|
||||
, m_confirmationPassword("1234567890")
|
||||
, m_confirmationPasswordIsValid(true)
|
||||
// END dev setup
|
||||
, m_accountsService(accountsService)
|
||||
{
|
||||
connect(this, &NewAccountController::passwordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||
connect(this, &NewAccountController::confirmationPasswordChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||
connect(this, &NewAccountController::nameChanged, this, &NewAccountController::checkAndUpdateDataValidity);
|
||||
}
|
||||
|
||||
void NewAccountController::createAccount()
|
||||
{
|
||||
// TODO: fix this after moving SingalManager to StatusGo wrapper lib
|
||||
QObject::connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeLogin, this, &NewAccountController::onNodeLogin);
|
||||
|
||||
auto setupAccountFn = [this]() {
|
||||
if(m_nameIsValid && m_passwordIsValid && m_confirmationPasswordIsValid) {
|
||||
auto genAccounts = m_accountsService->generatedAccounts();
|
||||
if(genAccounts.size() > 0) {
|
||||
if(m_accountsService->setupAccountAndLogin(genAccounts[0].id, m_password, m_name))
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
// TODO: refactor StatusGo wrapper to work with futures instead of SignalManager
|
||||
m_createAccountFuture = QtConcurrent::run(setupAccountFn)
|
||||
.then([]{ /*Nothing, we expect status-go events*/ })
|
||||
.onFailed([this] {
|
||||
emit accountCreationError();
|
||||
})
|
||||
.onCanceled([this] {
|
||||
emit accountCreationError();
|
||||
});
|
||||
}
|
||||
|
||||
const QString &NewAccountController::password() const
|
||||
{
|
||||
return m_password;
|
||||
}
|
||||
|
||||
void NewAccountController::setPassword(const QString &newPassword)
|
||||
{
|
||||
if (m_password == newPassword)
|
||||
return;
|
||||
m_password = newPassword;
|
||||
emit passwordChanged();
|
||||
}
|
||||
|
||||
const QString &NewAccountController::confirmationPassword() const
|
||||
{
|
||||
return m_confirmationPassword;
|
||||
}
|
||||
|
||||
void NewAccountController::setConfirmationPassword(const QString &newConfirmationPassword)
|
||||
{
|
||||
if (m_confirmationPassword == newConfirmationPassword)
|
||||
return;
|
||||
m_confirmationPassword = newConfirmationPassword;
|
||||
emit confirmationPasswordChanged();
|
||||
}
|
||||
|
||||
const QString &NewAccountController::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void NewAccountController::setName(const QString &newName)
|
||||
{
|
||||
if (m_name == newName)
|
||||
return;
|
||||
m_name = newName;
|
||||
emit nameChanged();
|
||||
}
|
||||
|
||||
bool NewAccountController::passwordIsValid() const
|
||||
{
|
||||
return m_passwordIsValid;
|
||||
}
|
||||
|
||||
bool NewAccountController::confirmationPasswordIsValid() const
|
||||
{
|
||||
return m_confirmationPasswordIsValid;
|
||||
}
|
||||
|
||||
bool NewAccountController::nameIsValid() const
|
||||
{
|
||||
return m_nameIsValid;
|
||||
}
|
||||
|
||||
void NewAccountController::onNodeLogin(const QString& error)
|
||||
{
|
||||
if(error.isEmpty())
|
||||
emit accountCreatedAndLoggedIn();
|
||||
else
|
||||
emit accountCreationError();
|
||||
}
|
||||
|
||||
void NewAccountController::checkAndUpdateDataValidity()
|
||||
{
|
||||
auto passwordValid = m_password.length() >= 6;
|
||||
if(passwordValid != m_passwordIsValid) {
|
||||
m_passwordIsValid = passwordValid;
|
||||
emit passwordIsValidChanged();
|
||||
}
|
||||
|
||||
auto confirmationPasswordValid = m_password == m_confirmationPassword;
|
||||
if(confirmationPasswordValid != m_confirmationPasswordIsValid) {
|
||||
m_confirmationPasswordIsValid = confirmationPasswordValid;
|
||||
emit confirmationPasswordIsValidChanged();
|
||||
}
|
||||
|
||||
auto nameValid = m_name.length() >= 10;
|
||||
if(nameValid != m_nameIsValid) {
|
||||
m_nameIsValid = nameValid;
|
||||
emit nameIsValidChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Status::Onboarding
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include "UserAccountsModel.h"
|
||||
|
||||
#include "Accounts/AccountsServiceInterface.h"
|
||||
#include "Accounts/AccountDto.h"
|
||||
|
||||
#include <QtQmlIntegration>
|
||||
|
||||
#include <QFuture>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
class ServiceInterface;
|
||||
|
||||
/*! \brief presentation layer for creation of a new account workflow
|
||||
*
|
||||
* \todo shared functionality should be moved to Common library (e.g. Name/Picture Validation)
|
||||
*/
|
||||
class NewAccountController: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Created and owned externally")
|
||||
|
||||
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
|
||||
Q_PROPERTY(QString confirmationPassword READ confirmationPassword WRITE setConfirmationPassword NOTIFY confirmationPasswordChanged)
|
||||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
|
||||
Q_PROPERTY(bool passwordIsValid READ passwordIsValid NOTIFY passwordIsValidChanged)
|
||||
Q_PROPERTY(bool confirmationPasswordIsValid READ confirmationPasswordIsValid NOTIFY confirmationPasswordIsValidChanged)
|
||||
Q_PROPERTY(bool nameIsValid READ nameIsValid NOTIFY nameIsValidChanged)
|
||||
|
||||
public:
|
||||
explicit NewAccountController(std::shared_ptr<AccountsServiceInterface> accountsService, QObject* parent = nullptr);
|
||||
|
||||
Q_INVOKABLE void createAccount();
|
||||
|
||||
const QString &password() const;
|
||||
void setPassword(const QString &newPassword);
|
||||
|
||||
const QString &confirmationPassword() const;
|
||||
void setConfirmationPassword(const QString &newConfirmationPassword);
|
||||
|
||||
const QString &name() const;
|
||||
void setName(const QString &newName);
|
||||
|
||||
bool passwordIsValid() const;
|
||||
bool confirmationPasswordIsValid() const;
|
||||
bool nameIsValid() const;
|
||||
|
||||
signals:
|
||||
void passwordChanged();
|
||||
void confirmationPasswordChanged();
|
||||
void nameChanged();
|
||||
void nameIsValidChanged();
|
||||
void passwordIsValidChanged();
|
||||
void confirmationPasswordIsValidChanged();
|
||||
|
||||
void accountCreatedAndLoggedIn();
|
||||
void accountCreationError();
|
||||
|
||||
private slots:
|
||||
void onNodeLogin(const QString& error);
|
||||
|
||||
private:
|
||||
void checkAndUpdateDataValidity();
|
||||
|
||||
QString m_password;
|
||||
QString m_confirmationPassword;
|
||||
QString m_name;
|
||||
|
||||
bool m_passwordIsValid = false;
|
||||
bool m_confirmationPasswordIsValid;
|
||||
bool m_nameIsValid = false;
|
||||
|
||||
std::shared_ptr<AccountsServiceInterface> m_accountsService;
|
||||
QFuture<void> m_createAccountFuture;
|
||||
};
|
||||
|
||||
} // namespace Status::Onboarding
|
|
@ -0,0 +1,77 @@
|
|||
#include "OnboardingController.h"
|
||||
|
||||
#include "NewAccountController.h"
|
||||
#include "UserAccount.h"
|
||||
|
||||
#include <StatusGo/SignalsManager.h>
|
||||
|
||||
namespace Status::Onboarding {
|
||||
|
||||
namespace StatusGo = Status::StatusGo;
|
||||
|
||||
OnboardingController::OnboardingController(std::shared_ptr<AccountsServiceInterface> accountsService)
|
||||
: QObject(nullptr)
|
||||
, m_accountsService(std::move(accountsService))
|
||||
{
|
||||
{ // Init accounts
|
||||
std::vector<std::shared_ptr<UserAccount>> accounts;
|
||||
for(auto &account : getOpenedAccounts()) {
|
||||
accounts.push_back(std::make_shared<UserAccount>(std::make_unique<AccountDto>(std::move(account))));
|
||||
}
|
||||
m_accounts = std::make_shared<UserAccountsModel>(std::move(accounts));
|
||||
}
|
||||
|
||||
connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeLogin, this, &OnboardingController::onLogin);
|
||||
}
|
||||
|
||||
OnboardingController::~OnboardingController()
|
||||
{
|
||||
// Here to move instatiation of unique_ptrs into this compilation unit
|
||||
}
|
||||
|
||||
void OnboardingController::onLogin(const QString& error)
|
||||
{
|
||||
if(error.isEmpty())
|
||||
emit accountLoggedIn();
|
||||
else
|
||||
emit accountLoginError(error);
|
||||
}
|
||||
|
||||
std::vector<AccountDto> OnboardingController::getOpenedAccounts() const
|
||||
{
|
||||
return m_accountsService->openAndListAccounts();
|
||||
}
|
||||
|
||||
void OnboardingController::login(QObject* user, const QString& password)
|
||||
{
|
||||
auto account = qobject_cast<UserAccount*>(user);
|
||||
assert(account != nullptr);
|
||||
auto error = m_accountsService->login(account->accountData(), password);
|
||||
if(!error.isEmpty())
|
||||
emit accountLoginError(error);
|
||||
}
|
||||
|
||||
UserAccountsModel *OnboardingController::accounts() const
|
||||
{
|
||||
return m_accounts.get();
|
||||
}
|
||||
|
||||
NewAccountController *OnboardingController::initNewAccountController()
|
||||
{
|
||||
m_newAccountController = std::make_unique<NewAccountController>(m_accountsService);
|
||||
emit newAccountControllerChanged();
|
||||
return m_newAccountController.get();
|
||||
}
|
||||
|
||||
void OnboardingController::terminateNewAccountController()
|
||||
{
|
||||
m_newAccountController.release()->deleteLater();
|
||||
emit newAccountControllerChanged();
|
||||
}
|
||||
|
||||
NewAccountController *OnboardingController::newAccountController() const
|
||||
{
|
||||
return m_newAccountController.get();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include "UserAccountsModel.h"
|
||||
|
||||
#include "Accounts/AccountsServiceInterface.h"
|
||||
#include "Accounts/AccountDto.h"
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QtQmlIntegration>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
class UserAccount;
|
||||
|
||||
class NewAccountController;
|
||||
|
||||
/*!
|
||||
* \todo refactor and remove the requirement to build only shared_ptr instances or use a factory
|
||||
* \todo refactor unnedded multiple inheritance
|
||||
* \todo don't use DTOs in controllers, use QObjects directly
|
||||
* \todo make dependency on SignalManager explicit. Now it is hidden.
|
||||
*/
|
||||
class OnboardingController final : public QObject
|
||||
, public std::enable_shared_from_this<OnboardingController>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Created by Module, for now")
|
||||
|
||||
Q_PROPERTY(UserAccountsModel* accounts READ accounts CONSTANT)
|
||||
Q_PROPERTY(NewAccountController* newAccountController READ newAccountController NOTIFY newAccountControllerChanged)
|
||||
|
||||
public:
|
||||
explicit OnboardingController(std::shared_ptr<AccountsServiceInterface> accountsService);
|
||||
~OnboardingController();
|
||||
|
||||
/// Retrieve available accounts
|
||||
std::vector<AccountDto> getOpenedAccounts() const;
|
||||
|
||||
/// Login user account
|
||||
/// TODO: \a user should be of type \c UserAccount but this doesn't work with Qt6 CMake API. Investigate and fix later on
|
||||
Q_INVOKABLE void login(QObject* user, const QString& password);
|
||||
|
||||
UserAccountsModel *accounts() const;
|
||||
|
||||
Q_INVOKABLE NewAccountController *initNewAccountController();
|
||||
Q_INVOKABLE void terminateNewAccountController();
|
||||
NewAccountController *newAccountController() const;
|
||||
|
||||
signals:
|
||||
void accountLoggedIn();
|
||||
void accountLoginError(const QString& error);
|
||||
void obtainingPasswordError(const QString& errorDescription);
|
||||
void obtainingPasswordSuccess(const QString& password);
|
||||
|
||||
void newAccountControllerChanged();
|
||||
|
||||
private slots:
|
||||
void onLogin(const QString& error);
|
||||
|
||||
private:
|
||||
const UserAccount* getSelectedAccount() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<AccountsServiceInterface> m_accountsService;
|
||||
std::shared_ptr<UserAccountsModel> m_accounts;
|
||||
|
||||
std::unique_ptr<NewAccountController> m_newAccountController;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
#include "OnboardingModule.h"
|
||||
|
||||
#include "Accounts/AccountsService.h"
|
||||
|
||||
#include <ApplicationCore/UserConfiguration.h>
|
||||
|
||||
|
||||
namespace AppCore = Status::ApplicationCore;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Onboarding {
|
||||
|
||||
OnboardingModule::OnboardingModule(const fs::path& userDataPath, QObject *parent)
|
||||
: OnboardingModule{parent}
|
||||
{
|
||||
m_userDataPath = userDataPath;
|
||||
initWithUserDataPath(m_userDataPath);
|
||||
}
|
||||
|
||||
OnboardingModule::OnboardingModule(QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_accountsService(std::make_shared<AccountsService>())
|
||||
{
|
||||
}
|
||||
|
||||
OnboardingController* OnboardingModule::controller() const
|
||||
{
|
||||
return m_controller.get();
|
||||
}
|
||||
|
||||
void OnboardingModule::componentComplete()
|
||||
{
|
||||
try {
|
||||
initWithUserDataPath(m_userDataPath);
|
||||
} catch(const std::exception &e) {
|
||||
qCritical() << "OnboardingModule: failed to initialize";
|
||||
}
|
||||
}
|
||||
|
||||
void OnboardingModule::initWithUserDataPath(const fs::path &path)
|
||||
{
|
||||
auto result = m_accountsService->init(path);
|
||||
if(!result)
|
||||
throw std::runtime_error(std::string("Failed to initialize OnboadingService") + path.string());
|
||||
m_controller = std::make_shared<OnboardingController>(
|
||||
m_accountsService);
|
||||
emit controllerChanged();
|
||||
}
|
||||
|
||||
const QString OnboardingModule::userDataPath() const
|
||||
{
|
||||
return QString::fromStdString(m_userDataPath.string());
|
||||
}
|
||||
|
||||
void OnboardingModule::setUserDataPath(const QString &newUserDataPath)
|
||||
{
|
||||
auto newVal = newUserDataPath.toStdString();
|
||||
if (m_userDataPath.compare(newVal) == 0)
|
||||
return;
|
||||
m_userDataPath = newVal;
|
||||
emit userDataPathChanged();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include "OnboardingController.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QtQmlIntegration>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Onboarding {
|
||||
|
||||
class AccountsService;
|
||||
|
||||
/*!
|
||||
* \brief Provide bootstrap of controllers and corresponding services
|
||||
*
|
||||
* \warning status-go is a stateful library and having multiple insteances of the same module is undefined behaviour
|
||||
* \todo current state is temporary until refactor StatusGo wrapper to match status-go requirements
|
||||
* \warning current state all module spawned/controlled objects have C++ ownership this generate the risk of dangling
|
||||
* invalid QML objects after module is destroyed. Consider moving all the ownership into QML after refactoring
|
||||
*/
|
||||
class OnboardingModule : public QObject, public QQmlParserStatus
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_INTERFACES(QQmlParserStatus)
|
||||
|
||||
Q_PROPERTY(OnboardingController* controller READ controller NOTIFY controllerChanged)
|
||||
Q_PROPERTY(QString userDataPath READ userDataPath WRITE setUserDataPath NOTIFY userDataPathChanged REQUIRED)
|
||||
|
||||
public:
|
||||
explicit OnboardingModule(const fs::path& userDataPath, QObject *parent = nullptr);
|
||||
explicit OnboardingModule(QObject *parent = nullptr);
|
||||
|
||||
OnboardingController* controller() const;
|
||||
|
||||
const QString userDataPath() const;
|
||||
void setUserDataPath(const QString &newUserDataPath);
|
||||
|
||||
/// QML inteface
|
||||
void classBegin() override {};
|
||||
void componentComplete() override;
|
||||
|
||||
signals:
|
||||
void controllerChanged();
|
||||
void userDataPathChanged();
|
||||
|
||||
private:
|
||||
|
||||
/// Throws exceptions
|
||||
void initWithUserDataPath(const fs::path &path);
|
||||
|
||||
// TODO: plain object after refactoring shared_ptr requirement for now
|
||||
std::shared_ptr<AccountsService> m_accountsService;
|
||||
std::shared_ptr<OnboardingController> m_controller;
|
||||
|
||||
fs::path m_userDataPath;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
# Onboarding refactoring
|
||||
|
||||
TODO
|
||||
|
||||
- [ ] Consider moving path requirements, into `StatusGoQt` or unify them as module requirement through abstraction
|
||||
- [ ] Refactor to use typed IDs across Account and Login services instead of plain strings.
|
||||
- A quick workaround would be to add a generic NamedType and convert strings at status-go APIs boundaries
|
||||
- [ ] Bring uniformity to namespace: `Status::<domain>`. Don't go too deep, not deeper than two domain-related namespaces
|
||||
- [ ] Consider RAII for controllers, remove `init`
|
|
@ -0,0 +1,37 @@
|
|||
#include "UserAccount.h"
|
||||
|
||||
#include "Accounts/AccountDto.h"
|
||||
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
UserAccount::UserAccount(std::unique_ptr<AccountDto> data)
|
||||
: QObject()
|
||||
, m_data(std::move(data))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const QString &UserAccount::name() const
|
||||
{
|
||||
return m_data->name;
|
||||
}
|
||||
|
||||
const AccountDto &UserAccount::accountData() const
|
||||
{
|
||||
return *m_data;
|
||||
}
|
||||
|
||||
void UserAccount::updateAccountData(const AccountDto& newData)
|
||||
{
|
||||
std::vector<std::function<void()>> notifyUpdates;
|
||||
|
||||
*m_data = newData;
|
||||
|
||||
if(newData.name != m_data->name)
|
||||
notifyUpdates.push_back([this]() { emit nameChanged(); });
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <QtQmlIntegration>
|
||||
|
||||
namespace Status::Onboarding
|
||||
{
|
||||
|
||||
class AccountDto;
|
||||
|
||||
/*!
|
||||
* \brief Represents a user account in Onboarding Presentation Layer
|
||||
*
|
||||
* @see OnboardingController
|
||||
* @see UserAccountsModel
|
||||
*/
|
||||
class UserAccount: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Created by Controller")
|
||||
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
public:
|
||||
explicit UserAccount(std::unique_ptr<AccountDto> data);
|
||||
|
||||
const QString &name() const;
|
||||
|
||||
const AccountDto& accountData() const;
|
||||
void updateAccountData(const AccountDto& newData);
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
|
||||
private:
|
||||
std::unique_ptr<AccountDto> m_data;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#include "UserAccountsModel.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace Status::Onboarding {
|
||||
|
||||
|
||||
UserAccountsModel::UserAccountsModel(const std::vector<std::shared_ptr<UserAccount>> accounts, QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_accounts(std::move(accounts))
|
||||
{
|
||||
}
|
||||
|
||||
UserAccountsModel::~UserAccountsModel()
|
||||
{
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> UserAccountsModel::roleNames() const
|
||||
{
|
||||
static QHash<int, QByteArray> roles{
|
||||
{Name, "name"},
|
||||
{Account, "account"}
|
||||
};
|
||||
return roles;
|
||||
}
|
||||
|
||||
int UserAccountsModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_accounts.size();
|
||||
}
|
||||
|
||||
QVariant UserAccountsModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if(!QAbstractItemModel::checkIndex(index))
|
||||
return QVariant();
|
||||
|
||||
switch(static_cast<ModelRole>(role)) {
|
||||
case Name: return QVariant::fromValue(m_accounts[index.row()].get()->name());
|
||||
case Account: return QVariant::fromValue<QObject*>(m_accounts[index.row()].get());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "UserAccount.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
namespace Status::Onboarding {
|
||||
|
||||
/*!
|
||||
* \brief Available UserAccount elements
|
||||
*/
|
||||
class UserAccountsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("Created by OnboardingController")
|
||||
|
||||
enum ModelRole {
|
||||
Name = Qt::UserRole + 1,
|
||||
Account
|
||||
};
|
||||
public:
|
||||
|
||||
explicit UserAccountsModel(const std::vector<std::shared_ptr<UserAccount>> accounts, QObject* parent = nullptr);
|
||||
~UserAccountsModel();
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual QVariant data(const QModelIndex& index, int role) const override;
|
||||
|
||||
private:
|
||||
const std::vector<std::shared_ptr<UserAccount>> m_accounts;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
# Unit and interface tests
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(TestOnboarding VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
test_AccountService.cpp
|
||||
test_OnboardingController.cpp
|
||||
test_OnboardingModule.cpp
|
||||
|
||||
ServiceMock.h
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
add_subdirectory(OnboardingTestHelpers)
|
||||
add_subdirectory(qml_tests)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
Qt6::Core
|
||||
|
||||
GTest::gtest
|
||||
GTest::gmock
|
||||
GTest::gtest_main
|
||||
|
||||
Status::TestHelpers
|
||||
Status::ApplicationCore
|
||||
|
||||
Status::OnboardingTestHelpers
|
||||
Status::Onboarding
|
||||
|
||||
# TODO tmp
|
||||
Status::StatusGoQt
|
||||
)
|
||||
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_add_tests(
|
||||
TARGET ${PROJECT_NAME}
|
||||
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
)
|
|
@ -0,0 +1,50 @@
|
|||
# Base library. Expect most of the module libraries to depend on it
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(OnboardingTestHelpers
|
||||
VERSION 0.1.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(GTest REQUIRED)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
URI Status.${PROJECT_NAME}
|
||||
VERSION 1.0
|
||||
)
|
||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScopedTestAccount.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Constants.h
|
||||
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ScopedTestAccount.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
Qt6::Quick
|
||||
Qt6::Qml
|
||||
|
||||
PRIVATE
|
||||
Status::TestHelpers
|
||||
Status::ApplicationCore
|
||||
Status::Onboarding
|
||||
Status::StatusGoQt
|
||||
|
||||
GTest::gtest
|
||||
)
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Status::Testing::Constants {
|
||||
|
||||
inline constexpr auto userDataDirName = "StatusTest";
|
||||
inline constexpr auto statusGoDataDirName = "data";
|
||||
inline constexpr auto tmpDataDirName = "tmp";
|
||||
inline constexpr auto logsDataDirName = "logs";
|
||||
inline constexpr auto qtDataDirName = "qt";
|
||||
inline constexpr auto keystoreDataDirName = "keystore";
|
||||
inline constexpr auto globalSettingsFileName = "global";
|
||||
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
#include "ScopedTestAccount.h"
|
||||
|
||||
#include <Constants.h>
|
||||
|
||||
#include <IOTestHelpers.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsService.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Testing = Status::Testing;
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
ScopedTestAccount::ScopedTestAccount(const std::string &tempTestSubfolderName, const QString &accountName, const QString &accountPassword, bool ignorePreviousState)
|
||||
: m_fusedTestFolder{std::make_unique<AutoCleanTempTestDir>(tempTestSubfolderName)}
|
||||
, m_accountName(accountName)
|
||||
, m_accountPassword(accountPassword)
|
||||
{
|
||||
int argc = 1;
|
||||
std::string appName{"test"};
|
||||
char* args[] = {appName.data()};
|
||||
m_app = std::make_unique<QCoreApplication>(argc, reinterpret_cast<char**>(args));
|
||||
|
||||
m_testFolderPath = m_fusedTestFolder->tempFolder() / Constants::statusGoDataDirName;
|
||||
fs::create_directory(m_testFolderPath);
|
||||
|
||||
// Setup accounts
|
||||
auto accountsService = std::make_shared<Onboarding::AccountsService>();
|
||||
auto result = accountsService->init(m_testFolderPath);
|
||||
if(!result)
|
||||
throw std::runtime_error("ScopedTestAccount - Failed to create temporary test account");
|
||||
|
||||
// TODO refactor and merge account creation events with login into Onboarding controller
|
||||
//
|
||||
// Create Login early to register and not miss onLoggedIn event signal from setupAccountAndLogin
|
||||
//
|
||||
|
||||
// Beware, smartpointer is a requirement
|
||||
m_onboarding = std::make_shared<Onboarding::OnboardingController>(accountsService);
|
||||
if(m_onboarding->getOpenedAccounts().size() != 0 && !ignorePreviousState)
|
||||
throw std::runtime_error("ScopedTestAccount - already have opened account");
|
||||
|
||||
int accountLoggedInCount = 0;
|
||||
QObject::connect(m_onboarding.get(), &Onboarding::OnboardingController::accountLoggedIn, [&accountLoggedInCount]() {
|
||||
accountLoggedInCount++;
|
||||
});
|
||||
bool accountLoggedInError = false;
|
||||
QObject::connect(m_onboarding.get(), &Onboarding::OnboardingController::accountLoginError, [&accountLoggedInError]() {
|
||||
accountLoggedInError = true;
|
||||
});
|
||||
|
||||
// Create Accounts
|
||||
auto genAccounts = accountsService->generatedAccounts();
|
||||
if(genAccounts.size() == 0)
|
||||
throw std::runtime_error("ScopedTestAccount - missing generated accounts");
|
||||
|
||||
if(accountsService->isFirstTimeAccountLogin())
|
||||
throw std::runtime_error("ScopedTestAccount - Service::isFirstTimeAccountLogin returned true");
|
||||
|
||||
if(!accountsService->setupAccountAndLogin(genAccounts[0].id, m_accountPassword, m_accountName))
|
||||
throw std::runtime_error("ScopedTestAccount - Service::setupAccountAndLogin failed");
|
||||
|
||||
if(!accountsService->isFirstTimeAccountLogin())
|
||||
throw std::runtime_error("ScopedTestAccount - Service::isFirstTimeAccountLogin returned false");
|
||||
if(!accountsService->getLoggedInAccount().isValid())
|
||||
throw std::runtime_error("ScopedTestAccount - newly created account is not valid");
|
||||
if(accountsService->getLoggedInAccount().name != accountName)
|
||||
throw std::runtime_error("ScopedTestAccount - newly created account has a wrong name");
|
||||
processMessages(2000, [accountLoggedInCount]() {
|
||||
return accountLoggedInCount == 0;
|
||||
});
|
||||
if(accountLoggedInCount != 1)
|
||||
throw std::runtime_error("ScopedTestAccount - missing confirmation of account creation");
|
||||
if(accountLoggedInError)
|
||||
throw std::runtime_error("ScopedTestAccount - account loggedin error");
|
||||
}
|
||||
|
||||
ScopedTestAccount::~ScopedTestAccount()
|
||||
{
|
||||
}
|
||||
|
||||
void ScopedTestAccount::processMessages(size_t maxWaitTimeMillis, std::function<bool()> shouldWaitUntilTimeout) {
|
||||
using namespace std::chrono_literals;
|
||||
std::chrono::milliseconds maxWaitTime{maxWaitTimeMillis};
|
||||
auto iterationSleepTime = 2ms;
|
||||
auto remainingIterations = maxWaitTime/iterationSleepTime;
|
||||
while (remainingIterations-- > 0 && shouldWaitUntilTimeout()) {
|
||||
std::this_thread::sleep_for(iterationSleepTime);
|
||||
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
}
|
||||
|
||||
void ScopedTestAccount::logOut()
|
||||
{
|
||||
if(Status::StatusGo::Accounts::logout().containsError())
|
||||
throw std::runtime_error("ScopedTestAccount - failed logging out");
|
||||
}
|
||||
|
||||
Onboarding::OnboardingController *ScopedTestAccount::onboardingController() const
|
||||
{
|
||||
return m_onboarding.get();
|
||||
}
|
||||
|
||||
const std::filesystem::path &ScopedTestAccount::fusedTestFolder() const
|
||||
{
|
||||
return m_testFolderPath;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QCoreApplication;
|
||||
|
||||
namespace Status::Onboarding {
|
||||
class OnboardingController;
|
||||
}
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
class AutoCleanTempTestDir;
|
||||
|
||||
class ScopedTestAccount final {
|
||||
public:
|
||||
/*!
|
||||
* \brief Create and logs in a new test account
|
||||
* \param tempTestSubfolderName subfolder name of the temporary test folder where to initalize user data \see AutoCleanTempTestDir
|
||||
* \todo make it more flexible by splitting into create account, login and wait for events
|
||||
*/
|
||||
explicit ScopedTestAccount(const std::string &tempTestSubfolderName,
|
||||
const QString &accountName = defaultAccountName,
|
||||
const QString &accountPassword = defaultAccountPassword,
|
||||
bool ignorePreviousState = false /*workaround to status-go persisting state*/);
|
||||
~ScopedTestAccount();
|
||||
|
||||
void processMessages(size_t millis, std::function<bool()> shouldWaitUntilTimeout);
|
||||
void logOut();
|
||||
|
||||
Status::Onboarding::OnboardingController* onboardingController() const;
|
||||
|
||||
const std::filesystem::path& fusedTestFolder() const;;
|
||||
|
||||
private:
|
||||
std::unique_ptr<AutoCleanTempTestDir> m_fusedTestFolder;
|
||||
std::unique_ptr<QCoreApplication> m_app;
|
||||
std::filesystem::path m_testFolderPath;
|
||||
std::shared_ptr<Status::Onboarding::OnboardingController> m_onboarding;
|
||||
std::function<bool()> m_checkIfShouldContinue;
|
||||
|
||||
QString m_accountName;
|
||||
QString m_accountPassword;
|
||||
|
||||
static constexpr auto defaultAccountName = "test_name";
|
||||
static constexpr auto defaultAccountPassword = "test_pwd*";
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "Onboarding/Accounts/AccountsServiceInterface.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
|
||||
namespace Status::Testing
|
||||
{
|
||||
|
||||
/*!
|
||||
* \brief The AccountsServiceMock test class
|
||||
*
|
||||
* \todo Consider if this is really neaded for testing controllers
|
||||
* \todo Move it to mocks subfolder
|
||||
*/
|
||||
class AccountsServiceMock final : public Onboarding::AccountsServiceInterface
|
||||
{
|
||||
public:
|
||||
virtual ~AccountsServiceMock() override {};
|
||||
|
||||
MOCK_METHOD(bool, init, (const fs::path&), (override));
|
||||
MOCK_METHOD(std::vector<Onboarding::AccountDto>, openAndListAccounts, (), (override));
|
||||
MOCK_METHOD(const std::vector<Onboarding::GeneratedAccountDto>&, generatedAccounts, (), (const, override));
|
||||
MOCK_METHOD(bool, setupAccountAndLogin, (const QString&, const QString&, const QString&), (override));
|
||||
MOCK_METHOD(const Onboarding::AccountDto&, getLoggedInAccount, (), (const, override));
|
||||
MOCK_METHOD(const Onboarding::GeneratedAccountDto&, getImportedAccount, (), (const, override));
|
||||
MOCK_METHOD(bool, isFirstTimeAccountLogin, (), (const, override));
|
||||
MOCK_METHOD(bool, setKeyStoreDir, (const QString&), (override));
|
||||
MOCK_METHOD(QString, login, (Onboarding::AccountDto, const QString&), (override));
|
||||
MOCK_METHOD(void, clear, (), (override));
|
||||
MOCK_METHOD(QString, generateAlias, (const QString&), (override));
|
||||
MOCK_METHOD(QString, generateIdenticon, (const QString&), (override));
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(TestOnboardingQml LANGUAGES CXX)
|
||||
|
||||
enable_testing(true)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml QuickTest REQUIRED)
|
||||
|
||||
add_executable(TestOnboardingQml
|
||||
"main.cpp"
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# no need to copy around qml test files for shadow builds - just set the respective define
|
||||
add_compile_definitions(QUICK_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
add_test(NAME TestOnboardingQml WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/TestOnboardingQml -input "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
add_custom_target("Run_TestOnboardingQml" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
add_dependencies("Run_TestOnboardingQml" TestOnboardingQml)
|
||||
|
||||
target_link_libraries(TestOnboardingQml PRIVATE
|
||||
Qt6::QuickTest
|
||||
Qt6::Qml
|
||||
Qt6::Quick
|
||||
|
||||
Status::TestHelpers
|
||||
|
||||
Status::Onboarding
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
#include <QtQuickTest>
|
||||
|
||||
QUICK_TEST_MAIN(TestOnboardingQml)
|
|
@ -0,0 +1,86 @@
|
|||
import QtQuick
|
||||
import QtQml
|
||||
import QtTest
|
||||
|
||||
import Status.Onboarding
|
||||
|
||||
import Status.TestHelpers
|
||||
|
||||
/**!
|
||||
* \todo use mocked values
|
||||
*/
|
||||
Item {
|
||||
id: root
|
||||
width: 400
|
||||
height: 300
|
||||
|
||||
Component {
|
||||
id: onboardingDepsComponent
|
||||
|
||||
Item {
|
||||
OnboardingModule {
|
||||
id: module
|
||||
|
||||
userDataPath: "/tmp/StatusTests/demo"
|
||||
}
|
||||
|
||||
// TODO: fix error "unable to assign Status::Onboarding::OnboardingController to Status::Onboarding::OnboardingController" then enable typed properties
|
||||
readonly property var /*OnboardingController*/ controller: module.controller
|
||||
readonly property var /*UserAccountsModel*/ accounts: controller.accounts
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: testLoader
|
||||
|
||||
anchors.fill: parent
|
||||
active: false
|
||||
}
|
||||
|
||||
TestCase {
|
||||
id: qmlWarningsTest
|
||||
|
||||
name: "TestQmlWarnings"
|
||||
|
||||
when: windowShown
|
||||
|
||||
//
|
||||
// Test guards
|
||||
|
||||
function init() {
|
||||
qtOuput.restartCapturing()
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
testLoader.active = false
|
||||
}
|
||||
|
||||
//
|
||||
// Tests
|
||||
|
||||
function test_moduleInitialization() {
|
||||
testLoader.sourceComponent = onboardingDepsComponent
|
||||
testLoader.active = true
|
||||
verify(waitForRendering(testLoader.item))
|
||||
testLoader.active = false
|
||||
verify(qtOuput.qtOuput().length === 0, `No output expected. Found:\n"${qtOuput.qtOuput()}"\n`)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCase {
|
||||
// id: qmlBenchmarks
|
||||
|
||||
// name: "QmlBenchmarks"
|
||||
|
||||
// function benchmark_loadAndUnloadModule() {
|
||||
// skip("Enable benchmarking after integrating it with reporting in CI")
|
||||
// testLoader.sourceComponent = onboardingDepsComponent
|
||||
// testLoader.active = true
|
||||
// testLoader.active = false
|
||||
// }
|
||||
// }
|
||||
|
||||
MonitorQtOutput {
|
||||
id: qtOuput
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#include "ServiceMock.h"
|
||||
|
||||
#include <IOTestHelpers.h>
|
||||
#include <Constants.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsService.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Testing = Status::Testing;
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
class AccountsServicesTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
std::unique_ptr<Onboarding::AccountsService> m_accountsService;
|
||||
std::unique_ptr<Testing::AutoCleanTempTestDir> m_fusedTestFolder;
|
||||
|
||||
void SetUp() override {
|
||||
m_fusedTestFolder = std::make_unique<Testing::AutoCleanTempTestDir>("AccountsServicesTest");
|
||||
m_accountsService = std::make_unique<Onboarding::AccountsService>();
|
||||
m_accountsService->init(m_fusedTestFolder->tempFolder() / Constants::statusGoDataDirName);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
m_fusedTestFolder.reset();
|
||||
m_accountsService.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_F(AccountsServicesTest, GeneratedAccounts)
|
||||
{
|
||||
auto genAccounts = m_accountsService->generatedAccounts();
|
||||
|
||||
ASSERT_EQ(5, genAccounts.size());
|
||||
|
||||
for(const auto& acc : genAccounts)
|
||||
{
|
||||
ASSERT_STRNE(qUtf8Printable(acc.id), "");
|
||||
ASSERT_STRNE(qUtf8Printable(acc.publicKey), "");
|
||||
ASSERT_STRNE(qUtf8Printable(acc.address), "");
|
||||
ASSERT_STRNE(qUtf8Printable(acc.keyUid), "");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AccountsServicesTest, DISABLED_GenerateAlias) // temporary disabled till we see what's happening on the status-go side since it doesn't return aliases for any pk
|
||||
{
|
||||
QString testPubKey = "0x04487f44bac3e90825bfa9720148308cb64835bebb7e888f519cebc127223187067629f8b70d0661a35d4af6516b225286";
|
||||
|
||||
auto alias = m_accountsService->generateAlias(testPubKey);
|
||||
|
||||
ASSERT_NE(alias, QString(""));
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,55 @@
|
|||
#include "ServiceMock.h"
|
||||
|
||||
#include <Constants.h>
|
||||
|
||||
#include <IOTestHelpers.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsService.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
class LoginTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
static std::shared_ptr<AccountsServiceMock> m_accountsServiceMock;
|
||||
|
||||
std::unique_ptr<Onboarding::AccountsService> m_accountsService;
|
||||
std::unique_ptr<Testing::AutoCleanTempTestDir> m_fusedTestFolder;
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
m_accountsServiceMock = std::make_shared<AccountsServiceMock>();
|
||||
}
|
||||
static void TearDownTestSuite() {
|
||||
m_accountsServiceMock.reset();
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
m_fusedTestFolder = std::make_unique<Testing::AutoCleanTempTestDir>("LoginTest");
|
||||
m_accountsService = std::make_unique<Onboarding::AccountsService>();
|
||||
m_accountsService->init(m_fusedTestFolder->tempFolder() / Constants::statusGoDataDirName);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
m_fusedTestFolder.release();
|
||||
m_accountsService.release();
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<AccountsServiceMock> LoginTest::m_accountsServiceMock;
|
||||
|
||||
TEST_F(LoginTest, DISABLED_TestLoginController)
|
||||
{
|
||||
// Controller hides as a regular class but at runtime it must be a shared pointer; TODO: refactor
|
||||
auto controller = std::make_shared<Onboarding::OnboardingController>(m_accountsServiceMock);
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -0,0 +1,170 @@
|
|||
#include <IOTestHelpers.h>
|
||||
|
||||
#include "ServiceMock.h"
|
||||
|
||||
#include <Constants.h>
|
||||
|
||||
#include <Onboarding/Accounts/AccountsService.h>
|
||||
#include <Onboarding/OnboardingController.h>
|
||||
|
||||
#include <StatusGo/SignalsManager.h>
|
||||
#include <StatusGo/Accounts/Accounts.h>
|
||||
|
||||
#include <ScopedTestAccount.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace Testing = Status::Testing;
|
||||
namespace Onboarding = Status::Onboarding;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Status::Testing {
|
||||
|
||||
static std::unique_ptr<Onboarding::AccountsService> m_accountsServiceMock;
|
||||
|
||||
TEST(OnboardingModule, TestInitService)
|
||||
{
|
||||
Testing::AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
auto testFolderPath = fusedTestFolder.tempFolder() / Constants::statusGoDataDirName;
|
||||
fs::create_directory(testFolderPath);
|
||||
auto accountsService = std::make_unique<Onboarding::AccountsService>();
|
||||
ASSERT_TRUE(accountsService->init(testFolderPath));
|
||||
}
|
||||
|
||||
/// This integration end to end test is here for documentation purpose and until all the functionality is covered by unit-tests
|
||||
/// \warning the test depends on IO and it is not deterministic, fast, focused or reliable and uses production classes. It is here for documenting only and dev process
|
||||
/// \todo refactor into unit-tests with mocked interfaces
|
||||
TEST(OnboardingModule, TestCreateAndLoginAccountEndToEnd)
|
||||
{
|
||||
int argc = 1;
|
||||
std::string appName{"test"};
|
||||
char* args[] = {appName.data()};
|
||||
QCoreApplication dummyApp{argc, reinterpret_cast<char**>(args)};
|
||||
|
||||
Testing::AutoCleanTempTestDir fusedTestFolder{test_info_->name()};
|
||||
auto testFolderPath = fusedTestFolder.tempFolder() / "Status Desktop";
|
||||
fs::create_directory(testFolderPath);
|
||||
|
||||
// Setup accounts
|
||||
auto accountsService = std::make_shared<Onboarding::AccountsService>();
|
||||
auto result = accountsService->init(testFolderPath);
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
// TODO refactor and merge account creation events with login into Onboarding controller
|
||||
//
|
||||
// Create Login early to register and not miss onLoggedIn event signal from setupAccountAndLogin
|
||||
//
|
||||
|
||||
// Beware, smartpointer is a requirement
|
||||
auto onboarding = std::make_shared<Onboarding::OnboardingController>(accountsService);
|
||||
EXPECT_EQ(onboarding->getOpenedAccounts().size(), 0);
|
||||
|
||||
int accountLoggedInCount = 0;
|
||||
QObject::connect(onboarding.get(), &Onboarding::OnboardingController::accountLoggedIn, [&accountLoggedInCount]() {
|
||||
accountLoggedInCount++;
|
||||
});
|
||||
bool accountLoggedInError = false;
|
||||
QObject::connect(onboarding.get(), &Onboarding::OnboardingController::accountLoginError, [&accountLoggedInError]() {
|
||||
accountLoggedInError = true;
|
||||
});
|
||||
|
||||
// Create Accounts
|
||||
auto genAccounts = accountsService->generatedAccounts();
|
||||
ASSERT_GT(genAccounts.size(), 0);
|
||||
|
||||
ASSERT_FALSE(accountsService->isFirstTimeAccountLogin());
|
||||
|
||||
constexpr auto accountName = "test_name";
|
||||
constexpr auto accountPassword = "test_pwd*";
|
||||
ASSERT_TRUE(accountsService->setupAccountAndLogin(genAccounts[0].id, accountPassword, accountName));
|
||||
|
||||
ASSERT_TRUE(accountsService->isFirstTimeAccountLogin());
|
||||
ASSERT_TRUE(accountsService->getLoggedInAccount().isValid());
|
||||
ASSERT_TRUE(accountsService->getLoggedInAccount().name == accountName);
|
||||
ASSERT_FALSE(accountsService->getImportedAccount().isValid());
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
auto maxWaitTime = 2000ms;
|
||||
auto iterationSleepTime = 2ms;
|
||||
auto remainingIterations = maxWaitTime/iterationSleepTime;
|
||||
while (remainingIterations-- > 0 && accountLoggedInCount == 0) {
|
||||
std::this_thread::sleep_for(iterationSleepTime);
|
||||
|
||||
QCoreApplication::sendPostedEvents();
|
||||
}
|
||||
|
||||
EXPECT_EQ(accountLoggedInCount, 1);
|
||||
EXPECT_FALSE(accountLoggedInError);
|
||||
EXPECT_FALSE(Status::StatusGo::Accounts::logout().containsError());
|
||||
}
|
||||
|
||||
/// This integration end to end test is here for documentation purpose and until all the functionality is covered by unit-tests
|
||||
/// \warning the test depends on IO and it is not deterministic, fast, focused or reliable. It is here for validation only
|
||||
/// \todo find a way to test the integration within a test environment. Also how about reusing an existing account
|
||||
/// \todo due to keeping status-go keeping the state thsi works only run separately
|
||||
TEST(OnboardingModule, TestLoginEndToEnd)
|
||||
{
|
||||
// Create test account and login
|
||||
//
|
||||
bool createAndLogin = false;
|
||||
QObject::connect(StatusGo::SignalsManager::instance(), &StatusGo::SignalsManager::nodeLogin, [&createAndLogin](const QString& error) {
|
||||
if(error.isEmpty()) {
|
||||
if(createAndLogin) {
|
||||
createAndLogin = false;
|
||||
} else
|
||||
createAndLogin = true;
|
||||
}
|
||||
});
|
||||
|
||||
constexpr auto accountName = "TestLoginAccountName";
|
||||
constexpr auto accountPassword = "1234567890";
|
||||
ScopedTestAccount testAccount(test_info_->name(), accountName, accountPassword, true);
|
||||
testAccount.processMessages(1000, [createAndLogin]() {
|
||||
return !createAndLogin;
|
||||
});
|
||||
ASSERT_TRUE(createAndLogin);
|
||||
|
||||
testAccount.logOut();
|
||||
|
||||
// Test account log in
|
||||
//
|
||||
|
||||
// Setup accounts
|
||||
auto accountsService = std::make_shared<Onboarding::AccountsService>();
|
||||
auto result = accountsService->init(testAccount.fusedTestFolder());
|
||||
ASSERT_TRUE(result);
|
||||
|
||||
auto onboarding = std::make_shared<Onboarding::OnboardingController>(accountsService);
|
||||
// We don't have a way yet to simulate status-go process exit
|
||||
//EXPECT_EQ(onboarding->getOpenedAccounts().count(), 0);
|
||||
|
||||
auto accounts = accountsService->openAndListAccounts();
|
||||
//ASSERT_EQ(accounts.size(), 1);
|
||||
ASSERT_GT(accounts.size(), 0);
|
||||
|
||||
int accountLoggedInCount = 0;
|
||||
QObject::connect(onboarding.get(), &Onboarding::OnboardingController::accountLoggedIn, [&accountLoggedInCount]() {
|
||||
accountLoggedInCount++;
|
||||
});
|
||||
bool accountLoggedInError = false;
|
||||
QObject::connect(onboarding.get(), &Onboarding::OnboardingController::accountLoginError, [&accountLoggedInError]() {
|
||||
accountLoggedInError = true;
|
||||
});
|
||||
|
||||
//auto errorString = accountsService->login(accounts[0], accountPassword);
|
||||
// Workaround until we reset the status-go state
|
||||
auto ourAccountRes = std::find_if(accounts.begin(), accounts.end(), [accountName](const auto &a) { return a.name == accountName; });
|
||||
auto errorString = accountsService->login(*ourAccountRes, accountPassword);
|
||||
ASSERT_EQ(errorString.length(), 0);
|
||||
|
||||
testAccount.processMessages(1000, [accountLoggedInCount, accountLoggedInError]() {
|
||||
return accountLoggedInCount == 0 && !accountLoggedInError;
|
||||
});
|
||||
ASSERT_EQ(accountLoggedInCount, 1);
|
||||
ASSERT_EQ(accountLoggedInError, 0);
|
||||
}
|
||||
|
||||
} // namespace
|
|
@ -8,10 +8,10 @@ project(StatusGoQt
|
|||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core REQUIRED)
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Core Concurrent REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED "")
|
||||
add_library(${PROJECT_NAME} SHARED)
|
||||
|
||||
# Use by linker only
|
||||
set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
||||
|
@ -19,19 +19,30 @@ set_property(GLOBAL PROPERTY DEBUG_CONFIGURATIONS Debug)
|
|||
add_subdirectory(src)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Concurrent
|
||||
|
||||
PRIVATE
|
||||
statusgo_shared
|
||||
)
|
||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Copy status-go lib close to the executable
|
||||
# Temporary workaround; TODO: see a better alternative that doesn't depend on target order (dedicated dependencies dir?)
|
||||
# and current directory (on mac). Use bundle or set rpath relative to executable
|
||||
get_target_property(STATUSGO_LIBRARY_PATH statusgo_shared IMPORTED_LOCATION)
|
||||
configure_file(${STATUSGO_LIBRARY_PATH} ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
|
||||
add_custom_command(
|
||||
TARGET
|
||||
${PROJECT_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
$<TARGET_FILE:statusgo_shared>
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
COMMENT "Copying status-go lib beside project executable"
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
${PROJECT_NAME}
|
||||
RUNTIME
|
||||
IMPORTED_RUNTIME_ARTIFACTS
|
||||
statusgo_shared
|
||||
)
|
|
@ -1,14 +1,14 @@
|
|||
#include "StatusBackend/Accounts.h"
|
||||
#include "Accounts.h"
|
||||
|
||||
#include "StatusBackend/Utils.h"
|
||||
#include "Utils.h"
|
||||
#include "libstatus.h"
|
||||
|
||||
const int NUMBER_OF_ADDRESSES_TO_GENERATE = 5;
|
||||
const int MNEMONIC_PHRASE_LENGTH = 12;
|
||||
|
||||
using namespace Backend;
|
||||
namespace Status::StatusGo::Accounts {
|
||||
|
||||
RpcResponse<QJsonArray> Accounts::generateAddresses(const QVector<QString>& paths)
|
||||
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"n", NUMBER_OF_ADDRESSES_TO_GENERATE},
|
||||
|
@ -27,7 +27,7 @@ RpcResponse<QJsonArray> Accounts::generateAddresses(const QVector<QString>& path
|
|||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(jsonResult);
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ RpcResponse<QJsonArray> Accounts::generateAddresses(const QVector<QString>& path
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QString> Accounts::generateIdenticon(const QString& publicKey)
|
||||
RpcResponse<QString> generateIdenticon(const QString& publicKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -52,7 +52,7 @@ RpcResponse<QString> Accounts::generateIdenticon(const QString& publicKey)
|
|||
{
|
||||
identicon = Identicon(publicKey.toUtf8().data());
|
||||
}
|
||||
return Utils::buildJsonRpcResponse(identicon);
|
||||
return Utils::buildPrivateRPCResponse(identicon);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -62,7 +62,7 @@ RpcResponse<QString> Accounts::generateIdenticon(const QString& publicKey)
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QString> Accounts::generateAlias(const QString& publicKey)
|
||||
RpcResponse<QString> generateAlias(const QString& publicKey)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -72,7 +72,7 @@ RpcResponse<QString> Accounts::generateAlias(const QString& publicKey)
|
|||
alias = GenerateAlias(publicKey.toUtf8().data());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(alias);
|
||||
return Utils::buildPrivateRPCResponse(alias);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -82,7 +82,7 @@ RpcResponse<QString> Accounts::generateAlias(const QString& publicKey)
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> Accounts::storeDerivedAccounts(const QString& id, const QString& hashedPassword,
|
||||
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& id, const QString& hashedPassword,
|
||||
const QVector<QString>& paths)
|
||||
{
|
||||
QJsonObject payload{
|
||||
|
@ -101,7 +101,9 @@ RpcResponse<QJsonObject> Accounts::storeDerivedAccounts(const QString& id, const
|
|||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(jsonResult);
|
||||
RpcResponse<QJsonObject> rpcResponse(jsonResult);
|
||||
rpcResponse.error = Utils::getRPCErrorInJson(jsonResult).value_or(RpcError());
|
||||
return rpcResponse;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -117,17 +119,16 @@ RpcResponse<QJsonObject> Accounts::storeDerivedAccounts(const QString& id, const
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> Accounts::saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
const QJsonArray& subaccounts, const QJsonObject& settings,
|
||||
const QJsonObject& nodeConfig)
|
||||
RpcResponse<QJsonObject> storeAccount(const QString& id, const QString& hashedPassword)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"accountID", id},
|
||||
{"password", hashedPassword}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
auto result = SaveAccountAndLogin(Utils::jsonToByteArray(std::move(account)).data(),
|
||||
hashedPassword.toUtf8().data(),
|
||||
Utils::jsonToByteArray(std::move(settings)).data(),
|
||||
Utils::jsonToByteArray(std::move(nodeConfig)).data(),
|
||||
Utils::jsonToByteArray(std::move(subaccounts)).data());
|
||||
auto result = MultiAccountStoreAccount(Utils::jsonToByteArray(std::move(payload)).data());
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
|
@ -135,39 +136,70 @@ RpcResponse<QJsonObject> Accounts::saveAccountAndLogin(const QString& hashedPass
|
|||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(jsonResult);
|
||||
RpcResponse<QJsonObject> rpcResponse(jsonResult);
|
||||
rpcResponse.error = Utils::getRPCErrorInJson(jsonResult).value_or(RpcError());
|
||||
return rpcResponse;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error saving account and login occurred, msg: %1").arg(e.what());
|
||||
response.error.message = QObject::tr("an error storing account occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error saving account and login occurred");
|
||||
response.error.message = QObject::tr("an error storing account occurred");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
Backend::RpcResponse<QJsonArray> Backend::Accounts::openAccounts(const QString& path)
|
||||
bool saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
const QJsonArray& subaccounts, const QJsonObject& settings,
|
||||
const QJsonObject& nodeConfig)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = OpenAccounts(path.toUtf8().data());
|
||||
QJsonArray jsonResult;
|
||||
auto result = SaveAccountAndLogin(Utils::jsonToByteArray(account).data(),
|
||||
hashedPassword.toUtf8().data(),
|
||||
Utils::jsonToByteArray(settings).data(),
|
||||
Utils::jsonToByteArray(nodeConfig).data(),
|
||||
Utils::jsonToByteArray(subaccounts).data());
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
auto msg = QObject::tr("parsing response failed");
|
||||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(jsonResult);
|
||||
return !Utils::getRPCErrorInJson(jsonResult).has_value();
|
||||
} catch (std::exception& e) {
|
||||
qWarning() << QString("an error saving account and login occurred, msg: %1").arg(e.what());
|
||||
} catch (...) {
|
||||
qWarning() << "an error saving account and login occurred";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
RpcResponse<QJsonArray> openAccounts(const char* dataDirPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = QString(OpenAccounts(const_cast<char*>(dataDirPath)));
|
||||
if(result == "null")
|
||||
return RpcResponse<QJsonArray>(QJsonArray());
|
||||
|
||||
QJsonArray jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult)) {
|
||||
throw std::domain_error("parsing response failed");
|
||||
}
|
||||
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
auto response = RpcResponse<QJsonArray>(QJsonArray());
|
||||
// TODO: don't translate exception messages. Exceptions are for developers and should never reach users
|
||||
response.error.message = QObject::tr("an error opening accounts occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
|
@ -179,14 +211,13 @@ Backend::RpcResponse<QJsonArray> Backend::Accounts::openAccounts(const QString&
|
|||
}
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> Accounts::login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"name", name},
|
||||
{"key-uid", keyUid},
|
||||
{"identityImage", QJsonValue()},
|
||||
{"identicon", identicon}
|
||||
{"identityImage", QJsonValue()}
|
||||
};
|
||||
|
||||
if(!thumbnail.isEmpty() && !large.isEmpty())
|
||||
|
@ -196,7 +227,8 @@ RpcResponse<QJsonObject> Accounts::login(const QString& name, const QString& key
|
|||
|
||||
try
|
||||
{
|
||||
auto result = Login(Utils::jsonToByteArray(std::move(payload)).data(), hashedPassword.toUtf8().data());
|
||||
auto payloadData = Utils::jsonToByteArray(std::move(payload));
|
||||
auto result = Login(payloadData.data(), hashedPassword.toUtf8().data());
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
|
@ -204,7 +236,7 @@ RpcResponse<QJsonObject> Accounts::login(const QString& name, const QString& key
|
|||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildJsonRpcResponse(jsonResult);
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -219,3 +251,78 @@ RpcResponse<QJsonObject> Accounts::login(const QString& name, const QString& key
|
|||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
||||
const QJsonObject& nodeConfig)
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"name", name},
|
||||
{"key-uid", keyUid},
|
||||
{"identityImage", QJsonValue()},
|
||||
};
|
||||
|
||||
if(!thumbnail.isEmpty() && !large.isEmpty())
|
||||
{
|
||||
payload["identityImage"] = QJsonObject{{"thumbnail", thumbnail}, {"large", large}};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto payloadData = Utils::jsonToByteArray(std::move(payload));
|
||||
auto nodeConfigData = Utils::jsonToByteArray(nodeConfig);
|
||||
auto result = LoginWithConfig(payloadData.data(), hashedPassword.toUtf8().data(), nodeConfigData.data());
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
auto msg = QObject::tr("parsing response failed");
|
||||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error logining in account occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error logining in account occurred");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> logout()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = Logout();
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
auto msg = QObject::tr("parsing response failed");
|
||||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
RpcResponse<QJsonObject> rpcResponse(jsonResult);
|
||||
rpcResponse.error = Utils::getRPCErrorInJson(jsonResult).value_or(RpcError());
|
||||
return rpcResponse;
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error logging out account occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error logging out account occurred");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include <QtCore>
|
||||
|
||||
namespace Backend::Accounts
|
||||
namespace Status::StatusGo::Accounts
|
||||
{
|
||||
RpcResponse<QJsonArray> generateAddresses(const QVector<QString>& paths);
|
||||
|
||||
|
@ -15,12 +15,19 @@ namespace Backend::Accounts
|
|||
RpcResponse<QJsonObject> storeDerivedAccounts(const QString& accountId, const QString& hashedPassword,
|
||||
const QVector<QString>& paths);
|
||||
|
||||
RpcResponse<QJsonObject> saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
RpcResponse<QJsonObject> storeAccount(const QString& id, const QString& hashedPassword);
|
||||
|
||||
bool saveAccountAndLogin(const QString& hashedPassword, const QJsonObject& account,
|
||||
const QJsonArray& subaccounts, const QJsonObject& settings,
|
||||
const QJsonObject& nodeConfig);
|
||||
|
||||
RpcResponse<QJsonArray> openAccounts(const QString& path);
|
||||
/// opens database and returns accounts list.
|
||||
RpcResponse<QJsonArray> openAccounts(const char* dataDirPath);
|
||||
|
||||
RpcResponse<QJsonObject> login(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large);
|
||||
RpcResponse<QJsonObject> loginWithConfig(const QString& name, const QString& keyUid, const QString& hashedPassword,
|
||||
const QString& identicon, const QString& thumbnail, const QString& large,
|
||||
const QJsonObject& nodeConfig);
|
||||
RpcResponse<QJsonObject> logout();
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/General.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Utils.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/Accounts.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Messenger/Service.h
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SignalsManager.h
|
||||
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/General.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Types.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Utils.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Accounts/Accounts.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Messenger/Service.cpp
|
||||
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SignalsManager.cpp
|
||||
)
|
|
@ -0,0 +1,37 @@
|
|||
#include "General.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "libstatus.h"
|
||||
|
||||
namespace Status::StatusGo::General
|
||||
{
|
||||
|
||||
RpcResponse<QJsonObject> initKeystore(const char* keystoreDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = InitKeystore(const_cast<char*>(keystoreDir));
|
||||
QJsonObject jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult)) {
|
||||
throw std::domain_error("parsing response failed");
|
||||
}
|
||||
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
// TODO: either use optional/smartpointers or exceptions instead of plain objects
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
// TODO: don't translate exception messages. Exceptions are for developers and should never reach users
|
||||
response.error.message = QObject::tr("an error opening accounts occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<QJsonObject>(QJsonObject());
|
||||
response.error.message = QObject::tr("an error opening accounts occurred");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace Status::StatusGo::General
|
||||
{
|
||||
|
||||
RpcResponse<QJsonObject> initKeystore(const char* keystoreDir);
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
#include "Service.h"
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
namespace Status::StatusGo::Messenger
|
||||
{
|
||||
|
||||
bool startMessenger()
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"jsonrpc", "2.0"},
|
||||
{"method", "wakuext_startMessenger"},
|
||||
{"params", QJsonArray()}
|
||||
};
|
||||
|
||||
auto callResult = Utils::callPrivateRpc<QJsonObject>(Utils::jsonToByteArray(payload));
|
||||
if(callResult.containsError())
|
||||
qWarning() << "Failed starting Messenger service. Error: " << callResult.error.message;
|
||||
return !callResult.containsError();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
namespace Status::StatusGo::Messenger
|
||||
{
|
||||
|
||||
bool startMessenger();
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
#include "SignalsManager.h"
|
||||
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "libstatus.h"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace Status::StatusGo {
|
||||
|
||||
std::map<std::string, SignalType> SignalsManager::signalMap;
|
||||
|
||||
// TODO: make me thread safe or better refactor into broadcasting mechanism
|
||||
SignalsManager* SignalsManager::instance()
|
||||
{
|
||||
static SignalsManager manager;
|
||||
return &manager;
|
||||
}
|
||||
|
||||
SignalsManager::SignalsManager()
|
||||
: QObject(nullptr)
|
||||
{
|
||||
SetSignalEventCallback((void*)&SignalsManager::signalCallback);
|
||||
|
||||
signalMap = {
|
||||
{"node.ready"s, SignalType::NodeReady},
|
||||
{"node.started"s, SignalType::NodeStarted},
|
||||
{"node.stopped"s, SignalType::NodeStopped},
|
||||
{"node.login"s, SignalType::NodeLogin},
|
||||
{"node.crashed"s, SignalType::NodeCrashed},
|
||||
|
||||
{"discovery.started"s, SignalType::DiscoveryStarted},
|
||||
{"discovery.stopped"s, SignalType::DiscoveryStopped},
|
||||
{"discovery.summary"s, SignalType::DiscoverySummary},
|
||||
|
||||
{"mailserver.changed"s, SignalType::MailserverChanged},
|
||||
{"mailserver.available"s, SignalType::MailserverAvailable},
|
||||
|
||||
{"history.request.started"s, SignalType::HistoryRequestStarted},
|
||||
{"history.request.batch.processed"s, SignalType::HistoryRequestBatchProcessed},
|
||||
{"history.request.completed"s, SignalType::HistoryRequestCompleted}
|
||||
};
|
||||
}
|
||||
|
||||
SignalsManager::~SignalsManager()
|
||||
{
|
||||
}
|
||||
|
||||
void SignalsManager::processSignal(const QString& statusSignal)
|
||||
{
|
||||
try
|
||||
{
|
||||
QJsonParseError json_error;
|
||||
const QJsonDocument signalEventDoc(QJsonDocument::fromJson(statusSignal.toUtf8(), &json_error));
|
||||
if(json_error.error != QJsonParseError::NoError)
|
||||
{
|
||||
qWarning() << "Invalid signal received";
|
||||
return;
|
||||
}
|
||||
decode(signalEventDoc.object());
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
qWarning() << "Error decoding signal, err: ", e.what();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SignalsManager::decode(const QJsonObject& signalEvent)
|
||||
{
|
||||
SignalType signalType(Unknown);
|
||||
auto signalName = signalEvent["type"].toString().toStdString();
|
||||
if(!signalMap.contains(signalName))
|
||||
{
|
||||
qWarning() << "Unknown signal received: " << signalName.c_str();
|
||||
return;
|
||||
}
|
||||
|
||||
signalType = signalMap[signalName];
|
||||
auto signalError = signalEvent["event"]["error"].toString();
|
||||
|
||||
switch(signalType)
|
||||
{
|
||||
// TODO: create extractor functions like in nim
|
||||
case NodeLogin:
|
||||
emit nodeLogin(signalError);
|
||||
break;
|
||||
case NodeReady:
|
||||
emit nodeReady(signalError);
|
||||
break;
|
||||
case NodeStarted:
|
||||
emit nodeStarted(signalError);
|
||||
break;
|
||||
case NodeStopped:
|
||||
emit nodeStopped(signalError);
|
||||
break;
|
||||
case NodeCrashed:
|
||||
qWarning() << "node.crashed, error: " << signalError;
|
||||
emit nodeCrashed(signalError);
|
||||
break;
|
||||
case DiscoveryStarted:
|
||||
emit discoveryStarted(signalError);
|
||||
break;
|
||||
case DiscoveryStopped:
|
||||
emit discoveryStopped(signalError);
|
||||
break;
|
||||
case DiscoverySummary:
|
||||
emit discoverySummary(signalEvent["event"].toArray().count(), signalError);
|
||||
break;
|
||||
case MailserverChanged:
|
||||
emit mailserverChanged(signalError);
|
||||
break;
|
||||
case MailserverAvailable:
|
||||
emit mailserverAvailable(signalError);
|
||||
break;
|
||||
case HistoryRequestStarted:
|
||||
emit historyRequestStarted(signalError);
|
||||
break;
|
||||
case HistoryRequestBatchProcessed:
|
||||
emit historyRequestBatchProcessed(signalError);
|
||||
break;
|
||||
case HistoryRequestCompleted:
|
||||
emit historyRequestCompleted(signalError);
|
||||
break;
|
||||
case Unknown: assert(false); break;
|
||||
}
|
||||
}
|
||||
|
||||
void SignalsManager::signalCallback(const char* data)
|
||||
{
|
||||
// TODO: overkill, use some kind of message broker
|
||||
auto dataStrPtr = std::make_shared<QString>(data);
|
||||
QFuture<void> future = QtConcurrent::run([dataStrPtr](){
|
||||
SignalsManager::instance()->processSignal(*dataStrPtr);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace Status::StatusGo {
|
||||
|
||||
enum SignalType
|
||||
{
|
||||
Unknown,
|
||||
NodeLogin,
|
||||
NodeReady,
|
||||
NodeStarted,
|
||||
NodeStopped,
|
||||
NodeCrashed,
|
||||
|
||||
DiscoveryStarted,
|
||||
DiscoveryStopped,
|
||||
DiscoverySummary,
|
||||
|
||||
MailserverChanged,
|
||||
MailserverAvailable,
|
||||
|
||||
HistoryRequestStarted,
|
||||
HistoryRequestBatchProcessed,
|
||||
HistoryRequestCompleted
|
||||
};
|
||||
|
||||
/*!
|
||||
\todo refactor into a message broker helper to be used by specific service APIs to deliver signals
|
||||
as part of the specific StatusGoAPI service
|
||||
\todo address thread safety
|
||||
*/
|
||||
class SignalsManager final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
static SignalsManager* instance();
|
||||
|
||||
void processSignal(const QString& ev);
|
||||
|
||||
signals:
|
||||
void nodeReady(const QString& error);
|
||||
void nodeStarted(const QString& error);
|
||||
void nodeStopped(const QString& error);
|
||||
void nodeLogin(const QString& error);
|
||||
void nodeCrashed(const QString& error);
|
||||
|
||||
void discoveryStarted(const QString& error);
|
||||
void discoveryStopped(const QString& error);
|
||||
void discoverySummary(size_t nodeCount, const QString& error);
|
||||
|
||||
void mailserverChanged(const QString& error);
|
||||
void mailserverAvailable(const QString& error);
|
||||
|
||||
void historyRequestStarted(const QString& error);
|
||||
void historyRequestBatchProcessed(const QString& error);
|
||||
void historyRequestCompleted(const QString& error);
|
||||
private:
|
||||
explicit SignalsManager();
|
||||
~SignalsManager();
|
||||
|
||||
private:
|
||||
static std::map<std::string, SignalType> signalMap;
|
||||
static void signalCallback(const char* data);
|
||||
void decode(const QJsonObject& signalEvent);
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Status::StatusGo
|
||||
{
|
||||
|
||||
// Used in calls where we don't have version and id returned from `status-go`
|
||||
|
||||
struct RpcError
|
||||
{
|
||||
// TODO: enum instead for known errors?
|
||||
static constexpr int NoError = -1;
|
||||
int code = NoError;
|
||||
QString message;
|
||||
|
||||
static constexpr auto UnknownVersion{""};
|
||||
static constexpr int UnknownId = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct RpcResponse
|
||||
{
|
||||
T result;
|
||||
QString jsonRpcVersion;
|
||||
int id;
|
||||
RpcError error;
|
||||
|
||||
explicit
|
||||
RpcResponse(T result, QString version = RpcError::UnknownVersion, int id = RpcError::UnknownId,
|
||||
RpcError error = RpcError())
|
||||
: result(std::move(result))
|
||||
, jsonRpcVersion(std::move(version))
|
||||
, id(id)
|
||||
, error(std::move(error))
|
||||
{ }
|
||||
|
||||
bool containsError() const {
|
||||
return !error.message.isEmpty() || error.code != RpcError::NoError;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#include "Utils.h"
|
||||
|
||||
#include "libstatus.h"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
namespace Status::StatusGo::Utils
|
||||
{
|
||||
|
||||
QJsonArray toJsonArray(const QVector<QString>& value)
|
||||
{
|
||||
QJsonArray array;
|
||||
for(auto& v : value)
|
||||
array << v;
|
||||
return array;
|
||||
}
|
||||
|
||||
const char* statusgoCallPrivateRPC(const char* inputJSON) {
|
||||
// Evil done here! status-go API doesn't follow the proper so we adapt
|
||||
return CallPrivateRPC(const_cast<char*>(inputJSON));
|
||||
}
|
||||
|
||||
QString hashString(const QString &str)
|
||||
{
|
||||
return "0x" + QString::fromUtf8(QCryptographicHash::hash(str.toUtf8(),
|
||||
QCryptographicHash::Keccak_256).toHex());
|
||||
}
|
||||
|
||||
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json)
|
||||
{
|
||||
auto errVal = json[Param::Error];
|
||||
if (errVal.isNull() || errVal.isUndefined())
|
||||
return std::nullopt;
|
||||
if(errVal.isString() && errVal.toString().length() == 0)
|
||||
return std::nullopt;
|
||||
|
||||
RpcError response;
|
||||
auto errObj = json[Param::Id].toObject();
|
||||
if (!errObj[Param::ErrorCode].isNull() && !errObj[Param::ErrorCode].isUndefined())
|
||||
response.code = errObj[Param::ErrorCode].toInt();
|
||||
if (!errObj[Param::ErrorMessage].isNull() && !errObj[Param::ErrorMessage].isUndefined())
|
||||
response.message = errObj[Param::ErrorMessage].toString();
|
||||
else
|
||||
response.message = errVal.toString();
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
|
||||
namespace Status::StatusGo::Utils
|
||||
{
|
||||
|
||||
namespace Param {
|
||||
static constexpr auto Id{"id"};
|
||||
static constexpr auto JsonRpc{"jsonrpc"};
|
||||
static constexpr auto Result{"result"};
|
||||
static constexpr auto Error{"error"};
|
||||
static constexpr auto ErrorMessage{"message"};
|
||||
static constexpr auto ErrorCode{"code"};
|
||||
}
|
||||
|
||||
template<class T>
|
||||
QByteArray jsonToByteArray(const T& json)
|
||||
{
|
||||
static_assert(std::is_same_v<T, QJsonObject> ||
|
||||
std::is_same_v<T, QJsonArray>, "Wrong Json type. Supported: Object, Array");
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QJsonArray toJsonArray(const QVector<QString>& value);
|
||||
|
||||
/// Check if json contains a standard status-go error and
|
||||
std::optional<RpcError> getRPCErrorInJson(const QJsonObject& json);
|
||||
|
||||
template<class T>
|
||||
bool checkReceivedResponse(const QString& response, T& json)
|
||||
{
|
||||
QJsonParseError error;
|
||||
auto jsonDocument = QJsonDocument::fromJson(response.toUtf8(), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
return false;
|
||||
|
||||
if constexpr (std::is_same_v<T, QJsonObject>)
|
||||
{
|
||||
json = jsonDocument.object();
|
||||
return true;
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||
{
|
||||
json = jsonDocument.array();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Clarify scope. The assumption done here are valid only for status-go::CallPrivateRPC API.
|
||||
template<class T>
|
||||
RpcResponse<T> buildPrivateRPCResponse(const T& json)
|
||||
{
|
||||
auto response = RpcResponse<T>(T());
|
||||
|
||||
if constexpr (std::is_same_v<T, QJsonObject>)
|
||||
{
|
||||
if (!json[Param::Id].isNull() && !json[Param::Id].isUndefined())
|
||||
response.id = json[Param::Id].toInt();
|
||||
|
||||
if (!json[Param::JsonRpc].isNull() && !json[Param::JsonRpc].isUndefined())
|
||||
response.jsonRpcVersion = json[Param::JsonRpc].toString();
|
||||
|
||||
response.error = getRPCErrorInJson(json).value_or(RpcError());
|
||||
|
||||
if (!json[Param::Result].isNull() && !json[Param::Result].isUndefined())
|
||||
response.result = json[Param::Result].toObject();
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||
{
|
||||
response.result = json;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
const char* statusgoCallPrivateRPC(const char* inputJSON);
|
||||
|
||||
template<class T>
|
||||
RpcResponse<T> callPrivateRpc(const QByteArray& payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = statusgoCallPrivateRPC(payload.data());
|
||||
T jsonResult;
|
||||
if(!Utils::checkReceivedResponse(result, jsonResult))
|
||||
{
|
||||
auto msg = QObject::tr("parsing response failed");
|
||||
throw std::domain_error(msg.toStdString());
|
||||
}
|
||||
|
||||
return Utils::buildPrivateRPCResponse(jsonResult);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
auto response = RpcResponse<T>(T());
|
||||
response.error.message = QObject::tr("an error executing rpc call occurred, msg: %1").arg(e.what());
|
||||
return response;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
auto response = RpcResponse<T>(T());
|
||||
response.error.message = QObject::tr("an error executing rpc call");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
QString hashString(const QString &str);
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ qt6_add_qml_module(${PROJECT_NAME}
|
|||
OUTPUT_DIRECTORY
|
||||
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status
|
||||
)
|
||||
add_library(Status::StatusQ ALIAS StatusQ)
|
||||
|
||||
add_subdirectory(qml/Status/Containers)
|
||||
add_subdirectory(qml/Status/Controls)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.0
|
||||
import QtQuick
|
||||
|
||||
/*!
|
||||
Template for a NavigationBar square button
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick
|
||||
|
||||
/*!
|
||||
Helper functions for colors and sizes transformations
|
||||
|
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5)
|
|||
|
||||
project(TestStatusQ LANGUAGES CXX)
|
||||
|
||||
enable_testing()
|
||||
enable_testing(true)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
|
@ -10,7 +10,7 @@ find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml QuickTest REQUIRED)
|
|||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
URI StatusQ.TestHelpers
|
||||
URI Status.TestHelpers
|
||||
VERSION 1.0
|
||||
# TODO: temporary until we make qt_target_qml_sources work
|
||||
QML_FILES
|
||||
|
@ -29,7 +29,6 @@ add_test(NAME ${PROJECT_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMM
|
|||
add_custom_target("Run_${PROJECT_NAME}" COMMAND ${CMAKE_CTEST_COMMAND} --test-dir "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
add_dependencies("Run_${PROJECT_NAME}" ${PROJECT_NAME})
|
||||
|
||||
# TODO: move this to a test helpers library
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
|
|
|
@ -14,9 +14,10 @@ find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
|
|||
qt6_standard_project_setup()
|
||||
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
URI Status.TestHelpers
|
||||
URI Status.${PROJECT_NAME}
|
||||
VERSION 1.0
|
||||
)
|
||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
|
@ -43,5 +44,3 @@ target_link_libraries(${PROJECT_NAME}
|
|||
PRIVATE
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
add_library(Status::TestHelpers ALIAS TestHelpers)
|
||||
|
|
|
@ -12,25 +12,25 @@ fs::path createTestFolder(const std::string& testName)
|
|||
auto tm = *std::localtime(&t);
|
||||
std::ostringstream timeOss;
|
||||
timeOss << std::put_time(&tm, "%d-%m-%Y_%H-%M-%S");
|
||||
auto tmpPath = fs::path(testing::TempDir())/(testName + "-" + timeOss.str());
|
||||
fs::create_directories(tmpPath);
|
||||
return tmpPath;
|
||||
return fs::path(testing::TempDir())/"StatusTests"/(testName + "-" + timeOss.str());
|
||||
}
|
||||
|
||||
AutoCleanTempTestDir::AutoCleanTempTestDir(const std::string &testName)
|
||||
AutoCleanTempTestDir::AutoCleanTempTestDir(const std::string &testName, bool createDir)
|
||||
: m_testFolder(createTestFolder(testName))
|
||||
{
|
||||
if(createDir)
|
||||
fs::create_directories(m_testFolder);
|
||||
}
|
||||
|
||||
AutoCleanTempTestDir::~AutoCleanTempTestDir()
|
||||
{
|
||||
// TODO: Consider making this concurrent safe and cleanup the root folder as well if empty
|
||||
fs::remove_all(m_testFolder);
|
||||
}
|
||||
|
||||
const std::filesystem::path& AutoCleanTempTestDir::testFolder()
|
||||
const std::filesystem::path& AutoCleanTempTestDir::tempFolder()
|
||||
{
|
||||
return m_testFolder;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ class AutoCleanTempTestDir {
|
|||
public:
|
||||
/// Creates a temporary folder to be used in tests. The folder content's will
|
||||
/// be removed when out of scope
|
||||
explicit AutoCleanTempTestDir(const std::string& testName);
|
||||
explicit AutoCleanTempTestDir(const std::string& testName, bool createDir = true);
|
||||
~AutoCleanTempTestDir();
|
||||
|
||||
const std::filesystem::path& testFolder();
|
||||
const std::filesystem::path& tempFolder();
|
||||
|
||||
private:
|
||||
const std::filesystem::path m_testFolder;
|
||||
|
|
|
@ -22,6 +22,8 @@ namespace Status::Testing {
|
|||
class MonitorQtOutput : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QML_ELEMENT
|
||||
public:
|
||||
MonitorQtOutput();
|
||||
~MonitorQtOutput();
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# Temporary library not to duplicate resources
|
||||
# TODO: refactor it when switching to C++ code
|
||||
#
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
project(StatusGoConfig
|
||||
VERSION 0.1.0
|
||||
LANGUAGES CXX)
|
||||
|
||||
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
|
||||
|
||||
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Qml REQUIRED)
|
||||
qt6_standard_project_setup()
|
||||
|
||||
# Resource path ":/Status/StaticConfig/<file>"
|
||||
qt6_add_qml_module(${PROJECT_NAME}
|
||||
URI Status.StaticConfig
|
||||
VERSION 1.0
|
||||
RESOURCES
|
||||
default-networks.json
|
||||
fleets.json
|
||||
infura_key
|
||||
node-config.json
|
||||
RESOURCE_PREFIX ""
|
||||
)
|
||||
add_library(Status::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PRIVATE
|
||||
Qt6::Qml
|
||||
)
|
|
@ -1,27 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(Status DESCRIPTION "Status project" LANGUAGES CXX)
|
||||
|
||||
include("${CMAKE_SOURCE_DIR}/cmake/project-config.cmake")
|
||||
include("${CMAKE_SOURCE_DIR}/cmake/conan.cmake")
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS
|
||||
Core
|
||||
Gui
|
||||
Quick
|
||||
Widgets
|
||||
Qml
|
||||
Quick
|
||||
QuickControls2
|
||||
QuickTemplates2
|
||||
Multimedia
|
||||
Concurrent
|
||||
LinguistTools)
|
||||
|
||||
# The following should be moved to conan.
|
||||
# But so far we're just adding libs from the vendor folder this way.
|
||||
# statusgo lib is registered globaly - /vendor/status-go/CMakeLists.txt
|
||||
SET(STATUS_GO_LIB statusgo_shared)
|
||||
add_subdirectory(${CMAKE_SOURCE_DIR}/../vendor/status-go bin/status-go)
|
||||
|
||||
add_subdirectory(projects)
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Release",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "RelWithDebInfo",
|
||||
"buildRoot": "${projectDir}\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"variables": []
|
||||
},
|
||||
{
|
||||
"name": "Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
add_executable(${PROJECT_NAME})
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::Quick
|
||||
Qt5::Qml
|
||||
Qt5::Quick
|
||||
Qt5::QuickControls2
|
||||
Qt5::QuickTemplates2
|
||||
Qt5::Multimedia
|
||||
Qt5::Concurrent
|
||||
Status.Services
|
||||
${STATUS_GO_LIB}
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"*.h"
|
||||
"*.cpp"
|
||||
${STATUS_RCC}
|
||||
${STATUS_RESOURCES_QRC}
|
||||
${STATUS_QRC}
|
||||
)
|
|
@ -1,31 +0,0 @@
|
|||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE)
|
||||
|
||||
find_library(FOUNDATION_FRAMEWORK Foundation)
|
||||
find_library(IO_KIT_FRAMEWORK IOKit)
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::Quick
|
||||
Qt5::Qml
|
||||
Qt5::Quick
|
||||
Qt5::QuickControls2
|
||||
Qt5::QuickTemplates2
|
||||
Qt5::Multimedia
|
||||
Qt5::Concurrent
|
||||
${FOUNDATION_FRAMEWORK}
|
||||
${IO_KIT_FRAMEWORK}
|
||||
Status.Services
|
||||
${STATUS_GO_LIB}
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"*.h"
|
||||
"*.cpp"
|
||||
"*.mm"
|
||||
${STATUS_RCC}
|
||||
${STATUS_RESOURCES_QRC}
|
||||
${STATUS_QRC}
|
||||
)
|
|
@ -1,25 +0,0 @@
|
|||
add_executable(${PROJECT_NAME} WIN32)
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::Quick
|
||||
Qt5::Qml
|
||||
Qt5::Quick
|
||||
Qt5::QuickControls2
|
||||
Qt5::QuickTemplates2
|
||||
Qt5::Multimedia
|
||||
Qt5::Concurrent
|
||||
Status.Services
|
||||
${STATUS_GO_LIB}
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"*.h"
|
||||
"*.cpp"
|
||||
${STATUS_RCC}
|
||||
${STATUS_RESOURCES_QRC}
|
||||
${STATUS_QRC}
|
||||
)
|
|
@ -1,27 +0,0 @@
|
|||
set(CONAN_WORKING_DIR ${CMAKE_BINARY_DIR}/conan)
|
||||
|
||||
if (EXISTS ${CONAN_WORKING_DIR})
|
||||
file(REMOVE_RECURSE ${CONAN_WORKING_DIR})
|
||||
endif ()
|
||||
|
||||
file(MAKE_DIRECTORY ${CONAN_WORKING_DIR})
|
||||
|
||||
if (${CMAKE_BUILD_TYPE} STREQUAL Debug)
|
||||
set(CONAN_PROFILE ${PROJECT_SOURCE_DIR}/conan-debug-profile)
|
||||
else ()
|
||||
set(CONAN_PROFILE ${PROJECT_SOURCE_DIR}/conan-release-profile)
|
||||
endif ()
|
||||
|
||||
execute_process(
|
||||
COMMAND conan install ${PROJECT_SOURCE_DIR} -pr=${CONAN_PROFILE}
|
||||
WORKING_DIRECTORY ${CONAN_WORKING_DIR}
|
||||
RESULT_VARIABLE CONAN_RESULT
|
||||
)
|
||||
|
||||
if (NOT ${CONAN_RESULT} EQUAL 0)
|
||||
message(FATAL_ERROR "Conan failed: ${CONAN_RESULT}.")
|
||||
endif ()
|
||||
|
||||
include(${CONAN_WORKING_DIR}/conanbuildinfo.cmake)
|
||||
|
||||
conan_basic_setup(KEEP_RPATHS)
|
|
@ -1,19 +0,0 @@
|
|||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
#set(CMAKE_AUTORCC ON) This is disabled because of `/ui/generate-rcc.go` script usage for generating .qrc and cmake command for .rcc
|
||||
|
||||
# Set this to TRUE if you want to create .ts files and translations.qrc
|
||||
set(UPDATE_TRANSLATIONS FALSE)
|
||||
|
||||
if ($ENV{QTDIR} LESS_EQUAL "")
|
||||
message(FATAL_ERROR "Please set the path to your Qt dir as `QTDIR` variable in your ENV. Example: QTDIR=/Qt/Qt5.14.2/5.14.2/clang_64")
|
||||
endif()
|
||||
|
||||
message("Located QtDir: " $ENV{QTDIR})
|
||||
set(CMAKE_PREFIX_PATH $ENV{QTDIR})
|
||||
|
||||
add_definitions(-DSTATUS_SOURCE_DIR="${CMAKE_SOURCE_DIR}")
|
||||
add_definitions(-DSTATUS_DEVELOPMENT=true)
|
|
@ -1,10 +0,0 @@
|
|||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core
|
||||
Status.Backend
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"*.h"
|
||||
"*.cpp"
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
find_library(FOUNDATION_FRAMEWORK Foundation)
|
||||
find_library(IO_KIT_FRAMEWORK IOKit)
|
||||
find_library(SECURITY_FRAMEWORK Security)
|
||||
find_library(CORE_SERVICES_FRAMEWORK CoreServices)
|
||||
find_library(LOCAL_AUTHENTICATION_FRAMEWORK LocalAuthentication)
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
Qt5::Core
|
||||
${FOUNDATION_FRAMEWORK}
|
||||
${IO_KIT_FRAMEWORK}
|
||||
${SECURITY_FRAMEWORK}
|
||||
${CORE_SERVICES_FRAMEWORK}
|
||||
${LOCAL_AUTHENTICATION_FRAMEWORK}
|
||||
Status.Backend
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SOURCES
|
||||
"*.h"
|
||||
"*.cpp"
|
||||
"*.mm"
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue